@cajoa/core 0.12.1 → 0.14.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/dist/index.cjs CHANGED
@@ -20,15 +20,21 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
20
20
  // src/index.ts
21
21
  var index_exports = {};
22
22
  __export(index_exports, {
23
+ Dynamic: () => Dynamic,
23
24
  ErrorBoundary: () => ErrorBoundary,
24
25
  For: () => For,
25
26
  Fragment: () => Fragment,
27
+ Match: () => Match,
28
+ Portal: () => Portal,
26
29
  Show: () => Show,
30
+ Suspense: () => Suspense,
31
+ Switch: () => Switch,
27
32
  batch: () => batch,
28
33
  catchError: () => catchError,
29
34
  createContext: () => createContext,
30
35
  createEffect: () => createEffect,
31
36
  createMemo: () => createMemo,
37
+ createResource: () => createResource,
32
38
  createRoot: () => createRoot,
33
39
  createSignal: () => createSignal,
34
40
  getRenderer: () => getRenderer,
@@ -36,6 +42,7 @@ __export(index_exports, {
36
42
  jsx: () => jsx,
37
43
  jsxDEV: () => jsxDEV,
38
44
  jsxs: () => jsxs,
45
+ lazy: () => lazy,
39
46
  onCleanup: () => onCleanup,
40
47
  onMount: () => onMount,
41
48
  provide: () => provide,
@@ -47,12 +54,22 @@ __export(index_exports, {
47
54
  module.exports = __toCommonJS(index_exports);
48
55
 
49
56
  // src/dev.ts
57
+ var muted = 0;
50
58
  function isDev() {
51
59
  return globalThis.__CAJOA_DEV__ === true;
52
60
  }
61
+ function quietly(fn) {
62
+ muted++;
63
+ try {
64
+ return fn();
65
+ } finally {
66
+ muted--;
67
+ }
68
+ }
53
69
  function warn(message) {
54
- if (isDev() && typeof console !== "undefined")
70
+ if (muted === 0 && isDev() && typeof console !== "undefined") {
55
71
  console.warn(`[cajoa] ${message}`);
72
+ }
56
73
  }
57
74
  function logError(message, error) {
58
75
  if (isDev() && typeof console !== "undefined")
@@ -259,6 +276,100 @@ function createMemo(fn) {
259
276
  return value;
260
277
  }
261
278
 
279
+ // src/reactivity/suspense.ts
280
+ var SuspenseContext = createContext(null);
281
+ function trackPending() {
282
+ const state = quietly(() => useContext(SuspenseContext));
283
+ return state ? () => state.add() : void 0;
284
+ }
285
+
286
+ // src/reactivity/resource.ts
287
+ function isPromiseLike(value) {
288
+ return typeof value?.then === "function";
289
+ }
290
+ function createResource(a, b) {
291
+ const hasSource = typeof b === "function";
292
+ const source = hasSource ? a : () => true;
293
+ const fetcher = hasSource ? b : a;
294
+ const [data, setData] = createSignal(void 0);
295
+ const [failure, setFailure] = createSignal(null);
296
+ const [loading, setLoading] = createSignal(false);
297
+ const [version, setVersion] = createSignal(0);
298
+ let run = 0;
299
+ const track = trackPending();
300
+ let release;
301
+ let settled = false;
302
+ const finish = (id, outcome) => {
303
+ if (id !== run) return;
304
+ settled = true;
305
+ batch(() => {
306
+ setLoading(false);
307
+ if ("error" in outcome) {
308
+ setFailure({ error: outcome.error });
309
+ } else {
310
+ setFailure(null);
311
+ setData(() => outcome.value);
312
+ }
313
+ });
314
+ release?.();
315
+ release = void 0;
316
+ };
317
+ createEffect(() => {
318
+ const s = source();
319
+ version();
320
+ run++;
321
+ const id = run;
322
+ onCleanup(() => {
323
+ run++;
324
+ release?.();
325
+ release = void 0;
326
+ });
327
+ if (s === false || s === null || s === void 0) {
328
+ batch(() => {
329
+ setLoading(false);
330
+ setFailure(null);
331
+ setData(() => void 0);
332
+ });
333
+ return;
334
+ }
335
+ if (!settled && track && !release) release = track();
336
+ setLoading(true);
337
+ setFailure(null);
338
+ let result;
339
+ try {
340
+ result = untrack(() => fetcher(s));
341
+ } catch (error) {
342
+ finish(id, { error });
343
+ return;
344
+ }
345
+ if (isPromiseLike(result)) {
346
+ result.then(
347
+ (value) => finish(id, { value }),
348
+ (error) => finish(id, { error })
349
+ );
350
+ } else {
351
+ finish(id, { value: result });
352
+ }
353
+ });
354
+ const resource = (() => {
355
+ const failed = failure();
356
+ if (failed) throw failed.error;
357
+ return data();
358
+ });
359
+ resource.loading = loading;
360
+ resource.error = () => failure()?.error;
361
+ return [
362
+ resource,
363
+ {
364
+ refetch: () => setVersion((v) => v + 1),
365
+ mutate: (value) => batch(() => {
366
+ setFailure(null);
367
+ setData(() => value);
368
+ })
369
+ }
370
+ ];
371
+ }
372
+
262
373
  // src/renderer/index.ts
263
374
  var current = null;
264
375
  function setRenderer(renderer) {
@@ -458,6 +569,32 @@ function ErrorBoundary(props) {
458
569
  };
459
570
  }
460
571
 
572
+ // src/components/switch.ts
573
+ function resolve3(value) {
574
+ return typeof value === "function" ? value() : value;
575
+ }
576
+ function Match(props) {
577
+ return { __match: true, when: props.when, children: props.children };
578
+ }
579
+ var isMatch = (value) => typeof value === "object" && value !== null && value.__match === true;
580
+ function Switch(props) {
581
+ const matches = [props.children].flat(Infinity).filter(isMatch);
582
+ const selected = createMemo(() => {
583
+ for (let i = 0; i < matches.length; i++) {
584
+ if (resolve3(matches[i].when)) return i;
585
+ }
586
+ return -1;
587
+ });
588
+ return () => {
589
+ const i = selected();
590
+ return untrack(
591
+ () => resolve3(
592
+ i === -1 ? props.fallback ?? null : matches[i].children ?? null
593
+ )
594
+ );
595
+ };
596
+ }
597
+
461
598
  // src/jsx-runtime.ts
462
599
  function jsx(type, props) {
463
600
  if (typeof type === "function") return type(props ?? {});
@@ -472,17 +609,133 @@ function Fragment(props) {
472
609
  appendChildren(frag, props.children === void 0 ? [] : [props.children]);
473
610
  return frag;
474
611
  }
612
+
613
+ // src/components/dynamic.ts
614
+ function Dynamic(props) {
615
+ const { is, ...rest } = props;
616
+ const read = typeof is === "function" ? is : () => is;
617
+ return () => {
618
+ const target = read();
619
+ if (target == null || target === false) return null;
620
+ if (typeof target !== "string" && typeof target !== "function") {
621
+ throw new Error(
622
+ `<Dynamic>: \`is\` must be a tag name, or a function that returns a tag name or a component (got ${typeof target}). To render a component directly, write is={() => MyComponent}.`
623
+ );
624
+ }
625
+ return untrack(() => jsx(target, rest));
626
+ };
627
+ }
628
+
629
+ // src/components/portal.ts
630
+ function resolve4(value) {
631
+ return typeof value === "function" ? value() : value;
632
+ }
633
+ function Portal(props) {
634
+ const r = getRenderer();
635
+ const mount = props.mount ?? r.portalRoot?.();
636
+ if (!mount) {
637
+ throw new Error(
638
+ "<Portal>: nowhere to render into. Pass a `mount` element (this renderer has no default)."
639
+ );
640
+ }
641
+ const start = r.createMarker();
642
+ const end = r.createMarker();
643
+ r.insert(mount, start);
644
+ r.insert(
645
+ mount,
646
+ materialize(() => resolve4(props.children ?? null))
647
+ );
648
+ r.insert(mount, end);
649
+ onCleanup(() => {
650
+ let node = r.nextSibling(start);
651
+ while (node && node !== end) {
652
+ const next = r.nextSibling(node);
653
+ r.remove(node);
654
+ node = next;
655
+ }
656
+ r.remove(start);
657
+ r.remove(end);
658
+ });
659
+ return null;
660
+ }
661
+
662
+ // src/components/suspense.ts
663
+ function resolve5(value) {
664
+ return typeof value === "function" ? value() : value;
665
+ }
666
+ function Suspense(props) {
667
+ const [pending2, setPending] = createSignal(0);
668
+ const [ready, setReady] = createSignal(false);
669
+ const state = {
670
+ add() {
671
+ setPending((n) => n + 1);
672
+ let released = false;
673
+ return () => {
674
+ if (released) return;
675
+ released = true;
676
+ setPending((n) => n - 1);
677
+ };
678
+ }
679
+ };
680
+ const content = untrack(
681
+ () => provide(
682
+ SuspenseContext,
683
+ state,
684
+ () => materialize(resolve5(props.children ?? null))
685
+ )
686
+ );
687
+ createEffect(() => {
688
+ if (pending2() === 0) setReady(true);
689
+ });
690
+ return () => ready() ? content : untrack(() => resolve5(props.fallback ?? null));
691
+ }
692
+
693
+ // src/components/lazy.ts
694
+ function lazy(loader) {
695
+ let promise;
696
+ let component;
697
+ let failure = null;
698
+ const load = () => promise ??= loader().then(
699
+ (module2) => {
700
+ component = typeof module2 === "function" ? module2 : module2.default;
701
+ },
702
+ (error) => {
703
+ failure = { error };
704
+ }
705
+ );
706
+ return function LazyComponent(props) {
707
+ if (component) return component(props);
708
+ if (failure) throw failure.error;
709
+ const release = trackPending()?.();
710
+ const [done, setDone] = createSignal(false);
711
+ load().then(() => {
712
+ setDone(true);
713
+ release?.();
714
+ });
715
+ return () => {
716
+ done();
717
+ if (failure) throw failure.error;
718
+ return component ? untrack(() => component(props)) : null;
719
+ };
720
+ };
721
+ }
475
722
  // Annotate the CommonJS export names for ESM import in node:
476
723
  0 && (module.exports = {
724
+ Dynamic,
477
725
  ErrorBoundary,
478
726
  For,
479
727
  Fragment,
728
+ Match,
729
+ Portal,
480
730
  Show,
731
+ Suspense,
732
+ Switch,
481
733
  batch,
482
734
  catchError,
483
735
  createContext,
484
736
  createEffect,
485
737
  createMemo,
738
+ createResource,
486
739
  createRoot,
487
740
  createSignal,
488
741
  getRenderer,
@@ -490,6 +743,7 @@ function Fragment(props) {
490
743
  jsx,
491
744
  jsxDEV,
492
745
  jsxs,
746
+ lazy,
493
747
  onCleanup,
494
748
  onMount,
495
749
  provide,
package/dist/index.d.cts CHANGED
@@ -17,6 +17,19 @@ declare function useContext<T>(ctx: Context<T>): T;
17
17
 
18
18
  declare function createMemo<T>(fn: () => T): () => T;
19
19
 
20
+ interface Resource<T> {
21
+ (): T | undefined;
22
+ loading: () => boolean;
23
+ error: () => unknown;
24
+ }
25
+ interface ResourceActions<T> {
26
+ refetch: () => void;
27
+ mutate(value: T | undefined): void;
28
+ }
29
+ type ResourceTuple<T> = [Resource<T>, ResourceActions<T>];
30
+ declare function createResource<T>(fetcher: () => T | Promise<T>): ResourceTuple<T>;
31
+ declare function createResource<T, S>(source: () => S | false | null | undefined, fetcher: (source: S) => T | Promise<T>): ResourceTuple<T>;
32
+
20
33
  type HostNode = object;
21
34
  interface Renderer<N = any> {
22
35
  createElement(tag: string): N;
@@ -28,6 +41,7 @@ interface Renderer<N = any> {
28
41
  remove(node: N): void;
29
42
  nextSibling(node: N): N | null;
30
43
  parentNode(node: N): N | null;
44
+ portalRoot?(): N;
31
45
  clear(parent: N): void;
32
46
  setProp(el: N, name: string, value: unknown): void;
33
47
  }
@@ -37,11 +51,11 @@ declare function h(tag: string, props: Record<string, any> | null, ...children:
37
51
 
38
52
  declare function render(component: () => HostNode, container: HostNode): () => void;
39
53
 
40
- type Lazy$1<T> = T | (() => T);
54
+ type Lazy$4<T> = T | (() => T);
41
55
  declare function Show<T>(props: {
42
- when: Lazy$1<T>;
43
- children?: Lazy$1<Child>;
44
- fallback?: Lazy$1<Child>;
56
+ when: Lazy$4<T>;
57
+ children?: Lazy$4<Child>;
58
+ fallback?: Lazy$4<Child>;
45
59
  }): () => string | number | boolean | object | null;
46
60
 
47
61
  declare function For<T>(props: {
@@ -50,13 +64,51 @@ declare function For<T>(props: {
50
64
  key?: (item: T, index: number) => unknown;
51
65
  }): () => object[];
52
66
 
53
- type Lazy<T> = T | (() => T);
67
+ type Lazy$3<T> = T | (() => T);
54
68
  declare function ErrorBoundary(props: {
55
69
  fallback: Child | ((error: unknown, reset: () => void) => Child);
56
- children?: Lazy<Child>;
70
+ children?: Lazy$3<Child>;
57
71
  onError?: (error: unknown) => void;
58
72
  }): () => unknown;
59
73
 
74
+ type Lazy$2<T> = T | (() => T);
75
+ interface MatchDef {
76
+ __match: true;
77
+ when: Lazy$2<unknown>;
78
+ children?: Lazy$2<Child>;
79
+ }
80
+ declare function Match(props: {
81
+ when: Lazy$2<unknown>;
82
+ children?: Lazy$2<Child>;
83
+ }): MatchDef;
84
+ declare function Switch(props: {
85
+ fallback?: Lazy$2<Child>;
86
+ children?: unknown;
87
+ }): () => string | number | boolean | object | null;
88
+
89
+ type Target = string | ((props: any) => unknown) | null | undefined | false;
90
+ declare function Dynamic(props: {
91
+ is: Target | (() => Target);
92
+ [key: string]: unknown;
93
+ }): () => any;
94
+
95
+ type Lazy$1<T> = T | (() => T);
96
+ declare function Portal(props: {
97
+ mount?: HostNode;
98
+ children?: Lazy$1<Child>;
99
+ }): null;
100
+
101
+ type Lazy<T> = T | (() => T);
102
+ declare function Suspense(props: {
103
+ fallback?: Lazy<Child>;
104
+ children?: Lazy<Child>;
105
+ }): () => unknown;
106
+
107
+ type Component<P> = (props: P) => unknown;
108
+ declare function lazy<P = Record<string, unknown>>(loader: () => Promise<{
109
+ default: Component<P>;
110
+ } | Component<P>>): Component<P>;
111
+
60
112
  declare function jsx(type: any, props: Record<string, any> | null): any;
61
113
  declare const jsxs: typeof jsx;
62
114
  declare const jsxDEV: typeof jsx;
@@ -76,5 +128,5 @@ declare function Fragment(props: {
76
128
  declare function setRenderer(renderer: Renderer): void;
77
129
  declare function getRenderer(): Renderer;
78
130
 
79
- export { ErrorBoundary, For, Fragment, JSX, Show, batch, catchError, createContext, createEffect, createMemo, createRoot, createSignal, getRenderer, h, jsx, jsxDEV, jsxs, onCleanup, onMount, provide, render, setRenderer, untrack, useContext };
80
- export type { Child, Context, HostNode, Renderer };
131
+ export { Dynamic, ErrorBoundary, For, Fragment, JSX, Match, Portal, Show, Suspense, Switch, batch, catchError, createContext, createEffect, createMemo, createResource, createRoot, createSignal, getRenderer, h, jsx, jsxDEV, jsxs, lazy, onCleanup, onMount, provide, render, setRenderer, untrack, useContext };
132
+ export type { Child, Context, HostNode, MatchDef, Renderer, Resource, ResourceActions };
package/dist/index.d.ts CHANGED
@@ -17,6 +17,19 @@ declare function useContext<T>(ctx: Context<T>): T;
17
17
 
18
18
  declare function createMemo<T>(fn: () => T): () => T;
19
19
 
20
+ interface Resource<T> {
21
+ (): T | undefined;
22
+ loading: () => boolean;
23
+ error: () => unknown;
24
+ }
25
+ interface ResourceActions<T> {
26
+ refetch: () => void;
27
+ mutate(value: T | undefined): void;
28
+ }
29
+ type ResourceTuple<T> = [Resource<T>, ResourceActions<T>];
30
+ declare function createResource<T>(fetcher: () => T | Promise<T>): ResourceTuple<T>;
31
+ declare function createResource<T, S>(source: () => S | false | null | undefined, fetcher: (source: S) => T | Promise<T>): ResourceTuple<T>;
32
+
20
33
  type HostNode = object;
21
34
  interface Renderer<N = any> {
22
35
  createElement(tag: string): N;
@@ -28,6 +41,7 @@ interface Renderer<N = any> {
28
41
  remove(node: N): void;
29
42
  nextSibling(node: N): N | null;
30
43
  parentNode(node: N): N | null;
44
+ portalRoot?(): N;
31
45
  clear(parent: N): void;
32
46
  setProp(el: N, name: string, value: unknown): void;
33
47
  }
@@ -37,11 +51,11 @@ declare function h(tag: string, props: Record<string, any> | null, ...children:
37
51
 
38
52
  declare function render(component: () => HostNode, container: HostNode): () => void;
39
53
 
40
- type Lazy$1<T> = T | (() => T);
54
+ type Lazy$4<T> = T | (() => T);
41
55
  declare function Show<T>(props: {
42
- when: Lazy$1<T>;
43
- children?: Lazy$1<Child>;
44
- fallback?: Lazy$1<Child>;
56
+ when: Lazy$4<T>;
57
+ children?: Lazy$4<Child>;
58
+ fallback?: Lazy$4<Child>;
45
59
  }): () => string | number | boolean | object | null;
46
60
 
47
61
  declare function For<T>(props: {
@@ -50,13 +64,51 @@ declare function For<T>(props: {
50
64
  key?: (item: T, index: number) => unknown;
51
65
  }): () => object[];
52
66
 
53
- type Lazy<T> = T | (() => T);
67
+ type Lazy$3<T> = T | (() => T);
54
68
  declare function ErrorBoundary(props: {
55
69
  fallback: Child | ((error: unknown, reset: () => void) => Child);
56
- children?: Lazy<Child>;
70
+ children?: Lazy$3<Child>;
57
71
  onError?: (error: unknown) => void;
58
72
  }): () => unknown;
59
73
 
74
+ type Lazy$2<T> = T | (() => T);
75
+ interface MatchDef {
76
+ __match: true;
77
+ when: Lazy$2<unknown>;
78
+ children?: Lazy$2<Child>;
79
+ }
80
+ declare function Match(props: {
81
+ when: Lazy$2<unknown>;
82
+ children?: Lazy$2<Child>;
83
+ }): MatchDef;
84
+ declare function Switch(props: {
85
+ fallback?: Lazy$2<Child>;
86
+ children?: unknown;
87
+ }): () => string | number | boolean | object | null;
88
+
89
+ type Target = string | ((props: any) => unknown) | null | undefined | false;
90
+ declare function Dynamic(props: {
91
+ is: Target | (() => Target);
92
+ [key: string]: unknown;
93
+ }): () => any;
94
+
95
+ type Lazy$1<T> = T | (() => T);
96
+ declare function Portal(props: {
97
+ mount?: HostNode;
98
+ children?: Lazy$1<Child>;
99
+ }): null;
100
+
101
+ type Lazy<T> = T | (() => T);
102
+ declare function Suspense(props: {
103
+ fallback?: Lazy<Child>;
104
+ children?: Lazy<Child>;
105
+ }): () => unknown;
106
+
107
+ type Component<P> = (props: P) => unknown;
108
+ declare function lazy<P = Record<string, unknown>>(loader: () => Promise<{
109
+ default: Component<P>;
110
+ } | Component<P>>): Component<P>;
111
+
60
112
  declare function jsx(type: any, props: Record<string, any> | null): any;
61
113
  declare const jsxs: typeof jsx;
62
114
  declare const jsxDEV: typeof jsx;
@@ -76,5 +128,5 @@ declare function Fragment(props: {
76
128
  declare function setRenderer(renderer: Renderer): void;
77
129
  declare function getRenderer(): Renderer;
78
130
 
79
- export { ErrorBoundary, For, Fragment, JSX, Show, batch, catchError, createContext, createEffect, createMemo, createRoot, createSignal, getRenderer, h, jsx, jsxDEV, jsxs, onCleanup, onMount, provide, render, setRenderer, untrack, useContext };
80
- export type { Child, Context, HostNode, Renderer };
131
+ export { Dynamic, ErrorBoundary, For, Fragment, JSX, Match, Portal, Show, Suspense, Switch, batch, catchError, createContext, createEffect, createMemo, createResource, createRoot, createSignal, getRenderer, h, jsx, jsxDEV, jsxs, lazy, onCleanup, onMount, provide, render, setRenderer, untrack, useContext };
132
+ export type { Child, Context, HostNode, MatchDef, Renderer, Resource, ResourceActions };
package/dist/index.js CHANGED
@@ -1,10 +1,20 @@
1
1
  // src/dev.ts
2
+ var muted = 0;
2
3
  function isDev() {
3
4
  return globalThis.__CAJOA_DEV__ === true;
4
5
  }
6
+ function quietly(fn) {
7
+ muted++;
8
+ try {
9
+ return fn();
10
+ } finally {
11
+ muted--;
12
+ }
13
+ }
5
14
  function warn(message) {
6
- if (isDev() && typeof console !== "undefined")
15
+ if (muted === 0 && isDev() && typeof console !== "undefined") {
7
16
  console.warn(`[cajoa] ${message}`);
17
+ }
8
18
  }
9
19
  function logError(message, error) {
10
20
  if (isDev() && typeof console !== "undefined")
@@ -211,6 +221,100 @@ function createMemo(fn) {
211
221
  return value;
212
222
  }
213
223
 
224
+ // src/reactivity/suspense.ts
225
+ var SuspenseContext = createContext(null);
226
+ function trackPending() {
227
+ const state = quietly(() => useContext(SuspenseContext));
228
+ return state ? () => state.add() : void 0;
229
+ }
230
+
231
+ // src/reactivity/resource.ts
232
+ function isPromiseLike(value) {
233
+ return typeof value?.then === "function";
234
+ }
235
+ function createResource(a, b) {
236
+ const hasSource = typeof b === "function";
237
+ const source = hasSource ? a : () => true;
238
+ const fetcher = hasSource ? b : a;
239
+ const [data, setData] = createSignal(void 0);
240
+ const [failure, setFailure] = createSignal(null);
241
+ const [loading, setLoading] = createSignal(false);
242
+ const [version, setVersion] = createSignal(0);
243
+ let run = 0;
244
+ const track = trackPending();
245
+ let release;
246
+ let settled = false;
247
+ const finish = (id, outcome) => {
248
+ if (id !== run) return;
249
+ settled = true;
250
+ batch(() => {
251
+ setLoading(false);
252
+ if ("error" in outcome) {
253
+ setFailure({ error: outcome.error });
254
+ } else {
255
+ setFailure(null);
256
+ setData(() => outcome.value);
257
+ }
258
+ });
259
+ release?.();
260
+ release = void 0;
261
+ };
262
+ createEffect(() => {
263
+ const s = source();
264
+ version();
265
+ run++;
266
+ const id = run;
267
+ onCleanup(() => {
268
+ run++;
269
+ release?.();
270
+ release = void 0;
271
+ });
272
+ if (s === false || s === null || s === void 0) {
273
+ batch(() => {
274
+ setLoading(false);
275
+ setFailure(null);
276
+ setData(() => void 0);
277
+ });
278
+ return;
279
+ }
280
+ if (!settled && track && !release) release = track();
281
+ setLoading(true);
282
+ setFailure(null);
283
+ let result;
284
+ try {
285
+ result = untrack(() => fetcher(s));
286
+ } catch (error) {
287
+ finish(id, { error });
288
+ return;
289
+ }
290
+ if (isPromiseLike(result)) {
291
+ result.then(
292
+ (value) => finish(id, { value }),
293
+ (error) => finish(id, { error })
294
+ );
295
+ } else {
296
+ finish(id, { value: result });
297
+ }
298
+ });
299
+ const resource = (() => {
300
+ const failed = failure();
301
+ if (failed) throw failed.error;
302
+ return data();
303
+ });
304
+ resource.loading = loading;
305
+ resource.error = () => failure()?.error;
306
+ return [
307
+ resource,
308
+ {
309
+ refetch: () => setVersion((v) => v + 1),
310
+ mutate: (value) => batch(() => {
311
+ setFailure(null);
312
+ setData(() => value);
313
+ })
314
+ }
315
+ ];
316
+ }
317
+
214
318
  // src/renderer/index.ts
215
319
  var current = null;
216
320
  function setRenderer(renderer) {
@@ -410,6 +514,32 @@ function ErrorBoundary(props) {
410
514
  };
411
515
  }
412
516
 
517
+ // src/components/switch.ts
518
+ function resolve3(value) {
519
+ return typeof value === "function" ? value() : value;
520
+ }
521
+ function Match(props) {
522
+ return { __match: true, when: props.when, children: props.children };
523
+ }
524
+ var isMatch = (value) => typeof value === "object" && value !== null && value.__match === true;
525
+ function Switch(props) {
526
+ const matches = [props.children].flat(Infinity).filter(isMatch);
527
+ const selected = createMemo(() => {
528
+ for (let i = 0; i < matches.length; i++) {
529
+ if (resolve3(matches[i].when)) return i;
530
+ }
531
+ return -1;
532
+ });
533
+ return () => {
534
+ const i = selected();
535
+ return untrack(
536
+ () => resolve3(
537
+ i === -1 ? props.fallback ?? null : matches[i].children ?? null
538
+ )
539
+ );
540
+ };
541
+ }
542
+
413
543
  // src/jsx-runtime.ts
414
544
  function jsx(type, props) {
415
545
  if (typeof type === "function") return type(props ?? {});
@@ -424,16 +554,132 @@ function Fragment(props) {
424
554
  appendChildren(frag, props.children === void 0 ? [] : [props.children]);
425
555
  return frag;
426
556
  }
557
+
558
+ // src/components/dynamic.ts
559
+ function Dynamic(props) {
560
+ const { is, ...rest } = props;
561
+ const read = typeof is === "function" ? is : () => is;
562
+ return () => {
563
+ const target = read();
564
+ if (target == null || target === false) return null;
565
+ if (typeof target !== "string" && typeof target !== "function") {
566
+ throw new Error(
567
+ `<Dynamic>: \`is\` must be a tag name, or a function that returns a tag name or a component (got ${typeof target}). To render a component directly, write is={() => MyComponent}.`
568
+ );
569
+ }
570
+ return untrack(() => jsx(target, rest));
571
+ };
572
+ }
573
+
574
+ // src/components/portal.ts
575
+ function resolve4(value) {
576
+ return typeof value === "function" ? value() : value;
577
+ }
578
+ function Portal(props) {
579
+ const r = getRenderer();
580
+ const mount = props.mount ?? r.portalRoot?.();
581
+ if (!mount) {
582
+ throw new Error(
583
+ "<Portal>: nowhere to render into. Pass a `mount` element (this renderer has no default)."
584
+ );
585
+ }
586
+ const start = r.createMarker();
587
+ const end = r.createMarker();
588
+ r.insert(mount, start);
589
+ r.insert(
590
+ mount,
591
+ materialize(() => resolve4(props.children ?? null))
592
+ );
593
+ r.insert(mount, end);
594
+ onCleanup(() => {
595
+ let node = r.nextSibling(start);
596
+ while (node && node !== end) {
597
+ const next = r.nextSibling(node);
598
+ r.remove(node);
599
+ node = next;
600
+ }
601
+ r.remove(start);
602
+ r.remove(end);
603
+ });
604
+ return null;
605
+ }
606
+
607
+ // src/components/suspense.ts
608
+ function resolve5(value) {
609
+ return typeof value === "function" ? value() : value;
610
+ }
611
+ function Suspense(props) {
612
+ const [pending2, setPending] = createSignal(0);
613
+ const [ready, setReady] = createSignal(false);
614
+ const state = {
615
+ add() {
616
+ setPending((n) => n + 1);
617
+ let released = false;
618
+ return () => {
619
+ if (released) return;
620
+ released = true;
621
+ setPending((n) => n - 1);
622
+ };
623
+ }
624
+ };
625
+ const content = untrack(
626
+ () => provide(
627
+ SuspenseContext,
628
+ state,
629
+ () => materialize(resolve5(props.children ?? null))
630
+ )
631
+ );
632
+ createEffect(() => {
633
+ if (pending2() === 0) setReady(true);
634
+ });
635
+ return () => ready() ? content : untrack(() => resolve5(props.fallback ?? null));
636
+ }
637
+
638
+ // src/components/lazy.ts
639
+ function lazy(loader) {
640
+ let promise;
641
+ let component;
642
+ let failure = null;
643
+ const load = () => promise ??= loader().then(
644
+ (module) => {
645
+ component = typeof module === "function" ? module : module.default;
646
+ },
647
+ (error) => {
648
+ failure = { error };
649
+ }
650
+ );
651
+ return function LazyComponent(props) {
652
+ if (component) return component(props);
653
+ if (failure) throw failure.error;
654
+ const release = trackPending()?.();
655
+ const [done, setDone] = createSignal(false);
656
+ load().then(() => {
657
+ setDone(true);
658
+ release?.();
659
+ });
660
+ return () => {
661
+ done();
662
+ if (failure) throw failure.error;
663
+ return component ? untrack(() => component(props)) : null;
664
+ };
665
+ };
666
+ }
427
667
  export {
668
+ Dynamic,
428
669
  ErrorBoundary,
429
670
  For,
430
671
  Fragment,
672
+ Match,
673
+ Portal,
431
674
  Show,
675
+ Suspense,
676
+ Switch,
432
677
  batch,
433
678
  catchError,
434
679
  createContext,
435
680
  createEffect,
436
681
  createMemo,
682
+ createResource,
437
683
  createRoot,
438
684
  createSignal,
439
685
  getRenderer,
@@ -441,6 +687,7 @@ export {
441
687
  jsx,
442
688
  jsxDEV,
443
689
  jsxs,
690
+ lazy,
444
691
  onCleanup,
445
692
  onMount,
446
693
  provide,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cajoa/core",
3
- "version": "0.12.1",
3
+ "version": "0.14.0",
4
4
  "description": "Cajoa core: signals, JSX runtime and the renderer interface.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",