UNPKG

htmx-ext-hold

Version:

An htmx extension to trigger events on 'hold' (mousedown/touchstart for a duration)

144 lines (142 loc) 4.5 kB
var __defProp = Object.defineProperty; var __getOwnPropNames = Object.getOwnPropertyNames; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __hasOwnProp = Object.prototype.hasOwnProperty; var __moduleCache = /* @__PURE__ */ new WeakMap; var __toCommonJS = (from) => { var entry = __moduleCache.get(from), desc; if (entry) return entry; entry = __defProp({}, "__esModule", { value: true }); if (from && typeof from === "object" || typeof from === "function") __getOwnPropNames(from).map((key) => !__hasOwnProp.call(entry, key) && __defProp(entry, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable })); __moduleCache.set(from, entry); return entry; }; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true, configurable: true, set: (newValue) => all[name] = () => newValue }); }; // src/index.ts var exports_src = {}; __export(exports_src, { default: () => src_default }); module.exports = __toCommonJS(exports_src); var DEFAULT_HOLD_DELAY = 500; var CSS_HOLD_PROGRESS_VAR = "--hold-progress"; var HOLD_ACTIVE_CLASS = "htmx-hold-active"; var START_EVENTS = ["mousedown", "touchstart"]; var CANCEL_EVENTS = [ "mouseup", "mouseleave", "touchend", "touchcancel" ]; var HOLD_TRIGGER_PATTERN = /\bhold\b/; var DATA_PROGRESS_ATTR = "holdProgress"; var processedElements = new WeakSet; function parseDelay(triggerSpec, htmx) { const match = triggerSpec.match(/hold\s+delay:(\d+(?:\.\d+)?(?:ms|s)?)/); if (!match?.[1]) return null; return htmx.parseInterval?.(match[1]) ?? null; } function registerElement(elt, triggerSpec, htmx) { if (processedElements.has(elt)) return; const delay = parseDelay(triggerSpec, htmx) ?? DEFAULT_HOLD_DELAY; let startTime = null; let animationFrameId = null; let holdTriggered = false; let isActive = false; const clearAnimation = () => { if (animationFrameId !== null) { cancelAnimationFrame(animationFrameId); animationFrameId = null; } }; const updateProgress = () => { if (startTime === null) return; const elapsed = Date.now() - startTime; const progress = Math.min(elapsed / delay, 1); elt.style.setProperty(CSS_HOLD_PROGRESS_VAR, progress.toString()); elt.dataset[DATA_PROGRESS_ATTR] = Math.round(progress * 100).toString(); if (progress >= 1 && !holdTriggered) { holdTriggered = true; clearAnimation(); htmx.trigger?.(elt, "hold"); } else if (!holdTriggered) { animationFrameId = requestAnimationFrame(updateProgress); } }; START_EVENTS.forEach((eventName) => { elt.addEventListener(eventName, (event) => { if (isActive) return; if (event.cancelable) event.preventDefault(); isActive = true; holdTriggered = false; startTime = Date.now(); htmx.addClass?.(elt, HOLD_ACTIVE_CLASS); elt.style.setProperty(CSS_HOLD_PROGRESS_VAR, "0"); elt.dataset[DATA_PROGRESS_ATTR] = "0"; animationFrameId = requestAnimationFrame(updateProgress); }); }); CANCEL_EVENTS.forEach((eventName) => { elt.addEventListener(eventName, () => { if (!isActive) return; isActive = false; startTime = null; holdTriggered = false; clearAnimation(); htmx.removeClass?.(elt, HOLD_ACTIVE_CLASS); elt.style.setProperty(CSS_HOLD_PROGRESS_VAR, "0"); elt.dataset[DATA_PROGRESS_ATTR] = "0"; }); }); processedElements.add(elt); } function registerHoldExtension() { const htmx = window.htmx; if (!htmx || typeof htmx.defineExtension !== "function") { console.error("htmx is not available."); return; } htmx.defineExtension("hold", { onEvent(name, evt) { if (name !== "htmx:afterProcessNode") return true; const elt = evt.detail.elt; if (!elt) return true; const triggerSpec = elt.getAttribute("hx-trigger") ?? elt.getAttribute("data-hx-trigger"); if (!triggerSpec || !HOLD_TRIGGER_PATTERN.test(triggerSpec)) return true; registerElement(elt, triggerSpec, htmx); return true; } }); } if (typeof window !== "undefined") { if (window.htmx) { registerHoldExtension(); } else if (typeof document !== "undefined") { document.addEventListener("htmx:load", registerHoldExtension, { once: true }); } } var src_default = registerHoldExtension;