ilha 0.4.4 → 0.6.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/README.md CHANGED
@@ -326,40 +326,6 @@ An error thrown inside an `.onError()` handler does not break other registered h
326
326
 
327
327
  ---
328
328
 
329
- ### `.bind(selector, stateKey | externalSignal)`
330
-
331
- Two-way binds a form element to a state key or an external signal. Handles `input`, `select`, `textarea`, `checkbox`, `radio`, and `number` inputs automatically.
332
-
333
- ```ts
334
- ilha
335
- .state("name", "")
336
- .state("agreed", false)
337
- .bind("input.name", "name")
338
- .bind("input[type=checkbox]", "agreed")
339
- .render(
340
- ({ state }) => html`
341
- <form>
342
- <input class="name" value="${state.name}" />
343
- <input type="checkbox" />
344
- <p>Hello, ${state.name}! Agreed: ${state.agreed}</p>
345
- </form>
346
- `,
347
- );
348
- ```
349
-
350
- You can also bind to an external signal — either a free-standing one created with `signal()` or a named global one created with `context()`:
351
-
352
- ```ts
353
- import { signal, context } from "ilha";
354
-
355
- const username = signal("");
356
- const theme = context("app.theme", "light");
357
-
358
- ilha.bind("input.name", username).bind("select.theme", theme).render(/* … */);
359
- ```
360
-
361
- ---
362
-
363
329
  ### `.css(strings, ...values)`
364
330
 
365
331
  Attaches scoped styles to the island. Accepts a tagged template literal or a plain string. The CSS is automatically wrapped in a `@scope` rule bounded to the island host, so styles are contained within the island and do not leak into child islands.
@@ -616,7 +582,7 @@ const Footer = ilha.render(() => html`<footer>Logged in as ${username()}</footer
616
582
  username("alice");
617
583
  ```
618
584
 
619
- Pairs naturally with `.bind()` for two-way form bindings against module-level state.
585
+ Works naturally with `bind:` template syntax for two-way form bindings against module-level state.
620
586
 
621
587
  ---
622
588
 
@@ -635,7 +601,7 @@ theme("dark"); // → sets to "dark"
635
601
 
636
602
  Safe to call in both SSR and browser environments.
637
603
 
638
- > **`signal()` vs `context()`** — both return the same accessor shape and can be passed to `.bind()`. Use `signal()` for one-off shared state where you'd hold the reference yourself; use `context()` when you want a name-keyed registry so the same signal can be looked up from anywhere by string key.
604
+ > **`signal()` vs `context()`** — both return the same accessor shape and can be used with `bind:` template syntax. Use `signal()` for one-off shared state where you'd hold the reference yourself; use `context()` when you want a name-keyed registry so the same signal can be looked up from anywhere by string key.
639
605
 
640
606
  ---
641
607
 
@@ -713,6 +679,32 @@ Interpolation rules:
713
679
  | Island / Island call | Emitted as `data-ilha-slot` host element |
714
680
  | Array | Each item processed recursively (no commas) |
715
681
 
682
+ **Template bindings** — use `bind:property=${signal}` inside `html\`\`` to create two-way bindings between form elements and signals:
683
+
684
+ ```ts
685
+ ilha.state("name", "").render(
686
+ ({ state }) => html`
687
+ <input bind:value=${state.name} />
688
+ <p>Hello, ${state.name()}!</p>
689
+ `,
690
+ );
691
+ ```
692
+
693
+ Supported bindings:
694
+
695
+ | Binding | Element | Bound property | Trigger event |
696
+ | -------------------- | ------------------------------------------------- | ------------------- | ------------- |
697
+ | `bind:value` | `<input>`, `<textarea>`, `<select>` | `value` | `input` |
698
+ | `bind:valueAsNumber` | `<input type="number">` | `valueAsNumber` | `input` |
699
+ | `bind:valueAsDate` | `<input type="date">` | `valueAsDate` | `input` |
700
+ | `bind:checked` | `<input type="checkbox">` | `checked` | `change` |
701
+ | `bind:group` | `<input type="radio">`, `<input type="checkbox">` | `checked` / `value` | `change` |
702
+ | `bind:open` | `<details>` | `open` | `toggle` |
703
+ | `bind:files` | `<input type="file">` | `files` | `change` |
704
+ | `bind:this` | Any element | element reference | — |
705
+
706
+ `bind:group` connects multiple inputs to a single signal — radio buttons hold the selected `value`, checkboxes hold an array of checked values. `bind:this` writes the DOM element into a signal on mount and `null` on unmount. External signals from `signal()` or `context()` work as binding targets too, enabling shared state across islands.
707
+
716
708
  **List rendering pattern:**
717
709
 
718
710
  ```ts
package/dist/index.d.ts CHANGED
@@ -55,6 +55,14 @@ interface MarkedSignalAccessor<T> {
55
55
  }
56
56
  declare function ilhaRaw(value: string): RawHtml;
57
57
  declare function ilhaCss(strings: TemplateStringsArray | string, ...values: (string | number)[]): string;
58
+ type JsxChild = unknown;
59
+ type JsxProps = Record<string, unknown> | null | undefined;
60
+ type JsxType = string | ((props: Record<string, unknown>) => unknown);
61
+ declare function ilhaJsx(type: JsxType, props: JsxProps, ...children: JsxChild[]): RawHtml;
62
+ declare function ilhaFragment(props: {
63
+ children?: JsxChild;
64
+ } | null, ...children: JsxChild[]): RawHtml;
65
+ declare function ilhaJsxDEV(type: JsxType, props: JsxProps): RawHtml;
58
66
  declare function ilhaHtml(strings: TemplateStringsArray, ...values: unknown[]): RawHtml;
59
67
  type ContextSignal<T> = {
60
68
  (): T;
@@ -64,7 +72,8 @@ declare function ilhaContext<T>(key: string, initial: T): ContextSignal<T>;
64
72
  /**
65
73
  * Create a free-standing reactive signal that lives outside any island.
66
74
  * Useful for sharing state across islands without prop drilling, or for
67
- * binding form inputs to module-level state via `.bind(selector, signal)`.
75
+ * binding form inputs to module-level state via the `bind:value=${signal}`
76
+ * template syntax.
68
77
  *
69
78
  * The returned accessor is a getter when called with no arguments and a
70
79
  * setter when called with one. Reading it inside a `.derived()`, `.effect()`,
@@ -106,16 +115,12 @@ interface DerivedEntry<TInput, TStateMap extends Record<string, unknown>> {
106
115
  fn: DerivedFn<TInput, TStateMap, unknown>;
107
116
  }
108
117
  type IslandDerived<TDerivedMap extends Record<string, unknown>> = { readonly [K in keyof TDerivedMap]: DerivedValue<TDerivedMap[K]> };
109
- type ExternalSignal<T = unknown> = {
118
+ interface ExternalSignal<T = unknown> {
110
119
  (): T;
111
120
  (value: T): void;
112
- };
113
- interface BindEntry<TStateMap extends Record<string, unknown>> {
114
- selector: string;
115
- target: (keyof TStateMap & string) | ExternalSignal;
116
121
  }
117
122
  type SignalAccessor<T> = MarkedSignalAccessor<T>;
118
- type MergeState<TStateMap extends Record<string, unknown>, K extends string, V> = { [P in keyof TStateMap as P extends K ? never : P]-?: TStateMap[P] } & Record<K, V>;
123
+ type MergeState<TStateMap extends Record<string, unknown>, K extends string, V> = Omit<TStateMap, K> & Record<K, V>;
119
124
  type IslandState<TStateMap extends Record<string, unknown>> = { readonly [K in keyof TStateMap]-?: SignalAccessor<TStateMap[K]> };
120
125
  interface HydratableOptions {
121
126
  name: string;
@@ -156,14 +161,14 @@ type EffectContext<TInput, TStateMap extends Record<string, unknown>> = {
156
161
  */
157
162
  signal: AbortSignal;
158
163
  };
159
- type OnMountContext<TInput, TStateMap extends Record<string, unknown>, TDerivedMap extends Record<string, unknown> = Record<string, never>> = {
164
+ type OnMountContext<TInput, TStateMap extends Record<string, unknown>, TDerivedMap extends Record<string, unknown> = Record<never, never>> = {
160
165
  state: IslandState<TStateMap>;
161
166
  derived: IslandDerived<TDerivedMap>;
162
167
  input: TInput;
163
168
  host: Element;
164
169
  hydrated: boolean;
165
170
  };
166
- type HandlerContext<TInput, TStateMap extends Record<string, unknown>, TDerivedMap extends Record<string, unknown> = Record<string, never>> = {
171
+ type HandlerContext<TInput, TStateMap extends Record<string, unknown>, TDerivedMap extends Record<string, unknown> = Record<never, never>> = {
167
172
  state: IslandState<TStateMap>;
168
173
  derived: IslandDerived<TDerivedMap>;
169
174
  input: TInput;
@@ -180,17 +185,15 @@ type HandlerContext<TInput, TStateMap extends Record<string, unknown>, TDerivedM
180
185
  */
181
186
  signal: AbortSignal;
182
187
  };
183
- type HTMLEventName = keyof HTMLElementEventMap & string;
184
- type Modifier = "once" | "capture" | "passive" | "abortable";
185
- type WithModifiers<E extends string> = E | `${E}:${Modifier}` | `${E}:${Modifier}:${Modifier}` | `${E}:${Modifier}:${Modifier}:${Modifier}`;
186
- type OnSelectorString = `@${WithModifiers<HTMLEventName>}` | `${string}@${WithModifiers<HTMLEventName>}`;
187
- type HandlerContextFor<TInput, TStateMap extends Record<string, unknown>, TEventName extends string, TDerivedMap extends Record<string, unknown> = Record<string, never>> = {
188
+ type HTMLEventFor<E extends string> = E extends keyof HTMLElementEventMap ? HTMLElementEventMap[E] : Event;
189
+ type HTMLTargetFor<E extends string> = E extends keyof HTMLElementEventMap ? NonNullable<HTMLElementEventMap[E]["target"]> extends Element ? NonNullable<HTMLElementEventMap[E]["target"]> : Element : Element;
190
+ type HandlerContextFor<TInput, TStateMap extends Record<string, unknown>, TEventName extends string, TDerivedMap extends Record<string, unknown> = Record<never, never>> = {
188
191
  state: IslandState<TStateMap>;
189
192
  derived: IslandDerived<TDerivedMap>;
190
193
  input: TInput;
191
194
  host: Element;
192
- target: TEventName extends keyof HTMLElementEventMap ? HTMLElementEventMap[TEventName]["target"] extends Element | null ? NonNullable<HTMLElementEventMap[TEventName]["target"]> : Element : Element;
193
- event: TEventName extends keyof HTMLElementEventMap ? HTMLElementEventMap[TEventName] : Event;
195
+ target: HTMLTargetFor<TEventName>;
196
+ event: HTMLEventFor<TEventName>;
194
197
  /**
195
198
  * AbortSignal that fires when the island unmounts. If the handler's selector
196
199
  * was registered with the `:abortable` modifier, the signal is also aborted
@@ -203,7 +206,7 @@ interface StateEntry<TInput> {
203
206
  key: string;
204
207
  init: StateInit<TInput, unknown>;
205
208
  }
206
- interface OnEntry<TInput, TStateMap extends Record<string, unknown>, TDerivedMap extends Record<string, unknown> = Record<string, never>> {
209
+ interface OnEntry<TInput, TStateMap extends Record<string, unknown>, TDerivedMap extends Record<string, unknown> = Record<never, never>> {
207
210
  selector: string;
208
211
  event: string;
209
212
  options: AddEventListenerOptions;
@@ -217,7 +220,7 @@ interface EffectEntry<TInput, TStateMap extends Record<string, unknown>> {
217
220
  * from `.on()` handlers; `"effect"` covers sync throws from `.effect()` runs
218
221
  * (async work spawned inside an effect is not awaited by the runtime). */
219
222
  type ErrorSource = "on" | "effect";
220
- type ErrorContext<TInput, TStateMap extends Record<string, unknown>, TDerivedMap extends Record<string, unknown> = Record<string, never>> = {
223
+ type ErrorContext<TInput, TStateMap extends Record<string, unknown>, TDerivedMap extends Record<string, unknown> = Record<never, never>> = {
221
224
  error: Error;
222
225
  source: ErrorSource;
223
226
  state: IslandState<TStateMap>;
@@ -251,20 +254,16 @@ interface BuilderConfig<TInput, TStateMap extends Record<string, unknown>, TDeri
251
254
  onMounts: OnMountEntry<TInput, TStateMap, TDerivedMap>[];
252
255
  onErrors: OnErrorEntry<TInput, TStateMap, TDerivedMap>[];
253
256
  transition: TransitionOptions | null;
254
- binds: BindEntry<TStateMap>[];
255
257
  css: string | null;
256
258
  }
257
- declare class IlhaBuilder<TInput extends Record<string, unknown>, TStateMap extends Record<string, unknown>, TDerivedMap extends Record<string, unknown> = Record<string, never>> {
259
+ declare class IlhaBuilder<TInput extends Record<string, unknown>, TStateMap extends Record<string, unknown>, TDerivedMap extends Record<string, unknown> = Record<never, never>> {
258
260
  readonly _cfg: BuilderConfig<TInput, TStateMap, TDerivedMap>;
259
261
  constructor(cfg: BuilderConfig<TInput, TStateMap, TDerivedMap>);
260
- input<T extends Record<string, unknown>>(): IlhaBuilder<T, Record<string, never>, Record<string, never>>;
261
- input<S extends StandardSchemaV1>(schema: S): IlhaBuilder<StandardSchemaV1.InferOutput<S> & Record<string, unknown>, Record<string, never>, Record<string, never>>;
262
+ input<T extends Record<string, unknown>>(): IlhaBuilder<T, Record<never, never>, Record<never, never>>;
263
+ input<S extends StandardSchemaV1>(schema: S): IlhaBuilder<StandardSchemaV1.InferOutput<S> & Record<string, unknown>, Record<never, never>, Record<never, never>>;
262
264
  state<V = undefined, K extends string = string>(key: K, init?: StateInit<TInput, V> | undefined): IlhaBuilder<TInput, MergeState<TStateMap, K, V>, TDerivedMap>;
263
265
  derived<K extends string, V>(key: K, fn: DerivedFn<TInput, TStateMap, V>): IlhaBuilder<TInput, TStateMap, TDerivedMap & Record<K, V>>;
264
- bind(selector: string, stateKey: keyof TStateMap & string): IlhaBuilder<TInput, TStateMap, TDerivedMap>;
265
- bind<T>(selector: string, externalSignal: ExternalSignal<T>): IlhaBuilder<TInput, TStateMap, TDerivedMap>;
266
- on<S extends OnSelectorString>(selectorOrCombined: S, handler: (ctx: S extends `${string}@${infer E}:${string}` ? HandlerContextFor<TInput, TStateMap, E, TDerivedMap> : S extends `${string}@${infer E}` ? HandlerContextFor<TInput, TStateMap, E, TDerivedMap> : HandlerContext<TInput, TStateMap, TDerivedMap>) => void | Promise<void>): IlhaBuilder<TInput, TStateMap, TDerivedMap>;
267
- on(selectorOrCombined: string, handler: (ctx: HandlerContext<TInput, TStateMap, TDerivedMap>) => void | Promise<void>): IlhaBuilder<TInput, TStateMap, TDerivedMap>;
266
+ on<S extends string>(selectorOrCombined: S, handler: (ctx: S extends `${string}@${infer E}:${string}` ? HandlerContextFor<TInput, TStateMap, E, TDerivedMap> : S extends `${string}@${infer E}` ? HandlerContextFor<TInput, TStateMap, E, TDerivedMap> : HandlerContext<TInput, TStateMap, TDerivedMap>) => void | Promise<void>): IlhaBuilder<TInput, TStateMap, TDerivedMap>;
268
267
  effect(fn: (ctx: EffectContext<TInput, TStateMap>) => (() => void) | void): IlhaBuilder<TInput, TStateMap, TDerivedMap>;
269
268
  onMount(fn: (ctx: OnMountContext<TInput, TStateMap, TDerivedMap>) => (() => void) | void): IlhaBuilder<TInput, TStateMap, TDerivedMap>;
270
269
  onError(fn: (ctx: ErrorContext<TInput, TStateMap, TDerivedMap>) => void): IlhaBuilder<TInput, TStateMap, TDerivedMap>;
@@ -275,9 +274,13 @@ declare class IlhaBuilder<TInput extends Record<string, unknown>, TStateMap exte
275
274
  declare function ilhaFrom<TInput, TStateMap extends Record<string, unknown>>(selector: string | Element, island: Island<TInput, TStateMap>, props?: Partial<TInput>): (() => void) | null;
276
275
  type IslandRegistry = Record<string, AnyIsland>;
277
276
  declare function mountAll(registry: IslandRegistry, options?: MountOptions): MountResult;
278
- declare const ilha: IlhaBuilder<Record<string, unknown>, Record<string, never>, Record<string, never>> & {
277
+ declare const ilha: IlhaBuilder<Record<string, unknown>, Record<never, never>, Record<never, never>> & {
279
278
  html: typeof ilhaHtml;
280
279
  raw: typeof ilhaRaw;
280
+ jsx: typeof ilhaJsx;
281
+ jsxs: typeof ilhaJsx;
282
+ jsxDEV: typeof ilhaJsxDEV;
283
+ Fragment: typeof ilhaFragment;
281
284
  mount: typeof mountAll;
282
285
  from: typeof ilhaFrom;
283
286
  context: typeof ilhaContext;
@@ -287,9 +290,13 @@ declare const ilha: IlhaBuilder<Record<string, unknown>, Record<string, never>,
287
290
  };
288
291
  declare const html: typeof ilhaHtml;
289
292
  declare const raw: typeof ilhaRaw;
293
+ declare const jsx: typeof ilhaJsx;
294
+ declare const jsxs: typeof ilhaJsx;
295
+ declare const jsxDEV: typeof ilhaJsxDEV;
296
+ declare const Fragment: typeof ilhaFragment;
290
297
  declare const css: typeof ilhaCss;
291
298
  declare const mount: typeof mountAll;
292
299
  declare const from: typeof ilhaFrom;
293
300
  declare const context: typeof ilhaContext;
294
301
  //#endregion
295
- export { DerivedValue, ErrorContext, ErrorSource, ExternalSignal, HandlerContext, HandlerContextFor, HydratableOptions, ISLAND_MOUNT_INTERNAL, Island, IslandDerived, IslandState, KeyedIsland, MountOptions, MountResult, OnMountContext, RawHtml, SignalAccessor, batch, context, css, ilha as default, from, html, ilhaSignal, ilhaSignal as signal, mount, raw, untrack };
302
+ export { DerivedValue, ErrorContext, ErrorSource, ExternalSignal, Fragment, HandlerContext, HandlerContextFor, HydratableOptions, ISLAND_MOUNT_INTERNAL, Island, IslandDerived, IslandState, KeyedIsland, MountOptions, MountResult, OnMountContext, RawHtml, SignalAccessor, batch, context, css, ilha as default, from, html, ilhaSignal, ilhaSignal as signal, jsx, jsxDEV, jsxs, mount, raw, untrack };
package/dist/index.js CHANGED
@@ -109,7 +109,8 @@ function pushRenderCtx(liveHost, asyncChildren) {
109
109
  slots: /* @__PURE__ */ new Map(),
110
110
  positional: 0,
111
111
  liveHost,
112
- pending: asyncChildren ? /* @__PURE__ */ new Map() : void 0
112
+ pending: asyncChildren ? /* @__PURE__ */ new Map() : void 0,
113
+ binds: []
113
114
  };
114
115
  renderCtxStack.push(ctx);
115
116
  return ctx;
@@ -190,8 +191,120 @@ function ilhaCss(strings, ...values) {
190
191
  }
191
192
  return result;
192
193
  }
194
+ const SAFE_NAME_RE = /^[A-Za-z_:][A-Za-z0-9:._-]*$/;
195
+ const SAFE_BIND_LOCAL_RE = /^[A-Za-z][A-Za-z0-9]*$/;
196
+ const VOID_ELEMENTS = new Set([
197
+ "area",
198
+ "base",
199
+ "br",
200
+ "col",
201
+ "embed",
202
+ "hr",
203
+ "img",
204
+ "input",
205
+ "link",
206
+ "meta",
207
+ "param",
208
+ "source",
209
+ "track",
210
+ "wbr"
211
+ ]);
212
+ const SAFE_CSS_PROP_RE = /^(-{2}[a-zA-Z][a-zA-Z0-9-]*|-?[a-zA-Z][a-zA-Z0-9-]*)$/;
213
+ const URL_ATTRS = new Set([
214
+ "href",
215
+ "src",
216
+ "action",
217
+ "formaction",
218
+ "cite",
219
+ "data",
220
+ "poster"
221
+ ]);
222
+ const SAFE_URL_RE = /^(?!javascript:|data:text\/html|data:text\/xml|data:application\/xhtml\+xml|data:image\/svg|vbscript:)/i;
223
+ function normalizeClass(value) {
224
+ if (Array.isArray(value)) return value.filter(Boolean).join(" ");
225
+ if (value && typeof value === "object") return Object.entries(value).filter(([, enabled]) => Boolean(enabled)).map(([name]) => name).join(" ");
226
+ return String(value);
227
+ }
228
+ function normalizeJsxChildren(props, children) {
229
+ const propChildren = props && "children" in props ? props.children : void 0;
230
+ return (children.length > 0 ? children : propChildren === void 0 ? [] : [propChildren]).flat(1);
231
+ }
232
+ function serializeStyle(value) {
233
+ return Object.entries(value).map(([k, v]) => {
234
+ if (!SAFE_CSS_PROP_RE.test(k)) return "";
235
+ const prop = k.replace(/[A-Z]/g, (m) => `-${m.toLowerCase()}`);
236
+ let safeV = String(v).replace(/["<>{};]/g, "");
237
+ if (/expression\(/i.test(safeV) || /^javascript:/i.test(safeV)) return "";
238
+ return `${prop}:${safeV}`;
239
+ }).filter(Boolean).join(";");
240
+ }
241
+ function pushJsxAttr(chunks, values, name, value) {
242
+ if (value == null || value === false || name === "children" || name === "key") return;
243
+ if (name === "__proto__" || name === "constructor" || name === "prototype") return;
244
+ if (name.startsWith("bind:")) {
245
+ const [prefix, localName, ...rest] = name.split(":");
246
+ if (prefix !== "bind" || rest.length > 0 || !localName || !SAFE_BIND_LOCAL_RE.test(localName)) return;
247
+ if (!isSignalAccessor(value)) return;
248
+ const safeName = `${prefix}:${localName}`;
249
+ chunks[chunks.length - 1] += ` ${safeName}=`;
250
+ values.push(value);
251
+ chunks.push("");
252
+ return;
253
+ }
254
+ if (!SAFE_NAME_RE.test(name)) return;
255
+ let safeName = name;
256
+ if (safeName === "className") safeName = "class";
257
+ if (safeName === "htmlFor") safeName = "for";
258
+ if (safeName.startsWith("on")) return;
259
+ if (safeName === "class") value = normalizeClass(value);
260
+ if (safeName === "style" && value && typeof value === "object") value = serializeStyle(value);
261
+ if ((URL_ATTRS.has(safeName) || /:(href|src|action|formaction|cite|data|poster)$/.test(safeName)) && typeof value === "string" && !SAFE_URL_RE.test(value.trim())) return;
262
+ if (value === true) {
263
+ chunks[chunks.length - 1] += ` ${safeName}`;
264
+ return;
265
+ }
266
+ chunks[chunks.length - 1] += ` ${safeName}="`;
267
+ values.push(value);
268
+ chunks.push("\"");
269
+ }
270
+ function renderJsxElement(type, props, children) {
271
+ const chunks = [`<${type}`];
272
+ const values = [];
273
+ if (props) for (const [name, value] of Object.entries(props)) pushJsxAttr(chunks, values, name, value);
274
+ chunks[chunks.length - 1] += ">";
275
+ if (!VOID_ELEMENTS.has(type)) {
276
+ for (const child of children) {
277
+ values.push(child);
278
+ chunks.push("");
279
+ }
280
+ chunks[chunks.length - 1] += `</${type}>`;
281
+ } else if (children.length > 0 && __DEV__) warn(`Void element <${type}> should not have children. They will be ignored.`);
282
+ return ilhaHtml(chunks, ...values);
283
+ }
284
+ function ilhaJsx(type, props, ...children) {
285
+ const normalizedChildren = normalizeJsxChildren(props, children);
286
+ if (typeof type === "function") {
287
+ const componentProps = {
288
+ ...props ?? {},
289
+ ...normalizedChildren.length > 0 ? { children: normalizedChildren } : {}
290
+ };
291
+ delete componentProps["key"];
292
+ const out = type(Object.keys(componentProps).length ? componentProps : {});
293
+ if (typeof out === "string") return ilhaHtml`${out}`;
294
+ if (out && typeof out === "object" && RAW in out) return out;
295
+ return ilhaHtml`${out}`;
296
+ }
297
+ return renderJsxElement(type, props, normalizedChildren);
298
+ }
299
+ function ilhaFragment(props, ...children) {
300
+ const normalizedChildren = normalizeJsxChildren(props, children);
301
+ return ilhaHtml(["", ...normalizedChildren.map(() => "")], ...normalizedChildren);
302
+ }
303
+ function ilhaJsxDEV(type, props) {
304
+ return ilhaJsx(type, props);
305
+ }
193
306
  function interpolateValue(v) {
194
- if (v == null) return "";
307
+ if (v == null || v === true || v === false) return "";
195
308
  if (Array.isArray(v)) return v.map(interpolateValue).join("");
196
309
  if (typeof v === "object" && RAW in v) return v.value;
197
310
  if (isIslandCall(v)) {
@@ -203,12 +316,117 @@ function interpolateValue(v) {
203
316
  if (typeof v === "function") return escapeHtml(v());
204
317
  return escapeHtml(v);
205
318
  }
319
+ const BIND_VALID_KINDS = new Set([
320
+ "value",
321
+ "checked",
322
+ "valueAsNumber",
323
+ "valueAsDate",
324
+ "files",
325
+ "open",
326
+ "group",
327
+ "this"
328
+ ]);
329
+ const BIND_PREFIX_RE = /\bbind:([a-zA-Z]+)\s*=\s*("|')?$/;
330
+ function formatDateForInput(d) {
331
+ if (d instanceof Date) {
332
+ if (isNaN(d.getTime())) {
333
+ if (__DEV__) warn("bind:valueAsDate received an invalid Date object — value attribute will be empty.");
334
+ return "";
335
+ }
336
+ const pad = (n) => String(n).padStart(2, "0");
337
+ return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`;
338
+ }
339
+ return "";
340
+ }
341
+ function extractElementStaticValue(prefix) {
342
+ const lastOpen = prefix.lastIndexOf("<");
343
+ if (lastOpen === -1) return null;
344
+ const tagPrefix = prefix.slice(lastOpen);
345
+ if (tagPrefix.includes(">")) return null;
346
+ const m = tagPrefix.match(/\bvalue\s*=\s*("([^"]*)"|'([^']*)'|([^\s>]+))/);
347
+ if (!m) return null;
348
+ return m[2] ?? m[3] ?? m[4] ?? null;
349
+ }
350
+ function emitBindSSR(kind, index, accessor, prefixForStaticPeek) {
351
+ const spec = `${kind}:${index}`;
352
+ let v;
353
+ try {
354
+ v = accessor();
355
+ } catch {
356
+ v = void 0;
357
+ }
358
+ switch (kind) {
359
+ case "value": return [` value="${escapeHtml(v ?? "")}"`, spec];
360
+ case "valueAsNumber": return [` value="${escapeHtml(v == null ? "" : String(v))}"`, spec];
361
+ case "valueAsDate": return [` value="${escapeHtml(formatDateForInput(v))}"`, spec];
362
+ case "checked": return [v ? ` checked` : ``, spec];
363
+ case "open": return [v ? ` open` : ``, spec];
364
+ case "files": return [``, spec];
365
+ case "this": return [``, spec];
366
+ case "group": {
367
+ const optionValue = extractElementStaticValue(prefixForStaticPeek);
368
+ if (optionValue == null) return [``, spec];
369
+ return [(Array.isArray(v) ? v.map(String).includes(optionValue) : v != null && String(v) === optionValue) ? ` checked` : ``, spec];
370
+ }
371
+ }
372
+ }
206
373
  function ilhaHtml(strings, ...values) {
207
374
  let result = "";
375
+ let stripLeadingQuote = null;
376
+ let pendingBindSpecs = "";
377
+ const ctx = currentRenderCtx();
208
378
  for (let i = 0; i < strings.length; i++) {
209
- result += strings[i];
210
- if (i < values.length) result += interpolateValue(values[i]);
379
+ let chunk = strings[i];
380
+ if (stripLeadingQuote !== null) {
381
+ if (chunk.startsWith(stripLeadingQuote)) chunk = chunk.slice(1);
382
+ stripLeadingQuote = null;
383
+ }
384
+ if (pendingBindSpecs !== "") {
385
+ const gtIdx = chunk.indexOf(">");
386
+ if (gtIdx !== -1) {
387
+ chunk = chunk.slice(0, gtIdx) + ` data-ilha-bind="${pendingBindSpecs}">` + chunk.slice(gtIdx + 1);
388
+ pendingBindSpecs = "";
389
+ }
390
+ }
391
+ if (i >= values.length) {
392
+ result += chunk;
393
+ continue;
394
+ }
395
+ const value = values[i];
396
+ const m = chunk.match(BIND_PREFIX_RE);
397
+ if (m && isSignalAccessor(value)) {
398
+ const name = m[1];
399
+ const openQuote = m[2] ?? null;
400
+ if (!BIND_VALID_KINDS.has(name)) {
401
+ if (__DEV__) warn(`Unknown bind:${name} — supported bindings are ${[...BIND_VALID_KINDS].map((k) => `bind:${k}`).join(", ")}.`);
402
+ result += chunk + interpolateValue(value);
403
+ continue;
404
+ }
405
+ if (!ctx) {
406
+ if (__DEV__) warn(`bind:${name} used outside an island render — bindings only work in .render(). The value is reflected once but not wired.`);
407
+ const stripped = chunk.slice(0, chunk.length - m[0].length);
408
+ const quote = openQuote ?? "\"";
409
+ result += stripped + name + "=" + quote + interpolateValue(value) + quote;
410
+ if (openQuote) stripLeadingQuote = openQuote;
411
+ continue;
412
+ }
413
+ const matchStart = chunk.length - m[0].length;
414
+ const cleanPrefix = chunk.slice(0, matchStart).replace(/\s+$/, "");
415
+ const index = ctx.binds.length;
416
+ ctx.binds.push({
417
+ kind: name,
418
+ accessor: value
419
+ });
420
+ const [valueAttrs, spec] = emitBindSSR(name, index, value, cleanPrefix);
421
+ result += cleanPrefix + valueAttrs;
422
+ pendingBindSpecs += (pendingBindSpecs ? "," : "") + spec;
423
+ if (openQuote) stripLeadingQuote = openQuote;
424
+ continue;
425
+ }
426
+ if (m && __DEV__) warn(`bind:${m[1]} requires a signal accessor — got ${typeof value}. Use ilha.signal() or a .state() accessor.`);
427
+ result += chunk + interpolateValue(value);
211
428
  }
429
+ if (pendingBindSpecs !== "") result += ` data-ilha-bind="${pendingBindSpecs}"`;
212
430
  return {
213
431
  [RAW]: true,
214
432
  value: dedentString(result)
@@ -231,7 +449,8 @@ function ilhaContext(key, initial) {
231
449
  /**
232
450
  * Create a free-standing reactive signal that lives outside any island.
233
451
  * Useful for sharing state across islands without prop drilling, or for
234
- * binding form inputs to module-level state via `.bind(selector, signal)`.
452
+ * binding form inputs to module-level state via the `bind:value=${signal}`
453
+ * template syntax.
235
454
  *
236
455
  * The returned accessor is a getter when called with no arguments and a
237
456
  * setter when called with one. Reading it inside a `.derived()`, `.effect()`,
@@ -240,11 +459,10 @@ function ilhaContext(key, initial) {
240
459
  */
241
460
  function ilhaSignal(initial) {
242
461
  const s = signal(initial);
243
- const accessor = (...args) => {
462
+ return markSignalAccessor((...args) => {
244
463
  if (args.length === 0) return s();
245
464
  s(args[0]);
246
- };
247
- return accessor;
465
+ });
248
466
  }
249
467
  /**
250
468
  * Run `fn` with reactive tracking suspended. Reading signals inside `fn`
@@ -411,63 +629,159 @@ function createDerivedProxy(entries, state, input, derivedSnapshot) {
411
629
  setup
412
630
  };
413
631
  }
414
- function resolveBindConfig(el) {
415
- const tag = el.tagName.toLowerCase();
416
- const type = el.type?.toLowerCase() ?? "";
417
- if (tag === "input" && type === "checkbox") return {
418
- prop: "checked",
419
- event: "change",
420
- read: (el) => el.checked,
421
- write: (el, v) => el.checked = Boolean(v)
422
- };
423
- if (tag === "input" && type === "radio") return {
424
- prop: "checked",
425
- event: "change",
426
- read: (el) => {
427
- const input = el;
428
- return input.checked ? input.value : void 0;
429
- },
430
- write: (el, v) => {
431
- el.checked = String(v ?? "") === el.value;
632
+ const BIND_SENTINEL_ATTR = "data-ilha-bind";
633
+ function parseBindSentinel(value) {
634
+ const out = [];
635
+ for (const part of value.split(",")) {
636
+ const [kind, idx] = part.split(":");
637
+ if (!kind || !idx) continue;
638
+ const i = Number(idx);
639
+ if (!Number.isInteger(i) || i < 0) continue;
640
+ out.push({
641
+ kind,
642
+ index: i
643
+ });
644
+ }
645
+ return out;
646
+ }
647
+ function resolveBindOps(el, kind) {
648
+ const input = el;
649
+ switch (kind) {
650
+ case "value": return {
651
+ event: el.tagName === "SELECT" ? "change" : "input",
652
+ read: (el) => el.value,
653
+ write: (el, v) => el.value = v == null ? "" : String(v)
654
+ };
655
+ case "valueAsNumber": return {
656
+ event: "input",
657
+ read: (el) => {
658
+ const n = el.valueAsNumber;
659
+ return Number.isNaN(n) ? null : n;
660
+ },
661
+ write: (el, v) => el.value = v == null || Number.isNaN(v) ? "" : String(v)
662
+ };
663
+ case "valueAsDate": return {
664
+ event: "input",
665
+ read: (el) => el.valueAsDate,
666
+ write: (el, v) => {
667
+ el.value = formatDateForInput(v);
668
+ }
669
+ };
670
+ case "checked": return {
671
+ event: "change",
672
+ read: (el) => el.checked,
673
+ write: (el, v) => el.checked = Boolean(v)
674
+ };
675
+ case "files": return {
676
+ event: "change",
677
+ read: (el) => el.files,
678
+ write: () => {}
679
+ };
680
+ case "open": return {
681
+ event: "toggle",
682
+ read: (el) => el.open,
683
+ write: (el, v) => el.open = Boolean(v)
684
+ };
685
+ case "this": return {
686
+ event: null,
687
+ read: () => void 0,
688
+ write: () => {}
689
+ };
690
+ case "group": {
691
+ const isCheckbox = input.type === "checkbox";
692
+ return {
693
+ event: "change",
694
+ read: (el) => {
695
+ const i = el;
696
+ if (isCheckbox) return {
697
+ __ilhaGroup: true,
698
+ value: i.value,
699
+ checked: i.checked
700
+ };
701
+ return i.checked ? i.value : void 0;
702
+ },
703
+ write: (el, v) => {
704
+ const i = el;
705
+ if (isCheckbox) i.checked = (Array.isArray(v) ? v.map(String) : []).includes(i.value);
706
+ else i.checked = v != null && String(v) === i.value;
707
+ }
708
+ };
432
709
  }
433
- };
434
- if (tag === "input" && type === "number") return {
435
- prop: "valueAsNumber",
436
- event: "input",
437
- read: (el) => el.valueAsNumber,
438
- write: (el, v) => el.value = String(v ?? "")
439
- };
440
- return {
441
- prop: "value",
442
- event: tag === "select" ? "change" : "input",
443
- read: (el) => el.value,
444
- write: (el, v) => el.value = String(v ?? "")
445
- };
710
+ }
446
711
  }
447
- function applyBindings(host, bindings, state) {
712
+ function applyTemplateBindings(host, binds) {
713
+ if (binds.length === 0) return () => {};
448
714
  const cleanups = [];
449
- for (const binding of bindings) {
450
- const accessor = typeof binding.target === "function" ? binding.target : state[binding.target];
451
- const targets = binding.selector === "" ? [host] : Array.from(host.querySelectorAll(binding.selector));
452
- if (__DEV__ && binding.selector !== "" && targets.length === 0) warn(`bind(): selector "${binding.selector}" matched no elements inside the island host. Check that the element exists in your render output.`);
453
- for (const target of targets) {
454
- const { event, read, write } = resolveBindConfig(target);
455
- const isRadio = target.tagName.toLowerCase() === "input" && target.type?.toLowerCase() === "radio";
456
- write(target, accessor());
715
+ const elements = [];
716
+ if (host.hasAttribute(BIND_SENTINEL_ATTR)) elements.push(host);
717
+ for (const el of host.querySelectorAll(`[${BIND_SENTINEL_ATTR}]`)) elements.push(el);
718
+ for (const el of elements) {
719
+ const specs = parseBindSentinel(el.getAttribute(BIND_SENTINEL_ATTR));
720
+ for (const spec of specs) {
721
+ const record = binds[spec.index];
722
+ if (!record) {
723
+ if (__DEV__) warn(`bind:${spec.kind} index ${spec.index} not found in render — the data-ilha-bind sentinel may have been hand-edited or survived a stale render.`);
724
+ continue;
725
+ }
726
+ if (record.kind !== spec.kind) {
727
+ if (__DEV__) warn(`bind:${spec.kind} sentinel points at a binding registered as bind:${record.kind}. Sentinel may be stale.`);
728
+ continue;
729
+ }
730
+ const { event, read, write } = resolveBindOps(el, spec.kind);
731
+ const accessor = record.accessor;
732
+ if (spec.kind === "this") {
733
+ accessor(el);
734
+ cleanups.push(() => accessor(null));
735
+ continue;
736
+ }
737
+ try {
738
+ write(el, accessor());
739
+ } catch (err) {
740
+ if (__DEV__) console.error(`[ilha] bind:${spec.kind} write failed:`, err);
741
+ }
742
+ if (event === null) continue;
457
743
  const listener = () => {
458
- const raw = read(target);
459
- if (isRadio && raw === void 0) return;
744
+ const raw = read(el);
745
+ if (spec.kind === "group") {
746
+ const groupRead = raw;
747
+ if (groupRead === void 0) return;
748
+ if (typeof groupRead === "object" && groupRead.__ilhaGroup) {
749
+ const currentArr = accessor();
750
+ const arr = Array.isArray(currentArr) ? [...currentArr] : [];
751
+ const idx = arr.findIndex((x) => String(x) === groupRead.value);
752
+ if (groupRead.checked && idx === -1) {
753
+ let coercedVal = groupRead.value;
754
+ const templateVal = Array.isArray(currentArr) && currentArr.length > 0 ? currentArr[0] : void 0;
755
+ if (templateVal !== void 0) {
756
+ if (typeof templateVal === "number") {
757
+ const n = Number(coercedVal);
758
+ coercedVal = Number.isNaN(n) ? coercedVal : n;
759
+ } else if (typeof templateVal === "boolean") coercedVal = Boolean(coercedVal);
760
+ }
761
+ arr.push(coercedVal);
762
+ } else if (!groupRead.checked && idx !== -1) arr.splice(idx, 1);
763
+ accessor(arr);
764
+ return;
765
+ }
766
+ const currentVal = accessor();
767
+ let coerced = groupRead;
768
+ if (typeof currentVal === "number" && typeof groupRead === "string") {
769
+ const n = Number(groupRead);
770
+ coerced = Number.isNaN(n) ? 0 : n;
771
+ } else if (typeof currentVal === "boolean") coerced = Boolean(groupRead);
772
+ accessor(coerced);
773
+ return;
774
+ }
460
775
  const currentVal = accessor();
461
- let value;
462
- if (typeof currentVal === "number") {
776
+ let value = raw;
777
+ if (typeof currentVal === "number" && typeof raw === "string") {
463
778
  const n = Number(raw);
464
- value = isNaN(n) ? 0 : n;
779
+ value = Number.isNaN(n) ? 0 : n;
465
780
  } else if (typeof currentVal === "boolean") value = Boolean(raw);
466
- else value = raw;
467
781
  accessor(value);
468
782
  };
469
- target.addEventListener(event, listener);
470
- cleanups.push(() => target.removeEventListener(event, listener));
783
+ el.addEventListener(event, listener);
784
+ cleanups.push(() => el.removeEventListener(event, listener));
471
785
  }
472
786
  }
473
787
  return () => cleanups.forEach((c) => c());
@@ -529,7 +843,6 @@ var IlhaBuilder = class IlhaBuilder {
529
843
  onMounts: [],
530
844
  onErrors: [],
531
845
  transition: null,
532
- binds: [],
533
846
  css: null
534
847
  });
535
848
  }
@@ -553,15 +866,6 @@ var IlhaBuilder = class IlhaBuilder {
553
866
  }]
554
867
  });
555
868
  }
556
- bind(selector, target) {
557
- return new IlhaBuilder({
558
- ...this._cfg,
559
- binds: [...this._cfg.binds, {
560
- selector,
561
- target
562
- }]
563
- });
564
- }
565
869
  on(selectorOrCombined, handler) {
566
870
  const parsed = parseOnArgs(selectorOrCombined);
567
871
  return new IlhaBuilder({
@@ -617,7 +921,7 @@ var IlhaBuilder = class IlhaBuilder {
617
921
  });
618
922
  }
619
923
  render(fn) {
620
- const { schema, states, deriveds, ons, effects, onMounts, onErrors, transition, binds, css: cssSource } = this._cfg;
924
+ const { schema, states, deriveds, ons, effects, onMounts, onErrors, transition, css: cssSource } = this._cfg;
621
925
  const stylePrefix = cssSource != null ? buildScopedStyle(cssSource) : "";
622
926
  function resolveInput(props) {
623
927
  const value = props ?? {};
@@ -635,7 +939,8 @@ var IlhaBuilder = class IlhaBuilder {
635
939
  try {
636
940
  return {
637
941
  html: await resolveAsyncChildren(html, ctx),
638
- slots: ctx.slots
942
+ slots: ctx.slots,
943
+ binds: ctx.binds
639
944
  };
640
945
  } finally {
641
946
  popRenderCtx();
@@ -644,7 +949,8 @@ var IlhaBuilder = class IlhaBuilder {
644
949
  }
645
950
  return {
646
951
  html,
647
- slots: ctx.slots
952
+ slots: ctx.slots,
953
+ binds: ctx.binds
648
954
  };
649
955
  } finally {
650
956
  if (!isAsync) popRenderCtx();
@@ -986,7 +1292,7 @@ Element: ${host.outerHTML.slice(0, 120)}`);
986
1292
  }), hasExistingContent ? host : void 0);
987
1293
  if (!hasExistingContent) host.innerHTML = stylePrefix + initial.html;
988
1294
  attachListeners();
989
- let stopBindings = applyBindings(host, binds, state);
1295
+ let stopBindings = applyTemplateBindings(host, initial.binds);
990
1296
  cleanups.push(() => stopBindings());
991
1297
  mountSlots(initial.slots);
992
1298
  cleanups.push(() => mountedSlots.forEach((entry) => entry.unmount()));
@@ -1050,7 +1356,7 @@ Element: ${host.outerHTML.slice(0, 120)}`);
1050
1356
  let initialized = false;
1051
1357
  const initialRenderedHtml = initial.html;
1052
1358
  const stopRender = effect(() => {
1053
- const { html: rendered, slots: newSlotMap } = renderWithCtx(() => fn({
1359
+ const { html: rendered, slots: newSlotMap, binds: newBinds } = renderWithCtx(() => fn({
1054
1360
  state,
1055
1361
  derived,
1056
1362
  input
@@ -1084,24 +1390,26 @@ Element: ${host.outerHTML.slice(0, 120)}`);
1084
1390
  stub.replaceWith(liveEl);
1085
1391
  }
1086
1392
  attachListeners();
1087
- stopBindings = applyBindings(host, binds, state);
1393
+ stopBindings = applyTemplateBindings(host, newBinds);
1088
1394
  mountSlots(newSlotMap);
1089
1395
  });
1090
- cleanups.push(stopRender);
1091
- cleanups.push(detachListeners);
1092
1396
  let tornDown = false;
1093
1397
  const unmount = () => {
1094
1398
  if (tornDown) return;
1095
1399
  tornDown = true;
1096
1400
  if (__DEV__ && _mountedHosts) _mountedHosts.delete(host);
1401
+ stopRender();
1402
+ detachListeners();
1097
1403
  if (transition?.leave) {
1098
1404
  const result = transition.leave(host);
1099
1405
  if (result instanceof Promise) {
1100
- result.then(() => cleanups.forEach((c) => c())).catch(console.error);
1406
+ result.then(() => {
1407
+ for (const c of cleanups) c();
1408
+ }).catch(console.error);
1101
1409
  return;
1102
1410
  }
1103
1411
  }
1104
- cleanups.forEach((c) => c());
1412
+ for (const c of cleanups) c();
1105
1413
  };
1106
1414
  const updateProps = (nextProps) => {
1107
1415
  if (tornDown) return;
@@ -1272,12 +1580,15 @@ const rootBuilder = new IlhaBuilder({
1272
1580
  onMounts: [],
1273
1581
  onErrors: [],
1274
1582
  transition: null,
1275
- binds: [],
1276
1583
  css: null
1277
1584
  });
1278
1585
  const ilha = Object.assign(rootBuilder, {
1279
1586
  html: ilhaHtml,
1280
1587
  raw: ilhaRaw,
1588
+ jsx: ilhaJsx,
1589
+ jsxs: ilhaJsx,
1590
+ jsxDEV: ilhaJsxDEV,
1591
+ Fragment: ilhaFragment,
1281
1592
  mount: mountAll,
1282
1593
  from: ilhaFrom,
1283
1594
  context: ilhaContext,
@@ -1287,9 +1598,13 @@ const ilha = Object.assign(rootBuilder, {
1287
1598
  });
1288
1599
  const html = ilhaHtml;
1289
1600
  const raw = ilhaRaw;
1601
+ const jsx = ilhaJsx;
1602
+ const jsxs = ilhaJsx;
1603
+ const jsxDEV = ilhaJsxDEV;
1604
+ const Fragment = ilhaFragment;
1290
1605
  const css = ilhaCss;
1291
1606
  const mount = mountAll;
1292
1607
  const from = ilhaFrom;
1293
1608
  const context = ilhaContext;
1294
1609
  //#endregion
1295
- export { ISLAND_MOUNT_INTERNAL, batch, context, css, ilha as default, from, html, ilhaSignal, ilhaSignal as signal, mount, raw, untrack };
1610
+ export { Fragment, ISLAND_MOUNT_INTERNAL, batch, context, css, ilha as default, from, html, ilhaSignal, ilhaSignal as signal, jsx, jsxDEV, jsxs, mount, raw, untrack };
@@ -0,0 +1,3 @@
1
+ import { Fragment, jsxDEV } from "./index.js";
2
+ import { t as JSX } from "./jsx-types-DSrM2NdI.js";
3
+ export { Fragment, type JSX, jsxDEV };
@@ -0,0 +1,2 @@
1
+ import { Fragment, jsxDEV } from "./index.js";
2
+ export { Fragment, jsxDEV };
@@ -0,0 +1,3 @@
1
+ import { Fragment, jsx, jsxs } from "./index.js";
2
+ import { t as JSX } from "./jsx-types-DSrM2NdI.js";
3
+ export { Fragment, type JSX, jsx, jsxs };
@@ -0,0 +1,2 @@
1
+ import { Fragment, jsx, jsxs } from "./index.js";
2
+ export { Fragment, jsx, jsxs };
@@ -0,0 +1,26 @@
1
+ import { RawHtml } from "./index.js";
2
+
3
+ //#region src/jsx-types.d.ts
4
+ declare namespace JSX {
5
+ type Child = unknown;
6
+ type ElementType = string | ((props: any) => any);
7
+ interface Element extends RawHtml {}
8
+ interface ElementChildrenAttribute {
9
+ children: {};
10
+ }
11
+ interface IntrinsicAttributes {
12
+ key?: string | number;
13
+ }
14
+ type IntrinsicElementProps = {
15
+ children?: Child;
16
+ class?: string | unknown[] | Record<string, boolean>;
17
+ className?: string | unknown[] | Record<string, boolean>;
18
+ htmlFor?: string;
19
+ [name: string]: unknown;
20
+ };
21
+ interface IntrinsicElements {
22
+ [name: string]: IntrinsicElementProps;
23
+ }
24
+ }
25
+ //#endregion
26
+ export { JSX as t };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ilha",
3
- "version": "0.4.4",
3
+ "version": "0.6.0",
4
4
  "description": "A tiny, framework-free island architecture library",
5
5
  "license": "MIT",
6
6
  "author": "Ryuz <ryuzer@proton.me>",
@@ -12,10 +12,21 @@
12
12
  "dist"
13
13
  ],
14
14
  "type": "module",
15
+ "browser": {
16
+ "process": false
17
+ },
15
18
  "exports": {
16
19
  ".": {
17
20
  "types": "./dist/index.d.ts",
18
21
  "import": "./dist/index.js"
22
+ },
23
+ "./jsx-runtime": {
24
+ "types": "./dist/jsx-runtime.d.ts",
25
+ "import": "./dist/jsx-runtime.js"
26
+ },
27
+ "./jsx-dev-runtime": {
28
+ "types": "./dist/jsx-dev-runtime.d.ts",
29
+ "import": "./dist/jsx-dev-runtime.js"
19
30
  }
20
31
  },
21
32
  "scripts": {
@@ -23,7 +34,7 @@
23
34
  "test": "bun test"
24
35
  },
25
36
  "dependencies": {
26
- "alien-signals": "3.2.0"
37
+ "alien-signals": "3.2.1"
27
38
  },
28
39
  "devDependencies": {
29
40
  "zod": "^4.4.3"