@x-tech-laboratories/x-libs 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -8
- package/dist/web/menus.d.ts +0 -7
- package/dist/web/menus.js +2 -87
- package/dist/web/preact/badges.d.ts +26 -0
- package/dist/web/preact/badges.js +28 -0
- package/dist/web/preact/controls.d.ts +14 -1
- package/dist/web/preact/controls.js +14 -7
- package/dist/web/preact/dialog.d.ts +25 -1
- package/dist/web/preact/dialog.js +56 -3
- package/dist/web/preact/layout.d.ts +37 -37
- package/dist/web/preact/layout.js +27 -25
- package/dist/web/preact/menus.d.ts +4 -3
- package/dist/web/preact/menus.js +56 -15
- package/dist/web/preact/shell.d.ts +27 -0
- package/dist/web/preact/shell.js +63 -0
- package/dist/web/preact/states.d.ts +27 -0
- package/dist/web/preact/states.js +48 -0
- package/dist/web/preact/table.d.ts +6 -1
- package/dist/web/preact/table.js +8 -1
- package/dist/web/preact/time.d.ts +4 -0
- package/dist/web/preact/time.js +9 -0
- package/dist/web/{toasts.d.ts → preact/toasts.d.ts} +2 -1
- package/dist/web/preact/toasts.js +34 -0
- package/dist/web/states.d.ts +1 -8
- package/dist/web/states.js +6 -43
- package/dist/web/tables.d.ts +4 -14
- package/dist/web/tables.js +1 -27
- package/dist/web/theme.d.ts +1 -1
- package/dist/web/theme.js +4 -1
- package/package.json +1 -1
- package/dist/web/badges.d.ts +0 -10
- package/dist/web/badges.js +0 -30
- package/dist/web/controls.d.ts +0 -6
- package/dist/web/controls.js +0 -55
- package/dist/web/dialogs.d.ts +0 -18
- package/dist/web/dialogs.js +0 -49
- package/dist/web/layout.d.ts +0 -28
- package/dist/web/layout.js +0 -53
- package/dist/web/shell.d.ts +0 -7
- package/dist/web/shell.js +0 -51
- package/dist/web/toasts.js +0 -22
package/dist/web/controls.js
DELETED
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
// Controls that change what a view shows or does: filters, selects, segmented choices, switches and button groups.
|
|
2
|
-
import { h } from "./dom.js";
|
|
3
|
-
// ── Filters ──────────────────────────────────────────────────────────────────
|
|
4
|
-
// A search field that hands on what was typed once typing pauses.
|
|
5
|
-
export function search_box(label, value, on_input) {
|
|
6
|
-
let timer;
|
|
7
|
-
return h("input", {
|
|
8
|
-
type: "search", class: "filter", placeholder: label, "aria-label": label, value,
|
|
9
|
-
oninput: (event) => {
|
|
10
|
-
clearTimeout(timer);
|
|
11
|
-
const text = event.target.value;
|
|
12
|
-
timer = window.setTimeout(() => on_input(text), 200);
|
|
13
|
-
},
|
|
14
|
-
});
|
|
15
|
-
}
|
|
16
|
-
export function select(label, options, value, on_change) {
|
|
17
|
-
return h("label", { class: "field" }, h("span", { class: "field-label" }, label), h("select", { onchange: (event) => on_change(event.target.value) }, ...options.map(([option, text]) => h("option", { value: option, selected: option === value }, text))));
|
|
18
|
-
}
|
|
19
|
-
// ── Choices ──────────────────────────────────────────────────────────────────
|
|
20
|
-
// One of a few choices side by side, such as cards or a table; the chosen one takes the accent.
|
|
21
|
-
export function segmented(label, choices, value, on_change) {
|
|
22
|
-
const buttons = choices.map(([choice, text]) => h("button", {
|
|
23
|
-
type: "button", role: "radio", "aria-checked": String(choice === value),
|
|
24
|
-
onclick: () => {
|
|
25
|
-
for (const [index, button] of buttons.entries())
|
|
26
|
-
button.setAttribute("aria-checked", String(choices[index][0] === choice));
|
|
27
|
-
on_change(choice);
|
|
28
|
-
},
|
|
29
|
-
}, text));
|
|
30
|
-
return h("div", { class: "segmented", role: "radiogroup", "aria-label": label }, ...buttons);
|
|
31
|
-
}
|
|
32
|
-
// Buttons joined into one control, such as zoom out, zoom in and fit.
|
|
33
|
-
export function button_group(label, ...buttons) {
|
|
34
|
-
return h("div", { class: "segmented", role: "group", "aria-label": label }, ...buttons);
|
|
35
|
-
}
|
|
36
|
-
// An on and off switch that waits on the change and flips back when the change did not take.
|
|
37
|
-
export function switch_toggle(label, checked, on_change) {
|
|
38
|
-
const box = h("input", { type: "checkbox", role: "switch", checked, "aria-label": label });
|
|
39
|
-
box.addEventListener("change", async () => {
|
|
40
|
-
const on = box.checked;
|
|
41
|
-
box.disabled = true;
|
|
42
|
-
try {
|
|
43
|
-
if ((await on_change(on)) === false)
|
|
44
|
-
box.checked = !on;
|
|
45
|
-
}
|
|
46
|
-
catch (error) {
|
|
47
|
-
box.checked = !on;
|
|
48
|
-
throw error;
|
|
49
|
-
}
|
|
50
|
-
finally {
|
|
51
|
-
box.disabled = false;
|
|
52
|
-
}
|
|
53
|
-
});
|
|
54
|
-
return box;
|
|
55
|
-
}
|
package/dist/web/dialogs.d.ts
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import { type Child } from "./dom.ts";
|
|
2
|
-
export interface DialogOptions {
|
|
3
|
-
wide?: boolean;
|
|
4
|
-
class_name?: string;
|
|
5
|
-
}
|
|
6
|
-
export declare function open_dialog(title: string, body: Child, buttons: (close: () => void) => Child, options?: DialogOptions): () => void;
|
|
7
|
-
export interface ConfirmOptions {
|
|
8
|
-
title: string;
|
|
9
|
-
message: Child;
|
|
10
|
-
confirm_label: string;
|
|
11
|
-
busy_label?: string;
|
|
12
|
-
danger?: boolean;
|
|
13
|
-
wide?: boolean;
|
|
14
|
-
type_to_confirm?: string;
|
|
15
|
-
run: () => Promise<void>;
|
|
16
|
-
}
|
|
17
|
-
export declare function confirm_dialog(options: ConfirmOptions): void;
|
|
18
|
-
export declare function text_dialog(title: string, text: string): void;
|
package/dist/web/dialogs.js
DELETED
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
// Modal dialogs: one with any body and buttons, one that asks before an action, and one that shows a long text.
|
|
2
|
-
import { h } from "./dom.js";
|
|
3
|
-
import { toast } from "./toasts.js";
|
|
4
|
-
// A modal dialog: the page behind it is inert, focus stays inside, and Escape closes it.
|
|
5
|
-
export function open_dialog(title, body, buttons, options = {}) {
|
|
6
|
-
const classes = ["dialog", options.wide && "wide", options.class_name].filter(Boolean).join(" ");
|
|
7
|
-
const dialog = h("dialog", { class: classes, "aria-labelledby": "dialog-title" });
|
|
8
|
-
const close = () => {
|
|
9
|
-
dialog.close();
|
|
10
|
-
dialog.remove();
|
|
11
|
-
};
|
|
12
|
-
dialog.addEventListener("close", () => dialog.remove());
|
|
13
|
-
dialog.append(h("h2", { id: "dialog-title" }, title), h("div", { class: "dialog-body" }, body), h("div", { class: "dialog-buttons" }, buttons(close)));
|
|
14
|
-
document.body.append(dialog);
|
|
15
|
-
dialog.showModal();
|
|
16
|
-
return close;
|
|
17
|
-
}
|
|
18
|
-
export function confirm_dialog(options) {
|
|
19
|
-
const confirm = h("button", { class: options.danger ? "danger solid" : "accent solid", disabled: options.type_to_confirm !== undefined }, options.confirm_label);
|
|
20
|
-
const typed = options.type_to_confirm === undefined ? null : h("input", {
|
|
21
|
-
type: "text", class: "filter", autocomplete: "off", "aria-label": `Type ${options.type_to_confirm} to confirm`,
|
|
22
|
-
oninput: (event) => { confirm.disabled = event.target.value !== options.type_to_confirm; },
|
|
23
|
-
});
|
|
24
|
-
const body = h("div", {}, typeof options.message === "string" ? h("p", {}, options.message) : options.message, typed && h("label", { class: "type-confirm" }, h("span", {}, "Type ", h("strong", {}, options.type_to_confirm), " to confirm:"), typed));
|
|
25
|
-
open_dialog(options.title, body, (close) => {
|
|
26
|
-
const cancel = h("button", { onclick: close }, "Cancel");
|
|
27
|
-
confirm.addEventListener("click", async () => {
|
|
28
|
-
confirm.disabled = true;
|
|
29
|
-
cancel.disabled = true;
|
|
30
|
-
confirm.textContent = options.busy_label ?? "Working…";
|
|
31
|
-
try {
|
|
32
|
-
await options.run();
|
|
33
|
-
}
|
|
34
|
-
finally {
|
|
35
|
-
close();
|
|
36
|
-
}
|
|
37
|
-
});
|
|
38
|
-
return [cancel, confirm];
|
|
39
|
-
}, { wide: options.wide });
|
|
40
|
-
(typed ?? confirm).focus();
|
|
41
|
-
}
|
|
42
|
-
// ── Text ─────────────────────────────────────────────────────────────────────
|
|
43
|
-
// Shows a long text result, such as a preview, with a button to copy it.
|
|
44
|
-
export function text_dialog(title, text) {
|
|
45
|
-
open_dialog(title, h("pre", { class: "output" }, text), (close) => [
|
|
46
|
-
h("button", { onclick: () => void navigator.clipboard?.writeText(text).then(() => toast("Copied.")) }, "Copy"),
|
|
47
|
-
h("button", { class: "accent solid", onclick: close }, "Close"),
|
|
48
|
-
], { wide: true });
|
|
49
|
-
}
|
package/dist/web/layout.d.ts
DELETED
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
import { type Child } from "./dom.ts";
|
|
2
|
-
export declare function section(title: Child, body: Child, tools?: Child): HTMLElement;
|
|
3
|
-
export declare function facts(rows: [label: string, value: Child, class_name?: string][], layout?: "grid" | "rows"): HTMLElement;
|
|
4
|
-
export declare function tile(label: string, value: Child, class_name?: string | null, href?: string | null): HTMLElement;
|
|
5
|
-
export declare function tiles(...items: Child[]): HTMLElement;
|
|
6
|
-
export interface CardOptions {
|
|
7
|
-
subtitle?: Child;
|
|
8
|
-
badge?: Child;
|
|
9
|
-
class_name?: string;
|
|
10
|
-
href?: string;
|
|
11
|
-
}
|
|
12
|
-
export declare function card(title: Child, body: Child, options?: CardOptions): HTMLElement;
|
|
13
|
-
export declare function cards(...items: Child[]): HTMLElement;
|
|
14
|
-
export interface TabItem {
|
|
15
|
-
id: string;
|
|
16
|
-
label: string;
|
|
17
|
-
href: string;
|
|
18
|
-
}
|
|
19
|
-
export declare function tabs(items: TabItem[], active: string, label?: string): HTMLElement;
|
|
20
|
-
export declare function crumbs(items: [label: string, href: string | null][]): HTMLElement;
|
|
21
|
-
export declare function toolbar(...children: Child[]): HTMLElement;
|
|
22
|
-
export declare function stepper(label: string, previous: {
|
|
23
|
-
text: string;
|
|
24
|
-
href: string | null;
|
|
25
|
-
}, position: Child, next: {
|
|
26
|
-
text: string;
|
|
27
|
-
href: string | null;
|
|
28
|
-
}): HTMLElement;
|
package/dist/web/layout.js
DELETED
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
// The pieces a page is laid out with: panels, facts, tiles, cards, tabs, breadcrumbs, toolbars and steppers.
|
|
2
|
-
import { h } from "./dom.js";
|
|
3
|
-
// ── Panels ───────────────────────────────────────────────────────────────────
|
|
4
|
-
export function section(title, body, tools = null) {
|
|
5
|
-
return h("section", { class: "panel" }, h("div", { class: "panel-head" }, h("h2", {}, title), tools && h("div", { class: "panel-tools" }, tools)), body);
|
|
6
|
-
}
|
|
7
|
-
// Labelled values in a grid of tiles, or as rows of label and value for long values such as paths.
|
|
8
|
-
export function facts(rows, layout = "grid") {
|
|
9
|
-
return h("dl", { class: layout === "rows" ? "facts facts-rows" : "facts" }, ...rows.map(([label, value, class_name]) => h("div", {}, h("dt", {}, label), h("dd", { class: class_name ?? null }, value ?? "—"))));
|
|
10
|
-
}
|
|
11
|
-
// ── Tiles and cards ──────────────────────────────────────────────────────────
|
|
12
|
-
// One headline figure under its label; a class such as tile-bad or tone-edge-warn marks its edge, and a link makes it open a page.
|
|
13
|
-
export function tile(label, value, class_name = null, href = null) {
|
|
14
|
-
const tile_class = `tile${class_name ? ` ${class_name}` : ""}`;
|
|
15
|
-
const children = [h("div", { class: "tile-label" }, label), h("div", { class: "tile-value" }, value)];
|
|
16
|
-
return href === null ? h("div", { class: tile_class }, ...children) : h("a", { class: tile_class, href }, ...children);
|
|
17
|
-
}
|
|
18
|
-
export function tiles(...items) {
|
|
19
|
-
return h("div", { class: "tiles" }, ...items);
|
|
20
|
-
}
|
|
21
|
-
// A titled card for one thing among many, laid out in a grid.
|
|
22
|
-
export function card(title, body, options = {}) {
|
|
23
|
-
const card_class = `card${options.class_name ? ` ${options.class_name}` : ""}`;
|
|
24
|
-
const children = [
|
|
25
|
-
h("div", { class: "card-head" }, h("strong", {}, title), options.badge),
|
|
26
|
-
options.subtitle && h("div", { class: "muted card-host" }, options.subtitle),
|
|
27
|
-
body,
|
|
28
|
-
];
|
|
29
|
-
return options.href ? h("a", { class: card_class, href: options.href }, ...children) : h("div", { class: card_class }, ...children);
|
|
30
|
-
}
|
|
31
|
-
export function cards(...items) {
|
|
32
|
-
return h("div", { class: "grid" }, ...items);
|
|
33
|
-
}
|
|
34
|
-
export function tabs(items, active, label = "Sections") {
|
|
35
|
-
return h("nav", { class: "tabs", "aria-label": label }, ...items.map((item) => h("a", { href: item.href, class: item.id === active ? "tab active" : "tab", "aria-current": item.id === active ? "page" : null }, item.label)));
|
|
36
|
-
}
|
|
37
|
-
export function crumbs(items) {
|
|
38
|
-
return h("nav", { class: "crumbs", "aria-label": "Breadcrumb" }, ...items.flatMap(([label, href], index) => [
|
|
39
|
-
index > 0 ? h("span", { class: "muted", "aria-hidden": "true" }, " / ") : null,
|
|
40
|
-
href ? h("a", { href }, label) : h("span", { "aria-current": "page" }, label),
|
|
41
|
-
]));
|
|
42
|
-
}
|
|
43
|
-
// ── Toolbars ─────────────────────────────────────────────────────────────────
|
|
44
|
-
export function toolbar(...children) {
|
|
45
|
-
return h("div", { class: "toolbar" }, ...children);
|
|
46
|
-
}
|
|
47
|
-
// Steps to the item before or after this one, each a link or a dead button when there is none, with where this one stands between.
|
|
48
|
-
export function stepper(label, previous, position, next) {
|
|
49
|
-
const step = (target) => target.href === null
|
|
50
|
-
? h("button", { disabled: true }, target.text)
|
|
51
|
-
: h("a", { class: "button", href: target.href }, target.text);
|
|
52
|
-
return h("div", { class: "stepper", role: "group", "aria-label": label }, step(previous), h("span", { class: "muted" }, position), step(next));
|
|
53
|
-
}
|
package/dist/web/shell.d.ts
DELETED
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
import { type ThemeChoice } from "./theme.ts";
|
|
2
|
-
export interface SidebarMenu {
|
|
3
|
-
close(): void;
|
|
4
|
-
}
|
|
5
|
-
export declare function sidebar_menu(menu: HTMLButtonElement, sidebar: HTMLElement, scrim: HTMLElement): SidebarMenu;
|
|
6
|
-
export declare function mark_section(sidebar: HTMLElement, section: string): void;
|
|
7
|
-
export declare function theme_button(button: HTMLButtonElement, choice: ThemeChoice): void;
|
package/dist/web/shell.js
DELETED
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
// The frame around every view: the sidebar that slides in on phones, the section it lights up, and the theme button.
|
|
2
|
-
import { next_theme } from "./theme.js";
|
|
3
|
-
// The menu button opens the sidebar over a scrim, and the scrim or Escape closes it again.
|
|
4
|
-
export function sidebar_menu(menu, sidebar, scrim) {
|
|
5
|
-
const close = () => {
|
|
6
|
-
document.body.classList.remove("menu-open");
|
|
7
|
-
menu.setAttribute("aria-expanded", "false");
|
|
8
|
-
scrim.hidden = true;
|
|
9
|
-
};
|
|
10
|
-
menu.addEventListener("click", () => {
|
|
11
|
-
const open = !document.body.classList.contains("menu-open");
|
|
12
|
-
document.body.classList.toggle("menu-open", open);
|
|
13
|
-
menu.setAttribute("aria-expanded", String(open));
|
|
14
|
-
scrim.hidden = !open;
|
|
15
|
-
if (open)
|
|
16
|
-
sidebar.querySelector("a")?.focus();
|
|
17
|
-
});
|
|
18
|
-
scrim.addEventListener("click", close);
|
|
19
|
-
document.addEventListener("keydown", (event) => {
|
|
20
|
-
if (event.key === "Escape" && document.body.classList.contains("menu-open")) {
|
|
21
|
-
close();
|
|
22
|
-
menu.focus();
|
|
23
|
-
}
|
|
24
|
-
});
|
|
25
|
-
return { close };
|
|
26
|
-
}
|
|
27
|
-
// Lights up the sidebar link whose data-section names the section, and marks it as the current page.
|
|
28
|
-
export function mark_section(sidebar, section) {
|
|
29
|
-
for (const item of sidebar.querySelectorAll("a[data-section]")) {
|
|
30
|
-
const active = item.dataset.section === section;
|
|
31
|
-
item.classList.toggle("active", active);
|
|
32
|
-
if (active)
|
|
33
|
-
item.setAttribute("aria-current", "page");
|
|
34
|
-
else
|
|
35
|
-
item.removeAttribute("aria-current");
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
// ── Theme ────────────────────────────────────────────────────────────────────
|
|
39
|
-
const theme_labels = { system: ["◐", "Theme: system"], light: ["☀", "Theme: light"], dark: ["☾", "Theme: dark"] };
|
|
40
|
-
// The button steps through system, light and dark, its glyph and label showing the current one.
|
|
41
|
-
export function theme_button(button, choice) {
|
|
42
|
-
const show = (theme) => {
|
|
43
|
-
const [icon, text] = theme_labels[theme];
|
|
44
|
-
button.textContent = icon;
|
|
45
|
-
button.title = `${text} (click to change)`;
|
|
46
|
-
button.setAttribute("aria-label", `${text}, click to change`);
|
|
47
|
-
};
|
|
48
|
-
choice.on_change(show);
|
|
49
|
-
button.addEventListener("click", () => choice.set(next_theme(choice.get())));
|
|
50
|
-
choice.apply();
|
|
51
|
-
}
|
package/dist/web/toasts.js
DELETED
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
// Short notes in the page's corner after something happened, with an optional link or action such as Undo.
|
|
2
|
-
import { h } from "./dom.js";
|
|
3
|
-
// The region toasts stack in, made at the foot of the page when the page has none of its own.
|
|
4
|
-
function region() {
|
|
5
|
-
const existing = document.querySelector("#toasts");
|
|
6
|
-
if (existing)
|
|
7
|
-
return existing;
|
|
8
|
-
const made = h("div", { id: "toasts", class: "toasts" });
|
|
9
|
-
document.body.append(made);
|
|
10
|
-
return made;
|
|
11
|
-
}
|
|
12
|
-
// Shows the message and returns a function that closes it early.
|
|
13
|
-
export function toast(message, options = {}) {
|
|
14
|
-
const tone = options.tone ?? "info";
|
|
15
|
-
const close = () => item.remove();
|
|
16
|
-
const action = options.action;
|
|
17
|
-
const item = h("div", { class: `toast tone-${tone}`, role: tone === "bad" ? "alert" : "status" }, h("span", {}, message), options.link && h("a", { href: options.link.href, onclick: close }, options.link.text), action && h("button", { class: "toast-action", onclick: () => { close(); action.run(); } }, action.text), h("button", { class: "icon", "aria-label": "Dismiss", onclick: close }, "×"));
|
|
18
|
-
region().append(item);
|
|
19
|
-
if (!(options.sticky ?? tone === "bad"))
|
|
20
|
-
setTimeout(close, options.duration_in_milliseconds ?? 6000);
|
|
21
|
-
return close;
|
|
22
|
-
}
|