@fun-land/fun-web 0.6.0 → 1.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/CHANGELOG.md +18 -0
- package/README.md +67 -11
- package/dist/esm/src/dom.d.ts +13 -5
- package/dist/esm/src/dom.js +25 -1
- package/dist/esm/src/dom.js.map +1 -1
- package/dist/esm/tsconfig.publish.tsbuildinfo +1 -1
- package/dist/src/dom.d.ts +13 -5
- package/dist/src/dom.js +27 -2
- package/dist/src/dom.js.map +1 -1
- package/dist/tsconfig.publish.tsbuildinfo +1 -1
- package/examples/todo-app/TodoApp.ts +4 -5
- package/package.json +12 -13
- package/src/dom.test.ts +182 -19
- package/src/dom.ts +49 -18
- package/src/mount.test.ts +2 -2
package/src/dom.test.ts
CHANGED
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
attrs,
|
|
7
7
|
append,
|
|
8
8
|
bindProperty,
|
|
9
|
+
bindView,
|
|
9
10
|
addClass,
|
|
10
11
|
toggleClass,
|
|
11
12
|
removeClass,
|
|
@@ -14,7 +15,7 @@ import {
|
|
|
14
15
|
querySelectorAll,
|
|
15
16
|
renderWhen,
|
|
16
17
|
} from "./dom";
|
|
17
|
-
import { funState } from "@fun-land/fun-state";
|
|
18
|
+
import { funState, mapRead, derive } from "@fun-land/fun-state";
|
|
18
19
|
|
|
19
20
|
describe("h()", () => {
|
|
20
21
|
it("should create an element", () => {
|
|
@@ -171,6 +172,44 @@ describe("hx()", () => {
|
|
|
171
172
|
"hx: signal is required"
|
|
172
173
|
);
|
|
173
174
|
});
|
|
175
|
+
|
|
176
|
+
it("should accept FunRead from mapRead in bind", () => {
|
|
177
|
+
const controller = new AbortController();
|
|
178
|
+
const num = funState(5);
|
|
179
|
+
const doubled = mapRead(num, (n) => String(n * 2));
|
|
180
|
+
|
|
181
|
+
const el = hx("div", {
|
|
182
|
+
signal: controller.signal,
|
|
183
|
+
bind: { textContent: doubled },
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
expect(el.textContent).toBe("10");
|
|
187
|
+
|
|
188
|
+
num.set(3);
|
|
189
|
+
expect(el.textContent).toBe("6");
|
|
190
|
+
|
|
191
|
+
controller.abort();
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
it("should accept FunRead from derive in bind", () => {
|
|
195
|
+
const controller = new AbortController();
|
|
196
|
+
const a = funState(2);
|
|
197
|
+
const b = funState(3);
|
|
198
|
+
const sum = derive(a, b, (x, y) => x + y);
|
|
199
|
+
|
|
200
|
+
const el = hx("input", {
|
|
201
|
+
signal: controller.signal,
|
|
202
|
+
props: { type: "number" },
|
|
203
|
+
bind: { valueAsNumber: sum },
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
expect(el.valueAsNumber).toBe(5);
|
|
207
|
+
|
|
208
|
+
a.set(10);
|
|
209
|
+
expect(el.valueAsNumber).toBe(13);
|
|
210
|
+
|
|
211
|
+
controller.abort();
|
|
212
|
+
});
|
|
174
213
|
});
|
|
175
214
|
|
|
176
215
|
describe("text()", () => {
|
|
@@ -372,6 +411,60 @@ describe("bindProperty()", () => {
|
|
|
372
411
|
expect(result).toBe(el);
|
|
373
412
|
controller.abort();
|
|
374
413
|
});
|
|
414
|
+
|
|
415
|
+
it("should bind FunRead from mapRead", () => {
|
|
416
|
+
const el = h("div");
|
|
417
|
+
const controller = new AbortController();
|
|
418
|
+
|
|
419
|
+
const num = funState(5);
|
|
420
|
+
const doubled = mapRead(num, (n) => String(n * 2));
|
|
421
|
+
enhance(el, bindProperty("textContent", doubled, controller.signal));
|
|
422
|
+
|
|
423
|
+
expect(el.textContent).toBe("10");
|
|
424
|
+
|
|
425
|
+
num.set(7);
|
|
426
|
+
expect(el.textContent).toBe("14");
|
|
427
|
+
|
|
428
|
+
controller.abort();
|
|
429
|
+
});
|
|
430
|
+
|
|
431
|
+
it("should bind FunRead from derive", () => {
|
|
432
|
+
const el = h("div");
|
|
433
|
+
const controller = new AbortController();
|
|
434
|
+
|
|
435
|
+
const first = funState("John");
|
|
436
|
+
const last = funState("Doe");
|
|
437
|
+
const full = derive(first, last, (f, l) => `${f} ${l}`);
|
|
438
|
+
enhance(el, bindProperty("textContent", full, controller.signal));
|
|
439
|
+
|
|
440
|
+
expect(el.textContent).toBe("John Doe");
|
|
441
|
+
|
|
442
|
+
first.set("Jane");
|
|
443
|
+
expect(el.textContent).toBe("Jane Doe");
|
|
444
|
+
|
|
445
|
+
last.set("Smith");
|
|
446
|
+
expect(el.textContent).toBe("Jane Smith");
|
|
447
|
+
|
|
448
|
+
controller.abort();
|
|
449
|
+
});
|
|
450
|
+
|
|
451
|
+
it("should compose mapRead over derive", () => {
|
|
452
|
+
const el = h("span");
|
|
453
|
+
const controller = new AbortController();
|
|
454
|
+
|
|
455
|
+
const price = funState(42);
|
|
456
|
+
const quantity = funState(3);
|
|
457
|
+
const total = derive(price, quantity, (p, q) => p * q);
|
|
458
|
+
const formatted = mapRead(total, (n) => `$${n.toFixed(2)}`);
|
|
459
|
+
enhance(el, bindProperty("textContent", formatted, controller.signal));
|
|
460
|
+
|
|
461
|
+
expect(el.textContent).toBe("$126.00");
|
|
462
|
+
|
|
463
|
+
price.set(50);
|
|
464
|
+
expect(el.textContent).toBe("$150.00");
|
|
465
|
+
|
|
466
|
+
controller.abort();
|
|
467
|
+
});
|
|
375
468
|
});
|
|
376
469
|
|
|
377
470
|
describe("on()", () => {
|
|
@@ -406,6 +499,76 @@ describe("on()", () => {
|
|
|
406
499
|
});
|
|
407
500
|
});
|
|
408
501
|
|
|
502
|
+
describe("bindView()", () => {
|
|
503
|
+
it("should default to a div container", () => {
|
|
504
|
+
const controller = new AbortController();
|
|
505
|
+
const state = funState(0);
|
|
506
|
+
|
|
507
|
+
const container = bindView(controller.signal, state, (_signal, value) =>
|
|
508
|
+
h("div", null, String(value))
|
|
509
|
+
) as HTMLElement;
|
|
510
|
+
|
|
511
|
+
state.set(1);
|
|
512
|
+
|
|
513
|
+
expect(container.tagName).toBe("DIV");
|
|
514
|
+
expect(container.textContent).toBe("1");
|
|
515
|
+
|
|
516
|
+
controller.abort();
|
|
517
|
+
});
|
|
518
|
+
|
|
519
|
+
it("should render into the provided container tag", () => {
|
|
520
|
+
const controller = new AbortController();
|
|
521
|
+
const state = funState(0);
|
|
522
|
+
|
|
523
|
+
const container = bindView(
|
|
524
|
+
controller.signal,
|
|
525
|
+
state,
|
|
526
|
+
(_signal, value) => h("div", null, String(value)),
|
|
527
|
+
{ tagName: "section" }
|
|
528
|
+
) as HTMLElement;
|
|
529
|
+
|
|
530
|
+
state.set(1);
|
|
531
|
+
|
|
532
|
+
expect(container.tagName).toBe("SECTION");
|
|
533
|
+
expect(container.children.length).toBe(1);
|
|
534
|
+
expect(container.textContent).toBe("1");
|
|
535
|
+
|
|
536
|
+
controller.abort();
|
|
537
|
+
});
|
|
538
|
+
|
|
539
|
+
it("should abort previous render on state changes", () => {
|
|
540
|
+
const controller = new AbortController();
|
|
541
|
+
const state = funState(0);
|
|
542
|
+
let abortCount = 0;
|
|
543
|
+
let renderCount = 0;
|
|
544
|
+
|
|
545
|
+
const container = bindView(
|
|
546
|
+
controller.signal,
|
|
547
|
+
state,
|
|
548
|
+
(signal, value) => {
|
|
549
|
+
renderCount += 1;
|
|
550
|
+
signal.addEventListener("abort", () => {
|
|
551
|
+
abortCount += 1;
|
|
552
|
+
});
|
|
553
|
+
return h("div", null, String(value));
|
|
554
|
+
},
|
|
555
|
+
{ tagName: "div" }
|
|
556
|
+
);
|
|
557
|
+
|
|
558
|
+
state.set(1);
|
|
559
|
+
expect(renderCount).toBe(1);
|
|
560
|
+
expect(abortCount).toBe(0);
|
|
561
|
+
|
|
562
|
+
state.set(2);
|
|
563
|
+
expect(renderCount).toBe(2);
|
|
564
|
+
expect(abortCount).toBe(1);
|
|
565
|
+
expect(container.textContent).toBe("2");
|
|
566
|
+
|
|
567
|
+
controller.abort();
|
|
568
|
+
expect(abortCount).toBe(2);
|
|
569
|
+
});
|
|
570
|
+
});
|
|
571
|
+
|
|
409
572
|
describe("pipeEndo()", () => {
|
|
410
573
|
it("should apply functions in order", () => {
|
|
411
574
|
const el = document.createElement("div");
|
|
@@ -493,7 +656,7 @@ describe("renderWhen", () => {
|
|
|
493
656
|
state: showState,
|
|
494
657
|
component: TestComponent,
|
|
495
658
|
props: { text: "Hello" },
|
|
496
|
-
signal: controller.signal
|
|
659
|
+
signal: controller.signal,
|
|
497
660
|
});
|
|
498
661
|
|
|
499
662
|
expect(container.children.length).toBe(1);
|
|
@@ -513,7 +676,7 @@ describe("renderWhen", () => {
|
|
|
513
676
|
state: showState,
|
|
514
677
|
component: TestComponent,
|
|
515
678
|
props: { text: "Hello" },
|
|
516
|
-
signal: controller.signal
|
|
679
|
+
signal: controller.signal,
|
|
517
680
|
});
|
|
518
681
|
|
|
519
682
|
expect(container.children.length).toBe(0);
|
|
@@ -529,7 +692,7 @@ describe("renderWhen", () => {
|
|
|
529
692
|
state: showState,
|
|
530
693
|
component: TestComponent,
|
|
531
694
|
props: { text: "Hello" },
|
|
532
|
-
signal: controller.signal
|
|
695
|
+
signal: controller.signal,
|
|
533
696
|
});
|
|
534
697
|
|
|
535
698
|
expect(container.children.length).toBe(0);
|
|
@@ -550,7 +713,7 @@ describe("renderWhen", () => {
|
|
|
550
713
|
state: showState,
|
|
551
714
|
component: TestComponent,
|
|
552
715
|
props: { text: "Hello" },
|
|
553
|
-
signal: controller.signal
|
|
716
|
+
signal: controller.signal,
|
|
554
717
|
});
|
|
555
718
|
|
|
556
719
|
const child = container.children[0] as HTMLElement;
|
|
@@ -581,7 +744,7 @@ describe("renderWhen", () => {
|
|
|
581
744
|
state: showState,
|
|
582
745
|
component: ComponentWithAbortListener,
|
|
583
746
|
props: { text: "Hello" },
|
|
584
|
-
signal: controller.signal
|
|
747
|
+
signal: controller.signal,
|
|
585
748
|
});
|
|
586
749
|
|
|
587
750
|
expect(abortCallback).not.toHaveBeenCalled();
|
|
@@ -610,7 +773,7 @@ describe("renderWhen", () => {
|
|
|
610
773
|
state: showState,
|
|
611
774
|
component: ComponentWithAbortListener,
|
|
612
775
|
props: { text: "Hello" },
|
|
613
|
-
signal: controller.signal
|
|
776
|
+
signal: controller.signal,
|
|
614
777
|
});
|
|
615
778
|
|
|
616
779
|
expect(abortCallback).not.toHaveBeenCalled();
|
|
@@ -628,7 +791,7 @@ describe("renderWhen", () => {
|
|
|
628
791
|
state: showState,
|
|
629
792
|
component: TestComponent,
|
|
630
793
|
props: { text: "Hello" },
|
|
631
|
-
signal: controller.signal
|
|
794
|
+
signal: controller.signal,
|
|
632
795
|
});
|
|
633
796
|
|
|
634
797
|
expect(container.children.length).toBe(0);
|
|
@@ -656,7 +819,7 @@ describe("renderWhen", () => {
|
|
|
656
819
|
state: showState,
|
|
657
820
|
component: TestComponent,
|
|
658
821
|
props: { text: "Hello" },
|
|
659
|
-
signal: controller.signal
|
|
822
|
+
signal: controller.signal,
|
|
660
823
|
}) as HTMLElement;
|
|
661
824
|
|
|
662
825
|
expect(container.style.display).toBe("contents");
|
|
@@ -672,7 +835,7 @@ describe("renderWhen", () => {
|
|
|
672
835
|
state: showState,
|
|
673
836
|
component: TestComponent,
|
|
674
837
|
props: { text: "Hello" },
|
|
675
|
-
signal: controller.signal
|
|
838
|
+
signal: controller.signal,
|
|
676
839
|
});
|
|
677
840
|
|
|
678
841
|
expect(container.children.length).toBe(1);
|
|
@@ -692,18 +855,14 @@ describe("renderWhen", () => {
|
|
|
692
855
|
_signal: AbortSignal,
|
|
693
856
|
props: { text: string; count: number }
|
|
694
857
|
) => {
|
|
695
|
-
return h(
|
|
696
|
-
"div",
|
|
697
|
-
null,
|
|
698
|
-
`${props.text}: ${props.count}`
|
|
699
|
-
);
|
|
858
|
+
return h("div", null, `${props.text}: ${props.count}`);
|
|
700
859
|
};
|
|
701
860
|
|
|
702
861
|
const container = renderWhen({
|
|
703
862
|
state: showState,
|
|
704
863
|
component: PropsComponent,
|
|
705
864
|
props: { text: "Count", count: 42 },
|
|
706
|
-
signal: controller.signal
|
|
865
|
+
signal: controller.signal,
|
|
707
866
|
});
|
|
708
867
|
|
|
709
868
|
expect(container.children[0].textContent).toBe("Count: 42");
|
|
@@ -713,7 +872,11 @@ describe("renderWhen", () => {
|
|
|
713
872
|
|
|
714
873
|
test("should work with predicate function", () => {
|
|
715
874
|
const controller = new AbortController();
|
|
716
|
-
enum Status {
|
|
875
|
+
enum Status {
|
|
876
|
+
Loading,
|
|
877
|
+
Success,
|
|
878
|
+
Error,
|
|
879
|
+
}
|
|
717
880
|
const statusState = funState(Status.Loading);
|
|
718
881
|
|
|
719
882
|
const container = renderWhen({
|
|
@@ -721,7 +884,7 @@ describe("renderWhen", () => {
|
|
|
721
884
|
predicate: (status) => status === Status.Success,
|
|
722
885
|
component: TestComponent,
|
|
723
886
|
props: { text: "Success!" },
|
|
724
|
-
signal: controller.signal
|
|
887
|
+
signal: controller.signal,
|
|
725
888
|
});
|
|
726
889
|
|
|
727
890
|
// Should not render initially
|
|
@@ -742,4 +905,4 @@ describe("renderWhen", () => {
|
|
|
742
905
|
|
|
743
906
|
controller.abort();
|
|
744
907
|
});
|
|
745
|
-
});
|
|
908
|
+
});
|
package/src/dom.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/** DOM utilities for functional element creation and manipulation */
|
|
2
|
-
import { type FunState } from "@fun-land/fun-state";
|
|
2
|
+
import { type FunState, type FunRead } from "@fun-land/fun-state";
|
|
3
3
|
import type { Component, ElementChild } from "./types";
|
|
4
4
|
import { Accessor } from "@fun-land/accessor";
|
|
5
5
|
|
|
@@ -11,7 +11,8 @@ export type Enhancer<El extends Element> = (element: El) => El;
|
|
|
11
11
|
*/
|
|
12
12
|
const entries = <T extends Record<string, unknown>>(
|
|
13
13
|
obj: T
|
|
14
|
-
): Array<[keyof T, T[keyof T]]> =>
|
|
14
|
+
): Array<[keyof T, T[keyof T]]> =>
|
|
15
|
+
Object.entries(obj) as Array<[keyof T, T[keyof T]]>;
|
|
15
16
|
|
|
16
17
|
/**
|
|
17
18
|
* Create an HTML element with attributes and children
|
|
@@ -30,6 +31,7 @@ export const h = <Tag extends keyof HTMLElementTagNameMap>(
|
|
|
30
31
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
31
32
|
attrs?: Record<string, any> | null,
|
|
32
33
|
children?: ElementChild | ElementChild[]
|
|
34
|
+
// eslint-disable-next-line complexity
|
|
33
35
|
): HTMLElementTagNameMap[Tag] => {
|
|
34
36
|
const element = document.createElement(tag);
|
|
35
37
|
|
|
@@ -82,7 +84,7 @@ type HxHandlers<El extends Element> = Partial<{
|
|
|
82
84
|
}>;
|
|
83
85
|
|
|
84
86
|
type HxBindings<El extends Element> = Partial<{
|
|
85
|
-
[K in WritableKeys<El> & string]:
|
|
87
|
+
[K in WritableKeys<El> & string]: FunRead<El[K]>;
|
|
86
88
|
}>;
|
|
87
89
|
|
|
88
90
|
type HxOptionsBase<El extends Element> = {
|
|
@@ -145,15 +147,13 @@ export function hx<Tag extends keyof HTMLElementTagNameMap>(
|
|
|
145
147
|
}
|
|
146
148
|
|
|
147
149
|
if (bind) {
|
|
148
|
-
const bindElementProperty = <
|
|
150
|
+
const bindElementProperty = <
|
|
151
|
+
K extends WritableKeys<HTMLElementTagNameMap[Tag]> & string,
|
|
152
|
+
>(
|
|
149
153
|
key: K,
|
|
150
|
-
state:
|
|
154
|
+
state: FunRead<HTMLElementTagNameMap[Tag][K]>
|
|
151
155
|
): void => {
|
|
152
|
-
bindProperty<HTMLElementTagNameMap[Tag], K>(
|
|
153
|
-
key,
|
|
154
|
-
state,
|
|
155
|
-
signal
|
|
156
|
-
)(element);
|
|
156
|
+
bindProperty<HTMLElementTagNameMap[Tag], K>(key, state, signal)(element);
|
|
157
157
|
};
|
|
158
158
|
|
|
159
159
|
for (const key of Object.keys(bind) as Array<
|
|
@@ -161,10 +161,7 @@ export function hx<Tag extends keyof HTMLElementTagNameMap>(
|
|
|
161
161
|
>) {
|
|
162
162
|
const state = bind[key];
|
|
163
163
|
if (!state) continue;
|
|
164
|
-
bindElementProperty(
|
|
165
|
-
key,
|
|
166
|
-
state
|
|
167
|
-
);
|
|
164
|
+
bindElementProperty(key, state);
|
|
168
165
|
}
|
|
169
166
|
}
|
|
170
167
|
|
|
@@ -240,7 +237,7 @@ export const attrs =
|
|
|
240
237
|
export const bindProperty =
|
|
241
238
|
<E extends Element, K extends keyof E & string>(
|
|
242
239
|
key: K,
|
|
243
|
-
state:
|
|
240
|
+
state: FunRead<E[K]>,
|
|
244
241
|
signal: AbortSignal
|
|
245
242
|
) =>
|
|
246
243
|
(el: E): E => {
|
|
@@ -254,6 +251,38 @@ export const bindProperty =
|
|
|
254
251
|
return el;
|
|
255
252
|
};
|
|
256
253
|
|
|
254
|
+
/**
|
|
255
|
+
* Render a single slot from state and abort previous render on updates.
|
|
256
|
+
* Useful when render creates subscriptions, timers, or event handlers.
|
|
257
|
+
* Defaults to a "div" container when no tagName is provided.
|
|
258
|
+
*/
|
|
259
|
+
export const bindView = <
|
|
260
|
+
Tag extends keyof HTMLElementTagNameMap = "div",
|
|
261
|
+
T = unknown,
|
|
262
|
+
>(
|
|
263
|
+
signal: AbortSignal,
|
|
264
|
+
state: FunRead<T>,
|
|
265
|
+
render: (regionSignal: AbortSignal, data: T) => Element,
|
|
266
|
+
options?: { tagName?: Tag }
|
|
267
|
+
): HTMLElementTagNameMap[Tag] => {
|
|
268
|
+
const tagName = (options?.tagName ?? "div") as Tag;
|
|
269
|
+
const element = document.createElement(tagName);
|
|
270
|
+
let childCtrl: AbortController | null = null;
|
|
271
|
+
state.watch(signal, (data) => {
|
|
272
|
+
childCtrl?.abort();
|
|
273
|
+
childCtrl = new AbortController();
|
|
274
|
+
element.replaceChildren(render(childCtrl.signal, data));
|
|
275
|
+
});
|
|
276
|
+
signal.addEventListener(
|
|
277
|
+
"abort",
|
|
278
|
+
() => {
|
|
279
|
+
childCtrl?.abort();
|
|
280
|
+
},
|
|
281
|
+
{ once: true }
|
|
282
|
+
);
|
|
283
|
+
return element;
|
|
284
|
+
};
|
|
285
|
+
|
|
257
286
|
/**
|
|
258
287
|
* Add CSS classes to an element (returns element for chaining)
|
|
259
288
|
* @returns {Enhancer}
|
|
@@ -386,6 +415,7 @@ export const bindListChildren =
|
|
|
386
415
|
rows.clear();
|
|
387
416
|
};
|
|
388
417
|
|
|
418
|
+
// eslint-disable-next-line complexity
|
|
389
419
|
const reconcile = (): void => {
|
|
390
420
|
const items = list.get();
|
|
391
421
|
|
|
@@ -468,7 +498,7 @@ export const bindListChildren =
|
|
|
468
498
|
* });
|
|
469
499
|
*/
|
|
470
500
|
export function renderWhen<State, Props>(options: {
|
|
471
|
-
state:
|
|
501
|
+
state: FunRead<State>;
|
|
472
502
|
predicate?: (value: State) => boolean;
|
|
473
503
|
component: Component<Props>;
|
|
474
504
|
props: Props;
|
|
@@ -487,6 +517,7 @@ export function renderWhen<State, Props>(options: {
|
|
|
487
517
|
let childCtrl: AbortController | null = null;
|
|
488
518
|
let childEl: Element | null = null;
|
|
489
519
|
|
|
520
|
+
// eslint-disable-next-line complexity
|
|
490
521
|
const reconcile = () => {
|
|
491
522
|
const shouldRender = predicate(state.get());
|
|
492
523
|
|
|
@@ -524,11 +555,11 @@ export function renderWhen<State, Props>(options: {
|
|
|
524
555
|
|
|
525
556
|
/** add passed class (idempotent) to element when state returns true */
|
|
526
557
|
export const bindClass =
|
|
527
|
-
(className: string, state:
|
|
558
|
+
(className: string, state: FunRead<boolean>, signal: AbortSignal) =>
|
|
528
559
|
<E extends Element>(el: E): E => {
|
|
529
560
|
state.watch(signal, (active) => el.classList.toggle(className, active));
|
|
530
561
|
return el;
|
|
531
562
|
};
|
|
532
563
|
|
|
533
564
|
export const querySelectorAll = <T extends Element>(selector: string): T[] =>
|
|
534
|
-
Array.from(document.querySelectorAll(selector));
|
|
565
|
+
Array.from(document.querySelectorAll(selector));
|
package/src/mount.test.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { mount } from "./mount";
|
|
2
2
|
import { h } from "./dom";
|
|
3
|
-
import {
|
|
4
|
-
import
|
|
3
|
+
import type { Component } from "./index";
|
|
4
|
+
import { funState, type FunState } from "@fun-land/fun-state";
|
|
5
5
|
|
|
6
6
|
describe("mount()", () => {
|
|
7
7
|
let container: HTMLDivElement;
|