@x-tech-laboratories/x-libs 0.3.0 → 0.6.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.
Files changed (67) hide show
  1. package/README.md +63 -10
  2. package/dist/dates.d.ts +9 -0
  3. package/dist/dates.js +60 -0
  4. package/dist/durations.d.ts +8 -1
  5. package/dist/durations.js +9 -5
  6. package/dist/electron/bridge.d.ts +30 -0
  7. package/dist/electron/bridge.js +32 -0
  8. package/dist/electron/window.d.ts +85 -0
  9. package/dist/electron/window.js +85 -0
  10. package/dist/web/menus.d.ts +4 -7
  11. package/dist/web/menus.js +28 -86
  12. package/dist/web/react/badges.d.ts +26 -0
  13. package/dist/web/react/badges.js +28 -0
  14. package/dist/web/react/controls.d.ts +37 -0
  15. package/dist/web/react/controls.js +44 -0
  16. package/dist/web/react/dialog.d.ts +35 -0
  17. package/dist/web/react/dialog.js +78 -0
  18. package/dist/web/react/layout.d.ts +80 -0
  19. package/dist/web/react/layout.js +71 -0
  20. package/dist/web/react/menus.d.ts +31 -0
  21. package/dist/web/react/menus.js +159 -0
  22. package/dist/web/react/shell.d.ts +37 -0
  23. package/dist/web/react/shell.js +81 -0
  24. package/dist/web/react/shortcuts.d.ts +23 -0
  25. package/dist/web/react/shortcuts.js +95 -0
  26. package/dist/web/{preact → react}/split.d.ts +4 -4
  27. package/dist/web/{preact → react}/split.js +9 -10
  28. package/dist/web/react/states.d.ts +27 -0
  29. package/dist/web/react/states.js +49 -0
  30. package/dist/web/react/table.d.ts +46 -0
  31. package/dist/web/react/table.js +210 -0
  32. package/dist/web/react/time.d.ts +4 -0
  33. package/dist/web/react/time.js +9 -0
  34. package/dist/web/{toasts.d.ts → react/toasts.d.ts} +4 -1
  35. package/dist/web/react/toasts.js +41 -0
  36. package/dist/web/react/tree.d.ts +23 -0
  37. package/dist/web/react/tree.js +168 -0
  38. package/dist/web/states.d.ts +1 -8
  39. package/dist/web/states.js +6 -43
  40. package/dist/web/tables.d.ts +21 -14
  41. package/dist/web/tables.js +69 -33
  42. package/dist/web/theme.d.ts +7 -2
  43. package/dist/web/theme.js +35 -22
  44. package/dist/web/tree.d.ts +35 -0
  45. package/dist/web/tree.js +107 -0
  46. package/package.json +15 -4
  47. package/dist/web/badges.d.ts +0 -10
  48. package/dist/web/badges.js +0 -30
  49. package/dist/web/controls.d.ts +0 -6
  50. package/dist/web/controls.js +0 -55
  51. package/dist/web/dialogs.d.ts +0 -18
  52. package/dist/web/dialogs.js +0 -49
  53. package/dist/web/layout.d.ts +0 -28
  54. package/dist/web/layout.js +0 -53
  55. package/dist/web/preact/controls.d.ts +0 -24
  56. package/dist/web/preact/controls.js +0 -36
  57. package/dist/web/preact/dialog.d.ts +0 -11
  58. package/dist/web/preact/dialog.js +0 -20
  59. package/dist/web/preact/layout.d.ts +0 -80
  60. package/dist/web/preact/layout.js +0 -69
  61. package/dist/web/preact/menus.d.ts +0 -18
  62. package/dist/web/preact/menus.js +0 -29
  63. package/dist/web/preact/table.d.ts +0 -18
  64. package/dist/web/preact/table.js +0 -13
  65. package/dist/web/shell.d.ts +0 -7
  66. package/dist/web/shell.js +0 -51
  67. package/dist/web/toasts.js +0 -22
@@ -1,69 +0,0 @@
1
- import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "preact/jsx-runtime";
2
- export function Section({ title, tools, children }) {
3
- return (_jsxs("section", { class: "panel", children: [_jsxs("div", { class: "panel-head", children: [_jsx("h2", { children: title }), tools && _jsx("div", { class: "panel-tools", children: tools })] }), children] }));
4
- }
5
- // Labelled values in a grid of tiles, or as rows of label and value for long values such as paths.
6
- export function Facts({ rows, layout = "grid" }) {
7
- return (_jsx("dl", { class: layout === "rows" ? "facts facts-rows" : "facts", children: rows.map(([label, value]) => (_jsxs("div", { children: [_jsx("dt", { children: label }), _jsx("dd", { children: value ?? "—" })] }, label))) }));
8
- }
9
- // One headline figure under its label, its edge marked by its tone.
10
- export function Tile({ label, value, tone, href }) {
11
- const tile_class = tone && tone !== "none" ? `tile tile-${tone}` : "tile";
12
- const children = _jsxs(_Fragment, { children: [_jsx("div", { class: "tile-label", children: label }), _jsx("div", { class: "tile-value", children: value })] });
13
- return href ? _jsx("a", { class: tile_class, href: href, children: children }) : _jsx("div", { class: tile_class, children: children });
14
- }
15
- export function Tiles({ children }) {
16
- return _jsx("div", { class: "tiles", children: children });
17
- }
18
- // A titled card for one thing among many, laid out in a grid of Cards.
19
- export function Card({ title, subtitle, badge, class_name, href, on_click, children }) {
20
- const card_class = class_name ? `card ${class_name}` : "card";
21
- const body = (_jsxs(_Fragment, { children: [_jsxs("div", { class: "card-head", children: [_jsx("strong", { children: title }), badge] }), subtitle && _jsx("div", { class: "muted card-host", children: subtitle }), children] }));
22
- if (href)
23
- return _jsx("a", { class: card_class, href: href, children: body });
24
- if (on_click)
25
- return _jsx("button", { type: "button", class: card_class, onClick: on_click, children: body });
26
- return _jsx("div", { class: card_class, children: body });
27
- }
28
- export function Cards({ children }) {
29
- return _jsx("div", { class: "grid", children: children });
30
- }
31
- export function Tabs({ items, active, label = "Sections", on_select }) {
32
- return (_jsx("nav", { class: "tabs", "aria-label": label, children: items.map((item) => {
33
- const tab_class = item.id === active ? "tab active" : "tab";
34
- const current = item.id === active ? "page" : undefined;
35
- return item.href
36
- ? _jsx("a", { class: tab_class, href: item.href, "aria-current": current, children: item.label }, item.id)
37
- : _jsx("button", { type: "button", class: tab_class, "aria-current": current, onClick: () => on_select?.(item.id), children: item.label }, item.id);
38
- }) }));
39
- }
40
- // ── Badges ───────────────────────────────────────────────────────────────────
41
- export function Badge({ tone = "none", title, children }) {
42
- return _jsx("span", { class: `badge tone-${tone}`, title: title, children: children });
43
- }
44
- export function Chip({ title, on_click, children }) {
45
- return on_click
46
- ? _jsx("button", { type: "button", class: "chip", title: title, onClick: on_click, children: children })
47
- : _jsx("span", { class: "chip", title: title, children: children });
48
- }
49
- // ── States ───────────────────────────────────────────────────────────────────
50
- export function Skeleton({ lines = 3 }) {
51
- return (_jsx("div", { class: "skeleton", "aria-busy": "true", "aria-label": "Loading", children: Array.from({ length: lines }, (_, index) => _jsx("div", { class: "skeleton-line", style: { width: `${90 - index * 15}%` } }, index)) }));
52
- }
53
- export function Empty({ title, hint }) {
54
- return (_jsxs("div", { class: "empty", children: [_jsx("p", { class: "empty-title", children: title }), hint && _jsx("p", { class: "muted", children: hint })] }));
55
- }
56
- export function Failure({ error, retry }) {
57
- return (_jsxs("div", { class: "failure", role: "alert", children: [_jsxs("p", { children: ["Could not load this: ", error instanceof Error ? error.message : String(error)] }), _jsx("button", { onClick: retry, children: "Retry" })] }));
58
- }
59
- // ── Settings ─────────────────────────────────────────────────────────────────
60
- export function SettingsGroup({ heading, hint, children }) {
61
- return (_jsxs("section", { class: "settings-group", children: [_jsx("h3", { class: "settings-heading", children: heading }), hint && _jsx("p", { class: "setting-hint", children: hint }), children] }));
62
- }
63
- // A setting's name and hint on one side and its control on the other.
64
- export function SettingRow({ label, hint, as_label = false, children }) {
65
- const text = (_jsxs("span", { class: "setting-text", children: [_jsx("span", { class: "setting-label", children: label }), hint && _jsx("span", { class: "setting-hint", children: hint })] }));
66
- return as_label
67
- ? _jsxs("label", { class: "setting-row", children: [text, children] })
68
- : _jsxs("div", { class: "setting-row", children: [text, children] });
69
- }
@@ -1,18 +0,0 @@
1
- import { type ComponentChildren } from "preact";
2
- import { type MenuItem } from "../menus.ts";
3
- export type { MenuItem } from "../menus.ts";
4
- interface ContextMenuProps {
5
- x: number;
6
- y: number;
7
- items: MenuItem[];
8
- on_close: () => void;
9
- }
10
- export declare function ContextMenu({ x, y, items, on_close }: ContextMenuProps): import("preact").JSX.Element;
11
- interface PopoverProps {
12
- x: number;
13
- y: number;
14
- label: string;
15
- on_close: () => void;
16
- children: ComponentChildren;
17
- }
18
- export declare function Popover({ x, y, label, on_close, children }: PopoverProps): import("preact").JSX.Element;
@@ -1,29 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "preact/jsx-runtime";
2
- // Preact menus and popovers opened at a point, kept on screen and closed the same way as the page's own.
3
- import { Fragment } from "preact";
4
- import { useEffect, useLayoutEffect, useRef } from "preact/hooks";
5
- import { dismiss_on_outside, menu_item_class, menu_item_role, place_on_screen } from "../menus.js";
6
- // ── Placing ──────────────────────────────────────────────────────────────────
7
- // Keeps the element on screen at the point, and calls the latest on_close when it should close.
8
- function use_floating(x, y, on_close) {
9
- const element = useRef(null);
10
- const close = useRef(on_close);
11
- close.current = on_close;
12
- useLayoutEffect(() => place_on_screen(element.current, x, y), [x, y]);
13
- useEffect(() => dismiss_on_outside(element.current, () => close.current()), []);
14
- return element;
15
- }
16
- export function ContextMenu({ x, y, items, on_close }) {
17
- const menu = use_floating(x, y, on_close);
18
- useEffect(() => menu.current?.querySelector(".menu-item:not(:disabled)")?.focus(), []);
19
- return (_jsx("div", { ref: menu, class: "menu", role: "menu", tabIndex: -1, children: items.map((item) => (_jsxs(Fragment, { children: [item.separator_before && _jsx("div", { class: "menu-separator", role: "separator" }), _jsxs("button", { type: "button", class: menu_item_class(item), role: menu_item_role(item), "aria-checked": item.checked === undefined ? undefined : item.checked, disabled: item.disabled, onClick: () => {
20
- on_close();
21
- item.run();
22
- }, children: [_jsx("span", { class: "menu-label", children: item.label }), item.hint && _jsx("span", { class: "menu-hint", children: item.hint })] })] }, item.label))) }));
23
- }
24
- // Holds any content, such as a column's filter, with its first field ready to type into.
25
- export function Popover({ x, y, label, on_close, children }) {
26
- const popover = use_floating(x, y, on_close);
27
- useEffect(() => popover.current?.querySelector("input, select, textarea, button")?.focus(), []);
28
- return (_jsx("div", { ref: popover, class: "popover", role: "dialog", "aria-label": label, children: children }));
29
- }
@@ -1,18 +0,0 @@
1
- import type { ComponentChildren } from "preact";
2
- import { type Sorting } from "../tables.ts";
3
- export type { Sorting } from "../tables.ts";
4
- export interface TableColumn<Row> {
5
- label: string;
6
- cell: (row: Row) => ComponentChildren;
7
- sort?: (row: Row) => string | number | null;
8
- class?: string;
9
- }
10
- interface DataTableProps<Row> {
11
- columns: TableColumn<Row>[];
12
- rows: Row[];
13
- row_key: (row: Row) => string | number;
14
- sorting?: Sorting;
15
- on_sort?: (sorting: Sorting) => void;
16
- caption?: string;
17
- }
18
- export declare function DataTable<Row>({ columns, rows, row_key, sorting, on_sort, caption }: DataTableProps<Row>): import("preact").JSX.Element;
@@ -1,13 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "preact/jsx-runtime";
2
- import { sorted_rows } from "../tables.js";
3
- export function DataTable({ columns, rows, row_key, sorting, on_sort, caption }) {
4
- const sort_column = sorting ? columns.find((column) => column.label === sorting.key && column.sort) : undefined;
5
- function header(column) {
6
- if (!column.sort || !on_sort)
7
- return _jsx("th", { class: column.class, scope: "col", children: column.label }, column.label);
8
- const active = sort_column === column;
9
- const descending = active && sorting.descending;
10
- return (_jsx("th", { class: column.class, scope: "col", "aria-sort": active ? (descending ? "descending" : "ascending") : "none", children: _jsxs("button", { class: "sort", onClick: () => on_sort({ key: column.label, descending: active ? !descending : false }), children: [column.label, active ? (descending ? " ↓" : " ↑") : ""] }) }, column.label));
11
- }
12
- return (_jsx("div", { class: "table-wrap", children: _jsxs("table", { class: "responsive", children: [caption && _jsx("caption", { class: "visually-hidden", children: caption }), _jsx("thead", { children: _jsx("tr", { children: columns.map(header) }) }), _jsx("tbody", { children: sorted_rows(columns, rows, sorting).map((row) => (_jsx("tr", { children: columns.map((column) => _jsx("td", { class: column.class, "data-label": column.label, children: column.cell(row) }, column.label)) }, row_key(row)))) })] }) }));
13
- }
@@ -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
- }
@@ -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
- }