@htmlplus/element 3.2.6 → 3.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bundlers.d.ts +5 -5
- package/dist/bundlers.js +4 -4
- package/dist/client.d.ts +71 -23
- package/dist/client.js +873 -624
- package/dist/jsx-runtime.d.ts +183 -183
- package/dist/transformer.d.ts +45 -40
- package/dist/transformer.js +375 -280
- 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, 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 } 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) {
|
|
@@ -163,18 +170,11 @@ const classes = (input, smart) => {
|
|
|
163
170
|
return result.filter((item) => item).join(' ');
|
|
164
171
|
};
|
|
165
172
|
|
|
166
|
-
/**
|
|
167
|
-
* Indicates whether the current code is running on a server.
|
|
168
|
-
*/
|
|
169
|
-
const isServer = () => {
|
|
170
|
-
return !(typeof window != 'undefined' && window.document);
|
|
171
|
-
};
|
|
172
|
-
|
|
173
173
|
const merge = (target, ...sources) => {
|
|
174
174
|
for (const source of sources) {
|
|
175
175
|
if (!source)
|
|
176
176
|
continue;
|
|
177
|
-
if (typeOf(source)
|
|
177
|
+
if (typeOf(source) !== 'object') {
|
|
178
178
|
target = source;
|
|
179
179
|
continue;
|
|
180
180
|
}
|
|
@@ -192,31 +192,31 @@ const merge = (target, ...sources) => {
|
|
|
192
192
|
return target;
|
|
193
193
|
};
|
|
194
194
|
|
|
195
|
-
|
|
196
|
-
|
|
195
|
+
/**
|
|
196
|
+
* TODO
|
|
197
|
+
*/
|
|
198
|
+
const getConfig = (namespace) => {
|
|
199
|
+
return globalThis[`$htmlplus:${namespace}$`] || {};
|
|
197
200
|
};
|
|
198
201
|
/**
|
|
199
202
|
* TODO
|
|
200
203
|
*/
|
|
201
|
-
const
|
|
202
|
-
|
|
203
|
-
return;
|
|
204
|
-
let config = window[`$htmlplus$`];
|
|
205
|
-
for (const key of keys) {
|
|
206
|
-
if (!config)
|
|
207
|
-
break;
|
|
208
|
-
config = config[key];
|
|
209
|
-
}
|
|
210
|
-
return config;
|
|
204
|
+
const getConfigCreator = (namespace) => () => {
|
|
205
|
+
return getConfig(namespace);
|
|
211
206
|
};
|
|
212
207
|
/**
|
|
213
208
|
* TODO
|
|
214
209
|
*/
|
|
215
|
-
const setConfig = (config, options) => {
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
210
|
+
const setConfig = (namespace, config, options) => {
|
|
211
|
+
const previous = options?.override ? {} : globalThis[`$htmlplus:${namespace}$`];
|
|
212
|
+
const next = merge({}, previous, config);
|
|
213
|
+
globalThis[`$htmlplus:${namespace}$`] = next;
|
|
214
|
+
};
|
|
215
|
+
/**
|
|
216
|
+
* TODO
|
|
217
|
+
*/
|
|
218
|
+
const setConfigCreator = (namespace) => (config, options) => {
|
|
219
|
+
return setConfig(namespace, config, options);
|
|
220
220
|
};
|
|
221
221
|
|
|
222
222
|
const defineProperty = Object.defineProperty;
|
|
@@ -253,6 +253,10 @@ const getTag = (target) => {
|
|
|
253
253
|
return target.constructor[STATIC_TAG] ?? target[STATIC_TAG];
|
|
254
254
|
};
|
|
255
255
|
|
|
256
|
+
const getNamespace = (target) => {
|
|
257
|
+
return getTag(target)?.split('-')?.at(0);
|
|
258
|
+
};
|
|
259
|
+
|
|
256
260
|
/**
|
|
257
261
|
* Determines whether the given input string is a valid
|
|
258
262
|
* [CSS Color](https://mdn.io/color-value) or not.
|
|
@@ -266,7 +270,16 @@ const isCSSColor = (input) => {
|
|
|
266
270
|
/**
|
|
267
271
|
* Indicates whether the direction of the element is `Right-To-Left` or not.
|
|
268
272
|
*/
|
|
269
|
-
const isRTL = (target) =>
|
|
273
|
+
const isRTL = (target) => {
|
|
274
|
+
return direction(target) === 'rtl';
|
|
275
|
+
};
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Indicates whether the current code is running on a server.
|
|
279
|
+
*/
|
|
280
|
+
const isServer = () => {
|
|
281
|
+
return !(typeof window !== 'undefined' && window.document);
|
|
282
|
+
};
|
|
270
283
|
|
|
271
284
|
const shadowRoot = (target) => {
|
|
272
285
|
return host(target)?.shadowRoot;
|
|
@@ -287,7 +300,8 @@ function queryAll(target, selectors) {
|
|
|
287
300
|
}
|
|
288
301
|
|
|
289
302
|
const task = (options) => {
|
|
290
|
-
let running
|
|
303
|
+
let running;
|
|
304
|
+
let promise;
|
|
291
305
|
const run = () => {
|
|
292
306
|
if (options.canStart && !options.canStart())
|
|
293
307
|
return Promise.resolve(false);
|
|
@@ -307,8 +321,10 @@ const task = (options) => {
|
|
|
307
321
|
if (!running)
|
|
308
322
|
return promise;
|
|
309
323
|
try {
|
|
310
|
-
if (options.canRun && !options.canRun())
|
|
311
|
-
|
|
324
|
+
if (options.canRun && !options.canRun()) {
|
|
325
|
+
running = false;
|
|
326
|
+
return running;
|
|
327
|
+
}
|
|
312
328
|
options.handler();
|
|
313
329
|
running = false;
|
|
314
330
|
return true;
|
|
@@ -321,23 +337,25 @@ const task = (options) => {
|
|
|
321
337
|
return run;
|
|
322
338
|
};
|
|
323
339
|
|
|
340
|
+
// biome-ignore-all lint: TODO
|
|
341
|
+
|
|
324
342
|
class MapSet extends Map {
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
343
|
+
set(key, value) {
|
|
344
|
+
super.set(key, value);
|
|
345
|
+
return value;
|
|
346
|
+
}
|
|
329
347
|
}
|
|
330
348
|
|
|
331
349
|
class WeakMapSet extends WeakMap {
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
350
|
+
set(key, value) {
|
|
351
|
+
super.set(key, value);
|
|
352
|
+
return value;
|
|
353
|
+
}
|
|
336
354
|
}
|
|
337
355
|
|
|
338
356
|
/*! (c) Andrea Giammarchi - ISC */
|
|
339
357
|
const empty =
|
|
340
|
-
|
|
358
|
+
/^(?:area|base|br|col|embed|hr|img|input|keygen|link|menuitem|meta|param|source|track|wbr)$/i;
|
|
341
359
|
const elements = /<([a-z]+[a-z0-9:._-]*)([^>]*?)(\/?)>/g;
|
|
342
360
|
const attributes$1 = /([^\s\\>"'=]+)\s*=\s*(['"]?)\x01/g;
|
|
343
361
|
const holes = /[\x01\x02]/g;
|
|
@@ -354,155 +372,155 @@ const holes = /[\x01\x02]/g;
|
|
|
354
372
|
* @returns {string} X/HTML with prefixed comments or attributes
|
|
355
373
|
*/
|
|
356
374
|
var instrument = (template, prefix, svg) => {
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
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++));
|
|
367
385
|
};
|
|
368
386
|
|
|
369
387
|
const ELEMENT_NODE = 1;
|
|
370
388
|
const nodeType = 111;
|
|
371
389
|
|
|
372
390
|
const remove = ({ firstChild, lastChild }) => {
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
391
|
+
const range = document.createRange();
|
|
392
|
+
range.setStartAfter(firstChild);
|
|
393
|
+
range.setEndAfter(lastChild);
|
|
394
|
+
range.deleteContents();
|
|
395
|
+
return firstChild;
|
|
378
396
|
};
|
|
379
397
|
|
|
380
398
|
const diffable = (node, operation) =>
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
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;
|
|
390
408
|
const persistent = (fragment) => {
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
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
|
+
};
|
|
405
423
|
};
|
|
406
424
|
|
|
407
425
|
const { isArray: isArray$1 } = Array;
|
|
408
426
|
|
|
409
427
|
const aria = (node) => (values) => {
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
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
|
+
}
|
|
416
434
|
};
|
|
417
435
|
|
|
418
436
|
const attribute = (node, name) => {
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
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
|
+
};
|
|
445
463
|
};
|
|
446
464
|
|
|
447
465
|
const boolean = (node, key, oldValue) => (newValue) => {
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
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
|
+
}
|
|
454
472
|
};
|
|
455
473
|
|
|
456
474
|
const data =
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
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
|
+
};
|
|
465
483
|
|
|
466
484
|
const event = (node, name) => {
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
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
|
+
};
|
|
478
496
|
};
|
|
479
497
|
|
|
480
498
|
const ref = (node) => {
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
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
|
+
};
|
|
489
507
|
};
|
|
490
508
|
|
|
491
509
|
const setter = (node, key) =>
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
510
|
+
key === 'dataset'
|
|
511
|
+
? data(node)
|
|
512
|
+
: (value) => {
|
|
513
|
+
node[key] = value;
|
|
514
|
+
};
|
|
497
515
|
|
|
498
516
|
const text = (node) => {
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
517
|
+
let oldValue;
|
|
518
|
+
return (newValue) => {
|
|
519
|
+
if (oldValue != newValue) {
|
|
520
|
+
oldValue = newValue;
|
|
521
|
+
node.textContent = newValue == null ? '' : newValue;
|
|
522
|
+
}
|
|
523
|
+
};
|
|
506
524
|
};
|
|
507
525
|
|
|
508
526
|
/**
|
|
@@ -533,148 +551,148 @@ const text = (node) => {
|
|
|
533
551
|
* @returns {Node[]} The same list of future children.
|
|
534
552
|
*/
|
|
535
553
|
var udomdiff = (parentNode, a, b, get, before) => {
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
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
|
-
|
|
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;
|
|
649
667
|
};
|
|
650
668
|
|
|
651
669
|
const { isArray, prototype } = Array;
|
|
652
670
|
const { indexOf } = prototype;
|
|
653
671
|
|
|
654
672
|
const {
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
673
|
+
createDocumentFragment,
|
|
674
|
+
createElement,
|
|
675
|
+
createElementNS,
|
|
676
|
+
createTextNode,
|
|
677
|
+
createTreeWalker,
|
|
678
|
+
importNode
|
|
661
679
|
} = new Proxy(typeof window == 'undefined' ? {} : window.document, {
|
|
662
|
-
|
|
680
|
+
get: (target, method) => (target[method] || function () {}).bind(target)
|
|
663
681
|
});
|
|
664
682
|
|
|
665
683
|
const createHTML = (html) => {
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
684
|
+
const template = createElement('template');
|
|
685
|
+
template.innerHTML = html;
|
|
686
|
+
return template.content;
|
|
669
687
|
};
|
|
670
688
|
|
|
671
689
|
let xml;
|
|
672
690
|
const createSVG = (svg) => {
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
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;
|
|
678
696
|
};
|
|
679
697
|
|
|
680
698
|
const createContent = (text, svg) => (svg ? createSVG(text) : createHTML(text));
|
|
@@ -684,91 +702,91 @@ const reducePath = ({ childNodes }, i) => childNodes[i];
|
|
|
684
702
|
|
|
685
703
|
// this helper avoid code bloat around handleAnything() callback
|
|
686
704
|
const diff = (comment, oldNodes, newNodes) =>
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
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
|
+
);
|
|
709
727
|
|
|
710
728
|
// if an interpolation represents a comment, the whole
|
|
711
729
|
// diffing will be related to such comment.
|
|
712
730
|
// This helper is in charge of understanding how the new
|
|
713
731
|
// content for such interpolation/hole should be updated
|
|
714
732
|
const handleAnything = (comment) => {
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
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
|
-
|
|
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;
|
|
772
790
|
};
|
|
773
791
|
|
|
774
792
|
// attributes can be:
|
|
@@ -782,25 +800,25 @@ const handleAnything = (comment) => {
|
|
|
782
800
|
// * onevent=${...} to automatically handle event listeners
|
|
783
801
|
// * generic=${...} to handle an attribute just like an attribute
|
|
784
802
|
const handleAttribute = (node, name /*, svg*/) => {
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
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*/);
|
|
804
822
|
};
|
|
805
823
|
|
|
806
824
|
// each mapped update carries the update type and its path
|
|
@@ -808,27 +826,27 @@ const handleAttribute = (node, name /*, svg*/) => {
|
|
|
808
826
|
// the path is how to retrieve the related node to update.
|
|
809
827
|
// In the attribute case, the attribute name is also carried along.
|
|
810
828
|
function handlers(options) {
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
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);
|
|
818
836
|
}
|
|
819
837
|
|
|
820
838
|
// from a fragment container, create an array of indexes
|
|
821
839
|
// related to its child nodes, so that it's possible
|
|
822
840
|
// to retrieve later on exact node via reducePath
|
|
823
841
|
const createPath = (node) => {
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
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;
|
|
832
850
|
};
|
|
833
851
|
|
|
834
852
|
// the prefix is used to identify either comments, attributes, or nodes
|
|
@@ -850,100 +868,100 @@ const cache$1 = new WeakMapSet();
|
|
|
850
868
|
const textOnly = /^(?:textarea|script|style|title|plaintext|xmp)$/;
|
|
851
869
|
|
|
852
870
|
const createCache = () => ({
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
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
|
|
867
885
|
});
|
|
868
886
|
|
|
869
887
|
// the entry stored in the rendered node cache, and per each "hole"
|
|
870
888
|
const createEntry = (type, template) => {
|
|
871
|
-
|
|
872
|
-
|
|
889
|
+
const { content, updates } = mapUpdates(type, template);
|
|
890
|
+
return { type, template, content, updates, wire: null };
|
|
873
891
|
};
|
|
874
892
|
|
|
875
893
|
// a template is instrumented to be able to retrieve where updates are needed.
|
|
876
894
|
// Each unique template becomes a fragment, cloned once per each other
|
|
877
895
|
// operation based on the same template, i.e. data => html`<p>${data}</p>`
|
|
878
896
|
const mapTemplate = (type, template) => {
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
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
|
-
|
|
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 };
|
|
934
952
|
};
|
|
935
953
|
|
|
936
954
|
// if a template is unknown, perform the previous mapping, otherwise grab
|
|
937
955
|
// its details such as the fragment with all nodes, and updates info.
|
|
938
956
|
const mapUpdates = (type, template) => {
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
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 };
|
|
947
965
|
};
|
|
948
966
|
|
|
949
967
|
// as html and svg can be nested calls, but no parent node is known
|
|
@@ -951,48 +969,48 @@ const mapUpdates = (type, template) => {
|
|
|
951
969
|
// discover what to do with each interpolation, which will result
|
|
952
970
|
// into an update operation.
|
|
953
971
|
const unroll = (info, { type, template, values }) => {
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
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));
|
|
972
990
|
};
|
|
973
991
|
|
|
974
992
|
// the stack retains, per each interpolation value, the cache
|
|
975
993
|
// related to each interpolation value, or null, if the render
|
|
976
994
|
// was conditional and the value is not special (Array or Hole)
|
|
977
995
|
const unrollValues = ({ stack }, values) => {
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
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;
|
|
996
1014
|
};
|
|
997
1015
|
|
|
998
1016
|
/**
|
|
@@ -1003,44 +1021,44 @@ const unrollValues = ({ stack }, values) => {
|
|
|
1003
1021
|
* @param {Array} values Zero, one, or more interpolated values to render.
|
|
1004
1022
|
*/
|
|
1005
1023
|
class Hole {
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1024
|
+
constructor(type, template, values) {
|
|
1025
|
+
this.type = type;
|
|
1026
|
+
this.template = template;
|
|
1027
|
+
this.values = values;
|
|
1028
|
+
}
|
|
1011
1029
|
}
|
|
1012
1030
|
|
|
1013
1031
|
// both `html` and `svg` template literal tags are polluted
|
|
1014
1032
|
// with a `for(ref[, id])` and a `node` tag too
|
|
1015
1033
|
const tag = (type) => {
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
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
|
+
);
|
|
1044
1062
|
};
|
|
1045
1063
|
|
|
1046
1064
|
// each rendered node gets its own cache
|
|
@@ -1052,18 +1070,18 @@ const cache = new WeakMapSet();
|
|
|
1052
1070
|
// and the new new content is appended, and if such content is a Hole
|
|
1053
1071
|
// then it's "unrolled" to resolve all its inner nodes.
|
|
1054
1072
|
const render = (where, what) => {
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
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;
|
|
1067
1085
|
};
|
|
1068
1086
|
|
|
1069
1087
|
const html$1 = tag('html');
|
|
@@ -1077,8 +1095,10 @@ tag('svg');
|
|
|
1077
1095
|
* @param callback Invoked when the rendering phase is completed.
|
|
1078
1096
|
*/
|
|
1079
1097
|
const requestUpdate = (target, name, previous, callback) => {
|
|
1098
|
+
// Ensure API_STACKS exists on target
|
|
1099
|
+
target[API_STACKS] ||= new Map();
|
|
1080
1100
|
// Creates/Gets a stacks.
|
|
1081
|
-
const stacks =
|
|
1101
|
+
const stacks = target[API_STACKS];
|
|
1082
1102
|
// Creates/Updates a stack.
|
|
1083
1103
|
const stack = stacks.get(name) || { callbacks: [], previous };
|
|
1084
1104
|
// Adds the callback to the stack, if exists.
|
|
@@ -1101,7 +1121,7 @@ const requestUpdate = (target, name, previous, callback) => {
|
|
|
1101
1121
|
// Invokes requests' callback.
|
|
1102
1122
|
stacks.forEach((state) => {
|
|
1103
1123
|
state.callbacks.forEach((callback, index, callbacks) => {
|
|
1104
|
-
callback(callbacks.length - 1
|
|
1124
|
+
callback(callbacks.length - 1 !== index);
|
|
1105
1125
|
});
|
|
1106
1126
|
});
|
|
1107
1127
|
// TODO
|
|
@@ -1119,7 +1139,7 @@ const requestUpdate = (target, name, previous, callback) => {
|
|
|
1119
1139
|
if (!hasVariable && localSheet)
|
|
1120
1140
|
return;
|
|
1121
1141
|
const parsed = raw
|
|
1122
|
-
.replace(regex1, (
|
|
1142
|
+
.replace(regex1, (_match, key) => {
|
|
1123
1143
|
let value = target;
|
|
1124
1144
|
for (const section of key.split('-')) {
|
|
1125
1145
|
value = value?.[section];
|
|
@@ -1130,7 +1150,7 @@ const requestUpdate = (target, name, previous, callback) => {
|
|
|
1130
1150
|
if (!localSheet) {
|
|
1131
1151
|
localSheet = new CSSStyleSheet();
|
|
1132
1152
|
target[API_STYLE] = localSheet;
|
|
1133
|
-
shadowRoot(target)
|
|
1153
|
+
shadowRoot(target)?.adoptedStyleSheets.push(localSheet);
|
|
1134
1154
|
}
|
|
1135
1155
|
const localStyle = parsed.replace(regex3, '');
|
|
1136
1156
|
localSheet.replaceSync(localStyle);
|
|
@@ -1145,7 +1165,7 @@ const requestUpdate = (target, name, previous, callback) => {
|
|
|
1145
1165
|
?.match(regex3)
|
|
1146
1166
|
?.join('')
|
|
1147
1167
|
?.replaceAll('global', '')
|
|
1148
|
-
?.replaceAll(':host', getTag(target));
|
|
1168
|
+
?.replaceAll(':host', getTag(target) || '');
|
|
1149
1169
|
globalSheet.replaceSync(globalStyle);
|
|
1150
1170
|
})();
|
|
1151
1171
|
// Calls the lifecycle's callback after the rendering phase.
|
|
@@ -1169,9 +1189,15 @@ const slots = (target) => {
|
|
|
1169
1189
|
const slots = {};
|
|
1170
1190
|
const children = Array.from(element.childNodes);
|
|
1171
1191
|
for (const child of children) {
|
|
1172
|
-
if (child.
|
|
1192
|
+
if (child.nodeType === Node.COMMENT_NODE)
|
|
1173
1193
|
continue;
|
|
1174
|
-
|
|
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
|
+
}
|
|
1175
1201
|
if (!name)
|
|
1176
1202
|
continue;
|
|
1177
1203
|
slots[name] = true;
|
|
@@ -1194,10 +1220,10 @@ const toCSSColor = (input) => {
|
|
|
1194
1220
|
};
|
|
1195
1221
|
|
|
1196
1222
|
const toCSSUnit = (input) => {
|
|
1197
|
-
if (input
|
|
1223
|
+
if (input === undefined || input === null || input === '') {
|
|
1198
1224
|
return;
|
|
1199
1225
|
}
|
|
1200
|
-
if (typeof input === 'number' || !isNaN(Number(input))) {
|
|
1226
|
+
if (typeof input === 'number' || (typeof input === 'string' && !Number.isNaN(Number(input)))) {
|
|
1201
1227
|
return `${input}px`;
|
|
1202
1228
|
}
|
|
1203
1229
|
if (/^\d+(\.\d+)?(px|pt|cm|mm|in|em|rem|%|vw|vh)$/.test(input)) {
|
|
@@ -1206,7 +1232,7 @@ const toCSSUnit = (input) => {
|
|
|
1206
1232
|
};
|
|
1207
1233
|
|
|
1208
1234
|
function toDecorator(util, ...args) {
|
|
1209
|
-
return
|
|
1235
|
+
return (target, key) => {
|
|
1210
1236
|
defineProperty(target, key, {
|
|
1211
1237
|
get() {
|
|
1212
1238
|
return util(this, ...args);
|
|
@@ -1219,7 +1245,8 @@ const toProperty = (input, type) => {
|
|
|
1219
1245
|
if (type === undefined)
|
|
1220
1246
|
return input;
|
|
1221
1247
|
const string = `${input}`;
|
|
1222
|
-
|
|
1248
|
+
const typeNumber = typeof type === 'number' ? type : 0;
|
|
1249
|
+
if (TYPE_BOOLEAN & typeNumber || type === Boolean) {
|
|
1223
1250
|
if (string === '')
|
|
1224
1251
|
return true;
|
|
1225
1252
|
if (string === 'true')
|
|
@@ -1227,46 +1254,55 @@ const toProperty = (input, type) => {
|
|
|
1227
1254
|
if (string === 'false')
|
|
1228
1255
|
return false;
|
|
1229
1256
|
}
|
|
1230
|
-
if (TYPE_NUMBER &
|
|
1231
|
-
if (string
|
|
1232
|
-
return parseFloat(
|
|
1257
|
+
if (TYPE_NUMBER & typeNumber || type === Number) {
|
|
1258
|
+
if (string !== '' && !Number.isNaN(Number(input))) {
|
|
1259
|
+
return parseFloat(string);
|
|
1233
1260
|
}
|
|
1234
1261
|
}
|
|
1235
|
-
if (TYPE_NULL &
|
|
1262
|
+
if (TYPE_NULL & typeNumber || type === null) {
|
|
1236
1263
|
if (string === 'null') {
|
|
1237
1264
|
return null;
|
|
1238
1265
|
}
|
|
1239
1266
|
}
|
|
1240
|
-
if (TYPE_DATE &
|
|
1241
|
-
const value = new Date(
|
|
1242
|
-
if (value.
|
|
1267
|
+
if (TYPE_DATE & typeNumber || type === Date) {
|
|
1268
|
+
const value = new Date(string);
|
|
1269
|
+
if (!Number.isNaN(value.getTime())) {
|
|
1243
1270
|
return value;
|
|
1244
1271
|
}
|
|
1245
1272
|
}
|
|
1246
|
-
if (TYPE_ARRAY &
|
|
1273
|
+
if (TYPE_ARRAY & typeNumber || type === Array) {
|
|
1247
1274
|
try {
|
|
1248
|
-
const value = JSON.parse(
|
|
1249
|
-
if (typeOf(value)
|
|
1275
|
+
const value = JSON.parse(string);
|
|
1276
|
+
if (typeOf(value) === 'array')
|
|
1250
1277
|
return value;
|
|
1251
|
-
}
|
|
1252
1278
|
}
|
|
1253
1279
|
catch { }
|
|
1254
1280
|
}
|
|
1255
|
-
if (TYPE_OBJECT &
|
|
1281
|
+
if (TYPE_OBJECT & typeNumber || type === Object) {
|
|
1256
1282
|
try {
|
|
1257
|
-
const value = JSON.parse(
|
|
1258
|
-
if (typeOf(value)
|
|
1283
|
+
const value = JSON.parse(string);
|
|
1284
|
+
if (typeOf(value) === 'object') {
|
|
1259
1285
|
return value;
|
|
1260
1286
|
}
|
|
1261
1287
|
}
|
|
1262
1288
|
catch { }
|
|
1263
1289
|
}
|
|
1264
|
-
if (TYPE_UNDEFINED &
|
|
1290
|
+
if (TYPE_UNDEFINED & typeNumber) {
|
|
1265
1291
|
if (string === 'undefined') {
|
|
1266
1292
|
return undefined;
|
|
1267
1293
|
}
|
|
1268
1294
|
}
|
|
1269
|
-
|
|
1295
|
+
if (TYPE_STRING & typeNumber || type === String) {
|
|
1296
|
+
return string;
|
|
1297
|
+
}
|
|
1298
|
+
// TODO: BigInt, Enum, Function
|
|
1299
|
+
try {
|
|
1300
|
+
// TODO
|
|
1301
|
+
return JSON.parse(string);
|
|
1302
|
+
}
|
|
1303
|
+
catch {
|
|
1304
|
+
return input;
|
|
1305
|
+
}
|
|
1270
1306
|
};
|
|
1271
1307
|
|
|
1272
1308
|
/**
|
|
@@ -1274,13 +1310,15 @@ const toProperty = (input, type) => {
|
|
|
1274
1310
|
*/
|
|
1275
1311
|
const toUnit = (input, unit = 'px') => {
|
|
1276
1312
|
if (input === null || input === undefined || input === '')
|
|
1277
|
-
return input;
|
|
1278
|
-
if (isNaN(+input))
|
|
1279
1313
|
return String(input);
|
|
1280
|
-
|
|
1314
|
+
if (Number.isNaN(Number(input)))
|
|
1315
|
+
return String(input);
|
|
1316
|
+
return `${Number(input)}${unit}`;
|
|
1281
1317
|
};
|
|
1282
1318
|
|
|
1283
|
-
const wrapMethod = (mode, target, key,
|
|
1319
|
+
const wrapMethod = (mode, target, key,
|
|
1320
|
+
// biome-ignore lint: TODO
|
|
1321
|
+
handler) => {
|
|
1284
1322
|
// Gets the original function
|
|
1285
1323
|
const original = target[key];
|
|
1286
1324
|
// Validate target property
|
|
@@ -1290,13 +1328,13 @@ const wrapMethod = (mode, target, key, handler) => {
|
|
|
1290
1328
|
// Creates new function
|
|
1291
1329
|
function wrapped(...args) {
|
|
1292
1330
|
// Calls the handler before the original
|
|
1293
|
-
if (mode
|
|
1331
|
+
if (mode === 'before') {
|
|
1294
1332
|
handler.apply(this, args);
|
|
1295
1333
|
}
|
|
1296
1334
|
// Calls the original
|
|
1297
1335
|
const result = original?.apply(this, args);
|
|
1298
1336
|
// Calls the handler after the original
|
|
1299
|
-
if (mode
|
|
1337
|
+
if (mode === 'after') {
|
|
1300
1338
|
handler.apply(this, args);
|
|
1301
1339
|
}
|
|
1302
1340
|
// Returns the result
|
|
@@ -1311,7 +1349,7 @@ const wrapMethod = (mode, target, key, handler) => {
|
|
|
1311
1349
|
* making it easier to reference `this` within the method.
|
|
1312
1350
|
*/
|
|
1313
1351
|
function Bind() {
|
|
1314
|
-
return
|
|
1352
|
+
return (_target, key, descriptor) => {
|
|
1315
1353
|
const original = descriptor.value;
|
|
1316
1354
|
return {
|
|
1317
1355
|
configurable: true,
|
|
@@ -1328,8 +1366,9 @@ function Bind() {
|
|
|
1328
1366
|
};
|
|
1329
1367
|
}
|
|
1330
1368
|
|
|
1369
|
+
// biome-ignore-all lint: TODO
|
|
1331
1370
|
function Provider(namespace) {
|
|
1332
|
-
return
|
|
1371
|
+
return (target, key) => {
|
|
1333
1372
|
const symbol = Symbol();
|
|
1334
1373
|
const [MAIN, SUB] = namespace.split('.');
|
|
1335
1374
|
const prefix = `${KEY}:${MAIN}`;
|
|
@@ -1376,12 +1415,14 @@ function Provider(namespace) {
|
|
|
1376
1415
|
cleanups(this).set(type, cleanup);
|
|
1377
1416
|
});
|
|
1378
1417
|
wrapMethod('after', target, LIFECYCLE_DISCONNECTED, function () {
|
|
1379
|
-
cleanups(this).forEach((cleanup) =>
|
|
1418
|
+
cleanups(this).forEach((cleanup) => {
|
|
1419
|
+
cleanup();
|
|
1420
|
+
});
|
|
1380
1421
|
});
|
|
1381
1422
|
};
|
|
1382
1423
|
}
|
|
1383
1424
|
function Consumer(namespace) {
|
|
1384
|
-
return
|
|
1425
|
+
return (target, key) => {
|
|
1385
1426
|
const symbol = Symbol();
|
|
1386
1427
|
const [MAIN, SUB] = namespace.split('.');
|
|
1387
1428
|
const prefix = `${KEY}:${MAIN}`;
|
|
@@ -1397,7 +1438,7 @@ function Consumer(namespace) {
|
|
|
1397
1438
|
if (SUB && this[SUB])
|
|
1398
1439
|
return;
|
|
1399
1440
|
// TODO
|
|
1400
|
-
let connected;
|
|
1441
|
+
let connected = false;
|
|
1401
1442
|
const options = {
|
|
1402
1443
|
bubbles: true
|
|
1403
1444
|
};
|
|
@@ -1443,7 +1484,9 @@ function Consumer(namespace) {
|
|
|
1443
1484
|
dispatch(window, `${type}:presence`);
|
|
1444
1485
|
});
|
|
1445
1486
|
wrapMethod('after', target, LIFECYCLE_DISCONNECTED, function () {
|
|
1446
|
-
cleanups(this).forEach((cleanup) =>
|
|
1487
|
+
cleanups(this).forEach((cleanup) => {
|
|
1488
|
+
cleanup();
|
|
1489
|
+
});
|
|
1447
1490
|
});
|
|
1448
1491
|
};
|
|
1449
1492
|
}
|
|
@@ -1456,7 +1499,7 @@ function Consumer(namespace) {
|
|
|
1456
1499
|
* @param {number} delay - The debounce delay in milliseconds.
|
|
1457
1500
|
*/
|
|
1458
1501
|
function Debounce(delay = 0) {
|
|
1459
|
-
return
|
|
1502
|
+
return (target, key, descriptor) => {
|
|
1460
1503
|
const KEY = Symbol();
|
|
1461
1504
|
const original = descriptor.value;
|
|
1462
1505
|
function clear() {
|
|
@@ -1491,27 +1534,36 @@ function Direction() {
|
|
|
1491
1534
|
* and its name, in kebab-case, serves as the element name.
|
|
1492
1535
|
*/
|
|
1493
1536
|
function Element() {
|
|
1494
|
-
|
|
1537
|
+
// biome-ignore lint: TODO
|
|
1538
|
+
return (constructor) => {
|
|
1495
1539
|
if (isServer())
|
|
1496
1540
|
return;
|
|
1497
1541
|
const tag = getTag(constructor);
|
|
1542
|
+
if (!tag)
|
|
1543
|
+
return;
|
|
1498
1544
|
if (customElements.get(tag))
|
|
1499
1545
|
return;
|
|
1500
1546
|
customElements.define(tag, proxy(constructor));
|
|
1501
1547
|
};
|
|
1502
1548
|
}
|
|
1549
|
+
// biome-ignore lint: TODO
|
|
1503
1550
|
const proxy = (constructor) => {
|
|
1504
1551
|
return class Plus extends HTMLElement {
|
|
1505
1552
|
#instance;
|
|
1553
|
+
// biome-ignore lint: TODO
|
|
1506
1554
|
static formAssociated = constructor['formAssociated'];
|
|
1555
|
+
// biome-ignore lint: TODO
|
|
1507
1556
|
static observedAttributes = constructor['observedAttributes'];
|
|
1508
1557
|
constructor() {
|
|
1509
1558
|
super();
|
|
1510
1559
|
this.attachShadow({
|
|
1511
1560
|
mode: 'open',
|
|
1561
|
+
// biome-ignore lint: TODO
|
|
1512
1562
|
delegatesFocus: constructor['delegatesFocus'],
|
|
1563
|
+
// biome-ignore lint: TODO
|
|
1513
1564
|
slotAssignment: constructor['slotAssignment']
|
|
1514
1565
|
});
|
|
1566
|
+
// biome-ignore lint: TODO
|
|
1515
1567
|
this.#instance = new constructor();
|
|
1516
1568
|
this.#instance[API_HOST] = () => this;
|
|
1517
1569
|
call(this.#instance, LIFECYCLE_CONSTRUCTED);
|
|
@@ -1520,13 +1572,37 @@ const proxy = (constructor) => {
|
|
|
1520
1572
|
call(this.#instance, LIFECYCLE_ADOPTED);
|
|
1521
1573
|
}
|
|
1522
1574
|
attributeChangedCallback(key, prev, next) {
|
|
1523
|
-
if (prev
|
|
1524
|
-
this.#instance[
|
|
1575
|
+
if (prev !== next) {
|
|
1576
|
+
this.#instance[`RAW:${key}`] = next;
|
|
1525
1577
|
}
|
|
1526
1578
|
}
|
|
1527
1579
|
connectedCallback() {
|
|
1528
1580
|
// TODO: experimental for global config
|
|
1529
|
-
|
|
1581
|
+
(() => {
|
|
1582
|
+
const namespace = getNamespace(this.#instance) || '';
|
|
1583
|
+
const tag = getTag(this.#instance) || '';
|
|
1584
|
+
const properties = getConfig(namespace).elements?.[tag]?.properties;
|
|
1585
|
+
if (!properties)
|
|
1586
|
+
return;
|
|
1587
|
+
const defaults = Object.fromEntries(Object.entries(properties).map(([key, value]) => [key, value?.default]));
|
|
1588
|
+
Object.assign(this, defaults);
|
|
1589
|
+
})();
|
|
1590
|
+
// TODO
|
|
1591
|
+
(() => {
|
|
1592
|
+
const key = Object.keys(this).find((key) => key.startsWith('__reactProps'));
|
|
1593
|
+
const props = this[key];
|
|
1594
|
+
if (!props)
|
|
1595
|
+
return;
|
|
1596
|
+
for (const [key, value] of Object.entries(props)) {
|
|
1597
|
+
if (this[key] !== undefined)
|
|
1598
|
+
continue;
|
|
1599
|
+
if (key === 'children')
|
|
1600
|
+
continue;
|
|
1601
|
+
if (typeof value !== 'object')
|
|
1602
|
+
continue;
|
|
1603
|
+
this[key] = value;
|
|
1604
|
+
}
|
|
1605
|
+
})();
|
|
1530
1606
|
this.#instance[API_CONNECTED] = true;
|
|
1531
1607
|
call(this.#instance, LIFECYCLE_CONNECTED);
|
|
1532
1608
|
requestUpdate(this.#instance, undefined, undefined, () => {
|
|
@@ -1546,7 +1622,7 @@ const proxy = (constructor) => {
|
|
|
1546
1622
|
* @param options An object that configures options for the event dispatcher.
|
|
1547
1623
|
*/
|
|
1548
1624
|
function Event(options = {}) {
|
|
1549
|
-
return
|
|
1625
|
+
return (target, key) => {
|
|
1550
1626
|
target[key] = function (detail) {
|
|
1551
1627
|
const element = host(this);
|
|
1552
1628
|
const framework = getFramework(this);
|
|
@@ -1558,6 +1634,7 @@ function Event(options = {}) {
|
|
|
1558
1634
|
options.bubbles = true;
|
|
1559
1635
|
type = pascalCase(type);
|
|
1560
1636
|
try {
|
|
1637
|
+
// biome-ignore lint: TODO
|
|
1561
1638
|
window['Blazor'].registerCustomEventType(type, {
|
|
1562
1639
|
createEventArgs: (event) => ({
|
|
1563
1640
|
detail: event.detail
|
|
@@ -1579,7 +1656,8 @@ function Event(options = {}) {
|
|
|
1579
1656
|
break;
|
|
1580
1657
|
}
|
|
1581
1658
|
let event;
|
|
1582
|
-
|
|
1659
|
+
const resolver = getConfig(getNamespace(target) || '').event?.resolver;
|
|
1660
|
+
event ||= resolver?.({ detail, element, framework, options, type });
|
|
1583
1661
|
event && element.dispatchEvent(event);
|
|
1584
1662
|
event ||= dispatch(this, type, { ...options, detail });
|
|
1585
1663
|
return event;
|
|
@@ -1611,7 +1689,7 @@ function IsRTL() {
|
|
|
1611
1689
|
* for the event listener.
|
|
1612
1690
|
*/
|
|
1613
1691
|
function Listen(type, options) {
|
|
1614
|
-
return
|
|
1692
|
+
return (target, key, descriptor) => {
|
|
1615
1693
|
const element = (instance) => {
|
|
1616
1694
|
switch (options?.target) {
|
|
1617
1695
|
case 'body':
|
|
@@ -1641,19 +1719,183 @@ function Listen(type, options) {
|
|
|
1641
1719
|
* and invoke it as needed, both internally and externally.
|
|
1642
1720
|
*/
|
|
1643
1721
|
function Method() {
|
|
1644
|
-
return
|
|
1722
|
+
return (target, key, _descriptor) => {
|
|
1645
1723
|
wrapMethod('before', target, LIFECYCLE_CONSTRUCTED, function () {
|
|
1646
1724
|
host(this)[key] = this[key].bind(this);
|
|
1647
1725
|
});
|
|
1648
1726
|
};
|
|
1649
1727
|
}
|
|
1650
1728
|
|
|
1729
|
+
const CONTAINER_DATA = Symbol();
|
|
1730
|
+
const getContainers = (breakpoints) => {
|
|
1731
|
+
return Object.entries(breakpoints || {}).reduce((result, [key, breakpoint]) => {
|
|
1732
|
+
if (breakpoint.type !== 'container')
|
|
1733
|
+
return result;
|
|
1734
|
+
result[key] = {
|
|
1735
|
+
min: breakpoint.min,
|
|
1736
|
+
max: breakpoint.max
|
|
1737
|
+
};
|
|
1738
|
+
return result;
|
|
1739
|
+
}, {});
|
|
1740
|
+
};
|
|
1741
|
+
const getMedias = (breakpoints) => {
|
|
1742
|
+
return Object.entries(breakpoints || {}).reduce((result, [key, breakpoint]) => {
|
|
1743
|
+
if (breakpoint.type !== 'media')
|
|
1744
|
+
return result;
|
|
1745
|
+
const parts = [];
|
|
1746
|
+
const min = 'min' in breakpoint ? breakpoint.min : undefined;
|
|
1747
|
+
const max = 'max' in breakpoint ? breakpoint.max : undefined;
|
|
1748
|
+
if (min !== undefined)
|
|
1749
|
+
parts.push(`(min-width: ${min}px)`);
|
|
1750
|
+
if (max !== undefined)
|
|
1751
|
+
parts.push(`(max-width: ${max}px)`);
|
|
1752
|
+
const query = parts.join(' and ');
|
|
1753
|
+
if (query)
|
|
1754
|
+
result[key] = query;
|
|
1755
|
+
return result;
|
|
1756
|
+
}, {});
|
|
1757
|
+
};
|
|
1758
|
+
const matchContainer = (element, container) => {
|
|
1759
|
+
const getData = () => {
|
|
1760
|
+
if (element[CONTAINER_DATA])
|
|
1761
|
+
return element[CONTAINER_DATA];
|
|
1762
|
+
const listeners = new Set();
|
|
1763
|
+
const observer = new ResizeObserver(() => {
|
|
1764
|
+
listeners.forEach((listener) => {
|
|
1765
|
+
listener();
|
|
1766
|
+
});
|
|
1767
|
+
});
|
|
1768
|
+
observer.observe(element);
|
|
1769
|
+
element[CONTAINER_DATA] = { listeners, observer };
|
|
1770
|
+
return element[CONTAINER_DATA];
|
|
1771
|
+
};
|
|
1772
|
+
const getMatches = () => {
|
|
1773
|
+
const width = element.offsetWidth;
|
|
1774
|
+
const matches = (container.min === undefined || width >= container.min) &&
|
|
1775
|
+
(container.max === undefined || width <= container.max);
|
|
1776
|
+
return matches;
|
|
1777
|
+
};
|
|
1778
|
+
const addEventListener = (_type, listener) => {
|
|
1779
|
+
getData().listeners.add(listener);
|
|
1780
|
+
};
|
|
1781
|
+
const removeEventListener = (_type, listener) => {
|
|
1782
|
+
const data = getData();
|
|
1783
|
+
data.listeners.delete(listener);
|
|
1784
|
+
if (data.listeners.size !== 0)
|
|
1785
|
+
return;
|
|
1786
|
+
data.observer.disconnect();
|
|
1787
|
+
delete element[CONTAINER_DATA];
|
|
1788
|
+
};
|
|
1789
|
+
return {
|
|
1790
|
+
get matches() {
|
|
1791
|
+
return getMatches();
|
|
1792
|
+
},
|
|
1793
|
+
addEventListener,
|
|
1794
|
+
removeEventListener
|
|
1795
|
+
};
|
|
1796
|
+
};
|
|
1797
|
+
function Overrides() {
|
|
1798
|
+
return (target, key) => {
|
|
1799
|
+
const DISPOSERS = Symbol();
|
|
1800
|
+
const breakpoints = getConfig(getNamespace(target) || '').breakpoints || {};
|
|
1801
|
+
const containers = getContainers(breakpoints);
|
|
1802
|
+
const medias = getMedias(breakpoints);
|
|
1803
|
+
wrapMethod('after', target, LIFECYCLE_UPDATE, function (states) {
|
|
1804
|
+
if (!states.has(key))
|
|
1805
|
+
return;
|
|
1806
|
+
this[DISPOSERS] ??= new Map();
|
|
1807
|
+
const disposers = this[DISPOSERS];
|
|
1808
|
+
const overrides = this[key] || {};
|
|
1809
|
+
const activeKeys = new Set(disposers.keys());
|
|
1810
|
+
const overrideKeys = Object.keys(overrides);
|
|
1811
|
+
const containerKeys = overrideKeys.filter((breakpoint) => breakpoint in containers);
|
|
1812
|
+
const mediaKeys = overrideKeys.filter((breakpoint) => breakpoint in medias);
|
|
1813
|
+
let timeout = -1;
|
|
1814
|
+
let next = {};
|
|
1815
|
+
const apply = (key) => {
|
|
1816
|
+
clearTimeout(timeout);
|
|
1817
|
+
Object.assign(next, overrides[key]);
|
|
1818
|
+
timeout = window.setTimeout(() => {
|
|
1819
|
+
Object.assign(host(this), overrides[key]);
|
|
1820
|
+
next = {};
|
|
1821
|
+
}, 0);
|
|
1822
|
+
};
|
|
1823
|
+
for (const overrideKey of overrideKeys) {
|
|
1824
|
+
if (activeKeys.delete(overrideKey))
|
|
1825
|
+
continue;
|
|
1826
|
+
const breakpoint = breakpoints[overrideKey];
|
|
1827
|
+
if (!breakpoint)
|
|
1828
|
+
continue;
|
|
1829
|
+
switch (breakpoint.type) {
|
|
1830
|
+
case 'container': {
|
|
1831
|
+
const container = containers[overrideKey];
|
|
1832
|
+
if (!container)
|
|
1833
|
+
break;
|
|
1834
|
+
const containerQueryList = matchContainer(host(this), container);
|
|
1835
|
+
const change = () => {
|
|
1836
|
+
for (const containerKey of containerKeys) {
|
|
1837
|
+
if (matchContainer(host(this), containers[containerKey]).matches) {
|
|
1838
|
+
apply(containerKey);
|
|
1839
|
+
}
|
|
1840
|
+
}
|
|
1841
|
+
};
|
|
1842
|
+
containerQueryList.addEventListener('change', change);
|
|
1843
|
+
const disposer = () => {
|
|
1844
|
+
containerQueryList.removeEventListener('change', change);
|
|
1845
|
+
};
|
|
1846
|
+
disposers.set(overrideKey, disposer);
|
|
1847
|
+
if (!containerQueryList.matches)
|
|
1848
|
+
break;
|
|
1849
|
+
change();
|
|
1850
|
+
break;
|
|
1851
|
+
}
|
|
1852
|
+
case 'media': {
|
|
1853
|
+
const media = medias[overrideKey];
|
|
1854
|
+
if (!media)
|
|
1855
|
+
break;
|
|
1856
|
+
const mediaQueryList = window.matchMedia(media);
|
|
1857
|
+
const change = () => {
|
|
1858
|
+
for (const mediaKey of mediaKeys) {
|
|
1859
|
+
if (window.matchMedia(medias[mediaKey]).matches) {
|
|
1860
|
+
apply(mediaKey);
|
|
1861
|
+
}
|
|
1862
|
+
}
|
|
1863
|
+
};
|
|
1864
|
+
mediaQueryList.addEventListener('change', change);
|
|
1865
|
+
const disposer = () => {
|
|
1866
|
+
mediaQueryList.removeEventListener('change', change);
|
|
1867
|
+
};
|
|
1868
|
+
disposers.set(overrideKey, disposer);
|
|
1869
|
+
if (!mediaQueryList.matches)
|
|
1870
|
+
break;
|
|
1871
|
+
change();
|
|
1872
|
+
break;
|
|
1873
|
+
}
|
|
1874
|
+
}
|
|
1875
|
+
}
|
|
1876
|
+
for (const activeKey of activeKeys) {
|
|
1877
|
+
const disposer = disposers.get(activeKey);
|
|
1878
|
+
disposer?.();
|
|
1879
|
+
disposers.delete(activeKey);
|
|
1880
|
+
}
|
|
1881
|
+
});
|
|
1882
|
+
wrapMethod('after', target, LIFECYCLE_DISCONNECTED, function () {
|
|
1883
|
+
this[DISPOSERS] ??= new Map();
|
|
1884
|
+
const disposers = this[DISPOSERS];
|
|
1885
|
+
disposers.forEach((disposer) => {
|
|
1886
|
+
disposer();
|
|
1887
|
+
});
|
|
1888
|
+
disposers.clear();
|
|
1889
|
+
});
|
|
1890
|
+
};
|
|
1891
|
+
}
|
|
1892
|
+
|
|
1651
1893
|
/**
|
|
1652
1894
|
* Creates a reactive property, reflecting a corresponding attribute value,
|
|
1653
1895
|
* and updates the element when the property is set.
|
|
1654
1896
|
*/
|
|
1655
1897
|
function Property(options) {
|
|
1656
|
-
return
|
|
1898
|
+
return (target, key, descriptor) => {
|
|
1657
1899
|
// Unique symbol for property storage to avoid naming conflicts
|
|
1658
1900
|
const KEY = Symbol();
|
|
1659
1901
|
// Unique symbol for the lock flag to prevent infinite loops during updates
|
|
@@ -1662,8 +1904,12 @@ function Property(options) {
|
|
|
1662
1904
|
const attribute = options?.attribute || kebabCase(key);
|
|
1663
1905
|
// Store the original setter (if it exists) to preserve its behavior
|
|
1664
1906
|
const originalSetter = descriptor?.set;
|
|
1907
|
+
// Ensure the element class has an observedAttributes array
|
|
1908
|
+
// biome-ignore lint: TODO
|
|
1909
|
+
target.constructor['observedAttributes'] ||= [];
|
|
1665
1910
|
// Register the attribute in the observedAttributes array for the element
|
|
1666
|
-
|
|
1911
|
+
// biome-ignore lint: TODO
|
|
1912
|
+
target.constructor['observedAttributes'].push(attribute);
|
|
1667
1913
|
// Getter function to retrieve the property value
|
|
1668
1914
|
function get() {
|
|
1669
1915
|
return this[KEY];
|
|
@@ -1713,7 +1959,7 @@ function Property(options) {
|
|
|
1713
1959
|
* Define a raw property setter to handle updates that trigger from the `attributeChangedCallback`,
|
|
1714
1960
|
* To intercept and process raw attribute values before they are assigned to the property
|
|
1715
1961
|
*/
|
|
1716
|
-
defineProperty(target,
|
|
1962
|
+
defineProperty(target, `RAW:${attribute}`, {
|
|
1717
1963
|
set(value) {
|
|
1718
1964
|
if (!this[LOCKED]) {
|
|
1719
1965
|
// Convert the raw value and set it to the corresponding property
|
|
@@ -1800,7 +2046,7 @@ function Slots() {
|
|
|
1800
2046
|
* element to re-render upon the desired property changes.
|
|
1801
2047
|
*/
|
|
1802
2048
|
function State() {
|
|
1803
|
-
return
|
|
2049
|
+
return (target, key) => {
|
|
1804
2050
|
const KEY = Symbol();
|
|
1805
2051
|
const name = String(key);
|
|
1806
2052
|
defineProperty(target, key, {
|
|
@@ -1822,7 +2068,7 @@ function State() {
|
|
|
1822
2068
|
|
|
1823
2069
|
// TODO: check the logic
|
|
1824
2070
|
function Style() {
|
|
1825
|
-
return
|
|
2071
|
+
return (target, key) => {
|
|
1826
2072
|
const LAST = Symbol();
|
|
1827
2073
|
const SHEET = Symbol();
|
|
1828
2074
|
wrapMethod('before', target, LIFECYCLE_UPDATED, function () {
|
|
@@ -1843,6 +2089,7 @@ function Style() {
|
|
|
1843
2089
|
value = value.call(this);
|
|
1844
2090
|
}
|
|
1845
2091
|
if (value instanceof Promise) {
|
|
2092
|
+
// biome-ignore lint: TODO
|
|
1846
2093
|
value.then(update((this[LAST] = value))).catch((error) => {
|
|
1847
2094
|
throw new Error('TODO', { cause: error });
|
|
1848
2095
|
});
|
|
@@ -1854,7 +2101,7 @@ function Style() {
|
|
|
1854
2101
|
};
|
|
1855
2102
|
}
|
|
1856
2103
|
const toCssString = (input, parent) => {
|
|
1857
|
-
if (typeof input
|
|
2104
|
+
if (typeof input === 'string') {
|
|
1858
2105
|
return input.trim();
|
|
1859
2106
|
}
|
|
1860
2107
|
if (Array.isArray(input)) {
|
|
@@ -1863,7 +2110,9 @@ const toCssString = (input, parent) => {
|
|
|
1863
2110
|
.filter(Boolean)
|
|
1864
2111
|
.join('\n');
|
|
1865
2112
|
}
|
|
1866
|
-
if (
|
|
2113
|
+
if (input === null)
|
|
2114
|
+
return '';
|
|
2115
|
+
if (typeof input !== 'object')
|
|
1867
2116
|
return '';
|
|
1868
2117
|
let result = '';
|
|
1869
2118
|
for (const key of Object.keys(input)) {
|
|
@@ -1892,7 +2141,7 @@ const toCssString = (input, parent) => {
|
|
|
1892
2141
|
* @param immediate Triggers the callback immediately after initialization.
|
|
1893
2142
|
*/
|
|
1894
2143
|
function Watch(keys, immediate) {
|
|
1895
|
-
return
|
|
2144
|
+
return (target, key) => {
|
|
1896
2145
|
// Gets all keys
|
|
1897
2146
|
const all = [keys].flat().filter((item) => item);
|
|
1898
2147
|
// Registers a lifecycle to detect changes.
|
|
@@ -1916,4 +2165,4 @@ const attributes = attributes$2;
|
|
|
1916
2165
|
const html = html$1;
|
|
1917
2166
|
const styles = styles$1;
|
|
1918
2167
|
|
|
1919
|
-
export { Bind, Consumer, Debounce, Direction, Element, Event, Host, IsRTL, Listen, Method, Property, Provider, Query, QueryAll, Slots, State, Style, Watch, attributes as a, classes, direction, dispatch, getConfig, html as h, host, isCSSColor, isRTL, off, on, query, queryAll, styles as s, setConfig, slots, toCSSColor, toCSSUnit, toUnit };
|
|
2168
|
+
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 };
|