ilha 0.4.1 → 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.
Files changed (2) hide show
  1. package/dist/index.js +72 -8
  2. package/package.json +1 -1
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";
@@ -685,11 +698,18 @@ var IlhaBuilder = class IlhaBuilder {
685
698
  });
686
699
  }
687
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
+ };
688
708
  if (__DEV__ && _mountedHosts) {
689
709
  if (_mountedHosts.has(host)) {
690
710
  warn(`mount(): this element is already mounted. Call the previous unmount() first to avoid memory leaks and duplicate event listeners.
691
711
  Element: ${host.outerHTML.slice(0, 120)}`);
692
- return () => {};
712
+ return noop;
693
713
  }
694
714
  _mountedHosts.add(host);
695
715
  }
@@ -701,7 +721,31 @@ Element: ${host.outerHTML.slice(0, 120)}`);
701
721
  warn("Failed to parse data-ilha-props — invalid JSON, falling back to empty props.");
702
722
  }
703
723
  }
704
- const input = resolveInput(props);
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
+ });
705
749
  let snapshotRaw;
706
750
  const rawState = host.getAttribute(STATE_ATTR);
707
751
  if (rawState) try {
@@ -771,7 +815,11 @@ Element: ${host.outerHTML.slice(0, 120)}`);
771
815
  mountedSlots.delete(id);
772
816
  }
773
817
  for (const [id, { island: childIsland, props }] of slotMap) {
774
- if (mountedSlots.has(id)) continue;
818
+ const existing = mountedSlots.get(id);
819
+ if (existing) {
820
+ existing.updateProps(props);
821
+ continue;
822
+ }
775
823
  const slotEl = findSlot(id);
776
824
  if (!slotEl) continue;
777
825
  let slotProps = props;
@@ -784,15 +832,19 @@ Element: ${host.outerHTML.slice(0, 120)}`);
784
832
  }
785
833
  }
786
834
  const prevSub = setActiveSub(void 0);
787
- let unmount;
835
+ let handle;
788
836
  try {
789
- unmount = childIsland.mount(slotEl, slotProps);
837
+ const internal = childIsland[ISLAND_MOUNT_INTERNAL];
838
+ handle = internal ? internal(slotEl, slotProps) : {
839
+ unmount: childIsland.mount(slotEl, slotProps),
840
+ updateProps: () => {}
841
+ };
790
842
  } finally {
791
843
  setActiveSub(prevSub);
792
844
  }
793
845
  mountedSlots.set(id, {
794
846
  el: slotEl,
795
- unmount
847
+ ...handle
796
848
  });
797
849
  }
798
850
  }
@@ -896,6 +948,7 @@ Element: ${host.outerHTML.slice(0, 120)}`);
896
948
  if (userCleanup) cleanups.push(userCleanup);
897
949
  }
898
950
  let initialized = false;
951
+ const initialRenderedHtml = initial.html;
899
952
  const stopRender = effect(() => {
900
953
  const { html: rendered, slots: newSlotMap } = renderWithCtx(() => fn({
901
954
  state,
@@ -905,7 +958,7 @@ Element: ${host.outerHTML.slice(0, 120)}`);
905
958
  const html = stylePrefix + rendered;
906
959
  if (!initialized) {
907
960
  initialized = true;
908
- return;
961
+ if (rendered === initialRenderedHtml) return;
909
962
  }
910
963
  detachListeners();
911
964
  stopBindings();
@@ -976,7 +1029,7 @@ Element: ${host.outerHTML.slice(0, 120)}`);
976
1029
  });
977
1030
  }
978
1031
  let tornDown = false;
979
- return () => {
1032
+ const unmount = () => {
980
1033
  if (tornDown) return;
981
1034
  tornDown = true;
982
1035
  if (__DEV__ && _mountedHosts) _mountedHosts.delete(host);
@@ -989,6 +1042,16 @@ Element: ${host.outerHTML.slice(0, 120)}`);
989
1042
  }
990
1043
  cleanups.forEach((c) => c());
991
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
+ };
992
1055
  }
993
1056
  /**
994
1057
  * Dual-mode island invocation:
@@ -1012,6 +1075,7 @@ Element: ${host.outerHTML.slice(0, 120)}`);
1012
1075
  };
1013
1076
  island.toString = (props) => renderToString(props, true);
1014
1077
  island.mount = (host, props) => mountIsland(host, props);
1078
+ island[ISLAND_MOUNT_INTERNAL] = (host, props) => mountIslandInternal(host, props);
1015
1079
  island.key = (key) => {
1016
1080
  if (typeof key !== "string" || key.trim().length === 0) throw new Error("island.key() requires a non-empty string.");
1017
1081
  if (key.includes(":")) throw new Error(`island.key() key cannot contain the slot separator ":" (got "${key}").`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ilha",
3
- "version": "0.4.1",
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>",