Skip to content

Commit 4456bf6

Browse files
authored
chore(ilha): replace .slot api with template island interpolation (#23)
* chore(ilha): remove .slot api and replace it with template island interpolation * fix(pr): improvements * chore(website): bump ilha * fix(pr): improvements
1 parent f18233b commit 4456bf6

35 files changed

Lines changed: 935 additions & 530 deletions

CHANGELOG.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,15 @@
22

33
## `ilha`
44

5+
### 0.3.0 - 2026-04-24
6+
7+
- **Breaking:** Removes `.slot()` builder method and `slots` render context. Child islands are now interpolated directly inside `html\`\``` templates.
8+
- **Breaking:** Removes `SlotAccessor`, `SlotsProxy`, `SlotMap`, and `InferIslandInput` types.
9+
- Adds `ISLAND` and `ISLAND_CALL` symbols for branding islands and composition markers.
10+
- Adds `Island.key()` and `KeyedIsland` for explicit child keys in lists and conditional rendering.
11+
- Adds render-time child preservation across parent re-renders via detach-before-morph / rehome-after-morph.
12+
- Adds backward-compatible island call behavior: inside `html\`\``` it returns a slot marker; outside it returns an SSR string.
13+
514
### 0.2.1 - 2026-04-23
615

716
- Removes `type()` helper.
@@ -62,6 +71,10 @@ Initial release of **@ilha/form** — a tiny, typed form binding library for ilh
6271

6372
## `@ilha/router`
6473

74+
### 0.2.2 - 2026-04-24
75+
76+
- Updates Ilha dependency to 0.3.0
77+
6578
### 0.2.1 - 2026-04-23
6679

6780
- Updates Ilha dependency to 0.2.1
@@ -99,6 +112,10 @@ Initial release of **@ilha/router** — a lightweight, isomorphic router for ilh
99112

100113
## `@ilha/store`
101114

115+
### 0.1.3 - 2026-04-24
116+
117+
- Updates Ilha dependency to 0.3.0
118+
102119
### 0.1.2 - 2026-04-23
103120

104121
- Updates Ilha dependency to 0.2.1

apps/website/docs/guide/getting-started/core-concepts.md

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,6 @@ A typical island might include:
5353
- [`.on()`](/guide/island/on) for event handlers.
5454
- [`.bind()`](/guide/island/bind) for form binding.
5555
- [`.effect()`](/guide/island/effect) and [`.onMount()`](/guide/island/onmount) for side effects.
56-
- [`.slot()`](/guide/island/slot) for child islands.
5756
- [`.css()`](/guide/island/css) for scoped styles.
5857
- [`.render()`](/guide/island/render) to produce the final island.
5958

@@ -103,12 +102,6 @@ ilha supports component-level styles with [`.css()`](/guide/island/css). Styles
103102

104103
This lets you keep structure, behavior, and styling close together when that is useful, without giving up isolation.
105104

106-
## Slots
107-
108-
An island can include other islands through named slots. This gives you composition without losing encapsulation.
109-
110-
A parent can render a child island inline during SSR, and that child can still mount independently on the client. In practice, this means you can build larger interfaces out of smaller interactive units.
111-
112105
## SSR and hydration
113106

114107
ilha is designed to work naturally with server rendering and hydration. You can render HTML on the server, send it to the browser, and later activate the island in place.

apps/website/docs/guide/getting-started/introduction.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ It lets you render on the server and mount in the browser with fine-grained sign
1313

1414
An **island** is a self-contained component that knows how to render itself to HTML and how to activate itself in the browser. That means the same component can be used for server-side rendering, client-side mounting, or both together in a hydration flow.
1515

16-
ilha is built around a fluent builder chain. You declare input, state, derived values, event handlers, effects, slots, transitions, and styles, then finish with [`.render()`](/guide/island/render) to produce a reusable component.
16+
ilha is built around a fluent builder chain. You declare input, state, derived values, event handlers, effects, transitions, and styles, then finish with [`.render()`](/guide/island/render) to produce a reusable component.
1717

1818
## Why it exists
1919

@@ -72,7 +72,6 @@ The fluent API lets you layer behavior step by step:
7272
- [`.derived()`](/guide/island/derived) for computed values.
7373
- [`.on()`](/guide/island/on) for events.
7474
- [`.effect()`](/guide/island/effect) and [`.onMount()`](/guide/island/onmount) for side effects.
75-
- [`.slot()`](/guide/island/slot) for nesting islands.
7675
- [`.css()`](/guide/island/css) for scoped styles.
7776
- [`.render()`](/guide/island/render) to finalize the component.
7877

apps/website/docs/guide/island/_meta.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@
66
"bind",
77
"effect",
88
"onmount",
9-
"slot",
109
"transition",
1110
"css",
1211
"render",
13-
"hydratable"
12+
"hydratable",
13+
"composition"
1414
]

apps/website/docs/guide/island/bind.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ description: Two-way bind form elements to state keys or external signals with a
77

88
Two-way binds a form element to a state key or external signal. When the state changes, the element updates. When the user interacts with the element, the state updates.
99

10+
[Interactive Tutorial](/tutorial/counter/bind)
11+
1012
## Basic usage
1113

1214
```ts twoslash
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
---
2+
title: Composition
3+
description: Embed child islands directly inside a parent's template, rendered inline during SSR and activated independently on the client.
4+
---
5+
6+
# Composing Islands
7+
8+
Child islands are interpolated directly inside a parent's html`` template. The child is rendered inline during SSR and activated independently on the client. Each child is managed as its own island — it owns its own state, lifecycle, and reactivity.
9+
10+
## Basic usage
11+
12+
```ts twoslash
13+
import ilha, { html } from "ilha";
14+
15+
const Icon = ilha.render(() => `<svg>…</svg>`);
16+
17+
const Card = ilha.render(
18+
() => html`
19+
<div class="card">
20+
${Icon}
21+
<p>Card content</p>
22+
</div>
23+
`,
24+
);
25+
```
26+
27+
## Passing props to a child
28+
29+
Call the child island with a props object to forward data:
30+
31+
```ts twoslash
32+
import ilha, { html } from "ilha";
33+
import { z } from "zod";
34+
35+
const Badge = ilha
36+
.input(
37+
z.object({
38+
label: z.string(),
39+
color: z.string().default("teal"),
40+
}),
41+
)
42+
.render(({ input }) => html` <span style="background:${input.color}">${input.label}</span> `);
43+
44+
const Card = ilha.render(
45+
() => html`
46+
<div>
47+
${Badge({ label: "New", color: "coral" })} // [!code highlight]
48+
<p>Content</p>
49+
</div>
50+
`,
51+
);
52+
```
53+
54+
Props are validated against the child island's schema, so type errors surface at authoring time.
55+
56+
## Multiple children
57+
58+
Interpolate as many child islands as needed:
59+
60+
```ts twoslash
61+
import ilha, { html } from "ilha";
62+
63+
const Avatar = ilha.render(() => `<img src="/avatar.png" />`);
64+
const Actions = ilha.render(() => html`<button>Follow</button>`);
65+
66+
const Profile = ilha.render(
67+
() => html`
68+
<div class="profile">
69+
${Avatar}
70+
<div class="profile-actions">${Actions}</div>
71+
</div>
72+
`,
73+
);
74+
```
75+
76+
## Keyed children
77+
78+
Use `.key()` when a child may reorder or appear conditionally. Keys must be unique within a parent render:
79+
80+
```ts twoslash
81+
const items = [] as any[];
82+
// ---cut---
83+
import ilha, { html } from "ilha";
84+
85+
const Item = ilha.input<{ name: string }>().render(({ input }) => html`<span>${input.name}</span>`);
86+
87+
const List = ilha.render(
88+
() =>
89+
html`<ul>
90+
${items.map((item) => html`<li>${Item.key(item.id)({ name: item.name })}</li>`)}
91+
</ul>`,
92+
);
93+
```
94+
95+
## SSR behavior
96+
97+
During SSR, interpolating a child island renders its HTML inline as part of the parent's output. The child island's styles, derived values, and render function all run as part of the parent's SSR pass.
98+
99+
```html
100+
<!-- Output of Card.toString() -->
101+
<div class="card">
102+
<svg>…</svg>
103+
<p>Card content</p>
104+
</div>
105+
```
106+
107+
## Client behavior
108+
109+
On the client, each child is mounted independently into its own host element. The parent manages the lifecycle of its children — when the parent unmounts, all children are unmounted too.
110+
111+
Children are preserved across parent re-renders. If a keyed list reorders, live child subtrees are detached before the parent morphs and reattached afterwards, so DOM state, listeners, and internal state remain intact.
112+
113+
## Accessing child state from the parent
114+
115+
Child islands are self-contained — the parent cannot directly read or write the child's state. If you need to share values between parent and child, use [`context()`](/guide/helpers/context) to create a shared global signal that both islands can read and write:
116+
117+
```ts twoslash
118+
import ilha, { html, context } from "ilha";
119+
120+
const expanded = context("card.expanded", false);
121+
122+
const Toggle = ilha
123+
.on("button@click", () => expanded(!expanded()))
124+
.render(() => html`<button>Toggle</button>`);
125+
126+
const Content = ilha
127+
.effect(() => {
128+
// reacts to expanded signal from sibling
129+
})
130+
.render(() => html`<p>Content</p>`);
131+
132+
const Card = ilha.render(() => html` <div>${Toggle} ${Content}</div> `);
133+
```
134+
135+
## Notes
136+
137+
- The parent's render cycle and the child's render cycle are independent. A state change in a child does not trigger a re-render in the parent.
138+
- Child props are serialized as `data-ilha-props` on the child's host element, so they are available during hydration without passing them again manually.
139+
- A dev warning is logged when two children in the same parent render share the same key.

apps/website/docs/guide/island/derived.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ description: Declare computed values that depend on state or input, with built-i
77

88
Declares a computed value that depends on state or input. Derived values can be synchronous or async, and they re-run automatically when any reactive dependency changes.
99

10+
[Interactive Tutorial](/tutorial/counter/derived)
11+
1012
## Basic usage
1113

1214
```ts twoslash

apps/website/docs/guide/island/effect.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ description: Register reactive side effects that run after mount and re-run when
77

88
Registers a reactive side effect that runs after the island mounts and re-runs automatically whenever any signal it reads changes. Use it to sync state to the outside world — the DOM, browser APIs, timers, or external systems.
99

10+
[Interactive Tutorial](/tutorial/counter/effect)
11+
1012
## Basic usage
1113

1214
```ts twoslash

apps/website/docs/guide/island/on.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ description: Attach DOM event listeners to island hosts or descendant elements w
77

88
Attaches a DOM event listener to the island host or any descendant element. Listeners are set up at mount time and cleaned up automatically on unmount.
99

10+
[Interactive Tutorial](/tutorial/counter/on)
11+
1012
## Basic usage
1113

1214
```ts twoslash

apps/website/docs/guide/island/render.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,10 @@ The render function receives a `RenderContext` with everything declared in the b
2727
state: IslandState; // reactive state signals
2828
derived: IslandDerived; // derived value envelopes
2929
input: TInput; // resolved input props
30-
slots: SlotsProxy; // named slot accessors
3130
}
3231
```
3332

34-
All four are always present, even if not declared. An island with no state gets an empty `state` object, and so on.
33+
All three are always present, even if not declared. An island with no state gets an empty `state` object, and so on.
3534

3635
## Return type
3736

0 commit comments

Comments
 (0)