@3sln/dodo 0.0.3 → 0.0.5

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/src/vdom.js CHANGED
@@ -32,32 +32,35 @@ class VNode {
32
32
  }
33
33
 
34
34
  function defaultShouldUpdate(a, b) {
35
- if (a === b) return false;
36
- if (a && b && typeof a === 'object' && typeof b === 'object') {
37
- if (a.constructor !== b.constructor) return true;
38
-
39
- if (Array.isArray(a)) {
40
- if (a.length !== b.length) return true;
41
- for (let j = 0; j < a.length; j++) {
42
- if (a[j] !== b[j]) return true;
43
- }
44
- return false;
45
- }
35
+ if (a === b) return false;
36
+ if (a && b && typeof a === 'object' && typeof b === 'object') {
37
+ if (a.constructor !== b.constructor) return true;
38
+
39
+ if (Array.isArray(a)) {
40
+ if (a.length !== b.length) return true;
41
+ for (let j = 0; j < a.length; j++) {
42
+ if (a[j] !== b[j]) return true;
43
+ }
44
+ return false;
45
+ }
46
46
 
47
- if (a.constructor === Object) {
48
- const keysA = Object.keys(a);
49
- if (keysA.length !== Object.keys(b).length) return true;
50
- for (const key of keysA) {
51
- if (!b.hasOwnProperty(key) || a[key] !== b[key]) return true;
52
- }
53
- return false;
54
- }
47
+ if (a.constructor === Object) {
48
+ const keysA = Object.keys(a);
49
+ if (keysA.length !== Object.keys(b).length) return true;
50
+ for (const key of keysA) {
51
+ if (!b.hasOwnProperty(key) || a[key] !== b[key]) return true;
52
+ }
53
+ return false;
55
54
  }
56
- return true;
55
+ }
56
+ return true;
57
57
  }
58
58
 
59
59
  function isIterable(x) {
60
- return Array.isArray(x) || (x != null && typeof x[Symbol.iterator] === 'function' && typeof x !== 'string');
60
+ return (
61
+ Array.isArray(x) ||
62
+ (x != null && typeof x[Symbol.iterator] === 'function' && typeof x !== 'string')
63
+ );
61
64
  }
62
65
 
63
66
  function alias(f) {
@@ -71,8 +74,10 @@ function special(o) {
71
74
  function newElementNamespace(parentNode, newNodeTag) {
72
75
  if (parentNode.namespaceURI === 'http://www.w3.org/1999/xhtml') {
73
76
  switch (newNodeTag) {
74
- case 'svg': return 'http://www.w3.org/2000/svg';
75
- case 'math': return 'http://www.w3.org/1998/Math/MathML';
77
+ case 'svg':
78
+ return 'http://www.w3.org/2000/svg';
79
+ case 'math':
80
+ return 'http://www.w3.org/1998/Math/MathML';
76
81
  }
77
82
  }
78
83
  return parentNode.namespaceURI ?? parentNode.host?.namespaceURI ?? 'http://www.w3.org/1999/xhtml';
@@ -80,7 +85,7 @@ function newElementNamespace(parentNode, newNodeTag) {
80
85
 
81
86
  function createElementNode(parentNode, tag, vdom) {
82
87
  const el = parentNode.ownerDocument.createElementNS(newElementNamespace(parentNode, tag), tag);
83
- el[NODE_STATE] = { originalProps: {}, newVdom: vdom };
88
+ el[NODE_STATE] = {originalProps: {}, newVdom: vdom};
84
89
  return el;
85
90
  }
86
91
 
@@ -117,10 +122,10 @@ function removePathFromFocusWithinSet(set, newPath) {
117
122
 
118
123
  function installFocusTrackingForDocument(doc) {
119
124
  const focusWithinSet = new Set();
120
- doc.addEventListener('focusin', (event) => {
125
+ doc.addEventListener('focusin', event => {
121
126
  addPathToFocusWithinSet(focusWithinSet, event.composedPath());
122
127
  });
123
- doc.addEventListener('focusout', (event) => {
128
+ doc.addEventListener('focusout', event => {
124
129
  const newPath = event.relatedTarget ? event.composedPath() : [];
125
130
  removePathFromFocusWithinSet(focusWithinSet, newPath);
126
131
  });
@@ -129,27 +134,50 @@ function installFocusTrackingForDocument(doc) {
129
134
  return focusWithinSet;
130
135
  }
131
136
 
132
- export default (userSettings) => {
137
+ export default userSettings => {
133
138
  const shouldUpdate = userSettings?.shouldUpdate ?? defaultShouldUpdate;
134
- const isMap = userSettings?.isMap ?? ((x) => x?.constructor === Object);
135
- const mapIter = userSettings?.mapIter ?? ((m) => Object.entries(m));
139
+ const isMap = userSettings?.isMap ?? (x => x?.constructor === Object);
140
+ const mapIter = userSettings?.mapIter ?? (m => Object.entries(m));
136
141
  const mapGet = userSettings?.mapGet ?? ((m, k) => m[k]);
137
142
  const mapMerge = userSettings?.mapMerge ?? ((...maps) => Object.assign({}, ...maps));
138
- const newMap = userSettings?.newMap ?? ((obj) => ({...obj}));
139
- const mapPut = userSettings?.mapPut ?? ((m, k, v) => { m[k] = v; return m; });
143
+ const newMap = userSettings?.newMap ?? (obj => ({...obj}));
144
+ const mapPut =
145
+ userSettings?.mapPut ??
146
+ ((m, k, v) => {
147
+ m[k] = v;
148
+ return m;
149
+ });
140
150
  const isSeq = userSettings?.isSeq ?? isIterable;
141
- const seqIter = userSettings?.seqIter ?? ((s) => s);
142
- const convertTagName = userSettings?.convertTagName ?? ((t) => t);
143
- const convertPropName = userSettings?.convertPropName ?? ((p) => p);
144
- const convertStyleName = userSettings?.convertStyleName ?? ((s) => s);
145
- const convertDataName = userSettings?.convertDataName ?? ((d) => d);
146
- const convertClassName = userSettings?.convertClassName ?? ((c) => c);
151
+ const seqIter = userSettings?.seqIter ?? (s => s);
152
+ const convertName = userSettings?.convertName ?? (x => x);
153
+ const convertTagName = userSettings?.convertTagName ?? convertName;
154
+ const convertPropName = userSettings?.convertPropName ?? convertName;
155
+ const convertStyleName = userSettings?.convertStyleName ?? convertName;
156
+ const convertDataName = userSettings?.convertDataName ?? convertName;
157
+ const convertClassName = userSettings?.convertClassName ?? convertName;
158
+ const convertHookName = userSettings?.convertHookName ?? convertName;
147
159
  const listenerKey = userSettings?.listenerKey ?? 'listener';
148
160
  const captureKey = userSettings?.captureKey ?? 'capture';
149
161
  const passiveKey = userSettings?.passiveKey ?? 'passive';
150
162
 
163
+ const EMPTY_MAP = newMap({});
164
+
165
+ function toIterator(iterableOrIterator) {
166
+ if (iterableOrIterator == null) return [][Symbol.iterator]();
167
+ if (typeof iterableOrIterator[Symbol.iterator] === 'function') {
168
+ return iterableOrIterator[Symbol.iterator]();
169
+ }
170
+ if (typeof iterableOrIterator.next === 'function') {
171
+ return iterableOrIterator;
172
+ }
173
+ return [iterableOrIterator][Symbol.iterator]();
174
+ }
175
+
151
176
  function flattenSeqIntoArray(array, items, excludeFalsey) {
152
- for (const item of seqIter(items)) {
177
+ const iterator = toIterator(seqIter(items));
178
+ let result;
179
+ while (!(result = iterator.next()).done) {
180
+ const item = result.value;
153
181
  if (excludeFalsey && (item == null || item == false)) {
154
182
  continue;
155
183
  }
@@ -157,7 +185,7 @@ export default (userSettings) => {
157
185
  if (!isSeq(item)) {
158
186
  array.push(item);
159
187
  } else {
160
- flattenSeqIntoArray(array, item);
188
+ flattenSeqIntoArray(array, item, excludeFalsey);
161
189
  }
162
190
  }
163
191
  }
@@ -176,7 +204,7 @@ export default (userSettings) => {
176
204
  if (!isSeq(item)) {
177
205
  array.push(item);
178
206
  } else {
179
- flattenVNodeChildrenIntoArray(array, seqIter(item));
207
+ flattenSeqIntoArray(array, seqIter(item));
180
208
  }
181
209
  }
182
210
  }
@@ -189,37 +217,52 @@ export default (userSettings) => {
189
217
  function h(tag, props, ...children) {
190
218
  if (!isMap(props)) {
191
219
  children.unshift(props);
192
- props = EMPTY_OBJECT;
220
+ props = EMPTY_MAP;
193
221
  }
194
- return new VNode(ELEMENT_NODE, convertTagName(tag), [props ?? EMPTY_OBJECT, ...children]);
222
+ return new VNode(ELEMENT_NODE, convertTagName(tag), [props ?? EMPTY_MAP, ...children]);
195
223
  }
196
224
 
197
225
  function reconcileElementStyling(target, oldStyling, newStyling) {
198
226
  const style = target.style;
199
- for (const [name, value] of mapIter(newStyling)) {
227
+ const newStylingIterator = toIterator(mapIter(newStyling));
228
+ let result;
229
+ while (!(result = newStylingIterator.next()).done) {
230
+ const [name, value] = result.value;
200
231
  style.setProperty(convertStyleName(name), value);
201
232
  }
202
- for (const [name] of mapIter(oldStyling)) {
233
+ const oldStylingIterator = toIterator(mapIter(oldStyling));
234
+ while (!(result = oldStylingIterator.next()).done) {
235
+ const [name] = result.value;
203
236
  if (mapGet(newStyling, name) !== undefined) continue;
204
237
  style.removeProperty(convertStyleName(name));
205
238
  }
206
239
  }
207
240
 
208
241
  function reconcileElementAttributes(target, oldAttrs, newAttrs) {
209
- for (const [name, value] of mapIter(newAttrs)) {
242
+ const newAttrsIterator = toIterator(mapIter(newAttrs));
243
+ let result;
244
+ while (!(result = newAttrsIterator.next()).done) {
245
+ const [name, value] = result.value;
210
246
  target.setAttribute(convertPropName(name), value);
211
247
  }
212
- for (const [name] of mapIter(oldAttrs)) {
248
+ const oldAttrsIterator = toIterator(mapIter(oldAttrs));
249
+ while (!(result = oldAttrsIterator.next()).done) {
250
+ const [name] = result.value;
213
251
  if (mapGet(newAttrs, name) !== undefined) continue;
214
252
  target.removeAttribute(convertPropName(name));
215
253
  }
216
254
  }
217
255
 
218
256
  function reconcileElementDataset(target, oldDataset, newDataset) {
219
- for (const [name, value] of mapIter(newDataset)) {
257
+ const newDatasetIterator = toIterator(mapIter(newDataset));
258
+ let result;
259
+ while (!(result = newDatasetIterator.next()).done) {
260
+ const [name, value] = result.value;
220
261
  target.dataset[convertDataName(name)] = value;
221
262
  }
222
- for (const [name] of mapIter(oldDataset)) {
263
+ const oldDatasetIterator = toIterator(mapIter(oldDataset));
264
+ while (!(result = oldDatasetIterator.next()).done) {
265
+ const [name] = result.value;
223
266
  if (mapGet(newDataset, name) !== undefined) continue;
224
267
  delete target.dataset[convertDataName(name)];
225
268
  }
@@ -240,136 +283,160 @@ export default (userSettings) => {
240
283
  function reconcileElementProps(target, props) {
241
284
  const nodeState = target[NODE_STATE];
242
285
  const isHtml = target.namespaceURI === 'http://www.w3.org/1999/xhtml';
243
- const oldProps = nodeState.vdom?.args[0] ?? EMPTY_OBJECT;
286
+ const oldProps = nodeState.vdom?.args[0] ?? EMPTY_MAP;
244
287
 
245
288
  // Handle new and changed props
246
- for (const [name, newValue] of mapIter(props)) {
247
- const propName = convertPropName(name);
248
- const oldValue = mapGet(oldProps, name);
249
-
250
- if (Object.is(newValue, oldValue)) continue;
251
-
252
- switch (propName) {
253
- case '$styling': {
254
- if (!isMap(newValue)) throw new Error('invalid value for styling prop');
255
- reconcileElementStyling(target, oldValue ?? EMPTY_OBJECT, newValue ?? EMPTY_OBJECT);
256
- break;
257
- }
258
- case '$classes': {
259
- if (!isSeq(newValue)) throw new Error('invalid value for classes prop');
260
- reconcileElementClasses(target, oldValue ?? [], newValue ?? []);
261
- break;
262
- }
263
- case '$attrs': {
264
- if (!isMap(newValue)) throw new Error('invalid value for attrs prop');
265
- reconcileElementAttributes(target, oldValue ?? EMPTY_OBJECT, newValue ?? EMPTY_OBJECT);
266
- break;
289
+ const propsIterator = toIterator(mapIter(props));
290
+ let result;
291
+ while (!(result = propsIterator.next()).done) {
292
+ const [name, newValue] = result.value;
293
+ const propName = convertPropName(name);
294
+ const oldValue = mapGet(oldProps, name);
295
+
296
+ if (Object.is(newValue, oldValue)) continue;
297
+
298
+ switch (propName) {
299
+ case '$styling': {
300
+ if (!isMap(newValue)) throw new Error('invalid value for styling prop');
301
+ reconcileElementStyling(target, oldValue ?? EMPTY_MAP, newValue ?? EMPTY_MAP);
302
+ break;
303
+ }
304
+ case '$classes': {
305
+ if (!isSeq(newValue)) throw new Error('invalid value for classes prop');
306
+ reconcileElementClasses(target, oldValue ?? [], newValue ?? []);
307
+ break;
308
+ }
309
+ case '$attrs': {
310
+ if (!isMap(newValue)) throw new Error('invalid value for attrs prop');
311
+ reconcileElementAttributes(target, oldValue ?? EMPTY_MAP, newValue ?? EMPTY_MAP);
312
+ break;
313
+ }
314
+ case '$dataset': {
315
+ if (!isMap(newValue)) throw new Error('invalid value for dataset prop');
316
+ reconcileElementDataset(target, oldValue ?? EMPTY_MAP, newValue ?? EMPTY_MAP);
317
+ break;
318
+ }
319
+ default: {
320
+ if (isHtml) {
321
+ const originalProps = nodeState.originalProps;
322
+ if (!(propName in originalProps)) {
323
+ originalProps[propName] = target[propName];
267
324
  }
268
- case '$dataset': {
269
- if (!isMap(newValue)) throw new Error('invalid value for dataset prop');
270
- reconcileElementDataset(target, oldValue ?? EMPTY_OBJECT, newValue ?? EMPTY_OBJECT);
271
- break;
325
+ if (newValue === undefined) {
326
+ target[propName] = originalProps[propName];
327
+ } else {
328
+ target[propName] = newValue;
272
329
  }
273
- default: {
274
- if (isHtml) {
275
- const originalProps = nodeState.originalProps;
276
- if (!(propName in originalProps)) {
277
- originalProps[propName] = target[propName];
278
- }
279
- if (newValue === undefined) {
280
- target[propName] = originalProps[propName];
281
- } else {
282
- target[propName] = newValue;
283
- }
284
- } else {
285
- if (newValue === undefined) {
286
- target.removeAttribute(propName);
287
- } else {
288
- target.setAttribute(propName, newValue);
289
- }
290
- }
291
- break;
330
+ } else {
331
+ if (newValue === undefined) {
332
+ target.removeAttribute(propName);
333
+ } else {
334
+ target.setAttribute(propName, newValue);
292
335
  }
336
+ }
337
+ break;
293
338
  }
339
+ }
294
340
  }
295
341
 
296
342
  // Handle removed props
297
- for (const [name, oldValue] of mapIter(oldProps)) {
298
- if (mapGet(props, name) !== undefined) continue; // it wasn't removed
299
-
300
- const propName = convertPropName(name);
301
- switch (propName) {
302
- case '$styling':
303
- reconcileElementStyling(target, oldValue ?? EMPTY_OBJECT, EMPTY_OBJECT);
304
- break;
305
- case '$classes':
306
- reconcileElementClasses(target, oldValue ?? [], []);
307
- break;
308
- case '$attrs':
309
- reconcileElementAttributes(target, oldValue ?? EMPTY_OBJECT, EMPTY_OBJECT);
310
- break;
311
- case '$dataset':
312
- reconcileElementDataset(target, oldValue ?? EMPTY_OBJECT, EMPTY_OBJECT);
313
- break;
314
- default: {
315
- if (isHtml) {
316
- const originalProps = nodeState.originalProps;
317
- if (propName in originalProps) {
318
- target[propName] = originalProps[propName];
319
- delete originalProps[propName];
320
- }
321
- } else {
322
- target.removeAttribute(propName);
323
- }
324
- break;
343
+ const oldPropsIterator = toIterator(mapIter(oldProps));
344
+ while (!(result = oldPropsIterator.next()).done) {
345
+ const [name, oldValue] = result.value;
346
+ if (mapGet(props, name) !== undefined) continue; // it wasn't removed
347
+
348
+ const propName = convertPropName(name);
349
+ switch (propName) {
350
+ case '$styling':
351
+ reconcileElementStyling(target, oldValue ?? EMPTY_MAP, EMPTY_MAP);
352
+ break;
353
+ case '$classes':
354
+ reconcileElementClasses(target, oldValue ?? [], []);
355
+ break;
356
+ case '$attrs':
357
+ reconcileElementAttributes(target, oldValue ?? EMPTY_MAP, EMPTY_MAP);
358
+ break;
359
+ case '$dataset':
360
+ reconcileElementDataset(target, oldValue ?? EMPTY_MAP, EMPTY_MAP);
361
+ break;
362
+ default: {
363
+ if (isHtml) {
364
+ const originalProps = nodeState.originalProps;
365
+ if (propName in originalProps) {
366
+ target[propName] = originalProps[propName];
367
+ delete originalProps[propName];
325
368
  }
369
+ } else {
370
+ target.removeAttribute(propName);
371
+ }
372
+ break;
326
373
  }
374
+ }
327
375
  }
328
376
  }
329
377
 
330
378
  function reconcileListeners(target, hooks) {
331
379
  const state = target[NODE_STATE];
332
380
  if (!state.vdom) {
333
- for (const [name, listener] of mapIter(hooks)) {
334
- if (name[0] === '$') continue;
381
+ const hooksIterator = toIterator(mapIter(hooks));
382
+ let result;
383
+ while (!(result = hooksIterator.next()).done) {
384
+ const [name, listener] = result.value;
385
+ const hookName = convertHookName(name);
386
+ if (hookName[0] === '$') continue;
335
387
  if (typeof listener === 'function') {
336
- target.addEventListener(name, listener);
388
+ target.addEventListener(hookName, listener);
337
389
  } else if (listener != null) {
338
- target.addEventListener(name, mapGet(listener, listenerKey), {
339
- capture: !!mapGet(listener, captureKey),
340
- passive: !!mapGet(listener, passiveKey)
390
+ target.addEventListener(hookName, mapGet(listener, listenerKey), {
391
+ capture: !!mapGet(listener, captureKey),
392
+ passive: !!mapGet(listener, passiveKey),
341
393
  });
342
394
  }
343
395
  }
344
396
  } else {
345
- const oldHooks = state.vdom.hooks ?? EMPTY_OBJECT;
346
- for (const [name, listener] of mapIter(hooks)) {
347
- if (name[0] === '$') continue;
397
+ const oldHooks = state.vdom.hooks ?? EMPTY_MAP;
398
+ const hooksIterator = toIterator(mapIter(hooks));
399
+ let result;
400
+ while (!(result = hooksIterator.next()).done) {
401
+ const [name, listener] = result.value;
402
+ const hookName = convertHookName(name);
403
+ if (hookName[0] === '$') continue;
348
404
  const oldListener = mapGet(oldHooks, name);
349
405
  if (listener === oldListener) continue;
350
406
 
351
407
  if (typeof oldListener === 'function') {
352
- target.removeEventListener(name, oldListener);
408
+ target.removeEventListener(hookName, oldListener);
353
409
  } else if (oldListener != null) {
354
- target.removeEventListener(name, mapGet(oldListener, listenerKey), !!mapGet(oldListener, captureKey));
410
+ target.removeEventListener(
411
+ hookName,
412
+ mapGet(oldListener, listenerKey),
413
+ !!mapGet(oldListener, captureKey),
414
+ );
355
415
  }
356
-
416
+
357
417
  if (typeof listener === 'function') {
358
- target.addEventListener(name, listener);
418
+ target.addEventListener(hookName, listener);
359
419
  } else if (listener != null) {
360
- target.addEventListener(name, mapGet(listener, listenerKey), {
361
- capture: !!mapGet(listener, captureKey),
362
- passive: !!mapGet(listener, passiveKey)
420
+ target.addEventListener(hookName, mapGet(listener, listenerKey), {
421
+ capture: !!mapGet(listener, captureKey),
422
+ passive: !!mapGet(listener, passiveKey),
363
423
  });
364
424
  }
365
425
  }
366
- for (const [name] of mapIter(oldHooks)) {
367
- if (name[0] === '$' || mapGet(hooks, name) !== undefined) continue;
426
+ const oldHooksIterator = toIterator(mapIter(oldHooks));
427
+ while (!(result = oldHooksIterator.next()).done) {
428
+ const [name] = result.value;
429
+ const hookName = convertHookName(name);
430
+ if (hookName[0] === '$' || mapGet(hooks, name) !== undefined) continue;
368
431
  const oldListener = mapGet(oldHooks, name);
369
432
  if (typeof oldListener === 'function') {
370
- target.removeEventListener(name, oldListener);
433
+ target.removeEventListener(hookName, oldListener);
371
434
  } else if (oldListener != null) {
372
- target.removeEventListener(name, mapGet(oldListener, listenerKey), !!mapGet(oldListener, captureKey));
435
+ target.removeEventListener(
436
+ hookName,
437
+ mapGet(oldListener, listenerKey),
438
+ !!mapGet(oldListener, captureKey),
439
+ );
373
440
  }
374
441
  }
375
442
  }
@@ -384,7 +451,7 @@ export default (userSettings) => {
384
451
  switch (newVdom.type) {
385
452
  case ELEMENT_NODE: {
386
453
  reconcileElementProps(target, args[0]);
387
- reconcileElementChildren(target, args.slice(1));
454
+ reconcileElementChildren(target, flattenVNodeChildren(args.slice(1)));
388
455
  break;
389
456
  }
390
457
  case OPAQUE_NODE: {
@@ -395,9 +462,9 @@ export default (userSettings) => {
395
462
  const innerVdom = newVdom.tag.apply(undefined, newVdom.args);
396
463
  if (innerVdom === undefined || innerVdom === null) break;
397
464
  if (isSeq(innerVdom)) {
398
- reconcileElementChildren(target, innerVdom);
465
+ reconcileElementChildren(target, flattenSeq(innerVdom));
399
466
  } else {
400
- reconcileElementChildren(target, [innerVdom]);
467
+ reconcileElementChildren(target, flattenVNodeChildren([innerVdom]));
401
468
  }
402
469
  break;
403
470
  }
@@ -412,7 +479,7 @@ export default (userSettings) => {
412
479
  }
413
480
 
414
481
  if (newVdom.hooks || oldVdom?.hooks) {
415
- reconcileListeners(target, newVdom.hooks ?? EMPTY_OBJECT);
482
+ reconcileListeners(target, newVdom.hooks ?? EMPTY_MAP);
416
483
  }
417
484
  try {
418
485
  newVdom.hooks?.$update?.(target, newVdom, oldVdom);
@@ -459,7 +526,6 @@ export default (userSettings) => {
459
526
  function cleanupTarget(target) {
460
527
  const state = target[NODE_STATE];
461
528
  if (state) {
462
-
463
529
  switch (state.vdom.type) {
464
530
  case ELEMENT_NODE:
465
531
  reconcileElementProps(target, {});
@@ -491,8 +557,7 @@ export default (userSettings) => {
491
557
  }
492
558
  }
493
559
 
494
- function reconcileElementChildren(target, childrenIterable) {
495
- const newChildren = flattenVNodeChildren(childrenIterable);
560
+ function reconcileElementChildren(target, newChildren) {
496
561
  const oldNodesToRemove = new Set(target.childNodes);
497
562
  const oldVNodeNodesPool = new Map();
498
563
  const oldTextNodesPool = [];
@@ -502,14 +567,21 @@ export default (userSettings) => {
502
567
  oldTextNodesPool.push(oldChild);
503
568
  continue;
504
569
  }
505
- const vdom = oldChild[NODE_STATE]?.vdom;
570
+
571
+ const oldChildState = oldChild[NODE_STATE];
572
+ const vdom = oldChildState?.vdom;
506
573
  if (vdom === undefined) {
574
+ if (oldChildState?.newVdom) {
575
+ throw new Error(
576
+ 'Attempt to reconcile against a target while already working on a reconciliation against that same target, this is not allowed',
577
+ );
578
+ }
507
579
  continue;
508
580
  }
509
581
 
510
582
  let oldNodesPoolForTag = oldVNodeNodesPool.get(vdom.tag);
511
583
  if (!oldNodesPoolForTag) {
512
- oldNodesPoolForTag = { nodesForKey: new Map(), nodesWithoutKey: [] };
584
+ oldNodesPoolForTag = {nodesForKey: new Map(), nodesWithoutKey: []};
513
585
  oldVNodeNodesPool.set(vdom.tag, oldNodesPoolForTag);
514
586
  }
515
587
 
@@ -540,6 +612,9 @@ export default (userSettings) => {
540
612
  newDomNode = pool.shift();
541
613
  const state = newDomNode[NODE_STATE];
542
614
  if (shouldUpdate(state.vdom.args, newVdom.args)) {
615
+ if (state.vdom.hooks || newVdom.hooks) {
616
+ reconcileListeners(target, newVdom.hooks);
617
+ }
543
618
  state.newVdom = newVdom;
544
619
  }
545
620
  } else {
@@ -551,6 +626,9 @@ export default (userSettings) => {
551
626
  newDomNode = unkeyedOldNode;
552
627
  const state = newDomNode[NODE_STATE];
553
628
  if (shouldUpdate(state.vdom.args, newVdom.args)) {
629
+ if (state.vdom.hooks || newVdom.hooks) {
630
+ reconcileListeners(target, newVdom.hooks);
631
+ }
554
632
  state.newVdom = newVdom;
555
633
  }
556
634
  } else {
@@ -583,7 +661,11 @@ export default (userSettings) => {
583
661
  const newChild = newDomChildren[i];
584
662
  const existingChildAtPosition = target.childNodes[i];
585
663
  if (newChild !== existingChildAtPosition) {
586
- (newChild.isConnected ? moveBefore : insertBefore).call(target, newChild, existingChildAtPosition);
664
+ (newChild.isConnected ? moveBefore : insertBefore).call(
665
+ target,
666
+ newChild,
667
+ existingChildAtPosition,
668
+ );
587
669
  }
588
670
  const state = newChild[NODE_STATE];
589
671
  if (state?.newVdom) {
@@ -668,14 +750,17 @@ export default (userSettings) => {
668
750
  }
669
751
 
670
752
  if (isSeq(vdom)) {
671
- reconcileElementChildren(target, seqIter(vdom));
753
+ reconcileElementChildren(target, flattenSeq(vdom));
672
754
  return;
673
755
  }
674
756
 
675
757
  if (vdom instanceof VNode) {
676
758
  if (state) {
677
759
  if (state.vdom.type === vdom.type) {
678
- if (shouldUpdate(state.vdom.args, vdom.args)) {
760
+ if (
761
+ shouldUpdate(state.vdom.args, vdom.args) ||
762
+ shouldUpdate(state.vdom.hooks, vdom.hooks)
763
+ ) {
679
764
  state.newVdom = vdom;
680
765
  reconcileNode(target);
681
766
  }
@@ -687,13 +772,18 @@ export default (userSettings) => {
687
772
  switch (vdom.type) {
688
773
  case ELEMENT_NODE:
689
774
  case OPAQUE_NODE:
690
- if (0 !== target.nodeName.localeCompare(convertTagName(vdom.tag), undefined, { sensitivity: 'base' })) {
775
+ if (
776
+ 0 !==
777
+ target.nodeName.localeCompare(convertTagName(vdom.tag), undefined, {
778
+ sensitivity: 'base',
779
+ })
780
+ ) {
691
781
  throw new Error('incompatible target for vdom');
692
782
  }
693
783
  break;
694
784
  }
695
785
 
696
- target[NODE_STATE] = { originalProps: {}, newVdom: vdom };
786
+ target[NODE_STATE] = {originalProps: {}, newVdom: vdom};
697
787
  try {
698
788
  vdom.hooks?.$attach?.(target);
699
789
  if (vdom.type === SPECIAL_NODE) {
@@ -709,9 +799,32 @@ export default (userSettings) => {
709
799
  throw new Error('invalid vdom');
710
800
  }
711
801
 
712
- return { h, alias, special, reconcile, settings: {
713
- shouldUpdate, isMap, mapIter, mapGet, mapMerge, newMap, mapPut, isSeq, flattenSeq, seqIter,
714
- convertTagName, convertPropName, convertStyleName, convertDataName, convertClassName,
715
- listenerKey, captureKey, passiveKey
716
- } };
717
- }
802
+ return {
803
+ h,
804
+ alias,
805
+ special,
806
+ reconcile,
807
+ settings: {
808
+ shouldUpdate,
809
+ isMap,
810
+ mapIter,
811
+ mapGet,
812
+ mapMerge,
813
+ newMap,
814
+ mapPut,
815
+ isSeq,
816
+ flattenSeq,
817
+ seqIter,
818
+ convertTagName,
819
+ convertPropName,
820
+ convertStyleName,
821
+ convertDataName,
822
+ convertClassName,
823
+ convertHookName,
824
+ convertName,
825
+ listenerKey,
826
+ captureKey,
827
+ passiveKey,
828
+ },
829
+ };
830
+ };