@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.
- package/README.md +16 -106
- package/index.js +128 -7
- package/package.json +1 -1
- package/src/html.js +119 -117
- package/src/vdom.js +113 -56
- 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
|
|
153
|
-
const
|
|
154
|
-
const
|
|
155
|
-
const
|
|
156
|
-
const
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
220
|
+
props = EMPTY_MAP;
|
|
203
221
|
}
|
|
204
|
-
return new VNode(ELEMENT_NODE, convertTagName(tag), [props ??
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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] ??
|
|
286
|
+
const oldProps = nodeState.vdom?.args[0] ?? EMPTY_MAP;
|
|
254
287
|
|
|
255
288
|
// Handle new and changed props
|
|
256
|
-
|
|
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 ??
|
|
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 ??
|
|
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 ??
|
|
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
|
-
|
|
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 ??
|
|
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 ??
|
|
357
|
+
reconcileElementAttributes(target, oldValue ?? EMPTY_MAP, EMPTY_MAP);
|
|
320
358
|
break;
|
|
321
359
|
case '$dataset':
|
|
322
|
-
reconcileElementDataset(target, oldValue ??
|
|
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
|
-
|
|
344
|
-
|
|
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(
|
|
388
|
+
target.addEventListener(hookName, listener);
|
|
347
389
|
} else if (listener != null) {
|
|
348
|
-
target.addEventListener(
|
|
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 ??
|
|
356
|
-
|
|
357
|
-
|
|
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(
|
|
408
|
+
target.removeEventListener(hookName, oldListener);
|
|
363
409
|
} else if (oldListener != null) {
|
|
364
410
|
target.removeEventListener(
|
|
365
|
-
|
|
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(
|
|
418
|
+
target.addEventListener(hookName, listener);
|
|
373
419
|
} else if (listener != null) {
|
|
374
|
-
target.addEventListener(
|
|
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
|
-
|
|
381
|
-
|
|
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(
|
|
433
|
+
target.removeEventListener(hookName, oldListener);
|
|
385
434
|
} else if (oldListener != null) {
|
|
386
435
|
target.removeEventListener(
|
|
387
|
-
|
|
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 ??
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
561
|
-
|
|
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
|
-
|
|
575
|
-
|
|
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,
|
|
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,
|