@dnax/ui 0.0.26 → 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.
@@ -0,0 +1,221 @@
1
+ // v-touch-hold — directive : détecte l'appui long (hold) sur un élément,
2
+ // tactile ET souris (modifier .mouse), équivalent Quasar
3
+ // https://quasar.dev/vue-directives/touch-hold
4
+ //
5
+ // Usage :
6
+ // <div v-touch-hold="handler" /> → 600ms, sensibilité 5/7px
7
+ // <div v-touch-hold.mouse="{ time: 400, sensitivity: 6 }" />
8
+ // <div v-touch-hold="'400:8:10'" /> → 400ms, 8px tactile, 10px souris
9
+ // <div v-touch-hold="350" />
10
+ // modifiers : mouse (inclut la souris), capture / mouseCapture, stop, prevent,
11
+ // passive
12
+ //
13
+ // Détails du handler (compatible Quasar) :
14
+ // { evt, position: { left, top }, duration }
15
+ // Après déclenchement, le geste est « consommé » : le click/touchend qui suit
16
+ // est supprimé (un appui long ne déclenche pas aussi un clic).
17
+
18
+ export interface TouchHoldDetails {
19
+ evt: Event
20
+ position: { left: number; top: number }
21
+ duration: number
22
+ }
23
+
24
+ export type TouchHoldHandler = (details: TouchHoldDetails) => void
25
+
26
+ const DEFAULTS = { time: 600, touchSensitivity: 5, mouseSensitivity: 7 }
27
+
28
+ interface HoldOptions {
29
+ handler: TouchHoldHandler
30
+ time: number
31
+ touchSensitivity: number
32
+ mouseSensitivity: number
33
+ mouse: boolean
34
+ stop: boolean
35
+ prevent: boolean
36
+ capture: boolean
37
+ passive: boolean
38
+ }
39
+
40
+ type QElement = HTMLElement & { __qTouchHoldCleanup?: () => void }
41
+
42
+ function parseValue(value: unknown): Partial<typeof DEFAULTS> {
43
+ if (typeof value === "number") return { time: value }
44
+ if (typeof value === "string") {
45
+ const [t, touch, mouse] = value.split(":")
46
+ const out: Partial<typeof DEFAULTS> = {}
47
+ const num = Number(t)
48
+ if (Number.isFinite(num)) out.time = num
49
+ if (touch !== undefined) {
50
+ const n = Number(touch)
51
+ if (Number.isFinite(n)) out.touchSensitivity = n
52
+ }
53
+ if (mouse !== undefined) {
54
+ const n = Number(mouse)
55
+ if (Number.isFinite(n)) out.mouseSensitivity = n
56
+ }
57
+ return out
58
+ }
59
+ if (Array.isArray(value)) {
60
+ const out: Partial<typeof DEFAULTS> = {}
61
+ const t = Number(value[0])
62
+ if (Number.isFinite(t)) out.time = t
63
+ const touch = Number(value[1])
64
+ if (Number.isFinite(touch)) out.touchSensitivity = touch
65
+ const mouse = Number(value[2])
66
+ if (Number.isFinite(mouse)) out.mouseSensitivity = mouse
67
+ return out
68
+ }
69
+ if (value && typeof value === "object") {
70
+ const o = value as Record<string, unknown>
71
+ const out: Partial<typeof DEFAULTS> = {}
72
+ const t = Number(o.time ?? o.duration)
73
+ if (Number.isFinite(t)) out.time = t
74
+ const touch = Number(o.sensitivity ?? o.touchSensitivity)
75
+ if (Number.isFinite(touch)) out.touchSensitivity = touch
76
+ const mouse = Number(o.mouseSensitivity)
77
+ if (Number.isFinite(mouse)) out.mouseSensitivity = mouse
78
+ return out
79
+ }
80
+ return {}
81
+ }
82
+
83
+ export const vTouchHold = {
84
+ mounted(el: HTMLElement, binding: { value?: unknown; modifiers?: Record<string, boolean> }) {
85
+ const handler = binding.value
86
+ if (typeof handler !== "function") {
87
+ console.warn("[v-touch-hold] value doit être une fonction (handler)")
88
+ return
89
+ }
90
+ const m = binding.modifiers ?? {}
91
+ const parsed = parseValue(binding.value)
92
+ const options: HoldOptions = {
93
+ handler: handler as TouchHoldHandler,
94
+ time: parsed.time ?? DEFAULTS.time,
95
+ touchSensitivity: parsed.touchSensitivity ?? DEFAULTS.touchSensitivity,
96
+ mouseSensitivity: parsed.mouseSensitivity ?? DEFAULTS.mouseSensitivity,
97
+ mouse: !!m.mouse || !!m.mouseCapture,
98
+ stop: !!m.stop,
99
+ prevent: !!m.prevent,
100
+ capture: !!m.capture || !!m.mouseCapture,
101
+ passive: !!m.passive,
102
+ }
103
+
104
+ // Éviter scroll / context menu / sélection pendant l'appui
105
+ el.style.touchAction = "none"
106
+ el.style.userSelect = "none"
107
+ ;(el as HTMLElement).style.webkitUserSelect = "none"
108
+
109
+ let timer: ReturnType<typeof setTimeout> | undefined
110
+ let pending = false
111
+ let consumed = false
112
+ let startX = 0
113
+ let startY = 0
114
+ let lastEvt: PointerEvent | null = null
115
+ let startTime = 0
116
+
117
+ const clearTimer = () => {
118
+ if (timer) clearTimeout(timer)
119
+ timer = undefined
120
+ }
121
+
122
+ const cancel = () => {
123
+ clearTimer()
124
+ pending = false
125
+ }
126
+
127
+ const finish = () => {
128
+ clearTimer()
129
+ if (!pending) return
130
+ pending = false
131
+ consumed = true
132
+ const duration = Math.round(performance.now() - startTime)
133
+ const evt = lastEvt
134
+ if (evt) {
135
+ options.handler({
136
+ evt,
137
+ position: { left: evt.clientX, top: evt.clientY },
138
+ duration: options.time || duration,
139
+ })
140
+ }
141
+ // Consomme la fin du geste : pas de click/touchend après un hold
142
+ const onUp = (e: Event) => {
143
+ if (options.prevent) e.preventDefault()
144
+ if (options.stop) e.stopPropagation()
145
+ window.removeEventListener("pointerup", onUp, true)
146
+ window.removeEventListener("pointercancel", onCancelAfter, true)
147
+ window.removeEventListener("click", onClick, true)
148
+ window.removeEventListener("touchend", onUp, true)
149
+ }
150
+ const onClick = (e: Event) => {
151
+ e.preventDefault()
152
+ e.stopPropagation()
153
+ window.removeEventListener("click", onClick, true)
154
+ window.removeEventListener("pointerup", onUp, true)
155
+ window.removeEventListener("touchend", onUp, true)
156
+ }
157
+ const onCancelAfter = () => {
158
+ window.removeEventListener("pointerup", onUp, true)
159
+ window.removeEventListener("pointercancel", onCancelAfter, true)
160
+ window.removeEventListener("click", onClick, true)
161
+ }
162
+ window.addEventListener("pointerup", onUp, true)
163
+ window.addEventListener("pointercancel", onCancelAfter, true)
164
+ window.addEventListener("touchend", onUp, true)
165
+ window.addEventListener("click", onClick, true)
166
+ }
167
+
168
+ const onPointerDown = (e: PointerEvent) => {
169
+ if (pending || consumed) return
170
+ if (e.pointerType === "mouse" && !options.mouse) return
171
+ if (options.prevent && !options.passive) e.preventDefault()
172
+ pending = true
173
+ startX = e.clientX
174
+ startY = e.clientY
175
+ startTime = performance.now()
176
+ lastEvt = e
177
+ const sensitivity = e.pointerType === "mouse" ? options.mouseSensitivity : options.touchSensitivity
178
+ timer = setTimeout(finish, options.time)
179
+
180
+ const onMove = (ev: PointerEvent) => {
181
+ if (!pending) return
182
+ if (Math.abs(ev.clientX - startX) > sensitivity || Math.abs(ev.clientY - startY) > sensitivity) {
183
+ cancel()
184
+ cleanup()
185
+ }
186
+ }
187
+ const onUpEarly = () => cancel()
188
+ const cleanup = () => {
189
+ el.removeEventListener("pointermove", onMove)
190
+ el.removeEventListener("pointerup", onUpEarly)
191
+ el.removeEventListener("pointercancel", onUpEarly)
192
+ }
193
+ el.addEventListener("pointermove", onMove, { passive: options.passive })
194
+ el.addEventListener("pointerup", onUpEarly)
195
+ el.addEventListener("pointercancel", onUpEarly)
196
+ ;(el as any).__qHoldCleanupEarly = cleanup
197
+ }
198
+
199
+ const onContext = (e: Event) => {
200
+ if (pending || consumed) e.preventDefault()
201
+ }
202
+
203
+ const capture = options.capture
204
+ el.addEventListener("pointerdown", onPointerDown, { capture, passive: options.passive })
205
+ el.addEventListener("contextmenu", onContext, { capture })
206
+
207
+ ;(el as QElement).__qTouchHoldCleanup = () => {
208
+ el.removeEventListener("pointerdown", onPointerDown, { capture } as any)
209
+ el.removeEventListener("contextmenu", onContext, { capture } as any)
210
+ ;(el as any).__qHoldCleanupEarly?.()
211
+ clearTimer()
212
+ el.style.touchAction = ""
213
+ el.style.userSelect = ""
214
+ el.style.webkitUserSelect = ""
215
+ }
216
+ },
217
+ unmounted(el: HTMLElement) {
218
+ ;(el as QElement).__qTouchHoldCleanup?.()
219
+ delete (el as QElement).__qTouchHoldCleanup
220
+ },
221
+ }
@@ -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
+ }