Files
brk/website_next/wallets/hold/index.js
T
2026-06-22 16:42:14 +02:00

137 lines
2.9 KiB
JavaScript

import { createElement } from "../dom.js";
const FILL_MS = 2_000;
const DRAIN_MS = 600;
/**
* @param {number} value
*/
function clampProgress(value) {
return Math.max(0, Math.min(1, value));
}
/**
* @param {HTMLButtonElement} button
* @param {() => void} onHold
*/
function bindHold(button, onHold) {
/** @type {number | undefined} */
let frame;
let holding = false;
let progress = 0;
let previous = 0;
function render() {
button.style.setProperty("--hold-progress", String(progress));
button.style.setProperty("--hold-progress-width", `${progress * 100}%`);
button.classList.toggle("active", progress > 0);
}
function stop() {
if (frame === undefined) return;
cancelAnimationFrame(frame);
frame = undefined;
}
/**
* @param {number} now
*/
function tick(now) {
const elapsed = now - previous;
const rate = elapsed / (holding ? FILL_MS : DRAIN_MS);
previous = now;
progress = clampProgress(progress + (holding ? rate : -rate));
render();
if (holding && progress === 1) {
stop();
holding = false;
progress = 0;
button.classList.remove("holding");
render();
onHold();
return;
}
if (!holding && progress === 0) {
stop();
return;
}
frame = requestAnimationFrame(tick);
}
function run() {
if (frame !== undefined) return;
previous = performance.now();
frame = requestAnimationFrame(tick);
}
function release() {
if (!holding) return;
holding = false;
button.classList.remove("holding");
run();
}
function hold() {
stop();
holding = true;
button.classList.add("holding");
run();
}
render();
button.addEventListener("pointerdown", (event) => {
if (event.button !== 0) return;
button.setPointerCapture(event.pointerId);
hold();
});
button.addEventListener("pointerup", release);
button.addEventListener("pointercancel", release);
button.addEventListener("lostpointercapture", release);
button.addEventListener("keydown", (event) => {
if (event.repeat || (event.key !== " " && event.key !== "Enter")) return;
event.preventDefault();
hold();
});
button.addEventListener("keyup", (event) => {
if (event.key === " " || event.key === "Enter") {
release();
}
});
button.addEventListener("blur", release);
}
/**
* @param {Object} options
* @param {string} options.label
* @param {string} options.title
* @param {string} [options.className]
* @param {() => void} options.onHold
*/
export function createHoldButton(options) {
const button = createElement("button", "hold");
const label = document.createElement("span");
if (options.className) {
button.classList.add(options.className);
}
button.type = "button";
button.dataset.label = options.label;
button.title = options.title;
label.append(options.label);
button.append(label);
bindHold(button, options.onHold);
return button;
}