@bodil/dom 0.1.8 → 0.1.10

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 (48) hide show
  1. package/dist/component.d.ts +218 -6
  2. package/dist/component.js +154 -13
  3. package/dist/component.js.map +1 -1
  4. package/dist/css.d.ts +4 -0
  5. package/dist/css.js +4 -0
  6. package/dist/css.js.map +1 -1
  7. package/dist/decorators/attribute.js +1 -1
  8. package/dist/decorators/attribute.js.map +1 -1
  9. package/dist/decorators/connect.test.js +1 -1
  10. package/dist/decorators/connect.test.js.map +1 -1
  11. package/dist/decorators/reactive.d.ts +1 -1
  12. package/dist/decorators/reactive.js +1 -1
  13. package/dist/decorators/reactive.js.map +1 -1
  14. package/dist/decorators/reactive.test.js +1 -1
  15. package/dist/decorators/reactive.test.js.map +1 -1
  16. package/dist/dom.d.ts +32 -0
  17. package/dist/dom.js +32 -0
  18. package/dist/dom.js.map +1 -1
  19. package/dist/emitter.d.ts +8 -0
  20. package/dist/emitter.js.map +1 -1
  21. package/dist/event.d.ts +4 -0
  22. package/dist/event.js.map +1 -1
  23. package/dist/geometry.d.ts +7 -0
  24. package/dist/geometry.js +4 -0
  25. package/dist/geometry.js.map +1 -1
  26. package/dist/index.d.ts +2 -1
  27. package/dist/index.js +2 -1
  28. package/dist/index.js.map +1 -1
  29. package/dist/signal.d.ts +29 -0
  30. package/dist/signal.js +78 -0
  31. package/dist/signal.js.map +1 -0
  32. package/dist/signal.test.d.ts +1 -0
  33. package/dist/signal.test.js +135 -0
  34. package/dist/signal.test.js.map +1 -0
  35. package/package.json +16 -8
  36. package/src/component.ts +237 -20
  37. package/src/css.ts +5 -0
  38. package/src/decorators/attribute.ts +1 -1
  39. package/src/decorators/connect.test.ts +1 -1
  40. package/src/decorators/reactive.test.ts +1 -1
  41. package/src/decorators/reactive.ts +4 -4
  42. package/src/dom.ts +33 -0
  43. package/src/emitter.ts +8 -0
  44. package/src/event.ts +4 -0
  45. package/src/geometry.ts +8 -0
  46. package/src/index.ts +2 -1
  47. package/src/signal.test.ts +89 -0
  48. package/src/signal.ts +109 -0
@@ -0,0 +1,89 @@
1
+ import { Signal } from "@bodil/signal";
2
+ import { html } from "lit";
3
+ import { customElement } from "lit/decorators.js";
4
+ import { expect, test } from "vitest";
5
+
6
+ import { Component } from "./component";
7
+ import { watch } from "./signal";
8
+
9
+ test("watch directive", async () => {
10
+ const counter = Signal.from(1);
11
+ let renders = 0;
12
+
13
+ @customElement("watch-directive-test")
14
+ class WatchDirectiveTest extends Component {
15
+ render() {
16
+ renders++;
17
+ return html`<p>${watch(counter)}</p>`;
18
+ }
19
+ }
20
+
21
+ const t = document.createElement("watch-directive-test") as WatchDirectiveTest;
22
+ document.body.append(t);
23
+ await t.updateComplete;
24
+
25
+ expect(t.query("p")?.innerText).toBe("1");
26
+ expect(renders).toBe(1);
27
+
28
+ // update the counter and yield, it should update immediately.
29
+ counter.set(2);
30
+ await Promise.resolve();
31
+
32
+ expect(t.query("p")?.innerText).toBe("2");
33
+ expect(renders).toBe(1);
34
+
35
+ // request an update, it should not update immediately on yield
36
+ // because of the scheduled update
37
+ t.requestUpdate();
38
+ counter.set(3);
39
+ await Promise.resolve();
40
+
41
+ expect(t.query("p")?.innerText).toBe("2");
42
+ expect(renders).toBe(1);
43
+
44
+ // wait for the update to complete, it should now be in sync
45
+ await t.updateComplete;
46
+ expect(t.query("p")?.innerText).toBe("3");
47
+ expect(renders).toBe(2);
48
+ });
49
+
50
+ test("watch directive with mapper function", async () => {
51
+ const counter = Signal.from(1);
52
+ let renders = 0;
53
+
54
+ @customElement("watch-directive-mapper-test")
55
+ class WatchDirectiveMapperTest extends Component {
56
+ render() {
57
+ renders++;
58
+ return html`<p>${watch(counter, (i) => i + 1000)}</p>`;
59
+ }
60
+ }
61
+
62
+ const t = document.createElement("watch-directive-mapper-test") as WatchDirectiveMapperTest;
63
+ document.body.append(t);
64
+ await t.updateComplete;
65
+
66
+ expect(t.query("p")?.innerText).toBe("1001");
67
+ expect(renders).toBe(1);
68
+
69
+ // update the counter and yield, it should update immediately.
70
+ counter.set(2);
71
+ await Promise.resolve();
72
+
73
+ expect(t.query("p")?.innerText).toBe("1002");
74
+ expect(renders).toBe(1);
75
+
76
+ // request an update, it should not update immediately on yield
77
+ // because of the scheduled update
78
+ t.requestUpdate();
79
+ counter.set(3);
80
+ await Promise.resolve();
81
+
82
+ expect(t.query("p")?.innerText).toBe("1002");
83
+ expect(renders).toBe(1);
84
+
85
+ // wait for the update to complete, it should now be in sync
86
+ await t.updateComplete;
87
+ expect(t.query("p")?.innerText).toBe("1003");
88
+ expect(renders).toBe(2);
89
+ });
package/src/signal.ts ADDED
@@ -0,0 +1,109 @@
1
+ /**
2
+ * Lit directives for working with signals.
3
+ * @module
4
+ */
5
+
6
+ import { defer } from "@bodil/core/async";
7
+ import { id } from "@bodil/core/fun";
8
+ import { Signal } from "@bodil/signal";
9
+ import { AsyncDirective } from "lit/async-directive.js";
10
+ import { directive, type DirectiveResult, type Part } from "lit/directive.js";
11
+
12
+ // Most of this is from
13
+ // https://github.com/lit/lit/blob/main/packages/labs/signals/src/lib/watch.ts
14
+
15
+ import type { Component } from "./component";
16
+
17
+ let effectsPending = false;
18
+ const hostlessWatcher = new Signal.subtle.Watcher(() => {
19
+ if (!effectsPending) {
20
+ effectsPending = true;
21
+ defer(() => {
22
+ effectsPending = false;
23
+ for (const signal of hostlessWatcher.getPending()) {
24
+ signal.get();
25
+ }
26
+ hostlessWatcher.watch();
27
+ });
28
+ }
29
+ });
30
+
31
+ /** @internal */
32
+ export class WatchDirective<T> extends AsyncDirective {
33
+ #host?: Component;
34
+ #signal?: Signal.State<T> | Signal.Computed<T>;
35
+ #mapper: (value: T) => unknown = id;
36
+ #watcher?: typeof hostlessWatcher;
37
+ #computed: Signal.Computed<unknown> | undefined;
38
+
39
+ #watch() {
40
+ if (this.#watcher !== undefined) {
41
+ return;
42
+ }
43
+ this.#computed = new Signal.Computed(() => {
44
+ const value = this.#signal === undefined ? undefined : this.#mapper(this.#signal.get());
45
+ if (this.#host?.isUpdatePending === true) {
46
+ return;
47
+ }
48
+ this.setValue(value);
49
+ return value;
50
+ });
51
+ this.#watcher = hostlessWatcher;
52
+ this.#watcher.watch(this.#computed);
53
+ Signal.subtle.untrack(() => this.#computed?.get());
54
+ }
55
+
56
+ #unwatch() {
57
+ if (this.#watcher !== undefined) {
58
+ this.#watcher.unwatch(this.#computed!);
59
+ this.#watcher = undefined;
60
+ }
61
+ }
62
+
63
+ render(signal: Signal.State<T> | Signal.Computed<T>, mapFn?: (value: T) => unknown): unknown {
64
+ return Signal.subtle.untrack(() =>
65
+ mapFn === undefined ? signal.get() : mapFn(signal.get()),
66
+ );
67
+ }
68
+
69
+ override update(
70
+ part: Part,
71
+ [signal, mapFn]: [
72
+ signal: Signal.State<T> | Signal.Computed<T>,
73
+ mapFn?: (value: T) => unknown,
74
+ ],
75
+ ): unknown {
76
+ this.#host ??= part.options?.host as Component;
77
+ if (signal !== this.#signal && this.#signal !== undefined) {
78
+ this.#unwatch();
79
+ }
80
+ this.#signal = signal;
81
+ this.#mapper = mapFn ?? id;
82
+ this.#watch();
83
+ return Signal.subtle.untrack(() => this.#mapper(this.#signal!.get()));
84
+ }
85
+
86
+ protected override disconnected(): void {
87
+ this.#unwatch();
88
+ }
89
+
90
+ protected override reconnected(): void {
91
+ this.#watch();
92
+ }
93
+ }
94
+
95
+ /**
96
+ * @internal
97
+ * @ignore
98
+ */
99
+ export type WatchDirectiveFunction = <T>(
100
+ signal: Signal.Any<T>,
101
+ mapFn?: (value: T) => unknown,
102
+ ) => DirectiveResult<typeof WatchDirective<T>>;
103
+
104
+ /**
105
+ * Render a signal and subscribe to it, updating the part when the signal
106
+ * changes independently of the host component.
107
+ * @function
108
+ */
109
+ export const watch = directive(WatchDirective) as WatchDirectiveFunction;