@3sln/dodo 0.0.4 → 0.0.6

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.
Files changed (6) hide show
  1. package/README.md +16 -106
  2. package/index.js +128 -7
  3. package/package.json +1 -1
  4. package/src/html.js +119 -117
  5. package/src/vdom.js +113 -56
  6. package/dodo.js +0 -716
package/src/vdom.js CHANGED
@@ -149,17 +149,35 @@ export default userSettings => {
149
149
  });
150
150
  const isSeq = userSettings?.isSeq ?? isIterable;
151
151
  const seqIter = userSettings?.seqIter ?? (s => s);
152
- const convertTagName = userSettings?.convertTagName ?? (t => t);
153
- const convertPropName = userSettings?.convertPropName ?? (p => p);
154
- const convertStyleName = userSettings?.convertStyleName ?? (s => s);
155
- const convertDataName = userSettings?.convertDataName ?? (d => d);
156
- const convertClassName = userSettings?.convertClassName ?? (c => c);
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;
157
159
  const listenerKey = userSettings?.listenerKey ?? 'listener';
158
160
  const captureKey = userSettings?.captureKey ?? 'capture';
159
161
  const passiveKey = userSettings?.passiveKey ?? 'passive';
160
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
+
161
176
  function flattenSeqIntoArray(array, items, excludeFalsey) {
162
- 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;
163
181
  if (excludeFalsey && (item == null || item == false)) {
164
182
  continue;
165
183
  }
@@ -167,7 +185,7 @@ export default userSettings => {
167
185
  if (!isSeq(item)) {
168
186
  array.push(item);
169
187
  } else {
170
- flattenSeqIntoArray(array, item);
188
+ flattenSeqIntoArray(array, item, excludeFalsey);
171
189
  }
172
190
  }
173
191
  }
@@ -186,7 +204,7 @@ export default userSettings => {
186
204
  if (!isSeq(item)) {
187
205
  array.push(item);
188
206
  } else {
189
- flattenVNodeChildrenIntoArray(array, seqIter(item));
207
+ flattenSeqIntoArray(array, seqIter(item));
190
208
  }
191
209
  }
192
210
  }
@@ -199,37 +217,52 @@ export default userSettings => {
199
217
  function h(tag, props, ...children) {
200
218
  if (!isMap(props)) {
201
219
  children.unshift(props);
202
- props = EMPTY_OBJECT;
220
+ props = EMPTY_MAP;
203
221
  }
204
- return new VNode(ELEMENT_NODE, convertTagName(tag), [props ?? EMPTY_OBJECT, ...children]);
222
+ return new VNode(ELEMENT_NODE, convertTagName(tag), [props ?? EMPTY_MAP, ...children]);
205
223
  }
206
224
 
207
225
  function reconcileElementStyling(target, oldStyling, newStyling) {
208
226
  const style = target.style;
209
- 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;
210
231
  style.setProperty(convertStyleName(name), value);
211
232
  }
212
- for (const [name] of mapIter(oldStyling)) {
233
+ const oldStylingIterator = toIterator(mapIter(oldStyling));
234
+ while (!(result = oldStylingIterator.next()).done) {
235
+ const [name] = result.value;
213
236
  if (mapGet(newStyling, name) !== undefined) continue;
214
237
  style.removeProperty(convertStyleName(name));
215
238
  }
216
239
  }
217
240
 
218
241
  function reconcileElementAttributes(target, oldAttrs, newAttrs) {
219
- 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;
220
246
  target.setAttribute(convertPropName(name), value);
221
247
  }
222
- for (const [name] of mapIter(oldAttrs)) {
248
+ const oldAttrsIterator = toIterator(mapIter(oldAttrs));
249
+ while (!(result = oldAttrsIterator.next()).done) {
250
+ const [name] = result.value;
223
251
  if (mapGet(newAttrs, name) !== undefined) continue;
224
252
  target.removeAttribute(convertPropName(name));
225
253
  }
226
254
  }
227
255
 
228
256
  function reconcileElementDataset(target, oldDataset, newDataset) {
229
- 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;
230
261
  target.dataset[convertDataName(name)] = value;
231
262
  }
232
- for (const [name] of mapIter(oldDataset)) {
263
+ const oldDatasetIterator = toIterator(mapIter(oldDataset));
264
+ while (!(result = oldDatasetIterator.next()).done) {
265
+ const [name] = result.value;
233
266
  if (mapGet(newDataset, name) !== undefined) continue;
234
267
  delete target.dataset[convertDataName(name)];
235
268
  }
@@ -250,10 +283,13 @@ export default userSettings => {
250
283
  function reconcileElementProps(target, props) {
251
284
  const nodeState = target[NODE_STATE];
252
285
  const isHtml = target.namespaceURI === 'http://www.w3.org/1999/xhtml';
253
- const oldProps = nodeState.vdom?.args[0] ?? EMPTY_OBJECT;
286
+ const oldProps = nodeState.vdom?.args[0] ?? EMPTY_MAP;
254
287
 
255
288
  // Handle new and changed props
256
- for (const [name, newValue] of mapIter(props)) {
289
+ const propsIterator = toIterator(mapIter(props));
290
+ let result;
291
+ while (!(result = propsIterator.next()).done) {
292
+ const [name, newValue] = result.value;
257
293
  const propName = convertPropName(name);
258
294
  const oldValue = mapGet(oldProps, name);
259
295
 
@@ -262,7 +298,7 @@ export default userSettings => {
262
298
  switch (propName) {
263
299
  case '$styling': {
264
300
  if (!isMap(newValue)) throw new Error('invalid value for styling prop');
265
- reconcileElementStyling(target, oldValue ?? EMPTY_OBJECT, newValue ?? EMPTY_OBJECT);
301
+ reconcileElementStyling(target, oldValue ?? EMPTY_MAP, newValue ?? EMPTY_MAP);
266
302
  break;
267
303
  }
268
304
  case '$classes': {
@@ -272,12 +308,12 @@ export default userSettings => {
272
308
  }
273
309
  case '$attrs': {
274
310
  if (!isMap(newValue)) throw new Error('invalid value for attrs prop');
275
- reconcileElementAttributes(target, oldValue ?? EMPTY_OBJECT, newValue ?? EMPTY_OBJECT);
311
+ reconcileElementAttributes(target, oldValue ?? EMPTY_MAP, newValue ?? EMPTY_MAP);
276
312
  break;
277
313
  }
278
314
  case '$dataset': {
279
315
  if (!isMap(newValue)) throw new Error('invalid value for dataset prop');
280
- reconcileElementDataset(target, oldValue ?? EMPTY_OBJECT, newValue ?? EMPTY_OBJECT);
316
+ reconcileElementDataset(target, oldValue ?? EMPTY_MAP, newValue ?? EMPTY_MAP);
281
317
  break;
282
318
  }
283
319
  default: {
@@ -304,22 +340,24 @@ export default userSettings => {
304
340
  }
305
341
 
306
342
  // Handle removed props
307
- for (const [name, oldValue] of mapIter(oldProps)) {
343
+ const oldPropsIterator = toIterator(mapIter(oldProps));
344
+ while (!(result = oldPropsIterator.next()).done) {
345
+ const [name, oldValue] = result.value;
308
346
  if (mapGet(props, name) !== undefined) continue; // it wasn't removed
309
347
 
310
348
  const propName = convertPropName(name);
311
349
  switch (propName) {
312
350
  case '$styling':
313
- reconcileElementStyling(target, oldValue ?? EMPTY_OBJECT, EMPTY_OBJECT);
351
+ reconcileElementStyling(target, oldValue ?? EMPTY_MAP, EMPTY_MAP);
314
352
  break;
315
353
  case '$classes':
316
354
  reconcileElementClasses(target, oldValue ?? [], []);
317
355
  break;
318
356
  case '$attrs':
319
- reconcileElementAttributes(target, oldValue ?? EMPTY_OBJECT, EMPTY_OBJECT);
357
+ reconcileElementAttributes(target, oldValue ?? EMPTY_MAP, EMPTY_MAP);
320
358
  break;
321
359
  case '$dataset':
322
- reconcileElementDataset(target, oldValue ?? EMPTY_OBJECT, EMPTY_OBJECT);
360
+ reconcileElementDataset(target, oldValue ?? EMPTY_MAP, EMPTY_MAP);
323
361
  break;
324
362
  default: {
325
363
  if (isHtml) {
@@ -340,51 +378,62 @@ export default userSettings => {
340
378
  function reconcileListeners(target, hooks) {
341
379
  const state = target[NODE_STATE];
342
380
  if (!state.vdom) {
343
- for (const [name, listener] of mapIter(hooks)) {
344
- 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;
345
387
  if (typeof listener === 'function') {
346
- target.addEventListener(name, listener);
388
+ target.addEventListener(hookName, listener);
347
389
  } else if (listener != null) {
348
- target.addEventListener(name, mapGet(listener, listenerKey), {
390
+ target.addEventListener(hookName, mapGet(listener, listenerKey), {
349
391
  capture: !!mapGet(listener, captureKey),
350
392
  passive: !!mapGet(listener, passiveKey),
351
393
  });
352
394
  }
353
395
  }
354
396
  } else {
355
- const oldHooks = state.vdom.hooks ?? EMPTY_OBJECT;
356
- for (const [name, listener] of mapIter(hooks)) {
357
- 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;
358
404
  const oldListener = mapGet(oldHooks, name);
359
405
  if (listener === oldListener) continue;
360
406
 
361
407
  if (typeof oldListener === 'function') {
362
- target.removeEventListener(name, oldListener);
408
+ target.removeEventListener(hookName, oldListener);
363
409
  } else if (oldListener != null) {
364
410
  target.removeEventListener(
365
- name,
411
+ hookName,
366
412
  mapGet(oldListener, listenerKey),
367
413
  !!mapGet(oldListener, captureKey),
368
414
  );
369
415
  }
370
416
 
371
417
  if (typeof listener === 'function') {
372
- target.addEventListener(name, listener);
418
+ target.addEventListener(hookName, listener);
373
419
  } else if (listener != null) {
374
- target.addEventListener(name, mapGet(listener, listenerKey), {
420
+ target.addEventListener(hookName, mapGet(listener, listenerKey), {
375
421
  capture: !!mapGet(listener, captureKey),
376
422
  passive: !!mapGet(listener, passiveKey),
377
423
  });
378
424
  }
379
425
  }
380
- for (const [name] of mapIter(oldHooks)) {
381
- 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;
382
431
  const oldListener = mapGet(oldHooks, name);
383
432
  if (typeof oldListener === 'function') {
384
- target.removeEventListener(name, oldListener);
433
+ target.removeEventListener(hookName, oldListener);
385
434
  } else if (oldListener != null) {
386
435
  target.removeEventListener(
387
- name,
436
+ hookName,
388
437
  mapGet(oldListener, listenerKey),
389
438
  !!mapGet(oldListener, captureKey),
390
439
  );
@@ -402,7 +451,7 @@ export default userSettings => {
402
451
  switch (newVdom.type) {
403
452
  case ELEMENT_NODE: {
404
453
  reconcileElementProps(target, args[0]);
405
- reconcileElementChildren(target, args.slice(1));
454
+ reconcileElementChildren(target, flattenVNodeChildren(args.slice(1)));
406
455
  break;
407
456
  }
408
457
  case OPAQUE_NODE: {
@@ -413,9 +462,9 @@ export default userSettings => {
413
462
  const innerVdom = newVdom.tag.apply(undefined, newVdom.args);
414
463
  if (innerVdom === undefined || innerVdom === null) break;
415
464
  if (isSeq(innerVdom)) {
416
- reconcileElementChildren(target, innerVdom);
465
+ reconcileElementChildren(target, flattenSeq(innerVdom));
417
466
  } else {
418
- reconcileElementChildren(target, [innerVdom]);
467
+ reconcileElementChildren(target, flattenVNodeChildren([innerVdom]));
419
468
  }
420
469
  break;
421
470
  }
@@ -430,7 +479,7 @@ export default userSettings => {
430
479
  }
431
480
 
432
481
  if (newVdom.hooks || oldVdom?.hooks) {
433
- reconcileListeners(target, newVdom.hooks ?? EMPTY_OBJECT);
482
+ reconcileListeners(target, newVdom.hooks ?? EMPTY_MAP);
434
483
  }
435
484
  try {
436
485
  newVdom.hooks?.$update?.(target, newVdom, oldVdom);
@@ -508,8 +557,7 @@ export default userSettings => {
508
557
  }
509
558
  }
510
559
 
511
- function reconcileElementChildren(target, childrenIterable) {
512
- const newChildren = flattenVNodeChildren(childrenIterable);
560
+ function reconcileElementChildren(target, newChildren) {
513
561
  const oldNodesToRemove = new Set(target.childNodes);
514
562
  const oldVNodeNodesPool = new Map();
515
563
  const oldTextNodesPool = [];
@@ -519,8 +567,15 @@ export default userSettings => {
519
567
  oldTextNodesPool.push(oldChild);
520
568
  continue;
521
569
  }
522
- const vdom = oldChild[NODE_STATE]?.vdom;
570
+
571
+ const oldChildState = oldChild[NODE_STATE];
572
+ const vdom = oldChildState?.vdom;
523
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
+ }
524
579
  continue;
525
580
  }
526
581
 
@@ -556,10 +611,10 @@ export default userSettings => {
556
611
  if (pool && pool.length > 0) {
557
612
  newDomNode = pool.shift();
558
613
  const state = newDomNode[NODE_STATE];
559
- if (
560
- shouldUpdate(state.vdom.args, newVdom.args) ||
561
- shouldUpdate(state.vdom.hooks, newVdom.hooks)
562
- ) {
614
+ if (shouldUpdate(state.vdom.args, newVdom.args)) {
615
+ if (state.vdom.hooks || newVdom.hooks) {
616
+ reconcileListeners(target, newVdom.hooks);
617
+ }
563
618
  state.newVdom = newVdom;
564
619
  }
565
620
  } else {
@@ -570,10 +625,10 @@ export default userSettings => {
570
625
  if (unkeyedOldNode) {
571
626
  newDomNode = unkeyedOldNode;
572
627
  const state = newDomNode[NODE_STATE];
573
- if (
574
- shouldUpdate(state.vdom.args, newVdom.args) ||
575
- shouldUpdate(state.vdom.hooks, newVdom.hooks)
576
- ) {
628
+ if (shouldUpdate(state.vdom.args, newVdom.args)) {
629
+ if (state.vdom.hooks || newVdom.hooks) {
630
+ reconcileListeners(target, newVdom.hooks);
631
+ }
577
632
  state.newVdom = newVdom;
578
633
  }
579
634
  } else {
@@ -695,7 +750,7 @@ export default userSettings => {
695
750
  }
696
751
 
697
752
  if (isSeq(vdom)) {
698
- reconcileElementChildren(target, seqIter(vdom));
753
+ reconcileElementChildren(target, flattenSeq(vdom));
699
754
  return;
700
755
  }
701
756
 
@@ -765,6 +820,8 @@ export default userSettings => {
765
820
  convertStyleName,
766
821
  convertDataName,
767
822
  convertClassName,
823
+ convertHookName,
824
+ convertName,
768
825
  listenerKey,
769
826
  captureKey,
770
827
  passiveKey,