export { PPTY }; function animDelta(anim) { const timing = anim.effect.getComputedTiming(); return (timing.delay ?? 0) + (timing.duration ?? 0) + (timing.endDelay ?? 0); } class PPTY { #root; #blockOptions; #startCallback = (root, debug) => { }; #finishCallback = (root, debug) => { }; constructor(root, blockOptions) { this.#root = root; this.#blockOptions = blockOptions; } onstart(handler) { this.#startCallback = handler; return this; } onfinish(handler) { this.#finishCallback = handler; return this; } run(debug = false) { var delay = 0; this.#startCallback(this.#root, debug); if (debug) return this.#finishCallback(this.#root, debug); this.#root .querySelectorAll(".ppty-block") .forEach((block, index, blocks) => { const prompt = block.querySelector(".ppty-prompt"); const command = block.querySelector(".ppty-command"); const output = block.querySelector(".ppty-output"); const options = this.#blockOptions[index]; const cps = options["cps"]; // chars per second const promptDelay = options["promptDelay"] * 1000; const commandDelay = options["commandDelay"] * 1000; const outputDelay = options["outputDelay"] * 1000; const blinkTime = options["blinkTime"] * 2 * 1000; // x2 then x1000 // WARNING: ensure prompt|command|output != null const promptAnim = prompt.animate( [{ visibility: "visible" }], { delay: delay + promptDelay, fill: "forwards", }, ); delay = animDelta(promptAnim); const showCursor = () => { command.style.borderRightColor = cursorColor; return true; }; const hideCursor = () => { command.style.borderRightColor = "transparent"; return false; }; const startCursorBlink = () => setInterval(() => { cursorVisible = cursorVisible ? hideCursor() : showCursor(); }, blinkTime); const cursorColor = command.style.borderRightColor; var cursorVisible = true; var blinkId = null; setTimeout(() => { command.style.visibility = "visible"; blinkId = startCursorBlink(); }, delay); // WARNING: ensure command.textContent != null const commandLen = command.textContent.trim().length const commandAnim = command.animate( [ { width: "0ch", visibility: "visible", }, { width: `${commandLen}ch`, visibility: "visible", } ], { duration: commandLen / cps * 1000, delay: delay + commandDelay, easing: `steps(${commandLen}, end)`, fill: "forwards", } ); // pause the cursor while typing setTimeout(() => { clearInterval(blinkId); showCursor(); cursorVisible = true; }, delay + commandDelay); delay = animDelta(commandAnim); setTimeout(() => { blinkId = startCursorBlink(); }, delay); // delay until output is visible delay += outputDelay; setTimeout(() => { clearInterval(blinkId); hideCursor(); }, delay); // unhide output output.animate( [{ visibility: "visible" }], { delay: delay, fill: "forwards", } ); // hide the cursor after output displays and run the callback setTimeout(() => { command.style.borderRightColor = "transparent"; if (index == blocks.length - 1) this.#finishCallback(this.#root, debug); }, delay); }); } }