EventManager / createDoubleTapDetector

دفتری برای ثبت شنونده‌های رویداد که به چرخهٔ عمر گره خورده و بر AbortController سوار است، به‌همراه یک تشخیص‌دهندهٔ کوچک دو-ضربه برای حرکت‌های لمسی که به نوع اشاره‌گر کاری ندارد.

مسئله‌ای که حل می‌کند برداشتن دوبارهٔ شنونده‌ها است. removeEventListener تنها وقتی کار می‌کند که دقیقاً همان ارجاع تابع و همان گزینه‌هایی را به آن بدهی که با آن ثبت کرده بودی. اگر سر راه، دستگیره را در یک تابع پیکانی بپیچی، دیگر هرگز نمی‌توانی برش داری. آن‌گاه مؤلفه‌هایی که پی‌درپی سوار و پیاده می‌شوند، هر دور یک شنونده نشت می‌دهند. AbortController همهٔ این‌ها را به یک abort() تبدیل می‌کند.

کاربرد

درون یک وب‌کامپوننت

import { EventManager } from 'ranuts/utils';

class MyElement extends HTMLElement {
  private _events = new EventManager();

  connectedCallback() {
    this._events.on(this._input, 'input', this.handleInput).on(this, 'click', this.handleClick, { capture: true });
  }

  disconnectedCallback() {
    this._events.abort(); // همهٔ شنونده‌ها را برمی‌دارد و برای اتصال بعدی از نو آماده می‌شود
  }
}

در کد معمولی صفحه

function initSection(container: HTMLElement) {
  const scope = new EventManager();

  scope.on(input, 'input', handleSearch).delegate(container, '[data-action]', 'click', (ev, target) => {
    handleAction(target.getAttribute('data-action'));
  });

  return () => scope.abort(); // هنگام برچیدن این بخش صدایش بزن
}

API

on

یک شنونده ثبت می‌کند که به همین مدیر گره خورده است. زنجیره‌پذیر است.

پارامترها

پارامتر توضیح نوع پیش‌فرض
target هدف رویداد EventTarget الزامی
type نام رویداد string الزامی
handler تابع دستگیره EventListener الزامی
options گزینه‌های addEventListener منهای signal Omit<AddEventListenerOptions,'signal'> -

بازگشت

آرگومان توضیح نوع
this خودِ مدیر، برای زنجیره کردن EventManager

delegate

واگذاری رویداد: یک شنونده به parent می‌چسباند و handler را تنها زمانی صدا می‌زند که رویداد از فرزندی برخاسته باشد که با selector هم‌خوانی دارد. زنجیره‌پذیر است.

دستگیره خودِ رویداد اصلی و عنصر هم‌خوان را دریافت می‌کند.

scope.delegate(list, '.item', 'click', (ev, item) => {
  console.log(item.getAttribute('data-id'));
});

پارامترها

پارامتر توضیح نوع پیش‌فرض
parent عنصری که آن تک‌شنونده به آن بسته می‌شود HTMLElement الزامی
selector گزینشگری که فرزند باید با آن هم‌خوان باشد string الزامی
type نام رویداد string الزامی
handler (event, matchedElement) => void Function الزامی
options گزینه‌های addEventListener منهای signal Omit<AddEventListenerOptions,'signal'> -

بازگشت

آرگومان توضیح نوع
this خودِ مدیر، برای زنجیره کردن EventManager

abort

همهٔ شنونده‌های ثبت‌شده را برمی‌دارد و AbortController درونی را از نو می‌سازد. چند بار صدا زدنش بی‌خطر است؛ فراخوانی‌های بعدی on() و delegate() از دامنه‌ای تمیز آغاز می‌شوند.

بازگشت

بدون مقدار بازگشتی (void)

signal

همان AbortSignal زیرین، برای وقتی که بخواهی خودت آن را به addEventListener بدهی.

آرگومان توضیح نوع
signal سیگنال لغو این مدیر AbortSignal

createDoubleTapDetector

تشخیص دو-ضربه فقط از روی نمونه‌های خام (x, y, زمان). به نوع اشاره‌گر کاری ندارد، پس چه از رویدادهای Pointer تغذیه‌اش کنی چه Touch چه Mouse، یکسان کار می‌کند. برای حرکت‌های لمسی ساخته شده است: دو ضربه برای جابه‌جایی، برای بزرگ‌نمایی، برای پسندیدن. در چنین جاهایی اگر منطق آستانهٔ زمان و فاصله را در هر نقطه از نو بنویسی، به‌آسانی و به‌شکلی نامحسوس اشتباه از آب درمی‌آید: مقایسهٔ تنها یک محور، یا فراموش کردن بازنشانی پس از یک تشخیص، که آن‌گاه سه ضربهٔ سریع به‌جای یکی، دو دو-ضربهٔ هم‌پوشان شمرده می‌شود.

import { createDoubleTapDetector } from 'ranuts/utils';

const detector = createDoubleTapDetector();
el.addEventListener('pointerup', (e) => {
  if (detector.check(e.clientX, e.clientY)) seek();
});

createDoubleTapDetector(options?)

پارامترها (DoubleTapDetectorOptions)

گزینه توضیح نوع پیش‌فرض
windowMs بیشترین فاصلهٔ مجاز میان دو ضربه، بر حسب میلی‌ثانیه number 300
maxDistancePx بیشترین فاصلهٔ مجاز میان دو ضربه روی صفحه، بر حسب پیکسل number 60

DoubleTapDetector

عضو توضیح نوع
check ضربه‌ای در (x, y) را ثبت می‌کند و می‌گوید آیا با ضربهٔ بلافاصله پیش از خود یک دو-ضربه می‌سازد یا نه. با تشخیص یک دو-ضربه، ردگیری از نو آغاز می‌شود، پس ضربهٔ سریع سوم جفتی تازه را شروع می‌کند و جزئی از همان دو-ضربه به شمار نمی‌آید (x: number, y: number, now?: number) => boolean
reset آخرین ضربهٔ ثبت‌شده را از یاد می‌برد؛ وقتی حرکتی جز ضربه (مثلاً کشیدن) آغاز می‌شود صدایش بزن () => void