ilha 0.6.1 → 0.7.1
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 +5 -2
- package/dist/index.d.ts +15 -7
- package/dist/index.js +55 -12
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -121,11 +121,11 @@ ilha
|
|
|
121
121
|
.render(({ derived }) => {
|
|
122
122
|
if (derived.user.loading) return `<p>Loading…</p>`;
|
|
123
123
|
if (derived.user.error) return `<p>Error: ${derived.user.error.message}</p>`;
|
|
124
|
-
return `<p>${derived.user.
|
|
124
|
+
return `<p>${derived.user().name}</p>`;
|
|
125
125
|
});
|
|
126
126
|
```
|
|
127
127
|
|
|
128
|
-
|
|
128
|
+
Read with `derived.name()` like state. Each accessor also exposes `loading`, `value`, and `error` for async work. Write `derived.name(value)` for optimistic UI.
|
|
129
129
|
|
|
130
130
|
---
|
|
131
131
|
|
|
@@ -239,12 +239,15 @@ The handler receives an `EffectContext`:
|
|
|
239
239
|
```ts
|
|
240
240
|
{
|
|
241
241
|
state: IslandState;
|
|
242
|
+
derived: IslandDerived;
|
|
242
243
|
input: TInput;
|
|
243
244
|
host: Element;
|
|
244
245
|
signal: AbortSignal; // aborts when the effect re-runs OR the island unmounts
|
|
245
246
|
}
|
|
246
247
|
```
|
|
247
248
|
|
|
249
|
+
Reading `derived.name()` subscribes the effect. Writing `derived.name(value)` does not — use writes for optimistic UI without pinning the effect to every derived update.
|
|
250
|
+
|
|
248
251
|
**Cancelling async work with `ctx.signal`** — unlike `.on()`, race-cancellation is the **default** behaviour for effects (no opt-in modifier needed) because dependency changes invariably make the previous run stale. Pass `signal` to async work to bail out of stale invocations without needing a manual cleanup function:
|
|
249
252
|
|
|
250
253
|
```ts
|
package/dist/index.d.ts
CHANGED
|
@@ -105,6 +105,13 @@ interface DerivedValue<T> {
|
|
|
105
105
|
value: T | undefined;
|
|
106
106
|
error: Error | undefined;
|
|
107
107
|
}
|
|
108
|
+
type DerivedAccessor<T> = {
|
|
109
|
+
readonly loading: boolean;
|
|
110
|
+
readonly value: T | undefined;
|
|
111
|
+
readonly error: Error | undefined;
|
|
112
|
+
(): T | undefined;
|
|
113
|
+
(value: T): void;
|
|
114
|
+
};
|
|
108
115
|
type DerivedFnContext<TInput, TStateMap extends Record<string, unknown>> = {
|
|
109
116
|
state: IslandState<TStateMap>;
|
|
110
117
|
input: TInput;
|
|
@@ -115,7 +122,7 @@ interface DerivedEntry<TInput, TStateMap extends Record<string, unknown>> {
|
|
|
115
122
|
key: string;
|
|
116
123
|
fn: DerivedFn<TInput, TStateMap, unknown>;
|
|
117
124
|
}
|
|
118
|
-
type IslandDerived<TDerivedMap extends Record<string, unknown>> = { readonly [K in keyof TDerivedMap]:
|
|
125
|
+
type IslandDerived<TDerivedMap extends Record<string, unknown>> = { readonly [K in keyof TDerivedMap]: DerivedAccessor<TDerivedMap[K]> };
|
|
119
126
|
type ExternalSignal<T = unknown> = SignalAccessor<T>;
|
|
120
127
|
type SignalAccessor<T> = MarkedSignalAccessor<T>;
|
|
121
128
|
type MergeState<TStateMap extends Record<string, unknown>, K extends string, V> = Omit<TStateMap, K> & Record<K, V>;
|
|
@@ -147,8 +154,9 @@ type RenderContext<TInput, TStateMap extends Record<string, unknown>, TDerivedMa
|
|
|
147
154
|
derived: IslandDerived<TDerivedMap>;
|
|
148
155
|
input: TInput;
|
|
149
156
|
};
|
|
150
|
-
type EffectContext<TInput, TStateMap extends Record<string, unknown>> = {
|
|
157
|
+
type EffectContext<TInput, TStateMap extends Record<string, unknown>, TDerivedMap extends Record<string, unknown> = Record<never, never>> = {
|
|
151
158
|
state: IslandState<TStateMap>;
|
|
159
|
+
derived: IslandDerived<TDerivedMap>;
|
|
152
160
|
input: TInput;
|
|
153
161
|
host: Element;
|
|
154
162
|
/**
|
|
@@ -211,8 +219,8 @@ interface OnEntry<TInput, TStateMap extends Record<string, unknown>, TDerivedMap
|
|
|
211
219
|
abortable: boolean;
|
|
212
220
|
handler: (ctx: HandlerContext<TInput, TStateMap, TDerivedMap>) => void | Promise<void>;
|
|
213
221
|
}
|
|
214
|
-
interface EffectEntry<TInput, TStateMap extends Record<string, unknown>> {
|
|
215
|
-
fn: (ctx: EffectContext<TInput, TStateMap>) => (() => void) | void;
|
|
222
|
+
interface EffectEntry<TInput, TStateMap extends Record<string, unknown>, TDerivedMap extends Record<string, unknown>> {
|
|
223
|
+
fn: (ctx: EffectContext<TInput, TStateMap, TDerivedMap>) => (() => void) | void;
|
|
216
224
|
}
|
|
217
225
|
/** Where the error originated. `"on"` covers sync throws and async rejections
|
|
218
226
|
* from `.on()` handlers; `"effect"` covers sync throws from `.effect()` runs
|
|
@@ -248,7 +256,7 @@ interface BuilderConfig<TInput, TStateMap extends Record<string, unknown>, TDeri
|
|
|
248
256
|
states: StateEntry<TInput>[];
|
|
249
257
|
deriveds: DerivedEntry<TInput, TStateMap>[];
|
|
250
258
|
ons: OnEntry<TInput, TStateMap, TDerivedMap>[];
|
|
251
|
-
effects: EffectEntry<TInput, TStateMap>[];
|
|
259
|
+
effects: EffectEntry<TInput, TStateMap, TDerivedMap>[];
|
|
252
260
|
onMounts: OnMountEntry<TInput, TStateMap, TDerivedMap>[];
|
|
253
261
|
onErrors: OnErrorEntry<TInput, TStateMap, TDerivedMap>[];
|
|
254
262
|
transition: TransitionOptions | null;
|
|
@@ -262,7 +270,7 @@ declare class IlhaBuilder<TInput extends Record<string, unknown>, TStateMap exte
|
|
|
262
270
|
state<V = undefined, K extends string = string>(key: K, init?: StateInit<TInput, V> | undefined): IlhaBuilder<TInput, MergeState<TStateMap, K, V>, TDerivedMap>;
|
|
263
271
|
derived<K extends string, V>(key: K, fn: DerivedFn<TInput, TStateMap, V>): IlhaBuilder<TInput, TStateMap, TDerivedMap & Record<K, V>>;
|
|
264
272
|
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>;
|
|
265
|
-
effect(fn: (ctx: EffectContext<TInput, TStateMap>) => (() => void) | void): IlhaBuilder<TInput, TStateMap, TDerivedMap>;
|
|
273
|
+
effect(fn: (ctx: EffectContext<TInput, TStateMap, TDerivedMap>) => (() => void) | void): IlhaBuilder<TInput, TStateMap, TDerivedMap>;
|
|
266
274
|
onMount(fn: (ctx: OnMountContext<TInput, TStateMap, TDerivedMap>) => (() => void) | void): IlhaBuilder<TInput, TStateMap, TDerivedMap>;
|
|
267
275
|
onError(fn: (ctx: ErrorContext<TInput, TStateMap, TDerivedMap>) => void): IlhaBuilder<TInput, TStateMap, TDerivedMap>;
|
|
268
276
|
transition(opts: TransitionOptions): IlhaBuilder<TInput, TStateMap, TDerivedMap>;
|
|
@@ -297,4 +305,4 @@ declare const mount: typeof mountAll;
|
|
|
297
305
|
declare const from: typeof ilhaFrom;
|
|
298
306
|
declare const context: typeof ilhaContext;
|
|
299
307
|
//#endregion
|
|
300
|
-
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 };
|
|
308
|
+
export { DerivedAccessor, DerivedValue, EffectContext, 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
|
@@ -465,6 +465,7 @@ function ilhaJsx(type, props, maybeKey, ...restChildren) {
|
|
|
465
465
|
if (isRawHtml(out)) return out;
|
|
466
466
|
if (typeof out === "string" && isIsland(type)) return ilhaRaw(emitIslandSlot(type, componentProps, slotKey));
|
|
467
467
|
if (typeof out === "string") return ilhaHtml`${out}`;
|
|
468
|
+
if (typeof out === "object" && out !== null && Object.getPrototypeOf(out) === Object.prototype && out[Symbol.for("ilha.renderPart")] === true && typeof out.toString === "function") return ilhaRaw(out.toString());
|
|
468
469
|
return ilhaHtml`${out}`;
|
|
469
470
|
}
|
|
470
471
|
return renderJsxElement(type, props, normalizedChildren);
|
|
@@ -676,6 +677,38 @@ function batch(fn) {
|
|
|
676
677
|
endBatch();
|
|
677
678
|
}
|
|
678
679
|
}
|
|
680
|
+
function createDerivedAccessor(read, write) {
|
|
681
|
+
const accessor = markSignalAccessor((...args) => {
|
|
682
|
+
if (args.length > 0) {
|
|
683
|
+
if (write) write(args[0]);
|
|
684
|
+
else if (__DEV__) warn("derived values are read-only");
|
|
685
|
+
return;
|
|
686
|
+
}
|
|
687
|
+
return read().value;
|
|
688
|
+
});
|
|
689
|
+
return new Proxy(accessor, { get(target, prop, receiver) {
|
|
690
|
+
if (prop === "loading" || prop === "value" || prop === "error") return read()[prop];
|
|
691
|
+
return Reflect.get(target, prop, receiver);
|
|
692
|
+
} });
|
|
693
|
+
}
|
|
694
|
+
function defaultDerivedAccessor() {
|
|
695
|
+
return createDerivedAccessor(() => ({
|
|
696
|
+
loading: false,
|
|
697
|
+
value: void 0,
|
|
698
|
+
error: void 0
|
|
699
|
+
}));
|
|
700
|
+
}
|
|
701
|
+
function buildDerivedAccessors(envelopes) {
|
|
702
|
+
const accessors = /* @__PURE__ */ new Map();
|
|
703
|
+
for (const [key, envelope] of Object.entries(envelopes)) accessors.set(key, createDerivedAccessor(() => envelope, (value) => {
|
|
704
|
+
envelope.loading = false;
|
|
705
|
+
envelope.value = value;
|
|
706
|
+
envelope.error = void 0;
|
|
707
|
+
}));
|
|
708
|
+
return new Proxy({}, { get(_, key) {
|
|
709
|
+
return accessors.get(key) ?? defaultDerivedAccessor();
|
|
710
|
+
} });
|
|
711
|
+
}
|
|
679
712
|
function createDerivedProxy(entries, state, input, derivedSnapshot) {
|
|
680
713
|
const envelopes = /* @__PURE__ */ new Map();
|
|
681
714
|
for (const entry of entries) {
|
|
@@ -719,14 +752,21 @@ function createDerivedProxy(entries, state, input, derivedSnapshot) {
|
|
|
719
752
|
const env = signal(initialEnvelope);
|
|
720
753
|
envelopes.set(entry.key, env);
|
|
721
754
|
}
|
|
755
|
+
const accessors = /* @__PURE__ */ new Map();
|
|
756
|
+
for (const entry of entries) {
|
|
757
|
+
const env = envelopes.get(entry.key);
|
|
758
|
+
accessors.set(entry.key, createDerivedAccessor(() => env(), (value) => {
|
|
759
|
+
const prevSub = setActiveSub(void 0);
|
|
760
|
+
env({
|
|
761
|
+
loading: false,
|
|
762
|
+
value,
|
|
763
|
+
error: void 0
|
|
764
|
+
});
|
|
765
|
+
setActiveSub(prevSub);
|
|
766
|
+
}));
|
|
767
|
+
}
|
|
722
768
|
const proxy = new Proxy({}, { get(_, key) {
|
|
723
|
-
|
|
724
|
-
if (!env) return {
|
|
725
|
-
loading: false,
|
|
726
|
-
value: void 0,
|
|
727
|
-
error: void 0
|
|
728
|
-
};
|
|
729
|
-
return env();
|
|
769
|
+
return accessors.get(key) ?? defaultDerivedAccessor();
|
|
730
770
|
} });
|
|
731
771
|
const setup = () => {
|
|
732
772
|
const stops = [];
|
|
@@ -1183,17 +1223,18 @@ var IlhaBuilder = class IlhaBuilder {
|
|
|
1183
1223
|
}
|
|
1184
1224
|
});
|
|
1185
1225
|
if (!results.some((r) => r.result instanceof Promise) || sync) {
|
|
1186
|
-
const
|
|
1187
|
-
for (const r of results) if (r.result instanceof Promise)
|
|
1226
|
+
const envelopes = {};
|
|
1227
|
+
for (const r of results) if (r.result instanceof Promise) envelopes[r.key] = {
|
|
1188
1228
|
loading: true,
|
|
1189
1229
|
value: void 0,
|
|
1190
1230
|
error: void 0
|
|
1191
1231
|
};
|
|
1192
|
-
else
|
|
1232
|
+
else envelopes[r.key] = {
|
|
1193
1233
|
loading: false,
|
|
1194
1234
|
value: r.result,
|
|
1195
1235
|
error: void 0
|
|
1196
1236
|
};
|
|
1237
|
+
const derived = buildDerivedAccessors(envelopes);
|
|
1197
1238
|
const prevSub = setActiveSub(void 0);
|
|
1198
1239
|
try {
|
|
1199
1240
|
const { html } = renderWithCtx(() => fn({
|
|
@@ -1227,8 +1268,9 @@ var IlhaBuilder = class IlhaBuilder {
|
|
|
1227
1268
|
};
|
|
1228
1269
|
}
|
|
1229
1270
|
})).then(async (resolved) => {
|
|
1230
|
-
const
|
|
1231
|
-
for (const r of resolved)
|
|
1271
|
+
const envelopes = {};
|
|
1272
|
+
for (const r of resolved) envelopes[r.key] = r.envelope;
|
|
1273
|
+
const derived = buildDerivedAccessors(envelopes);
|
|
1232
1274
|
const prevSub = setActiveSub(void 0);
|
|
1233
1275
|
try {
|
|
1234
1276
|
const { html } = await renderWithCtx(() => fn({
|
|
@@ -1537,6 +1579,7 @@ Element: ${host.outerHTML.slice(0, 120)}`);
|
|
|
1537
1579
|
try {
|
|
1538
1580
|
userCleanup = entry.fn({
|
|
1539
1581
|
state,
|
|
1582
|
+
derived,
|
|
1540
1583
|
input,
|
|
1541
1584
|
host,
|
|
1542
1585
|
signal: runSignal
|