@x-tech-laboratories/x-libs 0.14.0 → 0.14.1

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 (151) hide show
  1. package/dist/web/react/menus.js +11 -7
  2. package/package.json +1 -1
  3. package/dist/agent/client.d.ts +0 -32
  4. package/dist/agent/client.js +0 -148
  5. package/dist/agent/commands.d.ts +0 -23
  6. package/dist/agent/commands.js +0 -118
  7. package/dist/agent/keys.d.ts +0 -14
  8. package/dist/agent/keys.js +0 -81
  9. package/dist/agent/long_runs.d.ts +0 -32
  10. package/dist/agent/long_runs.js +0 -316
  11. package/dist/agent/protocol.d.ts +0 -148
  12. package/dist/agent/protocol.js +0 -131
  13. package/dist/agent/session.d.ts +0 -50
  14. package/dist/agent/session.js +0 -225
  15. package/dist/agent/tasks.d.ts +0 -17
  16. package/dist/agent/tasks.js +0 -15
  17. package/dist/backups.d.ts +0 -31
  18. package/dist/backups.js +0 -43
  19. package/dist/dates.d.ts +0 -9
  20. package/dist/dates.js +0 -60
  21. package/dist/durations.d.ts +0 -11
  22. package/dist/durations.js +0 -49
  23. package/dist/electron/app.d.ts +0 -13
  24. package/dist/electron/app.js +0 -27
  25. package/dist/electron/bridge.d.ts +0 -55
  26. package/dist/electron/bridge.js +0 -105
  27. package/dist/electron/events.d.ts +0 -21
  28. package/dist/electron/events.js +0 -25
  29. package/dist/electron/fetch.d.ts +0 -52
  30. package/dist/electron/fetch.js +0 -175
  31. package/dist/electron/window.d.ts +0 -96
  32. package/dist/electron/window.js +0 -128
  33. package/dist/log.d.ts +0 -30
  34. package/dist/log.js +0 -79
  35. package/dist/server/agents/agent.d.ts +0 -27
  36. package/dist/server/agents/agent.js +0 -16
  37. package/dist/server/agents/connection.d.ts +0 -51
  38. package/dist/server/agents/connection.js +0 -239
  39. package/dist/server/agents/hub.d.ts +0 -86
  40. package/dist/server/agents/hub.js +0 -241
  41. package/dist/server/agents/local.d.ts +0 -24
  42. package/dist/server/agents/local.js +0 -62
  43. package/dist/server/agents/route.d.ts +0 -3
  44. package/dist/server/agents/route.js +0 -24
  45. package/dist/server/backup_files.d.ts +0 -3
  46. package/dist/server/backup_files.js +0 -25
  47. package/dist/server/config.d.ts +0 -39
  48. package/dist/server/config.js +0 -109
  49. package/dist/server/cors.d.ts +0 -8
  50. package/dist/server/cors.js +0 -13
  51. package/dist/server/events.d.ts +0 -33
  52. package/dist/server/events.js +0 -68
  53. package/dist/server/hash_password.d.ts +0 -1
  54. package/dist/server/hash_password.js +0 -46
  55. package/dist/server/login_page.d.ts +0 -8
  56. package/dist/server/login_page.js +0 -79
  57. package/dist/server/passwords.d.ts +0 -8
  58. package/dist/server/passwords.js +0 -59
  59. package/dist/server/replies.d.ts +0 -12
  60. package/dist/server/replies.js +0 -41
  61. package/dist/server/sign_in.d.ts +0 -27
  62. package/dist/server/sign_in.js +0 -95
  63. package/dist/server/web_folder.d.ts +0 -11
  64. package/dist/server/web_folder.js +0 -40
  65. package/dist/web/dom.d.ts +0 -7
  66. package/dist/web/dom.js +0 -32
  67. package/dist/web/drag.d.ts +0 -3
  68. package/dist/web/drag.js +0 -13
  69. package/dist/web/errors.d.ts +0 -1
  70. package/dist/web/errors.js +0 -27
  71. package/dist/web/format.d.ts +0 -11
  72. package/dist/web/format.js +0 -82
  73. package/dist/web/menus.d.ts +0 -17
  74. package/dist/web/menus.js +0 -40
  75. package/dist/web/prefs.d.ts +0 -12
  76. package/dist/web/prefs.js +0 -62
  77. package/dist/web/react/badges.d.ts +0 -35
  78. package/dist/web/react/badges.js +0 -32
  79. package/dist/web/react/controls.d.ts +0 -86
  80. package/dist/web/react/controls.js +0 -154
  81. package/dist/web/react/dialog.d.ts +0 -45
  82. package/dist/web/react/dialog.js +0 -82
  83. package/dist/web/react/following.d.ts +0 -25
  84. package/dist/web/react/following.js +0 -34
  85. package/dist/web/react/layout.d.ts +0 -111
  86. package/dist/web/react/layout.js +0 -85
  87. package/dist/web/react/list_picker.d.ts +0 -19
  88. package/dist/web/react/list_picker.js +0 -50
  89. package/dist/web/react/markdown.d.ts +0 -8
  90. package/dist/web/react/markdown.js +0 -40
  91. package/dist/web/react/media.d.ts +0 -24
  92. package/dist/web/react/media.js +0 -18
  93. package/dist/web/react/menu_button.d.ts +0 -10
  94. package/dist/web/react/menu_button.js +0 -16
  95. package/dist/web/react/menus.d.ts +0 -31
  96. package/dist/web/react/page_view.d.ts +0 -16
  97. package/dist/web/react/page_view.js +0 -6
  98. package/dist/web/react/prefs.d.ts +0 -2
  99. package/dist/web/react/prefs.js +0 -11
  100. package/dist/web/react/progress.d.ts +0 -10
  101. package/dist/web/react/progress.js +0 -9
  102. package/dist/web/react/rating.d.ts +0 -8
  103. package/dist/web/react/rating.js +0 -23
  104. package/dist/web/react/reveal.d.ts +0 -10
  105. package/dist/web/react/reveal.js +0 -30
  106. package/dist/web/react/shell.d.ts +0 -46
  107. package/dist/web/react/shell.js +0 -94
  108. package/dist/web/react/shortcuts.d.ts +0 -23
  109. package/dist/web/react/shortcuts.js +0 -95
  110. package/dist/web/react/split.d.ts +0 -23
  111. package/dist/web/react/split.js +0 -94
  112. package/dist/web/react/states.d.ts +0 -36
  113. package/dist/web/react/states.js +0 -64
  114. package/dist/web/react/store.d.ts +0 -4
  115. package/dist/web/react/store.js +0 -7
  116. package/dist/web/react/suggestions.d.ts +0 -23
  117. package/dist/web/react/suggestions.js +0 -57
  118. package/dist/web/react/table.d.ts +0 -78
  119. package/dist/web/react/table.js +0 -404
  120. package/dist/web/react/test_stubs.d.ts +0 -1
  121. package/dist/web/react/test_stubs.js +0 -25
  122. package/dist/web/react/time.d.ts +0 -5
  123. package/dist/web/react/time.js +0 -19
  124. package/dist/web/react/toasts.d.ts +0 -29
  125. package/dist/web/react/toasts.js +0 -72
  126. package/dist/web/react/tree.d.ts +0 -28
  127. package/dist/web/react/tree.js +0 -183
  128. package/dist/web/react/volume.d.ts +0 -8
  129. package/dist/web/react/volume.js +0 -42
  130. package/dist/web/react/watch.d.ts +0 -3
  131. package/dist/web/react/watch.js +0 -5
  132. package/dist/web/router.d.ts +0 -7
  133. package/dist/web/router.js +0 -29
  134. package/dist/web/selection.d.ts +0 -25
  135. package/dist/web/selection.js +0 -106
  136. package/dist/web/split.d.ts +0 -15
  137. package/dist/web/split.js +0 -104
  138. package/dist/web/states.d.ts +0 -3
  139. package/dist/web/states.js +0 -10
  140. package/dist/web/store.d.ts +0 -11
  141. package/dist/web/store.js +0 -17
  142. package/dist/web/tables.d.ts +0 -28
  143. package/dist/web/tables.js +0 -88
  144. package/dist/web/theme.d.ts +0 -14
  145. package/dist/web/theme.js +0 -52
  146. package/dist/web/tree.d.ts +0 -40
  147. package/dist/web/tree.js +0 -130
  148. package/dist/web/virtual.d.ts +0 -7
  149. package/dist/web/virtual.js +0 -10
  150. package/dist/web/watch.d.ts +0 -11
  151. package/dist/web/watch.js +0 -37
@@ -1,72 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- // Short notes in the page's corner after something happened, with an optional link or action such as Undo.
3
- import { useEffect, useState } from "react";
4
- let shown = [];
5
- let next_id = 1;
6
- const listeners = new Set();
7
- function publish(toasts) {
8
- shown = toasts;
9
- for (const listener of listeners)
10
- listener(shown);
11
- }
12
- // Takes the toast away and tells its owner why, once only.
13
- function close_toast(id, reason) {
14
- const item = shown.find((each) => each.id === id);
15
- if (!item)
16
- return;
17
- publish(shown.filter((each) => each.id !== id));
18
- item.on_close?.(reason);
19
- }
20
- // Shows the message and returns a function that closes it early.
21
- export function toast(message, options = {}) {
22
- const id = next_id++;
23
- publish([...shown, { ...options, id, message }]);
24
- if (!(options.sticky ?? options.tone === "bad"))
25
- setTimeout(() => close_toast(id, "timeout"), options.duration_in_milliseconds ?? 6000);
26
- return () => close_toast(id, "closed");
27
- }
28
- // Shows the message with an Undo button, then either undoes or commits the action, exactly once.
29
- export function undoable(message, { commit, undo, duration_in_milliseconds = 8000, undo_label = "Undo" }) {
30
- let settled = false;
31
- let undo_wanted = false;
32
- const settle = (reason) => {
33
- if (settled)
34
- return;
35
- settled = true;
36
- if (reason === "action" || undo_wanted)
37
- undo();
38
- else
39
- commit();
40
- };
41
- const close = toast(message, {
42
- action: { text: undo_label, run: () => { } },
43
- duration_in_milliseconds,
44
- on_close: settle,
45
- });
46
- return {
47
- finish: () => close(),
48
- undo: () => {
49
- if (settled)
50
- return;
51
- undo_wanted = true;
52
- close();
53
- },
54
- get settled() {
55
- return settled;
56
- },
57
- };
58
- }
59
- // ── Host ─────────────────────────────────────────────────────────────────────
60
- // Where toasts stack; the Shell holds one, and a page without the Shell renders its own.
61
- export function Toasts() {
62
- const [toasts, set_toasts] = useState(shown);
63
- useEffect(() => {
64
- listeners.add(set_toasts);
65
- return () => void listeners.delete(set_toasts);
66
- }, []);
67
- return (_jsx("div", { id: "toasts", className: "toasts", children: toasts.map((item) => {
68
- const tone = item.tone ?? "info";
69
- const close = () => close_toast(item.id, "dismissed");
70
- return (_jsxs("div", { className: `toast tone-${tone}`, role: tone === "bad" ? "alert" : "status", children: [_jsx("span", { children: item.message }), item.link && _jsx("a", { href: item.link.href, onClick: close, children: item.link.text }), item.action && _jsx("button", { className: "toast-action", onClick: () => { close_toast(item.id, "action"); item.action.run(); }, children: item.action.text }), _jsx("button", { className: "icon remove", "aria-label": "Dismiss", onClick: close, children: "\u00D7" })] }, item.id));
71
- }) }));
72
- }
@@ -1,28 +0,0 @@
1
- import { type DragEvent, type MouseEvent, type ReactNode } from "react";
2
- import { type DropPlacement, type TreeItem, type TreeMove } from "../tree.ts";
3
- export type { DropPlacement, TreeItem, TreeMove } from "../tree.ts";
4
- interface TreeProps<Item extends TreeItem> {
5
- items: Item[];
6
- label: string;
7
- render_item: (item: Item) => ReactNode;
8
- render_extra?: (item: Item) => ReactNode;
9
- name_of?: (item: Item) => string;
10
- collapsed: ReadonlySet<string>;
11
- on_collapsed_change: (collapsed: Set<string>) => void;
12
- selected_id?: string | null;
13
- selected_ids?: ReadonlySet<string>;
14
- on_select?: (item: Item) => void;
15
- on_activate?: (item: Item) => void;
16
- row_class?: (item: Item) => string | undefined;
17
- on_move?: (item: Item, move: TreeMove) => void;
18
- can_drop?: (dragged: Item, target: Item, placement: DropPlacement) => boolean;
19
- allow_inside?: boolean;
20
- new_item?: {
21
- parent_id: string | null;
22
- form: ReactNode;
23
- } | null;
24
- on_context_menu?: (event: MouseEvent<HTMLElement>, item: Item) => void;
25
- accepts_external?: (event: DragEvent<HTMLElement>, item: Item) => boolean;
26
- on_external_drop?: (event: DragEvent<HTMLElement>, item: Item) => void;
27
- }
28
- export declare function Tree<Item extends TreeItem>({ items, label, render_item, render_extra, name_of, collapsed, on_collapsed_change, selected_id, selected_ids, on_select, on_activate, row_class: class_of, on_move, can_drop, allow_inside, new_item, on_context_menu, accepts_external, on_external_drop, }: TreeProps<Item>): import("react").JSX.Element;
@@ -1,183 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- // A tree of rows: fold and unfold, walk it with the arrow keys, and reorder it by dragging or with Alt and the arrow keys.
3
- import { useEffect, useRef, useState } from "react";
4
- import { by_position, can_drop as is_outside_subtree, key_drop, move_for_drop, nest, placement_at, } from "../tree.js";
5
- const KEY_MOVES = { ArrowUp: "up", ArrowDown: "down", ArrowRight: "indent", ArrowLeft: "outdent" };
6
- // The rows a person can see, top to bottom, skipping those inside folded rows.
7
- function visible_nodes(nodes, collapsed) {
8
- return nodes.flatMap((node) => [node, ...(collapsed.has(node.item.id) ? [] : visible_nodes(node.children, collapsed))]);
9
- }
10
- export function Tree({ items, label, render_item, render_extra, name_of, collapsed, on_collapsed_change, selected_id = null, selected_ids, on_select, on_activate, row_class: class_of, on_move, can_drop, allow_inside = true, new_item = null, on_context_menu, accepts_external, on_external_drop, }) {
11
- const [focused_id, set_focused_id] = useState(null);
12
- const [dragged_id, set_dragged_id] = useState(null);
13
- const [drop_target, set_drop_target] = useState(null);
14
- const row_elements = useRef(new Map());
15
- const moves_focus = useRef(false);
16
- const roots = nest([...items].sort(by_position), items);
17
- const shown = visible_nodes(roots, collapsed);
18
- const is_selected = (id) => selected_id === id || Boolean(selected_ids?.has(id));
19
- const first_selected = shown.find((node) => selected_ids?.has(node.item.id))?.item.id ?? null;
20
- const tab_stop = [focused_id, selected_id, first_selected].find((id) => id !== null && shown.some((node) => node.item.id === id)) ?? shown[0]?.item.id;
21
- useEffect(() => {
22
- if (!moves_focus.current || focused_id === null)
23
- return;
24
- moves_focus.current = false;
25
- row_elements.current.get(focused_id)?.focus();
26
- }, [focused_id, items]);
27
- // ── Folding ────────────────────────────────────────────────────────────────
28
- function set_collapsed(id, is_collapsed) {
29
- if (collapsed.has(id) === is_collapsed)
30
- return;
31
- const next = new Set(collapsed);
32
- if (is_collapsed)
33
- next.add(id);
34
- else
35
- next.delete(id);
36
- on_collapsed_change(next);
37
- }
38
- // ── Moving ─────────────────────────────────────────────────────────────────
39
- function is_allowed(dragged, target, placement) {
40
- if (placement === "inside" && !allow_inside)
41
- return false;
42
- return is_outside_subtree(dragged.id, target.id, items) && (can_drop?.(dragged, target, placement) ?? true);
43
- }
44
- // A row dropped into a folded one unfolds it, so the moved row stays in sight.
45
- function move(item, target, placement) {
46
- if (placement === "inside")
47
- set_collapsed(target.id, false);
48
- on_move(item, move_for_drop(item, target, placement, items));
49
- }
50
- function placement_over(event, target) {
51
- const dragged = items.find((item) => item.id === dragged_id);
52
- if (!dragged)
53
- return null;
54
- const box = event.currentTarget.getBoundingClientRect();
55
- const placement = placement_at(box.height > 0 ? (event.clientY - box.top) / box.height : 0.5, allow_inside);
56
- return is_allowed(dragged, target, placement) ? placement : null;
57
- }
58
- // A drag from outside the tree, such as a task, lands inside the row that accepts it.
59
- function accepts_from_outside(event, target) {
60
- return dragged_id === null && Boolean(accepts_external?.(event, target));
61
- }
62
- function drag_over(event, target) {
63
- const external = accepts_from_outside(event, target);
64
- const placement = external ? "inside" : placement_over(event, target);
65
- if (!placement)
66
- return;
67
- event.preventDefault();
68
- // An outside drag keeps the effect its source allowed, which may not include a move.
69
- if (event.dataTransfer && !external)
70
- event.dataTransfer.dropEffect = "move";
71
- if (drop_target?.id !== target.id || drop_target.placement !== placement)
72
- set_drop_target({ id: target.id, placement });
73
- }
74
- function drop(event, target) {
75
- if (accepts_from_outside(event, target)) {
76
- event.preventDefault();
77
- end_drag();
78
- on_external_drop?.(event, target);
79
- return;
80
- }
81
- const placement = placement_over(event, target);
82
- const dragged = items.find((item) => item.id === dragged_id);
83
- end_drag();
84
- if (!placement || !dragged)
85
- return;
86
- event.preventDefault();
87
- move(dragged, target, placement);
88
- }
89
- function end_drag() {
90
- set_dragged_id(null);
91
- set_drop_target(null);
92
- }
93
- // ── Keyboard ───────────────────────────────────────────────────────────────
94
- function focus_row(id) {
95
- if (id === undefined)
96
- return;
97
- moves_focus.current = true;
98
- set_focused_id(id);
99
- }
100
- function key_down(event) {
101
- const row = event.target.closest("[role='treeitem']");
102
- if (row !== event.target)
103
- return;
104
- const index = shown.findIndex((node) => node.item.id === row.dataset.id);
105
- const node = shown[index];
106
- if (!node)
107
- return;
108
- const { item } = node;
109
- const is_open = node.children.length > 0 && !collapsed.has(item.id);
110
- if (event.altKey && on_move && KEY_MOVES[event.key]) {
111
- const key_target = key_drop(item, KEY_MOVES[event.key], items);
112
- if (key_target && is_allowed(item, key_target.target, key_target.placement)) {
113
- move(item, key_target.target, key_target.placement);
114
- focus_row(item.id);
115
- }
116
- }
117
- else if (event.altKey || event.ctrlKey || event.metaKey) {
118
- return;
119
- }
120
- else if (event.key === "ArrowDown")
121
- focus_row(shown[index + 1]?.item.id);
122
- else if (event.key === "ArrowUp")
123
- focus_row(shown[index - 1]?.item.id);
124
- else if (event.key === "Home")
125
- focus_row(shown[0]?.item.id);
126
- else if (event.key === "End")
127
- focus_row(shown.at(-1)?.item.id);
128
- else if (event.key === "ArrowRight") {
129
- if (is_open)
130
- focus_row(node.children[0].item.id);
131
- else if (node.children.length > 0)
132
- set_collapsed(item.id, false);
133
- else
134
- return;
135
- }
136
- else if (event.key === "ArrowLeft") {
137
- if (is_open)
138
- set_collapsed(item.id, true);
139
- else if (item.parent_id !== null && shown.some((each) => each.item.id === item.parent_id))
140
- focus_row(item.parent_id);
141
- else
142
- return;
143
- }
144
- else if ((event.key === "Enter" || event.key === " ") && on_select)
145
- on_select(item);
146
- else
147
- return;
148
- event.preventDefault();
149
- }
150
- // ── Rows ───────────────────────────────────────────────────────────────────
151
- function new_item_row(parent_id) {
152
- if (!new_item || new_item.parent_id !== parent_id)
153
- return null;
154
- return _jsx("li", { role: "none", className: "tree-new", children: new_item.form });
155
- }
156
- function branch(node, depth) {
157
- const { item, children } = node;
158
- const has_children = children.length > 0;
159
- const is_collapsed = collapsed.has(item.id);
160
- const name = name_of?.(item) ?? "row";
161
- const new_child = new_item_row(item.id);
162
- const row_class = [
163
- "tree-row",
164
- class_of?.(item),
165
- dragged_id === item.id && "dragging",
166
- drop_target?.id === item.id && `drop-${drop_target.placement}`,
167
- ].filter(Boolean).join(" ");
168
- return (_jsxs("li", { ref: (element) => { if (element)
169
- row_elements.current.set(item.id, element);
170
- else
171
- row_elements.current.delete(item.id); }, role: "treeitem", className: "tree-item", "data-id": item.id, "aria-level": depth + 1, "aria-expanded": has_children ? !is_collapsed : undefined, "aria-selected": on_select || selected_ids ? is_selected(item.id) : undefined, tabIndex: tab_stop === item.id ? 0 : -1, onFocus: (event) => { if (event.target === event.currentTarget)
172
- set_focused_id(item.id); }, children: [_jsxs("div", { className: row_class, draggable: on_move ? true : undefined, onDragStart: (event) => {
173
- event.dataTransfer?.setData("text/plain", item.id);
174
- if (event.dataTransfer)
175
- event.dataTransfer.effectAllowed = "move";
176
- set_dragged_id(item.id);
177
- }, onDragEnd: end_drag, onDragOver: (event) => drag_over(event, item), onDragLeave: () => { if (drop_target?.id === item.id)
178
- set_drop_target(null); }, onDrop: (event) => drop(event, item), onClick: () => { focus_row(item.id); on_select?.(item); }, onDoubleClick: on_activate && (() => on_activate(item)), onContextMenu: on_context_menu && ((event) => on_context_menu(event, item)), children: [has_children
179
- ? (_jsx("button", { type: "button", className: "tree-toggle", tabIndex: -1, "aria-label": is_collapsed ? `Expand ${name}` : `Collapse ${name}`, onClick: (event) => { event.stopPropagation(); set_collapsed(item.id, !is_collapsed); }, children: _jsx("span", { "aria-hidden": "true", children: is_collapsed ? "▸" : "▾" }) }))
180
- : _jsx("span", { className: "tree-toggle", "aria-hidden": "true" }), _jsx("span", { className: "tree-label", children: render_item(item) }), render_extra && _jsx("span", { className: "tree-extra", children: render_extra(item) })] }), ((has_children && !is_collapsed) || new_child) && (_jsxs("ul", { role: "group", className: "tree-children", children: [!is_collapsed && children.map((child) => branch(child, depth + 1)), new_child] }))] }, item.id));
181
- }
182
- return (_jsxs("ul", { role: "tree", className: "tree", "aria-label": label, "aria-multiselectable": selected_ids ? true : undefined, onKeyDown: key_down, children: [roots.map((node) => branch(node, 0)), new_item_row(null)] }));
183
- }
@@ -1,8 +0,0 @@
1
- interface VolumeButtonProps {
2
- volume: number;
3
- on_change: (volume: number) => void;
4
- step?: number;
5
- label?: string;
6
- }
7
- export declare function VolumeButton({ volume, on_change, step, label }: VolumeButtonProps): import("react").JSX.Element;
8
- export {};
@@ -1,42 +0,0 @@
1
- import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- // A speaker button that opens a volume slider; scrolling over it adjusts the volume and a middle-click mutes.
3
- import { useEffect, useRef, useState } from "react";
4
- import { Popover } from "./menus.js";
5
- const speaker_icon = (volume) => volume === 0 ? "🔇" : volume < 34 ? "🔈" : volume < 67 ? "🔉" : "🔊";
6
- export function VolumeButton({ volume, on_change, step = 5, label = "Volume" }) {
7
- const [open, set_open] = useState(false);
8
- const trigger = useRef(null);
9
- // The level before muting, so unmuting returns to it.
10
- const before_mute = useRef(volume || 80);
11
- // The click that closed the popover by landing on its button must not open it again.
12
- const closed_at = useRef(0);
13
- const latest_volume = useRef(volume);
14
- latest_volume.current = volume;
15
- function set(next) {
16
- on_change(Math.max(0, Math.min(100, Math.round(next))));
17
- }
18
- function toggle_mute() {
19
- if (latest_volume.current > 0) {
20
- before_mute.current = latest_volume.current;
21
- set(0);
22
- }
23
- else {
24
- set(before_mute.current || 80);
25
- }
26
- }
27
- // React's wheel listener is passive and cannot stop the page scrolling, so this one is attached by hand.
28
- useEffect(() => {
29
- const on_wheel = (event) => {
30
- event.preventDefault();
31
- set(latest_volume.current + (event.deltaY < 0 ? step : -step));
32
- };
33
- const button = trigger.current;
34
- button.addEventListener("wheel", on_wheel, { passive: false });
35
- return () => button.removeEventListener("wheel", on_wheel);
36
- }, [step]);
37
- return (_jsxs(_Fragment, { children: [_jsx("button", { ref: trigger, type: "button", className: "icon volume-button", "aria-label": `${label} ${volume}%`, "aria-haspopup": "dialog", "aria-expanded": open, title: `${label} ${volume}% — scroll to adjust, middle-click to mute`, onClick: () => { if (Date.now() - closed_at.current > 300)
38
- set_open(!open); }, onAuxClick: (event) => { if (event.button === 1) {
39
- event.preventDefault();
40
- toggle_mute();
41
- } }, children: speaker_icon(volume) }), open && (_jsx(Popover, { anchor: trigger.current, label: label, on_close: () => { closed_at.current = Date.now(); set_open(false); }, children: _jsxs("div", { className: "volume-popover", children: [_jsx("button", { type: "button", className: "icon", onClick: toggle_mute, "aria-label": volume === 0 ? "Unmute" : "Mute", children: volume === 0 ? "🔇" : "🔊" }), _jsx("input", { className: "seek input-sm", style: { "--range-progress": `${volume}%` }, type: "range", min: 0, max: 100, step: step, value: volume, "aria-label": label, onChange: (event) => set(Number(event.currentTarget.value)) }), _jsxs("span", { className: "volume-percent", children: [volume, "%"] })] }) }))] }));
42
- }
@@ -1,3 +0,0 @@
1
- import type { Watch } from "../watch.ts";
2
- export type { Watch } from "../watch.ts";
3
- export declare function use_watch(watch: Watch): number;
@@ -1,5 +0,0 @@
1
- // Redraws a component whenever a watch it reads is bumped.
2
- import { useSyncExternalStore } from "react";
3
- export function use_watch(watch) {
4
- return useSyncExternalStore(watch.subscribe, watch.version);
5
- }
@@ -1,7 +0,0 @@
1
- export interface Route {
2
- parts: string[];
3
- query: URLSearchParams;
4
- }
5
- export declare function current_route(): Route;
6
- export declare function link(parts: string[], values?: Record<string, string | number | null | undefined>): string;
7
- export declare function replace_query(changes: Record<string, string | number | null | undefined>): URLSearchParams;
@@ -1,29 +0,0 @@
1
- // Reads and writes the hash route: a path such as #/hosts/odin, and a query holding a view's filters.
2
- export function current_route() {
3
- const hash = location.hash.replace(/^#\/?/, "");
4
- const [path, search = ""] = hash.split("?", 2);
5
- return { parts: path.split("/").filter(Boolean).map(decodeURIComponent), query: new URLSearchParams(search) };
6
- }
7
- // A link to a path built from parts, each one encoded, with an optional query.
8
- export function link(parts, values = {}) {
9
- const params = new URLSearchParams();
10
- for (const [name, value] of Object.entries(values)) {
11
- if (value !== null && value !== undefined && value !== "")
12
- params.set(name, String(value));
13
- }
14
- const search = params.toString();
15
- return `#/${parts.map(encodeURIComponent).join("/")}${search ? `?${search}` : ""}`;
16
- }
17
- // Changes the current route's query without a new history entry or a redraw, so typing in a filter keeps focus.
18
- export function replace_query(changes) {
19
- const route = current_route();
20
- for (const [name, value] of Object.entries(changes)) {
21
- if (value === null || value === undefined || value === "")
22
- route.query.delete(name);
23
- else
24
- route.query.set(name, String(value));
25
- }
26
- const search = route.query.toString();
27
- history.replaceState(null, "", `#/${route.parts.map(encodeURIComponent).join("/")}${search ? `?${search}` : ""}`);
28
- return route.query;
29
- }
@@ -1,25 +0,0 @@
1
- export type SelectionIntent = "replace" | "toggle" | "range" | "range-add";
2
- export declare function intent_for(modifiers: {
3
- shift?: boolean;
4
- ctrl?: boolean;
5
- }): SelectionIntent;
6
- export declare class Selection<Id = number> {
7
- private readonly picked;
8
- private anchor;
9
- get size(): number;
10
- has(id: Id): boolean;
11
- ids(): Id[];
12
- inspect(): {
13
- size: number;
14
- anchor: number | null;
15
- };
16
- clear(): void;
17
- apply(ordered: readonly Id[], index: number, intent: SelectionIntent): void;
18
- select_all(ordered: readonly Id[]): void;
19
- invert(ordered: readonly Id[]): void;
20
- retain(ordered: readonly Id[]): void;
21
- }
22
- export declare function toggled(picked: readonly string[], id: string): string[];
23
- export declare function with_range(order: readonly string[], picked: readonly string[], anchor_id: string | null, id: string): string[];
24
- export declare function still_shown(order: readonly string[], picked: readonly string[]): string[];
25
- export declare function row_after_key(key: string, current: number, last: number, rows_per_page: number): number | null;
@@ -1,106 +0,0 @@
1
- // Picking rows in a list: plain, Ctrl and Shift clicks, an anchor ranges measure from, and the row an arrow or page key moves to.
2
- export function intent_for(modifiers) {
3
- if (modifiers.shift)
4
- return modifiers.ctrl ? "range-add" : "range";
5
- return modifiers.ctrl ? "toggle" : "replace";
6
- }
7
- // ── Selection ────────────────────────────────────────────────────────────────
8
- export class Selection {
9
- picked = new Set();
10
- anchor = null;
11
- get size() {
12
- return this.picked.size;
13
- }
14
- has(id) {
15
- return this.picked.has(id);
16
- }
17
- ids() {
18
- return [...this.picked];
19
- }
20
- // The picks and the anchor, for finding why a range came out wrong.
21
- inspect() {
22
- return { size: this.picked.size, anchor: this.anchor };
23
- }
24
- clear() {
25
- this.picked.clear();
26
- this.anchor = null;
27
- }
28
- // Applies a click on the row at index in the list as shown.
29
- apply(ordered, index, intent) {
30
- if (index < 0 || index >= ordered.length)
31
- return;
32
- const id = ordered[index];
33
- if (intent === "replace") {
34
- this.picked.clear();
35
- this.picked.add(id);
36
- this.anchor = index;
37
- return;
38
- }
39
- if (intent === "toggle") {
40
- if (this.picked.has(id))
41
- this.picked.delete(id);
42
- else
43
- this.picked.add(id);
44
- this.anchor = index;
45
- return;
46
- }
47
- // The anchor stays put, so every extension measures from the same row.
48
- const from = this.anchor ?? index;
49
- const [low, high] = from <= index ? [from, index] : [index, from];
50
- if (intent === "range")
51
- this.picked.clear();
52
- for (let row = low; row <= high; row += 1)
53
- this.picked.add(ordered[row]);
54
- }
55
- select_all(ordered) {
56
- for (const id of ordered)
57
- this.picked.add(id);
58
- }
59
- invert(ordered) {
60
- const next = ordered.filter((id) => !this.picked.has(id));
61
- this.picked.clear();
62
- for (const id of next)
63
- this.picked.add(id);
64
- }
65
- // Drops picks no longer in the list, such as after a filter narrows it.
66
- retain(ordered) {
67
- const kept = new Set(ordered);
68
- for (const id of this.picked)
69
- if (!kept.has(id))
70
- this.picked.delete(id);
71
- }
72
- }
73
- // ── Ids ──────────────────────────────────────────────────────────────────────
74
- // The same picking as plain lists of ids held in an app's state, the anchor kept by id so it survives the rows moving between clicks.
75
- // Adds the id when missing and takes it out when there.
76
- export function toggled(picked, id) {
77
- return picked.includes(id) ? picked.filter((each) => each !== id) : [...picked, id];
78
- }
79
- // Every id between the anchor and the id in the shown order, both included, added to those picked; just the id when the anchor is not shown.
80
- export function with_range(order, picked, anchor_id, id) {
81
- const anchor_index = anchor_id === null ? -1 : order.indexOf(anchor_id);
82
- const index = order.indexOf(id);
83
- if (anchor_index === -1 || index === -1)
84
- return [id];
85
- const run = order.slice(Math.min(anchor_index, index), Math.max(anchor_index, index) + 1);
86
- return [...picked.filter((each) => !run.includes(each)), ...run];
87
- }
88
- // Keeps only the picked ids still shown, in the shown order.
89
- export function still_shown(order, picked) {
90
- const picked_ids = new Set(picked);
91
- return order.filter((id) => picked_ids.has(id));
92
- }
93
- // ── Keys ─────────────────────────────────────────────────────────────────────
94
- // The row an arrow, page, Home or End key moves to, held inside the list, or null for any other key.
95
- export function row_after_key(key, current, last, rows_per_page) {
96
- const clamp = (row) => Math.min(last, Math.max(0, row));
97
- switch (key) {
98
- case "ArrowDown": return clamp(current + 1);
99
- case "ArrowUp": return clamp(current - 1);
100
- case "PageDown": return clamp(current + rows_per_page);
101
- case "PageUp": return clamp(current - rows_per_page);
102
- case "Home": return 0;
103
- case "End": return Math.max(0, last);
104
- default: return null;
105
- }
106
- }
@@ -1,15 +0,0 @@
1
- export interface PaneSpec {
2
- min?: number;
3
- max?: number;
4
- grow?: boolean;
5
- }
6
- export declare const keyboard_step = 16;
7
- export declare const keyboard_step_large = 64;
8
- export declare function resize_at(sizes: readonly number[], index: number, delta: number, specs?: readonly PaneSpec[]): number[];
9
- export declare function fit(sizes: readonly number[], total: number, specs?: readonly PaneSpec[]): number[];
10
- export declare function distribute(total: number, specs: readonly PaneSpec[], preferred?: readonly (number | undefined)[]): number[];
11
- export declare function stored_sizes(storage_key: string, count: number): number[] | null;
12
- export declare function store_sizes(storage_key: string, sizes: readonly number[]): void;
13
- export type Axis = "horizontal" | "vertical";
14
- export declare function follow_drag(handle: HTMLElement, start: PointerEvent, axis: Axis, on_move: (moved: number) => void, on_end: () => void): void;
15
- export declare function key_step(event: KeyboardEvent, axis: Axis): number | null;
package/dist/web/split.js DELETED
@@ -1,104 +0,0 @@
1
- // Resizing panes: the arithmetic of what a drag may do, and the pointer and keyboard handling of a resize handle.
2
- const default_minimum = 40;
3
- // How far one arrow press moves a handle, and how far with Shift held.
4
- export const keyboard_step = 16;
5
- export const keyboard_step_large = 64;
6
- const minimum_of = (spec) => spec?.min ?? default_minimum;
7
- const maximum_of = (spec) => spec?.max ?? Number.POSITIVE_INFINITY;
8
- const clamp = (value, low, high) => Math.min(Math.max(value, low), high);
9
- // ── Sizing ───────────────────────────────────────────────────────────────────
10
- // Moves the boundary after pane index by delta pixels, changing only its two neighbours and stopping where either hits a limit.
11
- export function resize_at(sizes, index, delta, specs = []) {
12
- const before = sizes[index];
13
- const after = sizes[index + 1];
14
- if (before === undefined || after === undefined)
15
- return [...sizes];
16
- const most_growth = Math.min(maximum_of(specs[index]) - before, after - minimum_of(specs[index + 1]));
17
- const most_shrink = Math.min(before - minimum_of(specs[index]), maximum_of(specs[index + 1]) - after);
18
- const applied = clamp(delta, -most_shrink, most_growth);
19
- const next = [...sizes];
20
- next[index] = before + applied;
21
- next[index + 1] = after - applied;
22
- return next;
23
- }
24
- // Fits panes to the container's length, growing panes first and every pane otherwise, never below a pane's minimum.
25
- export function fit(sizes, total, specs = []) {
26
- if (sizes.length === 0)
27
- return [];
28
- const next = [...sizes];
29
- let difference = total - next.reduce((sum, size) => sum + size, 0);
30
- if (Math.abs(difference) < 0.5)
31
- return next.map(Math.round);
32
- const growers = next.map((_, index) => index).filter((index) => specs[index]?.grow);
33
- const targets = growers.length > 0 ? growers : next.map((_, index) => index);
34
- // A pane that hits its limit hands the rest back to the others, so a few passes share it out.
35
- for (let pass = 0; pass < 4 && Math.abs(difference) > 0.5; pass += 1) {
36
- const with_room = targets.filter((index) => difference > 0 ? next[index] < maximum_of(specs[index]) : next[index] > minimum_of(specs[index]));
37
- if (with_room.length === 0)
38
- break;
39
- const share = difference / with_room.length;
40
- for (const index of with_room) {
41
- const before = next[index];
42
- next[index] = clamp(before + share, minimum_of(specs[index]), maximum_of(specs[index]));
43
- difference -= next[index] - before;
44
- }
45
- }
46
- return next.map(Math.round);
47
- }
48
- // The first sizes for a container: each pane's preferred size or minimum, fitted to the length.
49
- export function distribute(total, specs, preferred) {
50
- return fit(specs.map((spec, index) => preferred?.[index] ?? spec.min ?? total / specs.length), total, specs);
51
- }
52
- // ── Remembering ──────────────────────────────────────────────────────────────
53
- // The sizes kept under the key, or null when there are none for this many panes.
54
- export function stored_sizes(storage_key, count) {
55
- try {
56
- const parsed = JSON.parse(localStorage.getItem(storage_key) ?? "null");
57
- if (!Array.isArray(parsed) || parsed.length !== count)
58
- return null;
59
- return parsed.every((size) => typeof size === "number" && Number.isFinite(size)) ? parsed : null;
60
- }
61
- catch {
62
- return null;
63
- }
64
- }
65
- export function store_sizes(storage_key, sizes) {
66
- try {
67
- localStorage.setItem(storage_key, JSON.stringify(sizes));
68
- }
69
- catch {
70
- // Without storage the sizes last until the page is closed.
71
- }
72
- }
73
- // Follows a drag that starts on the handle, handing on how far the pointer has moved along the axis.
74
- export function follow_drag(handle, start, axis, on_move, on_end) {
75
- if (start.button !== 0)
76
- return;
77
- start.preventDefault();
78
- const origin = axis === "horizontal" ? start.clientX : start.clientY;
79
- const move = (event) => on_move((axis === "horizontal" ? event.clientX : event.clientY) - origin);
80
- const stop = () => {
81
- if (handle.hasPointerCapture(start.pointerId))
82
- handle.releasePointerCapture(start.pointerId);
83
- handle.removeEventListener("pointermove", move);
84
- handle.removeEventListener("pointerup", stop);
85
- handle.removeEventListener("pointercancel", stop);
86
- handle.classList.remove("dragging");
87
- document.body.classList.remove("resizing", "vertical");
88
- on_end();
89
- };
90
- handle.setPointerCapture(start.pointerId);
91
- handle.addEventListener("pointermove", move);
92
- handle.addEventListener("pointerup", stop);
93
- handle.addEventListener("pointercancel", stop);
94
- handle.classList.add("dragging");
95
- document.body.classList.add("resizing");
96
- if (axis === "vertical")
97
- document.body.classList.add("vertical");
98
- }
99
- // How far an arrow key moves a handle along the axis, or null for any other key.
100
- export function key_step(event, axis) {
101
- const step = event.shiftKey ? keyboard_step_large : keyboard_step;
102
- const steps = axis === "horizontal" ? { ArrowLeft: -step, ArrowRight: step } : { ArrowUp: -step, ArrowDown: step };
103
- return steps[event.key] ?? null;
104
- }
@@ -1,3 +0,0 @@
1
- export declare class LoginNeeded extends Error {
2
- }
3
- export declare function pass_login_needed(error: unknown): boolean;