ilha 0.1.0 → 0.2.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 +92 -1
- package/dist/index.d.ts +5 -1
- package/dist/index.js +42 -8
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -129,7 +129,6 @@ ilha
|
|
|
129
129
|
.on("@click", ({ state }) => state.count(state.count() + 1)) // host click
|
|
130
130
|
.on("button.inc@click", ({ state }) => state.count(state.count() + 1)) // child click
|
|
131
131
|
.on("input@input:debounce", ({ state, event }) => {
|
|
132
|
-
// with modifier
|
|
133
132
|
state.query((event.target as HTMLInputElement).value);
|
|
134
133
|
})
|
|
135
134
|
.render(({ state }) => html`<div><button class="inc">+</button></div>`);
|
|
@@ -223,6 +222,63 @@ You can also bind to an external signal created with `context()`:
|
|
|
223
222
|
|
|
224
223
|
---
|
|
225
224
|
|
|
225
|
+
### `.css(strings, ...values)`
|
|
226
|
+
|
|
227
|
+
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.
|
|
228
|
+
|
|
229
|
+
```ts
|
|
230
|
+
import { css } from "ilha";
|
|
231
|
+
|
|
232
|
+
const Card = ilha.state("active", false).css`
|
|
233
|
+
.title { font-weight: 700; }
|
|
234
|
+
button { background: teal; color: white; }
|
|
235
|
+
`.render(
|
|
236
|
+
({ state }) => html`
|
|
237
|
+
<div>
|
|
238
|
+
<p class="title">Hello</p>
|
|
239
|
+
<button>Toggle</button>
|
|
240
|
+
</div>
|
|
241
|
+
`,
|
|
242
|
+
);
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
Interpolations are supported:
|
|
246
|
+
|
|
247
|
+
```ts
|
|
248
|
+
const accent = "teal";
|
|
249
|
+
|
|
250
|
+
ilha.css`button { background: ${accent}; }`.render(() => `<button>Go</button>`);
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
You can also pass a plain string (e.g. from an external `.css` file):
|
|
254
|
+
|
|
255
|
+
```ts
|
|
256
|
+
import styles from "./card.css?raw";
|
|
257
|
+
|
|
258
|
+
ilha.css(styles).render(() => `<div class="card">…</div>`);
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
**SSR output** — a `<style data-ilha-css>` tag is prepended as the first child of the island's rendered HTML:
|
|
262
|
+
|
|
263
|
+
```html
|
|
264
|
+
<style data-ilha-css>
|
|
265
|
+
@scope (:scope) to ([data-ilha]) {
|
|
266
|
+
.title {
|
|
267
|
+
font-weight: 700;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
</style>
|
|
271
|
+
<div>…</div>
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
**Client mount** — the style element is injected once as the first child of the host and preserved across re-renders (morph never replaces it). During hydration, the SSR-emitted `<style>` node is reused and not duplicated.
|
|
275
|
+
|
|
276
|
+
**`.hydratable()` integration** — the style tag is included inside the `data-ilha` wrapper regardless of the `snapshot` option.
|
|
277
|
+
|
|
278
|
+
> **Note:** Calling `.css()` more than once on the same builder chain is not supported. In dev mode a warning is logged and only the last stylesheet is used. Compose all your styles into a single `.css()` call.
|
|
279
|
+
|
|
280
|
+
---
|
|
281
|
+
|
|
226
282
|
### `.slot(name, island)`
|
|
227
283
|
|
|
228
284
|
Embeds a child island as a named slot. The child island is mounted and managed independently. During SSR the slot renders the child's HTML inline; during client mount the child island is activated for interactivity.
|
|
@@ -430,6 +486,41 @@ raw("<strong>bold</strong>"); // → passes through unescaped
|
|
|
430
486
|
|
|
431
487
|
---
|
|
432
488
|
|
|
489
|
+
### `css\`\`` tagged template
|
|
490
|
+
|
|
491
|
+
A passthrough tagged template for CSS strings. Functionally identical to a plain template literal — no runtime transformation occurs. Its purpose is purely to enable editor tooling (LSP syntax highlighting, Prettier formatting) to recognise the contents as CSS.
|
|
492
|
+
|
|
493
|
+
```ts
|
|
494
|
+
import { css } from "ilha";
|
|
495
|
+
|
|
496
|
+
const styles = css`
|
|
497
|
+
button {
|
|
498
|
+
background: teal;
|
|
499
|
+
color: white;
|
|
500
|
+
}
|
|
501
|
+
.label {
|
|
502
|
+
font-weight: 700;
|
|
503
|
+
}
|
|
504
|
+
`;
|
|
505
|
+
|
|
506
|
+
ilha.css(styles).render(() => `<button class="label">Go</button>`);
|
|
507
|
+
```
|
|
508
|
+
|
|
509
|
+
Interpolations work as normal string concatenation:
|
|
510
|
+
|
|
511
|
+
```ts
|
|
512
|
+
const accent = "coral";
|
|
513
|
+
const styles = css`
|
|
514
|
+
button {
|
|
515
|
+
background: ${accent};
|
|
516
|
+
}
|
|
517
|
+
`;
|
|
518
|
+
```
|
|
519
|
+
|
|
520
|
+
> **Note:** `css` (the named export) is the plain passthrough tag for tooling. `ilha.css` is the builder chain method that attaches styles to an island. They are intentionally separate.
|
|
521
|
+
|
|
522
|
+
---
|
|
523
|
+
|
|
433
524
|
### `type(coerce?)`
|
|
434
525
|
|
|
435
526
|
Creates a lightweight Standard Schema validator for use with `.input()` — useful when you don't want a full validation library.
|
package/dist/index.d.ts
CHANGED
|
@@ -48,6 +48,7 @@ interface MarkedSignalAccessor<T> {
|
|
|
48
48
|
[SIGNAL_ACCESSOR]: true;
|
|
49
49
|
}
|
|
50
50
|
declare function ilhaRaw(value: string): RawHtml;
|
|
51
|
+
declare function ilhaCss(strings: TemplateStringsArray | string, ...values: (string | number)[]): string;
|
|
51
52
|
declare function ilhaHtml(strings: TemplateStringsArray, ...values: unknown[]): RawHtml;
|
|
52
53
|
type ContextSignal<T> = {
|
|
53
54
|
(): T;
|
|
@@ -176,6 +177,7 @@ interface BuilderConfig<TInput, TStateMap extends Record<string, unknown>, TDeri
|
|
|
176
177
|
slots: Record<string, AnyIsland>;
|
|
177
178
|
transition: TransitionOptions | null;
|
|
178
179
|
binds: BindEntry<TStateMap>[];
|
|
180
|
+
css: string | null;
|
|
179
181
|
}
|
|
180
182
|
declare class IlhaBuilder<TInput extends Record<string, unknown>, TStateMap extends Record<string, unknown>, TDerivedMap extends Record<string, unknown> = Record<string, never>, TSlots extends SlotMap = Record<string, never>> {
|
|
181
183
|
readonly _cfg: BuilderConfig<TInput, TStateMap, TDerivedMap, TSlots>;
|
|
@@ -191,6 +193,7 @@ declare class IlhaBuilder<TInput extends Record<string, unknown>, TStateMap exte
|
|
|
191
193
|
onMount(fn: (ctx: OnMountContext<TInput, TStateMap, TDerivedMap>) => (() => void) | void): IlhaBuilder<TInput, TStateMap, TDerivedMap, TSlots>;
|
|
192
194
|
slot<K extends string, I extends AnyIsland>(name: K, island: I): IlhaBuilder<TInput, TStateMap, TDerivedMap, TSlots & Record<K, I>>;
|
|
193
195
|
transition(opts: TransitionOptions): IlhaBuilder<TInput, TStateMap, TDerivedMap, TSlots>;
|
|
196
|
+
css(strings: TemplateStringsArray | string, ...values: (string | number)[]): IlhaBuilder<TInput, TStateMap, TDerivedMap, TSlots>;
|
|
194
197
|
render(fn: (ctx: RenderContext<TInput, TStateMap, TDerivedMap, TSlots>) => string | RawHtml): Island<TInput, TStateMap>;
|
|
195
198
|
}
|
|
196
199
|
declare function ilhaFrom<TInput, TStateMap extends Record<string, unknown>>(selector: string | Element, island: Island<TInput, TStateMap>, props?: Partial<TInput>): (() => void) | null;
|
|
@@ -206,8 +209,9 @@ declare const ilha: IlhaBuilder<Record<string, unknown>, Record<string, never>,
|
|
|
206
209
|
declare function type<TInput, TOutput = TInput>(coerce?: (input: TInput) => TOutput): StandardSchemaV1<TInput, TOutput>;
|
|
207
210
|
declare const html: typeof ilhaHtml;
|
|
208
211
|
declare const raw: typeof ilhaRaw;
|
|
212
|
+
declare const css: typeof ilhaCss;
|
|
209
213
|
declare const mount: typeof mountAll;
|
|
210
214
|
declare const from: typeof ilhaFrom;
|
|
211
215
|
declare const context: typeof ilhaContext;
|
|
212
216
|
//#endregion
|
|
213
|
-
export { DerivedValue, HandlerContext, HandlerContextFor, HydratableOptions, Island, IslandDerived, IslandState, MountOptions, MountResult, OnMountContext, RawHtml, SignalAccessor, SlotAccessor, context, ilha as default, from, html, mount, raw, type };
|
|
217
|
+
export { DerivedValue, HandlerContext, HandlerContextFor, HydratableOptions, Island, IslandDerived, IslandState, MountOptions, MountResult, OnMountContext, RawHtml, SignalAccessor, SlotAccessor, context, css, ilha as default, from, html, mount, raw, type };
|
package/dist/index.js
CHANGED
|
@@ -82,6 +82,10 @@ const SIGNAL_ACCESSOR = Symbol("ilha.signalAccessor");
|
|
|
82
82
|
const SLOT_ATTR = "data-ilha-slot";
|
|
83
83
|
const PROPS_ATTR = "data-ilha-props";
|
|
84
84
|
const STATE_ATTR = "data-ilha-state";
|
|
85
|
+
const CSS_ATTR = "data-ilha-css";
|
|
86
|
+
function buildScopedStyle(css) {
|
|
87
|
+
return `<style ${CSS_ATTR}>@scope (:scope) to ([data-ilha]){${css}}</style>`;
|
|
88
|
+
}
|
|
85
89
|
function makeSlotAccessor(render) {
|
|
86
90
|
const fn = (props) => ({
|
|
87
91
|
[RAW]: true,
|
|
@@ -107,6 +111,15 @@ function ilhaRaw(value) {
|
|
|
107
111
|
value
|
|
108
112
|
};
|
|
109
113
|
}
|
|
114
|
+
function ilhaCss(strings, ...values) {
|
|
115
|
+
if (typeof strings === "string") return strings;
|
|
116
|
+
let result = "";
|
|
117
|
+
for (let i = 0; i < strings.length; i++) {
|
|
118
|
+
result += strings[i];
|
|
119
|
+
if (i < values.length) result += String(values[i]);
|
|
120
|
+
}
|
|
121
|
+
return result;
|
|
122
|
+
}
|
|
110
123
|
function interpolateValue(v) {
|
|
111
124
|
if (v == null) return "";
|
|
112
125
|
if (Array.isArray(v)) return v.map(interpolateValue).join("");
|
|
@@ -310,7 +323,8 @@ var IlhaBuilder = class IlhaBuilder {
|
|
|
310
323
|
onMounts: [],
|
|
311
324
|
slots: {},
|
|
312
325
|
transition: null,
|
|
313
|
-
binds: []
|
|
326
|
+
binds: [],
|
|
327
|
+
css: null
|
|
314
328
|
});
|
|
315
329
|
}
|
|
316
330
|
state(key, init) {
|
|
@@ -381,8 +395,26 @@ var IlhaBuilder = class IlhaBuilder {
|
|
|
381
395
|
transition: opts
|
|
382
396
|
});
|
|
383
397
|
}
|
|
398
|
+
css(strings, ...values) {
|
|
399
|
+
let source;
|
|
400
|
+
if (typeof strings === "string") source = strings;
|
|
401
|
+
else {
|
|
402
|
+
let result = "";
|
|
403
|
+
for (let i = 0; i < strings.length; i++) {
|
|
404
|
+
result += strings[i];
|
|
405
|
+
if (i < values.length) result += String(values[i]);
|
|
406
|
+
}
|
|
407
|
+
source = result;
|
|
408
|
+
}
|
|
409
|
+
if (__DEV__ && this._cfg.css !== null) warn("css(): called more than once on the same builder chain. The previous stylesheet has been discarded. Compose styles into a single .css() call instead.");
|
|
410
|
+
return new IlhaBuilder({
|
|
411
|
+
...this._cfg,
|
|
412
|
+
css: source
|
|
413
|
+
});
|
|
414
|
+
}
|
|
384
415
|
render(fn) {
|
|
385
|
-
const { schema, states, deriveds, ons, effects, onMounts, slots: slotDefs, transition, binds } = this._cfg;
|
|
416
|
+
const { schema, states, deriveds, ons, effects, onMounts, slots: slotDefs, transition, binds, css: cssSource } = this._cfg;
|
|
417
|
+
const stylePrefix = cssSource != null ? buildScopedStyle(cssSource) : "";
|
|
386
418
|
function resolveInput(props) {
|
|
387
419
|
const value = props ?? {};
|
|
388
420
|
if (!schema) return value;
|
|
@@ -460,7 +492,7 @@ var IlhaBuilder = class IlhaBuilder {
|
|
|
460
492
|
value: r.result,
|
|
461
493
|
error: void 0
|
|
462
494
|
};
|
|
463
|
-
return unwrapHtml(fn({
|
|
495
|
+
return stylePrefix + unwrapHtml(fn({
|
|
464
496
|
state,
|
|
465
497
|
derived,
|
|
466
498
|
input,
|
|
@@ -490,7 +522,7 @@ var IlhaBuilder = class IlhaBuilder {
|
|
|
490
522
|
})).then((resolved) => {
|
|
491
523
|
const derived = {};
|
|
492
524
|
for (const r of resolved) derived[r.key] = r.envelope;
|
|
493
|
-
return unwrapHtml(fn({
|
|
525
|
+
return stylePrefix + unwrapHtml(fn({
|
|
494
526
|
state,
|
|
495
527
|
derived,
|
|
496
528
|
input,
|
|
@@ -613,7 +645,7 @@ Element: ${host.outerHTML.slice(0, 120)}`);
|
|
|
613
645
|
listeners.length = 0;
|
|
614
646
|
}
|
|
615
647
|
const slots = makeSlotsProxy(false, host);
|
|
616
|
-
if (!(hydrated && host.childNodes.length > 0)) host.innerHTML = unwrapHtml(fn({
|
|
648
|
+
if (!(hydrated && host.childNodes.length > 0)) host.innerHTML = stylePrefix + unwrapHtml(fn({
|
|
617
649
|
state,
|
|
618
650
|
derived,
|
|
619
651
|
input,
|
|
@@ -642,7 +674,7 @@ Element: ${host.outerHTML.slice(0, 120)}`);
|
|
|
642
674
|
}
|
|
643
675
|
let initialized = false;
|
|
644
676
|
const stopRender = effect(() => {
|
|
645
|
-
const html = unwrapHtml(fn({
|
|
677
|
+
const html = stylePrefix + unwrapHtml(fn({
|
|
646
678
|
state,
|
|
647
679
|
derived,
|
|
648
680
|
input,
|
|
@@ -801,7 +833,8 @@ const rootBuilder = new IlhaBuilder({
|
|
|
801
833
|
onMounts: [],
|
|
802
834
|
slots: {},
|
|
803
835
|
transition: null,
|
|
804
|
-
binds: []
|
|
836
|
+
binds: [],
|
|
837
|
+
css: null
|
|
805
838
|
});
|
|
806
839
|
const ilha = Object.assign(rootBuilder, {
|
|
807
840
|
html: ilhaHtml,
|
|
@@ -821,8 +854,9 @@ function type(coerce) {
|
|
|
821
854
|
}
|
|
822
855
|
const html = ilhaHtml;
|
|
823
856
|
const raw = ilhaRaw;
|
|
857
|
+
const css = ilhaCss;
|
|
824
858
|
const mount = mountAll;
|
|
825
859
|
const from = ilhaFrom;
|
|
826
860
|
const context = ilhaContext;
|
|
827
861
|
//#endregion
|
|
828
|
-
export { context, ilha as default, from, html, mount, raw, type };
|
|
862
|
+
export { context, css, ilha as default, from, html, mount, raw, type };
|