Files
byre/web/dist/dom.js
T

127 lines
4.3 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
export function h(tag, props = {}, ...children) {
const el = document.createElement(tag);
const { class: className, style, data, ...rest } = props;
if (className) {
el.className = className;
}
if (style) {
el.setAttribute("style", style);
}
for (const [key, value] of Object.entries(data ?? {})) {
el.dataset[key] = value;
}
Object.assign(el, rest);
append(el, ...children);
return el;
}
export function append(el, ...children) {
for (const child of children) {
if (child === null || child === undefined || child === false) {
continue;
}
el.appendChild(typeof child === "object" ? child : document.createTextNode(String(child)));
}
}
export function replace(el, ...children) {
el.replaceChildren();
append(el, ...children);
}
export function copyable(pre) {
const button = h("button", { type: "button", class: "copy", title: "Copy to the clipboard" }, "copy");
button.onclick = async () => {
try {
await navigator.clipboard.writeText(pre.textContent ?? "");
}
catch (err) {
void showAlert(errorText(err));
return;
}
button.textContent = "copied";
setTimeout(() => {
button.textContent = "copy";
}, 1500);
};
return h("div", { class: "copyable" }, pre, button);
}
export function ago(iso) {
const seconds = Math.round((Date.now() - Date.parse(iso)) / 1000);
if (seconds < 60) {
return "just now";
}
if (seconds < 3600) {
return `${Math.floor(seconds / 60)}m ago`;
}
if (seconds < 86400) {
return `${Math.floor(seconds / 3600)}h ago`;
}
return `${Math.floor(seconds / 86400)}d ago`;
}
export function errorText(err) {
return err instanceof Error ? err.message : String(err);
}
function modal(content, buttons, anchor = null) {
const actions = buttons.map((b) => h("button", { type: "submit", value: b.value, class: b.class ?? "", formNoValidate: b.value === "cancel" }, b.label));
const body = typeof content === "string" ? [h("p", { class: "modal-text" }, content)] : content;
const dialog = h("dialog", { class: "modal" }, h("form", { method: "dialog" }, ...body, h("div", { class: "modal-actions" }, ...actions)));
for (const input of dialog.querySelectorAll("input")) {
input.onkeydown = (e) => {
if (e.key === "Enter") {
e.preventDefault();
actions.at(-1)?.click();
}
};
}
if (anchor) {
const rect = anchor.getBoundingClientRect();
dialog.classList.add("anchored");
dialog.style.top = `${rect.bottom + 4}px`;
dialog.style.right = `${document.documentElement.clientWidth - rect.right}px`;
}
document.body.append(dialog);
return new Promise((resolve) => {
dialog.onclose = () => {
dialog.remove();
resolve(dialog.returnValue);
};
dialog.showModal();
const input = dialog.querySelector("input");
if (input) {
input.focus();
input.select();
return;
}
actions.at(-1)?.focus();
});
}
export function openPanel(title, mount) {
if (document.querySelector("dialog.panel[open]")) {
return;
}
const close = h("button", { type: "button", class: "panel-close", title: "Close (Esc)" }, "×");
const body = h("div", { class: "panel-body" });
const dialog = h("dialog", { class: "modal panel" }, h("header", { class: "panel-head" }, h("h2", {}, title), close), body);
close.onclick = () => dialog.close();
dialog.onclick = (e) => {
if (e.target === dialog || e.target.closest("a[href^='#']")) {
dialog.close();
}
};
document.body.append(dialog);
const dispose = mount(body);
dialog.onclose = () => {
dispose();
dialog.remove();
};
dialog.showModal();
}
export async function showAlert(text) {
await modal(text, [{ label: "OK", value: "ok", class: "primary" }]);
}
export async function showConfirm(content, action, danger = false, anchor = null) {
const value = await modal(content, [
{ label: "Cancel", value: "cancel" },
{ label: action, value: "ok", class: danger ? "danger-primary" : "primary" },
], anchor);
return value === "ok";
}
//# sourceMappingURL=dom.js.map