ilha 0.6.1 → 0.7.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 +5 -2
- package/dist/index.d.ts +15 -7
- package/dist/index.js +54 -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
|
@@ -676,6 +676,38 @@ function batch(fn) {
|
|
|
676
676
|
endBatch();
|
|
677
677
|
}
|
|
678
678
|
}
|
|
679
|
+
function createDerivedAccessor(read, write) {
|
|
680
|
+
const accessor = markSignalAccessor((...args) => {
|
|
681
|
+
if (args.length > 0) {
|
|
682
|
+
if (write) write(args[0]);
|
|
683
|
+
else if (__DEV__) warn("derived values are read-only");
|
|
684
|
+
return;
|
|
685
|
+
}
|
|
686
|
+
return read().value;
|
|
687
|
+
});
|
|
688
|
+
return new Proxy(accessor, { get(target, prop, receiver) {
|
|
689
|
+
if (prop === "loading" || prop === "value" || prop === "error") return read()[prop];
|
|
690
|
+
return Reflect.get(target, prop, receiver);
|
|
691
|
+
} });
|
|
692
|
+
}
|
|
693
|
+
function defaultDerivedAccessor() {
|
|
694
|
+
return createDerivedAccessor(() => ({
|
|
695
|
+
loading: false,
|
|
696
|
+
value: void 0,
|
|
697
|
+
error: void 0
|
|
698
|
+
}));
|
|
699
|
+
}
|
|
700
|
+
function buildDerivedAccessors(envelopes) {
|
|
701
|
+
const accessors = /* @__PURE__ */ new Map();
|
|
702
|
+
for (const [key, envelope] of Object.entries(envelopes)) accessors.set(key, createDerivedAccessor(() => envelope, (value) => {
|
|
703
|
+
envelope.loading = false;
|
|
704
|
+
envelope.value = value;
|
|
705
|
+
envelope.error = void 0;
|
|
706
|
+
}));
|
|
707
|
+
return new Proxy({}, { get(_, key) {
|
|
708
|
+
return accessors.get(key) ?? defaultDerivedAccessor();
|
|
709
|
+
} });
|
|
710
|
+
}
|
|
679
711
|
function createDerivedProxy(entries, state, input, derivedSnapshot) {
|
|
680
712
|
const envelopes = /* @__PURE__ */ new Map();
|
|
681
713
|
for (const entry of entries) {
|
|
@@ -719,14 +751,21 @@ function createDerivedProxy(entries, state, input, derivedSnapshot) {
|
|
|
719
751
|
const env = signal(initialEnvelope);
|
|
720
752
|
envelopes.set(entry.key, env);
|
|
721
753
|
}
|
|
754
|
+
const accessors = /* @__PURE__ */ new Map();
|
|
755
|
+
for (const entry of entries) {
|
|
756
|
+
const env = envelopes.get(entry.key);
|
|
757
|
+
accessors.set(entry.key, createDerivedAccessor(() => env(), (value) => {
|
|
758
|
+
const prevSub = setActiveSub(void 0);
|
|
759
|
+
env({
|
|
760
|
+
loading: false,
|
|
761
|
+
value,
|
|
762
|
+
error: void 0
|
|
763
|
+
});
|
|
764
|
+
setActiveSub(prevSub);
|
|
765
|
+
}));
|
|
766
|
+
}
|
|
722
767
|
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();
|
|
768
|
+
return accessors.get(key) ?? defaultDerivedAccessor();
|
|
730
769
|
} });
|
|
731
770
|
const setup = () => {
|
|
732
771
|
const stops = [];
|
|
@@ -1183,17 +1222,18 @@ var IlhaBuilder = class IlhaBuilder {
|
|
|
1183
1222
|
}
|
|
1184
1223
|
});
|
|
1185
1224
|
if (!results.some((r) => r.result instanceof Promise) || sync) {
|
|
1186
|
-
const
|
|
1187
|
-
for (const r of results) if (r.result instanceof Promise)
|
|
1225
|
+
const envelopes = {};
|
|
1226
|
+
for (const r of results) if (r.result instanceof Promise) envelopes[r.key] = {
|
|
1188
1227
|
loading: true,
|
|
1189
1228
|
value: void 0,
|
|
1190
1229
|
error: void 0
|
|
1191
1230
|
};
|
|
1192
|
-
else
|
|
1231
|
+
else envelopes[r.key] = {
|
|
1193
1232
|
loading: false,
|
|
1194
1233
|
value: r.result,
|
|
1195
1234
|
error: void 0
|
|
1196
1235
|
};
|
|
1236
|
+
const derived = buildDerivedAccessors(envelopes);
|
|
1197
1237
|
const prevSub = setActiveSub(void 0);
|
|
1198
1238
|
try {
|
|
1199
1239
|
const { html } = renderWithCtx(() => fn({
|
|
@@ -1227,8 +1267,9 @@ var IlhaBuilder = class IlhaBuilder {
|
|
|
1227
1267
|
};
|
|
1228
1268
|
}
|
|
1229
1269
|
})).then(async (resolved) => {
|
|
1230
|
-
const
|
|
1231
|
-
for (const r of resolved)
|
|
1270
|
+
const envelopes = {};
|
|
1271
|
+
for (const r of resolved) envelopes[r.key] = r.envelope;
|
|
1272
|
+
const derived = buildDerivedAccessors(envelopes);
|
|
1232
1273
|
const prevSub = setActiveSub(void 0);
|
|
1233
1274
|
try {
|
|
1234
1275
|
const { html } = await renderWithCtx(() => fn({
|
|
@@ -1537,6 +1578,7 @@ Element: ${host.outerHTML.slice(0, 120)}`);
|
|
|
1537
1578
|
try {
|
|
1538
1579
|
userCleanup = entry.fn({
|
|
1539
1580
|
state,
|
|
1581
|
+
derived,
|
|
1540
1582
|
input,
|
|
1541
1583
|
host,
|
|
1542
1584
|
signal: runSignal
|