@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.
- package/README.md +71 -92
- package/dist/create-element.js +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/list.js +4 -4
- package/dist/log.d.ts +9 -14
- package/dist/log.d.ts.map +1 -1
- package/dist/log.js +20 -78
- package/dist/slot.js +4 -4
- package/dist/unmount.js +2 -2
- package/examples/index.html +4 -4
- package/examples/pages/console/index.css +73 -0
- package/examples/pages/console/index.html +13 -0
- package/examples/pages/console/index.ts +11 -0
- package/examples/pages/console/model/state.ts +25 -0
- package/examples/pages/console/ui/App.ts +57 -0
- package/examples/pages/console/ui/LineItem.ts +13 -0
- package/examples/pages/monitor/index.css +297 -0
- package/examples/pages/monitor/index.html +13 -0
- package/examples/pages/monitor/index.ts +24 -0
- package/examples/pages/monitor/model/state.ts +30 -0
- package/examples/pages/monitor/ui/App.ts +207 -0
- package/examples/pages/monitor/ui/TaskItem.ts +41 -0
- package/examples/{stream/styles.css → pages/stream/index.css} +1 -75
- package/examples/pages/stream/index.html +13 -0
- package/examples/pages/stream/index.ts +15 -0
- package/examples/pages/stream/model/state.ts +45 -0
- package/examples/pages/stream/ui/App.ts +70 -0
- package/examples/pages/stream/ui/LogLine.ts +27 -0
- package/examples/shared/lib/index.ts +19 -0
- package/examples/{console/styles.css → shared/styles/common.css} +5 -77
- package/examples/tsconfig.json +13 -0
- package/package.json +10 -8
- package/examples/console/app.js +0 -97
- package/examples/console/index.html +0 -23
- package/examples/monitor/app.js +0 -260
- package/examples/monitor/index.html +0 -23
- package/examples/monitor/styles.css +0 -425
- package/examples/stream/app.js +0 -136
- package/examples/stream/index.html +0 -23
package/README.md
CHANGED
|
@@ -25,11 +25,11 @@ One context handles subscriptions. One function unmounts everything.
|
|
|
25
25
|
npm install @evgkch/reactive @evgkch/reactive-dom
|
|
26
26
|
```
|
|
27
27
|
|
|
28
|
-
**Peer:** `@evgkch/reactive` ^1.
|
|
28
|
+
**Peer:** `@evgkch/reactive` ^1.1.0.
|
|
29
29
|
|
|
30
30
|
```ts
|
|
31
|
-
import * as R from "@evgkch/reactive";
|
|
32
|
-
import * as UI from "@evgkch/reactive-dom";
|
|
31
|
+
import * as R from "@evgkch/reactive";
|
|
32
|
+
import * as UI from "@evgkch/reactive-dom";
|
|
33
33
|
```
|
|
34
34
|
|
|
35
35
|
---
|
|
@@ -40,12 +40,10 @@ Creates a reusable factory from an HTML template. The template is parsed **once*
|
|
|
40
40
|
|
|
41
41
|
```ts
|
|
42
42
|
const UserCard = UI.Struct(
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
</div>
|
|
48
|
-
`,
|
|
43
|
+
`<div class="card">
|
|
44
|
+
<span data-ref="name"></span>
|
|
45
|
+
<span data-ref="age"></span>
|
|
46
|
+
</div>`,
|
|
49
47
|
(props: { user: User; theme: string }, refs, ctx) => {
|
|
50
48
|
ctx.batch(() => {
|
|
51
49
|
refs.name.textContent = props.user.name;
|
|
@@ -65,10 +63,10 @@ document.body.appendChild(el);
|
|
|
65
63
|
|
|
66
64
|
`ctx.batch` runs **synchronously on first call** — the element is fully rendered before it enters the DOM. On subsequent reactive changes it re-runs automatically.
|
|
67
65
|
|
|
68
|
-
|
|
66
|
+
To remove an element and stop its subscriptions:
|
|
69
67
|
|
|
70
68
|
```ts
|
|
71
|
-
UI.remove(el);
|
|
69
|
+
UI.remove(el);
|
|
72
70
|
```
|
|
73
71
|
|
|
74
72
|
---
|
|
@@ -79,13 +77,11 @@ Same signature as `UI.Struct`, but the factory has two modes depending on how it
|
|
|
79
77
|
|
|
80
78
|
```ts
|
|
81
79
|
const TaskItem = UI.List(
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
</li>
|
|
88
|
-
`,
|
|
80
|
+
`<li>
|
|
81
|
+
<input type="checkbox" data-ref="check" />
|
|
82
|
+
<span data-ref="text"></span>
|
|
83
|
+
<button data-ref="remove">✕</button>
|
|
84
|
+
</li>`,
|
|
89
85
|
(props: { item: Task }, refs, ctx) => {
|
|
90
86
|
ctx.batch(() => {
|
|
91
87
|
refs.text.textContent = props.item.text;
|
|
@@ -124,19 +120,19 @@ In list mode the library uses `Watch` to track operations on the source:
|
|
|
124
120
|
|
|
125
121
|
`onAdd` fires only for elements added **after** initial mount — not for the initial list. Use it for enter animations, not for rendering.
|
|
126
122
|
|
|
127
|
-
`onRemove` receives `done` — the element is removed from DOM only after `done()` is called, enabling exit animations.
|
|
123
|
+
`onRemove` receives `done` — the element is removed from the DOM only after `done()` is called, enabling exit animations.
|
|
128
124
|
|
|
129
125
|
---
|
|
130
126
|
|
|
131
127
|
## Context
|
|
132
128
|
|
|
133
|
-
Third argument in every setup after `props` and `refs`. Every subscription registered through `ctx` is automatically stopped when the component is unmounted.
|
|
129
|
+
Third argument in every setup, after `props` and `refs`. Every subscription registered through `ctx` is automatically stopped when the component is unmounted.
|
|
134
130
|
|
|
135
131
|
```ts
|
|
136
|
-
ctx.batch(fn)
|
|
137
|
-
ctx.watch(source, fn)
|
|
138
|
-
ctx.list(container, source, propsFactory, factory, hooks?)
|
|
139
|
-
ctx.slot(container, getter)
|
|
132
|
+
ctx.batch(fn) // reactive effect
|
|
133
|
+
ctx.watch(source, fn) // operation handler (sync, with patch)
|
|
134
|
+
ctx.list(container, source, propsFactory, factory, hooks?) // nested list
|
|
135
|
+
ctx.slot(container, getter) // conditional render
|
|
140
136
|
```
|
|
141
137
|
|
|
142
138
|
There is no need to manage stop functions manually — `ctx` collects them all.
|
|
@@ -149,18 +145,15 @@ There is no need to manage stop functions manually — `ctx` collects them all.
|
|
|
149
145
|
|
|
150
146
|
```ts
|
|
151
147
|
const ColumnItem = UI.Struct(
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
</div>
|
|
157
|
-
`,
|
|
148
|
+
`<div class="column">
|
|
149
|
+
<h3 data-ref="title"></h3>
|
|
150
|
+
<ul data-ref="tasks"></ul>
|
|
151
|
+
</div>`,
|
|
158
152
|
(props: { col: Column }, refs, ctx) => {
|
|
159
153
|
ctx.batch(() => {
|
|
160
154
|
refs.title.textContent = props.col.title;
|
|
161
155
|
});
|
|
162
156
|
ctx.list(refs.tasks, props.col.tasks, (item) => ({ item }), TaskItem);
|
|
163
|
-
// TaskItem's stop is registered in ctx — unmounted automatically with ColumnItem
|
|
164
157
|
},
|
|
165
158
|
);
|
|
166
159
|
```
|
|
@@ -178,7 +171,6 @@ const App = UI.Struct(`<div><div data-ref="panel"></div></div>`, (props, refs, c
|
|
|
178
171
|
|
|
179
172
|
```ts
|
|
180
173
|
const Item = UI.Struct(`<div data-ref="el"></div>`, (props: { item: Task }, refs, ctx) => {
|
|
181
|
-
// ctx.batch reads from multiple reactive sources — tracks all of them
|
|
182
174
|
ctx.batch(() => {
|
|
183
175
|
refs.el.className = [
|
|
184
176
|
props.item.done ? "done" : "",
|
|
@@ -200,40 +192,36 @@ Conditional render outside a component:
|
|
|
200
192
|
const stop = UI.Slot(container, () => (isOpen.get() ? Modal({ title }) : null));
|
|
201
193
|
```
|
|
202
194
|
|
|
203
|
-
An anchor `Comment` node fixes the position in DOM. When the getter result changes — previous element is unmounted, new one is inserted.
|
|
195
|
+
An anchor `Comment` node fixes the position in the DOM. When the getter result changes — the previous element is unmounted, the new one is inserted.
|
|
204
196
|
|
|
205
197
|
---
|
|
206
198
|
|
|
207
199
|
## UI.remove / UI.unmount
|
|
208
200
|
|
|
209
201
|
```ts
|
|
210
|
-
UI.remove(el); //
|
|
211
|
-
UI.unmount(el); //
|
|
202
|
+
UI.remove(el); // stop all subscriptions + el.remove()
|
|
203
|
+
UI.unmount(el); // stop all subscriptions, element stays in DOM
|
|
212
204
|
```
|
|
213
205
|
|
|
214
206
|
`UI.List` and `UI.Slot` call `UI.remove` automatically on their elements. For manually mounted elements call it explicitly.
|
|
215
207
|
|
|
216
208
|
---
|
|
217
209
|
|
|
218
|
-
## Debug logging
|
|
210
|
+
## Debug logging
|
|
219
211
|
|
|
220
|
-
Turn on component lifecycle logs to see mount, list add/remove, slot swap, and unmount
|
|
212
|
+
Turn on component lifecycle logs to see mount, list add/remove, slot swap, and unmount. Uses a `[reactive-dom]` prefix; meta is the element at the call site — same pattern as `@evgkch/reactive`.
|
|
221
213
|
|
|
222
214
|
```ts
|
|
223
|
-
import * as UI from "@evgkch/reactive-dom";
|
|
224
|
-
|
|
225
215
|
UI.configure({ log: true });
|
|
226
|
-
// mount
|
|
227
|
-
// list
|
|
228
|
-
// list
|
|
229
|
-
// unmount
|
|
216
|
+
// [reactive-dom] [struct:mount] <element>
|
|
217
|
+
// [reactive-dom] [list:mount] <element>
|
|
218
|
+
// [reactive-dom] [list:add] <element>
|
|
219
|
+
// [reactive-dom] [struct:unmount] <element>
|
|
230
220
|
```
|
|
231
221
|
|
|
232
|
-
- `configure({ log: true })` —
|
|
233
|
-
- `configure({ log:
|
|
234
|
-
- `
|
|
235
|
-
|
|
236
|
-
In the **examples** (monitor, console, stream), both the reactive logger (`R.configure({ log: true })`) and the component logger (`UI.configure({ log: true })`) are enabled when you open the app in the browser, so you see Batch/Watch from reactive and mount/list/slot/unmount from reactive-dom together.
|
|
222
|
+
- `configure({ log: true })` — use the default console logger.
|
|
223
|
+
- `configure({ log: logger })` — your logger `{ log(message: string, meta?: unknown): void }` (same shape as `@evgkch/reactive`).
|
|
224
|
+
- `configure({ log: null })` — off (default). No overhead: one guard, no work when disabled.
|
|
237
225
|
|
|
238
226
|
---
|
|
239
227
|
|
|
@@ -263,7 +251,7 @@ class TaskItemEl extends UI.ReactiveElement {
|
|
|
263
251
|
customElements.define("task-item", TaskItemEl);
|
|
264
252
|
```
|
|
265
253
|
|
|
266
|
-
`disconnectedCallback` calls `unmount` automatically
|
|
254
|
+
`disconnectedCallback` calls `unmount` automatically — `UI.List` needs no manual stop:
|
|
267
255
|
|
|
268
256
|
```ts
|
|
269
257
|
TaskItem(ul, tasks, (item) => {
|
|
@@ -290,20 +278,17 @@ const tasks = R.List([
|
|
|
290
278
|
R.Struct({ text: "extract data", done: true }),
|
|
291
279
|
]);
|
|
292
280
|
|
|
293
|
-
// live metrics
|
|
294
281
|
setInterval(() => {
|
|
295
282
|
cpu.set(Math.max(10, Math.min(99, cpu.get() + Math.floor(Math.random() * 11) - 5)));
|
|
296
283
|
}, 1000);
|
|
297
284
|
|
|
298
285
|
const TaskItem = UI.List(
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
</li>
|
|
306
|
-
`,
|
|
286
|
+
`<li>
|
|
287
|
+
<input type="checkbox" data-ref="check" />
|
|
288
|
+
<span data-ref="text"></span>
|
|
289
|
+
<span data-ref="status"></span>
|
|
290
|
+
<button data-ref="remove">kill</button>
|
|
291
|
+
</li>`,
|
|
307
292
|
(props: { item: Task }, refs, ctx) => {
|
|
308
293
|
ctx.batch(() => {
|
|
309
294
|
refs.text.textContent = props.item.text;
|
|
@@ -323,24 +308,22 @@ const TaskItem = UI.List(
|
|
|
323
308
|
);
|
|
324
309
|
|
|
325
310
|
const App = UI.Struct(
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
</div
|
|
342
|
-
</div>
|
|
343
|
-
`,
|
|
311
|
+
`<div class="monitor">
|
|
312
|
+
<div class="cpu-bar" data-ref="cpu-bar"></div>
|
|
313
|
+
<div class="prompt">
|
|
314
|
+
<span>$</span>
|
|
315
|
+
<input data-ref="input" placeholder="spawn process..." />
|
|
316
|
+
</div>
|
|
317
|
+
<ul data-ref="list"></ul>
|
|
318
|
+
<div class="statusbar">
|
|
319
|
+
<span data-ref="count"></span>
|
|
320
|
+
<div data-ref="filters">
|
|
321
|
+
<button data-filter="all">all</button>
|
|
322
|
+
<button data-filter="active">running</button>
|
|
323
|
+
<button data-filter="done">done</button>
|
|
324
|
+
</div>
|
|
325
|
+
</div>
|
|
326
|
+
</div>`,
|
|
344
327
|
(props, refs, ctx) => {
|
|
345
328
|
ctx.list(refs.list, tasks, (item) => ({ item }), TaskItem, {
|
|
346
329
|
onAdd: (el) => el.animate([{ opacity: 0 }, { opacity: 1 }], 150),
|
|
@@ -377,7 +360,6 @@ const App = UI.Struct(
|
|
|
377
360
|
input.value = "";
|
|
378
361
|
});
|
|
379
362
|
|
|
380
|
-
// hotkeys
|
|
381
363
|
document.addEventListener("keydown", (e) => {
|
|
382
364
|
if (document.activeElement === input) return;
|
|
383
365
|
if (e.key === "1") filter.set("all");
|
|
@@ -395,26 +377,23 @@ document.getElementById("app")!.appendChild(App({}));
|
|
|
395
377
|
|
|
396
378
|
## API reference
|
|
397
379
|
|
|
398
|
-
| API | Description
|
|
399
|
-
| ------------------------- |
|
|
400
|
-
| `Struct(html, setup)` | Factory `(props) => Element`. Setup: `(props, refs, ctx)`. Template parsed once.
|
|
401
|
-
| `List(html, setup)` | Dual factory. `(props) => Element` or `(container, source, propsFactory, hooks?) => stop`.
|
|
402
|
-
| `Slot(container, getter)` | Conditional slot. Swaps element on change. Returns `stop`.
|
|
403
|
-
| `remove(el)` |
|
|
404
|
-
| `unmount(el)` | Stop all subscriptions. Element stays in DOM.
|
|
405
|
-
| `ReactiveElement` | Base class for Custom Elements. Override `mount(ctx)`, call `invalidate()` from setters.
|
|
406
|
-
| `configure({ log? })` |
|
|
407
|
-
| `getComponentId(el)` | Stable id for a component root (e.g. `"c1"`). Use to correlate with logs or DevTools. |
|
|
380
|
+
| API | Description |
|
|
381
|
+
| ------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
382
|
+
| `Struct(html, setup)` | Factory `(props) => Element`. Setup: `(props, refs, ctx)`. Template parsed once. |
|
|
383
|
+
| `List(html, setup)` | Dual factory. `(props) => Element` or `(container, source, propsFactory, hooks?) => stop`. `onAdd` fires only for items added after initial mount. |
|
|
384
|
+
| `Slot(container, getter)` | Conditional slot outside a component. Swaps element on change. Returns `stop`. |
|
|
385
|
+
| `remove(el)` | Stop all subscriptions + `el.remove()`. |
|
|
386
|
+
| `unmount(el)` | Stop all subscriptions. Element stays in DOM. |
|
|
387
|
+
| `ReactiveElement` | Base class for Custom Elements. Override `mount(ctx)`, call `invalidate()` from setters. |
|
|
388
|
+
| `configure({ log? })` | `true` = console logger, `{ log(msg, meta?) }` = your logger, `null` = off. Same pattern as `@evgkch/reactive`. |
|
|
408
389
|
|
|
409
390
|
---
|
|
410
391
|
|
|
411
|
-
##
|
|
412
|
-
|
|
413
|
-
Examples are deployed automatically on push to `main`. The workflow builds the lib, fills `docs/` (gitignored) with the site, and deploys it — no duplicated code in the repo.
|
|
392
|
+
## Examples
|
|
414
393
|
|
|
415
|
-
|
|
394
|
+
Live: `https://evgkch.github.io/reactive-dom/` — monitor, console, stream.
|
|
416
395
|
|
|
417
|
-
|
|
396
|
+
Local: `npm run examples`
|
|
418
397
|
|
|
419
398
|
---
|
|
420
399
|
|
package/dist/create-element.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Context } from "./ctx.js";
|
|
2
|
-
import {
|
|
2
|
+
import { getLogger, PREFIX } from "./log.js";
|
|
3
3
|
import { setComponentStop } from "./unmount.js";
|
|
4
4
|
export function createElement(tmpl, setup, props) {
|
|
5
5
|
const fragment = tmpl.content.cloneNode(true);
|
|
@@ -22,7 +22,7 @@ export function createElement(tmpl, setup, props) {
|
|
|
22
22
|
s();
|
|
23
23
|
};
|
|
24
24
|
setComponentStop(el, stop);
|
|
25
|
-
|
|
25
|
+
getLogger()?.log(`${PREFIX} [struct:mount]`, el);
|
|
26
26
|
return el;
|
|
27
27
|
}
|
|
28
28
|
export function parseTemplate(html) {
|
package/dist/index.d.ts
CHANGED
|
@@ -6,6 +6,6 @@ export type { Watchable, ListSetup } from "./list.js";
|
|
|
6
6
|
export { Slot } from "./slot.js";
|
|
7
7
|
export { unmount, remove } from "./unmount.js";
|
|
8
8
|
export { ReactiveElement } from "./element.js";
|
|
9
|
-
export { configure,
|
|
10
|
-
export type {
|
|
9
|
+
export { configure, setLogger, getLogger } from "./log.js";
|
|
10
|
+
export type { ReactiveDomLogger, ConfigureOptions } from "./log.js";
|
|
11
11
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAC9C,YAAY,EAAE,IAAI,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AACrD,YAAY,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAC3C,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,YAAY,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AACtD,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAC/C,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAC/C,OAAO,EAAE,SAAS,EAAE,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAC9C,YAAY,EAAE,IAAI,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AACrD,YAAY,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAC3C,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,YAAY,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AACtD,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAC/C,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAC/C,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAC3D,YAAY,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -3,4 +3,4 @@ export { List } from "./list.js";
|
|
|
3
3
|
export { Slot } from "./slot.js";
|
|
4
4
|
export { unmount, remove } from "./unmount.js";
|
|
5
5
|
export { ReactiveElement } from "./element.js";
|
|
6
|
-
export { configure,
|
|
6
|
+
export { configure, setLogger, getLogger } from "./log.js";
|
package/dist/list.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createElement } from "./create-element.js";
|
|
2
2
|
import { parseTemplate } from "./create-element.js";
|
|
3
|
-
import {
|
|
3
|
+
import { getLogger, PREFIX } from "./log.js";
|
|
4
4
|
import { unmount } from "./unmount.js";
|
|
5
5
|
/**
|
|
6
6
|
* Mount reactive list into container. Patches via Watch.
|
|
@@ -13,7 +13,7 @@ export function mountList(container, source, factory, hooks) {
|
|
|
13
13
|
nodes.set(item, el);
|
|
14
14
|
container.insertBefore(el, ref);
|
|
15
15
|
if (runOnAdd) {
|
|
16
|
-
|
|
16
|
+
getLogger()?.log(`${PREFIX} [list:add]`, el);
|
|
17
17
|
hooks?.onAdd?.(el);
|
|
18
18
|
}
|
|
19
19
|
return el;
|
|
@@ -23,7 +23,7 @@ export function mountList(container, source, factory, hooks) {
|
|
|
23
23
|
if (!el)
|
|
24
24
|
return;
|
|
25
25
|
nodes.delete(item);
|
|
26
|
-
|
|
26
|
+
getLogger()?.log(`${PREFIX} [list:remove]`, el);
|
|
27
27
|
unmount(el);
|
|
28
28
|
if (hooks?.onRemove) {
|
|
29
29
|
hooks.onRemove(el, () => el.remove());
|
|
@@ -32,7 +32,7 @@ export function mountList(container, source, factory, hooks) {
|
|
|
32
32
|
el.remove();
|
|
33
33
|
}
|
|
34
34
|
}
|
|
35
|
-
|
|
35
|
+
getLogger()?.log(`${PREFIX} [list:mount]`, container);
|
|
36
36
|
// Initial mount: no onAdd so items are visible immediately (onAdd often animates from opacity 0)
|
|
37
37
|
source.forEach((item) => addItem(item, null, false));
|
|
38
38
|
const stopWatch = source.watch(({ start, removed, added }) => {
|
package/dist/log.d.ts
CHANGED
|
@@ -1,18 +1,13 @@
|
|
|
1
|
-
|
|
2
|
-
export declare
|
|
1
|
+
/** Same shape as @evgkch/reactive: one logger, inline at call site, no overhead when null. */
|
|
2
|
+
export declare const PREFIX = "[reactive-dom]";
|
|
3
|
+
export interface ReactiveDomLogger {
|
|
4
|
+
log(message: string, meta?: unknown): void;
|
|
5
|
+
}
|
|
6
|
+
export declare function setLogger(lg: ReactiveDomLogger | null): void;
|
|
7
|
+
export declare function getLogger(): ReactiveDomLogger | null;
|
|
3
8
|
export interface ConfigureOptions {
|
|
4
|
-
|
|
9
|
+
/** `null` = off, `true` = default console, or your logger. No overhead when null. */
|
|
10
|
+
log?: ReactiveDomLogger | null | true;
|
|
5
11
|
}
|
|
6
12
|
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
13
|
//# sourceMappingURL=log.d.ts.map
|
package/dist/log.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"log.d.ts","sourceRoot":"","sources":["../src/log.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"log.d.ts","sourceRoot":"","sources":["../src/log.ts"],"names":[],"mappings":"AAAA,8FAA8F;AAC9F,eAAO,MAAM,MAAM,mBAAmB,CAAC;AAEvC,MAAM,WAAW,iBAAiB;IAC9B,GAAG,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;CAC9C;AAID,wBAAgB,SAAS,CAAC,EAAE,EAAE,iBAAiB,GAAG,IAAI,GAAG,IAAI,CAE5D;AAED,wBAAgB,SAAS,IAAI,iBAAiB,GAAG,IAAI,CAEpD;AAED,MAAM,WAAW,gBAAgB;IAC7B,qFAAqF;IACrF,GAAG,CAAC,EAAE,iBAAiB,GAAG,IAAI,GAAG,IAAI,CAAC;CACzC;AAED,wBAAgB,SAAS,CAAC,IAAI,GAAE,gBAAqB,GAAG,IAAI,CAe3D"}
|
package/dist/log.js
CHANGED
|
@@ -1,84 +1,26 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
logLevel = level;
|
|
1
|
+
/** Same shape as @evgkch/reactive: one logger, inline at call site, no overhead when null. */
|
|
2
|
+
export const PREFIX = "[reactive-dom]";
|
|
3
|
+
let logger = null;
|
|
4
|
+
export function setLogger(lg) {
|
|
5
|
+
logger = lg;
|
|
7
6
|
}
|
|
8
|
-
export function
|
|
9
|
-
|
|
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(" "));
|
|
7
|
+
export function getLogger() {
|
|
8
|
+
return logger;
|
|
33
9
|
}
|
|
34
|
-
export function
|
|
35
|
-
if (
|
|
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)
|
|
10
|
+
export function configure(opts = {}) {
|
|
11
|
+
if (opts.log === undefined)
|
|
67
12
|
return;
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
}
|
|
71
|
-
export function logSlotSwap(container, prev, next) {
|
|
72
|
-
if (!logLevel)
|
|
13
|
+
if (opts.log === null) {
|
|
14
|
+
logger = null;
|
|
73
15
|
return;
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
16
|
+
}
|
|
17
|
+
if (opts.log === true) {
|
|
18
|
+
logger = {
|
|
19
|
+
log(msg, meta) {
|
|
20
|
+
console.log(msg, meta ?? "");
|
|
21
|
+
},
|
|
22
|
+
};
|
|
81
23
|
return;
|
|
82
|
-
|
|
83
|
-
|
|
24
|
+
}
|
|
25
|
+
logger = opts.log;
|
|
84
26
|
}
|
package/dist/slot.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Batch } from "@evgkch/reactive";
|
|
2
|
-
import {
|
|
2
|
+
import { getLogger, PREFIX } from "./log.js";
|
|
3
3
|
import { unmount } from "./unmount.js";
|
|
4
4
|
/**
|
|
5
5
|
* Conditional slot. Getter returns Element | null.
|
|
@@ -8,7 +8,7 @@ import { unmount } from "./unmount.js";
|
|
|
8
8
|
export function Slot(container, getter) {
|
|
9
9
|
const anchor = document.createComment("slot");
|
|
10
10
|
container.appendChild(anchor);
|
|
11
|
-
|
|
11
|
+
getLogger()?.log(`${PREFIX} [slot:mount]`, container);
|
|
12
12
|
let current = null;
|
|
13
13
|
const stopBatch = Batch(() => {
|
|
14
14
|
const next = getter();
|
|
@@ -21,12 +21,12 @@ export function Slot(container, getter) {
|
|
|
21
21
|
if (next) {
|
|
22
22
|
container.insertBefore(next, anchor);
|
|
23
23
|
}
|
|
24
|
-
|
|
24
|
+
getLogger()?.log(`${PREFIX} [slot:swap]`, { container, prev: current, next });
|
|
25
25
|
current = next;
|
|
26
26
|
});
|
|
27
27
|
const stop = () => {
|
|
28
28
|
stopBatch();
|
|
29
|
-
|
|
29
|
+
getLogger()?.log(`${PREFIX} [slot:stop]`, container);
|
|
30
30
|
if (current) {
|
|
31
31
|
unmount(current);
|
|
32
32
|
current.remove();
|
package/dist/unmount.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { getLogger, PREFIX } from "./log.js";
|
|
2
2
|
/**
|
|
3
3
|
* WeakMap: Element → stop. No leak — GC collects with the element.
|
|
4
4
|
*/
|
|
@@ -10,7 +10,7 @@ export function setComponentStop(el, stop) {
|
|
|
10
10
|
export function unmount(el) {
|
|
11
11
|
const stop = componentMap.get(el);
|
|
12
12
|
if (stop) {
|
|
13
|
-
|
|
13
|
+
getLogger()?.log(`${PREFIX} [struct:unmount]`, el);
|
|
14
14
|
stop();
|
|
15
15
|
componentMap.delete(el);
|
|
16
16
|
}
|
package/examples/index.html
CHANGED
|
@@ -9,18 +9,18 @@
|
|
|
9
9
|
</head>
|
|
10
10
|
<body>
|
|
11
11
|
<h1>Examples</h1>
|
|
12
|
-
<p
|
|
12
|
+
<p><code>npm run build && npm run examples</code>, then open the links below.</p>
|
|
13
13
|
<ul>
|
|
14
14
|
<li>
|
|
15
|
-
<a href="./monitor/">Monitor</a>
|
|
15
|
+
<a href="./pages/monitor/">Monitor</a>
|
|
16
16
|
<span>Fake process monitor: list, add, toggle, kill, CPU/MEM/procs, hotkeys</span>
|
|
17
17
|
</li>
|
|
18
18
|
<li>
|
|
19
|
-
<a href="./console/">Console</a>
|
|
19
|
+
<a href="./pages/console/">Console</a>
|
|
20
20
|
<span>REPL-style console: type help, ping, echo <msg>, clear</span>
|
|
21
21
|
</li>
|
|
22
22
|
<li>
|
|
23
|
-
<a href="./stream/">Stream</a>
|
|
23
|
+
<a href="./pages/stream/">Stream</a>
|
|
24
24
|
<span>Live log stream: filter by level (info/warn/err), clear</span>
|
|
25
25
|
</li>
|
|
26
26
|
</ul>
|