@b9g/crank 0.6.1 → 0.7.1

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/dom.js CHANGED
@@ -1,310 +1,441 @@
1
1
  /// <reference types="dom.d.ts" />
2
2
  import { Renderer, Portal } from './crank.js';
3
+ import { camelToKebabCase, formatStyleValue } from './_css.js';
3
4
 
4
5
  const SVG_NAMESPACE = "http://www.w3.org/2000/svg";
5
- const impl = {
6
- scope(xmlns, tag, props) {
6
+ const MATHML_NAMESPACE = "http://www.w3.org/1998/Math/MathML";
7
+ function isWritableProperty(element, name) {
8
+ // walk up the object's prototype chain to find the owner
9
+ let propOwner = element;
10
+ do {
11
+ if (Object.prototype.hasOwnProperty.call(propOwner, name)) {
12
+ break;
13
+ }
14
+ } while ((propOwner = Object.getPrototypeOf(propOwner)));
15
+ if (propOwner === null) {
16
+ return false;
17
+ }
18
+ // get the descriptor for the named property and check whether it implies
19
+ // that the property is writable
20
+ const descriptor = Object.getOwnPropertyDescriptor(propOwner, name);
21
+ if (descriptor != null &&
22
+ (descriptor.writable === true || descriptor.set !== undefined)) {
23
+ return true;
24
+ }
25
+ return false;
26
+ }
27
+ function emitHydrationWarning(propName, quietProps, expectedValue, actualValue, element, displayName) {
28
+ const checkName = propName;
29
+ const showName = displayName || propName;
30
+ if (!quietProps || !quietProps.has(checkName)) {
31
+ if (expectedValue === null || expectedValue === false) {
32
+ console.warn(`Expected "${showName}" to be missing but found ${String(actualValue)} while hydrating:`, element);
33
+ }
34
+ else if (expectedValue === true || expectedValue === "") {
35
+ console.warn(`Expected "${showName}" to be ${expectedValue === true ? "present" : '""'} but found ${String(actualValue)} while hydrating:`, element);
36
+ }
37
+ else if (typeof window !== "undefined" &&
38
+ window.location &&
39
+ new URL(expectedValue, window.location.origin).href ===
40
+ new URL(actualValue, window.location.origin).href) ;
41
+ else {
42
+ console.warn(`Expected "${showName}" to be "${String(expectedValue)}" but found ${String(actualValue)} while hydrating:`, element);
43
+ }
44
+ }
45
+ }
46
+ const adapter = {
47
+ scope({ scope: xmlns, tag, props, }) {
7
48
  switch (tag) {
8
49
  case Portal:
50
+ // TODO: read the namespace from the portal root element
9
51
  xmlns = undefined;
10
52
  break;
11
53
  case "svg":
12
54
  xmlns = SVG_NAMESPACE;
13
55
  break;
56
+ case "math":
57
+ xmlns = MATHML_NAMESPACE;
58
+ break;
14
59
  }
15
60
  return props.xmlns || xmlns;
16
61
  },
17
- create(tag, _props, xmlns) {
62
+ create({ tag, tagName, scope: xmlns, }) {
18
63
  if (typeof tag !== "string") {
19
- throw new Error(`Unknown tag: ${tag.toString()}`);
64
+ throw new Error(`Unknown tag: ${tagName}`);
20
65
  }
21
66
  else if (tag.toLowerCase() === "svg") {
22
67
  xmlns = SVG_NAMESPACE;
23
68
  }
69
+ else if (tag.toLowerCase() === "math") {
70
+ xmlns = MATHML_NAMESPACE;
71
+ }
24
72
  return xmlns
25
73
  ? document.createElementNS(xmlns, tag)
26
74
  : document.createElement(tag);
27
75
  },
28
- hydrate(tag, node, props) {
76
+ adopt({ tag, tagName, node, }) {
29
77
  if (typeof tag !== "string" && tag !== Portal) {
30
- throw new Error(`Unknown tag: ${tag.toString()}`);
78
+ throw new Error(`Unknown tag: ${tagName}`);
31
79
  }
32
- if (typeof tag === "string" &&
33
- tag.toUpperCase() !== node.tagName) {
34
- // TODO: consider pros and cons of hydration warnings
35
- //console.error(`Expected <${tag}> while hydrating but found:`, node);
36
- return undefined;
80
+ if (node === document.body ||
81
+ node === document.head ||
82
+ node === document.documentElement ||
83
+ node === document) {
84
+ console.warn(`Hydrating ${node.nodeName.toLowerCase()} is discouraged as it is destructive and may remove unknown nodes.`);
37
85
  }
38
- const children = [];
39
- for (let i = 0; i < node.childNodes.length; i++) {
40
- const child = node.childNodes[i];
41
- if (child.nodeType === Node.TEXT_NODE) {
42
- children.push(child.data);
43
- }
44
- else if (child.nodeType === Node.ELEMENT_NODE) {
45
- children.push(child);
46
- }
86
+ if (node == null ||
87
+ (typeof tag === "string" &&
88
+ (node.nodeType !== Node.ELEMENT_NODE ||
89
+ tag.toLowerCase() !== node.tagName.toLowerCase()))) {
90
+ console.warn(`Expected <${tagName}> while hydrating but found: `, node);
91
+ return;
47
92
  }
48
- // TODO: extract props from nodes
49
- return { props, children };
93
+ return Array.from(node.childNodes);
50
94
  },
51
- patch(_tag,
52
- // TODO: Why does this assignment work?
53
- node, name,
54
- // TODO: Stricter typings?
55
- value, oldValue, xmlns) {
95
+ patch({ tagName, node, props, oldProps, scope: xmlns, copyProps, quietProps, isHydrating, }) {
96
+ if (node.nodeType !== Node.ELEMENT_NODE) {
97
+ throw new TypeError(`Cannot patch node: ${String(node)}`);
98
+ }
99
+ else if (props.class && props.className) {
100
+ console.error(`Both "class" and "className" set in props for <${tagName}>. Use one or the other.`);
101
+ }
102
+ const element = node;
56
103
  const isSVG = xmlns === SVG_NAMESPACE;
57
- const colonIndex = name.indexOf(":");
58
- if (colonIndex !== -1) {
59
- const ns = name.slice(0, colonIndex);
60
- const name1 = name.slice(colonIndex + 1);
61
- switch (ns) {
62
- case "prop":
63
- node[name1] = value;
64
- return;
65
- case "attr":
104
+ const isMathML = xmlns === MATHML_NAMESPACE;
105
+ for (let name in { ...oldProps, ...props }) {
106
+ let value = props[name];
107
+ const oldValue = oldProps ? oldProps[name] : undefined;
108
+ {
109
+ if (copyProps != null && copyProps.has(name)) {
110
+ continue;
111
+ }
112
+ // handle prop:name or attr:name properties
113
+ const colonIndex = name.indexOf(":");
114
+ if (colonIndex !== -1) {
115
+ const [ns, name1] = [
116
+ name.slice(0, colonIndex),
117
+ name.slice(colonIndex + 1),
118
+ ];
119
+ switch (ns) {
120
+ case "prop":
121
+ node[name1] = value;
122
+ continue;
123
+ case "attr":
124
+ if (value == null || value === false) {
125
+ if (isHydrating && element.hasAttribute(name1)) {
126
+ emitHydrationWarning(name, quietProps, value, element.getAttribute(name1), element);
127
+ }
128
+ element.removeAttribute(name1);
129
+ }
130
+ else if (value === true) {
131
+ if (isHydrating && !element.hasAttribute(name1)) {
132
+ emitHydrationWarning(name, quietProps, value, null, element);
133
+ }
134
+ element.setAttribute(name1, "");
135
+ }
136
+ else if (typeof value !== "string") {
137
+ value = String(value);
138
+ }
139
+ if (isHydrating && element.getAttribute(name1) !== value) {
140
+ emitHydrationWarning(name, quietProps, value, element.getAttribute(name1), element);
141
+ }
142
+ element.setAttribute(name1, String(value));
143
+ continue;
144
+ }
145
+ }
146
+ }
147
+ switch (name) {
148
+ // TODO: fix hydration warnings for the style prop
149
+ case "style": {
150
+ const style = element.style;
66
151
  if (value == null || value === false) {
67
- node.removeAttribute(name1);
152
+ if (isHydrating && style.cssText !== "") {
153
+ emitHydrationWarning(name, quietProps, value, style.cssText, element);
154
+ }
155
+ element.removeAttribute("style");
68
156
  }
69
157
  else if (value === true) {
70
- node.setAttribute(name1, "");
158
+ if (isHydrating && style.cssText !== "") {
159
+ emitHydrationWarning(name, quietProps, "", style.cssText, element);
160
+ }
161
+ element.setAttribute("style", "");
71
162
  }
72
163
  else if (typeof value === "string") {
73
- node.setAttribute(name1, value);
164
+ if (style.cssText !== value) {
165
+ // TODO: Fix hydration warnings for styles
166
+ //if (isHydrating) {
167
+ // emitHydrationWarning(
168
+ // name,
169
+ // quietProps,
170
+ // value,
171
+ // style.cssText,
172
+ // element,
173
+ // );
174
+ //}
175
+ style.cssText = value;
176
+ }
74
177
  }
75
178
  else {
76
- node.setAttribute(name, String(value));
179
+ if (typeof oldValue === "string") {
180
+ // if the old value was a string, we need to clear the style
181
+ // TODO: only clear the styles enumerated in the old value
182
+ style.cssText = "";
183
+ }
184
+ for (const styleName in { ...oldValue, ...value }) {
185
+ const cssName = camelToKebabCase(styleName);
186
+ const styleValue = value && value[styleName];
187
+ if (styleValue == null) {
188
+ if (isHydrating && style.getPropertyValue(cssName) !== "") {
189
+ emitHydrationWarning(name, quietProps, null, style.getPropertyValue(cssName), element, `style.${styleName}`);
190
+ }
191
+ style.removeProperty(cssName);
192
+ }
193
+ else {
194
+ const formattedValue = formatStyleValue(cssName, styleValue);
195
+ if (style.getPropertyValue(cssName) !== formattedValue) {
196
+ // TODO: hydration warnings for style props
197
+ //if (isHydrating) {
198
+ // emitHydrationWarning(
199
+ // name,
200
+ // quietProps,
201
+ // formattedValue,
202
+ // style.getPropertyValue(cssName),
203
+ // element,
204
+ // `style.${styleName}`,
205
+ // );
206
+ //}
207
+ style.setProperty(cssName, formattedValue);
208
+ }
209
+ }
210
+ }
77
211
  }
78
- return;
79
- }
80
- }
81
- switch (name) {
82
- case "style": {
83
- const style = node.style;
84
- if (style == null) {
85
- node.setAttribute("style", value);
212
+ break;
86
213
  }
87
- else if (value == null || value === false) {
88
- node.removeAttribute("style");
89
- }
90
- else if (value === true) {
91
- node.setAttribute("style", "");
92
- }
93
- else if (typeof value === "string") {
94
- if (style.cssText !== value) {
95
- style.cssText = value;
214
+ case "class":
215
+ case "className":
216
+ if (value === true) {
217
+ if (isHydrating && element.getAttribute("class") !== "") {
218
+ emitHydrationWarning(name, quietProps, "", element.getAttribute("class"), element);
219
+ }
220
+ element.setAttribute("class", "");
96
221
  }
97
- }
98
- else {
99
- if (typeof oldValue === "string") {
100
- style.cssText = "";
222
+ else if (value == null) {
223
+ if (isHydrating && element.hasAttribute("class")) {
224
+ emitHydrationWarning(name, quietProps, value, element.getAttribute("class"), element);
225
+ }
226
+ element.removeAttribute("class");
101
227
  }
102
- for (const styleName in { ...oldValue, ...value }) {
103
- const styleValue = value && value[styleName];
104
- if (styleValue == null) {
105
- style.removeProperty(styleName);
228
+ else if (typeof value === "object") {
229
+ // class={{"included-class": true, "excluded-class": false}} syntax
230
+ if (typeof oldValue === "string") {
231
+ // if the old value was a string, we need to clear all classes
232
+ element.setAttribute("class", "");
233
+ }
234
+ let shouldIssueWarning = false;
235
+ const hydratingClasses = isHydrating
236
+ ? new Set(Array.from(element.classList))
237
+ : undefined;
238
+ const hydratingClassName = isHydrating
239
+ ? element.getAttribute("class")
240
+ : undefined;
241
+ for (const className in { ...oldValue, ...value }) {
242
+ const classValue = value && value[className];
243
+ if (classValue) {
244
+ element.classList.add(className);
245
+ if (hydratingClasses && hydratingClasses.has(className)) {
246
+ hydratingClasses.delete(className);
247
+ }
248
+ else if (isHydrating) {
249
+ shouldIssueWarning = true;
250
+ }
251
+ }
252
+ else {
253
+ element.classList.remove(className);
254
+ }
106
255
  }
107
- else if (style.getPropertyValue(styleName) !== styleValue) {
108
- style.setProperty(styleName, styleValue);
256
+ if (shouldIssueWarning ||
257
+ (hydratingClasses && hydratingClasses.size > 0)) {
258
+ emitHydrationWarning(name, quietProps, Object.keys(value)
259
+ .filter((k) => value[k])
260
+ .join(" "), hydratingClassName || "", element);
109
261
  }
110
262
  }
111
- }
112
- break;
113
- }
114
- case "class":
115
- case "className":
116
- if (value === true) {
117
- node.setAttribute("class", "");
118
- }
119
- else if (value == null) {
120
- node.removeAttribute("class");
121
- }
122
- else if (!isSVG) {
123
- if (node.className !== value) {
124
- node["className"] = value;
263
+ else if (!isSVG && !isMathML) {
264
+ if (element.className !== value) {
265
+ if (isHydrating) {
266
+ emitHydrationWarning(name, quietProps, value, element.className, element);
267
+ }
268
+ element.className = value;
269
+ }
125
270
  }
126
- }
127
- else if (node.getAttribute("class") !== value) {
128
- node.setAttribute("class", value);
129
- }
130
- break;
131
- case "innerHTML":
132
- if (value !== oldValue) {
133
- node.innerHTML = value;
134
- }
135
- break;
136
- default: {
137
- if (name[0] === "o" &&
138
- name[1] === "n" &&
139
- name[2] === name[2].toUpperCase() &&
140
- typeof value === "function") {
141
- // Support React-style event names (onClick, onChange, etc.)
142
- name = name.toLowerCase();
143
- }
144
- if (name in node &&
145
- // boolean properties will coerce strings, but sometimes they map to
146
- // enumerated attributes, where truthy strings ("false", "no") map to
147
- // falsy properties, so we use attributes in this case.
148
- !(typeof value === "string" &&
149
- typeof node[name] === "boolean")) {
150
- // walk up the object's prototype chain to find the owner of the
151
- // named property
152
- let obj = node;
153
- do {
154
- if (Object.prototype.hasOwnProperty.call(obj, name)) {
155
- break;
271
+ else if (element.getAttribute("class") !== value) {
272
+ if (isHydrating) {
273
+ emitHydrationWarning(name, quietProps, value, element.getAttribute("class"), element);
156
274
  }
157
- } while ((obj = Object.getPrototypeOf(obj)));
158
- // get the descriptor for the named property and check whether it
159
- // implies that the property is writable
160
- const descriptor = Object.getOwnPropertyDescriptor(obj, name);
161
- if (descriptor != null &&
162
- (descriptor.writable === true || descriptor.set !== undefined)) {
163
- if (node[name] !== value || oldValue === undefined) {
164
- node[name] = value;
275
+ element.setAttribute("class", value);
276
+ }
277
+ break;
278
+ case "innerHTML":
279
+ if (value !== oldValue) {
280
+ if (isHydrating) {
281
+ emitHydrationWarning(name, quietProps, value, element.innerHTML, element);
165
282
  }
166
- return;
283
+ element.innerHTML = value;
167
284
  }
168
- // if the property wasn't writable, fall through to the code below
169
- // which uses setAttribute() instead of assigning directly.
170
- }
171
- if (value === true) {
172
- value = "";
173
- }
174
- else if (value == null || value === false) {
175
- node.removeAttribute(name);
176
- return;
177
- }
178
- if (node.getAttribute(name) !== value) {
179
- node.setAttribute(name, value);
180
- }
181
- }
182
- }
183
- },
184
- arrange(tag, node, props, children, _oldProps, oldChildren) {
185
- if (tag === Portal && (node == null || typeof node.nodeType !== "number")) {
186
- throw new TypeError(`Portal root is not a node. Received: ${JSON.stringify(node && node.toString())}`);
187
- }
188
- if (!("innerHTML" in props) &&
189
- // We don’t want to update elements without explicit children (<div/>),
190
- // because these elements sometimes have child nodes added via raw
191
- // DOM manipulations.
192
- // However, if an element has previously rendered children, we clear the
193
- // them because it would be surprising not to clear Crank managed
194
- // children, even if the new element does not have explicit children.
195
- ("children" in props || (oldChildren && oldChildren.length))) {
196
- if (children.length === 0) {
197
- node.textContent = "";
198
- }
199
- else {
200
- let oldChild = node.firstChild;
201
- let i = 0;
202
- while (oldChild !== null && i < children.length) {
203
- const newChild = children[i];
204
- if (oldChild === newChild) {
205
- oldChild = oldChild.nextSibling;
206
- i++;
285
+ break;
286
+ default: {
287
+ if (name[0] === "o" &&
288
+ name[1] === "n" &&
289
+ name[2] === name[2].toUpperCase() &&
290
+ typeof value === "function") {
291
+ // Support React-style event names (onClick, onChange, etc.)
292
+ name = name.toLowerCase();
207
293
  }
208
- else if (typeof newChild === "string") {
209
- if (oldChild.nodeType === Node.TEXT_NODE) {
210
- if (oldChild.data !== newChild) {
211
- oldChild.data = newChild;
294
+ // try to set the property directly
295
+ if (name in element &&
296
+ // boolean properties will coerce strings, but sometimes they map to
297
+ // enumerated attributes, where truthy strings ("false", "no") map to
298
+ // falsy properties, so we force using setAttribute.
299
+ !(typeof value === "string" &&
300
+ typeof element[name] === "boolean") &&
301
+ isWritableProperty(element, name)) {
302
+ if (element[name] !== value || oldValue === undefined) {
303
+ if (isHydrating &&
304
+ typeof element[name] === "string" &&
305
+ element[name] !== value) {
306
+ emitHydrationWarning(name, quietProps, value, element[name], element);
212
307
  }
213
- oldChild = oldChild.nextSibling;
308
+ // if the property is writable, assign it directly
309
+ element[name] = value;
214
310
  }
215
- else {
216
- node.insertBefore(document.createTextNode(newChild), oldChild);
311
+ continue;
312
+ }
313
+ if (value === true) {
314
+ value = "";
315
+ }
316
+ else if (value == null || value === false) {
317
+ if (isHydrating && element.hasAttribute(name)) {
318
+ emitHydrationWarning(name, quietProps, value, element.getAttribute(name), element);
217
319
  }
218
- i++;
320
+ element.removeAttribute(name);
321
+ continue;
219
322
  }
220
- else if (oldChild.nodeType === Node.TEXT_NODE) {
221
- const nextSibling = oldChild.nextSibling;
222
- node.removeChild(oldChild);
223
- oldChild = nextSibling;
323
+ else if (typeof value !== "string") {
324
+ value = String(value);
224
325
  }
225
- else {
226
- node.insertBefore(newChild, oldChild);
227
- i++;
228
- // TODO: This is an optimization but we need to think a little more about other cases like prepending.
229
- if (oldChild !== children[i]) {
230
- const nextSibling = oldChild.nextSibling;
231
- node.removeChild(oldChild);
232
- oldChild = nextSibling;
326
+ if (element.getAttribute(name) !== value) {
327
+ if (isHydrating) {
328
+ emitHydrationWarning(name, quietProps, value, element.getAttribute(name), element);
233
329
  }
330
+ element.setAttribute(name, value);
234
331
  }
235
332
  }
236
- // remove excess DOM nodes
237
- while (oldChild !== null) {
238
- const nextSibling = oldChild.nextSibling;
239
- node.removeChild(oldChild);
240
- oldChild = nextSibling;
333
+ }
334
+ }
335
+ },
336
+ arrange({ tag, node, props, children, }) {
337
+ if (tag === Portal && (node == null || typeof node.nodeType !== "number")) {
338
+ throw new TypeError(`<Portal> root is not a node. Received: ${String(node)}`);
339
+ }
340
+ if (!("innerHTML" in props)) {
341
+ let oldChild = node.firstChild;
342
+ for (let i = 0; i < children.length; i++) {
343
+ const newChild = children[i];
344
+ if (oldChild === newChild) {
345
+ // the child is already in the right place, so we can skip it
346
+ oldChild = oldChild.nextSibling;
241
347
  }
242
- // append excess children
243
- for (; i < children.length; i++) {
244
- const newChild = children[i];
245
- node.appendChild(typeof newChild === "string"
246
- ? document.createTextNode(newChild)
247
- : newChild);
348
+ else {
349
+ node.insertBefore(newChild, oldChild);
350
+ if (tag !== Portal &&
351
+ oldChild &&
352
+ i + 1 < children.length &&
353
+ oldChild !== children[i + 1]) {
354
+ oldChild = oldChild.nextSibling;
355
+ }
248
356
  }
249
357
  }
250
358
  }
251
359
  },
252
- text(text, _scope, hydrationData) {
253
- if (hydrationData != null) {
254
- let value = hydrationData.children.shift();
255
- if (typeof value !== "string" || !value.startsWith(text)) ;
256
- else if (text.length < value.length) {
257
- value = value.slice(text.length);
258
- hydrationData.children.unshift(value);
360
+ remove({ node, parentNode, isNested, }) {
361
+ if (!isNested && node.parentNode === parentNode) {
362
+ parentNode.removeChild(node);
363
+ }
364
+ },
365
+ text({ value, oldNode, hydrationNodes, }) {
366
+ if (hydrationNodes != null) {
367
+ let node = hydrationNodes.shift();
368
+ if (!node || node.nodeType !== Node.TEXT_NODE) {
369
+ console.warn(`Expected "${value}" while hydrating but found:`, node);
370
+ }
371
+ else {
372
+ // value is a text node, check if it matches the expected text
373
+ const textData = node.data;
374
+ if (textData.length > value.length) {
375
+ if (textData.startsWith(value)) {
376
+ // the text node is longer than the expected text, so we
377
+ // reuse the existing text node, but truncate it and unshift the rest
378
+ node.data = value;
379
+ hydrationNodes.unshift(document.createTextNode(textData.slice(value.length)));
380
+ return node;
381
+ }
382
+ }
383
+ else if (textData === value) {
384
+ return node;
385
+ }
386
+ // We log textData and not node because node will be mutated
387
+ console.warn(`Expected "${value}" while hydrating but found:`, textData);
388
+ oldNode = node;
389
+ }
390
+ }
391
+ if (oldNode != null) {
392
+ if (oldNode.data !== value) {
393
+ oldNode.data = value;
259
394
  }
395
+ return oldNode;
260
396
  }
261
- return text;
397
+ return document.createTextNode(value);
262
398
  },
263
- raw(value, xmlns, hydrationData) {
264
- let result;
399
+ raw({ value, scope: xmlns, hydrationNodes, }) {
400
+ let nodes;
265
401
  if (typeof value === "string") {
266
402
  const el = xmlns == null
267
403
  ? document.createElement("div")
268
- : document.createElementNS(xmlns, "svg");
404
+ : xmlns === SVG_NAMESPACE
405
+ ? document.createElementNS(xmlns, "svg")
406
+ : document.createElementNS(xmlns, "math");
269
407
  el.innerHTML = value;
270
- if (el.childNodes.length === 0) {
271
- result = undefined;
272
- }
273
- else if (el.childNodes.length === 1) {
274
- result = el.childNodes[0];
275
- }
276
- else {
277
- result = Array.from(el.childNodes);
278
- }
408
+ nodes = Array.from(el.childNodes);
279
409
  }
280
410
  else {
281
- result = value;
411
+ nodes = value == null ? [] : Array.isArray(value) ? [...value] : [value];
282
412
  }
283
- if (hydrationData != null) {
284
- // TODO: maybe we should warn on incorrect values
285
- if (Array.isArray(result)) {
286
- for (let i = 0; i < result.length; i++) {
287
- const node = result[i];
288
- if (typeof node !== "string" &&
289
- (node.nodeType === Node.ELEMENT_NODE ||
290
- node.nodeType === Node.TEXT_NODE)) {
291
- hydrationData.children.shift();
292
- }
413
+ if (hydrationNodes != null) {
414
+ for (let i = 0; i < nodes.length; i++) {
415
+ const node = nodes[i];
416
+ // check if node is equal to the next node in the hydration array
417
+ const hydrationNode = hydrationNodes.shift();
418
+ if (hydrationNode &&
419
+ typeof hydrationNode === "object" &&
420
+ typeof hydrationNode.nodeType === "number" &&
421
+ node.isEqualNode(hydrationNode)) {
422
+ nodes[i] = hydrationNode;
293
423
  }
294
- }
295
- else if (result != null && typeof result !== "string") {
296
- if (result.nodeType === Node.ELEMENT_NODE ||
297
- result.nodeType === Node.TEXT_NODE) {
298
- hydrationData.children.shift();
424
+ else {
425
+ console.warn(`Expected <Raw value="${String(value)}"> while hydrating but found:`, hydrationNode);
299
426
  }
300
427
  }
301
428
  }
302
- return result;
429
+ return nodes.length === 0
430
+ ? undefined
431
+ : nodes.length === 1
432
+ ? nodes[0]
433
+ : nodes;
303
434
  },
304
435
  };
305
436
  class DOMRenderer extends Renderer {
306
437
  constructor() {
307
- super(impl);
438
+ super(adapter);
308
439
  }
309
440
  render(children, root, ctx) {
310
441
  validateRoot(root);
@@ -316,12 +447,15 @@ class DOMRenderer extends Renderer {
316
447
  }
317
448
  }
318
449
  function validateRoot(root) {
319
- if (root === null ||
450
+ if (root == null ||
320
451
  (typeof root === "object" && typeof root.nodeType !== "number")) {
321
- throw new TypeError(`Render root is not a node. Received: ${JSON.stringify(root && root.toString())}`);
452
+ throw new TypeError(`Render root is not a node. Received: ${String(root)}`);
453
+ }
454
+ else if (root.nodeType !== Node.ELEMENT_NODE) {
455
+ throw new TypeError(`Render root must be an element node. Received: ${String(root)}`);
322
456
  }
323
457
  }
324
458
  const renderer = new DOMRenderer();
325
459
 
326
- export { DOMRenderer, impl, renderer };
460
+ export { DOMRenderer, adapter, renderer };
327
461
  //# sourceMappingURL=dom.js.map