| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109 |
- import type { DirectiveBinding, ObjectDirective } from 'vue';
- export const START_HOLD_EVENT = 'start-hold';
- export interface StartDirectiveValue {
- threshold: number;
- fn?: () => void;
- /** 默认派发 `start-hold`;`false` 时不派发 */
- emit?: boolean;
- }
- type StartBinding = DirectiveBinding<StartDirectiveValue>;
- type Entry = {
- binding: StartBinding;
- };
- const entries = new WeakMap<HTMLElement, Entry>();
- const listenOpts: AddEventListenerOptions = { capture: true };
- function resolveMode(modifiers: StartBinding['modifiers']): 'duration' | 'distance' {
- if (modifiers.distance && modifiers.duration) {
- console.warn('[v-start] Both .duration and .distance are set; using .distance.');
- return 'distance';
- }
- if (modifiers.distance) return 'distance';
- return 'duration';
- }
- function readValue(binding: StartBinding): StartDirectiveValue | null {
- const v = binding.value;
- if (v == null || typeof v !== 'object') return null;
- if (Number.isNaN(v.threshold)) return null;
- return v;
- }
- function mount(el: HTMLElement, binding: StartBinding) {
- let down: { timeStamp: number; clientX: number; clientY: number; pointerId: number } | null = null;
- const onPointerDown = (e: PointerEvent) => {
- down = {
- timeStamp: e.timeStamp,
- clientX: e.clientX,
- clientY: e.clientY,
- pointerId: e.pointerId,
- };
- };
- const onPointerUp = (e: PointerEvent) => {
- if (!down || e.pointerId !== down.pointerId) return;
- const entry = entries.get(el);
- if (!entry) return;
- const value = readValue(entry.binding);
- const mode = resolveMode(entry.binding.modifiers);
- const start = down;
- down = null;
- if (!value) return;
- const delta =
- mode === 'duration'
- ? e.timeStamp - start.timeStamp
- : Math.hypot(e.clientX - start.clientX, e.clientY - start.clientY);
- if (delta <= value.threshold) return;
- value.fn?.();
- if (value.emit !== false) {
- const detail =
- mode === 'duration' ? { durationMs: delta } : { distancePx: delta };
- el.dispatchEvent(
- new CustomEvent(START_HOLD_EVENT, {
- detail,
- bubbles: true,
- }),
- );
- }
- };
- el.addEventListener('pointerdown', onPointerDown, listenOpts);
- el.addEventListener('pointerup', onPointerUp, listenOpts);
- entries.set(el, { binding });
- return () => {
- el.removeEventListener('pointerdown', onPointerDown, listenOpts);
- el.removeEventListener('pointerup', onPointerUp, listenOpts);
- entries.delete(el);
- };
- }
- export const startDirective: ObjectDirective<HTMLElement, StartDirectiveValue> = {
- mounted(el, binding) {
- (el as HTMLElement & { __startCleanup?: () => void }).__startCleanup = mount(el, binding);
- },
- updated(el, binding) {
- const e = entries.get(el);
- if (e) e.binding = binding;
- },
- unmounted(el) {
- (el as HTMLElement & { __startCleanup?: () => void }).__startCleanup?.();
- (el as HTMLElement & { __startCleanup?: () => void }).__startCleanup = undefined;
- },
- };
|