@fun-land/fun-web 0.5.0 → 0.6.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fun-land/fun-web",
3
- "version": "0.5.0",
3
+ "version": "0.6.0",
4
4
  "description": "A web library for component-based development using fun-land",
5
5
  "main": "dist/src/index.js",
6
6
  "module": "dist/esm/src/index.js",
@@ -30,7 +30,7 @@
30
30
  },
31
31
  "devDependencies": {
32
32
  "@fun-land/accessor": "4.0.2",
33
- "@fun-land/fun-state": "9.0.1",
33
+ "@fun-land/fun-state": "9.1.0",
34
34
  "esbuild": "^0.24.2"
35
35
  },
36
36
  "scripts": {
@@ -49,5 +49,5 @@
49
49
  "url": "https://github.com/fun-land/fun-land/issues"
50
50
  },
51
51
  "license": "MIT",
52
- "gitHead": "0a5f0f1e9c1aa37b37041f06b3fe7bd79280a529"
52
+ "gitHead": "1968bacf79ec442f1e1808721a150f4f2b0cbea8"
53
53
  }
package/src/dom.test.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import {
2
2
  h,
3
+ hx,
3
4
  text,
4
5
  attr,
5
6
  attrs,
@@ -10,11 +11,10 @@ import {
10
11
  removeClass,
11
12
  on,
12
13
  enhance,
13
- $,
14
- $$,
14
+ querySelectorAll,
15
15
  renderWhen,
16
16
  } from "./dom";
17
- import { FunState, funState } from "./state";
17
+ import { funState } from "@fun-land/fun-state";
18
18
 
19
19
  describe("h()", () => {
20
20
  it("should create an element", () => {
@@ -116,6 +116,63 @@ describe("h()", () => {
116
116
  });
117
117
  });
118
118
 
119
+ describe("hx()", () => {
120
+ it("should apply props and attrs", () => {
121
+ const controller = new AbortController();
122
+ const el = hx("div", {
123
+ signal: controller.signal,
124
+ props: { id: "test", className: "foo" },
125
+ attrs: { "data-test": "value", role: "button" },
126
+ });
127
+
128
+ expect(el.id).toBe("test");
129
+ expect(el.className).toBe("foo");
130
+ expect(el.getAttribute("data-test")).toBe("value");
131
+ expect(el.getAttribute("role")).toBe("button");
132
+ });
133
+
134
+ it("should append children", () => {
135
+ const child = h("span", null, "child");
136
+ const controller = new AbortController();
137
+ const el = hx("div", { signal: controller.signal }, child);
138
+
139
+ expect(el.children.length).toBe(1);
140
+ expect(el.textContent).toBe("child");
141
+ });
142
+
143
+ it("should bind properties and events with signal", () => {
144
+ const controller = new AbortController();
145
+ const state = funState("hello");
146
+ const handler = jest.fn();
147
+
148
+ const el = hx("input", {
149
+ signal: controller.signal,
150
+ bind: { value: state },
151
+ on: { input: handler },
152
+ });
153
+
154
+ expect(el.value).toBe("hello");
155
+
156
+ state.set("world");
157
+ expect(el.value).toBe("world");
158
+
159
+ el.dispatchEvent(new Event("input"));
160
+ expect(handler).toHaveBeenCalledTimes(1);
161
+
162
+ controller.abort();
163
+ state.set("after");
164
+ el.dispatchEvent(new Event("input"));
165
+ expect(el.value).toBe("world");
166
+ expect(handler).toHaveBeenCalledTimes(1);
167
+ });
168
+
169
+ it("should require signal", () => {
170
+ expect(() => (hx as unknown as (tag: string) => Element)("div")).toThrow(
171
+ "hx: signal is required"
172
+ );
173
+ });
174
+ });
175
+
119
176
  describe("text()", () => {
120
177
  it("should set text content", () => {
121
178
  const el = document.createElement("div");
@@ -367,353 +424,7 @@ describe("pipeEndo()", () => {
367
424
  });
368
425
  });
369
426
 
370
- type Keyed = { key: string };
371
- type Item = Keyed & { label: string };
372
-
373
- function makeAbortSignal(): {
374
- controller: AbortController;
375
- signal: AbortSignal;
376
- } {
377
- const controller = new AbortController();
378
- return { controller, signal: controller.signal };
379
- }
380
-
381
- /**
382
- * Because the renderRow above can't reliably read the key from itemState in this harness,
383
- * we’ll make a dedicated renderRow for tests that receives the key by closure from the list.
384
- *
385
- * This version doesn't require list.focus, so it's robust.
386
- */
387
- // function setupKeyedChildrenWithKeyAwareRenderer(
388
- // parent: Element,
389
- // signal: AbortSignal,
390
- // listState: { get: () => Item[]; watch: FunState<Item[]>["watch"] }
391
- // ) {
392
- // const mountCountByKey = new Map<string, number>();
393
- // const abortCountByKey = new Map<string, number>();
394
- // const elByKey = new Map<string, Element>();
395
-
396
- // const api = keyedChildren<Item>(parent, signal, listState as any, (row) => {
397
- // // In your real impl, itemState.get().key should exist.
398
- // const item = row.state.get();
399
- // const k = item.key;
400
-
401
- // const el = document.createElement("li");
402
- // el.dataset.key = k;
403
- // el.textContent = item.label;
404
-
405
- // elByKey.set(k, el);
406
- // mountCountByKey.set(k, (mountCountByKey.get(k) ?? 0) + 1);
407
-
408
- // row.signal.addEventListener("abort", () => {
409
- // abortCountByKey.set(k, (abortCountByKey.get(k) ?? 0) + 1);
410
- // });
411
-
412
- // return el;
413
- // });
414
-
415
- // return { api, mountCountByKey, abortCountByKey, elByKey };
416
- // }
417
-
418
- // describe("keyedChildren", () => {
419
- // test("mounts initial rows in order", () => {
420
- // const container = document.createElement("ul");
421
- // const { controller, signal } = makeAbortSignal();
422
-
423
- // const listState = funState<Item[]>([
424
- // { key: "a", label: "A" },
425
- // { key: "b", label: "B" },
426
- // ]);
427
-
428
- // // Use the key-aware renderer; it expects itemState.get()
429
- // const { elByKey } = setupKeyedChildrenWithKeyAwareRenderer(
430
- // container,
431
- // signal,
432
- // listState
433
- // );
434
-
435
- // expect(container.children).toHaveLength(2);
436
- // expect((container.children[0] as HTMLElement).dataset.key).toBe("a");
437
- // expect((container.children[1] as HTMLElement).dataset.key).toBe("b");
438
- // expect(elByKey.get("a")?.textContent).toBe("A");
439
- // expect(elByKey.get("b")?.textContent).toBe("B");
440
-
441
- // controller.abort();
442
- // });
443
-
444
- // test("does not recreate existing row elements when item contents change", () => {
445
- // const container = document.createElement("ul");
446
- // const { controller, signal } = makeAbortSignal();
447
-
448
- // const listState = funState<Item[]>([
449
- // { key: "a", label: "A" },
450
- // { key: "b", label: "B" },
451
- // ]);
452
-
453
- // const { mountCountByKey } = setupKeyedChildrenWithKeyAwareRenderer(
454
- // container,
455
- // signal,
456
- // listState
457
- // );
458
-
459
- // const firstElA = container.children[0] as Element;
460
- // const firstElB = container.children[1] as Element;
461
-
462
- // // Update label of "a" (array ref changes)
463
- // listState.set([
464
- // { key: "a", label: "A!" },
465
- // { key: "b", label: "B" },
466
- // ]);
467
-
468
- // // Elements should be the same instances (not remounted)
469
- // expect(container.children[0]).toBe(firstElA);
470
- // expect(container.children[1]).toBe(firstElB);
471
-
472
- // // Mount counts should still be 1 each
473
- // expect(mountCountByKey.get("a")).toBe(1);
474
- // expect(mountCountByKey.get("b")).toBe(1);
475
-
476
- // controller.abort();
477
- // });
478
-
479
- // test("reorders by moving existing nodes, without recreating them", () => {
480
- // const container = document.createElement("ul");
481
- // const { controller, signal } = makeAbortSignal();
482
-
483
- // const listeners = new Set<(xs: Item[]) => void>();
484
- // let items: Item[] = [
485
- // { key: "a", label: "A" },
486
- // { key: "b", label: "B" },
487
- // { key: "c", label: "C" },
488
- // ];
489
-
490
- // const listState: FunState<Item[]> = {
491
- // get: () => items,
492
- // query: () => {
493
- // throw new Error("not used");
494
- // },
495
- // mod: (f: (items: Item[]) => Item[]) => {
496
- // items = f(items);
497
- // listeners.forEach((l) => l(items));
498
- // },
499
- // set: (v: Item[]) => {
500
- // items = v;
501
- // listeners.forEach((l) => l(items));
502
- // },
503
- // focus: (acc: any) =>
504
- // ({
505
- // get: () => acc.query(items)[0],
506
- // watch: (_s: AbortSignal, _cb: any) => void 0,
507
- // }) as any,
508
- // prop: () => {
509
- // throw new Error("not used");
510
- // },
511
- // watch: (sig: AbortSignal, cb: (items: Item[]) => void) => {
512
- // listeners.add(cb);
513
- // sig.addEventListener(
514
- // "abort",
515
- // () => {
516
- // listeners.delete(cb);
517
- // },
518
- // { once: true }
519
- // );
520
- // },
521
- // watchAll: (_sig: AbortSignal, _cb: (values: Item[][]) => void) => {
522
- // throw new Error("watchAll not used in these tests");
523
- // },
524
- // };
525
-
526
- // const { mountCountByKey } = setupKeyedChildrenWithKeyAwareRenderer(
527
- // container,
528
- // signal,
529
- // listState
530
- // );
531
-
532
- // const elA = container.children[0];
533
- // const elB = container.children[1];
534
- // const elC = container.children[2];
535
-
536
- // // Reorder
537
- // listState.set([
538
- // { key: "c", label: "C" },
539
- // { key: "a", label: "A" },
540
- // { key: "b", label: "B" },
541
- // ]);
542
-
543
- // expect((container.children[0] as HTMLElement).dataset.key).toBe("c");
544
- // expect((container.children[1] as HTMLElement).dataset.key).toBe("a");
545
- // expect((container.children[2] as HTMLElement).dataset.key).toBe("b");
546
-
547
- // // Same element identity (moved, not recreated)
548
- // expect(container.children[1]).toBe(elA);
549
- // expect(container.children[2]).toBe(elB);
550
- // expect(container.children[0]).toBe(elC);
551
-
552
- // // Not remounted
553
- // expect(mountCountByKey.get("a")).toBe(1);
554
- // expect(mountCountByKey.get("b")).toBe(1);
555
- // expect(mountCountByKey.get("c")).toBe(1);
556
-
557
- // controller.abort();
558
- // });
559
-
560
- // test("removes rows when keys disappear and aborts their row controllers", () => {
561
- // const container = document.createElement("ul");
562
- // const { controller, signal } = makeAbortSignal();
563
-
564
- // const listeners = new Set<(xs: Item[]) => void>();
565
- // let items: Item[] = [
566
- // { key: "a", label: "A" },
567
- // { key: "b", label: "B" },
568
- // ];
569
-
570
- // const listState: FunState<Item[]> = {
571
- // get: () => items,
572
- // query: () => {
573
- // throw new Error("not used");
574
- // },
575
- // mod: (f: (items: Item[]) => Item[]) => {
576
- // items = f(items);
577
- // listeners.forEach((l) => l(items));
578
- // },
579
- // set: (v: Item[]) => {
580
- // items = v;
581
- // listeners.forEach((l) => l(items));
582
- // },
583
- // focus: (acc: any) =>
584
- // ({
585
- // get: () => acc.query(items)[0],
586
- // watch: (_s: AbortSignal, _cb: any) => void 0,
587
- // }) as any,
588
- // prop: () => {
589
- // throw new Error("not used");
590
- // },
591
- // watch: (sig: AbortSignal, cb: (items: Item[]) => void) => {
592
- // listeners.add(cb);
593
- // sig.addEventListener(
594
- // "abort",
595
- // () => {
596
- // listeners.delete(cb);
597
- // },
598
- // { once: true }
599
- // );
600
- // },
601
- // watchAll: (_sig: AbortSignal, _cb: (values: Item[][]) => void) => {
602
- // throw new Error("watchAll not used in these tests");
603
- // },
604
- // };
605
-
606
- // const { abortCountByKey } = setupKeyedChildrenWithKeyAwareRenderer(
607
- // container,
608
- // signal,
609
- // listState
610
- // );
611
-
612
- // expect(container.children).toHaveLength(2);
613
-
614
- // // Remove "a"
615
- // listState.set([{ key: "b", label: "B" }]);
616
-
617
- // expect(container.children).toHaveLength(1);
618
- // expect((container.children[0] as HTMLElement).dataset.key).toBe("b");
619
-
620
- // // "a" row controller should have been aborted once
621
- // expect(abortCountByKey.get("a")).toBe(1);
622
-
623
- // controller.abort();
624
- // });
625
-
626
- // test("unsubscribes from list updates on parent abort", () => {
627
- // const container = document.createElement("ul");
628
- // const { controller, signal } = makeAbortSignal();
629
-
630
- // const list = funState<Item[]>([{ key: "a", label: "A" }]);
631
-
632
- // // Subscribe and verify callback is called before abort
633
- // const callback = jest.fn();
634
- // list.watch(signal, callback);
635
-
636
- // list.set([{ key: "a", label: "A2" }]);
637
- // // Called with initial value, then with updated value
638
- // expect(callback).toHaveBeenCalledTimes(2);
639
-
640
- // // After abort, callback should not be called
641
- // controller.abort();
642
-
643
- // list.set([{ key: "a", label: "A3" }]);
644
- // expect(callback).toHaveBeenCalledTimes(2); // Still 2, not called again
645
-
646
- // void container;
647
- // });
648
-
649
- // test("throws error on duplicate keys", () => {
650
- // const container = document.createElement("ul");
651
- // const { controller, signal } = makeAbortSignal();
652
-
653
- // const listState = funState<Item[]>([
654
- // { key: "a", label: "A" },
655
- // { key: "a", label: "B" }, // duplicate key!
656
- // ]);
657
-
658
- // expect(() => {
659
- // keyedChildren(container, signal, listState, () => {
660
- // return document.createElement("li");
661
- // });
662
- // }).toThrow('keyedChildren: duplicate key "a"');
663
-
664
- // controller.abort();
665
- // });
666
- // });
667
-
668
- describe("$ (querySelector)", () => {
669
- beforeEach(() => {
670
- document.body.innerHTML = "";
671
- });
672
-
673
- test("should return element if found", () => {
674
- const div = h("div", { id: "test", className: "my-class" });
675
- document.body.appendChild(div);
676
-
677
- const result = $<HTMLDivElement>("#test");
678
- expect(result).toBe(div);
679
- expect(result?.id).toBe("test");
680
- });
681
-
682
- test("should return undefined if not found", () => {
683
- const result = $("#nonexistent");
684
- expect(result).toBeUndefined();
685
- });
686
-
687
- test("should work with class selectors", () => {
688
- const div = h("div", { className: "test-class" });
689
- document.body.appendChild(div);
690
-
691
- const result = $(".test-class");
692
- expect(result).toBe(div);
693
- });
694
-
695
- test("should return first matching element", () => {
696
- const div1 = h("div", { className: "item" });
697
- const div2 = h("div", { className: "item" });
698
- document.body.appendChild(div1);
699
- document.body.appendChild(div2);
700
-
701
- const result = $(".item");
702
- expect(result).toBe(div1);
703
- });
704
-
705
- test("should infer element type", () => {
706
- const input = h("input", { type: "text", id: "myinput" });
707
- document.body.appendChild(input);
708
-
709
- const result = $<HTMLInputElement>("#myinput");
710
- expect(result).toBe(input);
711
- // TypeScript should know this is HTMLInputElement
712
- expect(result!.value).toBeDefined();
713
- });
714
- });
715
-
716
- describe("$$ (querySelectorAll)", () => {
427
+ describe("querySelectorAll()", () => {
717
428
  beforeEach(() => {
718
429
  document.body.innerHTML = "";
719
430
  });
@@ -726,7 +437,7 @@ describe("$$ (querySelectorAll)", () => {
726
437
  document.body.appendChild(div2);
727
438
  document.body.appendChild(div3);
728
439
 
729
- const results = $$(".item");
440
+ const results = querySelectorAll(".item");
730
441
  expect(results).toHaveLength(3);
731
442
  expect(results[0]).toBe(div1);
732
443
  expect(results[1]).toBe(div2);
@@ -734,7 +445,7 @@ describe("$$ (querySelectorAll)", () => {
734
445
  });
735
446
 
736
447
  test("should return empty array if no matches", () => {
737
- const results = $$(".nonexistent");
448
+ const results = querySelectorAll(".nonexistent");
738
449
  expect(results).toEqual([]);
739
450
  expect(Array.isArray(results)).toBe(true);
740
451
  });
@@ -747,7 +458,7 @@ describe("$$ (querySelectorAll)", () => {
747
458
  container.appendChild(item2);
748
459
  document.body.appendChild(container);
749
460
 
750
- const results = $$<HTMLSpanElement>("#container .item");
461
+ const results = querySelectorAll<HTMLSpanElement>("#container .item");
751
462
  expect(results).toHaveLength(2);
752
463
  expect(results[0]).toBe(item1);
753
464
  expect(results[1]).toBe(item2);
@@ -757,7 +468,7 @@ describe("$$ (querySelectorAll)", () => {
757
468
  const div1 = h("div", { className: "item" });
758
469
  document.body.appendChild(div1);
759
470
 
760
- const results = $$(".item");
471
+ const results = querySelectorAll(".item");
761
472
  expect(Array.isArray(results)).toBe(true);
762
473
  // Should have array methods
763
474
  expect(typeof results.map).toBe("function");
package/src/dom.ts CHANGED
@@ -5,6 +5,14 @@ import { Accessor } from "@fun-land/accessor";
5
5
 
6
6
  export type Enhancer<El extends Element> = (element: El) => El;
7
7
 
8
+ /**
9
+ * Type-preserving Object.entries helper for objects with known keys
10
+ * @internal
11
+ */
12
+ const entries = <T extends Record<string, unknown>>(
13
+ obj: T
14
+ ): Array<[keyof T, T[keyof T]]> => Object.entries(obj) as Array<[keyof T, T[keyof T]]>;
15
+
8
16
  /**
9
17
  * Create an HTML element with attributes and children
10
18
  *
@@ -54,6 +62,124 @@ export const h = <Tag extends keyof HTMLElementTagNameMap>(
54
62
  return element;
55
63
  };
56
64
 
65
+ // Helper type to extract only writable properties
66
+ type WritableKeys<T> = {
67
+ [K in keyof T]-?: (<F>() => F extends { [Q in K]: T[K] } ? 1 : 2) extends <
68
+ F,
69
+ >() => F extends { -readonly [Q in K]: T[K] } ? 1 : 2
70
+ ? K
71
+ : never;
72
+ }[keyof T];
73
+
74
+ type HxProps<El extends Element> = Partial<{
75
+ [K in WritableKeys<El> & string]: El[K] | null | undefined;
76
+ }>;
77
+
78
+ type HxHandlers<El extends Element> = Partial<{
79
+ [K in keyof GlobalEventHandlersEventMap]: (
80
+ ev: GlobalEventHandlersEventMap[K] & { currentTarget: El }
81
+ ) => void;
82
+ }>;
83
+
84
+ type HxBindings<El extends Element> = Partial<{
85
+ [K in WritableKeys<El> & string]: FunState<El[K]>;
86
+ }>;
87
+
88
+ type HxOptionsBase<El extends Element> = {
89
+ props?: HxProps<El>;
90
+ attrs?: Record<string, string | number | boolean | null | undefined>;
91
+ };
92
+
93
+ type HxOptions<El extends Element> = HxOptionsBase<El> & {
94
+ signal: AbortSignal;
95
+ on?: HxHandlers<El>;
96
+ bind?: HxBindings<El>;
97
+ };
98
+
99
+ /**
100
+ * Create an element with structured props, attrs, event handlers, and bindings.
101
+ *
102
+ * @example
103
+ * hx("input", {
104
+ * signal,
105
+ * props: { type: "text" },
106
+ * attrs: { "data-test": "name" },
107
+ * bind: { value: nameState },
108
+ * on: { input: (e) => nameState.set(e.currentTarget.value) },
109
+ * });
110
+ */
111
+ export function hx<Tag extends keyof HTMLElementTagNameMap>(
112
+ tag: Tag,
113
+ options: HxOptions<HTMLElementTagNameMap[Tag]>,
114
+ children?: ElementChild | ElementChild[]
115
+ ): HTMLElementTagNameMap[Tag];
116
+ // eslint-disable-next-line complexity
117
+ export function hx<Tag extends keyof HTMLElementTagNameMap>(
118
+ tag: Tag,
119
+ options: HxOptions<HTMLElementTagNameMap[Tag]>,
120
+ children?: ElementChild | ElementChild[]
121
+ ): HTMLElementTagNameMap[Tag] {
122
+ if (!options?.signal) {
123
+ throw new Error("hx: signal is required");
124
+ }
125
+
126
+ const { signal, props, attrs: attrMap, on: onMap, bind } = options;
127
+ const element = document.createElement(tag);
128
+
129
+ if (props) {
130
+ for (const [key, value] of entries(props)) {
131
+ if (value == null) continue;
132
+ element[key] = value;
133
+ }
134
+ }
135
+
136
+ if (attrMap) {
137
+ for (const [key, value] of Object.entries(attrMap)) {
138
+ if (value == null) continue;
139
+ element.setAttribute(key, String(value));
140
+ }
141
+ }
142
+
143
+ if (children != null) {
144
+ appendChildren(children)(element);
145
+ }
146
+
147
+ if (bind) {
148
+ const bindElementProperty = <K extends WritableKeys<HTMLElementTagNameMap[Tag]> & string>(
149
+ key: K,
150
+ state: FunState<HTMLElementTagNameMap[Tag][K]>
151
+ ): void => {
152
+ bindProperty<HTMLElementTagNameMap[Tag], K>(
153
+ key,
154
+ state,
155
+ signal
156
+ )(element);
157
+ };
158
+
159
+ for (const key of Object.keys(bind) as Array<
160
+ WritableKeys<HTMLElementTagNameMap[Tag]> & string
161
+ >) {
162
+ const state = bind[key];
163
+ if (!state) continue;
164
+ bindElementProperty(
165
+ key,
166
+ state
167
+ );
168
+ }
169
+ }
170
+
171
+ if (onMap) {
172
+ for (const [event, handler] of Object.entries(onMap)) {
173
+ if (!handler) continue;
174
+ element.addEventListener(event, handler as EventListener, {
175
+ signal,
176
+ });
177
+ }
178
+ }
179
+
180
+ return element;
181
+ }
182
+
57
183
  /**
58
184
  * Append children to an element, flattening arrays and converting primitives to text nodes
59
185
  * @returns {Enhancer}
@@ -396,8 +522,13 @@ export function renderWhen<State, Props>(options: {
396
522
  return container;
397
523
  }
398
524
 
399
- export const $ = <T extends Element>(selector: string): T | undefined =>
400
- document.querySelector<T>(selector) ?? undefined;
525
+ /** add passed class (idempotent) to element when state returns true */
526
+ export const bindClass =
527
+ (className: string, state: FunState<boolean>, signal: AbortSignal) =>
528
+ <E extends Element>(el: E): E => {
529
+ state.watch(signal, (active) => el.classList.toggle(className, active));
530
+ return el;
531
+ };
401
532
 
402
- export const $$ = <T extends Element>(selector: string): T[] =>
533
+ export const querySelectorAll = <T extends Element>(selector: string): T[] =>
403
534
  Array.from(document.querySelectorAll(selector));
package/src/index.ts CHANGED
@@ -1,26 +1,7 @@
1
1
  // @fun-land/fun-web - Web component library for fun-land
2
2
 
3
3
  export type { Component, ElementChild } from "./types";
4
- export type { FunState } from "./state";
5
4
  export type { MountedComponent } from "./mount";
6
5
  export type { KeyedChildren } from "./dom";
7
-
8
- export { funState } from "./state";
9
- export {
10
- h,
11
- text,
12
- attr,
13
- attrs,
14
- addClass,
15
- removeClass,
16
- toggleClass,
17
- append,
18
- on,
19
- bindProperty,
20
- bindListChildren,
21
- renderWhen,
22
- enhance,
23
- $,
24
- $$,
25
- } from "./dom";
6
+ export * from "./dom";
26
7
  export { mount } from "./mount";