@evgkch/reactive-dom 0.1.0 → 0.2.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 (41) hide show
  1. package/README.md +71 -92
  2. package/dist/create-element.js +2 -2
  3. package/dist/index.d.ts +2 -2
  4. package/dist/index.d.ts.map +1 -1
  5. package/dist/index.js +1 -1
  6. package/dist/list.js +4 -4
  7. package/dist/log.d.ts +9 -14
  8. package/dist/log.d.ts.map +1 -1
  9. package/dist/log.js +20 -78
  10. package/dist/slot.js +4 -4
  11. package/dist/unmount.js +2 -2
  12. package/examples/index.html +4 -4
  13. package/examples/pages/console/index.css +73 -0
  14. package/examples/pages/console/index.html +13 -0
  15. package/examples/pages/console/index.ts +11 -0
  16. package/examples/pages/console/model/state.ts +25 -0
  17. package/examples/pages/console/ui/App.ts +57 -0
  18. package/examples/pages/console/ui/LineItem.ts +13 -0
  19. package/examples/pages/monitor/index.css +297 -0
  20. package/examples/pages/monitor/index.html +13 -0
  21. package/examples/pages/monitor/index.ts +24 -0
  22. package/examples/pages/monitor/model/state.ts +30 -0
  23. package/examples/pages/monitor/ui/App.ts +207 -0
  24. package/examples/pages/monitor/ui/TaskItem.ts +41 -0
  25. package/examples/{stream/styles.css → pages/stream/index.css} +1 -75
  26. package/examples/pages/stream/index.html +13 -0
  27. package/examples/pages/stream/index.ts +15 -0
  28. package/examples/pages/stream/model/state.ts +45 -0
  29. package/examples/pages/stream/ui/App.ts +70 -0
  30. package/examples/pages/stream/ui/LogLine.ts +27 -0
  31. package/examples/shared/lib/index.ts +19 -0
  32. package/examples/{console/styles.css → shared/styles/common.css} +5 -77
  33. package/examples/tsconfig.json +13 -0
  34. package/package.json +10 -8
  35. package/examples/console/app.js +0 -97
  36. package/examples/console/index.html +0 -23
  37. package/examples/monitor/app.js +0 -260
  38. package/examples/monitor/index.html +0 -23
  39. package/examples/monitor/styles.css +0 -425
  40. package/examples/stream/app.js +0 -136
  41. package/examples/stream/index.html +0 -23
@@ -0,0 +1,73 @@
1
+ @import url("../../shared/styles/common.css");
2
+
3
+ .panel {
4
+ width: 560px;
5
+ max-width: 100%;
6
+ height: 400px;
7
+ border: 1px solid var(--border);
8
+ background: var(--surface);
9
+ display: flex;
10
+ flex-direction: column;
11
+ }
12
+
13
+ .output {
14
+ flex: 1;
15
+ min-height: 0;
16
+ overflow-y: scroll;
17
+ padding: 0.5rem 0.75rem;
18
+ border-bottom: 1px solid var(--border);
19
+ }
20
+
21
+ .line {
22
+ padding: 0.2rem 0;
23
+ font-size: 12px;
24
+ word-break: break-word;
25
+ }
26
+
27
+ .line.in {
28
+ color: var(--accent);
29
+ }
30
+
31
+ .line.in::before {
32
+ content: "> ";
33
+ color: var(--muted);
34
+ }
35
+
36
+ .line.out {
37
+ color: var(--ink);
38
+ }
39
+
40
+ .line.out::before {
41
+ content: " ";
42
+ }
43
+
44
+ .line.err {
45
+ color: var(--red);
46
+ }
47
+
48
+ .console-input {
49
+ flex: 1;
50
+ font-family: "JetBrains Mono", monospace;
51
+ font-size: 12px;
52
+ background: transparent;
53
+ border: none;
54
+ padding: 0.55rem 0;
55
+ color: var(--ink);
56
+ outline: none;
57
+ caret-color: var(--accent);
58
+ }
59
+
60
+ .console-input::placeholder {
61
+ color: var(--dim);
62
+ }
63
+
64
+ .empty {
65
+ padding: 0.5rem 0;
66
+ font-size: 11px;
67
+ color: var(--dim);
68
+ }
69
+
70
+ .empty::before {
71
+ content: "// type a command and press Enter";
72
+ color: var(--border);
73
+ }
@@ -0,0 +1,13 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1">
6
+ <title>Console — reactive-dom</title>
7
+ <link rel="stylesheet" href="./index.css">
8
+ </head>
9
+ <body>
10
+ <div id="app"></div>
11
+ <script type="module" src="./index.ts"></script>
12
+ </body>
13
+ </html>
@@ -0,0 +1,11 @@
1
+ import { configureLogging } from "../../shared/lib/index.js";
2
+ import { lines } from "./model/state.js";
3
+ import { App } from "./ui/App.js";
4
+
5
+ configureLogging();
6
+
7
+ export { App, lines };
8
+
9
+ if (typeof document !== "undefined" && document.getElementById("app")) {
10
+ document.getElementById("app")!.appendChild(App({}));
11
+ }
@@ -0,0 +1,25 @@
1
+ import * as R from "@evgkch/reactive";
2
+
3
+ export interface LineEntry {
4
+ type: string;
5
+ text: string;
6
+ }
7
+
8
+ export const lines = R.List([
9
+ R.Struct({ type: "out", text: "Welcome. Type help, ping, or echo <msg>." }),
10
+ ]) as unknown as LineEntry[] & { watch(fn: (p: unknown) => void): () => void };
11
+
12
+ export const replies: Record<string, string> = {
13
+ help: "Commands: help, ping, echo <msg>, clear.",
14
+ ping: "pong",
15
+ clear: "",
16
+ };
17
+
18
+ export function respond(cmd: string): string | null {
19
+ const t = cmd.trim().toLowerCase();
20
+ if (t === "clear") return null;
21
+ if (t === "help") return replies.help;
22
+ if (t === "ping") return replies.ping;
23
+ if (t.startsWith("echo ")) return cmd.slice(5).trim() || "(empty)";
24
+ return `Unknown: ${cmd}`;
25
+ }
@@ -0,0 +1,57 @@
1
+ import * as R from "@evgkch/reactive";
2
+ import * as UI from "@evgkch/reactive-dom";
3
+ import type { LineEntry } from "../model/state.js";
4
+ import { lines, respond } from "../model/state.js";
5
+ import { LineItem } from "./LineItem.js";
6
+
7
+ export const App = UI.Struct(
8
+ `<div class="panel">
9
+ <div class="titlebar">
10
+ <div class="titlebar-left">
11
+ <span class="dot dot-red"></span>
12
+ <span class="dot dot-yellow"></span>
13
+ <span class="dot dot-green"></span>
14
+ <span class="titlebar-title">SYS://CONSOLE</span>
15
+ </div>
16
+ <div class="titlebar-right" data-ref="count">0 lines</div>
17
+ </div>
18
+ <div class="output" data-ref="output"></div>
19
+ <div class="input-row">
20
+ <span class="prompt">$</span>
21
+ <input class="console-input" data-ref="input" placeholder="command..." />
22
+ </div>
23
+ </div>`,
24
+ (props, refs, ctx) => {
25
+ const input = refs.input as HTMLInputElement;
26
+
27
+ ctx.list(refs.output, lines, (item) => ({ item }), LineItem, {
28
+ onAdd: () => {
29
+ requestAnimationFrame(() => {
30
+ refs.output.scrollTop = refs.output.scrollHeight;
31
+ });
32
+ },
33
+ });
34
+
35
+ ctx.batch(() => {
36
+ refs.count.textContent = lines.length + " line" + (lines.length !== 1 ? "s" : "");
37
+ });
38
+
39
+ function submit() {
40
+ const text = input.value.trim();
41
+ if (!text) return;
42
+ lines.push(R.Struct({ type: "in", text }) as unknown as LineEntry);
43
+ input.value = "";
44
+ const out = respond(text);
45
+ if (out !== null) {
46
+ lines.push(R.Struct({ type: "out", text: out }) as unknown as LineEntry);
47
+ } else {
48
+ lines.splice(0, lines.length);
49
+ lines.push(R.Struct({ type: "out", text: "Welcome. Type help, ping, or echo <msg>." }) as unknown as LineEntry);
50
+ }
51
+ }
52
+
53
+ input.addEventListener("keydown", (e: KeyboardEvent) => {
54
+ if (e.key === "Enter") submit();
55
+ });
56
+ }
57
+ );
@@ -0,0 +1,13 @@
1
+ import * as UI from "@evgkch/reactive-dom";
2
+ import type { LineEntry } from "../model/state.js";
3
+
4
+ export const LineItem = UI.List(
5
+ `<div class="line"></div>`,
6
+ (props: { item: LineEntry }, refs, ctx) => {
7
+ ctx.batch(() => {
8
+ const { type, text } = props.item;
9
+ refs.el.textContent = text;
10
+ refs.el.className = "line " + type;
11
+ });
12
+ }
13
+ );
@@ -0,0 +1,297 @@
1
+ @import url("../../shared/styles/common.css");
2
+
3
+ .monitor {
4
+ width: 100%;
5
+ max-width: 480px;
6
+ border: 1px solid var(--border);
7
+ background: var(--surface);
8
+ }
9
+
10
+ .input-row {
11
+ border-bottom: 1px solid var(--border);
12
+ }
13
+
14
+ .row {
15
+ display: flex;
16
+ align-items: stretch;
17
+ border-bottom: 1px solid var(--border);
18
+ }
19
+
20
+ .row:last-child {
21
+ border-bottom: none;
22
+ }
23
+
24
+ .cell {
25
+ padding: 0.5rem 0.75rem;
26
+ flex: 1;
27
+ }
28
+
29
+ .cell + .cell {
30
+ border-left: 1px solid var(--border);
31
+ }
32
+
33
+ .metrics {
34
+ display: grid;
35
+ grid-template-columns: 1fr 1fr 1fr;
36
+ border-bottom: 1px solid var(--border);
37
+ }
38
+
39
+ .metric {
40
+ padding: 0.5rem 0.75rem;
41
+ border-right: 1px solid var(--border);
42
+ }
43
+
44
+ .metric:last-child {
45
+ border-right: none;
46
+ }
47
+
48
+ .metric-label {
49
+ font-size: 10px;
50
+ color: var(--muted);
51
+ letter-spacing: 0.08em;
52
+ margin-bottom: 0.25rem;
53
+ }
54
+
55
+ .metric-bar {
56
+ height: 3px;
57
+ background: var(--border);
58
+ margin-bottom: 0.2rem;
59
+ position: relative;
60
+ }
61
+
62
+ .metric-fill {
63
+ height: 100%;
64
+ background: var(--accent);
65
+ transition: width 0.8s ease;
66
+ }
67
+
68
+ .metric-fill.warn { background: var(--yellow); }
69
+ .metric-fill.crit { background: var(--red); }
70
+
71
+ .metric-val {
72
+ font-size: 11px;
73
+ font-weight: 700;
74
+ color: var(--ink);
75
+ }
76
+
77
+ .new-input {
78
+ flex: 1;
79
+ font-family: "JetBrains Mono", monospace;
80
+ font-size: 12px;
81
+ background: transparent;
82
+ border: none;
83
+ padding: 0.55rem 0;
84
+ color: var(--ink);
85
+ outline: none;
86
+ caret-color: var(--accent);
87
+ }
88
+
89
+ .new-input::placeholder {
90
+ color: var(--dim);
91
+ }
92
+
93
+ .kbd-hint {
94
+ font-size: 10px;
95
+ color: var(--dim);
96
+ flex-shrink: 0;
97
+ }
98
+
99
+ .task-list {
100
+ list-style: none;
101
+ min-height: 2rem;
102
+ }
103
+
104
+ .task-list li {
105
+ display: grid;
106
+ grid-template-columns: 18px 1fr auto auto;
107
+ align-items: center;
108
+ gap: 0.6rem;
109
+ padding: 0.45rem 0.75rem;
110
+ border-bottom: 1px solid #1a1a1a;
111
+ transition: background 0.1s;
112
+ cursor: default;
113
+ visibility: visible;
114
+ opacity: 1;
115
+ }
116
+
117
+ .task-list li:last-child {
118
+ border-bottom: none;
119
+ }
120
+
121
+ .task-list li:hover {
122
+ background: #131313;
123
+ }
124
+
125
+ .task-list li.done {
126
+ opacity: 0.5;
127
+ }
128
+
129
+ .task-list li.selected {
130
+ background: #141a14;
131
+ }
132
+
133
+ .task-check {
134
+ appearance: none;
135
+ width: 14px;
136
+ height: 14px;
137
+ border: 1px solid var(--muted);
138
+ cursor: pointer;
139
+ position: relative;
140
+ flex-shrink: 0;
141
+ transition: border-color 0.1s;
142
+ }
143
+
144
+ .task-check:hover {
145
+ border-color: var(--accent);
146
+ }
147
+
148
+ .task-check:checked {
149
+ border-color: var(--accent);
150
+ }
151
+
152
+ .task-check:checked::after {
153
+ content: "✓";
154
+ position: absolute;
155
+ inset: 0;
156
+ display: flex;
157
+ align-items: center;
158
+ justify-content: center;
159
+ font-size: 10px;
160
+ color: var(--accent);
161
+ font-weight: 700;
162
+ }
163
+
164
+ .task-text {
165
+ font-size: 12px;
166
+ word-break: break-word;
167
+ color: var(--ink);
168
+ }
169
+
170
+ .task-list li.done .task-text {
171
+ color: var(--muted);
172
+ text-decoration: line-through;
173
+ text-decoration-color: #444;
174
+ }
175
+
176
+ .task-status {
177
+ font-size: 10px;
178
+ letter-spacing: 0.06em;
179
+ color: var(--muted);
180
+ flex-shrink: 0;
181
+ width: 52px;
182
+ text-align: right;
183
+ }
184
+
185
+ .task-list li:not(.done) .task-status {
186
+ color: var(--blue);
187
+ }
188
+
189
+ .task-list li.done .task-status {
190
+ color: var(--dim);
191
+ }
192
+
193
+ .task-remove {
194
+ font-family: "JetBrains Mono", monospace;
195
+ font-size: 10px;
196
+ background: none;
197
+ border: none;
198
+ color: var(--dim);
199
+ cursor: pointer;
200
+ padding: 0.1rem 0.2rem;
201
+ opacity: 0;
202
+ transition: opacity 0.1s, color 0.1s;
203
+ flex-shrink: 0;
204
+ }
205
+
206
+ .task-list li:hover .task-remove {
207
+ opacity: 1;
208
+ }
209
+
210
+ .task-remove:hover {
211
+ color: var(--red);
212
+ }
213
+
214
+ .statusbar {
215
+ display: flex;
216
+ align-items: center;
217
+ justify-content: space-between;
218
+ padding: 0.35rem 0.75rem;
219
+ border-top: 1px solid var(--border);
220
+ background: var(--bg);
221
+ }
222
+
223
+ .status-left {
224
+ font-size: 10px;
225
+ color: var(--muted);
226
+ display: flex;
227
+ gap: 1rem;
228
+ }
229
+
230
+ .status-left span {
231
+ color: var(--ink);
232
+ }
233
+
234
+ .filters {
235
+ display: flex;
236
+ gap: 0;
237
+ }
238
+
239
+ .filters button {
240
+ font-family: "JetBrains Mono", monospace;
241
+ font-size: 10px;
242
+ background: none;
243
+ border: 1px solid transparent;
244
+ color: var(--muted);
245
+ cursor: pointer;
246
+ padding: 0.15rem 0.5rem;
247
+ letter-spacing: 0.05em;
248
+ transition: color 0.1s, border-color 0.1s;
249
+ }
250
+
251
+ .filters button:hover {
252
+ color: var(--ink);
253
+ }
254
+
255
+ .filters button.active {
256
+ color: var(--accent);
257
+ border-color: var(--border);
258
+ }
259
+
260
+ .hotkeys {
261
+ display: flex;
262
+ gap: 1rem;
263
+ padding: 0.35rem 0.75rem;
264
+ border-top: 1px solid var(--border);
265
+ background: var(--bg);
266
+ flex-wrap: wrap;
267
+ }
268
+
269
+ .hotkey {
270
+ font-size: 10px;
271
+ color: var(--dim);
272
+ display: flex;
273
+ align-items: center;
274
+ gap: 0.3rem;
275
+ }
276
+
277
+ .hotkey kbd {
278
+ font-family: "JetBrains Mono", monospace;
279
+ font-size: 9px;
280
+ background: var(--border);
281
+ color: var(--ink);
282
+ padding: 0.1rem 0.3rem;
283
+ border-radius: 2px;
284
+ border-bottom: 1px solid #333;
285
+ }
286
+
287
+ .empty {
288
+ padding: 1.25rem 0.75rem;
289
+ font-size: 11px;
290
+ color: var(--dim);
291
+ border-bottom: 1px solid #1a1a1a;
292
+ }
293
+
294
+ .empty::before {
295
+ content: "// ";
296
+ color: var(--border);
297
+ }
@@ -0,0 +1,13 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1">
6
+ <title>Monitor — reactive-dom</title>
7
+ <link rel="stylesheet" href="./index.css">
8
+ </head>
9
+ <body>
10
+ <div id="app"></div>
11
+ <script type="module" src="./index.ts"></script>
12
+ </body>
13
+ </html>
@@ -0,0 +1,24 @@
1
+ import * as R from "@evgkch/reactive";
2
+ import { configureLogging } from "../../shared/lib/index.js";
3
+ import { cpu, filter, mem, tasks, uptime } from "./model/state.js";
4
+ import { App } from "./ui/App.js";
5
+
6
+ configureLogging();
7
+
8
+ if (typeof document !== "undefined" && document.getElementById("app")) {
9
+ setInterval(() => {
10
+ cpu.set(
11
+ Math.max(10, Math.min(99, cpu.get() + Math.floor(Math.random() * 11) - 5))
12
+ );
13
+ mem.set(
14
+ Math.max(20, Math.min(95, mem.get() + Math.floor(Math.random() * 7) - 3))
15
+ );
16
+ uptime.update((n: number) => n + 1);
17
+ }, 1000);
18
+ }
19
+
20
+ export { App, filter, tasks };
21
+
22
+ if (typeof document !== "undefined" && document.getElementById("app")) {
23
+ document.getElementById("app")!.appendChild(App({}));
24
+ }
@@ -0,0 +1,30 @@
1
+ import * as R from "@evgkch/reactive";
2
+
3
+ export interface TaskEntry {
4
+ text: string;
5
+ done: boolean;
6
+ }
7
+
8
+ export const filter = R.Value("all");
9
+ export const cpu = R.Value(42);
10
+ export const mem = R.Value(61);
11
+ export const uptime = R.Value(0);
12
+
13
+ export const tasks = R.List([
14
+ R.Struct({ text: "infiltrate server", done: false }),
15
+ R.Struct({ text: "extract data", done: true }),
16
+ R.Struct({ text: "cover tracks", done: false }),
17
+ ]) as unknown as TaskEntry[] & { watch(fn: (p: unknown) => void): () => void };
18
+
19
+ export function fmtUptime(s: number): string {
20
+ const h = String(Math.floor(s / 3600)).padStart(2, "0");
21
+ const m = String(Math.floor((s % 3600) / 60)).padStart(2, "0");
22
+ const sec = String(s % 60).padStart(2, "0");
23
+ return `${h}:${m}:${sec}`;
24
+ }
25
+
26
+ export function barClass(val: number): string {
27
+ if (val >= 85) return "crit";
28
+ if (val >= 65) return "warn";
29
+ return "";
30
+ }