-
Notifications
You must be signed in to change notification settings - Fork 0
/
mount.ts
127 lines (108 loc) · 3.27 KB
/
mount.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
/** !
* Copyright (c) 2020, Matt Dunn
*
* @author Matt Dunn (https://matt-dunn.github.io/)
* @licence MIT
*/
import { HTMLElementMap, Node } from "../types";
import { looseRef } from "../utils";
import { MxFactory } from "../render";
export const getDOMNodes = Symbol("getDOMNodes");
type ElementWrapper = {
[getDOMNodes]: () => HTMLElement[];
find: (selector: string) => ElementWrapper;
simulate: <A = any>(eventName: string, eventArgs?: A) => ElementWrapper;
at: (index: number) => ElementWrapper;
hasClass: (className: string) => boolean;
forEach: (
cb: (element: HTMLElement, index: number) => void
) => ElementWrapper;
map: <T>(cb: (element: HTMLElement, index: number) => T) => T[];
length: number;
html: () => string;
update: () => void;
};
type Mount = (node: Node) => ElementWrapper;
const syntheticEvent = <A = any>(eventArgs?: A) => ({
...eventArgs,
preventDefault: () => undefined,
stopPropagation: () => undefined,
});
type ElementWrapperOptions = {
render: () => void;
};
const elementWrapper = (
parentElement: HTMLElementMap,
element: HTMLElementMap | HTMLElementMap[],
options: ElementWrapperOptions
): ElementWrapper => {
const elements = looseRef(Array.isArray(element) ? element : [element] || []);
return {
[getDOMNodes]: () => elements.current,
find(selector) {
return elementWrapper(
parentElement,
elements.current.reduce(
(elements, element) => [
...elements,
...Array.from<HTMLElement>(element.querySelectorAll(selector)),
],
[] as HTMLElement[]
),
options
);
},
simulate(eventName, eventArgs?) {
elements.current.forEach((element) => {
const handler = element?.[`on${eventName}`];
handler && handler(syntheticEvent(eventArgs));
});
return this;
},
at(index) {
if ((Array.isArray(element) && !element[index]) || index !== 0) {
throw new TypeError(`No element found at [${index}]`);
}
if (Array.isArray(element)) {
return elementWrapper(parentElement, element[index], options);
}
return this;
},
hasClass(className) {
if (elements.current.length !== 1) {
throw new TypeError(
`hasClass can only be executed on a single element. Found ${
elements.current.length
} elements: ${elements.current.map((el) => el.tagName)}.`
);
}
return elements.current[0].classList.contains(className);
},
forEach(cb) {
elements.current.forEach(cb);
return this;
},
map: (cb) => elements.current.map(cb),
length: elements.current.length,
html() {
return elements.current.reduce(
(html, element) => html + element.outerHTML.trim(),
""
);
},
update() {
options.render();
elements.current = Array.isArray(parentElement.firstElementChild)
? parentElement.firstElementChild
: [parentElement.firstElementChild as HTMLElement] || [];
return this;
},
};
};
export const mount: Mount = (node) => {
const el = document.createElement("div");
const mx = MxFactory();
const render = mx.render(node)(el);
render();
return elementWrapper(el, el.firstElementChild as HTMLElement, { render });
};