@evgkch/reactive-dom 0.1.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/dist/list.js ADDED
@@ -0,0 +1,79 @@
1
+ import { createElement } from "./create-element.js";
2
+ import { parseTemplate } from "./create-element.js";
3
+ import { logListAdd, logListMount, logListRemove } from "./log.js";
4
+ import { unmount } from "./unmount.js";
5
+ /**
6
+ * Mount reactive list into container. Patches via Watch.
7
+ * On remove: unmount(el), then onRemove(el, done) or el.remove().
8
+ */
9
+ export function mountList(container, source, factory, hooks) {
10
+ const nodes = new Map();
11
+ function addItem(item, ref, runOnAdd = true) {
12
+ const el = factory(item);
13
+ nodes.set(item, el);
14
+ container.insertBefore(el, ref);
15
+ if (runOnAdd) {
16
+ logListAdd(el);
17
+ hooks?.onAdd?.(el);
18
+ }
19
+ return el;
20
+ }
21
+ function removeItem(item) {
22
+ const el = nodes.get(item);
23
+ if (!el)
24
+ return;
25
+ nodes.delete(item);
26
+ logListRemove(el);
27
+ unmount(el);
28
+ if (hooks?.onRemove) {
29
+ hooks.onRemove(el, () => el.remove());
30
+ }
31
+ else {
32
+ el.remove();
33
+ }
34
+ }
35
+ logListMount(container);
36
+ // Initial mount: no onAdd so items are visible immediately (onAdd often animates from opacity 0)
37
+ source.forEach((item) => addItem(item, null, false));
38
+ const stopWatch = source.watch(({ start, removed, added }) => {
39
+ removed.forEach((item) => removeItem(item));
40
+ let ref = container.children[start] ?? null;
41
+ for (let i = added.length - 1; i >= 0; i--) {
42
+ ref = addItem(added[i], ref);
43
+ }
44
+ });
45
+ const stop = () => {
46
+ stopWatch();
47
+ nodes.forEach((el) => {
48
+ unmount(el);
49
+ el.remove();
50
+ });
51
+ nodes.clear();
52
+ };
53
+ return stop;
54
+ }
55
+ function isElement(x) {
56
+ return typeof x === "object" && x !== null && x.nodeType === 1;
57
+ }
58
+ function isWatchable(x) {
59
+ return typeof x === "object" && x !== null && typeof x.watch === "function" && typeof x.forEach === "function";
60
+ }
61
+ /**
62
+ * List(html, setup) returns a dual-purpose function:
63
+ * - (props) => Element — create one item (for use in ctx.list as itemFactory)
64
+ * - (container, source, propsFactory, hooks?) => () => void — mount list, returns stop
65
+ */
66
+ export function List(html, setup) {
67
+ const tmpl = parseTemplate(html);
68
+ function listComponent(...args) {
69
+ if (args.length >= 2 && isElement(args[0]) && isWatchable(args[1])) {
70
+ const container = args[0];
71
+ const source = args[1];
72
+ const propsFactory = (args[2] ?? ((x) => ({ item: x })));
73
+ const hooks = args[3];
74
+ return mountList(container, source, (item) => listComponent(propsFactory(item)), hooks);
75
+ }
76
+ return createElement(tmpl, setup, args[0]);
77
+ }
78
+ return listComponent;
79
+ }
package/dist/log.d.ts ADDED
@@ -0,0 +1,18 @@
1
+ export type LogLevel = false | true | "verbose";
2
+ export declare function setLogLevel(level: LogLevel): void;
3
+ export interface ConfigureOptions {
4
+ log?: LogLevel;
5
+ }
6
+ export declare function configure(opts?: ConfigureOptions): void;
7
+ export declare function getLogLevel(): LogLevel;
8
+ /** Stable id for a component root element — use in console to find references. */
9
+ export declare function getComponentId(el: Element): string;
10
+ export declare function logMount(el: Element): void;
11
+ export declare function logUnmount(el: Element): void;
12
+ export declare function logListMount(container: Element): void;
13
+ export declare function logListAdd(el: Element): void;
14
+ export declare function logListRemove(el: Element): void;
15
+ export declare function logSlotMount(container: Element): void;
16
+ export declare function logSlotSwap(container: Element, prev: Element | null, next: Element | null): void;
17
+ export declare function logSlotStop(container: Element): void;
18
+ //# sourceMappingURL=log.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"log.d.ts","sourceRoot":"","sources":["../src/log.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,QAAQ,GAAG,KAAK,GAAG,IAAI,GAAG,SAAS,CAAC;AAOhD,wBAAgB,WAAW,CAAC,KAAK,EAAE,QAAQ,GAAG,IAAI,CAEjD;AAED,MAAM,WAAW,gBAAgB;IAC7B,GAAG,CAAC,EAAE,QAAQ,CAAC;CAClB;AAED,wBAAgB,SAAS,CAAC,IAAI,GAAE,gBAAqB,GAAG,IAAI,CAE3D;AAED,wBAAgB,WAAW,IAAI,QAAQ,CAEtC;AAED,kFAAkF;AAClF,wBAAgB,cAAc,CAAC,EAAE,EAAE,OAAO,GAAG,MAAM,CAOlD;AAUD,wBAAgB,QAAQ,CAAC,EAAE,EAAE,OAAO,GAAG,IAAI,CAK1C;AAED,wBAAgB,UAAU,CAAC,EAAE,EAAE,OAAO,GAAG,IAAI,CAI5C;AAED,wBAAgB,YAAY,CAAC,SAAS,EAAE,OAAO,GAAG,IAAI,CAIrD;AAED,wBAAgB,UAAU,CAAC,EAAE,EAAE,OAAO,GAAG,IAAI,CAI5C;AAED,wBAAgB,aAAa,CAAC,EAAE,EAAE,OAAO,GAAG,IAAI,CAI/C;AAED,wBAAgB,YAAY,CAAC,SAAS,EAAE,OAAO,GAAG,IAAI,CAIrD;AAED,wBAAgB,WAAW,CAAC,SAAS,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,GAAG,IAAI,EAAE,IAAI,EAAE,OAAO,GAAG,IAAI,GAAG,IAAI,CAMhG;AAED,wBAAgB,WAAW,CAAC,SAAS,EAAE,OAAO,GAAG,IAAI,CAIpD"}
package/dist/log.js ADDED
@@ -0,0 +1,84 @@
1
+ const PREFIX = "[reactive-dom]";
2
+ let logLevel = false;
3
+ const componentIds = new WeakMap();
4
+ let nextId = 0;
5
+ export function setLogLevel(level) {
6
+ logLevel = level;
7
+ }
8
+ export function configure(opts = {}) {
9
+ if (opts.log !== undefined)
10
+ setLogLevel(opts.log);
11
+ }
12
+ export function getLogLevel() {
13
+ return logLevel;
14
+ }
15
+ /** Stable id for a component root element — use in console to find references. */
16
+ export function getComponentId(el) {
17
+ let id = componentIds.get(el);
18
+ if (id === undefined) {
19
+ id = `c${++nextId}`;
20
+ componentIds.set(el, id);
21
+ }
22
+ return id;
23
+ }
24
+ function log(msg, id, extra) {
25
+ if (!logLevel)
26
+ return;
27
+ const parts = [PREFIX, msg];
28
+ if (id)
29
+ parts.push(id);
30
+ if (extra)
31
+ parts.push(extra);
32
+ console.log(parts.join(" "));
33
+ }
34
+ export function logMount(el) {
35
+ if (!logLevel)
36
+ return;
37
+ const id = getComponentId(el);
38
+ const tag = el.tagName.toLowerCase();
39
+ log("mount", id, `<${tag}>`);
40
+ }
41
+ export function logUnmount(el) {
42
+ if (!logLevel)
43
+ return;
44
+ const id = getComponentId(el);
45
+ log("unmount", id);
46
+ }
47
+ export function logListMount(container) {
48
+ if (!logLevel)
49
+ return;
50
+ const id = getComponentId(container);
51
+ log("list mount", id);
52
+ }
53
+ export function logListAdd(el) {
54
+ if (!logLevel)
55
+ return;
56
+ const id = getComponentId(el);
57
+ log("list add", id);
58
+ }
59
+ export function logListRemove(el) {
60
+ if (!logLevel)
61
+ return;
62
+ const id = getComponentId(el);
63
+ log("list remove", id);
64
+ }
65
+ export function logSlotMount(container) {
66
+ if (!logLevel)
67
+ return;
68
+ const id = getComponentId(container);
69
+ log("slot mount", id);
70
+ }
71
+ export function logSlotSwap(container, prev, next) {
72
+ if (!logLevel)
73
+ return;
74
+ const id = getComponentId(container);
75
+ const prevId = prev ? getComponentId(prev) : "-";
76
+ const nextId = next ? getComponentId(next) : "-";
77
+ log("slot swap", id, `${prevId} → ${nextId}`);
78
+ }
79
+ export function logSlotStop(container) {
80
+ if (!logLevel)
81
+ return;
82
+ const id = getComponentId(container);
83
+ log("slot stop", id);
84
+ }
package/dist/slot.d.ts ADDED
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Conditional slot. Getter returns Element | null.
3
+ * On change — unmount old, insert new. Anchor Comment fixes position.
4
+ */
5
+ export declare function Slot(container: Element, getter: () => Element | null): () => void;
6
+ //# sourceMappingURL=slot.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"slot.d.ts","sourceRoot":"","sources":["../src/slot.ts"],"names":[],"mappings":"AAIA;;;GAGG;AACH,wBAAgB,IAAI,CAChB,SAAS,EAAE,OAAO,EAClB,MAAM,EAAE,MAAM,OAAO,GAAG,IAAI,GAC7B,MAAM,IAAI,CAgCZ"}
package/dist/slot.js ADDED
@@ -0,0 +1,38 @@
1
+ import { Batch } from "@evgkch/reactive";
2
+ import { logSlotMount, logSlotStop, logSlotSwap } from "./log.js";
3
+ import { unmount } from "./unmount.js";
4
+ /**
5
+ * Conditional slot. Getter returns Element | null.
6
+ * On change — unmount old, insert new. Anchor Comment fixes position.
7
+ */
8
+ export function Slot(container, getter) {
9
+ const anchor = document.createComment("slot");
10
+ container.appendChild(anchor);
11
+ logSlotMount(container);
12
+ let current = null;
13
+ const stopBatch = Batch(() => {
14
+ const next = getter();
15
+ if (next === current)
16
+ return;
17
+ if (current) {
18
+ unmount(current);
19
+ current.remove();
20
+ }
21
+ if (next) {
22
+ container.insertBefore(next, anchor);
23
+ }
24
+ logSlotSwap(container, current, next);
25
+ current = next;
26
+ });
27
+ const stop = () => {
28
+ stopBatch();
29
+ logSlotStop(container);
30
+ if (current) {
31
+ unmount(current);
32
+ current.remove();
33
+ current = null;
34
+ }
35
+ anchor.remove();
36
+ };
37
+ return stop;
38
+ }
@@ -0,0 +1,11 @@
1
+ import type { Refs } from "./ctx.js";
2
+ import type { Context } from "./ctx.js";
3
+ export type { Refs } from "./ctx.js";
4
+ export { Context } from "./ctx.js";
5
+ export type StructSetup<P> = (props: P, refs: Refs, ctx: Context) => void;
6
+ /**
7
+ * Struct(html, setup) → factory. Template parsed once; each call clones and runs setup(props, refs, ctx).
8
+ * Stop stored in WeakMap; use UI.unmount(el) to teardown.
9
+ */
10
+ export declare function Struct<P>(html: string, setup: StructSetup<P>): (props: P) => Element;
11
+ //# sourceMappingURL=struct.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"struct.d.ts","sourceRoot":"","sources":["../src/struct.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,UAAU,CAAC;AACrC,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,UAAU,CAAC;AAExC,YAAY,EAAE,IAAI,EAAE,MAAM,UAAU,CAAC;AACrC,OAAO,EAAE,OAAO,EAAE,MAAM,UAAU,CAAC;AAEnC,MAAM,MAAM,WAAW,CAAC,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,GAAG,EAAE,OAAO,KAAK,IAAI,CAAC;AAE1E;;;GAGG;AACH,wBAAgB,MAAM,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,WAAW,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,EAAE,CAAC,KAAK,OAAO,CAGpF"}
package/dist/struct.js ADDED
@@ -0,0 +1,10 @@
1
+ import { createElement, parseTemplate } from "./create-element.js";
2
+ export { Context } from "./ctx.js";
3
+ /**
4
+ * Struct(html, setup) → factory. Template parsed once; each call clones and runs setup(props, refs, ctx).
5
+ * Stop stored in WeakMap; use UI.unmount(el) to teardown.
6
+ */
7
+ export function Struct(html, setup) {
8
+ const tmpl = parseTemplate(html);
9
+ return (props) => createElement(tmpl, setup, props);
10
+ }
@@ -0,0 +1,6 @@
1
+ export declare function setComponentStop(el: Element, stop: () => void): void;
2
+ /** Unsubscribe component and all its child Batch/List/Slot. */
3
+ export declare function unmount(el: Element): void;
4
+ /** Unmount and remove element in one call. */
5
+ export declare function remove(el: Element): void;
6
+ //# sourceMappingURL=unmount.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"unmount.d.ts","sourceRoot":"","sources":["../src/unmount.ts"],"names":[],"mappings":"AAOA,wBAAgB,gBAAgB,CAAC,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,IAAI,GAAG,IAAI,CAEpE;AAED,+DAA+D;AAC/D,wBAAgB,OAAO,CAAC,EAAE,EAAE,OAAO,GAAG,IAAI,CAOzC;AAED,8CAA8C;AAC9C,wBAAgB,MAAM,CAAC,EAAE,EAAE,OAAO,GAAG,IAAI,CAGxC"}
@@ -0,0 +1,22 @@
1
+ import { logUnmount } from "./log.js";
2
+ /**
3
+ * WeakMap: Element → stop. No leak — GC collects with the element.
4
+ */
5
+ const componentMap = new WeakMap();
6
+ export function setComponentStop(el, stop) {
7
+ componentMap.set(el, stop);
8
+ }
9
+ /** Unsubscribe component and all its child Batch/List/Slot. */
10
+ export function unmount(el) {
11
+ const stop = componentMap.get(el);
12
+ if (stop) {
13
+ logUnmount(el);
14
+ stop();
15
+ componentMap.delete(el);
16
+ }
17
+ }
18
+ /** Unmount and remove element in one call. */
19
+ export function remove(el) {
20
+ unmount(el);
21
+ el.remove();
22
+ }
@@ -0,0 +1,7 @@
1
+ *,
2
+ *::before,
3
+ *::after {
4
+ box-sizing: border-box;
5
+ margin: 0;
6
+ padding: 0;
7
+ }
@@ -0,0 +1,97 @@
1
+ import * as R from "@evgkch/reactive";
2
+ import * as UI from "@evgkch/reactive-dom";
3
+
4
+ if (typeof document !== "undefined" && document.getElementById("app")) {
5
+ R.configure({ log: true });
6
+ UI.configure({ log: true });
7
+ }
8
+
9
+ // Reactive state: list of { type: 'in'|'out'|'err', text }
10
+ const lines = R.List([
11
+ R.Struct({ type: "out", text: "Welcome. Type help, ping, or echo <msg>." }),
12
+ ]);
13
+
14
+ const replies = {
15
+ help: "Commands: help, ping, echo <msg>, clear.",
16
+ ping: "pong",
17
+ clear: "",
18
+ };
19
+
20
+ function respond(cmd) {
21
+ const t = cmd.trim().toLowerCase();
22
+ if (t === "clear") return null;
23
+ if (t === "help") return replies.help;
24
+ if (t === "ping") return replies.ping;
25
+ if (t.startsWith("echo ")) return cmd.slice(5).trim() || "(empty)";
26
+ return `Unknown: ${cmd}`;
27
+ }
28
+
29
+ const LineItem = UI.List(
30
+ `<div class="line"></div>`,
31
+ (props, refs, ctx) => {
32
+ ctx.batch(() => {
33
+ const { type, text } = props.item;
34
+ refs.el.textContent = text;
35
+ refs.el.className = "line " + type;
36
+ });
37
+ }
38
+ );
39
+
40
+ const App = UI.Struct(
41
+ `<div class="panel">
42
+ <div class="titlebar">
43
+ <div class="titlebar-left">
44
+ <span class="dot dot-red"></span>
45
+ <span class="dot dot-yellow"></span>
46
+ <span class="dot dot-green"></span>
47
+ <span class="titlebar-title">SYS://CONSOLE</span>
48
+ </div>
49
+ <div class="titlebar-right" data-ref="count">0 lines</div>
50
+ </div>
51
+ <div class="output" data-ref="output"></div>
52
+ <div class="input-row">
53
+ <span class="prompt">$</span>
54
+ <input class="console-input" data-ref="input" placeholder="command..." />
55
+ </div>
56
+ </div>`,
57
+ (props, refs, ctx) => {
58
+ const input = refs.input;
59
+
60
+ ctx.list(refs.output, lines, (item) => ({ item }), LineItem, {
61
+ onAdd: () => {
62
+ requestAnimationFrame(() => {
63
+ requestAnimationFrame(() => {
64
+ refs.output.scrollTop = refs.output.scrollHeight;
65
+ });
66
+ });
67
+ },
68
+ });
69
+
70
+ ctx.batch(() => {
71
+ refs.count.textContent = lines.length + " line" + (lines.length !== 1 ? "s" : "");
72
+ });
73
+
74
+ function submit() {
75
+ const text = input.value.trim();
76
+ if (!text) return;
77
+ lines.push(R.Struct({ type: "in", text }));
78
+ input.value = "";
79
+ const out = respond(text);
80
+ if (out !== null) {
81
+ lines.push(R.Struct({ type: "out", text: out }));
82
+ } else {
83
+ lines.splice(0, lines.length);
84
+ lines.push(R.Struct({ type: "out", text: "Welcome. Type help, ping, or echo <msg>." }));
85
+ }
86
+ }
87
+
88
+ input.addEventListener("keydown", (e) => {
89
+ if (e.key === "Enter") submit();
90
+ });
91
+ }
92
+ );
93
+
94
+ export { App, lines };
95
+ if (typeof document !== "undefined" && document.getElementById("app")) {
96
+ document.getElementById("app").appendChild(App({}));
97
+ }
@@ -0,0 +1,23 @@
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
+ <script type="importmap">
8
+ {
9
+ "imports": {
10
+ "@evgkch/reactive": "/node_modules/@evgkch/reactive/dist/index.js",
11
+ "@evgkch/reactive-dom": "/dist/index.js"
12
+ }
13
+ }
14
+ </script>
15
+ <link rel="preconnect" href="https://fonts.googleapis.com">
16
+ <link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;700&display=swap" rel="stylesheet">
17
+ <link rel="stylesheet" href="./styles.css">
18
+ </head>
19
+ <body>
20
+ <div id="app"></div>
21
+ <script type="module" src="./app.js"></script>
22
+ </body>
23
+ </html>
@@ -0,0 +1,162 @@
1
+ @import url("https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;700&display=swap");
2
+
3
+ *,
4
+ *::before,
5
+ *::after {
6
+ box-sizing: border-box;
7
+ margin: 0;
8
+ padding: 0;
9
+ }
10
+
11
+ :root {
12
+ --bg: #080808;
13
+ --surface: #0f0f0f;
14
+ --border: #2a2a2a;
15
+ --ink: #d0d0d0;
16
+ --muted: #666;
17
+ --dim: #444;
18
+ --accent: #39ff14;
19
+ --red: #ff3333;
20
+ --yellow: #ffcc00;
21
+ --blue: #4488ff;
22
+ }
23
+
24
+ body {
25
+ font-family: "JetBrains Mono", monospace;
26
+ background: var(--bg);
27
+ color: var(--ink);
28
+ min-height: 100vh;
29
+ display: flex;
30
+ align-items: center;
31
+ justify-content: center;
32
+ padding: 2rem 1rem;
33
+ font-size: 12px;
34
+ line-height: 1.5;
35
+ }
36
+
37
+ .panel {
38
+ width: 560px;
39
+ max-width: 100%;
40
+ height: 400px;
41
+ border: 1px solid var(--border);
42
+ background: var(--surface);
43
+ display: flex;
44
+ flex-direction: column;
45
+ }
46
+
47
+ .titlebar {
48
+ display: flex;
49
+ align-items: center;
50
+ justify-content: space-between;
51
+ padding: 0.4rem 0.75rem;
52
+ border-bottom: 1px solid var(--border);
53
+ background: var(--bg);
54
+ }
55
+
56
+ .titlebar-left {
57
+ display: flex;
58
+ align-items: center;
59
+ gap: 0.5rem;
60
+ }
61
+
62
+ .dot {
63
+ width: 8px;
64
+ height: 8px;
65
+ border-radius: 50%;
66
+ display: inline-block;
67
+ }
68
+
69
+ .dot-red { background: var(--red); }
70
+ .dot-yellow { background: var(--yellow); }
71
+ .dot-green { background: var(--accent); }
72
+
73
+ .titlebar-title {
74
+ font-size: 11px;
75
+ font-weight: 700;
76
+ color: var(--ink);
77
+ letter-spacing: 0.1em;
78
+ margin-left: 0.5rem;
79
+ }
80
+
81
+ .titlebar-right {
82
+ font-size: 10px;
83
+ color: var(--muted);
84
+ letter-spacing: 0.05em;
85
+ }
86
+
87
+ .output {
88
+ flex: 1;
89
+ min-height: 0;
90
+ overflow-y: scroll;
91
+ padding: 0.5rem 0.75rem;
92
+ border-bottom: 1px solid var(--border);
93
+ }
94
+
95
+ .line {
96
+ padding: 0.2rem 0;
97
+ font-size: 12px;
98
+ word-break: break-word;
99
+ }
100
+
101
+ .line.in {
102
+ color: var(--accent);
103
+ }
104
+
105
+ .line.in::before {
106
+ content: "> ";
107
+ color: var(--muted);
108
+ }
109
+
110
+ .line.out {
111
+ color: var(--ink);
112
+ }
113
+
114
+ .line.out::before {
115
+ content: " ";
116
+ }
117
+
118
+ .line.err {
119
+ color: var(--red);
120
+ }
121
+
122
+ .input-row {
123
+ display: flex;
124
+ align-items: center;
125
+ padding: 0 0.75rem;
126
+ background: var(--bg);
127
+ }
128
+
129
+ .prompt {
130
+ color: var(--accent);
131
+ font-weight: 700;
132
+ margin-right: 0.5rem;
133
+ flex-shrink: 0;
134
+ font-size: 12px;
135
+ }
136
+
137
+ .console-input {
138
+ flex: 1;
139
+ font-family: "JetBrains Mono", monospace;
140
+ font-size: 12px;
141
+ background: transparent;
142
+ border: none;
143
+ padding: 0.55rem 0;
144
+ color: var(--ink);
145
+ outline: none;
146
+ caret-color: var(--accent);
147
+ }
148
+
149
+ .console-input::placeholder {
150
+ color: var(--dim);
151
+ }
152
+
153
+ .empty {
154
+ padding: 0.5rem 0;
155
+ font-size: 11px;
156
+ color: var(--dim);
157
+ }
158
+
159
+ .empty::before {
160
+ content: "// type a command and press Enter";
161
+ color: var(--border);
162
+ }
@@ -0,0 +1,28 @@
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>reactive-dom examples</title>
7
+ <link rel="stylesheet" href="./base.css">
8
+ <link rel="stylesheet" href="./styles.css">
9
+ </head>
10
+ <body>
11
+ <h1>Examples</h1>
12
+ <p>From repo root: <code>npm run build && npm run examples</code>, then open the links below.</p>
13
+ <ul>
14
+ <li>
15
+ <a href="./monitor/">Monitor</a>
16
+ <span>Fake process monitor: list, add, toggle, kill, CPU/MEM/procs, hotkeys</span>
17
+ </li>
18
+ <li>
19
+ <a href="./console/">Console</a>
20
+ <span>REPL-style console: type help, ping, echo &lt;msg&gt;, clear</span>
21
+ </li>
22
+ <li>
23
+ <a href="./stream/">Stream</a>
24
+ <span>Live log stream: filter by level (info/warn/err), clear</span>
25
+ </li>
26
+ </ul>
27
+ </body>
28
+ </html>