thunderous 1.0.2 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +89 -12
- package/dist/index.js +89 -12
- package/package.json +2 -1
package/dist/index.cjs
CHANGED
@@ -722,21 +722,82 @@ var logValueError = (value) => {
|
|
722
722
|
value
|
723
723
|
);
|
724
724
|
};
|
725
|
+
var arrayToDocumentFragment = (array, parent) => {
|
726
|
+
const documentFragment = new DocumentFragment();
|
727
|
+
let count = 0;
|
728
|
+
const keys = /* @__PURE__ */ new Set();
|
729
|
+
for (const item of array) {
|
730
|
+
const node = getNewNode(item, parent).cloneNode(true);
|
731
|
+
if (node instanceof DocumentFragment) {
|
732
|
+
const child = node.firstElementChild;
|
733
|
+
if (node.children.length > 1) {
|
734
|
+
console.error(
|
735
|
+
"When rendering arrays, fragments must contain only one top-level element at a time. Error occured in:",
|
736
|
+
parent
|
737
|
+
);
|
738
|
+
}
|
739
|
+
if (child === null) continue;
|
740
|
+
let key = child.getAttribute("key");
|
741
|
+
if (key === null) {
|
742
|
+
console.warn(
|
743
|
+
"When rendering arrays, a `key` attribute should be provided on each child element. An index was automatically applied, but this could result in unexpected behavior:",
|
744
|
+
child
|
745
|
+
);
|
746
|
+
key = String(count);
|
747
|
+
child.setAttribute("key", key);
|
748
|
+
}
|
749
|
+
if (keys.has(key)) {
|
750
|
+
console.warn(
|
751
|
+
`When rendering arrays, each child should have a unique \`key\` attribute. Duplicate key "${key}" found on:`,
|
752
|
+
child
|
753
|
+
);
|
754
|
+
}
|
755
|
+
keys.add(key);
|
756
|
+
count++;
|
757
|
+
}
|
758
|
+
documentFragment.append(node);
|
759
|
+
}
|
760
|
+
return documentFragment;
|
761
|
+
};
|
762
|
+
var getNewNode = (value, parent) => {
|
763
|
+
if (typeof value === "string") return new Text(value);
|
764
|
+
if (Array.isArray(value)) return arrayToDocumentFragment(value, parent);
|
765
|
+
if (value instanceof DocumentFragment) return value;
|
766
|
+
return new Text("");
|
767
|
+
};
|
725
768
|
var html = (strings, ...values) => {
|
726
769
|
let innerHTML = "";
|
727
770
|
const signalMap = /* @__PURE__ */ new Map();
|
728
|
-
|
729
|
-
|
771
|
+
const processValue = (value) => {
|
772
|
+
if (!isServer && value instanceof DocumentFragment) {
|
773
|
+
const tempDiv = document.createElement("div");
|
774
|
+
tempDiv.append(value.cloneNode(true));
|
775
|
+
return tempDiv.innerHTML;
|
776
|
+
}
|
730
777
|
if (typeof value === "function") {
|
778
|
+
const getter = value;
|
731
779
|
const uniqueKey = crypto.randomUUID();
|
732
|
-
signalMap.set(uniqueKey,
|
733
|
-
|
780
|
+
signalMap.set(uniqueKey, getter);
|
781
|
+
let result = getter();
|
782
|
+
if (Array.isArray(result)) {
|
783
|
+
result = result.map((item) => processValue(item)).join("");
|
784
|
+
}
|
785
|
+
return isServer ? String(result) : `{{signal:${uniqueKey}}}`;
|
734
786
|
}
|
735
787
|
if (typeof value === "object" && value !== null) {
|
736
788
|
logValueError(value);
|
737
|
-
|
789
|
+
return "";
|
738
790
|
}
|
739
|
-
|
791
|
+
return String(value);
|
792
|
+
};
|
793
|
+
strings.forEach((string, i) => {
|
794
|
+
let value = values[i] ?? "";
|
795
|
+
if (Array.isArray(value)) {
|
796
|
+
value = value.map((item) => processValue(item)).join("");
|
797
|
+
} else {
|
798
|
+
value = processValue(value);
|
799
|
+
}
|
800
|
+
innerHTML += string + String(value === null ? "" : value);
|
740
801
|
});
|
741
802
|
if (isServer) {
|
742
803
|
return innerHTML;
|
@@ -748,24 +809,40 @@ var html = (strings, ...values) => {
|
|
748
809
|
for (const child of element.childNodes) {
|
749
810
|
if (child instanceof Text && signalBindingRegex.test(child.data)) {
|
750
811
|
const textList = child.data.split(signalBindingRegex);
|
812
|
+
const sibling = child.nextSibling;
|
751
813
|
textList.forEach((text, i) => {
|
752
814
|
const uniqueKey = text.replace(/\{\{signal:(.+)\}\}/, "$1");
|
753
815
|
const signal = uniqueKey !== text ? signalMap.get(uniqueKey) : void 0;
|
754
816
|
const newValue = signal !== void 0 ? signal() : text;
|
755
|
-
const newNode = (
|
756
|
-
if (typeof newValue === "string") return new Text(newValue);
|
757
|
-
if (newValue instanceof DocumentFragment) return newValue;
|
758
|
-
return new Text("");
|
759
|
-
})();
|
817
|
+
const newNode = getNewNode(newValue, element);
|
760
818
|
if (i === 0) {
|
761
819
|
child.replaceWith(newNode);
|
762
820
|
} else {
|
763
|
-
element.insertBefore(newNode,
|
821
|
+
element.insertBefore(newNode, sibling);
|
764
822
|
}
|
765
823
|
if (signal !== void 0 && newNode instanceof Text) {
|
766
824
|
createEffect(() => {
|
767
825
|
newNode.data = signal();
|
768
826
|
});
|
827
|
+
} else if (signal !== void 0 && newNode instanceof DocumentFragment) {
|
828
|
+
createEffect(() => {
|
829
|
+
const result = signal();
|
830
|
+
const nextNode = getNewNode(result, element);
|
831
|
+
if (nextNode instanceof Text) {
|
832
|
+
throw new TypeError(
|
833
|
+
"Signal mismatch: expected DocumentFragment or Array<DocumentFragment>, but got Text"
|
834
|
+
);
|
835
|
+
}
|
836
|
+
let lastSibling = element.lastChild;
|
837
|
+
for (const child2 of nextNode.children) {
|
838
|
+
const key = child2.getAttribute("key");
|
839
|
+
const matchingNode = element.querySelector(`[key="${key}"]`);
|
840
|
+
if (matchingNode === null) continue;
|
841
|
+
lastSibling = matchingNode.nextSibling;
|
842
|
+
child2.replaceWith(matchingNode);
|
843
|
+
}
|
844
|
+
element.insertBefore(nextNode, lastSibling);
|
845
|
+
});
|
769
846
|
}
|
770
847
|
});
|
771
848
|
}
|
package/dist/index.js
CHANGED
@@ -687,21 +687,82 @@ var logValueError = (value) => {
|
|
687
687
|
value
|
688
688
|
);
|
689
689
|
};
|
690
|
+
var arrayToDocumentFragment = (array, parent) => {
|
691
|
+
const documentFragment = new DocumentFragment();
|
692
|
+
let count = 0;
|
693
|
+
const keys = /* @__PURE__ */ new Set();
|
694
|
+
for (const item of array) {
|
695
|
+
const node = getNewNode(item, parent).cloneNode(true);
|
696
|
+
if (node instanceof DocumentFragment) {
|
697
|
+
const child = node.firstElementChild;
|
698
|
+
if (node.children.length > 1) {
|
699
|
+
console.error(
|
700
|
+
"When rendering arrays, fragments must contain only one top-level element at a time. Error occured in:",
|
701
|
+
parent
|
702
|
+
);
|
703
|
+
}
|
704
|
+
if (child === null) continue;
|
705
|
+
let key = child.getAttribute("key");
|
706
|
+
if (key === null) {
|
707
|
+
console.warn(
|
708
|
+
"When rendering arrays, a `key` attribute should be provided on each child element. An index was automatically applied, but this could result in unexpected behavior:",
|
709
|
+
child
|
710
|
+
);
|
711
|
+
key = String(count);
|
712
|
+
child.setAttribute("key", key);
|
713
|
+
}
|
714
|
+
if (keys.has(key)) {
|
715
|
+
console.warn(
|
716
|
+
`When rendering arrays, each child should have a unique \`key\` attribute. Duplicate key "${key}" found on:`,
|
717
|
+
child
|
718
|
+
);
|
719
|
+
}
|
720
|
+
keys.add(key);
|
721
|
+
count++;
|
722
|
+
}
|
723
|
+
documentFragment.append(node);
|
724
|
+
}
|
725
|
+
return documentFragment;
|
726
|
+
};
|
727
|
+
var getNewNode = (value, parent) => {
|
728
|
+
if (typeof value === "string") return new Text(value);
|
729
|
+
if (Array.isArray(value)) return arrayToDocumentFragment(value, parent);
|
730
|
+
if (value instanceof DocumentFragment) return value;
|
731
|
+
return new Text("");
|
732
|
+
};
|
690
733
|
var html = (strings, ...values) => {
|
691
734
|
let innerHTML = "";
|
692
735
|
const signalMap = /* @__PURE__ */ new Map();
|
693
|
-
|
694
|
-
|
736
|
+
const processValue = (value) => {
|
737
|
+
if (!isServer && value instanceof DocumentFragment) {
|
738
|
+
const tempDiv = document.createElement("div");
|
739
|
+
tempDiv.append(value.cloneNode(true));
|
740
|
+
return tempDiv.innerHTML;
|
741
|
+
}
|
695
742
|
if (typeof value === "function") {
|
743
|
+
const getter = value;
|
696
744
|
const uniqueKey = crypto.randomUUID();
|
697
|
-
signalMap.set(uniqueKey,
|
698
|
-
|
745
|
+
signalMap.set(uniqueKey, getter);
|
746
|
+
let result = getter();
|
747
|
+
if (Array.isArray(result)) {
|
748
|
+
result = result.map((item) => processValue(item)).join("");
|
749
|
+
}
|
750
|
+
return isServer ? String(result) : `{{signal:${uniqueKey}}}`;
|
699
751
|
}
|
700
752
|
if (typeof value === "object" && value !== null) {
|
701
753
|
logValueError(value);
|
702
|
-
|
754
|
+
return "";
|
703
755
|
}
|
704
|
-
|
756
|
+
return String(value);
|
757
|
+
};
|
758
|
+
strings.forEach((string, i) => {
|
759
|
+
let value = values[i] ?? "";
|
760
|
+
if (Array.isArray(value)) {
|
761
|
+
value = value.map((item) => processValue(item)).join("");
|
762
|
+
} else {
|
763
|
+
value = processValue(value);
|
764
|
+
}
|
765
|
+
innerHTML += string + String(value === null ? "" : value);
|
705
766
|
});
|
706
767
|
if (isServer) {
|
707
768
|
return innerHTML;
|
@@ -713,24 +774,40 @@ var html = (strings, ...values) => {
|
|
713
774
|
for (const child of element.childNodes) {
|
714
775
|
if (child instanceof Text && signalBindingRegex.test(child.data)) {
|
715
776
|
const textList = child.data.split(signalBindingRegex);
|
777
|
+
const sibling = child.nextSibling;
|
716
778
|
textList.forEach((text, i) => {
|
717
779
|
const uniqueKey = text.replace(/\{\{signal:(.+)\}\}/, "$1");
|
718
780
|
const signal = uniqueKey !== text ? signalMap.get(uniqueKey) : void 0;
|
719
781
|
const newValue = signal !== void 0 ? signal() : text;
|
720
|
-
const newNode = (
|
721
|
-
if (typeof newValue === "string") return new Text(newValue);
|
722
|
-
if (newValue instanceof DocumentFragment) return newValue;
|
723
|
-
return new Text("");
|
724
|
-
})();
|
782
|
+
const newNode = getNewNode(newValue, element);
|
725
783
|
if (i === 0) {
|
726
784
|
child.replaceWith(newNode);
|
727
785
|
} else {
|
728
|
-
element.insertBefore(newNode,
|
786
|
+
element.insertBefore(newNode, sibling);
|
729
787
|
}
|
730
788
|
if (signal !== void 0 && newNode instanceof Text) {
|
731
789
|
createEffect(() => {
|
732
790
|
newNode.data = signal();
|
733
791
|
});
|
792
|
+
} else if (signal !== void 0 && newNode instanceof DocumentFragment) {
|
793
|
+
createEffect(() => {
|
794
|
+
const result = signal();
|
795
|
+
const nextNode = getNewNode(result, element);
|
796
|
+
if (nextNode instanceof Text) {
|
797
|
+
throw new TypeError(
|
798
|
+
"Signal mismatch: expected DocumentFragment or Array<DocumentFragment>, but got Text"
|
799
|
+
);
|
800
|
+
}
|
801
|
+
let lastSibling = element.lastChild;
|
802
|
+
for (const child2 of nextNode.children) {
|
803
|
+
const key = child2.getAttribute("key");
|
804
|
+
const matchingNode = element.querySelector(`[key="${key}"]`);
|
805
|
+
if (matchingNode === null) continue;
|
806
|
+
lastSibling = matchingNode.nextSibling;
|
807
|
+
child2.replaceWith(matchingNode);
|
808
|
+
}
|
809
|
+
element.insertBefore(nextNode, lastSibling);
|
810
|
+
});
|
734
811
|
}
|
735
812
|
});
|
736
813
|
}
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "thunderous",
|
3
|
-
"version": "1.0
|
3
|
+
"version": "1.1.0",
|
4
4
|
"description": "",
|
5
5
|
"type": "module",
|
6
6
|
"main": "./dist/index.cjs",
|
@@ -31,6 +31,7 @@
|
|
31
31
|
"license": "MIT",
|
32
32
|
"scripts": {
|
33
33
|
"demo": "cd demo && npm start",
|
34
|
+
"demo:ssr": "cd demo && npm run ssr",
|
34
35
|
"www": "cd www && npm run dev",
|
35
36
|
"build": "tsup src/index.ts --format cjs,esm --dts --no-clean",
|
36
37
|
"test": "find src -name '*.test.ts' | xargs c8 node --import tsx --test",
|