ilha 0.10.1 → 0.10.3

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.
@@ -242,6 +242,8 @@ type OnMountContext<TInput, TStateMap extends Record<string, unknown>, TDerivedM
242
242
  input: TInput;
243
243
  host: Element;
244
244
  hydrated: boolean;
245
+ /** AbortSignal that aborts when the island unmounts. */
246
+ signal: AbortSignal;
245
247
  };
246
248
  type HandlerContext<TInput, TStateMap extends Record<string, unknown>, TDerivedMap extends Record<string, unknown> = Record<never, never>, TActionMap extends ActionMap = Record<never, never>> = {
247
249
  state: IslandState<TStateMap>;
package/dist/index.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- import { A as __ilhaJsxSlot, B as mount, C as NativeEventModifier, D as SignalSetter, E as SignalAccessor, F as html, H as raw, I as ilha, L as ilhaComputed, M as context, N as css, O as SignalWriter, P as from, R as ilhaEffect, S as NativeEventHandler, T as RawHtml, U as untrack, V as onUncaughtError, _ as IslandState, a as EffectContext, b as MountResult, c as ExternalSignal, d as HydratableOptions, f as ISLAND_MOUNT_HANDLES, g as IslandDerived, h as IslandActions, i as DerivedValue, j as batch, k as __ilhaJsxEvent, l as HandlerContext, m as Island, n as ActionContext, o as ErrorContext, p as ISLAND_MOUNT_INTERNAL, r as DerivedAccessor, s as ErrorSource, t as ActionAccessor, u as HandlerContextFor, v as KeyedIsland, w as OnMountContext, x as NativeEventContext, y as MountOptions, z as ilhaSignal } from "./index-Bi_I3qEL.js";
1
+ import { A as __ilhaJsxSlot, B as mount, C as NativeEventModifier, D as SignalSetter, E as SignalAccessor, F as html, H as raw, I as ilha, L as ilhaComputed, M as context, N as css, O as SignalWriter, P as from, R as ilhaEffect, S as NativeEventHandler, T as RawHtml, U as untrack, V as onUncaughtError, _ as IslandState, a as EffectContext, b as MountResult, c as ExternalSignal, d as HydratableOptions, f as ISLAND_MOUNT_HANDLES, g as IslandDerived, h as IslandActions, i as DerivedValue, j as batch, k as __ilhaJsxEvent, l as HandlerContext, m as Island, n as ActionContext, o as ErrorContext, p as ISLAND_MOUNT_INTERNAL, r as DerivedAccessor, s as ErrorSource, t as ActionAccessor, u as HandlerContextFor, v as KeyedIsland, w as OnMountContext, x as NativeEventContext, y as MountOptions, z as ilhaSignal } from "./index-BDM797j2.js";
2
2
  export { ActionAccessor, ActionContext, DerivedAccessor, DerivedValue, EffectContext, ErrorContext, ErrorSource, ExternalSignal, HandlerContext, HandlerContextFor, HydratableOptions, ISLAND_MOUNT_HANDLES, ISLAND_MOUNT_INTERNAL, Island, IslandActions, IslandDerived, IslandState, KeyedIsland, MountOptions, MountResult, NativeEventContext, NativeEventHandler, NativeEventModifier, OnMountContext, RawHtml, SignalAccessor, SignalSetter, SignalWriter, __ilhaJsxEvent, __ilhaJsxSlot, batch, ilhaComputed as computed, context, css, ilha as default, ilhaEffect as effect, from, html, ilhaSignal, ilhaSignal as signal, mount, onUncaughtError, raw, untrack };
package/dist/index.js CHANGED
@@ -465,6 +465,7 @@ const RAW = Symbol.for("ilha.raw");
465
465
  const SIGNAL_ACCESSOR = Symbol.for("ilha.signalAccessor");
466
466
  const ISLAND = Symbol.for("ilha.island");
467
467
  const ISLAND_CALL = Symbol.for("ilha.islandCall");
468
+ const ASTRO_RENDERER_GLOBAL = Symbol.for("ilha.astroRenderer");
468
469
  /** @internal Internal hook used by a parent's mountSlots to mount a child island and
469
470
  * retain a handle to push updated props into it on subsequent parent
470
471
  * re-renders. Not part of the public surface. */
@@ -523,8 +524,8 @@ function isIslandCall(v) {
523
524
  function emitIslandSlot({ island, props, key }) {
524
525
  const ctx = currentRenderCtx();
525
526
  let id;
526
- if (key !== void 0) id = `k:${key}`;
527
- else id = ctx ? `p:${ctx.positional++}` : "p:0";
527
+ if (key === void 0) id = ctx ? `p:${ctx.positional++}` : "p:0";
528
+ else id = `k:${key}`;
528
529
  if (ctx) ctx.slots.set(id, {
529
530
  island,
530
531
  props
@@ -827,7 +828,8 @@ function ilhaHtml(strings, ...values) {
827
828
  }
828
829
  if (pendingBindSpecs !== "" || pendingEventSpecs !== "") {
829
830
  const { index: gtIdx, quote } = findTagCloseIndex(chunk, pendingQuote);
830
- if (gtIdx !== -1) {
831
+ if (gtIdx === -1) pendingQuote = quote;
832
+ else {
831
833
  const head = chunk.slice(0, gtIdx).replace(/\s*\/\s*$/, "");
832
834
  const bindAttr = pendingBindSpecs ? ` data-ilha-bind="${pendingBindSpecs}"` : "";
833
835
  const eventAttr = pendingEventSpecs ? ` data-ilha-on="${pendingEventSpecs}"` : "";
@@ -835,7 +837,7 @@ function ilhaHtml(strings, ...values) {
835
837
  pendingBindSpecs = "";
836
838
  pendingEventSpecs = "";
837
839
  pendingQuote = null;
838
- } else pendingQuote = quote;
840
+ }
839
841
  }
840
842
  if (i >= values.length) {
841
843
  result += chunk;
@@ -843,13 +845,14 @@ function ilhaHtml(strings, ...values) {
843
845
  }
844
846
  const value = values[i];
845
847
  const eventMatch = chunk.match(EVENT_PREFIX_RE);
846
- if (eventMatch && isEventAttributePosition(result + chunk, result.length + chunk.length - eventMatch[0].length)) {
848
+ const eventMatchStart = eventMatch ? chunk.length - eventMatch[0].length : -1;
849
+ const eventAttributeFragment = eventMatch !== null && chunk.slice(0, eventMatchStart).trim() === "";
850
+ if (eventMatch && (eventAttributeFragment || isEventAttributePosition(result + chunk, result.length + eventMatchStart))) {
847
851
  const eventName = eventMatch[1];
848
852
  const rawModifier = eventMatch[2];
849
853
  const modifier = NATIVE_EVENT_MODIFIERS.has(rawModifier) ? rawModifier : void 0;
850
854
  const openQuote = eventMatch[3] ?? null;
851
- const matchStart = chunk.length - eventMatch[0].length;
852
- const cleanPrefix = chunk.slice(0, matchStart).replace(/\s+$/, "");
855
+ const cleanPrefix = chunk.slice(0, eventMatchStart).replace(/\s+$/, "");
853
856
  result += cleanPrefix;
854
857
  if (ctx && typeof value === "function") {
855
858
  const index = ctx.events.length;
@@ -1077,46 +1080,128 @@ function buildDerivedAccessors(envelopes) {
1077
1080
  return accessors.get(key) ?? defaultDerivedAccessor();
1078
1081
  } });
1079
1082
  }
1083
+ const ILHA_QUERY = Symbol.for("ilha.store.query");
1084
+ const ILHA_QUERY_RUN = Symbol.for("ilha.store.query.run");
1085
+ const ILHA_QUERY_PENDING = Symbol.for("ilha.store.query.pending");
1086
+ const ILHA_QUERY_STACK = Symbol.for("ilha.store.query.stack");
1087
+ function queryCaptureStack() {
1088
+ const g = globalThis;
1089
+ let stack = g[ILHA_QUERY_STACK];
1090
+ if (!stack) {
1091
+ stack = [];
1092
+ g[ILHA_QUERY_STACK] = stack;
1093
+ }
1094
+ return stack;
1095
+ }
1096
+ function isQueryCall(value) {
1097
+ return value !== null && typeof value === "object" && ILHA_QUERY in value;
1098
+ }
1099
+ function resolveQueryCall(result, captureSlot) {
1100
+ const pending = globalThis[ILHA_QUERY_PENDING];
1101
+ globalThis[ILHA_QUERY_PENDING] = void 0;
1102
+ return captureSlot.queryCall ?? pending ?? (isQueryCall(result) ? result : void 0);
1103
+ }
1104
+ function runBrandedQuery(queryCall, abortSignal, env) {
1105
+ const writeEnv = (next) => {
1106
+ if (abortSignal.aborted) return;
1107
+ const prevSub = setActiveSub(void 0);
1108
+ env(next);
1109
+ setActiveSub(prevSub);
1110
+ };
1111
+ const run = queryCall[ILHA_QUERY_RUN];
1112
+ if (typeof run !== "function") {
1113
+ writeEnv({
1114
+ loading: false,
1115
+ value: void 0,
1116
+ error: /* @__PURE__ */ new Error("[@ilha] branded query is missing a callable runner")
1117
+ });
1118
+ return;
1119
+ }
1120
+ try {
1121
+ run.call(queryCall, abortSignal, {
1122
+ onResult: (value) => {
1123
+ writeEnv({
1124
+ loading: false,
1125
+ value,
1126
+ error: void 0
1127
+ });
1128
+ },
1129
+ onError: (err) => {
1130
+ writeEnv({
1131
+ loading: false,
1132
+ value: void 0,
1133
+ error: err
1134
+ });
1135
+ },
1136
+ onFetchStart: () => {
1137
+ const prevSub = setActiveSub(void 0);
1138
+ env({
1139
+ loading: true,
1140
+ value: env().value,
1141
+ error: void 0
1142
+ });
1143
+ setActiveSub(prevSub);
1144
+ }
1145
+ });
1146
+ } catch (err) {
1147
+ writeEnv({
1148
+ loading: false,
1149
+ value: void 0,
1150
+ error: err instanceof Error ? err : new Error(String(err))
1151
+ });
1152
+ }
1153
+ }
1080
1154
  function createDerivedProxy({ entries, state, input, derivedSnapshot }) {
1081
1155
  const envelopes = /* @__PURE__ */ new Map();
1082
1156
  for (const entry of entries) {
1083
1157
  let initialEnvelope;
1084
1158
  if (derivedSnapshot != null && entry.key in derivedSnapshot) initialEnvelope = { ...derivedSnapshot[entry.key] };
1085
- else if (!(entry.fn.constructor.name === "AsyncFunction" || entry.fn.constructor.name === "AsyncGeneratorFunction")) {
1159
+ else if (entry.fn.constructor.name === "AsyncFunction" || entry.fn.constructor.name === "AsyncGeneratorFunction") initialEnvelope = {
1160
+ loading: true,
1161
+ value: void 0,
1162
+ error: void 0
1163
+ };
1164
+ else {
1086
1165
  const ac = new AbortController();
1166
+ const captureSlot = {};
1167
+ const stack = queryCaptureStack();
1168
+ globalThis[ILHA_QUERY_PENDING] = void 0;
1169
+ stack.push(captureSlot);
1087
1170
  try {
1088
1171
  const result = entry.fn({
1089
1172
  state,
1090
1173
  input,
1091
1174
  signal: ac.signal
1092
1175
  });
1093
- if (!(result instanceof Promise)) initialEnvelope = {
1094
- loading: false,
1095
- value: result,
1176
+ if (resolveQueryCall(result, captureSlot)) initialEnvelope = {
1177
+ loading: true,
1178
+ value: void 0,
1096
1179
  error: void 0
1097
1180
  };
1098
- else {
1181
+ else if (result instanceof Promise) {
1099
1182
  initialEnvelope = {
1100
1183
  loading: true,
1101
1184
  value: void 0,
1102
1185
  error: void 0
1103
1186
  };
1104
1187
  result.catch(() => {});
1105
- }
1188
+ } else initialEnvelope = {
1189
+ loading: false,
1190
+ value: result,
1191
+ error: void 0
1192
+ };
1106
1193
  } catch (err) {
1194
+ globalThis[ILHA_QUERY_PENDING] = void 0;
1107
1195
  initialEnvelope = {
1108
1196
  loading: false,
1109
1197
  value: void 0,
1110
1198
  error: err instanceof Error ? err : new Error(String(err))
1111
1199
  };
1112
1200
  } finally {
1201
+ stack.pop();
1113
1202
  ac.abort();
1114
1203
  }
1115
- } else initialEnvelope = {
1116
- loading: true,
1117
- value: void 0,
1118
- error: void 0
1119
- };
1204
+ }
1120
1205
  const env = signal(initialEnvelope);
1121
1206
  envelopes.set(entry.key, env);
1122
1207
  }
@@ -1148,6 +1233,10 @@ function createDerivedProxy({ entries, state, input, derivedSnapshot }) {
1148
1233
  ac = new AbortController();
1149
1234
  const currentAc = ac;
1150
1235
  let result;
1236
+ const captureSlot = {};
1237
+ const stack = queryCaptureStack();
1238
+ globalThis[ILHA_QUERY_PENDING] = void 0;
1239
+ stack.push(captureSlot);
1151
1240
  try {
1152
1241
  result = entry.fn({
1153
1242
  state,
@@ -1155,6 +1244,7 @@ function createDerivedProxy({ entries, state, input, derivedSnapshot }) {
1155
1244
  signal: currentAc.signal
1156
1245
  });
1157
1246
  } catch (err) {
1247
+ globalThis[ILHA_QUERY_PENDING] = void 0;
1158
1248
  if (skipFirst) {
1159
1249
  skipFirst = false;
1160
1250
  return;
@@ -1167,12 +1257,19 @@ function createDerivedProxy({ entries, state, input, derivedSnapshot }) {
1167
1257
  });
1168
1258
  setActiveSub(prevSub);
1169
1259
  return;
1260
+ } finally {
1261
+ stack.pop();
1170
1262
  }
1263
+ const queryCall = resolveQueryCall(result, captureSlot);
1171
1264
  if (skipFirst) {
1172
1265
  skipFirst = false;
1173
1266
  if (result instanceof Promise) result.catch(() => {});
1174
1267
  return;
1175
1268
  }
1269
+ if (queryCall) {
1270
+ runBrandedQuery(queryCall, currentAc.signal, env);
1271
+ return;
1272
+ }
1176
1273
  if (!(result instanceof Promise)) {
1177
1274
  const prevSub = setActiveSub(void 0);
1178
1275
  env({
@@ -1193,6 +1290,16 @@ function createDerivedProxy({ entries, state, input, derivedSnapshot }) {
1193
1290
  setActiveSub(prevSub);
1194
1291
  result.then((value) => {
1195
1292
  if (currentAc.signal.aborted) return;
1293
+ if (isQueryCall(value)) {
1294
+ runBrandedQuery(value, currentAc.signal, env);
1295
+ return;
1296
+ }
1297
+ const latePending = globalThis[ILHA_QUERY_PENDING];
1298
+ globalThis[ILHA_QUERY_PENDING] = void 0;
1299
+ if (latePending) {
1300
+ runBrandedQuery(latePending, currentAc.signal, env);
1301
+ return;
1302
+ }
1196
1303
  env({
1197
1304
  loading: false,
1198
1305
  value,
@@ -1374,7 +1481,7 @@ function applyJsxEvents({ host, records, reportError, unmountSignal, onceFired }
1374
1481
  }
1375
1482
  return () => cleanups.forEach((cleanup) => cleanup());
1376
1483
  }
1377
- function applyTemplateBindings(host, binds) {
1484
+ function applyTemplateBindings(host, binds, phase = "all") {
1378
1485
  if (binds.length === 0) return () => {};
1379
1486
  const cleanups = [];
1380
1487
  const elements = [];
@@ -1389,14 +1496,16 @@ function applyTemplateBindings(host, binds) {
1389
1496
  const { event, read, write } = resolveBindOps(el, spec.kind);
1390
1497
  const accessor = record.accessor;
1391
1498
  if (spec.kind === "this") {
1392
- accessor(el);
1393
- cleanups.push(() => accessor(null));
1499
+ if (phase !== "listen") {
1500
+ accessor(el);
1501
+ cleanups.push(() => accessor(null));
1502
+ }
1394
1503
  continue;
1395
1504
  }
1396
- try {
1505
+ if (phase !== "listen") try {
1397
1506
  write(el, accessor());
1398
1507
  } catch (err) {}
1399
- if (event === null) continue;
1508
+ if (phase === "reflect" || event === null) continue;
1400
1509
  const listener = () => {
1401
1510
  const raw = read(el);
1402
1511
  if (spec.kind === "group") {
@@ -1621,7 +1730,7 @@ var IlhaBuilder = class IlhaBuilder {
1621
1730
  }
1622
1731
  render(fn) {
1623
1732
  const { schema, defaultInput, states, deriveds, actions, ons, effects, onMounts, onErrors, transition, css: cssSource, as: slotAs } = this._cfg;
1624
- const stylePrefix = cssSource != null ? buildScopedStyle(cssSource) : "";
1733
+ const stylePrefix = cssSource == null ? "" : buildScopedStyle(cssSource);
1625
1734
  const configuredSlotTag = slotAs ?? "div";
1626
1735
  function resolveInput(props) {
1627
1736
  const merged = {
@@ -1724,6 +1833,7 @@ var IlhaBuilder = class IlhaBuilder {
1724
1833
  if (onMounts.length === 0) return;
1725
1834
  if (currentRenderCtx()) return;
1726
1835
  const host = createSsrOnMountHost();
1836
+ const controller = new AbortController();
1727
1837
  const ssrCleanups = [];
1728
1838
  for (const entry of onMounts) {
1729
1839
  const prevSub = setActiveSub(void 0);
@@ -1735,7 +1845,8 @@ var IlhaBuilder = class IlhaBuilder {
1735
1845
  action,
1736
1846
  input,
1737
1847
  host,
1738
- hydrated: false
1848
+ hydrated: false,
1849
+ signal: controller.signal
1739
1850
  });
1740
1851
  } catch (err) {
1741
1852
  const error = err instanceof Error ? err : new Error(String(err));
@@ -1759,6 +1870,7 @@ var IlhaBuilder = class IlhaBuilder {
1759
1870
  }
1760
1871
  if (typeof userCleanup === "function") ssrCleanups.push(userCleanup);
1761
1872
  }
1873
+ controller.abort();
1762
1874
  for (const teardown of ssrCleanups.reverse()) try {
1763
1875
  teardown();
1764
1876
  } catch (err) {
@@ -2246,7 +2358,8 @@ var IlhaBuilder = class IlhaBuilder {
2246
2358
  action,
2247
2359
  input,
2248
2360
  host,
2249
- hydrated
2361
+ hydrated,
2362
+ signal: unmountController.signal
2250
2363
  });
2251
2364
  } catch (err) {
2252
2365
  reportError(err, "mount");
@@ -2278,7 +2391,8 @@ var IlhaBuilder = class IlhaBuilder {
2278
2391
  liveHost: preserveSSRDom ? host : void 0
2279
2392
  });
2280
2393
  if (!preserveSSRDom) host.innerHTML = stylePrefix + initial.html;
2281
- let stopBindings = applyTemplateBindings(host, initial.binds);
2394
+ const stopInitialBindingReflection = applyTemplateBindings(host, initial.binds, "reflect");
2395
+ let stopBindings = stopInitialBindingReflection;
2282
2396
  cleanups.push(() => stopBindings());
2283
2397
  let stopJsxEvents = applyJsxEvents({
2284
2398
  host,
@@ -2292,6 +2406,11 @@ var IlhaBuilder = class IlhaBuilder {
2292
2406
  cleanups.push(() => mountedSlots.forEach((entry) => entry.unmount()));
2293
2407
  attachListeners();
2294
2408
  if (!hydrated && onMounts.length > 0) invokeOnMounts();
2409
+ const stopInitialBindingListeners = applyTemplateBindings(host, initial.binds, "listen");
2410
+ stopBindings = () => {
2411
+ stopInitialBindingListeners();
2412
+ stopInitialBindingReflection();
2413
+ };
2295
2414
  for (const entry of effects) {
2296
2415
  let userCleanup;
2297
2416
  let runController = null;
@@ -2527,6 +2646,8 @@ var IlhaBuilder = class IlhaBuilder {
2527
2646
  return keyed;
2528
2647
  };
2529
2648
  island[ISLAND] = true;
2649
+ const astroRendererName = globalThis[ASTRO_RENDERER_GLOBAL];
2650
+ if (typeof astroRendererName === "string") island[Symbol.for("astro:renderer")] = astroRendererName;
2530
2651
  island.define = (tagName, options) => {
2531
2652
  if (typeof customElements === "undefined" || typeof HTMLElement === "undefined") {
2532
2653
  `${tagName}`;
@@ -1,2 +1,2 @@
1
- import { a as JSX, r as jsxDEV, t as Fragment } from "./jsx-runtime-DJ2B4fvA.js";
1
+ import { a as JSX, r as jsxDEV, t as Fragment } from "./jsx-runtime-C6R6IiOD.js";
2
2
  export { Fragment, type JSX, jsxDEV };
@@ -1,4 +1,4 @@
1
- import { C as NativeEventModifier, E as SignalAccessor, O as SignalWriter, S as NativeEventHandler, T as RawHtml } from "./index-Bi_I3qEL.js";
1
+ import { C as NativeEventModifier, E as SignalAccessor, O as SignalWriter, S as NativeEventHandler, T as RawHtml } from "./index-BDM797j2.js";
2
2
  //#region src/jsx-types.d.ts
3
3
  declare namespace JSX {
4
4
  export type Child = unknown;
@@ -1,2 +1,2 @@
1
- import { a as JSX, i as jsxs, n as jsx, r as jsxDEV, t as Fragment } from "./jsx-runtime-DJ2B4fvA.js";
1
+ import { a as JSX, i as jsxs, n as jsx, r as jsxDEV, t as Fragment } from "./jsx-runtime-C6R6IiOD.js";
2
2
  export { Fragment, type JSX, jsx, jsxDEV, jsxs };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ilha",
3
- "version": "0.10.1",
3
+ "version": "0.10.3",
4
4
  "description": "A tiny, framework-free island architecture library",
5
5
  "keywords": [
6
6
  "framework-free",
@@ -53,6 +53,7 @@
53
53
  },
54
54
  "scripts": {
55
55
  "build": "tsc && tsdown",
56
+ "cleanup": "rimraf dist node_modules",
56
57
  "test": "bun test"
57
58
  },
58
59
  "dependencies": {