thunderous 2.4.3 → 2.4.5-next.1776736743
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/index.cjs +132 -91
- package/dist/index.d.cts +55 -2
- package/dist/index.d.ts +55 -2
- package/dist/index.js +130 -91
- package/package.json +19 -8
package/dist/index.cjs
CHANGED
|
@@ -20,6 +20,8 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
20
20
|
// src/index.ts
|
|
21
21
|
var index_exports = {};
|
|
22
22
|
__export(index_exports, {
|
|
23
|
+
clearRenderState: () => clearRenderState,
|
|
24
|
+
clearServerCss: () => clearServerCss,
|
|
23
25
|
clientOnlyCallback: () => clientOnlyCallback,
|
|
24
26
|
createEffect: () => createEffect,
|
|
25
27
|
createRegistry: () => createRegistry,
|
|
@@ -100,13 +102,14 @@ var createSignal = (initVal, options) => {
|
|
|
100
102
|
const effectRef = effects.get(sym);
|
|
101
103
|
if (effectRef !== void 0) {
|
|
102
104
|
try {
|
|
103
|
-
effectRef.fn({
|
|
105
|
+
const result = effectRef.fn({
|
|
104
106
|
lastValue: effectRef.value,
|
|
105
107
|
destroy: () => {
|
|
106
108
|
effects.delete(sym);
|
|
107
109
|
queueMicrotask(() => subscribers.delete(sym));
|
|
108
110
|
}
|
|
109
111
|
});
|
|
112
|
+
if (result !== void 0) effectRef.value = result;
|
|
110
113
|
} catch (error) {
|
|
111
114
|
console.error("Error in subscriber:", { error, oldValue, newValue, fn: effectRef.fn });
|
|
112
115
|
}
|
|
@@ -147,14 +150,16 @@ var derived = (fn, options) => {
|
|
|
147
150
|
};
|
|
148
151
|
var createEffect = (fn, value) => {
|
|
149
152
|
const privateIdent = ident = {};
|
|
150
|
-
|
|
153
|
+
const effectRef = { fn, value };
|
|
154
|
+
effects.set(ident, effectRef);
|
|
151
155
|
try {
|
|
152
|
-
fn({
|
|
156
|
+
const result = fn({
|
|
153
157
|
lastValue: value,
|
|
154
158
|
destroy: () => {
|
|
155
159
|
effects.delete(privateIdent);
|
|
156
160
|
}
|
|
157
161
|
});
|
|
162
|
+
if (result !== void 0) effectRef.value = result;
|
|
158
163
|
} catch (error) {
|
|
159
164
|
console.error("Error in effect:", { error, fn });
|
|
160
165
|
}
|
|
@@ -163,12 +168,6 @@ var createEffect = (fn, value) => {
|
|
|
163
168
|
|
|
164
169
|
// src/utilities.ts
|
|
165
170
|
var NOOP = () => void 0;
|
|
166
|
-
var queryComment = (node, comment) => {
|
|
167
|
-
const walker = document.createTreeWalker(node, NodeFilter.SHOW_COMMENT, {
|
|
168
|
-
acceptNode: (n) => n.nodeValue === comment ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_SKIP
|
|
169
|
-
});
|
|
170
|
-
return walker.nextNode();
|
|
171
|
-
};
|
|
172
171
|
var queryChildren = (children, selector) => {
|
|
173
172
|
for (const child of children) {
|
|
174
173
|
if (child instanceof Element && child.matches(selector)) {
|
|
@@ -184,6 +183,9 @@ var serverDefineFns = /* @__PURE__ */ new Set();
|
|
|
184
183
|
var onServerDefine = (fn) => {
|
|
185
184
|
serverDefineFns.add(fn);
|
|
186
185
|
};
|
|
186
|
+
var clearServerCss = () => {
|
|
187
|
+
serverCss.clear();
|
|
188
|
+
};
|
|
187
189
|
var serverDefine = ({
|
|
188
190
|
tagName,
|
|
189
191
|
serverRender,
|
|
@@ -321,7 +323,7 @@ var clientOnlyCallback = (fn) => {
|
|
|
321
323
|
// src/render.ts
|
|
322
324
|
var CALLBACK_BINDING_REGEX = /(\{\{callback:.+\}\})/;
|
|
323
325
|
var LEGACY_CALLBACK_BINDING_REGEX = /(this.getRootNode\(\).host.__customCallbackFns.get\('.+'\)\(event\))/;
|
|
324
|
-
var SIGNAL_BINDING_REGEX = /(\{\{signal
|
|
326
|
+
var SIGNAL_BINDING_REGEX = /(\{\{signal:.+?\}\})/;
|
|
325
327
|
var FRAGMENT_ATTRIBUTE = "___thunderous-fragment";
|
|
326
328
|
var renderState = {
|
|
327
329
|
currentShadowRoot: null,
|
|
@@ -332,6 +334,13 @@ var renderState = {
|
|
|
332
334
|
propertyMap: /* @__PURE__ */ new Map(),
|
|
333
335
|
registry: typeof customElements !== "undefined" ? customElements : {}
|
|
334
336
|
};
|
|
337
|
+
var clearRenderState = () => {
|
|
338
|
+
renderState.signalMap.clear();
|
|
339
|
+
renderState.callbackMap.clear();
|
|
340
|
+
renderState.propertyMap.clear();
|
|
341
|
+
renderState.fragmentMap.clear();
|
|
342
|
+
renderState.childrenMap.clear();
|
|
343
|
+
};
|
|
335
344
|
var logPropertyWarning = (propName, element) => {
|
|
336
345
|
console.warn(
|
|
337
346
|
`Property "${propName}" does not exist on element:`,
|
|
@@ -339,19 +348,34 @@ var logPropertyWarning = (propName, element) => {
|
|
|
339
348
|
"\n\nThunderous will attempt to set the property anyway, but this may result in unexpected behavior. Please make sure the property exists on the element prior to setting it."
|
|
340
349
|
);
|
|
341
350
|
};
|
|
342
|
-
var asNodeList = (value, parent) => {
|
|
351
|
+
var asNodeList = (value, parent, autoKey) => {
|
|
352
|
+
if (value === null || value === void 0) return [];
|
|
343
353
|
if (typeof value === "string") return [new Text(value)];
|
|
344
|
-
if (value
|
|
354
|
+
if (typeof value === "number" || typeof value === "boolean") return [new Text(String(value))];
|
|
355
|
+
if (value instanceof DocumentFragment) {
|
|
356
|
+
const children = Array.from(value.children);
|
|
357
|
+
if (autoKey !== void 0 && children.length > 0) {
|
|
358
|
+
const child = children[0];
|
|
359
|
+
if (child instanceof Element && child.getAttribute("key") === null) {
|
|
360
|
+
child.setAttribute("key", String(autoKey));
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
return children;
|
|
364
|
+
}
|
|
345
365
|
if (Array.isArray(value)) {
|
|
346
366
|
const nodeList = [];
|
|
347
367
|
let count = 0;
|
|
348
368
|
const keys = /* @__PURE__ */ new Set();
|
|
349
369
|
for (const item of value) {
|
|
350
370
|
const cachedItem = item instanceof DocumentFragment ? renderState.childrenMap.get(item) : void 0;
|
|
351
|
-
const children = cachedItem ?? asNodeList(item, parent);
|
|
371
|
+
const children = cachedItem ?? asNodeList(item, parent, item instanceof DocumentFragment ? count : void 0);
|
|
352
372
|
if (cachedItem === void 0 && item instanceof DocumentFragment) {
|
|
353
373
|
renderState.childrenMap.set(item, children);
|
|
354
374
|
}
|
|
375
|
+
if (!(item instanceof DocumentFragment)) {
|
|
376
|
+
nodeList.push(...children);
|
|
377
|
+
continue;
|
|
378
|
+
}
|
|
355
379
|
if (children.length > 1) {
|
|
356
380
|
console.error(
|
|
357
381
|
"When rendering arrays, fragments must contain only one top-level element at a time. Error occured in:",
|
|
@@ -404,73 +428,68 @@ var processValue = (value) => {
|
|
|
404
428
|
renderState.callbackMap.set(uniqueKey, value);
|
|
405
429
|
return isServer ? String(value()) : `{{callback:${uniqueKey}}}`;
|
|
406
430
|
}
|
|
407
|
-
return String(value);
|
|
431
|
+
return value === null || value === void 0 ? "" : String(value);
|
|
408
432
|
};
|
|
409
433
|
var evaluateBindings = (element, fragment) => {
|
|
410
|
-
for (const child of
|
|
434
|
+
for (const child of Array.from(element.childNodes)) {
|
|
411
435
|
if (child instanceof Text && SIGNAL_BINDING_REGEX.test(child.data)) {
|
|
412
436
|
const textList = child.data.split(SIGNAL_BINDING_REGEX);
|
|
413
|
-
const
|
|
414
|
-
const
|
|
415
|
-
|
|
437
|
+
const allInitialChildren = [];
|
|
438
|
+
const signalEntries = [];
|
|
439
|
+
let signalIndex = 0;
|
|
440
|
+
const totalSignals = textList.filter((t) => SIGNAL_BINDING_REGEX.test(t)).length;
|
|
441
|
+
textList.forEach((text) => {
|
|
416
442
|
const uniqueKey = SIGNAL_BINDING_REGEX.test(text) ? text.replace(/\{\{signal:(.+)\}\}/, "$1") : void 0;
|
|
417
443
|
const signal = uniqueKey !== void 0 ? renderState.signalMap.get(uniqueKey) : void 0;
|
|
418
444
|
const newValue = signal !== void 0 ? signal() : text;
|
|
419
|
-
const
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
if (endAnchor2 !== null) {
|
|
425
|
-
endAnchor2.before(...initialChildren);
|
|
426
|
-
} else {
|
|
427
|
-
element.append(...initialChildren);
|
|
428
|
-
}
|
|
445
|
+
const autoKey = signal !== void 0 && totalSignals > 1 ? signalIndex++ : void 0;
|
|
446
|
+
const initialChildren = asNodeList(newValue, element, autoKey);
|
|
447
|
+
allInitialChildren.push(...initialChildren);
|
|
448
|
+
if (uniqueKey !== void 0 && signal !== void 0) {
|
|
449
|
+
signalEntries.push({ uniqueKey, signal, initialChildren, autoKey });
|
|
429
450
|
}
|
|
430
|
-
|
|
451
|
+
});
|
|
452
|
+
child.replaceWith(...allInitialChildren);
|
|
453
|
+
signalEntries.forEach(({ uniqueKey, signal, initialChildren, autoKey }) => {
|
|
454
|
+
const firstChild = initialChildren[0];
|
|
455
|
+
const lastChild = initialChildren[initialChildren.length - 1];
|
|
456
|
+
if (firstChild === void 0) return;
|
|
431
457
|
const startAnchor = document.createComment(`${uniqueKey}:start`);
|
|
432
|
-
|
|
433
|
-
prevSibling.after(startAnchor);
|
|
434
|
-
} else {
|
|
435
|
-
element.prepend(startAnchor);
|
|
436
|
-
}
|
|
458
|
+
firstChild.before(startAnchor);
|
|
437
459
|
const endAnchor = document.createComment(`${uniqueKey}:end`);
|
|
438
|
-
|
|
439
|
-
nextSibling.before(endAnchor);
|
|
440
|
-
} else {
|
|
441
|
-
element.append(endAnchor);
|
|
442
|
-
}
|
|
460
|
+
lastChild.after(endAnchor);
|
|
443
461
|
const bindText = (node, signal2) => {
|
|
444
462
|
createEffect(({ destroy }) => {
|
|
445
463
|
const result = signal2();
|
|
446
464
|
if (Array.isArray(result)) {
|
|
447
465
|
destroy();
|
|
448
|
-
bindArray(signal2);
|
|
466
|
+
bindArray(signal2, autoKey);
|
|
449
467
|
return;
|
|
450
468
|
}
|
|
451
469
|
if (result instanceof DocumentFragment) {
|
|
452
470
|
destroy();
|
|
453
|
-
bindFragment(signal2);
|
|
471
|
+
bindFragment(signal2, initialChildren, autoKey);
|
|
454
472
|
return;
|
|
455
473
|
}
|
|
456
|
-
node.data = result === null ? "" : String(result);
|
|
474
|
+
node.data = result === null || result === void 0 ? "" : String(result);
|
|
457
475
|
});
|
|
458
476
|
};
|
|
459
|
-
const bindArray = (signal2) => {
|
|
477
|
+
const bindArray = (signal2, autoKey2) => {
|
|
460
478
|
createEffect(
|
|
461
479
|
({ lastValue: oldChildren, destroy }) => {
|
|
462
480
|
const result = signal2();
|
|
463
|
-
const newChildren = asNodeList(result, element);
|
|
464
|
-
const
|
|
465
|
-
if (!Array.isArray(result)
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
481
|
+
const newChildren = asNodeList(result, element, autoKey2);
|
|
482
|
+
const firstChild2 = newChildren[0];
|
|
483
|
+
if (!Array.isArray(result)) {
|
|
484
|
+
if (newChildren.length === 1 && firstChild2 instanceof Text) {
|
|
485
|
+
while (startAnchor.nextSibling !== endAnchor) {
|
|
486
|
+
startAnchor.nextSibling?.remove();
|
|
487
|
+
}
|
|
488
|
+
startAnchor.after(firstChild2);
|
|
489
|
+
destroy();
|
|
490
|
+
bindText(firstChild2, signal2);
|
|
491
|
+
return;
|
|
492
|
+
}
|
|
474
493
|
}
|
|
475
494
|
while (startAnchor.nextSibling !== endAnchor) {
|
|
476
495
|
startAnchor.nextSibling?.remove();
|
|
@@ -480,16 +499,15 @@ var evaluateBindings = (element, fragment) => {
|
|
|
480
499
|
for (const persistedChild of oldChildren) {
|
|
481
500
|
if (persistedChild instanceof Element) {
|
|
482
501
|
const key = persistedChild.getAttribute("key");
|
|
483
|
-
if (key === null) continue;
|
|
484
502
|
const newChild = queryChildren(newChildren, `[key="${key}"]`);
|
|
485
503
|
if (newChild === null) {
|
|
486
504
|
persistedChild.remove();
|
|
487
505
|
continue;
|
|
488
506
|
}
|
|
489
|
-
for (const attr of
|
|
507
|
+
for (const attr of Array.from(persistedChild.attributes)) {
|
|
490
508
|
if (!newChild.hasAttribute(attr.name)) persistedChild.removeAttribute(attr.name);
|
|
491
509
|
}
|
|
492
|
-
for (const newAttr of
|
|
510
|
+
for (const newAttr of Array.from(newChild.attributes)) {
|
|
493
511
|
const oldAttrValue = persistedChild.getAttribute(newAttr.name);
|
|
494
512
|
if (oldAttrValue?.startsWith("this.__customCallbackFns")) continue;
|
|
495
513
|
persistedChild.setAttribute(newAttr.name, newAttr.value);
|
|
@@ -502,40 +520,52 @@ var evaluateBindings = (element, fragment) => {
|
|
|
502
520
|
null
|
|
503
521
|
);
|
|
504
522
|
};
|
|
505
|
-
const bindFragment = (signal2) => {
|
|
523
|
+
const bindFragment = (signal2, initialChildren2, autoKey2) => {
|
|
506
524
|
const initialFragment = signal2();
|
|
507
|
-
|
|
525
|
+
const firstInitialChild = initialChildren2[0];
|
|
526
|
+
if (firstInitialChild instanceof Element) {
|
|
527
|
+
renderState.childrenMap.set(initialFragment, initialChildren2);
|
|
528
|
+
}
|
|
508
529
|
createEffect(({ destroy }) => {
|
|
509
530
|
const result = signal2();
|
|
510
|
-
const cachedChildren = renderState.childrenMap.get(
|
|
511
|
-
const children = cachedChildren ?? asNodeList(result, element);
|
|
531
|
+
const cachedChildren = result instanceof DocumentFragment ? renderState.childrenMap.get(result) : void 0;
|
|
532
|
+
const children = cachedChildren ?? asNodeList(result, element, autoKey2);
|
|
533
|
+
if (result instanceof DocumentFragment && !renderState.childrenMap.has(result)) {
|
|
534
|
+
renderState.childrenMap.set(result, children);
|
|
535
|
+
}
|
|
512
536
|
if (Array.isArray(result)) {
|
|
513
537
|
destroy();
|
|
514
|
-
bindArray(signal2);
|
|
538
|
+
bindArray(signal2, autoKey2);
|
|
515
539
|
return;
|
|
516
540
|
}
|
|
517
|
-
if (result instanceof
|
|
518
|
-
|
|
519
|
-
|
|
541
|
+
if (!(result instanceof DocumentFragment) && result !== null && result !== void 0) {
|
|
542
|
+
while (startAnchor.nextSibling !== endAnchor) {
|
|
543
|
+
startAnchor.nextSibling?.remove();
|
|
544
|
+
}
|
|
545
|
+
const children2 = asNodeList(result, element, autoKey2);
|
|
546
|
+
const text = children2[0];
|
|
547
|
+
startAnchor.after(text);
|
|
520
548
|
destroy();
|
|
521
|
-
bindText(
|
|
549
|
+
bindText(text, signal2);
|
|
522
550
|
return;
|
|
523
551
|
}
|
|
524
552
|
while (startAnchor.nextSibling !== endAnchor) {
|
|
525
553
|
startAnchor.nextSibling?.remove();
|
|
526
554
|
}
|
|
555
|
+
if (result === null || result === void 0) {
|
|
556
|
+
return;
|
|
557
|
+
}
|
|
527
558
|
startAnchor.after(...children);
|
|
528
559
|
});
|
|
529
560
|
};
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
}
|
|
561
|
+
const currentValue = signal();
|
|
562
|
+
if (Array.isArray(currentValue)) {
|
|
563
|
+
bindArray(signal, autoKey);
|
|
564
|
+
} else if (currentValue instanceof DocumentFragment) {
|
|
565
|
+
bindFragment(signal, initialChildren, autoKey);
|
|
566
|
+
} else {
|
|
567
|
+
const initialChild = initialChildren[0];
|
|
568
|
+
bindText(initialChild, signal);
|
|
539
569
|
}
|
|
540
570
|
});
|
|
541
571
|
}
|
|
@@ -546,7 +576,7 @@ var evaluateBindings = (element, fragment) => {
|
|
|
546
576
|
child.replaceWith(childFragment);
|
|
547
577
|
}
|
|
548
578
|
} else if (child instanceof Element) {
|
|
549
|
-
for (const attr of
|
|
579
|
+
for (const attr of Array.from(child.attributes)) {
|
|
550
580
|
const attrName = attr.name;
|
|
551
581
|
if (SIGNAL_BINDING_REGEX.test(attr.value)) {
|
|
552
582
|
const textList = attr.value.split(SIGNAL_BINDING_REGEX);
|
|
@@ -572,7 +602,6 @@ var evaluateBindings = (element, fragment) => {
|
|
|
572
602
|
if (newText !== prevText) child.setAttribute(attrName, newText);
|
|
573
603
|
}
|
|
574
604
|
if (attrName.startsWith("prop-id:")) {
|
|
575
|
-
if (child.hasAttribute(attrName)) child.removeAttribute(attrName);
|
|
576
605
|
const propId = attrName.replace("prop-id:", "");
|
|
577
606
|
const propName = renderState.propertyMap.get(propId);
|
|
578
607
|
if (propName === void 0) {
|
|
@@ -607,9 +636,7 @@ var evaluateBindings = (element, fragment) => {
|
|
|
607
636
|
child.__customCallbackFns.set(uniqueKey, callback);
|
|
608
637
|
}
|
|
609
638
|
}
|
|
610
|
-
if (
|
|
611
|
-
child.setAttribute(attrName, `this.__customCallbackFns.get('${uniqueKey}')(event)`);
|
|
612
|
-
} else if (attrName.startsWith("prop-id:")) {
|
|
639
|
+
if (attrName.startsWith("prop-id:")) {
|
|
613
640
|
child.removeAttribute(attrName);
|
|
614
641
|
const propId = attrName.replace("prop-id:", "");
|
|
615
642
|
const propName = renderState.propertyMap.get(propId);
|
|
@@ -622,6 +649,8 @@ var evaluateBindings = (element, fragment) => {
|
|
|
622
649
|
}
|
|
623
650
|
if (!(propName in child)) logPropertyWarning(propName, child);
|
|
624
651
|
child[propName] = child.__customCallbackFns.get(uniqueKey);
|
|
652
|
+
} else {
|
|
653
|
+
child.setAttribute(attrName, `this.__customCallbackFns.get('${uniqueKey}')(event)`);
|
|
625
654
|
}
|
|
626
655
|
});
|
|
627
656
|
} else if (attrName.startsWith("prop-id:")) {
|
|
@@ -651,7 +680,7 @@ var html = (strings, ...values) => {
|
|
|
651
680
|
} else {
|
|
652
681
|
value = processValue(value);
|
|
653
682
|
}
|
|
654
|
-
innerHTML2 += str + String(value
|
|
683
|
+
innerHTML2 += str + String(value);
|
|
655
684
|
return innerHTML2;
|
|
656
685
|
}, "");
|
|
657
686
|
if (isServer) return innerHTML;
|
|
@@ -672,7 +701,7 @@ var html = (strings, ...values) => {
|
|
|
672
701
|
evaluateBindings(fragment, fragment);
|
|
673
702
|
return fragment;
|
|
674
703
|
};
|
|
675
|
-
var
|
|
704
|
+
var isAdoptedStylesSupported = () => typeof window !== "undefined" && window.ShadowRoot?.prototype.hasOwnProperty("adoptedStyleSheets") && window.CSSStyleSheet?.prototype.hasOwnProperty("replace");
|
|
676
705
|
var isCSSStyleSheet = (stylesheet) => {
|
|
677
706
|
return typeof CSSStyleSheet !== "undefined" && stylesheet instanceof CSSStyleSheet;
|
|
678
707
|
};
|
|
@@ -696,7 +725,7 @@ var css = (strings, ...values) => {
|
|
|
696
725
|
if (isServer) {
|
|
697
726
|
return cssText;
|
|
698
727
|
}
|
|
699
|
-
const stylesheet =
|
|
728
|
+
const stylesheet = isAdoptedStylesSupported() ? new CSSStyleSheet() : document.createElement("style");
|
|
700
729
|
const textList = cssText.split(signalBindingRegex);
|
|
701
730
|
createEffect(() => {
|
|
702
731
|
const newCSSTextList = [];
|
|
@@ -803,8 +832,9 @@ var customElement = (render, options) => {
|
|
|
803
832
|
#observer = options?.observedAttributes !== void 0 ? null : new MutationObserver((mutations) => {
|
|
804
833
|
for (const mutation of mutations) {
|
|
805
834
|
const attrName = mutation.attributeName;
|
|
806
|
-
if (
|
|
807
|
-
|
|
835
|
+
if (!(attrName in this.#attrSignals)) {
|
|
836
|
+
this.#attrSignals[attrName] = createSignal(this.getAttribute(attrName));
|
|
837
|
+
}
|
|
808
838
|
const [getter, setter] = this.#attrSignals[attrName];
|
|
809
839
|
const oldValue = getter();
|
|
810
840
|
const newValue = this.getAttribute(attrName);
|
|
@@ -904,7 +934,9 @@ You must set an initial value before calling a property signal's getter.
|
|
|
904
934
|
{},
|
|
905
935
|
{
|
|
906
936
|
get: (_, prop) => {
|
|
907
|
-
if (!(prop in this.#attrSignals))
|
|
937
|
+
if (!(prop in this.#attrSignals)) {
|
|
938
|
+
this.#attrSignals[prop] = createSignal(this.getAttribute(prop));
|
|
939
|
+
}
|
|
908
940
|
const [getter] = this.#attrSignals[prop];
|
|
909
941
|
const setter = (newValue) => this.setAttribute(prop, newValue);
|
|
910
942
|
return [getter, setter];
|
|
@@ -973,9 +1005,7 @@ You must set an initial value before calling a property signal's getter.
|
|
|
973
1005
|
constructor() {
|
|
974
1006
|
try {
|
|
975
1007
|
super();
|
|
976
|
-
|
|
977
|
-
this.__customCallbackFns = /* @__PURE__ */ new Map();
|
|
978
|
-
}
|
|
1008
|
+
this.__customCallbackFns = /* @__PURE__ */ new Map();
|
|
979
1009
|
for (const attr of this.attributes) {
|
|
980
1010
|
this.#attrSignals[attr.name] = createSignal(attr.value);
|
|
981
1011
|
}
|
|
@@ -986,12 +1016,13 @@ You must set an initial value before calling a property signal's getter.
|
|
|
986
1016
|
{ cause: error }
|
|
987
1017
|
);
|
|
988
1018
|
console.error(_error);
|
|
989
|
-
throw _error;
|
|
990
1019
|
}
|
|
991
1020
|
}
|
|
992
1021
|
connectedCallback() {
|
|
993
1022
|
for (const [attrName, attr] of this.#attributesAsPropertiesMap) {
|
|
994
|
-
if (!(attrName in this.#attrSignals))
|
|
1023
|
+
if (!(attrName in this.#attrSignals)) {
|
|
1024
|
+
this.#attrSignals[attrName] = createSignal(this.getAttribute(attrName));
|
|
1025
|
+
}
|
|
995
1026
|
const propName = attr.prop;
|
|
996
1027
|
const [getter] = this.#getPropSignal(propName, { allowUndefined: true });
|
|
997
1028
|
let busy = false;
|
|
@@ -1008,6 +1039,14 @@ You must set an initial value before calling a property signal's getter.
|
|
|
1008
1039
|
busy = false;
|
|
1009
1040
|
});
|
|
1010
1041
|
}
|
|
1042
|
+
for (const attrName of Object.keys(this.#attrSignals)) {
|
|
1043
|
+
const signal = this.#attrSignals[attrName];
|
|
1044
|
+
const [getter, setter] = signal;
|
|
1045
|
+
const currentValue = this.getAttribute(attrName);
|
|
1046
|
+
if (getter() !== currentValue) {
|
|
1047
|
+
setter(currentValue);
|
|
1048
|
+
}
|
|
1049
|
+
}
|
|
1011
1050
|
if (this.#observer !== null) {
|
|
1012
1051
|
this.#observer.observe(this, { attributes: true });
|
|
1013
1052
|
}
|
|
@@ -1176,6 +1215,8 @@ var createRegistry = (args) => {
|
|
|
1176
1215
|
};
|
|
1177
1216
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1178
1217
|
0 && (module.exports = {
|
|
1218
|
+
clearRenderState,
|
|
1219
|
+
clearServerCss,
|
|
1179
1220
|
clientOnlyCallback,
|
|
1180
1221
|
createEffect,
|
|
1181
1222
|
createRegistry,
|
package/dist/index.d.cts
CHANGED
|
@@ -94,7 +94,7 @@ type RegistryArgs = {
|
|
|
94
94
|
|
|
95
95
|
type Styles = CSSStyleSheet | HTMLStyleElement;
|
|
96
96
|
|
|
97
|
-
type SignalOptions = { debugMode
|
|
97
|
+
type SignalOptions = { debugMode?: boolean; label?: string };
|
|
98
98
|
type SignalGetter<T> = {
|
|
99
99
|
(options?: SignalOptions): T;
|
|
100
100
|
getter: true;
|
|
@@ -143,7 +143,41 @@ declare const customElement: <Props extends CustomElementProps>(render: RenderFu
|
|
|
143
143
|
*/
|
|
144
144
|
declare const createRegistry: (args?: RegistryArgs) => RegistryResult;
|
|
145
145
|
|
|
146
|
+
/**
|
|
147
|
+
* Add a callback to handle each call to `define()` on the server.
|
|
148
|
+
*
|
|
149
|
+
* This enables you to intercept those definitions and respond to them,
|
|
150
|
+
* for example to inject declarative shadow DOM templates.
|
|
151
|
+
*
|
|
152
|
+
* @example
|
|
153
|
+
* ```ts
|
|
154
|
+
* let response = originalResponse;
|
|
155
|
+
* onServerDefine((tagName, htmlString) => {
|
|
156
|
+
* // ...
|
|
157
|
+
* response = htmlString.replace(tagName, `my-${tagName}`);
|
|
158
|
+
* });
|
|
159
|
+
* ```
|
|
160
|
+
*/
|
|
146
161
|
declare const onServerDefine: (fn: ServerDefineFn) => void;
|
|
162
|
+
/**
|
|
163
|
+
* Thunderous tracks its state using several maps to associate values with
|
|
164
|
+
* their respective elements.
|
|
165
|
+
*
|
|
166
|
+
* This function clears the map that tracks CSS on the server side, to prevent
|
|
167
|
+
* memory leaks and purge stale data from previous renders.
|
|
168
|
+
*
|
|
169
|
+
* If you are building a framework or plugin that depends on Thunderous, you
|
|
170
|
+
* should call this function before every render. Otherwise, the map will
|
|
171
|
+
* accumulate stale data and may create significant performance issues.
|
|
172
|
+
*
|
|
173
|
+
* @example
|
|
174
|
+
* ```ts
|
|
175
|
+
* import { clearServerCss } from 'thunderous'
|
|
176
|
+
*
|
|
177
|
+
* clearServerCss();
|
|
178
|
+
* ```
|
|
179
|
+
*/
|
|
180
|
+
declare const clearServerCss: () => void;
|
|
147
181
|
declare const insertTemplates: (tagName: string, template: string, inputString: string) => string;
|
|
148
182
|
declare const clientOnlyCallback: (fn: (() => void) | (() => Promise<void>)) => void | Promise<void>;
|
|
149
183
|
|
|
@@ -178,10 +212,29 @@ declare const derived: <T>(fn: () => T, options?: SignalOptions) => SignalGetter
|
|
|
178
212
|
*/
|
|
179
213
|
declare const createEffect: <T = unknown>(fn: Effect<T>, value?: T) => void;
|
|
180
214
|
|
|
215
|
+
/**
|
|
216
|
+
* Thunderous tracks its state using several maps to associate values with
|
|
217
|
+
* their respective elements.
|
|
218
|
+
*
|
|
219
|
+
* This function clears the maps tracking render state, to prevent memory
|
|
220
|
+
* leaks and purge stale data from previous renders.
|
|
221
|
+
*
|
|
222
|
+
* If you are building a framework or plugin that depends on Thunderous, you
|
|
223
|
+
* should call this function before every render. Otherwise, the maps will
|
|
224
|
+
* accumulate stale data and may create significant performance issues.
|
|
225
|
+
*
|
|
226
|
+
* @example
|
|
227
|
+
* ```ts
|
|
228
|
+
* import { clearRenderState } from 'thunderous'
|
|
229
|
+
*
|
|
230
|
+
* clearRenderState();
|
|
231
|
+
* ```
|
|
232
|
+
*/
|
|
233
|
+
declare const clearRenderState: () => void;
|
|
181
234
|
/**
|
|
182
235
|
* A tagged template function for creating DocumentFragment instances.
|
|
183
236
|
*/
|
|
184
237
|
declare const html: (strings: TemplateStringsArray, ...values: unknown[]) => DocumentFragment;
|
|
185
238
|
declare const css: (strings: TemplateStringsArray, ...values: unknown[]) => Styles;
|
|
186
239
|
|
|
187
|
-
export { type ElementResult, type HTMLCustomElement, type RegistryResult, type RenderArgs, type RenderFunction, type Signal, type SignalGetter, type SignalSetter, clientOnlyCallback, createEffect, createRegistry, createSignal, css, customElement, derived, html, insertTemplates, onServerDefine };
|
|
240
|
+
export { type ElementResult, type HTMLCustomElement, type RegistryResult, type RenderArgs, type RenderFunction, type Signal, type SignalGetter, type SignalSetter, clearRenderState, clearServerCss, clientOnlyCallback, createEffect, createRegistry, createSignal, css, customElement, derived, html, insertTemplates, onServerDefine };
|
package/dist/index.d.ts
CHANGED
|
@@ -94,7 +94,7 @@ type RegistryArgs = {
|
|
|
94
94
|
|
|
95
95
|
type Styles = CSSStyleSheet | HTMLStyleElement;
|
|
96
96
|
|
|
97
|
-
type SignalOptions = { debugMode
|
|
97
|
+
type SignalOptions = { debugMode?: boolean; label?: string };
|
|
98
98
|
type SignalGetter<T> = {
|
|
99
99
|
(options?: SignalOptions): T;
|
|
100
100
|
getter: true;
|
|
@@ -143,7 +143,41 @@ declare const customElement: <Props extends CustomElementProps>(render: RenderFu
|
|
|
143
143
|
*/
|
|
144
144
|
declare const createRegistry: (args?: RegistryArgs) => RegistryResult;
|
|
145
145
|
|
|
146
|
+
/**
|
|
147
|
+
* Add a callback to handle each call to `define()` on the server.
|
|
148
|
+
*
|
|
149
|
+
* This enables you to intercept those definitions and respond to them,
|
|
150
|
+
* for example to inject declarative shadow DOM templates.
|
|
151
|
+
*
|
|
152
|
+
* @example
|
|
153
|
+
* ```ts
|
|
154
|
+
* let response = originalResponse;
|
|
155
|
+
* onServerDefine((tagName, htmlString) => {
|
|
156
|
+
* // ...
|
|
157
|
+
* response = htmlString.replace(tagName, `my-${tagName}`);
|
|
158
|
+
* });
|
|
159
|
+
* ```
|
|
160
|
+
*/
|
|
146
161
|
declare const onServerDefine: (fn: ServerDefineFn) => void;
|
|
162
|
+
/**
|
|
163
|
+
* Thunderous tracks its state using several maps to associate values with
|
|
164
|
+
* their respective elements.
|
|
165
|
+
*
|
|
166
|
+
* This function clears the map that tracks CSS on the server side, to prevent
|
|
167
|
+
* memory leaks and purge stale data from previous renders.
|
|
168
|
+
*
|
|
169
|
+
* If you are building a framework or plugin that depends on Thunderous, you
|
|
170
|
+
* should call this function before every render. Otherwise, the map will
|
|
171
|
+
* accumulate stale data and may create significant performance issues.
|
|
172
|
+
*
|
|
173
|
+
* @example
|
|
174
|
+
* ```ts
|
|
175
|
+
* import { clearServerCss } from 'thunderous'
|
|
176
|
+
*
|
|
177
|
+
* clearServerCss();
|
|
178
|
+
* ```
|
|
179
|
+
*/
|
|
180
|
+
declare const clearServerCss: () => void;
|
|
147
181
|
declare const insertTemplates: (tagName: string, template: string, inputString: string) => string;
|
|
148
182
|
declare const clientOnlyCallback: (fn: (() => void) | (() => Promise<void>)) => void | Promise<void>;
|
|
149
183
|
|
|
@@ -178,10 +212,29 @@ declare const derived: <T>(fn: () => T, options?: SignalOptions) => SignalGetter
|
|
|
178
212
|
*/
|
|
179
213
|
declare const createEffect: <T = unknown>(fn: Effect<T>, value?: T) => void;
|
|
180
214
|
|
|
215
|
+
/**
|
|
216
|
+
* Thunderous tracks its state using several maps to associate values with
|
|
217
|
+
* their respective elements.
|
|
218
|
+
*
|
|
219
|
+
* This function clears the maps tracking render state, to prevent memory
|
|
220
|
+
* leaks and purge stale data from previous renders.
|
|
221
|
+
*
|
|
222
|
+
* If you are building a framework or plugin that depends on Thunderous, you
|
|
223
|
+
* should call this function before every render. Otherwise, the maps will
|
|
224
|
+
* accumulate stale data and may create significant performance issues.
|
|
225
|
+
*
|
|
226
|
+
* @example
|
|
227
|
+
* ```ts
|
|
228
|
+
* import { clearRenderState } from 'thunderous'
|
|
229
|
+
*
|
|
230
|
+
* clearRenderState();
|
|
231
|
+
* ```
|
|
232
|
+
*/
|
|
233
|
+
declare const clearRenderState: () => void;
|
|
181
234
|
/**
|
|
182
235
|
* A tagged template function for creating DocumentFragment instances.
|
|
183
236
|
*/
|
|
184
237
|
declare const html: (strings: TemplateStringsArray, ...values: unknown[]) => DocumentFragment;
|
|
185
238
|
declare const css: (strings: TemplateStringsArray, ...values: unknown[]) => Styles;
|
|
186
239
|
|
|
187
|
-
export { type ElementResult, type HTMLCustomElement, type RegistryResult, type RenderArgs, type RenderFunction, type Signal, type SignalGetter, type SignalSetter, clientOnlyCallback, createEffect, createRegistry, createSignal, css, customElement, derived, html, insertTemplates, onServerDefine };
|
|
240
|
+
export { type ElementResult, type HTMLCustomElement, type RegistryResult, type RenderArgs, type RenderFunction, type Signal, type SignalGetter, type SignalSetter, clearRenderState, clearServerCss, clientOnlyCallback, createEffect, createRegistry, createSignal, css, customElement, derived, html, insertTemplates, onServerDefine };
|
package/dist/index.js
CHANGED
|
@@ -65,13 +65,14 @@ var createSignal = (initVal, options) => {
|
|
|
65
65
|
const effectRef = effects.get(sym);
|
|
66
66
|
if (effectRef !== void 0) {
|
|
67
67
|
try {
|
|
68
|
-
effectRef.fn({
|
|
68
|
+
const result = effectRef.fn({
|
|
69
69
|
lastValue: effectRef.value,
|
|
70
70
|
destroy: () => {
|
|
71
71
|
effects.delete(sym);
|
|
72
72
|
queueMicrotask(() => subscribers.delete(sym));
|
|
73
73
|
}
|
|
74
74
|
});
|
|
75
|
+
if (result !== void 0) effectRef.value = result;
|
|
75
76
|
} catch (error) {
|
|
76
77
|
console.error("Error in subscriber:", { error, oldValue, newValue, fn: effectRef.fn });
|
|
77
78
|
}
|
|
@@ -112,14 +113,16 @@ var derived = (fn, options) => {
|
|
|
112
113
|
};
|
|
113
114
|
var createEffect = (fn, value) => {
|
|
114
115
|
const privateIdent = ident = {};
|
|
115
|
-
|
|
116
|
+
const effectRef = { fn, value };
|
|
117
|
+
effects.set(ident, effectRef);
|
|
116
118
|
try {
|
|
117
|
-
fn({
|
|
119
|
+
const result = fn({
|
|
118
120
|
lastValue: value,
|
|
119
121
|
destroy: () => {
|
|
120
122
|
effects.delete(privateIdent);
|
|
121
123
|
}
|
|
122
124
|
});
|
|
125
|
+
if (result !== void 0) effectRef.value = result;
|
|
123
126
|
} catch (error) {
|
|
124
127
|
console.error("Error in effect:", { error, fn });
|
|
125
128
|
}
|
|
@@ -128,12 +131,6 @@ var createEffect = (fn, value) => {
|
|
|
128
131
|
|
|
129
132
|
// src/utilities.ts
|
|
130
133
|
var NOOP = () => void 0;
|
|
131
|
-
var queryComment = (node, comment) => {
|
|
132
|
-
const walker = document.createTreeWalker(node, NodeFilter.SHOW_COMMENT, {
|
|
133
|
-
acceptNode: (n) => n.nodeValue === comment ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_SKIP
|
|
134
|
-
});
|
|
135
|
-
return walker.nextNode();
|
|
136
|
-
};
|
|
137
134
|
var queryChildren = (children, selector) => {
|
|
138
135
|
for (const child of children) {
|
|
139
136
|
if (child instanceof Element && child.matches(selector)) {
|
|
@@ -149,6 +146,9 @@ var serverDefineFns = /* @__PURE__ */ new Set();
|
|
|
149
146
|
var onServerDefine = (fn) => {
|
|
150
147
|
serverDefineFns.add(fn);
|
|
151
148
|
};
|
|
149
|
+
var clearServerCss = () => {
|
|
150
|
+
serverCss.clear();
|
|
151
|
+
};
|
|
152
152
|
var serverDefine = ({
|
|
153
153
|
tagName,
|
|
154
154
|
serverRender,
|
|
@@ -286,7 +286,7 @@ var clientOnlyCallback = (fn) => {
|
|
|
286
286
|
// src/render.ts
|
|
287
287
|
var CALLBACK_BINDING_REGEX = /(\{\{callback:.+\}\})/;
|
|
288
288
|
var LEGACY_CALLBACK_BINDING_REGEX = /(this.getRootNode\(\).host.__customCallbackFns.get\('.+'\)\(event\))/;
|
|
289
|
-
var SIGNAL_BINDING_REGEX = /(\{\{signal
|
|
289
|
+
var SIGNAL_BINDING_REGEX = /(\{\{signal:.+?\}\})/;
|
|
290
290
|
var FRAGMENT_ATTRIBUTE = "___thunderous-fragment";
|
|
291
291
|
var renderState = {
|
|
292
292
|
currentShadowRoot: null,
|
|
@@ -297,6 +297,13 @@ var renderState = {
|
|
|
297
297
|
propertyMap: /* @__PURE__ */ new Map(),
|
|
298
298
|
registry: typeof customElements !== "undefined" ? customElements : {}
|
|
299
299
|
};
|
|
300
|
+
var clearRenderState = () => {
|
|
301
|
+
renderState.signalMap.clear();
|
|
302
|
+
renderState.callbackMap.clear();
|
|
303
|
+
renderState.propertyMap.clear();
|
|
304
|
+
renderState.fragmentMap.clear();
|
|
305
|
+
renderState.childrenMap.clear();
|
|
306
|
+
};
|
|
300
307
|
var logPropertyWarning = (propName, element) => {
|
|
301
308
|
console.warn(
|
|
302
309
|
`Property "${propName}" does not exist on element:`,
|
|
@@ -304,19 +311,34 @@ var logPropertyWarning = (propName, element) => {
|
|
|
304
311
|
"\n\nThunderous will attempt to set the property anyway, but this may result in unexpected behavior. Please make sure the property exists on the element prior to setting it."
|
|
305
312
|
);
|
|
306
313
|
};
|
|
307
|
-
var asNodeList = (value, parent) => {
|
|
314
|
+
var asNodeList = (value, parent, autoKey) => {
|
|
315
|
+
if (value === null || value === void 0) return [];
|
|
308
316
|
if (typeof value === "string") return [new Text(value)];
|
|
309
|
-
if (value
|
|
317
|
+
if (typeof value === "number" || typeof value === "boolean") return [new Text(String(value))];
|
|
318
|
+
if (value instanceof DocumentFragment) {
|
|
319
|
+
const children = Array.from(value.children);
|
|
320
|
+
if (autoKey !== void 0 && children.length > 0) {
|
|
321
|
+
const child = children[0];
|
|
322
|
+
if (child instanceof Element && child.getAttribute("key") === null) {
|
|
323
|
+
child.setAttribute("key", String(autoKey));
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
return children;
|
|
327
|
+
}
|
|
310
328
|
if (Array.isArray(value)) {
|
|
311
329
|
const nodeList = [];
|
|
312
330
|
let count = 0;
|
|
313
331
|
const keys = /* @__PURE__ */ new Set();
|
|
314
332
|
for (const item of value) {
|
|
315
333
|
const cachedItem = item instanceof DocumentFragment ? renderState.childrenMap.get(item) : void 0;
|
|
316
|
-
const children = cachedItem ?? asNodeList(item, parent);
|
|
334
|
+
const children = cachedItem ?? asNodeList(item, parent, item instanceof DocumentFragment ? count : void 0);
|
|
317
335
|
if (cachedItem === void 0 && item instanceof DocumentFragment) {
|
|
318
336
|
renderState.childrenMap.set(item, children);
|
|
319
337
|
}
|
|
338
|
+
if (!(item instanceof DocumentFragment)) {
|
|
339
|
+
nodeList.push(...children);
|
|
340
|
+
continue;
|
|
341
|
+
}
|
|
320
342
|
if (children.length > 1) {
|
|
321
343
|
console.error(
|
|
322
344
|
"When rendering arrays, fragments must contain only one top-level element at a time. Error occured in:",
|
|
@@ -369,73 +391,68 @@ var processValue = (value) => {
|
|
|
369
391
|
renderState.callbackMap.set(uniqueKey, value);
|
|
370
392
|
return isServer ? String(value()) : `{{callback:${uniqueKey}}}`;
|
|
371
393
|
}
|
|
372
|
-
return String(value);
|
|
394
|
+
return value === null || value === void 0 ? "" : String(value);
|
|
373
395
|
};
|
|
374
396
|
var evaluateBindings = (element, fragment) => {
|
|
375
|
-
for (const child of
|
|
397
|
+
for (const child of Array.from(element.childNodes)) {
|
|
376
398
|
if (child instanceof Text && SIGNAL_BINDING_REGEX.test(child.data)) {
|
|
377
399
|
const textList = child.data.split(SIGNAL_BINDING_REGEX);
|
|
378
|
-
const
|
|
379
|
-
const
|
|
380
|
-
|
|
400
|
+
const allInitialChildren = [];
|
|
401
|
+
const signalEntries = [];
|
|
402
|
+
let signalIndex = 0;
|
|
403
|
+
const totalSignals = textList.filter((t) => SIGNAL_BINDING_REGEX.test(t)).length;
|
|
404
|
+
textList.forEach((text) => {
|
|
381
405
|
const uniqueKey = SIGNAL_BINDING_REGEX.test(text) ? text.replace(/\{\{signal:(.+)\}\}/, "$1") : void 0;
|
|
382
406
|
const signal = uniqueKey !== void 0 ? renderState.signalMap.get(uniqueKey) : void 0;
|
|
383
407
|
const newValue = signal !== void 0 ? signal() : text;
|
|
384
|
-
const
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
if (endAnchor2 !== null) {
|
|
390
|
-
endAnchor2.before(...initialChildren);
|
|
391
|
-
} else {
|
|
392
|
-
element.append(...initialChildren);
|
|
393
|
-
}
|
|
408
|
+
const autoKey = signal !== void 0 && totalSignals > 1 ? signalIndex++ : void 0;
|
|
409
|
+
const initialChildren = asNodeList(newValue, element, autoKey);
|
|
410
|
+
allInitialChildren.push(...initialChildren);
|
|
411
|
+
if (uniqueKey !== void 0 && signal !== void 0) {
|
|
412
|
+
signalEntries.push({ uniqueKey, signal, initialChildren, autoKey });
|
|
394
413
|
}
|
|
395
|
-
|
|
414
|
+
});
|
|
415
|
+
child.replaceWith(...allInitialChildren);
|
|
416
|
+
signalEntries.forEach(({ uniqueKey, signal, initialChildren, autoKey }) => {
|
|
417
|
+
const firstChild = initialChildren[0];
|
|
418
|
+
const lastChild = initialChildren[initialChildren.length - 1];
|
|
419
|
+
if (firstChild === void 0) return;
|
|
396
420
|
const startAnchor = document.createComment(`${uniqueKey}:start`);
|
|
397
|
-
|
|
398
|
-
prevSibling.after(startAnchor);
|
|
399
|
-
} else {
|
|
400
|
-
element.prepend(startAnchor);
|
|
401
|
-
}
|
|
421
|
+
firstChild.before(startAnchor);
|
|
402
422
|
const endAnchor = document.createComment(`${uniqueKey}:end`);
|
|
403
|
-
|
|
404
|
-
nextSibling.before(endAnchor);
|
|
405
|
-
} else {
|
|
406
|
-
element.append(endAnchor);
|
|
407
|
-
}
|
|
423
|
+
lastChild.after(endAnchor);
|
|
408
424
|
const bindText = (node, signal2) => {
|
|
409
425
|
createEffect(({ destroy }) => {
|
|
410
426
|
const result = signal2();
|
|
411
427
|
if (Array.isArray(result)) {
|
|
412
428
|
destroy();
|
|
413
|
-
bindArray(signal2);
|
|
429
|
+
bindArray(signal2, autoKey);
|
|
414
430
|
return;
|
|
415
431
|
}
|
|
416
432
|
if (result instanceof DocumentFragment) {
|
|
417
433
|
destroy();
|
|
418
|
-
bindFragment(signal2);
|
|
434
|
+
bindFragment(signal2, initialChildren, autoKey);
|
|
419
435
|
return;
|
|
420
436
|
}
|
|
421
|
-
node.data = result === null ? "" : String(result);
|
|
437
|
+
node.data = result === null || result === void 0 ? "" : String(result);
|
|
422
438
|
});
|
|
423
439
|
};
|
|
424
|
-
const bindArray = (signal2) => {
|
|
440
|
+
const bindArray = (signal2, autoKey2) => {
|
|
425
441
|
createEffect(
|
|
426
442
|
({ lastValue: oldChildren, destroy }) => {
|
|
427
443
|
const result = signal2();
|
|
428
|
-
const newChildren = asNodeList(result, element);
|
|
429
|
-
const
|
|
430
|
-
if (!Array.isArray(result)
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
444
|
+
const newChildren = asNodeList(result, element, autoKey2);
|
|
445
|
+
const firstChild2 = newChildren[0];
|
|
446
|
+
if (!Array.isArray(result)) {
|
|
447
|
+
if (newChildren.length === 1 && firstChild2 instanceof Text) {
|
|
448
|
+
while (startAnchor.nextSibling !== endAnchor) {
|
|
449
|
+
startAnchor.nextSibling?.remove();
|
|
450
|
+
}
|
|
451
|
+
startAnchor.after(firstChild2);
|
|
452
|
+
destroy();
|
|
453
|
+
bindText(firstChild2, signal2);
|
|
454
|
+
return;
|
|
455
|
+
}
|
|
439
456
|
}
|
|
440
457
|
while (startAnchor.nextSibling !== endAnchor) {
|
|
441
458
|
startAnchor.nextSibling?.remove();
|
|
@@ -445,16 +462,15 @@ var evaluateBindings = (element, fragment) => {
|
|
|
445
462
|
for (const persistedChild of oldChildren) {
|
|
446
463
|
if (persistedChild instanceof Element) {
|
|
447
464
|
const key = persistedChild.getAttribute("key");
|
|
448
|
-
if (key === null) continue;
|
|
449
465
|
const newChild = queryChildren(newChildren, `[key="${key}"]`);
|
|
450
466
|
if (newChild === null) {
|
|
451
467
|
persistedChild.remove();
|
|
452
468
|
continue;
|
|
453
469
|
}
|
|
454
|
-
for (const attr of
|
|
470
|
+
for (const attr of Array.from(persistedChild.attributes)) {
|
|
455
471
|
if (!newChild.hasAttribute(attr.name)) persistedChild.removeAttribute(attr.name);
|
|
456
472
|
}
|
|
457
|
-
for (const newAttr of
|
|
473
|
+
for (const newAttr of Array.from(newChild.attributes)) {
|
|
458
474
|
const oldAttrValue = persistedChild.getAttribute(newAttr.name);
|
|
459
475
|
if (oldAttrValue?.startsWith("this.__customCallbackFns")) continue;
|
|
460
476
|
persistedChild.setAttribute(newAttr.name, newAttr.value);
|
|
@@ -467,40 +483,52 @@ var evaluateBindings = (element, fragment) => {
|
|
|
467
483
|
null
|
|
468
484
|
);
|
|
469
485
|
};
|
|
470
|
-
const bindFragment = (signal2) => {
|
|
486
|
+
const bindFragment = (signal2, initialChildren2, autoKey2) => {
|
|
471
487
|
const initialFragment = signal2();
|
|
472
|
-
|
|
488
|
+
const firstInitialChild = initialChildren2[0];
|
|
489
|
+
if (firstInitialChild instanceof Element) {
|
|
490
|
+
renderState.childrenMap.set(initialFragment, initialChildren2);
|
|
491
|
+
}
|
|
473
492
|
createEffect(({ destroy }) => {
|
|
474
493
|
const result = signal2();
|
|
475
|
-
const cachedChildren = renderState.childrenMap.get(
|
|
476
|
-
const children = cachedChildren ?? asNodeList(result, element);
|
|
494
|
+
const cachedChildren = result instanceof DocumentFragment ? renderState.childrenMap.get(result) : void 0;
|
|
495
|
+
const children = cachedChildren ?? asNodeList(result, element, autoKey2);
|
|
496
|
+
if (result instanceof DocumentFragment && !renderState.childrenMap.has(result)) {
|
|
497
|
+
renderState.childrenMap.set(result, children);
|
|
498
|
+
}
|
|
477
499
|
if (Array.isArray(result)) {
|
|
478
500
|
destroy();
|
|
479
|
-
bindArray(signal2);
|
|
501
|
+
bindArray(signal2, autoKey2);
|
|
480
502
|
return;
|
|
481
503
|
}
|
|
482
|
-
if (result instanceof
|
|
483
|
-
|
|
484
|
-
|
|
504
|
+
if (!(result instanceof DocumentFragment) && result !== null && result !== void 0) {
|
|
505
|
+
while (startAnchor.nextSibling !== endAnchor) {
|
|
506
|
+
startAnchor.nextSibling?.remove();
|
|
507
|
+
}
|
|
508
|
+
const children2 = asNodeList(result, element, autoKey2);
|
|
509
|
+
const text = children2[0];
|
|
510
|
+
startAnchor.after(text);
|
|
485
511
|
destroy();
|
|
486
|
-
bindText(
|
|
512
|
+
bindText(text, signal2);
|
|
487
513
|
return;
|
|
488
514
|
}
|
|
489
515
|
while (startAnchor.nextSibling !== endAnchor) {
|
|
490
516
|
startAnchor.nextSibling?.remove();
|
|
491
517
|
}
|
|
518
|
+
if (result === null || result === void 0) {
|
|
519
|
+
return;
|
|
520
|
+
}
|
|
492
521
|
startAnchor.after(...children);
|
|
493
522
|
});
|
|
494
523
|
};
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
}
|
|
524
|
+
const currentValue = signal();
|
|
525
|
+
if (Array.isArray(currentValue)) {
|
|
526
|
+
bindArray(signal, autoKey);
|
|
527
|
+
} else if (currentValue instanceof DocumentFragment) {
|
|
528
|
+
bindFragment(signal, initialChildren, autoKey);
|
|
529
|
+
} else {
|
|
530
|
+
const initialChild = initialChildren[0];
|
|
531
|
+
bindText(initialChild, signal);
|
|
504
532
|
}
|
|
505
533
|
});
|
|
506
534
|
}
|
|
@@ -511,7 +539,7 @@ var evaluateBindings = (element, fragment) => {
|
|
|
511
539
|
child.replaceWith(childFragment);
|
|
512
540
|
}
|
|
513
541
|
} else if (child instanceof Element) {
|
|
514
|
-
for (const attr of
|
|
542
|
+
for (const attr of Array.from(child.attributes)) {
|
|
515
543
|
const attrName = attr.name;
|
|
516
544
|
if (SIGNAL_BINDING_REGEX.test(attr.value)) {
|
|
517
545
|
const textList = attr.value.split(SIGNAL_BINDING_REGEX);
|
|
@@ -537,7 +565,6 @@ var evaluateBindings = (element, fragment) => {
|
|
|
537
565
|
if (newText !== prevText) child.setAttribute(attrName, newText);
|
|
538
566
|
}
|
|
539
567
|
if (attrName.startsWith("prop-id:")) {
|
|
540
|
-
if (child.hasAttribute(attrName)) child.removeAttribute(attrName);
|
|
541
568
|
const propId = attrName.replace("prop-id:", "");
|
|
542
569
|
const propName = renderState.propertyMap.get(propId);
|
|
543
570
|
if (propName === void 0) {
|
|
@@ -572,9 +599,7 @@ var evaluateBindings = (element, fragment) => {
|
|
|
572
599
|
child.__customCallbackFns.set(uniqueKey, callback);
|
|
573
600
|
}
|
|
574
601
|
}
|
|
575
|
-
if (
|
|
576
|
-
child.setAttribute(attrName, `this.__customCallbackFns.get('${uniqueKey}')(event)`);
|
|
577
|
-
} else if (attrName.startsWith("prop-id:")) {
|
|
602
|
+
if (attrName.startsWith("prop-id:")) {
|
|
578
603
|
child.removeAttribute(attrName);
|
|
579
604
|
const propId = attrName.replace("prop-id:", "");
|
|
580
605
|
const propName = renderState.propertyMap.get(propId);
|
|
@@ -587,6 +612,8 @@ var evaluateBindings = (element, fragment) => {
|
|
|
587
612
|
}
|
|
588
613
|
if (!(propName in child)) logPropertyWarning(propName, child);
|
|
589
614
|
child[propName] = child.__customCallbackFns.get(uniqueKey);
|
|
615
|
+
} else {
|
|
616
|
+
child.setAttribute(attrName, `this.__customCallbackFns.get('${uniqueKey}')(event)`);
|
|
590
617
|
}
|
|
591
618
|
});
|
|
592
619
|
} else if (attrName.startsWith("prop-id:")) {
|
|
@@ -616,7 +643,7 @@ var html = (strings, ...values) => {
|
|
|
616
643
|
} else {
|
|
617
644
|
value = processValue(value);
|
|
618
645
|
}
|
|
619
|
-
innerHTML2 += str + String(value
|
|
646
|
+
innerHTML2 += str + String(value);
|
|
620
647
|
return innerHTML2;
|
|
621
648
|
}, "");
|
|
622
649
|
if (isServer) return innerHTML;
|
|
@@ -637,7 +664,7 @@ var html = (strings, ...values) => {
|
|
|
637
664
|
evaluateBindings(fragment, fragment);
|
|
638
665
|
return fragment;
|
|
639
666
|
};
|
|
640
|
-
var
|
|
667
|
+
var isAdoptedStylesSupported = () => typeof window !== "undefined" && window.ShadowRoot?.prototype.hasOwnProperty("adoptedStyleSheets") && window.CSSStyleSheet?.prototype.hasOwnProperty("replace");
|
|
641
668
|
var isCSSStyleSheet = (stylesheet) => {
|
|
642
669
|
return typeof CSSStyleSheet !== "undefined" && stylesheet instanceof CSSStyleSheet;
|
|
643
670
|
};
|
|
@@ -661,7 +688,7 @@ var css = (strings, ...values) => {
|
|
|
661
688
|
if (isServer) {
|
|
662
689
|
return cssText;
|
|
663
690
|
}
|
|
664
|
-
const stylesheet =
|
|
691
|
+
const stylesheet = isAdoptedStylesSupported() ? new CSSStyleSheet() : document.createElement("style");
|
|
665
692
|
const textList = cssText.split(signalBindingRegex);
|
|
666
693
|
createEffect(() => {
|
|
667
694
|
const newCSSTextList = [];
|
|
@@ -768,8 +795,9 @@ var customElement = (render, options) => {
|
|
|
768
795
|
#observer = options?.observedAttributes !== void 0 ? null : new MutationObserver((mutations) => {
|
|
769
796
|
for (const mutation of mutations) {
|
|
770
797
|
const attrName = mutation.attributeName;
|
|
771
|
-
if (
|
|
772
|
-
|
|
798
|
+
if (!(attrName in this.#attrSignals)) {
|
|
799
|
+
this.#attrSignals[attrName] = createSignal(this.getAttribute(attrName));
|
|
800
|
+
}
|
|
773
801
|
const [getter, setter] = this.#attrSignals[attrName];
|
|
774
802
|
const oldValue = getter();
|
|
775
803
|
const newValue = this.getAttribute(attrName);
|
|
@@ -869,7 +897,9 @@ You must set an initial value before calling a property signal's getter.
|
|
|
869
897
|
{},
|
|
870
898
|
{
|
|
871
899
|
get: (_, prop) => {
|
|
872
|
-
if (!(prop in this.#attrSignals))
|
|
900
|
+
if (!(prop in this.#attrSignals)) {
|
|
901
|
+
this.#attrSignals[prop] = createSignal(this.getAttribute(prop));
|
|
902
|
+
}
|
|
873
903
|
const [getter] = this.#attrSignals[prop];
|
|
874
904
|
const setter = (newValue) => this.setAttribute(prop, newValue);
|
|
875
905
|
return [getter, setter];
|
|
@@ -938,9 +968,7 @@ You must set an initial value before calling a property signal's getter.
|
|
|
938
968
|
constructor() {
|
|
939
969
|
try {
|
|
940
970
|
super();
|
|
941
|
-
|
|
942
|
-
this.__customCallbackFns = /* @__PURE__ */ new Map();
|
|
943
|
-
}
|
|
971
|
+
this.__customCallbackFns = /* @__PURE__ */ new Map();
|
|
944
972
|
for (const attr of this.attributes) {
|
|
945
973
|
this.#attrSignals[attr.name] = createSignal(attr.value);
|
|
946
974
|
}
|
|
@@ -951,12 +979,13 @@ You must set an initial value before calling a property signal's getter.
|
|
|
951
979
|
{ cause: error }
|
|
952
980
|
);
|
|
953
981
|
console.error(_error);
|
|
954
|
-
throw _error;
|
|
955
982
|
}
|
|
956
983
|
}
|
|
957
984
|
connectedCallback() {
|
|
958
985
|
for (const [attrName, attr] of this.#attributesAsPropertiesMap) {
|
|
959
|
-
if (!(attrName in this.#attrSignals))
|
|
986
|
+
if (!(attrName in this.#attrSignals)) {
|
|
987
|
+
this.#attrSignals[attrName] = createSignal(this.getAttribute(attrName));
|
|
988
|
+
}
|
|
960
989
|
const propName = attr.prop;
|
|
961
990
|
const [getter] = this.#getPropSignal(propName, { allowUndefined: true });
|
|
962
991
|
let busy = false;
|
|
@@ -973,6 +1002,14 @@ You must set an initial value before calling a property signal's getter.
|
|
|
973
1002
|
busy = false;
|
|
974
1003
|
});
|
|
975
1004
|
}
|
|
1005
|
+
for (const attrName of Object.keys(this.#attrSignals)) {
|
|
1006
|
+
const signal = this.#attrSignals[attrName];
|
|
1007
|
+
const [getter, setter] = signal;
|
|
1008
|
+
const currentValue = this.getAttribute(attrName);
|
|
1009
|
+
if (getter() !== currentValue) {
|
|
1010
|
+
setter(currentValue);
|
|
1011
|
+
}
|
|
1012
|
+
}
|
|
976
1013
|
if (this.#observer !== null) {
|
|
977
1014
|
this.#observer.observe(this, { attributes: true });
|
|
978
1015
|
}
|
|
@@ -1140,6 +1177,8 @@ var createRegistry = (args) => {
|
|
|
1140
1177
|
};
|
|
1141
1178
|
};
|
|
1142
1179
|
export {
|
|
1180
|
+
clearRenderState,
|
|
1181
|
+
clearServerCss,
|
|
1143
1182
|
clientOnlyCallback,
|
|
1144
1183
|
createEffect,
|
|
1145
1184
|
createRegistry,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "thunderous",
|
|
3
|
-
"version": "2.4.
|
|
3
|
+
"version": "2.4.5-next.1776736743",
|
|
4
4
|
"description": "A lightweight, functional web components library that brings the power of signals to your UI.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|
|
@@ -15,7 +15,8 @@
|
|
|
15
15
|
"author": "Jonathan DeWitt <jon.dewitt@thunder.solutions>",
|
|
16
16
|
"repository": {
|
|
17
17
|
"type": "git",
|
|
18
|
-
"url": "https://github.com/Thunder-Solutions/Thunderous"
|
|
18
|
+
"url": "https://github.com/Thunder-Solutions/Thunderous",
|
|
19
|
+
"directory": "packages/thunderous"
|
|
19
20
|
},
|
|
20
21
|
"keywords": [
|
|
21
22
|
"thunderous",
|
|
@@ -27,7 +28,7 @@
|
|
|
27
28
|
"bugs": {
|
|
28
29
|
"url": "https://github.com/Thunder-Solutions/Thunderous/issues"
|
|
29
30
|
},
|
|
30
|
-
"homepage": "https://
|
|
31
|
+
"homepage": "https://thunderous.dev",
|
|
31
32
|
"license": "MIT",
|
|
32
33
|
"peerDependencies": {
|
|
33
34
|
"@webcomponents/scoped-custom-element-registry": "^0.0.10"
|
|
@@ -37,19 +38,29 @@
|
|
|
37
38
|
"optional": true
|
|
38
39
|
}
|
|
39
40
|
},
|
|
41
|
+
"devDependencies": {
|
|
42
|
+
"@playwright/test": "^1.58.2",
|
|
43
|
+
"@vitest/browser": "^4.1.4",
|
|
44
|
+
"@vitest/browser-playwright": "^4.1.4",
|
|
45
|
+
"@vitest/coverage-istanbul": "^4.1.4",
|
|
46
|
+
"istanbul-merge": "^2.0.0",
|
|
47
|
+
"nyc": "^18.0.0",
|
|
48
|
+
"vitest": "^4.1.4"
|
|
49
|
+
},
|
|
40
50
|
"scripts": {
|
|
41
51
|
"demo": "cd demo && npm start",
|
|
42
|
-
"demo:ssr": "cd demo &&
|
|
52
|
+
"demo:ssr": "cd demo && pnpm ssr",
|
|
43
53
|
"build": "tsup src/index.ts --format cjs,esm --dts --no-clean",
|
|
44
|
-
"
|
|
45
|
-
"test
|
|
46
|
-
"test:
|
|
54
|
+
"clean": "rm -rf dist",
|
|
55
|
+
"test": "pnpm test:server & pnpm test:client && pnpm coverage",
|
|
56
|
+
"test:server": "vitest run --config vitest.server.config.ts --coverage",
|
|
57
|
+
"test:client": "vitest run --config vitest.client.config.ts --coverage",
|
|
58
|
+
"coverage": "istanbul-merge --out ./coverage/merged/coverage-final.json ./coverage/server/coverage-final.json ./coverage/client/coverage-final.json && nyc report && nyc check-coverage",
|
|
47
59
|
"typecheck": "tsc --noEmit",
|
|
48
60
|
"lint": "eslint .",
|
|
49
61
|
"lint:fix": "eslint . --fix",
|
|
50
62
|
"format": "prettier --check . --ignore-path ../../.gitignore",
|
|
51
63
|
"format:fix": "prettier --write . --ignore-path ../../.gitignore",
|
|
52
|
-
"preversion": "npm run typecheck && npm run lint && npm test && npm run build",
|
|
53
64
|
"version": "node postversion.js"
|
|
54
65
|
}
|
|
55
66
|
}
|