UNPKG

just-event.js

Version:

Modern, lightweight event utility with jQuery-like API.

372 lines (345 loc) 13.9 kB
define((function () { 'use strict'; function _classPrivateFieldLooseBase(e, t) { if (!{}.hasOwnProperty.call(e, t)) throw new TypeError("attempted to use private field on non-instance"); return e; } var id = 0; function _classPrivateFieldLooseKey(e) { return "__private_" + id++ + "_" + e; } /** Returns the object type of the given payload */ function getType(payload) { return Object.prototype.toString.call(payload).slice(8, -1); } /** Returns whether the payload is an array */ function isArray(payload) { return getType(payload) === 'Array'; } /** Returns whether the payload is a string */ function isString(payload) { return getType(payload) === 'String'; } /** Returns whether the payload is a function (regular or async) */ function isFunction(payload) { return typeof payload === 'function'; } /** * Does a generic check to check that the given payload is of a given type. In cases like Number, it * will return true for NaN as NaN is a Number (thanks javascript!); It will, however, differentiate * between object and null * * @throws {TypeError} Will throw type error if type is an invalid type */ function isType(payload, type) { if (!(type instanceof Function)) { throw new TypeError('Type must be a function'); } if (!Object.prototype.hasOwnProperty.call(type, 'prototype')) { throw new TypeError('Type is not a class'); } // Classes usually have names (as functions usually have names) const name = type.name; return getType(payload) === name || Boolean(payload && payload.constructor === type); } function isInstanceOf(value, classOrClassName) { if (typeof classOrClassName === 'function') { for (let p = value; p; p = Object.getPrototypeOf(p)) { if (isType(p, classOrClassName)) { return true; } } return false; } else { for (let p = value; p; p = Object.getPrototypeOf(p)) { if (getType(p) === classOrClassName) { return true; } } return false; } } var Utils = { // 处理传入的参数 normalizeElements(selector) { if (isString(selector)) { return Array.from(document.querySelectorAll(selector)); } if (this.isDomElement(selector)) { return [selector]; } // 如果本身传递的就是Nodelist if (isInstanceOf(selector, "NodeList") || isArray(selector)) { return Array.from(selector).filter(this.isDomElement); } // 无效输入:返回空数组 console.warn("Invalid selector:", selector); return []; }, // 特殊的对象 isDomElement(el) { return isInstanceOf(el, "HTMLElement") || isInstanceOf(el, "Document") || isInstanceOf(el, "Window"); }, closestUntil(el, selector, stopElm) { if (!el || !selector) return null; while (el) { if (el.matches(selector)) return el; if (el === stopElm || el === document.body) break; el = el.parentElement; } return null; } }; const byteToHex = []; for (let i = 0; i < 256; ++i) { byteToHex.push((i + 0x100).toString(16).slice(1)); } function unsafeStringify(arr, offset = 0) { return (byteToHex[arr[offset + 0]] + byteToHex[arr[offset + 1]] + byteToHex[arr[offset + 2]] + byteToHex[arr[offset + 3]] + '-' + byteToHex[arr[offset + 4]] + byteToHex[arr[offset + 5]] + '-' + byteToHex[arr[offset + 6]] + byteToHex[arr[offset + 7]] + '-' + byteToHex[arr[offset + 8]] + byteToHex[arr[offset + 9]] + '-' + byteToHex[arr[offset + 10]] + byteToHex[arr[offset + 11]] + byteToHex[arr[offset + 12]] + byteToHex[arr[offset + 13]] + byteToHex[arr[offset + 14]] + byteToHex[arr[offset + 15]]).toLowerCase(); } let getRandomValues; const rnds8 = new Uint8Array(16); function rng() { if (!getRandomValues) { if (typeof crypto === 'undefined' || !crypto.getRandomValues) { throw new Error('crypto.getRandomValues() not supported. See https://github.com/uuidjs/uuid#getrandomvalues-not-supported'); } getRandomValues = crypto.getRandomValues.bind(crypto); } return getRandomValues(rnds8); } const randomUUID = typeof crypto !== 'undefined' && crypto.randomUUID && crypto.randomUUID.bind(crypto); var native = { randomUUID }; function v4(options, buf, offset) { if (native.randomUUID && true && !options) { return native.randomUUID(); } options = options || {}; const rnds = options.random ?? options.rng?.() ?? rng(); if (rnds.length < 16) { throw new Error('Random bytes length must be >= 16'); } rnds[6] = (rnds[6] & 0x0f) | 0x40; rnds[8] = (rnds[8] & 0x3f) | 0x80; return unsafeStringify(rnds); } // 用于存储所有事件监听器 const listenersMap = new Map(); var _namespaceSeparator = /*#__PURE__*/_classPrivateFieldLooseKey("namespaceSeparator"); var _eventNameKey = /*#__PURE__*/_classPrivateFieldLooseKey("eventNameKey"); var _getEventKey = /*#__PURE__*/_classPrivateFieldLooseKey("getEventKey"); var _getRealEventType = /*#__PURE__*/_classPrivateFieldLooseKey("getRealEventType"); var _getEvents = /*#__PURE__*/_classPrivateFieldLooseKey("getEvents"); var _resolveArgs = /*#__PURE__*/_classPrivateFieldLooseKey("resolveArgs"); var _clean = /*#__PURE__*/_classPrivateFieldLooseKey("clean"); class Event { constructor(selector) { // 清理事件 Object.defineProperty(this, _clean, { value: _clean2 }); // 验证参数是否传递了类型为函数的listener Object.defineProperty(this, _resolveArgs, { value: _resolveArgs2 }); // 获取事件数组 Object.defineProperty(this, _getEvents, { value: _getEvents2 }); // 获取事件类型 Object.defineProperty(this, _getRealEventType, { value: _getRealEventType2 }); // 为元素和事件生成唯一键 Object.defineProperty(this, _getEventKey, { value: _getEventKey2 }); // 命名空间分隔符 Object.defineProperty(this, _namespaceSeparator, { writable: true, value: "." }); // 事件名称保存的key值 Object.defineProperty(this, _eventNameKey, { writable: true, value: "__eventName" }); // 获取目标元素,可以是单个 DOM 元素或多个元素 this.elements = Utils.normalizeElements(selector); } // 为事件绑定监听器,支持委托 on(eventNames, selectorOrListener, listener) { const resolved = _classPrivateFieldLooseBase(this, _resolveArgs)[_resolveArgs](selectorOrListener, listener); if (!resolved) return this; const { isDelegated } = resolved; listener = resolved.listener; for (const eventName of _classPrivateFieldLooseBase(this, _getEvents)[_getEvents](eventNames)) { for (const el of this.elements) { const eventKey = _classPrivateFieldLooseBase(this, _getEventKey)[_getEventKey](el, eventName); const handler = event => { // 保持和JQuery相同的行为 event.delegateTarget = el; if (isDelegated) { const matchedTarget = Utils.closestUntil(event.target, selectorOrListener, el); if (!matchedTarget) return; // 保持和JQuery相同的行为 Object.defineProperty(event, "currentTarget", { value: matchedTarget }); } // 保持和jQuery的行为相同,让trigger方法支持命名空间 const triggerEventName = event[_classPrivateFieldLooseBase(this, _eventNameKey)[_eventNameKey]]; if (triggerEventName && !eventKey.startsWith(triggerEventName)) return; listener.call(event.currentTarget, event); }; if (!listenersMap.has(eventKey)) listenersMap.set(eventKey, []); listenersMap.get(eventKey).push(handler); // 缓存监听器 el.addEventListener(_classPrivateFieldLooseBase(this, _getRealEventType)[_getRealEventType](eventName), handler); // 绑定事件 } } return this; } // 绑定一次性事件监听器 one(eventNames, selectorOrListener, listener) { const resolved = _classPrivateFieldLooseBase(this, _resolveArgs)[_resolveArgs](selectorOrListener, listener); if (!resolved) return this; const { isDelegated } = resolved; listener = resolved.listener; for (const eventName of _classPrivateFieldLooseBase(this, _getEvents)[_getEvents](eventNames)) { const onceHandler = event => { this.off(eventName); // 移除事件 listener.call(event.currentTarget, event); }; if (isDelegated) { this.on(eventName, selectorOrListener, onceHandler); } else { this.on(eventName, onceHandler); } } return this; } // 移除事件监听器 off(eventNames) { for (const eventName of _classPrivateFieldLooseBase(this, _getEvents)[_getEvents](eventNames)) { for (const el of this.elements) { // 传入的事件key const inputEventKey = _classPrivateFieldLooseBase(this, _getEventKey)[_getEventKey](el, eventName); // 带命名空间的前缀 if (eventName.startsWith(_classPrivateFieldLooseBase(this, _namespaceSeparator)[_namespaceSeparator])) { // 遍历事件映射,找出所有匹配命名空间的 key for (const [eventKey, listeners] of listenersMap) { eventKey.endsWith(inputEventKey) && _classPrivateFieldLooseBase(this, _clean)[_clean](el, eventKey.replace(inputEventKey, ""), //直接得到没有命名空间的事件名 eventKey, listeners); } } else { // 事件+命名空间 if (eventName.includes(_classPrivateFieldLooseBase(this, _namespaceSeparator)[_namespaceSeparator])) { const listeners = listenersMap.get(inputEventKey); listeners && _classPrivateFieldLooseBase(this, _clean)[_clean](el, eventName, inputEventKey, listeners); } else { // 只包含事件 for (const [eventKey, listeners] of listenersMap) { eventKey.startsWith(eventName) && _classPrivateFieldLooseBase(this, _clean)[_clean](el, eventName, eventKey, listeners); } } } } } return this; } // 触发自定义事件,支持命名空间 trigger(eventNames, detail = null) { for (const eventName of _classPrivateFieldLooseBase(this, _getEvents)[_getEvents](eventNames)) { const realEventName = _classPrivateFieldLooseBase(this, _getRealEventType)[_getRealEventType](eventName); for (const el of this.elements) { // 命名空间触发,如 click.app1 if (eventName.includes(_classPrivateFieldLooseBase(this, _namespaceSeparator)[_namespaceSeparator])) { for (const [eventKey] of listenersMap) { if (eventKey.startsWith(eventName)) { const namespaceEvent = new CustomEvent(realEventName, { bubbles: true, detail }); // 记录该属性在on方法中根据此属性判断是否调用 Object.defineProperty(namespaceEvent, _classPrivateFieldLooseBase(this, _eventNameKey)[_eventNameKey], { value: eventName, writable: false, // 不可写 configurable: false, // 不可删除或重新定义 enumerable: true // 可枚举(可选) }); // 让让支持冒泡,并传递当前的命名空间 el.dispatchEvent(namespaceEvent); } } } else { // 仅事件类型触发,如 click,走原生 dispatch el.dispatchEvent(new CustomEvent(eventName, { bubbles: true, detail })); } } } return this; } } function _getEventKey2(el, eventName) { if (!el.__eventListenerUUID) { // 如果元素没有关联 UUID,则生成一个 el.__eventListenerUUID = v4(); } return `${eventName}__${el.__eventListenerUUID}`; // 唯一键格式:事件名 + UUID } function _getRealEventType2(eventName) { // click.app1、click 都只得到真正的事件类型 click return eventName.split(_classPrivateFieldLooseBase(this, _namespaceSeparator)[_namespaceSeparator])[0]; } function _getEvents2(eventName) { return !isString(eventName) ? [] : eventName.trim().split(/\s+/); } function _resolveArgs2(selectorOrListener, listener) { const isDelegated = isString(selectorOrListener); listener = isDelegated ? listener : selectorOrListener; if (!isFunction(listener)) return null; return { isDelegated, listener }; } function _clean2(el, eventName, eventKey, listeners) { for (const listener of listeners) { el.removeEventListener(_classPrivateFieldLooseBase(this, _getRealEventType)[_getRealEventType](eventName), listener); } listenersMap.delete(eventKey); } const $ = selector => { return new Event(selector); }; return $; })); //# sourceMappingURL=just-event.amd.js.map