ilha 0.4.0 → 0.4.2
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.js +136 -37
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -4,6 +4,18 @@ const __DEV__ = typeof process !== "undefined" ? process.env?.["NODE_ENV"] !== "
|
|
|
4
4
|
function warn(msg) {
|
|
5
5
|
if (__DEV__) console.warn(`[ilha] ${msg}`);
|
|
6
6
|
}
|
|
7
|
+
function shallowEqualInput(a, b) {
|
|
8
|
+
if (Object.is(a, b)) return true;
|
|
9
|
+
if (typeof a !== "object" || a === null || typeof b !== "object" || b === null) return false;
|
|
10
|
+
const ak = Object.keys(a);
|
|
11
|
+
const bk = Object.keys(b);
|
|
12
|
+
if (ak.length !== bk.length) return false;
|
|
13
|
+
for (const k of ak) {
|
|
14
|
+
if (!Object.prototype.hasOwnProperty.call(b, k)) return false;
|
|
15
|
+
if (!Object.is(a[k], b[k])) return false;
|
|
16
|
+
}
|
|
17
|
+
return true;
|
|
18
|
+
}
|
|
7
19
|
function syncAttributes(from, to) {
|
|
8
20
|
for (const { name, value } of to.attributes) if (from.getAttribute(name) !== value) from.setAttribute(name, value);
|
|
9
21
|
for (const { name } of Array.from(from.attributes)) if (!to.hasAttribute(name)) from.removeAttribute(name);
|
|
@@ -80,6 +92,7 @@ const RAW = Symbol("ilha.raw");
|
|
|
80
92
|
const SIGNAL_ACCESSOR = Symbol("ilha.signalAccessor");
|
|
81
93
|
const ISLAND = Symbol("ilha.island");
|
|
82
94
|
const ISLAND_CALL = Symbol("ilha.islandCall");
|
|
95
|
+
const ISLAND_MOUNT_INTERNAL = Symbol("ilha.islandMountInternal");
|
|
83
96
|
const SLOT_ATTR = "data-ilha-slot";
|
|
84
97
|
const PROPS_ATTR = "data-ilha-props";
|
|
85
98
|
const STATE_ATTR = "data-ilha-state";
|
|
@@ -605,6 +618,7 @@ var IlhaBuilder = class IlhaBuilder {
|
|
|
605
618
|
const input = resolveInput(props);
|
|
606
619
|
const state = buildPlainState(input);
|
|
607
620
|
const results = deriveds.map((entry) => {
|
|
621
|
+
const prevSub = setActiveSub(void 0);
|
|
608
622
|
try {
|
|
609
623
|
return {
|
|
610
624
|
key: entry.key,
|
|
@@ -619,6 +633,8 @@ var IlhaBuilder = class IlhaBuilder {
|
|
|
619
633
|
key: entry.key,
|
|
620
634
|
result: Promise.reject(err)
|
|
621
635
|
};
|
|
636
|
+
} finally {
|
|
637
|
+
setActiveSub(prevSub);
|
|
622
638
|
}
|
|
623
639
|
});
|
|
624
640
|
if (!results.some((r) => r.result instanceof Promise) || sync) {
|
|
@@ -633,12 +649,17 @@ var IlhaBuilder = class IlhaBuilder {
|
|
|
633
649
|
value: r.result,
|
|
634
650
|
error: void 0
|
|
635
651
|
};
|
|
636
|
-
const
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
652
|
+
const prevSub = setActiveSub(void 0);
|
|
653
|
+
try {
|
|
654
|
+
const { html } = renderWithCtx(() => fn({
|
|
655
|
+
state,
|
|
656
|
+
derived,
|
|
657
|
+
input
|
|
658
|
+
}));
|
|
659
|
+
return stylePrefix + html;
|
|
660
|
+
} finally {
|
|
661
|
+
setActiveSub(prevSub);
|
|
662
|
+
}
|
|
642
663
|
}
|
|
643
664
|
return Promise.all(results.map(async (r) => {
|
|
644
665
|
try {
|
|
@@ -663,20 +684,32 @@ var IlhaBuilder = class IlhaBuilder {
|
|
|
663
684
|
})).then(async (resolved) => {
|
|
664
685
|
const derived = {};
|
|
665
686
|
for (const r of resolved) derived[r.key] = r.envelope;
|
|
666
|
-
const
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
687
|
+
const prevSub = setActiveSub(void 0);
|
|
688
|
+
try {
|
|
689
|
+
const { html } = await renderWithCtx(() => fn({
|
|
690
|
+
state,
|
|
691
|
+
derived,
|
|
692
|
+
input
|
|
693
|
+
}), void 0, true);
|
|
694
|
+
return stylePrefix + html;
|
|
695
|
+
} finally {
|
|
696
|
+
setActiveSub(prevSub);
|
|
697
|
+
}
|
|
672
698
|
});
|
|
673
699
|
}
|
|
674
700
|
function mountIsland(host, props) {
|
|
701
|
+
return mountIslandInternal(host, props).unmount;
|
|
702
|
+
}
|
|
703
|
+
function mountIslandInternal(host, props) {
|
|
704
|
+
const noop = {
|
|
705
|
+
unmount: () => {},
|
|
706
|
+
updateProps: () => {}
|
|
707
|
+
};
|
|
675
708
|
if (__DEV__ && _mountedHosts) {
|
|
676
709
|
if (_mountedHosts.has(host)) {
|
|
677
710
|
warn(`mount(): this element is already mounted. Call the previous unmount() first to avoid memory leaks and duplicate event listeners.
|
|
678
711
|
Element: ${host.outerHTML.slice(0, 120)}`);
|
|
679
|
-
return
|
|
712
|
+
return noop;
|
|
680
713
|
}
|
|
681
714
|
_mountedHosts.add(host);
|
|
682
715
|
}
|
|
@@ -688,7 +721,31 @@ Element: ${host.outerHTML.slice(0, 120)}`);
|
|
|
688
721
|
warn("Failed to parse data-ilha-props — invalid JSON, falling back to empty props.");
|
|
689
722
|
}
|
|
690
723
|
}
|
|
691
|
-
const
|
|
724
|
+
const inputSignal = signal(resolveInput(props));
|
|
725
|
+
const input = new Proxy({}, {
|
|
726
|
+
get(_t, key) {
|
|
727
|
+
return inputSignal()[key];
|
|
728
|
+
},
|
|
729
|
+
has(_t, key) {
|
|
730
|
+
return key in inputSignal();
|
|
731
|
+
},
|
|
732
|
+
ownKeys() {
|
|
733
|
+
const prevSub = setActiveSub(void 0);
|
|
734
|
+
try {
|
|
735
|
+
return Reflect.ownKeys(inputSignal());
|
|
736
|
+
} finally {
|
|
737
|
+
setActiveSub(prevSub);
|
|
738
|
+
}
|
|
739
|
+
},
|
|
740
|
+
getOwnPropertyDescriptor(_t, key) {
|
|
741
|
+
const prevSub = setActiveSub(void 0);
|
|
742
|
+
try {
|
|
743
|
+
return Reflect.getOwnPropertyDescriptor(inputSignal(), key);
|
|
744
|
+
} finally {
|
|
745
|
+
setActiveSub(prevSub);
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
});
|
|
692
749
|
let snapshotRaw;
|
|
693
750
|
const rawState = host.getAttribute(STATE_ATTR);
|
|
694
751
|
if (rawState) try {
|
|
@@ -758,7 +815,11 @@ Element: ${host.outerHTML.slice(0, 120)}`);
|
|
|
758
815
|
mountedSlots.delete(id);
|
|
759
816
|
}
|
|
760
817
|
for (const [id, { island: childIsland, props }] of slotMap) {
|
|
761
|
-
|
|
818
|
+
const existing = mountedSlots.get(id);
|
|
819
|
+
if (existing) {
|
|
820
|
+
existing.updateProps(props);
|
|
821
|
+
continue;
|
|
822
|
+
}
|
|
762
823
|
const slotEl = findSlot(id);
|
|
763
824
|
if (!slotEl) continue;
|
|
764
825
|
let slotProps = props;
|
|
@@ -771,15 +832,19 @@ Element: ${host.outerHTML.slice(0, 120)}`);
|
|
|
771
832
|
}
|
|
772
833
|
}
|
|
773
834
|
const prevSub = setActiveSub(void 0);
|
|
774
|
-
let
|
|
835
|
+
let handle;
|
|
775
836
|
try {
|
|
776
|
-
|
|
837
|
+
const internal = childIsland[ISLAND_MOUNT_INTERNAL];
|
|
838
|
+
handle = internal ? internal(slotEl, slotProps) : {
|
|
839
|
+
unmount: childIsland.mount(slotEl, slotProps),
|
|
840
|
+
updateProps: () => {}
|
|
841
|
+
};
|
|
777
842
|
} finally {
|
|
778
843
|
setActiveSub(prevSub);
|
|
779
844
|
}
|
|
780
845
|
mountedSlots.set(id, {
|
|
781
846
|
el: slotEl,
|
|
782
|
-
|
|
847
|
+
...handle
|
|
783
848
|
});
|
|
784
849
|
}
|
|
785
850
|
}
|
|
@@ -883,6 +948,7 @@ Element: ${host.outerHTML.slice(0, 120)}`);
|
|
|
883
948
|
if (userCleanup) cleanups.push(userCleanup);
|
|
884
949
|
}
|
|
885
950
|
let initialized = false;
|
|
951
|
+
const initialRenderedHtml = initial.html;
|
|
886
952
|
const stopRender = effect(() => {
|
|
887
953
|
const { html: rendered, slots: newSlotMap } = renderWithCtx(() => fn({
|
|
888
954
|
state,
|
|
@@ -892,7 +958,7 @@ Element: ${host.outerHTML.slice(0, 120)}`);
|
|
|
892
958
|
const html = stylePrefix + rendered;
|
|
893
959
|
if (!initialized) {
|
|
894
960
|
initialized = true;
|
|
895
|
-
return;
|
|
961
|
+
if (rendered === initialRenderedHtml) return;
|
|
896
962
|
}
|
|
897
963
|
detachListeners();
|
|
898
964
|
stopBindings();
|
|
@@ -963,7 +1029,7 @@ Element: ${host.outerHTML.slice(0, 120)}`);
|
|
|
963
1029
|
});
|
|
964
1030
|
}
|
|
965
1031
|
let tornDown = false;
|
|
966
|
-
|
|
1032
|
+
const unmount = () => {
|
|
967
1033
|
if (tornDown) return;
|
|
968
1034
|
tornDown = true;
|
|
969
1035
|
if (__DEV__ && _mountedHosts) _mountedHosts.delete(host);
|
|
@@ -976,6 +1042,16 @@ Element: ${host.outerHTML.slice(0, 120)}`);
|
|
|
976
1042
|
}
|
|
977
1043
|
cleanups.forEach((c) => c());
|
|
978
1044
|
};
|
|
1045
|
+
const updateProps = (nextProps) => {
|
|
1046
|
+
if (tornDown) return;
|
|
1047
|
+
const next = resolveInput(nextProps);
|
|
1048
|
+
if (shallowEqualInput(inputSignal(), next)) return;
|
|
1049
|
+
inputSignal(next);
|
|
1050
|
+
};
|
|
1051
|
+
return {
|
|
1052
|
+
unmount,
|
|
1053
|
+
updateProps
|
|
1054
|
+
};
|
|
979
1055
|
}
|
|
980
1056
|
/**
|
|
981
1057
|
* Dual-mode island invocation:
|
|
@@ -999,6 +1075,7 @@ Element: ${host.outerHTML.slice(0, 120)}`);
|
|
|
999
1075
|
};
|
|
1000
1076
|
island.toString = (props) => renderToString(props, true);
|
|
1001
1077
|
island.mount = (host, props) => mountIsland(host, props);
|
|
1078
|
+
island[ISLAND_MOUNT_INTERNAL] = (host, props) => mountIslandInternal(host, props);
|
|
1002
1079
|
island.key = (key) => {
|
|
1003
1080
|
if (typeof key !== "string" || key.trim().length === 0) throw new Error("island.key() requires a non-empty string.");
|
|
1004
1081
|
if (key.includes(":")) throw new Error(`island.key() key cannot contain the slot separator ":" (got "${key}").`);
|
|
@@ -1028,23 +1105,45 @@ Element: ${host.outerHTML.slice(0, 120)}`);
|
|
|
1028
1105
|
if (doState) for (const entry of states) snapshotData[entry.key] = plainState[entry.key]();
|
|
1029
1106
|
if (doDerived) {
|
|
1030
1107
|
const derivedResults = {};
|
|
1031
|
-
for (const entry of deriveds)
|
|
1032
|
-
const
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
}
|
|
1108
|
+
for (const entry of deriveds) {
|
|
1109
|
+
const prevSub = setActiveSub(void 0);
|
|
1110
|
+
let resultPromise;
|
|
1111
|
+
let syncError;
|
|
1112
|
+
let threw = false;
|
|
1113
|
+
try {
|
|
1114
|
+
resultPromise = entry.fn({
|
|
1115
|
+
state: plainState,
|
|
1116
|
+
input,
|
|
1117
|
+
signal: new AbortController().signal
|
|
1118
|
+
});
|
|
1119
|
+
} catch (err) {
|
|
1120
|
+
threw = true;
|
|
1121
|
+
syncError = err;
|
|
1122
|
+
} finally {
|
|
1123
|
+
setActiveSub(prevSub);
|
|
1124
|
+
}
|
|
1125
|
+
if (threw) {
|
|
1126
|
+
derivedResults[entry.key] = {
|
|
1127
|
+
loading: false,
|
|
1128
|
+
value: void 0,
|
|
1129
|
+
error: syncError instanceof Error ? syncError.message : String(syncError)
|
|
1130
|
+
};
|
|
1131
|
+
continue;
|
|
1132
|
+
}
|
|
1133
|
+
try {
|
|
1134
|
+
const result = await Promise.resolve(resultPromise);
|
|
1135
|
+
derivedResults[entry.key] = {
|
|
1136
|
+
loading: false,
|
|
1137
|
+
value: result,
|
|
1138
|
+
error: void 0
|
|
1139
|
+
};
|
|
1140
|
+
} catch (err) {
|
|
1141
|
+
derivedResults[entry.key] = {
|
|
1142
|
+
loading: false,
|
|
1143
|
+
value: void 0,
|
|
1144
|
+
error: err instanceof Error ? err.message : String(err)
|
|
1145
|
+
};
|
|
1146
|
+
}
|
|
1048
1147
|
}
|
|
1049
1148
|
snapshotData["_derived"] = derivedResults;
|
|
1050
1149
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ilha",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.2",
|
|
4
4
|
"description": "A tiny, framework-free island architecture library",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Ryuz <ryuzer@proton.me>",
|
|
@@ -26,6 +26,6 @@
|
|
|
26
26
|
"alien-signals": "3.1.2"
|
|
27
27
|
},
|
|
28
28
|
"devDependencies": {
|
|
29
|
-
"zod": "^4.4.
|
|
29
|
+
"zod": "^4.4.3"
|
|
30
30
|
}
|
|
31
31
|
}
|