cajoa 0.2.0 → 0.4.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 CHANGED
@@ -1,13 +1,14 @@
1
1
  # Cajoa
2
2
 
3
- A small, beginner-friendly frontend framework built on **signals**.
3
+ A small, beginner-friendly frontend framework built on **signals** and **JSX**.
4
4
  No virtual DOM, no hooks rules, no re-rendering components.
5
5
 
6
- > ⚠️ **v0.2 (early stage).** The API may still change. Not production-ready yet.
6
+ > ⚠️ **v0.4 (early stage).** The API may still change. Not production-ready yet.
7
7
 
8
8
  ## Why Cajoa?
9
9
 
10
10
  - **Components run once.** A component is just a function that builds DOM. Only the parts that depend on a signal update.
11
+ - **Plain JSX.** Write `<button onClick={...}>`, no special syntax to learn.
11
12
  - **Tiny and readable.** The whole core is small enough to read in one sitting.
12
13
  - **TypeScript-first**, ESM + CommonJS, zero runtime dependencies.
13
14
 
@@ -17,54 +18,94 @@ No virtual DOM, no hooks rules, no re-rendering components.
17
18
  npm install cajoa
18
19
  ```
19
20
 
21
+ ## Setup (JSX)
22
+
23
+ In your `tsconfig.json`:
24
+
25
+ ```json
26
+ {
27
+ "compilerOptions": {
28
+ "jsx": "react-jsx",
29
+ "jsxImportSource": "cajoa"
30
+ }
31
+ }
32
+ ```
33
+
34
+ If you use Vite and your JSX is not transformed, also set it in `vite.config.ts`
35
+ (`oxc: { jsx: { runtime: "automatic", importSource: "cajoa" } }` on newer Vite,
36
+ or `esbuild: { jsx: "automatic", jsxImportSource: "cajoa" }` on older ones).
37
+
20
38
  ## Quick start
21
39
 
22
- ```ts
23
- import { createSignal, h, render } from "cajoa";
40
+ ```tsx
41
+ import { createSignal, render } from "cajoa";
24
42
 
25
43
  function Counter() {
26
44
  const [count, setCount] = createSignal(0);
27
45
 
28
- return h(
29
- "button",
30
- { onClick: () => setCount((c) => c + 1) },
31
- "Clicked ",
32
- count,
33
- " times",
46
+ return (
47
+ <button onClick={() => setCount((c) => c + 1)}>
48
+ Clicked {count} times
49
+ </button>
34
50
  );
35
51
  }
36
52
 
37
53
  render(Counter, document.getElementById("app")!);
38
54
  ```
39
55
 
40
- Passing `count` (the function itself, not `count()`) is what makes that part of the DOM reactive.
56
+ ## Two rules to remember
57
+
58
+ Cajoa has no compiler, so JSX expressions run exactly once. That gives two rules:
59
+
60
+ 1. **Pass the signal, don't call it.**
61
+ `{count}` is reactive. `{count()}` is read once and never updates.
62
+ For computed values, pass a function: `{() => count() * 2}`.
63
+ 2. **`Show` children are a function.**
64
+ `<Show when={on}>{() => <p>Hello</p>}</Show>`
65
+ Otherwise the element is created even when it is hidden.
41
66
 
42
67
  ## Lists and conditions
43
68
 
44
- ```ts
45
- import { createSignal, h, Show, For } from "cajoa";
69
+ ```tsx
70
+ import { createSignal, Show, For } from "cajoa";
46
71
 
47
72
  const [todos, setTodos] = createSignal([{ id: 1, text: "Learn Cajoa" }]);
48
73
 
49
- h(
50
- "div",
51
- null,
52
- Show({
53
- when: () => todos().length > 0,
54
- fallback: () => h("p", null, "Nothing to do."),
55
- children: () =>
56
- h(
57
- "ul",
58
- null,
59
- For({
60
- each: todos,
61
- children: (todo) => h("li", null, todo.text),
62
- }),
63
- ),
64
- }),
74
+ <Show when={() => todos().length > 0} fallback={() => <p>Nothing to do.</p>}>
75
+ {() => (
76
+ <ul>
77
+ <For each={todos}>{(todo) => <li>{todo.text}</li>}</For>
78
+ </ul>
79
+ )}
80
+ </Show>;
81
+ ```
82
+
83
+ ## Routing
84
+
85
+ ```tsx
86
+ import { render, Router, Route, Link, useParams } from "cajoa";
87
+
88
+ const User = () => <h1>User {useParams().id}</h1>;
89
+
90
+ render(
91
+ () => (
92
+ <div>
93
+ <nav>
94
+ <Link href="/">Home</Link> <Link href="/users/7">User 7</Link>
95
+ </nav>
96
+ <Router>
97
+ <Route path="/" component={() => <h1>Home</h1>} />
98
+ <Route path="/users/:id" component={User} />
99
+ <Route path="*" component={() => <h1>Not found</h1>} />
100
+ </Router>
101
+ </div>
102
+ ),
103
+ document.getElementById("app")!,
65
104
  );
66
105
  ```
67
106
 
107
+ Routes are matched in order, so put `path="*"` last.
108
+
68
109
  ## API
69
110
 
70
111
  ### Reactivity
@@ -76,14 +117,19 @@ h(
76
117
  | `createMemo(fn)` | Cached computed value. Only notifies when the result changes. |
77
118
  | `batch(fn)` | Groups several `set` calls into one update. |
78
119
 
79
- ### DOM
120
+ ### Components
121
+
122
+ | Component | What it does |
123
+ | ---------------------- | ---------------------------------------------------------------------------------------------------- |
124
+ | `<Show when fallback>` | Conditional rendering. `children` and `fallback` are functions. |
125
+ | `<For each key?>` | List rendering. Reuses DOM nodes for unchanged items. The child is a function that returns a `Node`. |
80
126
 
81
- | Function | What it does |
82
- | ------------------------------------ | -------------------------------------------------------------------------------------- |
83
- | `h(tag, props, ...children)` | Creates a real DOM element. Function props/children are reactive. |
84
- | `render(component, container)` | Mounts a component. Returns a `dispose` function. |
85
- | `Show({ when, children, fallback })` | Conditional rendering. |
86
- | `For({ each, children, key? })` | List rendering. Reuses DOM nodes for unchanged items. `children` must return a `Node`. |
127
+ ### Rendering
128
+
129
+ | Function | What it does |
130
+ | ------------------------------ | ------------------------------------------------- |
131
+ | `render(component, container)` | Mounts a component. Returns a `dispose` function. |
132
+ | `h(tag, props, ...children)` | Low-level element creator. JSX compiles to this. |
87
133
 
88
134
  ### Lifecycle
89
135
 
@@ -101,30 +147,46 @@ h(
101
147
  | `provide(ctx, value, fn)` | Runs `fn`; everything created inside can read `value`. |
102
148
  | `useContext(ctx)` | Reads the nearest provided value (or the default). |
103
149
 
150
+ ### Router
151
+
152
+ | Function | What it does |
153
+ | ---------------------------- | ---------------------------------------------------------- |
154
+ | `<Router>` | Renders the first `<Route>` that matches the current path. |
155
+ | `<Route path component>` | Maps a path (`/users/:id`, `*`) to a component. |
156
+ | `<Link href>` | An `<a>` that navigates without reloading the page. |
157
+ | `navigate(to, { replace? })` | Navigates programmatically. |
158
+ | `useParams()` | Reads the route params (`{ id: "7" }`). |
159
+ | `usePath()` | Returns a signal getter for the current pathname. |
160
+ | `untrack(fn)` | Runs `fn` without subscribing to signals. |
161
+
104
162
  ## Good to know
105
163
 
106
164
  - `For` uses the item itself as the key by default. If you pass `key`, items with the same key are **not** re-rendered when their data changes, so keys must be unique and the item should hold its own signals if it needs to update.
107
165
  - `useContext` only works while a component is being created (or inside `onMount` / effects). Read it at the top of your component and use the value later in handlers.
108
166
  - Updating an object in place will not trigger effects. Set a new object instead.
167
+ - Fragments (`<>...</>`) work anywhere, but inside `<For>` the child must be a single element, because `For` reuses that node.
109
168
 
110
169
  ## Known limitations
111
170
 
112
- - No JSX support yet (`h()` only).
113
171
  - No SSR.
172
+ - Router: pathname only (no query/hash handling), no nested routes, no `base` path.
173
+ - `<Show>` and `<Router>` return a function, so they must sit inside an element, not be the root returned by `render`.
174
+ - Single-page apps need a server fallback to `index.html` for unknown paths.
114
175
  - `createMemo` can show a brief inconsistent state when memos depend on each other.
115
176
  - `set` calls are not batched automatically inside event handlers. Wrap them in `batch()`.
116
177
 
117
178
  ## Roadmap
118
179
 
119
180
  - [x] Signals and effects
120
- - [x] DOM bindings (`h`, `render`)
121
- - [x] `Show`, `For`
122
- - [x] Lifecycle (`onMount`, `onCleanup`) and disposal
181
+ - [x] DOM bindings and `render`
182
+ - [x] `Show`, `For` (keyed)
183
+ - [x] Lifecycle and disposal
123
184
  - [x] `createMemo`, `batch`
124
- - [x] Keyed `For`
125
185
  - [x] Context
126
- - [ ] JSX support
127
- - [ ] Router
186
+ - [x] JSX support
187
+ - [x] Fragments
188
+ - [ ] Vite plugin (removes the two rules above)
189
+ - [x] Router
128
190
  - [ ] SSR
129
191
 
130
192
  ## Development
package/dist/index.cjs CHANGED
@@ -21,6 +21,10 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
21
21
  var index_exports = {};
22
22
  __export(index_exports, {
23
23
  For: () => For,
24
+ Fragment: () => Fragment,
25
+ Link: () => Link,
26
+ Route: () => Route,
27
+ Router: () => Router,
24
28
  Show: () => Show,
25
29
  batch: () => batch,
26
30
  createContext: () => createContext,
@@ -29,11 +33,18 @@ __export(index_exports, {
29
33
  createRoot: () => createRoot,
30
34
  createSignal: () => createSignal,
31
35
  h: () => h,
36
+ jsx: () => jsx,
37
+ jsxDEV: () => jsxDEV,
38
+ jsxs: () => jsxs,
39
+ navigate: () => navigate,
32
40
  onCleanup: () => onCleanup,
33
41
  onMount: () => onMount,
34
42
  provide: () => provide,
35
43
  render: () => render,
36
- useContext: () => useContext
44
+ untrack: () => untrack,
45
+ useContext: () => useContext,
46
+ useParams: () => useParams,
47
+ usePath: () => usePath
37
48
  });
38
49
  module.exports = __toCommonJS(index_exports);
39
50
 
@@ -169,6 +180,15 @@ function useContext(ctx) {
169
180
  }
170
181
  return ctx.defaultValue;
171
182
  }
183
+ function untrack(fn) {
184
+ const prev = currentEffect;
185
+ currentEffect = null;
186
+ try {
187
+ return fn();
188
+ } finally {
189
+ currentEffect = prev;
190
+ }
191
+ }
172
192
 
173
193
  // src/dom/h.ts
174
194
  function h(tag, props, ...children) {
@@ -251,9 +271,17 @@ function render(component, container) {
251
271
  });
252
272
  }
253
273
 
274
+ // src/reactivity/memo.ts
275
+ function createMemo(fn) {
276
+ const [value, setValue] = createSignal(void 0);
277
+ createEffect(() => setValue(() => fn()));
278
+ return value;
279
+ }
280
+
254
281
  // src/components/show.ts
255
282
  function Show(props) {
256
- return () => props.when() ? props.children() : props.fallback?.() ?? null;
283
+ const visible = createMemo(() => !!props.when());
284
+ return () => visible() ? untrack(() => props.children()) : untrack(() => props.fallback?.() ?? null);
257
285
  }
258
286
 
259
287
  // src/components/for.ts
@@ -287,15 +315,97 @@ function For(props) {
287
315
  };
288
316
  }
289
317
 
290
- // src/reactivity/memo.ts
291
- function createMemo(fn) {
292
- const [value, setValue] = createSignal(void 0);
293
- createEffect(() => setValue(() => fn()));
294
- return value;
318
+ // src/jsx-runtime.ts
319
+ function jsx(type, props) {
320
+ if (typeof type === "function") return type(props ?? {});
321
+ const { children, ...rest } = props ?? {};
322
+ const kids = children === void 0 ? [] : [children];
323
+ return h(type, rest, ...kids);
324
+ }
325
+ var jsxs = jsx;
326
+ var jsxDEV = jsx;
327
+ function Fragment(props) {
328
+ const frag = document.createDocumentFragment();
329
+ appendChildren(frag, props.children === void 0 ? [] : [props.children]);
330
+ return frag;
331
+ }
332
+
333
+ // src/router.ts
334
+ var read = () => typeof location === "undefined" ? "/" : location.pathname;
335
+ var [path, setPath] = createSignal(read());
336
+ var listening = false;
337
+ function listen() {
338
+ if (listening || typeof window === "undefined") return;
339
+ listening = true;
340
+ window.addEventListener("popstate", () => setPath(read()));
341
+ }
342
+ function navigate(to, options = {}) {
343
+ if (new URL(to, location.href).href === location.href) return;
344
+ history[options.replace ? "replaceState" : "pushState"](null, "", to);
345
+ setPath(read());
346
+ }
347
+ var usePath = () => path;
348
+ var Params = createContext({});
349
+ function useParams() {
350
+ return useContext(Params);
351
+ }
352
+ var split = (s) => s.split("/").filter(Boolean);
353
+ function decode(s) {
354
+ try {
355
+ return decodeURIComponent(s);
356
+ } catch {
357
+ return s;
358
+ }
359
+ }
360
+ function match(pattern, pathname) {
361
+ if (pattern === "*") return {};
362
+ const p = split(pattern);
363
+ const u = split(pathname);
364
+ if (p.length !== u.length) return null;
365
+ const params = {};
366
+ for (let i = 0; i < p.length; i++) {
367
+ if (p[i].startsWith(":")) params[p[i].slice(1)] = decode(u[i]);
368
+ else if (p[i] !== u[i]) return null;
369
+ }
370
+ return params;
371
+ }
372
+ function Route(props) {
373
+ return { __route: true, path: props.path, component: props.component };
374
+ }
375
+ function Router(props) {
376
+ listen();
377
+ const routes = [props.children].flat(Infinity).filter(
378
+ (r) => r?.__route === true
379
+ );
380
+ return () => {
381
+ const current = path();
382
+ for (const route of routes) {
383
+ const params = match(route.path, current);
384
+ if (params) {
385
+ return untrack(() => provide(Params, params, () => route.component()));
386
+ }
387
+ }
388
+ return null;
389
+ };
390
+ }
391
+ function Link(props) {
392
+ const { href, children, ...rest } = props;
393
+ const onClick = (e) => {
394
+ if (/^[a-z][a-z0-9+.-]*:|^\/\//i.test(href)) return;
395
+ if (e.defaultPrevented || e.button !== 0) return;
396
+ if (e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) return;
397
+ e.preventDefault();
398
+ navigate(href);
399
+ };
400
+ return h("a", { ...rest, href, onClick }, children);
295
401
  }
296
402
  // Annotate the CommonJS export names for ESM import in node:
297
403
  0 && (module.exports = {
298
404
  For,
405
+ Fragment,
406
+ Link,
407
+ Route,
408
+ Router,
299
409
  Show,
300
410
  batch,
301
411
  createContext,
@@ -304,9 +414,16 @@ function createMemo(fn) {
304
414
  createRoot,
305
415
  createSignal,
306
416
  h,
417
+ jsx,
418
+ jsxDEV,
419
+ jsxs,
420
+ navigate,
307
421
  onCleanup,
308
422
  onMount,
309
423
  provide,
310
424
  render,
311
- useContext
425
+ untrack,
426
+ useContext,
427
+ useParams,
428
+ usePath
312
429
  });
package/dist/index.d.cts CHANGED
@@ -12,6 +12,7 @@ interface Context<T> {
12
12
  declare function createContext<T>(defaultValue: T): Context<T>;
13
13
  declare function provide<T, R>(ctx: Context<T>, value: T, fn: () => R): R;
14
14
  declare function useContext<T>(ctx: Context<T>): T;
15
+ declare function untrack<T>(fn: () => T): T;
15
16
 
16
17
  type Child = string | number | boolean | null | undefined | Node | (() => unknown) | Child[];
17
18
  declare function h(tag: string, props: Record<string, any> | null, ...children: Child[]): HTMLElement;
@@ -32,5 +33,44 @@ declare function For<T>(props: {
32
33
 
33
34
  declare function createMemo<T>(fn: () => T): () => T;
34
35
 
35
- export { For, Show, batch, createContext, createEffect, createMemo, createRoot, createSignal, h, onCleanup, onMount, provide, render, useContext };
36
- export type { Child, Context };
36
+ declare function jsx(type: any, props: Record<string, any> | null): any;
37
+ declare const jsxs: typeof jsx;
38
+ declare const jsxDEV: typeof jsx;
39
+ declare namespace JSX {
40
+ type Element = any;
41
+ interface IntrinsicElements {
42
+ [tag: string]: any;
43
+ }
44
+ interface ElementChildrenAttribute {
45
+ children: {};
46
+ }
47
+ }
48
+ declare function Fragment(props: {
49
+ children?: Child;
50
+ }): DocumentFragment;
51
+
52
+ declare function navigate(to: string, options?: {
53
+ replace?: boolean;
54
+ }): void;
55
+ declare const usePath: () => () => string;
56
+ declare function useParams(): Record<string, string>;
57
+ interface RouteDef {
58
+ __route: true;
59
+ path: string;
60
+ component: () => unknown;
61
+ }
62
+ declare function Route(props: {
63
+ path: string;
64
+ component: () => unknown;
65
+ }): RouteDef;
66
+ declare function Router(props: {
67
+ children?: unknown;
68
+ }): () => unknown;
69
+ declare function Link(props: {
70
+ href: string;
71
+ children?: unknown;
72
+ [key: string]: unknown;
73
+ }): HTMLElement;
74
+
75
+ export { For, Fragment, JSX, Link, Route, Router, Show, batch, createContext, createEffect, createMemo, createRoot, createSignal, h, jsx, jsxDEV, jsxs, navigate, onCleanup, onMount, provide, render, untrack, useContext, useParams, usePath };
76
+ export type { Child, Context, RouteDef };
package/dist/index.d.ts CHANGED
@@ -12,6 +12,7 @@ interface Context<T> {
12
12
  declare function createContext<T>(defaultValue: T): Context<T>;
13
13
  declare function provide<T, R>(ctx: Context<T>, value: T, fn: () => R): R;
14
14
  declare function useContext<T>(ctx: Context<T>): T;
15
+ declare function untrack<T>(fn: () => T): T;
15
16
 
16
17
  type Child = string | number | boolean | null | undefined | Node | (() => unknown) | Child[];
17
18
  declare function h(tag: string, props: Record<string, any> | null, ...children: Child[]): HTMLElement;
@@ -32,5 +33,44 @@ declare function For<T>(props: {
32
33
 
33
34
  declare function createMemo<T>(fn: () => T): () => T;
34
35
 
35
- export { For, Show, batch, createContext, createEffect, createMemo, createRoot, createSignal, h, onCleanup, onMount, provide, render, useContext };
36
- export type { Child, Context };
36
+ declare function jsx(type: any, props: Record<string, any> | null): any;
37
+ declare const jsxs: typeof jsx;
38
+ declare const jsxDEV: typeof jsx;
39
+ declare namespace JSX {
40
+ type Element = any;
41
+ interface IntrinsicElements {
42
+ [tag: string]: any;
43
+ }
44
+ interface ElementChildrenAttribute {
45
+ children: {};
46
+ }
47
+ }
48
+ declare function Fragment(props: {
49
+ children?: Child;
50
+ }): DocumentFragment;
51
+
52
+ declare function navigate(to: string, options?: {
53
+ replace?: boolean;
54
+ }): void;
55
+ declare const usePath: () => () => string;
56
+ declare function useParams(): Record<string, string>;
57
+ interface RouteDef {
58
+ __route: true;
59
+ path: string;
60
+ component: () => unknown;
61
+ }
62
+ declare function Route(props: {
63
+ path: string;
64
+ component: () => unknown;
65
+ }): RouteDef;
66
+ declare function Router(props: {
67
+ children?: unknown;
68
+ }): () => unknown;
69
+ declare function Link(props: {
70
+ href: string;
71
+ children?: unknown;
72
+ [key: string]: unknown;
73
+ }): HTMLElement;
74
+
75
+ export { For, Fragment, JSX, Link, Route, Router, Show, batch, createContext, createEffect, createMemo, createRoot, createSignal, h, jsx, jsxDEV, jsxs, navigate, onCleanup, onMount, provide, render, untrack, useContext, useParams, usePath };
76
+ export type { Child, Context, RouteDef };
package/dist/index.js CHANGED
@@ -130,6 +130,15 @@ function useContext(ctx) {
130
130
  }
131
131
  return ctx.defaultValue;
132
132
  }
133
+ function untrack(fn) {
134
+ const prev = currentEffect;
135
+ currentEffect = null;
136
+ try {
137
+ return fn();
138
+ } finally {
139
+ currentEffect = prev;
140
+ }
141
+ }
133
142
 
134
143
  // src/dom/h.ts
135
144
  function h(tag, props, ...children) {
@@ -212,9 +221,17 @@ function render(component, container) {
212
221
  });
213
222
  }
214
223
 
224
+ // src/reactivity/memo.ts
225
+ function createMemo(fn) {
226
+ const [value, setValue] = createSignal(void 0);
227
+ createEffect(() => setValue(() => fn()));
228
+ return value;
229
+ }
230
+
215
231
  // src/components/show.ts
216
232
  function Show(props) {
217
- return () => props.when() ? props.children() : props.fallback?.() ?? null;
233
+ const visible = createMemo(() => !!props.when());
234
+ return () => visible() ? untrack(() => props.children()) : untrack(() => props.fallback?.() ?? null);
218
235
  }
219
236
 
220
237
  // src/components/for.ts
@@ -248,14 +265,96 @@ function For(props) {
248
265
  };
249
266
  }
250
267
 
251
- // src/reactivity/memo.ts
252
- function createMemo(fn) {
253
- const [value, setValue] = createSignal(void 0);
254
- createEffect(() => setValue(() => fn()));
255
- return value;
268
+ // src/jsx-runtime.ts
269
+ function jsx(type, props) {
270
+ if (typeof type === "function") return type(props ?? {});
271
+ const { children, ...rest } = props ?? {};
272
+ const kids = children === void 0 ? [] : [children];
273
+ return h(type, rest, ...kids);
274
+ }
275
+ var jsxs = jsx;
276
+ var jsxDEV = jsx;
277
+ function Fragment(props) {
278
+ const frag = document.createDocumentFragment();
279
+ appendChildren(frag, props.children === void 0 ? [] : [props.children]);
280
+ return frag;
281
+ }
282
+
283
+ // src/router.ts
284
+ var read = () => typeof location === "undefined" ? "/" : location.pathname;
285
+ var [path, setPath] = createSignal(read());
286
+ var listening = false;
287
+ function listen() {
288
+ if (listening || typeof window === "undefined") return;
289
+ listening = true;
290
+ window.addEventListener("popstate", () => setPath(read()));
291
+ }
292
+ function navigate(to, options = {}) {
293
+ if (new URL(to, location.href).href === location.href) return;
294
+ history[options.replace ? "replaceState" : "pushState"](null, "", to);
295
+ setPath(read());
296
+ }
297
+ var usePath = () => path;
298
+ var Params = createContext({});
299
+ function useParams() {
300
+ return useContext(Params);
301
+ }
302
+ var split = (s) => s.split("/").filter(Boolean);
303
+ function decode(s) {
304
+ try {
305
+ return decodeURIComponent(s);
306
+ } catch {
307
+ return s;
308
+ }
309
+ }
310
+ function match(pattern, pathname) {
311
+ if (pattern === "*") return {};
312
+ const p = split(pattern);
313
+ const u = split(pathname);
314
+ if (p.length !== u.length) return null;
315
+ const params = {};
316
+ for (let i = 0; i < p.length; i++) {
317
+ if (p[i].startsWith(":")) params[p[i].slice(1)] = decode(u[i]);
318
+ else if (p[i] !== u[i]) return null;
319
+ }
320
+ return params;
321
+ }
322
+ function Route(props) {
323
+ return { __route: true, path: props.path, component: props.component };
324
+ }
325
+ function Router(props) {
326
+ listen();
327
+ const routes = [props.children].flat(Infinity).filter(
328
+ (r) => r?.__route === true
329
+ );
330
+ return () => {
331
+ const current = path();
332
+ for (const route of routes) {
333
+ const params = match(route.path, current);
334
+ if (params) {
335
+ return untrack(() => provide(Params, params, () => route.component()));
336
+ }
337
+ }
338
+ return null;
339
+ };
340
+ }
341
+ function Link(props) {
342
+ const { href, children, ...rest } = props;
343
+ const onClick = (e) => {
344
+ if (/^[a-z][a-z0-9+.-]*:|^\/\//i.test(href)) return;
345
+ if (e.defaultPrevented || e.button !== 0) return;
346
+ if (e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) return;
347
+ e.preventDefault();
348
+ navigate(href);
349
+ };
350
+ return h("a", { ...rest, href, onClick }, children);
256
351
  }
257
352
  export {
258
353
  For,
354
+ Fragment,
355
+ Link,
356
+ Route,
357
+ Router,
259
358
  Show,
260
359
  batch,
261
360
  createContext,
@@ -264,9 +363,16 @@ export {
264
363
  createRoot,
265
364
  createSignal,
266
365
  h,
366
+ jsx,
367
+ jsxDEV,
368
+ jsxs,
369
+ navigate,
267
370
  onCleanup,
268
371
  onMount,
269
372
  provide,
270
373
  render,
271
- useContext
374
+ untrack,
375
+ useContext,
376
+ useParams,
377
+ usePath
272
378
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cajoa",
3
- "version": "0.2.0",
3
+ "version": "0.4.0",
4
4
  "description": "A beginner-friendly, signals-based frontend framework.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
@@ -16,6 +16,26 @@
16
16
  "types": "./dist/index.d.cts",
17
17
  "default": "./dist/index.cjs"
18
18
  }
19
+ },
20
+ "./jsx-runtime": {
21
+ "import": {
22
+ "types": "./dist/index.d.ts",
23
+ "default": "./dist/index.js"
24
+ },
25
+ "require": {
26
+ "types": "./dist/index.d.cts",
27
+ "default": "./dist/index.cjs"
28
+ }
29
+ },
30
+ "./jsx-dev-runtime": {
31
+ "import": {
32
+ "types": "./dist/index.d.ts",
33
+ "default": "./dist/index.js"
34
+ },
35
+ "require": {
36
+ "types": "./dist/index.d.cts",
37
+ "default": "./dist/index.cjs"
38
+ }
19
39
  }
20
40
  },
21
41
  "files": [
@@ -31,7 +51,7 @@
31
51
  },
32
52
  "repository": {
33
53
  "type": "git",
34
- "url": "git+https://github.com/cjmgrc/Cajoa.git"
54
+ "url": "git+https://github.com/CajoaTS/Cajoa.git"
35
55
  },
36
56
  "keywords": [
37
57
  "frontend",
@@ -44,10 +64,11 @@
44
64
  "author": "cjmgrc",
45
65
  "license": "Apache-2.0",
46
66
  "bugs": {
47
- "url": "https://github.com/cjmgrc/Cajoa/issues"
67
+ "url": "https://github.com/CajoaTS/Cajoa/issues"
48
68
  },
49
- "homepage": "https://github.com/cjmgrc/Cajoa#readme",
69
+ "homepage": "https://github.com/CajoaTS/Cajoa#readme",
50
70
  "devDependencies": {
71
+ "@types/node": "^26.6.5",
51
72
  "jsdom": "^29.1.1",
52
73
  "tsup": "^9.0.0",
53
74
  "typescript": "^5.9.3",