1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 |
|
9 |
|
10 |
|
11 |
|
12 |
|
13 |
|
14 |
|
15 |
|
16 |
|
17 | import { DEBUG } from "./global";
|
18 | import { NameOrCtorDef } from "./types";
|
19 |
|
20 |
|
21 |
|
22 |
|
23 |
|
24 | let inAttributes = false;
|
25 |
|
26 |
|
27 |
|
28 |
|
29 |
|
30 | let inSkip = false;
|
31 |
|
32 |
|
33 |
|
34 |
|
35 | let inPatch = false;
|
36 |
|
37 |
|
38 |
|
39 |
|
40 |
|
41 |
|
42 |
|
43 | function assert<T extends {}>(val: T | null | undefined): T {
|
44 | if (DEBUG && !val) {
|
45 | throw new Error("Expected value to be defined");
|
46 | }
|
47 |
|
48 | return val!;
|
49 | }
|
50 |
|
51 |
|
52 |
|
53 |
|
54 |
|
55 | function assertInPatch(functionName: string) {
|
56 | if (!inPatch) {
|
57 | throw new Error("Cannot call " + functionName + "() unless in patch.");
|
58 | }
|
59 | }
|
60 |
|
61 |
|
62 |
|
63 |
|
64 |
|
65 |
|
66 | function assertNoUnclosedTags(
|
67 | openElement: Node | null,
|
68 | root: Node | DocumentFragment
|
69 | ) {
|
70 | if (openElement === root) {
|
71 | return;
|
72 | }
|
73 |
|
74 | let currentElement = openElement;
|
75 | const openTags: Array<string> = [];
|
76 | while (currentElement && currentElement !== root) {
|
77 | openTags.push(currentElement.nodeName.toLowerCase());
|
78 | currentElement = currentElement.parentNode;
|
79 | }
|
80 |
|
81 | throw new Error("One or more tags were not closed:\n" + openTags.join("\n"));
|
82 | }
|
83 |
|
84 |
|
85 |
|
86 |
|
87 |
|
88 | function assertPatchOuterHasParentNode(parent: Node | null) {
|
89 | if (!parent) {
|
90 | console.warn(
|
91 | "patchOuter requires the node have a parent if there is a key."
|
92 | );
|
93 | }
|
94 | }
|
95 |
|
96 |
|
97 |
|
98 |
|
99 |
|
100 | function assertNotInAttributes(functionName: string) {
|
101 | if (inAttributes) {
|
102 | throw new Error(
|
103 | functionName +
|
104 | "() can not be called between " +
|
105 | "elementOpenStart() and elementOpenEnd()."
|
106 | );
|
107 | }
|
108 | }
|
109 |
|
110 |
|
111 |
|
112 |
|
113 |
|
114 | function assertNotInSkip(functionName: string) {
|
115 | if (inSkip) {
|
116 | throw new Error(
|
117 | functionName +
|
118 | "() may not be called inside an element " +
|
119 | "that has called skip()."
|
120 | );
|
121 | }
|
122 | }
|
123 |
|
124 |
|
125 |
|
126 |
|
127 |
|
128 | function assertInAttributes(functionName: string) {
|
129 | if (!inAttributes) {
|
130 | throw new Error(
|
131 | functionName +
|
132 | "() can only be called after calling " +
|
133 | "elementOpenStart()."
|
134 | );
|
135 | }
|
136 | }
|
137 |
|
138 |
|
139 |
|
140 |
|
141 | function assertVirtualAttributesClosed() {
|
142 | if (inAttributes) {
|
143 | throw new Error(
|
144 | "elementOpenEnd() must be called after calling " + "elementOpenStart()."
|
145 | );
|
146 | }
|
147 | }
|
148 |
|
149 |
|
150 |
|
151 |
|
152 |
|
153 |
|
154 | function assertCloseMatchesOpenTag(
|
155 | currentNameOrCtor: NameOrCtorDef,
|
156 | nameOrCtor: NameOrCtorDef
|
157 | ) {
|
158 | if (currentNameOrCtor !== nameOrCtor) {
|
159 | throw new Error(
|
160 | 'Received a call to close "' +
|
161 | nameOrCtor +
|
162 | '" but "' +
|
163 | currentNameOrCtor +
|
164 | '" was open.'
|
165 | );
|
166 | }
|
167 | }
|
168 |
|
169 |
|
170 |
|
171 |
|
172 |
|
173 |
|
174 |
|
175 | function assertNoChildrenDeclaredYet(
|
176 | functionName: string,
|
177 | previousNode: Node | null
|
178 | ) {
|
179 | if (previousNode !== null) {
|
180 | throw new Error(
|
181 | functionName +
|
182 | "() must come before any child " +
|
183 | "declarations inside the current element."
|
184 | );
|
185 | }
|
186 | }
|
187 |
|
188 |
|
189 |
|
190 |
|
191 |
|
192 |
|
193 |
|
194 |
|
195 |
|
196 |
|
197 |
|
198 | function assertPatchElementNoExtras(
|
199 | maybeStartNode: Node | null,
|
200 | maybeCurrentNode: Node | null,
|
201 | expectedNextNode: Node | null,
|
202 | expectedPrevNode: Node | null
|
203 | ) {
|
204 | const startNode = assert(maybeStartNode);
|
205 | const currentNode = assert(maybeCurrentNode);
|
206 | const wasUpdated =
|
207 | currentNode.nextSibling === expectedNextNode &&
|
208 | currentNode.previousSibling === expectedPrevNode;
|
209 | const wasChanged =
|
210 | currentNode.nextSibling === startNode.nextSibling &&
|
211 | currentNode.previousSibling === expectedPrevNode;
|
212 | const wasRemoved = currentNode === startNode;
|
213 |
|
214 | if (!wasUpdated && !wasChanged && !wasRemoved) {
|
215 | throw new Error(
|
216 | "There must be exactly one top level call corresponding " +
|
217 | "to the patched element."
|
218 | );
|
219 | }
|
220 | }
|
221 |
|
222 |
|
223 |
|
224 |
|
225 | function updatePatchContext(newContext: {} | null) {
|
226 | inPatch = newContext != null;
|
227 | }
|
228 |
|
229 |
|
230 |
|
231 |
|
232 |
|
233 |
|
234 | function setInAttributes(value: boolean) {
|
235 | const previous = inAttributes;
|
236 | inAttributes = value;
|
237 | return previous;
|
238 | }
|
239 |
|
240 |
|
241 |
|
242 |
|
243 |
|
244 |
|
245 |
|
246 | function setInSkip(value: boolean) {
|
247 | const previous = inSkip;
|
248 | inSkip = value;
|
249 | return previous;
|
250 | }
|
251 |
|
252 | export {
|
253 | assert,
|
254 | assertInPatch,
|
255 | assertNoUnclosedTags,
|
256 | assertNotInAttributes,
|
257 | assertInAttributes,
|
258 | assertCloseMatchesOpenTag,
|
259 | assertVirtualAttributesClosed,
|
260 | assertNoChildrenDeclaredYet,
|
261 | assertNotInSkip,
|
262 | assertPatchElementNoExtras,
|
263 | assertPatchOuterHasParentNode,
|
264 | setInAttributes,
|
265 | setInSkip,
|
266 | updatePatchContext
|
267 | };
|