@htmlplus/element 3.3.0 → 3.4.0
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/dist/bundlers.d.ts +5 -5
- package/dist/bundlers.js +4 -4
- package/dist/client.d.ts +51 -26
- package/dist/client.js +710 -633
- package/dist/constants.d.ts +2 -1
- package/dist/constants.js +2 -1
- package/dist/jsx-runtime.d.ts +183 -183
- package/dist/transformer.d.ts +45 -40
- package/dist/transformer.js +394 -278
- package/package.json +96 -92
package/dist/client.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { kebabCase, pascalCase } from 'change-case';
|
|
2
|
-
import { API_HOST, STATIC_TAG, API_STACKS, API_REQUEST, API_CONNECTED, LIFECYCLE_UPDATE, STATIC_STYLE, API_STYLE, LIFECYCLE_UPDATED, API_RENDER_COMPLETED, METHOD_RENDER, TYPE_BOOLEAN, TYPE_NUMBER, TYPE_NULL, TYPE_DATE, TYPE_ARRAY, TYPE_OBJECT, TYPE_UNDEFINED, TYPE_STRING, KEY, LIFECYCLE_CONNECTED, LIFECYCLE_DISCONNECTED, LIFECYCLE_CONSTRUCTED, LIFECYCLE_ADOPTED, LIFECYCLE_READY } from './constants.js';
|
|
2
|
+
import { API_HOST, STATIC_TAG, API_STACKS, API_REQUEST, API_CONNECTED, LIFECYCLE_UPDATE, STATIC_STYLE, API_STYLE, LIFECYCLE_UPDATED, API_RENDER_COMPLETED, METHOD_RENDER, TYPE_BOOLEAN, TYPE_NUMBER, TYPE_NULL, TYPE_DATE, TYPE_ARRAY, TYPE_OBJECT, TYPE_UNDEFINED, TYPE_STRING, KEY, LIFECYCLE_CONNECTED, LIFECYCLE_DISCONNECTED, LIFECYCLE_CONSTRUCTED, LIFECYCLE_ADOPTED, LIFECYCLE_READY, API_DEFAULTS } from './constants.js';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Indicates the host of the element.
|
|
@@ -27,11 +27,19 @@ const dispatch = (target, type, eventInitDict) => {
|
|
|
27
27
|
*/
|
|
28
28
|
const on = (target, type, handler, options) => {
|
|
29
29
|
const element = host(target);
|
|
30
|
-
if (type
|
|
30
|
+
if (type !== 'outside') {
|
|
31
31
|
return element.addEventListener(type, handler, options);
|
|
32
32
|
}
|
|
33
33
|
const callback = (event) => {
|
|
34
|
-
|
|
34
|
+
const has = event.composedPath().some((item) => item === element);
|
|
35
|
+
if (has)
|
|
36
|
+
return;
|
|
37
|
+
if (typeof handler === 'function') {
|
|
38
|
+
handler(event);
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
handler.handleEvent(event);
|
|
42
|
+
}
|
|
35
43
|
};
|
|
36
44
|
type = 'ontouchstart' in window.document.documentElement ? 'touchstart' : 'click';
|
|
37
45
|
on(document, type, callback, options);
|
|
@@ -48,11 +56,11 @@ const on = (target, type, handler, options) => {
|
|
|
48
56
|
*/
|
|
49
57
|
const off = (target, type, handler, options) => {
|
|
50
58
|
const element = host(target);
|
|
51
|
-
if (type
|
|
52
|
-
return element.removeEventListener(type, handler, options);
|
|
59
|
+
if (type !== 'outside') {
|
|
60
|
+
return void element.removeEventListener(type, handler, options);
|
|
53
61
|
}
|
|
54
62
|
const index = outsides.findIndex((outside) => {
|
|
55
|
-
return outside.element
|
|
63
|
+
return (outside.element === element && outside.handler === handler && outside.options === options);
|
|
56
64
|
});
|
|
57
65
|
const outside = outsides[index];
|
|
58
66
|
if (!outside)
|
|
@@ -71,12 +79,10 @@ const toEvent = (input) => {
|
|
|
71
79
|
|
|
72
80
|
const updateAttribute = (target, key, value) => {
|
|
73
81
|
const element = host(target);
|
|
74
|
-
if (
|
|
75
|
-
element.removeAttribute(key);
|
|
76
|
-
}
|
|
77
|
-
else {
|
|
78
|
-
element.setAttribute(key, value === true ? '' : value);
|
|
82
|
+
if (value === undefined || value === null || value === false) {
|
|
83
|
+
return void element.removeAttribute(key);
|
|
79
84
|
}
|
|
85
|
+
element.setAttribute(key, value === true ? '' : String(value));
|
|
80
86
|
};
|
|
81
87
|
|
|
82
88
|
const symbol = Symbol();
|
|
@@ -132,9 +138,10 @@ const classes = (input, smart) => {
|
|
|
132
138
|
break;
|
|
133
139
|
}
|
|
134
140
|
case 'object': {
|
|
135
|
-
const
|
|
141
|
+
const obj = input;
|
|
142
|
+
const keys = Object.keys(obj);
|
|
136
143
|
for (const key of keys) {
|
|
137
|
-
const value =
|
|
144
|
+
const value = obj[key];
|
|
138
145
|
const name = kebabCase(key);
|
|
139
146
|
const type = typeOf(value);
|
|
140
147
|
if (!smart) {
|
|
@@ -167,7 +174,7 @@ const merge = (target, ...sources) => {
|
|
|
167
174
|
for (const source of sources) {
|
|
168
175
|
if (!source)
|
|
169
176
|
continue;
|
|
170
|
-
if (typeOf(source)
|
|
177
|
+
if (typeOf(source) !== 'object') {
|
|
171
178
|
target = source;
|
|
172
179
|
continue;
|
|
173
180
|
}
|
|
@@ -263,13 +270,15 @@ const isCSSColor = (input) => {
|
|
|
263
270
|
/**
|
|
264
271
|
* Indicates whether the direction of the element is `Right-To-Left` or not.
|
|
265
272
|
*/
|
|
266
|
-
const isRTL = (target) =>
|
|
273
|
+
const isRTL = (target) => {
|
|
274
|
+
return direction(target) === 'rtl';
|
|
275
|
+
};
|
|
267
276
|
|
|
268
277
|
/**
|
|
269
278
|
* Indicates whether the current code is running on a server.
|
|
270
279
|
*/
|
|
271
280
|
const isServer = () => {
|
|
272
|
-
return !(typeof window
|
|
281
|
+
return !(typeof window !== 'undefined' && window.document);
|
|
273
282
|
};
|
|
274
283
|
|
|
275
284
|
const shadowRoot = (target) => {
|
|
@@ -291,7 +300,8 @@ function queryAll(target, selectors) {
|
|
|
291
300
|
}
|
|
292
301
|
|
|
293
302
|
const task = (options) => {
|
|
294
|
-
let running
|
|
303
|
+
let running;
|
|
304
|
+
let promise;
|
|
295
305
|
const run = () => {
|
|
296
306
|
if (options.canStart && !options.canStart())
|
|
297
307
|
return Promise.resolve(false);
|
|
@@ -311,8 +321,10 @@ const task = (options) => {
|
|
|
311
321
|
if (!running)
|
|
312
322
|
return promise;
|
|
313
323
|
try {
|
|
314
|
-
if (options.canRun && !options.canRun())
|
|
315
|
-
|
|
324
|
+
if (options.canRun && !options.canRun()) {
|
|
325
|
+
running = false;
|
|
326
|
+
return running;
|
|
327
|
+
}
|
|
316
328
|
options.handler();
|
|
317
329
|
running = false;
|
|
318
330
|
return true;
|
|
@@ -325,23 +337,25 @@ const task = (options) => {
|
|
|
325
337
|
return run;
|
|
326
338
|
};
|
|
327
339
|
|
|
340
|
+
// biome-ignore-all lint: TODO
|
|
341
|
+
|
|
328
342
|
class MapSet extends Map {
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
343
|
+
set(key, value) {
|
|
344
|
+
super.set(key, value);
|
|
345
|
+
return value;
|
|
346
|
+
}
|
|
333
347
|
}
|
|
334
348
|
|
|
335
349
|
class WeakMapSet extends WeakMap {
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
350
|
+
set(key, value) {
|
|
351
|
+
super.set(key, value);
|
|
352
|
+
return value;
|
|
353
|
+
}
|
|
340
354
|
}
|
|
341
355
|
|
|
342
356
|
/*! (c) Andrea Giammarchi - ISC */
|
|
343
357
|
const empty =
|
|
344
|
-
|
|
358
|
+
/^(?:area|base|br|col|embed|hr|img|input|keygen|link|menuitem|meta|param|source|track|wbr)$/i;
|
|
345
359
|
const elements = /<([a-z]+[a-z0-9:._-]*)([^>]*?)(\/?)>/g;
|
|
346
360
|
const attributes$1 = /([^\s\\>"'=]+)\s*=\s*(['"]?)\x01/g;
|
|
347
361
|
const holes = /[\x01\x02]/g;
|
|
@@ -358,155 +372,155 @@ const holes = /[\x01\x02]/g;
|
|
|
358
372
|
* @returns {string} X/HTML with prefixed comments or attributes
|
|
359
373
|
*/
|
|
360
374
|
var instrument = (template, prefix, svg) => {
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
375
|
+
let i = 0;
|
|
376
|
+
return template
|
|
377
|
+
.join('\x01')
|
|
378
|
+
.trim()
|
|
379
|
+
.replace(elements, (_, name, attrs, selfClosing) => {
|
|
380
|
+
let ml = name + attrs.replace(attributes$1, '\x02=$2$1').trimEnd();
|
|
381
|
+
if (selfClosing.length) ml += svg || empty.test(name) ? ' /' : '></' + name;
|
|
382
|
+
return '<' + ml + '>';
|
|
383
|
+
})
|
|
384
|
+
.replace(holes, (hole) => (hole === '\x01' ? '<!--' + prefix + i++ + '-->' : prefix + i++));
|
|
371
385
|
};
|
|
372
386
|
|
|
373
387
|
const ELEMENT_NODE = 1;
|
|
374
388
|
const nodeType = 111;
|
|
375
389
|
|
|
376
390
|
const remove = ({ firstChild, lastChild }) => {
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
391
|
+
const range = document.createRange();
|
|
392
|
+
range.setStartAfter(firstChild);
|
|
393
|
+
range.setEndAfter(lastChild);
|
|
394
|
+
range.deleteContents();
|
|
395
|
+
return firstChild;
|
|
382
396
|
};
|
|
383
397
|
|
|
384
398
|
const diffable = (node, operation) =>
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
399
|
+
node.nodeType === nodeType
|
|
400
|
+
? 1 / operation < 0
|
|
401
|
+
? operation
|
|
402
|
+
? remove(node)
|
|
403
|
+
: node.lastChild
|
|
404
|
+
: operation
|
|
405
|
+
? node.valueOf()
|
|
406
|
+
: node.firstChild
|
|
407
|
+
: node;
|
|
394
408
|
const persistent = (fragment) => {
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
+
const { firstChild, lastChild } = fragment;
|
|
410
|
+
if (firstChild === lastChild) return lastChild || fragment;
|
|
411
|
+
const { childNodes } = fragment;
|
|
412
|
+
const nodes = [...childNodes];
|
|
413
|
+
return {
|
|
414
|
+
ELEMENT_NODE,
|
|
415
|
+
nodeType,
|
|
416
|
+
firstChild,
|
|
417
|
+
lastChild,
|
|
418
|
+
valueOf() {
|
|
419
|
+
if (childNodes.length !== nodes.length) fragment.append(...nodes);
|
|
420
|
+
return fragment;
|
|
421
|
+
}
|
|
422
|
+
};
|
|
409
423
|
};
|
|
410
424
|
|
|
411
425
|
const { isArray: isArray$1 } = Array;
|
|
412
426
|
|
|
413
427
|
const aria = (node) => (values) => {
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
428
|
+
for (const key in values) {
|
|
429
|
+
const name = key === 'role' ? key : `aria-${key}`;
|
|
430
|
+
const value = values[key];
|
|
431
|
+
if (value == null) node.removeAttribute(name);
|
|
432
|
+
else node.setAttribute(name, value);
|
|
433
|
+
}
|
|
420
434
|
};
|
|
421
435
|
|
|
422
436
|
const attribute = (node, name) => {
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
437
|
+
let oldValue,
|
|
438
|
+
orphan = true;
|
|
439
|
+
const attributeNode = document.createAttributeNS(null, name);
|
|
440
|
+
return (newValue) => {
|
|
441
|
+
if (oldValue !== newValue) {
|
|
442
|
+
oldValue = newValue;
|
|
443
|
+
if (oldValue == null) {
|
|
444
|
+
if (!orphan) {
|
|
445
|
+
node.removeAttributeNode(attributeNode);
|
|
446
|
+
orphan = true;
|
|
447
|
+
}
|
|
448
|
+
} else {
|
|
449
|
+
const value = newValue;
|
|
450
|
+
if (value == null) {
|
|
451
|
+
if (!orphan) node.removeAttributeNode(attributeNode);
|
|
452
|
+
orphan = true;
|
|
453
|
+
} else {
|
|
454
|
+
attributeNode.value = value;
|
|
455
|
+
if (orphan) {
|
|
456
|
+
node.setAttributeNodeNS(attributeNode);
|
|
457
|
+
orphan = false;
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
};
|
|
449
463
|
};
|
|
450
464
|
|
|
451
465
|
const boolean = (node, key, oldValue) => (newValue) => {
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
466
|
+
if (oldValue !== !!newValue) {
|
|
467
|
+
// when IE won't be around anymore ...
|
|
468
|
+
// node.toggleAttribute(key, oldValue = !!newValue);
|
|
469
|
+
if ((oldValue = !!newValue)) node.setAttribute(key, '');
|
|
470
|
+
else node.removeAttribute(key);
|
|
471
|
+
}
|
|
458
472
|
};
|
|
459
473
|
|
|
460
474
|
const data =
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
475
|
+
({ dataset }) =>
|
|
476
|
+
(values) => {
|
|
477
|
+
for (const key in values) {
|
|
478
|
+
const value = values[key];
|
|
479
|
+
if (value == null) delete dataset[key];
|
|
480
|
+
else dataset[key] = value;
|
|
481
|
+
}
|
|
482
|
+
};
|
|
469
483
|
|
|
470
484
|
const event = (node, name) => {
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
485
|
+
let oldValue,
|
|
486
|
+
lower,
|
|
487
|
+
type = name.slice(2);
|
|
488
|
+
if (!(name in node) && (lower = name.toLowerCase()) in node) type = lower.slice(2);
|
|
489
|
+
return (newValue) => {
|
|
490
|
+
const info = isArray$1(newValue) ? newValue : [newValue, false];
|
|
491
|
+
if (oldValue !== info[0]) {
|
|
492
|
+
if (oldValue) node.removeEventListener(type, oldValue, info[1]);
|
|
493
|
+
if ((oldValue = info[0])) node.addEventListener(type, oldValue, info[1]);
|
|
494
|
+
}
|
|
495
|
+
};
|
|
482
496
|
};
|
|
483
497
|
|
|
484
498
|
const ref = (node) => {
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
499
|
+
let oldValue;
|
|
500
|
+
return (value) => {
|
|
501
|
+
if (oldValue !== value) {
|
|
502
|
+
oldValue = value;
|
|
503
|
+
if (typeof value === 'function') value(node);
|
|
504
|
+
else value.current = node;
|
|
505
|
+
}
|
|
506
|
+
};
|
|
493
507
|
};
|
|
494
508
|
|
|
495
509
|
const setter = (node, key) =>
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
510
|
+
key === 'dataset'
|
|
511
|
+
? data(node)
|
|
512
|
+
: (value) => {
|
|
513
|
+
node[key] = value;
|
|
514
|
+
};
|
|
501
515
|
|
|
502
516
|
const text = (node) => {
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
517
|
+
let oldValue;
|
|
518
|
+
return (newValue) => {
|
|
519
|
+
if (oldValue != newValue) {
|
|
520
|
+
oldValue = newValue;
|
|
521
|
+
node.textContent = newValue == null ? '' : newValue;
|
|
522
|
+
}
|
|
523
|
+
};
|
|
510
524
|
};
|
|
511
525
|
|
|
512
526
|
/**
|
|
@@ -537,148 +551,148 @@ const text = (node) => {
|
|
|
537
551
|
* @returns {Node[]} The same list of future children.
|
|
538
552
|
*/
|
|
539
553
|
var udomdiff = (parentNode, a, b, get, before) => {
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
554
|
+
const bLength = b.length;
|
|
555
|
+
let aEnd = a.length;
|
|
556
|
+
let bEnd = bLength;
|
|
557
|
+
let aStart = 0;
|
|
558
|
+
let bStart = 0;
|
|
559
|
+
let map = null;
|
|
560
|
+
while (aStart < aEnd || bStart < bEnd) {
|
|
561
|
+
// append head, tail, or nodes in between: fast path
|
|
562
|
+
if (aEnd === aStart) {
|
|
563
|
+
// we could be in a situation where the rest of nodes that
|
|
564
|
+
// need to be added are not at the end, and in such case
|
|
565
|
+
// the node to `insertBefore`, if the index is more than 0
|
|
566
|
+
// must be retrieved, otherwise it's gonna be the first item.
|
|
567
|
+
const node =
|
|
568
|
+
bEnd < bLength
|
|
569
|
+
? bStart
|
|
570
|
+
? get(b[bStart - 1], -0).nextSibling
|
|
571
|
+
: get(b[bEnd - bStart], 0)
|
|
572
|
+
: before;
|
|
573
|
+
while (bStart < bEnd) parentNode.insertBefore(get(b[bStart++], 1), node);
|
|
574
|
+
}
|
|
575
|
+
// remove head or tail: fast path
|
|
576
|
+
else if (bEnd === bStart) {
|
|
577
|
+
while (aStart < aEnd) {
|
|
578
|
+
// remove the node only if it's unknown or not live
|
|
579
|
+
if (!map || !map.has(a[aStart])) parentNode.removeChild(get(a[aStart], -1));
|
|
580
|
+
aStart++;
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
// same node: fast path
|
|
584
|
+
else if (a[aStart] === b[bStart]) {
|
|
585
|
+
aStart++;
|
|
586
|
+
bStart++;
|
|
587
|
+
}
|
|
588
|
+
// same tail: fast path
|
|
589
|
+
else if (a[aEnd - 1] === b[bEnd - 1]) {
|
|
590
|
+
aEnd--;
|
|
591
|
+
bEnd--;
|
|
592
|
+
}
|
|
593
|
+
// The once here single last swap "fast path" has been removed in v1.1.0
|
|
594
|
+
// https://github.com/WebReflection/udomdiff/blob/single-final-swap/esm/index.js#L69-L85
|
|
595
|
+
// reverse swap: also fast path
|
|
596
|
+
else if (a[aStart] === b[bEnd - 1] && b[bStart] === a[aEnd - 1]) {
|
|
597
|
+
// this is a "shrink" operation that could happen in these cases:
|
|
598
|
+
// [1, 2, 3, 4, 5]
|
|
599
|
+
// [1, 4, 3, 2, 5]
|
|
600
|
+
// or asymmetric too
|
|
601
|
+
// [1, 2, 3, 4, 5]
|
|
602
|
+
// [1, 2, 3, 5, 6, 4]
|
|
603
|
+
const node = get(a[--aEnd], -1).nextSibling;
|
|
604
|
+
parentNode.insertBefore(get(b[bStart++], 1), get(a[aStart++], -1).nextSibling);
|
|
605
|
+
parentNode.insertBefore(get(b[--bEnd], 1), node);
|
|
606
|
+
// mark the future index as identical (yeah, it's dirty, but cheap 👍)
|
|
607
|
+
// The main reason to do this, is that when a[aEnd] will be reached,
|
|
608
|
+
// the loop will likely be on the fast path, as identical to b[bEnd].
|
|
609
|
+
// In the best case scenario, the next loop will skip the tail,
|
|
610
|
+
// but in the worst one, this node will be considered as already
|
|
611
|
+
// processed, bailing out pretty quickly from the map index check
|
|
612
|
+
a[aEnd] = b[bEnd];
|
|
613
|
+
}
|
|
614
|
+
// map based fallback, "slow" path
|
|
615
|
+
else {
|
|
616
|
+
// the map requires an O(bEnd - bStart) operation once
|
|
617
|
+
// to store all future nodes indexes for later purposes.
|
|
618
|
+
// In the worst case scenario, this is a full O(N) cost,
|
|
619
|
+
// and such scenario happens at least when all nodes are different,
|
|
620
|
+
// but also if both first and last items of the lists are different
|
|
621
|
+
if (!map) {
|
|
622
|
+
map = new Map();
|
|
623
|
+
let i = bStart;
|
|
624
|
+
while (i < bEnd) map.set(b[i], i++);
|
|
625
|
+
}
|
|
626
|
+
// if it's a future node, hence it needs some handling
|
|
627
|
+
if (map.has(a[aStart])) {
|
|
628
|
+
// grab the index of such node, 'cause it might have been processed
|
|
629
|
+
const index = map.get(a[aStart]);
|
|
630
|
+
// if it's not already processed, look on demand for the next LCS
|
|
631
|
+
if (bStart < index && index < bEnd) {
|
|
632
|
+
let i = aStart;
|
|
633
|
+
// counts the amount of nodes that are the same in the future
|
|
634
|
+
let sequence = 1;
|
|
635
|
+
while (++i < aEnd && i < bEnd && map.get(a[i]) === index + sequence) sequence++;
|
|
636
|
+
// effort decision here: if the sequence is longer than replaces
|
|
637
|
+
// needed to reach such sequence, which would brings again this loop
|
|
638
|
+
// to the fast path, prepend the difference before a sequence,
|
|
639
|
+
// and move only the future list index forward, so that aStart
|
|
640
|
+
// and bStart will be aligned again, hence on the fast path.
|
|
641
|
+
// An example considering aStart and bStart are both 0:
|
|
642
|
+
// a: [1, 2, 3, 4]
|
|
643
|
+
// b: [7, 1, 2, 3, 6]
|
|
644
|
+
// this would place 7 before 1 and, from that time on, 1, 2, and 3
|
|
645
|
+
// will be processed at zero cost
|
|
646
|
+
if (sequence > index - bStart) {
|
|
647
|
+
const node = get(a[aStart], 0);
|
|
648
|
+
while (bStart < index) parentNode.insertBefore(get(b[bStart++], 1), node);
|
|
649
|
+
}
|
|
650
|
+
// if the effort wasn't good enough, fallback to a replace,
|
|
651
|
+
// moving both source and target indexes forward, hoping that some
|
|
652
|
+
// similar node will be found later on, to go back to the fast path
|
|
653
|
+
else {
|
|
654
|
+
parentNode.replaceChild(get(b[bStart++], 1), get(a[aStart++], -1));
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
// otherwise move the source forward, 'cause there's nothing to do
|
|
658
|
+
else aStart++;
|
|
659
|
+
}
|
|
660
|
+
// this node has no meaning in the future list, so it's more than safe
|
|
661
|
+
// to remove it, and check the next live node out instead, meaning
|
|
662
|
+
// that only the live list index should be forwarded
|
|
663
|
+
else parentNode.removeChild(get(a[aStart++], -1));
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
return b;
|
|
653
667
|
};
|
|
654
668
|
|
|
655
669
|
const { isArray, prototype } = Array;
|
|
656
670
|
const { indexOf } = prototype;
|
|
657
671
|
|
|
658
672
|
const {
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
673
|
+
createDocumentFragment,
|
|
674
|
+
createElement,
|
|
675
|
+
createElementNS,
|
|
676
|
+
createTextNode,
|
|
677
|
+
createTreeWalker,
|
|
678
|
+
importNode
|
|
665
679
|
} = new Proxy(typeof window == 'undefined' ? {} : window.document, {
|
|
666
|
-
|
|
680
|
+
get: (target, method) => (target[method] || function () {}).bind(target)
|
|
667
681
|
});
|
|
668
682
|
|
|
669
683
|
const createHTML = (html) => {
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
684
|
+
const template = createElement('template');
|
|
685
|
+
template.innerHTML = html;
|
|
686
|
+
return template.content;
|
|
673
687
|
};
|
|
674
688
|
|
|
675
689
|
let xml;
|
|
676
690
|
const createSVG = (svg) => {
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
691
|
+
if (!xml) xml = createElementNS('http://www.w3.org/2000/svg', 'svg');
|
|
692
|
+
xml.innerHTML = svg;
|
|
693
|
+
const content = createDocumentFragment();
|
|
694
|
+
content.append(...xml.childNodes);
|
|
695
|
+
return content;
|
|
682
696
|
};
|
|
683
697
|
|
|
684
698
|
const createContent = (text, svg) => (svg ? createSVG(text) : createHTML(text));
|
|
@@ -688,91 +702,91 @@ const reducePath = ({ childNodes }, i) => childNodes[i];
|
|
|
688
702
|
|
|
689
703
|
// this helper avoid code bloat around handleAnything() callback
|
|
690
704
|
const diff = (comment, oldNodes, newNodes) =>
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
705
|
+
udomdiff(
|
|
706
|
+
comment.parentNode,
|
|
707
|
+
// TODO: there is a possible edge case where a node has been
|
|
708
|
+
// removed manually, or it was a keyed one, attached
|
|
709
|
+
// to a shared reference between renders.
|
|
710
|
+
// In this case udomdiff might fail at removing such node
|
|
711
|
+
// as its parent won't be the expected one.
|
|
712
|
+
// The best way to avoid this issue is to filter oldNodes
|
|
713
|
+
// in search of those not live, or not in the current parent
|
|
714
|
+
// anymore, but this would require both a change to uwire,
|
|
715
|
+
// exposing a parentNode from the firstChild, as example,
|
|
716
|
+
// but also a filter per each diff that should exclude nodes
|
|
717
|
+
// that are not in there, penalizing performance quite a lot.
|
|
718
|
+
// As this has been also a potential issue with domdiff,
|
|
719
|
+
// and both lighterhtml and hyperHTML might fail with this
|
|
720
|
+
// very specific edge case, I might as well document this possible
|
|
721
|
+
// "diffing shenanigan" and call it a day.
|
|
722
|
+
oldNodes,
|
|
723
|
+
newNodes,
|
|
724
|
+
diffable,
|
|
725
|
+
comment
|
|
726
|
+
);
|
|
713
727
|
|
|
714
728
|
// if an interpolation represents a comment, the whole
|
|
715
729
|
// diffing will be related to such comment.
|
|
716
730
|
// This helper is in charge of understanding how the new
|
|
717
731
|
// content for such interpolation/hole should be updated
|
|
718
732
|
const handleAnything = (comment) => {
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
733
|
+
let oldValue,
|
|
734
|
+
text,
|
|
735
|
+
nodes = [];
|
|
736
|
+
const anyContent = (newValue) => {
|
|
737
|
+
switch (typeof newValue) {
|
|
738
|
+
// primitives are handled as text content
|
|
739
|
+
case 'string':
|
|
740
|
+
case 'number':
|
|
741
|
+
case 'boolean':
|
|
742
|
+
if (oldValue !== newValue) {
|
|
743
|
+
oldValue = newValue;
|
|
744
|
+
if (!text) text = createTextNode('');
|
|
745
|
+
text.data = newValue;
|
|
746
|
+
nodes = diff(comment, nodes, [text]);
|
|
747
|
+
}
|
|
748
|
+
break;
|
|
749
|
+
// null, and undefined are used to cleanup previous content
|
|
750
|
+
case 'object':
|
|
751
|
+
case 'undefined':
|
|
752
|
+
if (newValue == null) {
|
|
753
|
+
if (oldValue != newValue) {
|
|
754
|
+
oldValue = newValue;
|
|
755
|
+
nodes = diff(comment, nodes, []);
|
|
756
|
+
}
|
|
757
|
+
break;
|
|
758
|
+
}
|
|
759
|
+
// arrays and nodes have a special treatment
|
|
760
|
+
if (isArray(newValue)) {
|
|
761
|
+
oldValue = newValue;
|
|
762
|
+
// arrays can be used to cleanup, if empty
|
|
763
|
+
if (newValue.length === 0) nodes = diff(comment, nodes, []);
|
|
764
|
+
// or diffed, if these contains nodes or "wires"
|
|
765
|
+
else if (typeof newValue[0] === 'object') nodes = diff(comment, nodes, newValue);
|
|
766
|
+
// in all other cases the content is stringified as is
|
|
767
|
+
else anyContent(String(newValue));
|
|
768
|
+
break;
|
|
769
|
+
}
|
|
770
|
+
// if the new value is a DOM node, or a wire, and it's
|
|
771
|
+
// different from the one already live, then it's diffed.
|
|
772
|
+
// if the node is a fragment, it's appended once via its childNodes
|
|
773
|
+
// There is no `else` here, meaning if the content
|
|
774
|
+
// is not expected one, nothing happens, as easy as that.
|
|
775
|
+
if (oldValue !== newValue && 'ELEMENT_NODE' in newValue) {
|
|
776
|
+
oldValue = newValue;
|
|
777
|
+
nodes = diff(
|
|
778
|
+
comment,
|
|
779
|
+
nodes,
|
|
780
|
+
newValue.nodeType === 11 ? [...newValue.childNodes] : [newValue]
|
|
781
|
+
);
|
|
782
|
+
}
|
|
783
|
+
break;
|
|
784
|
+
case 'function':
|
|
785
|
+
anyContent(newValue(comment));
|
|
786
|
+
break;
|
|
787
|
+
}
|
|
788
|
+
};
|
|
789
|
+
return anyContent;
|
|
776
790
|
};
|
|
777
791
|
|
|
778
792
|
// attributes can be:
|
|
@@ -786,25 +800,25 @@ const handleAnything = (comment) => {
|
|
|
786
800
|
// * onevent=${...} to automatically handle event listeners
|
|
787
801
|
// * generic=${...} to handle an attribute just like an attribute
|
|
788
802
|
const handleAttribute = (node, name /*, svg*/) => {
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
803
|
+
switch (name[0]) {
|
|
804
|
+
case '?':
|
|
805
|
+
return boolean(node, name.slice(1), false);
|
|
806
|
+
case '.':
|
|
807
|
+
return setter(node, name.slice(1));
|
|
808
|
+
case '@':
|
|
809
|
+
return event(node, 'on' + name.slice(1));
|
|
810
|
+
case 'o':
|
|
811
|
+
if (name[1] === 'n') return event(node, name);
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
switch (name) {
|
|
815
|
+
case 'ref':
|
|
816
|
+
return ref(node);
|
|
817
|
+
case 'aria':
|
|
818
|
+
return aria(node);
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
return attribute(node, name /*, svg*/);
|
|
808
822
|
};
|
|
809
823
|
|
|
810
824
|
// each mapped update carries the update type and its path
|
|
@@ -812,27 +826,27 @@ const handleAttribute = (node, name /*, svg*/) => {
|
|
|
812
826
|
// the path is how to retrieve the related node to update.
|
|
813
827
|
// In the attribute case, the attribute name is also carried along.
|
|
814
828
|
function handlers(options) {
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
829
|
+
const { type, path } = options;
|
|
830
|
+
const node = path.reduceRight(reducePath, this);
|
|
831
|
+
return type === 'node'
|
|
832
|
+
? handleAnything(node)
|
|
833
|
+
: type === 'attr'
|
|
834
|
+
? handleAttribute(node, options.name /*, options.svg*/)
|
|
835
|
+
: text(node);
|
|
822
836
|
}
|
|
823
837
|
|
|
824
838
|
// from a fragment container, create an array of indexes
|
|
825
839
|
// related to its child nodes, so that it's possible
|
|
826
840
|
// to retrieve later on exact node via reducePath
|
|
827
841
|
const createPath = (node) => {
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
842
|
+
const path = [];
|
|
843
|
+
let { parentNode } = node;
|
|
844
|
+
while (parentNode) {
|
|
845
|
+
path.push(indexOf.call(parentNode.childNodes, node));
|
|
846
|
+
node = parentNode;
|
|
847
|
+
({ parentNode } = node);
|
|
848
|
+
}
|
|
849
|
+
return path;
|
|
836
850
|
};
|
|
837
851
|
|
|
838
852
|
// the prefix is used to identify either comments, attributes, or nodes
|
|
@@ -854,100 +868,100 @@ const cache$1 = new WeakMapSet();
|
|
|
854
868
|
const textOnly = /^(?:textarea|script|style|title|plaintext|xmp)$/;
|
|
855
869
|
|
|
856
870
|
const createCache = () => ({
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
+
stack: [], // each template gets a stack for each interpolation "hole"
|
|
872
|
+
|
|
873
|
+
entry: null, // each entry contains details, such as:
|
|
874
|
+
// * the template that is representing
|
|
875
|
+
// * the type of node it represents (html or svg)
|
|
876
|
+
// * the content fragment with all nodes
|
|
877
|
+
// * the list of updates per each node (template holes)
|
|
878
|
+
// * the "wired" node or fragment that will get updates
|
|
879
|
+
// if the template or type are different from the previous one
|
|
880
|
+
// the entry gets re-created each time
|
|
881
|
+
|
|
882
|
+
wire: null // each rendered node represent some wired content and
|
|
883
|
+
// this reference to the latest one. If different, the node
|
|
884
|
+
// will be cleaned up and the new "wire" will be appended
|
|
871
885
|
});
|
|
872
886
|
|
|
873
887
|
// the entry stored in the rendered node cache, and per each "hole"
|
|
874
888
|
const createEntry = (type, template) => {
|
|
875
|
-
|
|
876
|
-
|
|
889
|
+
const { content, updates } = mapUpdates(type, template);
|
|
890
|
+
return { type, template, content, updates, wire: null };
|
|
877
891
|
};
|
|
878
892
|
|
|
879
893
|
// a template is instrumented to be able to retrieve where updates are needed.
|
|
880
894
|
// Each unique template becomes a fragment, cloned once per each other
|
|
881
895
|
// operation based on the same template, i.e. data => html`<p>${data}</p>`
|
|
882
896
|
const mapTemplate = (type, template) => {
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
897
|
+
const svg = type === 'svg';
|
|
898
|
+
const text = instrument(template, prefix, svg);
|
|
899
|
+
const content = createContent(text, svg);
|
|
900
|
+
// once instrumented and reproduced as fragment, it's crawled
|
|
901
|
+
// to find out where each update is in the fragment tree
|
|
902
|
+
const tw = createTreeWalker(content, 1 | 128);
|
|
903
|
+
const nodes = [];
|
|
904
|
+
const length = template.length - 1;
|
|
905
|
+
let i = 0;
|
|
906
|
+
// updates are searched via unique names, linearly increased across the tree
|
|
907
|
+
// <div isµ0="attr" isµ1="other"><!--isµ2--><style><!--isµ3--</style></div>
|
|
908
|
+
let search = `${prefix}${i}`;
|
|
909
|
+
while (i < length) {
|
|
910
|
+
const node = tw.nextNode();
|
|
911
|
+
// if not all updates are bound but there's nothing else to crawl
|
|
912
|
+
// it means that there is something wrong with the template.
|
|
913
|
+
if (!node) throw `bad template: ${text}`;
|
|
914
|
+
// if the current node is a comment, and it contains isµX
|
|
915
|
+
// it means the update should take care of any content
|
|
916
|
+
if (node.nodeType === 8) {
|
|
917
|
+
// The only comments to be considered are those
|
|
918
|
+
// which content is exactly the same as the searched one.
|
|
919
|
+
if (node.data === search) {
|
|
920
|
+
nodes.push({ type: 'node', path: createPath(node) });
|
|
921
|
+
search = `${prefix}${++i}`;
|
|
922
|
+
}
|
|
923
|
+
} else {
|
|
924
|
+
// if the node is not a comment, loop through all its attributes
|
|
925
|
+
// named isµX and relate attribute updates to this node and the
|
|
926
|
+
// attribute name, retrieved through node.getAttribute("isµX")
|
|
927
|
+
// the isµX attribute will be removed as irrelevant for the layout
|
|
928
|
+
// let svg = -1;
|
|
929
|
+
while (node.hasAttribute(search)) {
|
|
930
|
+
nodes.push({
|
|
931
|
+
type: 'attr',
|
|
932
|
+
path: createPath(node),
|
|
933
|
+
name: node.getAttribute(search)
|
|
934
|
+
});
|
|
935
|
+
node.removeAttribute(search);
|
|
936
|
+
search = `${prefix}${++i}`;
|
|
937
|
+
}
|
|
938
|
+
// if the node was a style, textarea, or others, check its content
|
|
939
|
+
// and if it is <!--isµX--> then update tex-only this node
|
|
940
|
+
if (textOnly.test(node.localName) && node.textContent.trim() === `<!--${search}-->`) {
|
|
941
|
+
node.textContent = '';
|
|
942
|
+
nodes.push({ type: 'text', path: createPath(node) });
|
|
943
|
+
search = `${prefix}${++i}`;
|
|
944
|
+
}
|
|
945
|
+
}
|
|
946
|
+
}
|
|
947
|
+
// once all nodes to update, or their attributes, are known, the content
|
|
948
|
+
// will be cloned in the future to represent the template, and all updates
|
|
949
|
+
// related to such content retrieved right away without needing to re-crawl
|
|
950
|
+
// the exact same template, and its content, more than once.
|
|
951
|
+
return { content, nodes };
|
|
938
952
|
};
|
|
939
953
|
|
|
940
954
|
// if a template is unknown, perform the previous mapping, otherwise grab
|
|
941
955
|
// its details such as the fragment with all nodes, and updates info.
|
|
942
956
|
const mapUpdates = (type, template) => {
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
957
|
+
const { content, nodes } =
|
|
958
|
+
cache$1.get(template) || cache$1.set(template, mapTemplate(type, template));
|
|
959
|
+
// clone deeply the fragment
|
|
960
|
+
const fragment = importNode(content, true);
|
|
961
|
+
// and relate an update handler per each node that needs one
|
|
962
|
+
const updates = nodes.map(handlers, fragment);
|
|
963
|
+
// return the fragment and all updates to use within its nodes
|
|
964
|
+
return { content: fragment, updates };
|
|
951
965
|
};
|
|
952
966
|
|
|
953
967
|
// as html and svg can be nested calls, but no parent node is known
|
|
@@ -955,48 +969,48 @@ const mapUpdates = (type, template) => {
|
|
|
955
969
|
// discover what to do with each interpolation, which will result
|
|
956
970
|
// into an update operation.
|
|
957
971
|
const unroll = (info, { type, template, values }) => {
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
972
|
+
// interpolations can contain holes and arrays, so these need
|
|
973
|
+
// to be recursively discovered
|
|
974
|
+
const length = unrollValues(info, values);
|
|
975
|
+
let { entry } = info;
|
|
976
|
+
// if the cache entry is either null or different from the template
|
|
977
|
+
// and the type this unroll should resolve, create a new entry
|
|
978
|
+
// assigning a new content fragment and the list of updates.
|
|
979
|
+
if (!entry || entry.template !== template || entry.type !== type)
|
|
980
|
+
info.entry = entry = createEntry(type, template);
|
|
981
|
+
const { content, updates, wire } = entry;
|
|
982
|
+
// even if the fragment and its nodes is not live yet,
|
|
983
|
+
// it is already possible to update via interpolations values.
|
|
984
|
+
for (let i = 0; i < length; i++) updates[i](values[i]);
|
|
985
|
+
// if the entry was new, or representing a different template or type,
|
|
986
|
+
// create a new persistent entity to use during diffing.
|
|
987
|
+
// This is simply a DOM node, when the template has a single container,
|
|
988
|
+
// as in `<p></p>`, or a "wire" in `<p></p><p></p>` and similar cases.
|
|
989
|
+
return wire || (entry.wire = persistent(content));
|
|
976
990
|
};
|
|
977
991
|
|
|
978
992
|
// the stack retains, per each interpolation value, the cache
|
|
979
993
|
// related to each interpolation value, or null, if the render
|
|
980
994
|
// was conditional and the value is not special (Array or Hole)
|
|
981
995
|
const unrollValues = ({ stack }, values) => {
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
996
|
+
const { length } = values;
|
|
997
|
+
for (let i = 0; i < length; i++) {
|
|
998
|
+
const hole = values[i];
|
|
999
|
+
// each Hole gets unrolled and re-assigned as value
|
|
1000
|
+
// so that domdiff will deal with a node/wire, not with a hole
|
|
1001
|
+
if (hole instanceof Hole) values[i] = unroll(stack[i] || (stack[i] = createCache()), hole);
|
|
1002
|
+
// arrays are recursively resolved so that each entry will contain
|
|
1003
|
+
// also a DOM node or a wire, hence it can be diffed if/when needed
|
|
1004
|
+
else if (isArray(hole)) unrollValues(stack[i] || (stack[i] = createCache()), hole);
|
|
1005
|
+
// if the value is nothing special, the stack doesn't need to retain data
|
|
1006
|
+
// this is useful also to cleanup previously retained data, if the value
|
|
1007
|
+
// was a Hole, or an Array, but not anymore, i.e.:
|
|
1008
|
+
// const update = content => html`<div>${content}</div>`;
|
|
1009
|
+
// update(listOfItems); update(null); update(html`hole`)
|
|
1010
|
+
else stack[i] = null;
|
|
1011
|
+
}
|
|
1012
|
+
if (length < stack.length) stack.splice(length);
|
|
1013
|
+
return length;
|
|
1000
1014
|
};
|
|
1001
1015
|
|
|
1002
1016
|
/**
|
|
@@ -1007,44 +1021,44 @@ const unrollValues = ({ stack }, values) => {
|
|
|
1007
1021
|
* @param {Array} values Zero, one, or more interpolated values to render.
|
|
1008
1022
|
*/
|
|
1009
1023
|
class Hole {
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1024
|
+
constructor(type, template, values) {
|
|
1025
|
+
this.type = type;
|
|
1026
|
+
this.template = template;
|
|
1027
|
+
this.values = values;
|
|
1028
|
+
}
|
|
1015
1029
|
}
|
|
1016
1030
|
|
|
1017
1031
|
// both `html` and `svg` template literal tags are polluted
|
|
1018
1032
|
// with a `for(ref[, id])` and a `node` tag too
|
|
1019
1033
|
const tag = (type) => {
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1034
|
+
// both `html` and `svg` tags have their own cache
|
|
1035
|
+
const keyed = new WeakMapSet();
|
|
1036
|
+
// keyed operations always re-use the same cache and unroll
|
|
1037
|
+
// the template and its interpolations right away
|
|
1038
|
+
const fixed =
|
|
1039
|
+
(cache) =>
|
|
1040
|
+
(template, ...values) =>
|
|
1041
|
+
unroll(cache, { type, template, values });
|
|
1042
|
+
return Object.assign(
|
|
1043
|
+
// non keyed operations are recognized as instance of Hole
|
|
1044
|
+
// during the "unroll", recursively resolved and updated
|
|
1045
|
+
(template, ...values) => new Hole(type, template, values),
|
|
1046
|
+
{
|
|
1047
|
+
// keyed operations need a reference object, usually the parent node
|
|
1048
|
+
// which is showing keyed results, and optionally a unique id per each
|
|
1049
|
+
// related node, handy with JSON results and mutable list of objects
|
|
1050
|
+
// that usually carry a unique identifier
|
|
1051
|
+
for(ref, id) {
|
|
1052
|
+
const memo = keyed.get(ref) || keyed.set(ref, new MapSet());
|
|
1053
|
+
return memo.get(id) || memo.set(id, fixed(createCache()));
|
|
1054
|
+
},
|
|
1055
|
+
// it is possible to create one-off content out of the box via node tag
|
|
1056
|
+
// this might return the single created node, or a fragment with all
|
|
1057
|
+
// nodes present at the root level and, of course, their child nodes
|
|
1058
|
+
node: (template, ...values) =>
|
|
1059
|
+
unroll(createCache(), new Hole(type, template, values)).valueOf()
|
|
1060
|
+
}
|
|
1061
|
+
);
|
|
1048
1062
|
};
|
|
1049
1063
|
|
|
1050
1064
|
// each rendered node gets its own cache
|
|
@@ -1056,18 +1070,18 @@ const cache = new WeakMapSet();
|
|
|
1056
1070
|
// and the new new content is appended, and if such content is a Hole
|
|
1057
1071
|
// then it's "unrolled" to resolve all its inner nodes.
|
|
1058
1072
|
const render = (where, what) => {
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1073
|
+
const hole = typeof what === 'function' ? what() : what;
|
|
1074
|
+
const info = cache.get(where) || cache.set(where, createCache());
|
|
1075
|
+
const wire = hole instanceof Hole ? unroll(info, hole) : hole;
|
|
1076
|
+
if (wire !== info.wire) {
|
|
1077
|
+
info.wire = wire;
|
|
1078
|
+
// valueOf() simply returns the node itself, but in case it was a "wire"
|
|
1079
|
+
// it will eventually re-append all nodes to its fragment so that such
|
|
1080
|
+
// fragment can be re-appended many times in a meaningful way
|
|
1081
|
+
// (wires are basically persistent fragments facades with special behavior)
|
|
1082
|
+
where.replaceChildren(wire.valueOf());
|
|
1083
|
+
}
|
|
1084
|
+
return where;
|
|
1071
1085
|
};
|
|
1072
1086
|
|
|
1073
1087
|
const html$1 = tag('html');
|
|
@@ -1081,8 +1095,10 @@ tag('svg');
|
|
|
1081
1095
|
* @param callback Invoked when the rendering phase is completed.
|
|
1082
1096
|
*/
|
|
1083
1097
|
const requestUpdate = (target, name, previous, callback) => {
|
|
1098
|
+
// Ensure API_STACKS exists on target
|
|
1099
|
+
target[API_STACKS] ||= new Map();
|
|
1084
1100
|
// Creates/Gets a stacks.
|
|
1085
|
-
const stacks =
|
|
1101
|
+
const stacks = target[API_STACKS];
|
|
1086
1102
|
// Creates/Updates a stack.
|
|
1087
1103
|
const stack = stacks.get(name) || { callbacks: [], previous };
|
|
1088
1104
|
// Adds the callback to the stack, if exists.
|
|
@@ -1105,7 +1121,7 @@ const requestUpdate = (target, name, previous, callback) => {
|
|
|
1105
1121
|
// Invokes requests' callback.
|
|
1106
1122
|
stacks.forEach((state) => {
|
|
1107
1123
|
state.callbacks.forEach((callback, index, callbacks) => {
|
|
1108
|
-
callback(callbacks.length - 1
|
|
1124
|
+
callback(callbacks.length - 1 !== index);
|
|
1109
1125
|
});
|
|
1110
1126
|
});
|
|
1111
1127
|
// TODO
|
|
@@ -1123,7 +1139,7 @@ const requestUpdate = (target, name, previous, callback) => {
|
|
|
1123
1139
|
if (!hasVariable && localSheet)
|
|
1124
1140
|
return;
|
|
1125
1141
|
const parsed = raw
|
|
1126
|
-
.replace(regex1, (
|
|
1142
|
+
.replace(regex1, (_match, key) => {
|
|
1127
1143
|
let value = target;
|
|
1128
1144
|
for (const section of key.split('-')) {
|
|
1129
1145
|
value = value?.[section];
|
|
@@ -1134,7 +1150,7 @@ const requestUpdate = (target, name, previous, callback) => {
|
|
|
1134
1150
|
if (!localSheet) {
|
|
1135
1151
|
localSheet = new CSSStyleSheet();
|
|
1136
1152
|
target[API_STYLE] = localSheet;
|
|
1137
|
-
shadowRoot(target)
|
|
1153
|
+
shadowRoot(target)?.adoptedStyleSheets.push(localSheet);
|
|
1138
1154
|
}
|
|
1139
1155
|
const localStyle = parsed.replace(regex3, '');
|
|
1140
1156
|
localSheet.replaceSync(localStyle);
|
|
@@ -1149,7 +1165,7 @@ const requestUpdate = (target, name, previous, callback) => {
|
|
|
1149
1165
|
?.match(regex3)
|
|
1150
1166
|
?.join('')
|
|
1151
1167
|
?.replaceAll('global', '')
|
|
1152
|
-
?.replaceAll(':host', getTag(target));
|
|
1168
|
+
?.replaceAll(':host', getTag(target) || '');
|
|
1153
1169
|
globalSheet.replaceSync(globalStyle);
|
|
1154
1170
|
})();
|
|
1155
1171
|
// Calls the lifecycle's callback after the rendering phase.
|
|
@@ -1173,9 +1189,15 @@ const slots = (target) => {
|
|
|
1173
1189
|
const slots = {};
|
|
1174
1190
|
const children = Array.from(element.childNodes);
|
|
1175
1191
|
for (const child of children) {
|
|
1176
|
-
if (child.
|
|
1192
|
+
if (child.nodeType === Node.COMMENT_NODE)
|
|
1177
1193
|
continue;
|
|
1178
|
-
|
|
1194
|
+
let name;
|
|
1195
|
+
if (child instanceof HTMLElement) {
|
|
1196
|
+
name = child.slot || 'default';
|
|
1197
|
+
}
|
|
1198
|
+
else if (child.nodeType === Node.TEXT_NODE && child.nodeValue?.trim()) {
|
|
1199
|
+
name = 'default';
|
|
1200
|
+
}
|
|
1179
1201
|
if (!name)
|
|
1180
1202
|
continue;
|
|
1181
1203
|
slots[name] = true;
|
|
@@ -1198,10 +1220,10 @@ const toCSSColor = (input) => {
|
|
|
1198
1220
|
};
|
|
1199
1221
|
|
|
1200
1222
|
const toCSSUnit = (input) => {
|
|
1201
|
-
if (input
|
|
1223
|
+
if (input === undefined || input === null || input === '') {
|
|
1202
1224
|
return;
|
|
1203
1225
|
}
|
|
1204
|
-
if (typeof input === 'number' || !isNaN(Number(input))) {
|
|
1226
|
+
if (typeof input === 'number' || (typeof input === 'string' && !Number.isNaN(Number(input)))) {
|
|
1205
1227
|
return `${input}px`;
|
|
1206
1228
|
}
|
|
1207
1229
|
if (/^\d+(\.\d+)?(px|pt|cm|mm|in|em|rem|%|vw|vh)$/.test(input)) {
|
|
@@ -1210,7 +1232,7 @@ const toCSSUnit = (input) => {
|
|
|
1210
1232
|
};
|
|
1211
1233
|
|
|
1212
1234
|
function toDecorator(util, ...args) {
|
|
1213
|
-
return
|
|
1235
|
+
return (target, key) => {
|
|
1214
1236
|
defineProperty(target, key, {
|
|
1215
1237
|
get() {
|
|
1216
1238
|
return util(this, ...args);
|
|
@@ -1223,7 +1245,8 @@ const toProperty = (input, type) => {
|
|
|
1223
1245
|
if (type === undefined)
|
|
1224
1246
|
return input;
|
|
1225
1247
|
const string = `${input}`;
|
|
1226
|
-
|
|
1248
|
+
const typeNumber = typeof type === 'number' ? type : 0;
|
|
1249
|
+
if (TYPE_BOOLEAN & typeNumber || type === Boolean) {
|
|
1227
1250
|
if (string === '')
|
|
1228
1251
|
return true;
|
|
1229
1252
|
if (string === 'true')
|
|
@@ -1231,55 +1254,51 @@ const toProperty = (input, type) => {
|
|
|
1231
1254
|
if (string === 'false')
|
|
1232
1255
|
return false;
|
|
1233
1256
|
}
|
|
1234
|
-
if (TYPE_NUMBER &
|
|
1235
|
-
if (string
|
|
1236
|
-
return parseFloat(
|
|
1257
|
+
if (TYPE_NUMBER & typeNumber || type === Number) {
|
|
1258
|
+
if (string !== '' && !Number.isNaN(Number(input))) {
|
|
1259
|
+
return parseFloat(string);
|
|
1237
1260
|
}
|
|
1238
1261
|
}
|
|
1239
|
-
if (TYPE_NULL &
|
|
1262
|
+
if (TYPE_NULL & typeNumber || type === null) {
|
|
1240
1263
|
if (string === 'null') {
|
|
1241
1264
|
return null;
|
|
1242
1265
|
}
|
|
1243
1266
|
}
|
|
1244
|
-
if (TYPE_DATE &
|
|
1245
|
-
const value = new Date(
|
|
1246
|
-
if (value.
|
|
1267
|
+
if (TYPE_DATE & typeNumber || type === Date) {
|
|
1268
|
+
const value = new Date(string);
|
|
1269
|
+
if (!Number.isNaN(value.getTime())) {
|
|
1247
1270
|
return value;
|
|
1248
1271
|
}
|
|
1249
1272
|
}
|
|
1250
|
-
if (TYPE_ARRAY &
|
|
1273
|
+
if (TYPE_ARRAY & typeNumber || type === Array) {
|
|
1251
1274
|
try {
|
|
1252
|
-
const value = JSON.parse(
|
|
1253
|
-
if (typeOf(value)
|
|
1275
|
+
const value = JSON.parse(string);
|
|
1276
|
+
if (typeOf(value) === 'array')
|
|
1254
1277
|
return value;
|
|
1255
|
-
}
|
|
1256
1278
|
}
|
|
1257
1279
|
catch { }
|
|
1258
1280
|
}
|
|
1259
|
-
if (TYPE_OBJECT &
|
|
1281
|
+
if (TYPE_OBJECT & typeNumber || type === Object) {
|
|
1260
1282
|
try {
|
|
1261
|
-
const value = JSON.parse(
|
|
1262
|
-
if (typeOf(value)
|
|
1283
|
+
const value = JSON.parse(string);
|
|
1284
|
+
if (typeOf(value) === 'object') {
|
|
1263
1285
|
return value;
|
|
1264
1286
|
}
|
|
1265
1287
|
}
|
|
1266
1288
|
catch { }
|
|
1267
1289
|
}
|
|
1268
|
-
if (TYPE_UNDEFINED &
|
|
1290
|
+
if (TYPE_UNDEFINED & typeNumber) {
|
|
1269
1291
|
if (string === 'undefined') {
|
|
1270
1292
|
return undefined;
|
|
1271
1293
|
}
|
|
1272
1294
|
}
|
|
1273
|
-
if (TYPE_STRING &
|
|
1274
|
-
return
|
|
1295
|
+
if (TYPE_STRING & typeNumber || type === String) {
|
|
1296
|
+
return string;
|
|
1275
1297
|
}
|
|
1276
|
-
// TODO
|
|
1277
|
-
// if (CONSTANTS.TYPE_BIGINT & type || type === BigInt) { }
|
|
1278
|
-
// if (CONSTANTS.TYPE_ENUM & type || type === TODO) { }
|
|
1279
|
-
// if (CONSTANTS.TYPE_FUNCTION & type || type === Function) { }
|
|
1298
|
+
// TODO: BigInt, Enum, Function
|
|
1280
1299
|
try {
|
|
1281
1300
|
// TODO
|
|
1282
|
-
return JSON.parse(
|
|
1301
|
+
return JSON.parse(string);
|
|
1283
1302
|
}
|
|
1284
1303
|
catch {
|
|
1285
1304
|
return input;
|
|
@@ -1291,13 +1310,15 @@ const toProperty = (input, type) => {
|
|
|
1291
1310
|
*/
|
|
1292
1311
|
const toUnit = (input, unit = 'px') => {
|
|
1293
1312
|
if (input === null || input === undefined || input === '')
|
|
1294
|
-
return input;
|
|
1295
|
-
if (isNaN(+input))
|
|
1296
1313
|
return String(input);
|
|
1297
|
-
|
|
1314
|
+
if (Number.isNaN(Number(input)))
|
|
1315
|
+
return String(input);
|
|
1316
|
+
return `${Number(input)}${unit}`;
|
|
1298
1317
|
};
|
|
1299
1318
|
|
|
1300
|
-
const wrapMethod = (mode, target, key,
|
|
1319
|
+
const wrapMethod = (mode, target, key,
|
|
1320
|
+
// biome-ignore lint: TODO
|
|
1321
|
+
handler) => {
|
|
1301
1322
|
// Gets the original function
|
|
1302
1323
|
const original = target[key];
|
|
1303
1324
|
// Validate target property
|
|
@@ -1307,13 +1328,13 @@ const wrapMethod = (mode, target, key, handler) => {
|
|
|
1307
1328
|
// Creates new function
|
|
1308
1329
|
function wrapped(...args) {
|
|
1309
1330
|
// Calls the handler before the original
|
|
1310
|
-
if (mode
|
|
1331
|
+
if (mode === 'before') {
|
|
1311
1332
|
handler.apply(this, args);
|
|
1312
1333
|
}
|
|
1313
1334
|
// Calls the original
|
|
1314
1335
|
const result = original?.apply(this, args);
|
|
1315
1336
|
// Calls the handler after the original
|
|
1316
|
-
if (mode
|
|
1337
|
+
if (mode === 'after') {
|
|
1317
1338
|
handler.apply(this, args);
|
|
1318
1339
|
}
|
|
1319
1340
|
// Returns the result
|
|
@@ -1328,7 +1349,7 @@ const wrapMethod = (mode, target, key, handler) => {
|
|
|
1328
1349
|
* making it easier to reference `this` within the method.
|
|
1329
1350
|
*/
|
|
1330
1351
|
function Bind() {
|
|
1331
|
-
return
|
|
1352
|
+
return (_target, key, descriptor) => {
|
|
1332
1353
|
const original = descriptor.value;
|
|
1333
1354
|
return {
|
|
1334
1355
|
configurable: true,
|
|
@@ -1345,8 +1366,9 @@ function Bind() {
|
|
|
1345
1366
|
};
|
|
1346
1367
|
}
|
|
1347
1368
|
|
|
1369
|
+
// biome-ignore-all lint: TODO
|
|
1348
1370
|
function Provider(namespace) {
|
|
1349
|
-
return
|
|
1371
|
+
return (target, key) => {
|
|
1350
1372
|
const symbol = Symbol();
|
|
1351
1373
|
const [MAIN, SUB] = namespace.split('.');
|
|
1352
1374
|
const prefix = `${KEY}:${MAIN}`;
|
|
@@ -1393,12 +1415,14 @@ function Provider(namespace) {
|
|
|
1393
1415
|
cleanups(this).set(type, cleanup);
|
|
1394
1416
|
});
|
|
1395
1417
|
wrapMethod('after', target, LIFECYCLE_DISCONNECTED, function () {
|
|
1396
|
-
cleanups(this).forEach((cleanup) =>
|
|
1418
|
+
cleanups(this).forEach((cleanup) => {
|
|
1419
|
+
cleanup();
|
|
1420
|
+
});
|
|
1397
1421
|
});
|
|
1398
1422
|
};
|
|
1399
1423
|
}
|
|
1400
1424
|
function Consumer(namespace) {
|
|
1401
|
-
return
|
|
1425
|
+
return (target, key) => {
|
|
1402
1426
|
const symbol = Symbol();
|
|
1403
1427
|
const [MAIN, SUB] = namespace.split('.');
|
|
1404
1428
|
const prefix = `${KEY}:${MAIN}`;
|
|
@@ -1414,7 +1438,7 @@ function Consumer(namespace) {
|
|
|
1414
1438
|
if (SUB && this[SUB])
|
|
1415
1439
|
return;
|
|
1416
1440
|
// TODO
|
|
1417
|
-
let connected;
|
|
1441
|
+
let connected = false;
|
|
1418
1442
|
const options = {
|
|
1419
1443
|
bubbles: true
|
|
1420
1444
|
};
|
|
@@ -1460,7 +1484,9 @@ function Consumer(namespace) {
|
|
|
1460
1484
|
dispatch(window, `${type}:presence`);
|
|
1461
1485
|
});
|
|
1462
1486
|
wrapMethod('after', target, LIFECYCLE_DISCONNECTED, function () {
|
|
1463
|
-
cleanups(this).forEach((cleanup) =>
|
|
1487
|
+
cleanups(this).forEach((cleanup) => {
|
|
1488
|
+
cleanup();
|
|
1489
|
+
});
|
|
1464
1490
|
});
|
|
1465
1491
|
};
|
|
1466
1492
|
}
|
|
@@ -1473,7 +1499,7 @@ function Consumer(namespace) {
|
|
|
1473
1499
|
* @param {number} delay - The debounce delay in milliseconds.
|
|
1474
1500
|
*/
|
|
1475
1501
|
function Debounce(delay = 0) {
|
|
1476
|
-
return
|
|
1502
|
+
return (target, key, descriptor) => {
|
|
1477
1503
|
const KEY = Symbol();
|
|
1478
1504
|
const original = descriptor.value;
|
|
1479
1505
|
function clear() {
|
|
@@ -1508,27 +1534,36 @@ function Direction() {
|
|
|
1508
1534
|
* and its name, in kebab-case, serves as the element name.
|
|
1509
1535
|
*/
|
|
1510
1536
|
function Element() {
|
|
1511
|
-
|
|
1537
|
+
// biome-ignore lint: TODO
|
|
1538
|
+
return (constructor) => {
|
|
1512
1539
|
if (isServer())
|
|
1513
1540
|
return;
|
|
1514
1541
|
const tag = getTag(constructor);
|
|
1542
|
+
if (!tag)
|
|
1543
|
+
return;
|
|
1515
1544
|
if (customElements.get(tag))
|
|
1516
1545
|
return;
|
|
1517
1546
|
customElements.define(tag, proxy(constructor));
|
|
1518
1547
|
};
|
|
1519
1548
|
}
|
|
1549
|
+
// biome-ignore lint: TODO
|
|
1520
1550
|
const proxy = (constructor) => {
|
|
1521
1551
|
return class Plus extends HTMLElement {
|
|
1522
1552
|
#instance;
|
|
1553
|
+
// biome-ignore lint: TODO
|
|
1523
1554
|
static formAssociated = constructor['formAssociated'];
|
|
1555
|
+
// biome-ignore lint: TODO
|
|
1524
1556
|
static observedAttributes = constructor['observedAttributes'];
|
|
1525
1557
|
constructor() {
|
|
1526
1558
|
super();
|
|
1527
1559
|
this.attachShadow({
|
|
1528
1560
|
mode: 'open',
|
|
1561
|
+
// biome-ignore lint: TODO
|
|
1529
1562
|
delegatesFocus: constructor['delegatesFocus'],
|
|
1563
|
+
// biome-ignore lint: TODO
|
|
1530
1564
|
slotAssignment: constructor['slotAssignment']
|
|
1531
1565
|
});
|
|
1566
|
+
// biome-ignore lint: TODO
|
|
1532
1567
|
this.#instance = new constructor();
|
|
1533
1568
|
this.#instance[API_HOST] = () => this;
|
|
1534
1569
|
call(this.#instance, LIFECYCLE_CONSTRUCTED);
|
|
@@ -1537,15 +1572,15 @@ const proxy = (constructor) => {
|
|
|
1537
1572
|
call(this.#instance, LIFECYCLE_ADOPTED);
|
|
1538
1573
|
}
|
|
1539
1574
|
attributeChangedCallback(key, prev, next) {
|
|
1540
|
-
if (prev
|
|
1541
|
-
this.#instance[
|
|
1575
|
+
if (prev !== next) {
|
|
1576
|
+
this.#instance[`RAW:${key}`] = next;
|
|
1542
1577
|
}
|
|
1543
1578
|
}
|
|
1544
1579
|
connectedCallback() {
|
|
1545
1580
|
// TODO: experimental for global config
|
|
1546
1581
|
(() => {
|
|
1547
|
-
const namespace = getNamespace(this.#instance);
|
|
1548
|
-
const tag = getTag(this.#instance);
|
|
1582
|
+
const namespace = getNamespace(this.#instance) || '';
|
|
1583
|
+
const tag = getTag(this.#instance) || '';
|
|
1549
1584
|
const properties = getConfig(namespace).elements?.[tag]?.properties;
|
|
1550
1585
|
if (!properties)
|
|
1551
1586
|
return;
|
|
@@ -1559,11 +1594,11 @@ const proxy = (constructor) => {
|
|
|
1559
1594
|
if (!props)
|
|
1560
1595
|
return;
|
|
1561
1596
|
for (const [key, value] of Object.entries(props)) {
|
|
1562
|
-
if (this[key]
|
|
1597
|
+
if (this[key] !== undefined)
|
|
1563
1598
|
continue;
|
|
1564
|
-
if (key
|
|
1599
|
+
if (key === 'children')
|
|
1565
1600
|
continue;
|
|
1566
|
-
if (typeof value
|
|
1601
|
+
if (typeof value !== 'object')
|
|
1567
1602
|
continue;
|
|
1568
1603
|
this[key] = value;
|
|
1569
1604
|
}
|
|
@@ -1587,7 +1622,7 @@ const proxy = (constructor) => {
|
|
|
1587
1622
|
* @param options An object that configures options for the event dispatcher.
|
|
1588
1623
|
*/
|
|
1589
1624
|
function Event(options = {}) {
|
|
1590
|
-
return
|
|
1625
|
+
return (target, key) => {
|
|
1591
1626
|
target[key] = function (detail) {
|
|
1592
1627
|
const element = host(this);
|
|
1593
1628
|
const framework = getFramework(this);
|
|
@@ -1599,6 +1634,7 @@ function Event(options = {}) {
|
|
|
1599
1634
|
options.bubbles = true;
|
|
1600
1635
|
type = pascalCase(type);
|
|
1601
1636
|
try {
|
|
1637
|
+
// biome-ignore lint: TODO
|
|
1602
1638
|
window['Blazor'].registerCustomEventType(type, {
|
|
1603
1639
|
createEventArgs: (event) => ({
|
|
1604
1640
|
detail: event.detail
|
|
@@ -1620,7 +1656,7 @@ function Event(options = {}) {
|
|
|
1620
1656
|
break;
|
|
1621
1657
|
}
|
|
1622
1658
|
let event;
|
|
1623
|
-
const resolver = getConfig(getNamespace(target)).event?.resolver;
|
|
1659
|
+
const resolver = getConfig(getNamespace(target) || '').event?.resolver;
|
|
1624
1660
|
event ||= resolver?.({ detail, element, framework, options, type });
|
|
1625
1661
|
event && element.dispatchEvent(event);
|
|
1626
1662
|
event ||= dispatch(this, type, { ...options, detail });
|
|
@@ -1653,7 +1689,7 @@ function IsRTL() {
|
|
|
1653
1689
|
* for the event listener.
|
|
1654
1690
|
*/
|
|
1655
1691
|
function Listen(type, options) {
|
|
1656
|
-
return
|
|
1692
|
+
return (target, key, descriptor) => {
|
|
1657
1693
|
const element = (instance) => {
|
|
1658
1694
|
switch (options?.target) {
|
|
1659
1695
|
case 'body':
|
|
@@ -1683,7 +1719,7 @@ function Listen(type, options) {
|
|
|
1683
1719
|
* and invoke it as needed, both internally and externally.
|
|
1684
1720
|
*/
|
|
1685
1721
|
function Method() {
|
|
1686
|
-
return
|
|
1722
|
+
return (target, key, _descriptor) => {
|
|
1687
1723
|
wrapMethod('before', target, LIFECYCLE_CONSTRUCTED, function () {
|
|
1688
1724
|
host(this)[key] = this[key].bind(this);
|
|
1689
1725
|
});
|
|
@@ -1720,17 +1756,18 @@ const getMedias = (breakpoints) => {
|
|
|
1720
1756
|
}, {});
|
|
1721
1757
|
};
|
|
1722
1758
|
const matchContainer = (element, container) => {
|
|
1723
|
-
const $element = element;
|
|
1724
1759
|
const getData = () => {
|
|
1725
|
-
if (
|
|
1726
|
-
return
|
|
1760
|
+
if (element[CONTAINER_DATA])
|
|
1761
|
+
return element[CONTAINER_DATA];
|
|
1727
1762
|
const listeners = new Set();
|
|
1728
1763
|
const observer = new ResizeObserver(() => {
|
|
1729
|
-
listeners.forEach((listener) =>
|
|
1764
|
+
listeners.forEach((listener) => {
|
|
1765
|
+
listener();
|
|
1766
|
+
});
|
|
1730
1767
|
});
|
|
1731
1768
|
observer.observe(element);
|
|
1732
|
-
|
|
1733
|
-
return
|
|
1769
|
+
element[CONTAINER_DATA] = { listeners, observer };
|
|
1770
|
+
return element[CONTAINER_DATA];
|
|
1734
1771
|
};
|
|
1735
1772
|
const getMatches = () => {
|
|
1736
1773
|
const width = element.offsetWidth;
|
|
@@ -1738,16 +1775,16 @@ const matchContainer = (element, container) => {
|
|
|
1738
1775
|
(container.max === undefined || width <= container.max);
|
|
1739
1776
|
return matches;
|
|
1740
1777
|
};
|
|
1741
|
-
const addEventListener = (
|
|
1778
|
+
const addEventListener = (_type, listener) => {
|
|
1742
1779
|
getData().listeners.add(listener);
|
|
1743
1780
|
};
|
|
1744
|
-
const removeEventListener = (
|
|
1781
|
+
const removeEventListener = (_type, listener) => {
|
|
1745
1782
|
const data = getData();
|
|
1746
1783
|
data.listeners.delete(listener);
|
|
1747
1784
|
if (data.listeners.size !== 0)
|
|
1748
1785
|
return;
|
|
1749
1786
|
data.observer.disconnect();
|
|
1750
|
-
delete
|
|
1787
|
+
delete element[CONTAINER_DATA];
|
|
1751
1788
|
};
|
|
1752
1789
|
return {
|
|
1753
1790
|
get matches() {
|
|
@@ -1758,29 +1795,35 @@ const matchContainer = (element, container) => {
|
|
|
1758
1795
|
};
|
|
1759
1796
|
};
|
|
1760
1797
|
function Overrides() {
|
|
1761
|
-
return
|
|
1798
|
+
return (target, key) => {
|
|
1762
1799
|
const DISPOSERS = Symbol();
|
|
1763
|
-
const breakpoints = getConfig(getNamespace(target)).breakpoints || {};
|
|
1800
|
+
const breakpoints = getConfig(getNamespace(target) || '').breakpoints || {};
|
|
1764
1801
|
const containers = getContainers(breakpoints);
|
|
1765
1802
|
const medias = getMedias(breakpoints);
|
|
1766
1803
|
wrapMethod('after', target, LIFECYCLE_UPDATE, function (states) {
|
|
1767
1804
|
if (!states.has(key))
|
|
1768
1805
|
return;
|
|
1769
|
-
|
|
1806
|
+
this[DISPOSERS] ??= new Map();
|
|
1807
|
+
const disposers = this[DISPOSERS];
|
|
1770
1808
|
const overrides = this[key] || {};
|
|
1771
1809
|
const activeKeys = new Set(disposers.keys());
|
|
1772
1810
|
const overrideKeys = Object.keys(overrides);
|
|
1773
1811
|
const containerKeys = overrideKeys.filter((breakpoint) => breakpoint in containers);
|
|
1774
1812
|
const mediaKeys = overrideKeys.filter((breakpoint) => breakpoint in medias);
|
|
1775
|
-
let timeout;
|
|
1776
1813
|
let next = {};
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
Object.assign(next, overrides[
|
|
1780
|
-
|
|
1781
|
-
|
|
1814
|
+
let scheduled = false;
|
|
1815
|
+
const apply = (overrideKey) => {
|
|
1816
|
+
overrideKey && Object.assign(next, overrides[overrideKey]);
|
|
1817
|
+
if (scheduled)
|
|
1818
|
+
return;
|
|
1819
|
+
scheduled = true;
|
|
1820
|
+
queueMicrotask(() => {
|
|
1821
|
+
scheduled = false;
|
|
1822
|
+
const defaults = Object.assign({}, this[API_DEFAULTS], next);
|
|
1823
|
+
delete defaults[key];
|
|
1824
|
+
Object.assign(host(this), defaults);
|
|
1782
1825
|
next = {};
|
|
1783
|
-
}
|
|
1826
|
+
});
|
|
1784
1827
|
};
|
|
1785
1828
|
for (const overrideKey of overrideKeys) {
|
|
1786
1829
|
if (activeKeys.delete(overrideKey))
|
|
@@ -1800,6 +1843,7 @@ function Overrides() {
|
|
|
1800
1843
|
apply(containerKey);
|
|
1801
1844
|
}
|
|
1802
1845
|
}
|
|
1846
|
+
apply();
|
|
1803
1847
|
};
|
|
1804
1848
|
containerQueryList.addEventListener('change', change);
|
|
1805
1849
|
const disposer = () => {
|
|
@@ -1822,6 +1866,7 @@ function Overrides() {
|
|
|
1822
1866
|
apply(mediaKey);
|
|
1823
1867
|
}
|
|
1824
1868
|
}
|
|
1869
|
+
apply();
|
|
1825
1870
|
};
|
|
1826
1871
|
mediaQueryList.addEventListener('change', change);
|
|
1827
1872
|
const disposer = () => {
|
|
@@ -1837,13 +1882,16 @@ function Overrides() {
|
|
|
1837
1882
|
}
|
|
1838
1883
|
for (const activeKey of activeKeys) {
|
|
1839
1884
|
const disposer = disposers.get(activeKey);
|
|
1840
|
-
disposer();
|
|
1885
|
+
disposer?.();
|
|
1841
1886
|
disposers.delete(activeKey);
|
|
1842
1887
|
}
|
|
1843
1888
|
});
|
|
1844
1889
|
wrapMethod('after', target, LIFECYCLE_DISCONNECTED, function () {
|
|
1845
|
-
|
|
1846
|
-
disposers
|
|
1890
|
+
this[DISPOSERS] ??= new Map();
|
|
1891
|
+
const disposers = this[DISPOSERS];
|
|
1892
|
+
disposers.forEach((disposer) => {
|
|
1893
|
+
disposer();
|
|
1894
|
+
});
|
|
1847
1895
|
disposers.clear();
|
|
1848
1896
|
});
|
|
1849
1897
|
};
|
|
@@ -1854,7 +1902,7 @@ function Overrides() {
|
|
|
1854
1902
|
* and updates the element when the property is set.
|
|
1855
1903
|
*/
|
|
1856
1904
|
function Property(options) {
|
|
1857
|
-
return
|
|
1905
|
+
return (target, key, descriptor) => {
|
|
1858
1906
|
// Unique symbol for property storage to avoid naming conflicts
|
|
1859
1907
|
const KEY = Symbol();
|
|
1860
1908
|
// Unique symbol for the lock flag to prevent infinite loops during updates
|
|
@@ -1863,8 +1911,12 @@ function Property(options) {
|
|
|
1863
1911
|
const attribute = options?.attribute || kebabCase(key);
|
|
1864
1912
|
// Store the original setter (if it exists) to preserve its behavior
|
|
1865
1913
|
const originalSetter = descriptor?.set;
|
|
1914
|
+
// Ensure the element class has an observedAttributes array
|
|
1915
|
+
// biome-ignore lint: TODO
|
|
1916
|
+
target.constructor['observedAttributes'] ||= [];
|
|
1866
1917
|
// Register the attribute in the observedAttributes array for the element
|
|
1867
|
-
|
|
1918
|
+
// biome-ignore lint: TODO
|
|
1919
|
+
target.constructor['observedAttributes'].push(attribute);
|
|
1868
1920
|
// Getter function to retrieve the property value
|
|
1869
1921
|
function get() {
|
|
1870
1922
|
return this[KEY];
|
|
@@ -1914,7 +1966,7 @@ function Property(options) {
|
|
|
1914
1966
|
* Define a raw property setter to handle updates that trigger from the `attributeChangedCallback`,
|
|
1915
1967
|
* To intercept and process raw attribute values before they are assigned to the property
|
|
1916
1968
|
*/
|
|
1917
|
-
defineProperty(target,
|
|
1969
|
+
defineProperty(target, `RAW:${attribute}`, {
|
|
1918
1970
|
set(value) {
|
|
1919
1971
|
if (!this[LOCKED]) {
|
|
1920
1972
|
// Convert the raw value and set it to the corresponding property
|
|
@@ -1922,6 +1974,11 @@ function Property(options) {
|
|
|
1922
1974
|
}
|
|
1923
1975
|
}
|
|
1924
1976
|
});
|
|
1977
|
+
// TODO
|
|
1978
|
+
wrapMethod('before', target, LIFECYCLE_CONNECTED, function () {
|
|
1979
|
+
this[API_DEFAULTS] ||= {};
|
|
1980
|
+
this[API_DEFAULTS][key] = this[key];
|
|
1981
|
+
});
|
|
1925
1982
|
// Attach getter and setter to the host element on construction
|
|
1926
1983
|
wrapMethod('before', target, LIFECYCLE_CONSTRUCTED, function () {
|
|
1927
1984
|
const get = () => {
|
|
@@ -2001,7 +2058,7 @@ function Slots() {
|
|
|
2001
2058
|
* element to re-render upon the desired property changes.
|
|
2002
2059
|
*/
|
|
2003
2060
|
function State() {
|
|
2004
|
-
return
|
|
2061
|
+
return (target, key) => {
|
|
2005
2062
|
const KEY = Symbol();
|
|
2006
2063
|
const name = String(key);
|
|
2007
2064
|
defineProperty(target, key, {
|
|
@@ -2023,7 +2080,7 @@ function State() {
|
|
|
2023
2080
|
|
|
2024
2081
|
// TODO: check the logic
|
|
2025
2082
|
function Style() {
|
|
2026
|
-
return
|
|
2083
|
+
return (target, key) => {
|
|
2027
2084
|
const LAST = Symbol();
|
|
2028
2085
|
const SHEET = Symbol();
|
|
2029
2086
|
wrapMethod('before', target, LIFECYCLE_UPDATED, function () {
|
|
@@ -2044,6 +2101,7 @@ function Style() {
|
|
|
2044
2101
|
value = value.call(this);
|
|
2045
2102
|
}
|
|
2046
2103
|
if (value instanceof Promise) {
|
|
2104
|
+
// biome-ignore lint: TODO
|
|
2047
2105
|
value.then(update((this[LAST] = value))).catch((error) => {
|
|
2048
2106
|
throw new Error('TODO', { cause: error });
|
|
2049
2107
|
});
|
|
@@ -2055,7 +2113,7 @@ function Style() {
|
|
|
2055
2113
|
};
|
|
2056
2114
|
}
|
|
2057
2115
|
const toCssString = (input, parent) => {
|
|
2058
|
-
if (typeof input
|
|
2116
|
+
if (typeof input === 'string') {
|
|
2059
2117
|
return input.trim();
|
|
2060
2118
|
}
|
|
2061
2119
|
if (Array.isArray(input)) {
|
|
@@ -2064,7 +2122,9 @@ const toCssString = (input, parent) => {
|
|
|
2064
2122
|
.filter(Boolean)
|
|
2065
2123
|
.join('\n');
|
|
2066
2124
|
}
|
|
2067
|
-
if (
|
|
2125
|
+
if (input === null)
|
|
2126
|
+
return '';
|
|
2127
|
+
if (typeof input !== 'object')
|
|
2068
2128
|
return '';
|
|
2069
2129
|
let result = '';
|
|
2070
2130
|
for (const key of Object.keys(input)) {
|
|
@@ -2083,6 +2143,23 @@ const toCssString = (input, parent) => {
|
|
|
2083
2143
|
return parent ? result : `:host {${result}}`;
|
|
2084
2144
|
};
|
|
2085
2145
|
|
|
2146
|
+
function Variant() {
|
|
2147
|
+
return (target, key) => {
|
|
2148
|
+
wrapMethod('after', target, LIFECYCLE_UPDATE, function (states) {
|
|
2149
|
+
if (!states.has(key))
|
|
2150
|
+
return;
|
|
2151
|
+
const namespace = getNamespace(target) || '';
|
|
2152
|
+
const tag = getTag(this) || '';
|
|
2153
|
+
const properties = getConfig(namespace).elements?.[tag]?.variants?.[this[key]]?.properties;
|
|
2154
|
+
if (!properties)
|
|
2155
|
+
return;
|
|
2156
|
+
const defaults = Object.assign({}, this[API_DEFAULTS], properties);
|
|
2157
|
+
delete defaults[key];
|
|
2158
|
+
Object.assign(this, defaults);
|
|
2159
|
+
});
|
|
2160
|
+
};
|
|
2161
|
+
}
|
|
2162
|
+
|
|
2086
2163
|
/**
|
|
2087
2164
|
* Monitors `@Property` and `@State` to detect changes.
|
|
2088
2165
|
* The decorated method will be called after any changes,
|
|
@@ -2093,7 +2170,7 @@ const toCssString = (input, parent) => {
|
|
|
2093
2170
|
* @param immediate Triggers the callback immediately after initialization.
|
|
2094
2171
|
*/
|
|
2095
2172
|
function Watch(keys, immediate) {
|
|
2096
|
-
return
|
|
2173
|
+
return (target, key) => {
|
|
2097
2174
|
// Gets all keys
|
|
2098
2175
|
const all = [keys].flat().filter((item) => item);
|
|
2099
2176
|
// Registers a lifecycle to detect changes.
|
|
@@ -2117,4 +2194,4 @@ const attributes = attributes$2;
|
|
|
2117
2194
|
const html = html$1;
|
|
2118
2195
|
const styles = styles$1;
|
|
2119
2196
|
|
|
2120
|
-
export { Bind, Consumer, Debounce, Direction, Element, Event, Host, IsRTL, Listen, Method, Overrides, Property, Provider, Query, QueryAll, Slots, State, Style, Watch, attributes as a, classes, direction, dispatch, getConfig, getConfigCreator, html as h, host, isCSSColor, isRTL, off, on, query, queryAll, styles as s, setConfig, setConfigCreator, slots, toCSSColor, toCSSUnit, toUnit };
|
|
2197
|
+
export { Bind, Consumer, Debounce, Direction, Element, Event, Host, IsRTL, Listen, Method, Overrides, Property, Provider, Query, QueryAll, Slots, State, Style, Variant, Watch, attributes as a, classes, direction, dispatch, getConfig, getConfigCreator, html as h, host, isCSSColor, isRTL, off, on, query, queryAll, styles as s, setConfig, setConfigCreator, slots, toCSSColor, toCSSUnit, toUnit };
|