rask-ui 0.11.0 → 0.12.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 +220 -3
- package/dist/batch.d.ts.map +1 -1
- package/dist/batch.js +36 -55
- package/dist/component.d.ts +2 -1
- package/dist/component.d.ts.map +1 -1
- package/dist/component.js +33 -22
- package/dist/createContext.d.ts +1 -0
- package/dist/createContext.d.ts.map +1 -1
- package/dist/createContext.js +11 -0
- package/dist/createEffect.d.ts.map +1 -1
- package/dist/createEffect.js +6 -4
- package/dist/createRouter.d.ts +8 -0
- package/dist/createRouter.d.ts.map +1 -0
- package/dist/createRouter.js +24 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/jsx-runtime.d.ts +2 -2
- package/dist/jsx-runtime.js +2 -2
- package/dist/plugin.d.ts +9 -1
- package/dist/plugin.d.ts.map +1 -1
- package/dist/plugin.js +4 -3
- package/dist/tests/batch.test.d.ts +2 -0
- package/dist/tests/batch.test.d.ts.map +1 -0
- package/dist/tests/batch.test.js +244 -0
- package/dist/tests/createComputed.test.d.ts +2 -0
- package/dist/tests/createComputed.test.d.ts.map +1 -0
- package/dist/tests/createComputed.test.js +257 -0
- package/dist/tests/createContext.test.d.ts +2 -0
- package/dist/tests/createContext.test.d.ts.map +1 -0
- package/dist/tests/createContext.test.js +136 -0
- package/dist/tests/createEffect.test.d.ts +2 -0
- package/dist/tests/createEffect.test.d.ts.map +1 -0
- package/dist/tests/createEffect.test.js +454 -0
- package/dist/tests/createState.test.d.ts +2 -0
- package/dist/tests/createState.test.d.ts.map +1 -0
- package/dist/tests/createState.test.js +144 -0
- package/dist/tests/createTask.test.d.ts +2 -0
- package/dist/tests/createTask.test.d.ts.map +1 -0
- package/dist/tests/createTask.test.js +322 -0
- package/dist/tests/createView.test.d.ts +2 -0
- package/dist/tests/createView.test.d.ts.map +1 -0
- package/dist/tests/createView.test.js +203 -0
- package/dist/tests/error.test.d.ts +2 -0
- package/dist/tests/error.test.d.ts.map +1 -0
- package/dist/tests/error.test.js +168 -0
- package/dist/tests/observation.test.d.ts +2 -0
- package/dist/tests/observation.test.d.ts.map +1 -0
- package/dist/tests/observation.test.js +341 -0
- package/package.json +4 -3
- package/swc-plugin/target/wasm32-wasip1/release/swc_plugin_rask_component.wasm +0 -0
package/README.md
CHANGED
|
@@ -522,9 +522,9 @@ function LiveData() {
|
|
|
522
522
|
|
|
523
523
|
**Features:**
|
|
524
524
|
|
|
525
|
-
- Runs immediately on creation
|
|
525
|
+
- Runs immediately and synchronously on creation during setup
|
|
526
526
|
- Automatically tracks reactive dependencies accessed during execution
|
|
527
|
-
- Re-runs
|
|
527
|
+
- Re-runs when dependencies change
|
|
528
528
|
- Automatically cleaned up when component unmounts
|
|
529
529
|
- Optional dispose function for cleaning up resources before re-execution
|
|
530
530
|
- Can be used for side effects like logging, syncing to localStorage, managing subscriptions, or updating derived state
|
|
@@ -532,7 +532,7 @@ function LiveData() {
|
|
|
532
532
|
**Notes:**
|
|
533
533
|
|
|
534
534
|
- Only call during component setup phase (not in render function)
|
|
535
|
-
-
|
|
535
|
+
- Effect runs synchronously during setup, making it predictable and easier to reason about
|
|
536
536
|
- Be careful with effects that modify state - can cause infinite loops if not careful
|
|
537
537
|
- Dispose functions run before the effect re-executes, not when the component unmounts
|
|
538
538
|
- For component unmount cleanup, use `createCleanup()` instead
|
|
@@ -988,6 +988,223 @@ Error:
|
|
|
988
988
|
|
|
989
989
|
---
|
|
990
990
|
|
|
991
|
+
### Routing
|
|
992
|
+
|
|
993
|
+
#### `createRouter<T>(config, options?)`
|
|
994
|
+
|
|
995
|
+
Creates a reactive router for client-side navigation. Built on [typed-client-router](https://github.com/christianalfoni/typed-client-router), it integrates seamlessly with RASK's reactive system for fully type-safe routing.
|
|
996
|
+
|
|
997
|
+
```tsx
|
|
998
|
+
import { createRouter } from "rask-ui";
|
|
999
|
+
|
|
1000
|
+
const routes = {
|
|
1001
|
+
home: "/",
|
|
1002
|
+
about: "/about",
|
|
1003
|
+
user: "/users/:id",
|
|
1004
|
+
post: "/posts/:id",
|
|
1005
|
+
} as const;
|
|
1006
|
+
|
|
1007
|
+
function App() {
|
|
1008
|
+
const router = createRouter(routes);
|
|
1009
|
+
|
|
1010
|
+
return () => {
|
|
1011
|
+
// Match current route
|
|
1012
|
+
if (router.route?.name === "home") {
|
|
1013
|
+
return <Home />;
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1016
|
+
if (router.route?.name === "user") {
|
|
1017
|
+
return <User id={router.route.params.id} />;
|
|
1018
|
+
}
|
|
1019
|
+
|
|
1020
|
+
if (router.route?.name === "post") {
|
|
1021
|
+
return <Post id={router.route.params.id} />;
|
|
1022
|
+
}
|
|
1023
|
+
|
|
1024
|
+
return <NotFound />;
|
|
1025
|
+
};
|
|
1026
|
+
}
|
|
1027
|
+
```
|
|
1028
|
+
|
|
1029
|
+
**Parameters:**
|
|
1030
|
+
|
|
1031
|
+
- `config: T` - Route configuration object mapping route names to path patterns
|
|
1032
|
+
- `options?: { base?: string }` - Optional base path for all routes
|
|
1033
|
+
|
|
1034
|
+
**Returns:** Router object with reactive state and navigation methods:
|
|
1035
|
+
|
|
1036
|
+
**Properties:**
|
|
1037
|
+
- `route?: Route` - Current active route with `name` and `params` properties (reactive)
|
|
1038
|
+
- `queries: Record<string, string>` - Current URL query parameters (reactive)
|
|
1039
|
+
|
|
1040
|
+
**Methods:**
|
|
1041
|
+
- `push(name, params?, query?)` - Navigate to a route by name
|
|
1042
|
+
- `replace(name, params?, query?)` - Replace current route (no history entry)
|
|
1043
|
+
- `setQuery(query)` - Update query parameters
|
|
1044
|
+
- `url(name, params?, query?)` - Generate URL for a route
|
|
1045
|
+
|
|
1046
|
+
**Route Configuration:**
|
|
1047
|
+
|
|
1048
|
+
Define routes with path patterns. Use `:param` for dynamic segments:
|
|
1049
|
+
|
|
1050
|
+
```tsx
|
|
1051
|
+
const routes = {
|
|
1052
|
+
home: "/",
|
|
1053
|
+
users: "/users",
|
|
1054
|
+
user: "/users/:id",
|
|
1055
|
+
userPosts: "/users/:userId/posts/:postId",
|
|
1056
|
+
} as const;
|
|
1057
|
+
```
|
|
1058
|
+
|
|
1059
|
+
**Navigation:**
|
|
1060
|
+
|
|
1061
|
+
Navigate programmatically using route names:
|
|
1062
|
+
|
|
1063
|
+
```tsx
|
|
1064
|
+
function Navigation() {
|
|
1065
|
+
const router = createRouter(routes);
|
|
1066
|
+
|
|
1067
|
+
return () => (
|
|
1068
|
+
<nav>
|
|
1069
|
+
<button onClick={() => router.push("home")}>Home</button>
|
|
1070
|
+
<button onClick={() => router.push("user", { id: "123" })}>
|
|
1071
|
+
User 123
|
|
1072
|
+
</button>
|
|
1073
|
+
<button onClick={() => router.push("home", {}, { tab: "recent" })}>
|
|
1074
|
+
Home (Recent)
|
|
1075
|
+
</button>
|
|
1076
|
+
</nav>
|
|
1077
|
+
);
|
|
1078
|
+
}
|
|
1079
|
+
```
|
|
1080
|
+
|
|
1081
|
+
**Query Parameters:**
|
|
1082
|
+
|
|
1083
|
+
Access and modify query parameters:
|
|
1084
|
+
|
|
1085
|
+
```tsx
|
|
1086
|
+
function SearchPage() {
|
|
1087
|
+
const router = createRouter(routes);
|
|
1088
|
+
|
|
1089
|
+
return () => (
|
|
1090
|
+
<div>
|
|
1091
|
+
<p>Search: {router.queries.q || "none"}</p>
|
|
1092
|
+
<input
|
|
1093
|
+
value={router.queries.q || ""}
|
|
1094
|
+
onInput={(e) => router.setQuery({ q: e.target.value })}
|
|
1095
|
+
/>
|
|
1096
|
+
</div>
|
|
1097
|
+
);
|
|
1098
|
+
}
|
|
1099
|
+
```
|
|
1100
|
+
|
|
1101
|
+
**Reactive Routing:**
|
|
1102
|
+
|
|
1103
|
+
The router integrates with RASK's reactivity system. Accessing `router.route` or `router.queries` automatically tracks dependencies:
|
|
1104
|
+
|
|
1105
|
+
```tsx
|
|
1106
|
+
function App() {
|
|
1107
|
+
const router = createRouter(routes);
|
|
1108
|
+
const state = createState({ posts: [] });
|
|
1109
|
+
|
|
1110
|
+
// Effect runs when route changes
|
|
1111
|
+
createEffect(() => {
|
|
1112
|
+
if (router.route?.name === "user") {
|
|
1113
|
+
// Fetch user data when route changes
|
|
1114
|
+
fetch(`/api/users/${router.route.params.id}`)
|
|
1115
|
+
.then((r) => r.json())
|
|
1116
|
+
.then((data) => (state.posts = data.posts));
|
|
1117
|
+
}
|
|
1118
|
+
});
|
|
1119
|
+
|
|
1120
|
+
return () => (
|
|
1121
|
+
<div>
|
|
1122
|
+
{router.route?.name === "user" && (
|
|
1123
|
+
<div>
|
|
1124
|
+
<h1>User {router.route.params.id}</h1>
|
|
1125
|
+
<ul>
|
|
1126
|
+
{state.posts.map((post) => (
|
|
1127
|
+
<li key={post.id}>{post.title}</li>
|
|
1128
|
+
))}
|
|
1129
|
+
</ul>
|
|
1130
|
+
</div>
|
|
1131
|
+
)}
|
|
1132
|
+
</div>
|
|
1133
|
+
);
|
|
1134
|
+
}
|
|
1135
|
+
```
|
|
1136
|
+
|
|
1137
|
+
**Type Safety:**
|
|
1138
|
+
|
|
1139
|
+
Routes are fully type-safe. TypeScript will infer parameter types from route patterns:
|
|
1140
|
+
|
|
1141
|
+
```tsx
|
|
1142
|
+
const routes = {
|
|
1143
|
+
user: "/users/:id",
|
|
1144
|
+
post: "/posts/:postId/:commentId",
|
|
1145
|
+
} as const;
|
|
1146
|
+
|
|
1147
|
+
const router = createRouter(routes);
|
|
1148
|
+
|
|
1149
|
+
// ✅ Type-safe - id is required
|
|
1150
|
+
router.push("user", { id: "123" });
|
|
1151
|
+
|
|
1152
|
+
// ❌ Type error - missing required params
|
|
1153
|
+
router.push("post", { postId: "1" }); // Error: missing commentId
|
|
1154
|
+
|
|
1155
|
+
// ✅ Type-safe params access
|
|
1156
|
+
if (router.route?.name === "post") {
|
|
1157
|
+
const postId = router.route.params.postId; // string
|
|
1158
|
+
const commentId = router.route.params.commentId; // string
|
|
1159
|
+
}
|
|
1160
|
+
```
|
|
1161
|
+
|
|
1162
|
+
**Context Pattern:**
|
|
1163
|
+
|
|
1164
|
+
Share router across components using context:
|
|
1165
|
+
|
|
1166
|
+
```tsx
|
|
1167
|
+
import { createRouter, createContext } from "rask-ui";
|
|
1168
|
+
|
|
1169
|
+
const routes = {
|
|
1170
|
+
home: "/",
|
|
1171
|
+
about: "/about",
|
|
1172
|
+
} as const;
|
|
1173
|
+
|
|
1174
|
+
const RouterContext = createContext<Router<typeof routes>>();
|
|
1175
|
+
|
|
1176
|
+
function App() {
|
|
1177
|
+
const router = createRouter(routes);
|
|
1178
|
+
|
|
1179
|
+
RouterContext.inject(router);
|
|
1180
|
+
|
|
1181
|
+
return () => <Content />;
|
|
1182
|
+
}
|
|
1183
|
+
|
|
1184
|
+
function Content() {
|
|
1185
|
+
const router = RouterContext.get();
|
|
1186
|
+
|
|
1187
|
+
return () => (
|
|
1188
|
+
<nav>
|
|
1189
|
+
<button onClick={() => router.push("home")}>Home</button>
|
|
1190
|
+
<button onClick={() => router.push("about")}>About</button>
|
|
1191
|
+
</nav>
|
|
1192
|
+
);
|
|
1193
|
+
}
|
|
1194
|
+
```
|
|
1195
|
+
|
|
1196
|
+
**Features:**
|
|
1197
|
+
|
|
1198
|
+
- **Type-safe** - Full TypeScript inference for routes and parameters
|
|
1199
|
+
- **Reactive** - Automatically tracks route changes
|
|
1200
|
+
- **Declarative** - Navigate using route names, not URLs
|
|
1201
|
+
- **Query parameters** - Built-in query string management
|
|
1202
|
+
- **No special components** - Use standard conditionals and component composition
|
|
1203
|
+
- **History API** - Built on the browser's History API
|
|
1204
|
+
- **Automatic cleanup** - Router listener cleaned up when component unmounts
|
|
1205
|
+
|
|
1206
|
+
---
|
|
1207
|
+
|
|
991
1208
|
### Developer Tools
|
|
992
1209
|
|
|
993
1210
|
#### `inspect(root, callback)`
|
package/dist/batch.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"batch.d.ts","sourceRoot":"","sources":["../src/batch.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,cAAc,GAAG,CAAC,MAAM,IAAI,CAAC,GAAG;IAAE,QAAQ,EAAE,OAAO,CAAA;CAAE,CAAC;
|
|
1
|
+
{"version":3,"file":"batch.d.ts","sourceRoot":"","sources":["../src/batch.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,cAAc,GAAG,CAAC,MAAM,IAAI,CAAC,GAAG;IAAE,QAAQ,EAAE,OAAO,CAAA;CAAE,CAAC;AA8ClE,wBAAgB,KAAK,CAAC,EAAE,EAAE,cAAc,QAiBvC;AAED,wBAAgB,SAAS,CAAC,EAAE,EAAE,MAAM,IAAI,QAgBvC"}
|
package/dist/batch.js
CHANGED
|
@@ -1,11 +1,7 @@
|
|
|
1
1
|
const asyncQueue = [];
|
|
2
|
-
const
|
|
2
|
+
const syncQueueStack = [];
|
|
3
3
|
let inInteractive = 0;
|
|
4
4
|
let asyncScheduled = false;
|
|
5
|
-
let inSyncBatch = 0;
|
|
6
|
-
// New: guards against re-entrant flushing
|
|
7
|
-
let inAsyncFlush = false;
|
|
8
|
-
let inSyncFlush = false;
|
|
9
5
|
function scheduleAsyncFlush() {
|
|
10
6
|
if (asyncScheduled)
|
|
11
7
|
return;
|
|
@@ -13,77 +9,62 @@ function scheduleAsyncFlush() {
|
|
|
13
9
|
queueMicrotask(flushAsyncQueue);
|
|
14
10
|
}
|
|
15
11
|
function flushAsyncQueue() {
|
|
16
|
-
if (inAsyncFlush)
|
|
17
|
-
return;
|
|
18
|
-
inAsyncFlush = true;
|
|
19
12
|
asyncScheduled = false;
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
cb.__queued = false;
|
|
31
|
-
}
|
|
32
|
-
asyncQueue.length = 0;
|
|
33
|
-
}
|
|
34
|
-
finally {
|
|
35
|
-
inAsyncFlush = false;
|
|
13
|
+
if (!asyncQueue.length)
|
|
14
|
+
return;
|
|
15
|
+
// Note: we intentionally DO NOT snapshot.
|
|
16
|
+
// If callbacks queue more async work, it gets picked up
|
|
17
|
+
// in this same loop because length grows.
|
|
18
|
+
for (let i = 0; i < asyncQueue.length; i++) {
|
|
19
|
+
const cb = asyncQueue[i];
|
|
20
|
+
asyncQueue[i] = undefined;
|
|
21
|
+
cb();
|
|
22
|
+
cb.__queued = false;
|
|
36
23
|
}
|
|
24
|
+
asyncQueue.length = 0;
|
|
37
25
|
}
|
|
38
|
-
function flushSyncQueue() {
|
|
39
|
-
if (
|
|
26
|
+
function flushSyncQueue(queue) {
|
|
27
|
+
if (!queue.length)
|
|
40
28
|
return;
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
const cb = syncQueue[i];
|
|
50
|
-
syncQueue[i] = undefined;
|
|
51
|
-
cb();
|
|
52
|
-
cb.__queued = false;
|
|
53
|
-
}
|
|
54
|
-
syncQueue.length = 0;
|
|
55
|
-
}
|
|
56
|
-
finally {
|
|
57
|
-
inSyncFlush = false;
|
|
29
|
+
// No snapshot, just iterate.
|
|
30
|
+
// New callbacks queued via nested syncBatch will create
|
|
31
|
+
// their own queue on the stack and flush independently.
|
|
32
|
+
for (let i = 0; i < queue.length; i++) {
|
|
33
|
+
const cb = queue[i];
|
|
34
|
+
queue[i] = undefined;
|
|
35
|
+
cb();
|
|
36
|
+
cb.__queued = false;
|
|
58
37
|
}
|
|
38
|
+
queue.length = 0;
|
|
59
39
|
}
|
|
60
40
|
export function queue(cb) {
|
|
61
41
|
// Optional: uncomment this if you want deduping:
|
|
62
42
|
// if (cb.__queued) return;
|
|
63
43
|
cb.__queued = true;
|
|
64
|
-
|
|
65
|
-
|
|
44
|
+
// If we're in a sync batch, push to the current sync queue
|
|
45
|
+
if (syncQueueStack.length) {
|
|
46
|
+
syncQueueStack[syncQueueStack.length - 1].push(cb);
|
|
66
47
|
return;
|
|
67
48
|
}
|
|
49
|
+
// Otherwise, push to async queue
|
|
68
50
|
asyncQueue.push(cb);
|
|
69
51
|
if (!inInteractive) {
|
|
70
52
|
scheduleAsyncFlush();
|
|
71
53
|
}
|
|
72
54
|
}
|
|
73
55
|
export function syncBatch(cb) {
|
|
74
|
-
|
|
56
|
+
// Create a new queue for this sync batch
|
|
57
|
+
const queue = [];
|
|
58
|
+
syncQueueStack.push(queue);
|
|
75
59
|
try {
|
|
76
60
|
cb();
|
|
77
61
|
}
|
|
78
62
|
catch (e) {
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
inSyncBatch--;
|
|
83
|
-
if (!inSyncBatch) {
|
|
84
|
-
// Only the outermost syncBatch triggers a flush.
|
|
85
|
-
// If this happens *inside* an ongoing flushSyncQueue,
|
|
86
|
-
// inSyncFlush will be true and flushSyncQueue will no-op.
|
|
87
|
-
flushSyncQueue();
|
|
63
|
+
// Pop the queue even on error, but don't flush
|
|
64
|
+
syncQueueStack.pop();
|
|
65
|
+
throw e;
|
|
88
66
|
}
|
|
67
|
+
// Pop the queue and flush it
|
|
68
|
+
syncQueueStack.pop();
|
|
69
|
+
flushSyncQueue(queue);
|
|
89
70
|
}
|
package/dist/component.d.ts
CHANGED
|
@@ -17,6 +17,8 @@ export declare class RaskStatefulComponent<P extends Props<any>> extends Compone
|
|
|
17
17
|
private reactiveProps?;
|
|
18
18
|
private observer;
|
|
19
19
|
private isRendering;
|
|
20
|
+
private willRender;
|
|
21
|
+
private nextProps;
|
|
20
22
|
effects: Array<{
|
|
21
23
|
isDirty: boolean;
|
|
22
24
|
run: () => void;
|
|
@@ -32,7 +34,6 @@ export declare class RaskStatefulComponent<P extends Props<any>> extends Compone
|
|
|
32
34
|
*
|
|
33
35
|
*/
|
|
34
36
|
componentWillUpdate(nextProps: any): void;
|
|
35
|
-
componentWillReceiveProps(): void;
|
|
36
37
|
shouldComponentUpdate(nextProps: Props<any>): boolean;
|
|
37
38
|
render(): any;
|
|
38
39
|
}
|
package/dist/component.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"component.d.ts","sourceRoot":"","sources":["../src/component.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"component.d.ts","sourceRoot":"","sources":["../src/component.ts"],"names":[],"mappings":"AAAA,OAAO,EAAwB,KAAK,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAExE,OAAO,EAAsB,QAAQ,EAAU,MAAM,eAAe,CAAC;AAIrE,MAAM,MAAM,8BAA8B,CAAC,CAAC,SAAS,KAAK,CAAC,GAAG,CAAC,IAC3D,CAAC,MAAM,KAAK,CAAC,GACb,CAAC,CAAC,KAAK,EAAE,CAAC,KAAK,KAAK,CAAC,CAAC;AAE1B,qBAAa,sBAAuB,SAAQ,SAAS;IAC3C,QAAQ,EAAE,8BAA8B,CAAC,GAAG,CAAC,CAAC;IACtD,QAAQ,WAEL;IACH,qBAAqB,CAAC,SAAS,EAAE,KAAK,CAAC,GAAG,CAAC,GAAG,OAAO;IAUrD,MAAM;CAMP;AAID,wBAAgB,mBAAmB,+BAMlC;AAED,wBAAgB,iBAAiB,CAAC,EAAE,EAAE,MAAM,IAAI,QAM/C;AAED,wBAAgB,aAAa,CAAC,EAAE,EAAE,MAAM,IAAI,QAM3C;AAED,MAAM,MAAM,6BAA6B,CAAC,CAAC,SAAS,KAAK,CAAC,GAAG,CAAC,IAC1D,CAAC,MAAM,MAAM,KAAK,CAAC,GACnB,CAAC,CAAC,KAAK,EAAE,CAAC,KAAK,MAAM,KAAK,CAAC,CAAC;AAEhC,qBAAa,qBAAqB,CAAC,CAAC,SAAS,KAAK,CAAC,GAAG,CAAC,CAAE,SAAQ,SAAS,CAAC,CAAC,CAAC;IACnE,KAAK,EAAE,6BAA6B,CAAC,CAAC,CAAC,CAAC;IAChD,OAAO,CAAC,QAAQ,CAAC,CAAc;IAC/B,OAAO,CAAC,aAAa,CAAC,CAAa;IACnC,OAAO,CAAC,QAAQ,CAKb;IAEH,OAAO,CAAC,WAAW,CAAS;IAE5B,OAAO,CAAC,UAAU,CAAQ;IAG1B,OAAO,CAAC,SAAS,CAAmB;IACpC,OAAO,EAAE,KAAK,CAAC;QAAE,OAAO,EAAE,OAAO,CAAC;QAAC,GAAG,EAAE,MAAM,IAAI,CAAA;KAAE,CAAC,CAAM;IAC3D,QAAQ,gBAAa;IACrB,eAAe;IAUf,QAAQ,EAAE,KAAK,CAAC,MAAM,IAAI,CAAC,CAAM;IACjC,UAAU,EAAE,KAAK,CAAC,MAAM,IAAI,CAAC,CAAM;IACnC,OAAO,CAAC,mBAAmB;IAyD3B,iBAAiB,IAAI,IAAI;IAGzB,oBAAoB,IAAI,IAAI;IAG5B;;OAEG;IACH,mBAAmB,CAAC,SAAS,EAAE,GAAG;IAkBlC,qBAAqB,CAAC,SAAS,EAAE,KAAK,CAAC,GAAG,CAAC,GAAG,OAAO;IAWrD,MAAM;CA0CP;AAED,wBAAgB,eAAe,CAAC,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,EAAE,GAAG,CAAC,EAAE,MAAM,SAO9D"}
|
package/dist/component.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { createComponentVNode, Component
|
|
1
|
+
import { createComponentVNode, Component } from "inferno";
|
|
2
2
|
import { getCurrentObserver, Observer, Signal } from "./observation";
|
|
3
3
|
import { syncBatch } from "./batch";
|
|
4
|
+
import { PROXY_MARKER } from "./createState";
|
|
4
5
|
export class RaskStatelessComponent extends Component {
|
|
5
6
|
observer = new Observer(() => {
|
|
6
7
|
this.forceUpdate();
|
|
@@ -44,9 +45,18 @@ export class RaskStatefulComponent extends Component {
|
|
|
44
45
|
renderFn;
|
|
45
46
|
reactiveProps;
|
|
46
47
|
observer = new Observer(() => {
|
|
48
|
+
if (this.willRender) {
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
47
51
|
this.forceUpdate();
|
|
48
52
|
});
|
|
53
|
+
// Flag to prevent props from tracking in render scope (We use props reconciliation)
|
|
49
54
|
isRendering = false;
|
|
55
|
+
// Flag to prevent observer notifications to cause render during reconciliation
|
|
56
|
+
willRender = true;
|
|
57
|
+
// Since reactive props updates before the reconciliation (without causing a new one), we
|
|
58
|
+
// need to return these from the reactive props
|
|
59
|
+
nextProps = this.props;
|
|
50
60
|
effects = [];
|
|
51
61
|
contexts = new Map();
|
|
52
62
|
getChildContext() {
|
|
@@ -64,44 +74,42 @@ export class RaskStatefulComponent extends Component {
|
|
|
64
74
|
const reactiveProps = {};
|
|
65
75
|
const self = this;
|
|
66
76
|
const signals = new Map();
|
|
67
|
-
for (const prop in this.
|
|
68
|
-
const value = this.
|
|
77
|
+
for (const prop in this.nextProps) {
|
|
78
|
+
const value = this.nextProps[prop];
|
|
69
79
|
// Skip known non-reactive props
|
|
70
|
-
if (
|
|
71
|
-
prop === "children" ||
|
|
72
|
-
prop === "key" ||
|
|
73
|
-
prop === "ref") {
|
|
80
|
+
if (prop === "key" || prop === "ref") {
|
|
74
81
|
reactiveProps[prop] = value;
|
|
75
82
|
continue;
|
|
76
83
|
}
|
|
77
84
|
// Skip objects/arrays - they're already reactive if they're proxies
|
|
78
85
|
// No need to wrap them in additional signals
|
|
79
|
-
if (typeof value === "object" &&
|
|
86
|
+
if (typeof value === "object" &&
|
|
87
|
+
value !== null &&
|
|
88
|
+
PROXY_MARKER in value) {
|
|
80
89
|
reactiveProps[prop] = value;
|
|
81
90
|
continue;
|
|
82
91
|
}
|
|
83
92
|
// Only create reactive getters for primitives
|
|
84
93
|
Object.defineProperty(reactiveProps, prop, {
|
|
94
|
+
enumerable: true,
|
|
85
95
|
get() {
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
signals.set(prop, signal);
|
|
94
|
-
}
|
|
95
|
-
observer.subscribeSignal(signal);
|
|
96
|
+
const observer = getCurrentObserver();
|
|
97
|
+
if (!self.isRendering && observer) {
|
|
98
|
+
// Lazy create signal only when accessed in reactive context
|
|
99
|
+
let signal = signals.get(prop);
|
|
100
|
+
if (!signal) {
|
|
101
|
+
signal = new Signal();
|
|
102
|
+
signals.set(prop, signal);
|
|
96
103
|
}
|
|
104
|
+
observer.subscribeSignal(signal);
|
|
97
105
|
}
|
|
98
106
|
// @ts-ignore
|
|
99
|
-
return self.
|
|
107
|
+
return self.nextProps[prop];
|
|
100
108
|
},
|
|
101
109
|
set(value) {
|
|
102
110
|
// Only notify if signal was created (i.e., prop was accessed reactively)
|
|
103
111
|
const signal = signals.get(prop);
|
|
104
|
-
if (signal
|
|
112
|
+
if (signal) {
|
|
105
113
|
signal.notify();
|
|
106
114
|
}
|
|
107
115
|
},
|
|
@@ -119,9 +127,12 @@ export class RaskStatefulComponent extends Component {
|
|
|
119
127
|
*
|
|
120
128
|
*/
|
|
121
129
|
componentWillUpdate(nextProps) {
|
|
130
|
+
this.willRender = true;
|
|
131
|
+
this.nextProps = nextProps;
|
|
122
132
|
syncBatch(() => {
|
|
123
133
|
for (const prop in nextProps) {
|
|
124
|
-
if (prop === "children"
|
|
134
|
+
if (prop === "children" ||
|
|
135
|
+
this.props[prop] === nextProps[prop]) {
|
|
125
136
|
continue;
|
|
126
137
|
}
|
|
127
138
|
// @ts-ignore
|
|
@@ -129,7 +140,6 @@ export class RaskStatefulComponent extends Component {
|
|
|
129
140
|
}
|
|
130
141
|
});
|
|
131
142
|
}
|
|
132
|
-
componentWillReceiveProps() { }
|
|
133
143
|
shouldComponentUpdate(nextProps) {
|
|
134
144
|
// Shallow comparison of props, excluding internal props
|
|
135
145
|
for (const prop in nextProps) {
|
|
@@ -165,6 +175,7 @@ export class RaskStatefulComponent extends Component {
|
|
|
165
175
|
this.isRendering = true;
|
|
166
176
|
result = this.renderFn();
|
|
167
177
|
this.isRendering = false;
|
|
178
|
+
this.willRender = false;
|
|
168
179
|
}
|
|
169
180
|
catch (error) {
|
|
170
181
|
if (typeof this.context.notifyError !== "function") {
|
package/dist/createContext.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"createContext.d.ts","sourceRoot":"","sources":["../src/createContext.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAIH,wBAAgB,aAAa,CAAC,CAAC;kBAEb,CAAC;WASR,CAAC
|
|
1
|
+
{"version":3,"file":"createContext.d.ts","sourceRoot":"","sources":["../src/createContext.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAIH,wBAAgB,aAAa,CAAC,CAAC;kBAEb,CAAC;WASR,CAAC;;EA2CX"}
|
package/dist/createContext.js
CHANGED
|
@@ -46,6 +46,17 @@ export function createContext() {
|
|
|
46
46
|
}
|
|
47
47
|
return contextValue;
|
|
48
48
|
},
|
|
49
|
+
hasValue() {
|
|
50
|
+
let currentComponent = getCurrentComponent();
|
|
51
|
+
if (!currentComponent) {
|
|
52
|
+
throw new Error("You can not get context outside component setup");
|
|
53
|
+
}
|
|
54
|
+
if (typeof currentComponent.context.getContext !== "function") {
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
const contextValue = currentComponent.context.getContext(context);
|
|
58
|
+
return Boolean(contextValue);
|
|
59
|
+
},
|
|
49
60
|
};
|
|
50
61
|
return context;
|
|
51
62
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"createEffect.d.ts","sourceRoot":"","sources":["../src/createEffect.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"createEffect.d.ts","sourceRoot":"","sources":["../src/createEffect.ts"],"names":[],"mappings":"AAIA,wBAAgB,YAAY,CAAC,EAAE,EAAE,MAAM,IAAI,GAAG,CAAC,MAAM,IAAI,CAAC,QA8BzD"}
|
package/dist/createEffect.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { syncBatch } from "./batch";
|
|
1
2
|
import { getCurrentComponent, createCleanup } from "./component";
|
|
2
3
|
import { Observer } from "./observation";
|
|
3
4
|
export function createEffect(cb) {
|
|
@@ -10,9 +11,7 @@ export function createEffect(cb) {
|
|
|
10
11
|
}
|
|
11
12
|
let disposer;
|
|
12
13
|
const observer = new Observer(() => {
|
|
13
|
-
|
|
14
|
-
// (Like when components sets props) should not synchronously trigger effects
|
|
15
|
-
queueMicrotask(runEffect);
|
|
14
|
+
syncBatch(runEffect);
|
|
16
15
|
});
|
|
17
16
|
const runEffect = () => {
|
|
18
17
|
try {
|
|
@@ -26,7 +25,10 @@ export function createEffect(cb) {
|
|
|
26
25
|
stopObserving();
|
|
27
26
|
};
|
|
28
27
|
if (currentComponent) {
|
|
29
|
-
createCleanup(() =>
|
|
28
|
+
createCleanup(() => {
|
|
29
|
+
observer.dispose();
|
|
30
|
+
disposer?.();
|
|
31
|
+
});
|
|
30
32
|
}
|
|
31
33
|
runEffect();
|
|
32
34
|
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { RoutesConfig, TRouter, TRoutes } from "typed-client-router";
|
|
2
|
+
export type Router<T extends RoutesConfig> = Omit<TRouter<T>, "current" | "listen" | "pathname"> & {
|
|
3
|
+
route?: TRoutes<T>;
|
|
4
|
+
};
|
|
5
|
+
export declare function createRouter<const T extends RoutesConfig>(config: T, options?: {
|
|
6
|
+
base?: string;
|
|
7
|
+
}): Router<T>;
|
|
8
|
+
//# sourceMappingURL=createRouter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"createRouter.d.ts","sourceRoot":"","sources":["../src/createRouter.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,YAAY,EACZ,OAAO,EACP,OAAO,EACR,MAAM,qBAAqB,CAAC;AAI7B,MAAM,MAAM,MAAM,CAAC,CAAC,SAAS,YAAY,IAAI,IAAI,CAC/C,OAAO,CAAC,CAAC,CAAC,EACV,SAAS,GAAG,QAAQ,GAAG,UAAU,CAClC,GAAG;IACF,KAAK,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC;CACpB,CAAC;AAEF,wBAAgB,YAAY,CAAC,KAAK,CAAC,CAAC,SAAS,YAAY,EACvD,MAAM,EAAE,CAAC,EACT,OAAO,CAAC,EAAE;IACR,IAAI,CAAC,EAAE,MAAM,CAAC;CACf,GACA,MAAM,CAAC,CAAC,CAAC,CAwBX"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { createRouter as internalCreateRouter, } from "typed-client-router";
|
|
2
|
+
import { getCurrentObserver, Signal } from "./observation";
|
|
3
|
+
import { createCleanup } from "./component";
|
|
4
|
+
export function createRouter(config, options) {
|
|
5
|
+
const router = internalCreateRouter(config, options);
|
|
6
|
+
const signal = new Signal();
|
|
7
|
+
createCleanup(router.listen(() => signal.notify()));
|
|
8
|
+
return {
|
|
9
|
+
get route() {
|
|
10
|
+
const observer = getCurrentObserver();
|
|
11
|
+
if (observer) {
|
|
12
|
+
observer.subscribeSignal(signal);
|
|
13
|
+
}
|
|
14
|
+
return router.current;
|
|
15
|
+
},
|
|
16
|
+
get queries() {
|
|
17
|
+
return router.queries;
|
|
18
|
+
},
|
|
19
|
+
setQuery: router.setQuery,
|
|
20
|
+
push: router.push,
|
|
21
|
+
replace: router.replace,
|
|
22
|
+
url: router.url,
|
|
23
|
+
};
|
|
24
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -10,5 +10,6 @@ export { createEffect } from "./createEffect";
|
|
|
10
10
|
export { createComputed } from "./createComputed";
|
|
11
11
|
export { syncBatch } from "./batch";
|
|
12
12
|
export { inspect } from "./inspect";
|
|
13
|
+
export { Router, createRouter } from "./createRouter";
|
|
13
14
|
export { createVNode, createComponentVNode, createFragment, createTextVNode, normalizeProps, Component, } from "inferno";
|
|
14
15
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAClC,OAAO,EACL,aAAa,EACb,iBAAiB,EACjB,qBAAqB,EACrB,sBAAsB,GACvB,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAEhD,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AACzD,OAAO,EAAE,UAAU,EAAE,SAAS,IAAI,IAAI,EAAE,MAAM,cAAc,CAAC;AAC7D,OAAO,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AACxC,OAAO,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AACpC,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAClD,OAAO,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AACpC,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAClC,OAAO,EACL,aAAa,EACb,iBAAiB,EACjB,qBAAqB,EACrB,sBAAsB,GACvB,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAEhD,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AACzD,OAAO,EAAE,UAAU,EAAE,SAAS,IAAI,IAAI,EAAE,MAAM,cAAc,CAAC;AAC7D,OAAO,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AACxC,OAAO,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AACpC,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAClD,OAAO,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AACpC,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAGtD,OAAO,EACL,WAAW,EACX,oBAAoB,EACpB,cAAc,EACd,eAAe,EACf,cAAc,EACd,SAAS,GACV,MAAM,SAAS,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -10,5 +10,6 @@ export { createEffect } from "./createEffect";
|
|
|
10
10
|
export { createComputed } from "./createComputed";
|
|
11
11
|
export { syncBatch } from "./batch";
|
|
12
12
|
export { inspect } from "./inspect";
|
|
13
|
+
export { createRouter } from "./createRouter";
|
|
13
14
|
// Re-export Inferno JSX runtime functions so users don't need to install Inferno directly
|
|
14
15
|
export { createVNode, createComponentVNode, createFragment, createTextVNode, normalizeProps, Component, } from "inferno";
|
package/dist/jsx-runtime.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import
|
|
2
|
-
export { Fragment } from
|
|
1
|
+
import "inferno";
|
|
2
|
+
export { Fragment } from "inferno";
|
|
3
3
|
export declare function jsx(type: any, props: any, key?: any): any;
|
|
4
4
|
export declare function jsxs(type: any, props: any, key?: any): any;
|
|
5
5
|
//# sourceMappingURL=jsx-runtime.d.ts.map
|
package/dist/jsx-runtime.js
CHANGED
|
@@ -2,6 +2,6 @@
|
|
|
2
2
|
// The actual JSX transformation is done by SWC plugin at build time
|
|
3
3
|
// This file only provides type declarations for TypeScript's "jsx": "react-jsx" mode
|
|
4
4
|
// Import inferno to get the global JSX namespace
|
|
5
|
-
import
|
|
5
|
+
import "inferno";
|
|
6
6
|
// Re-export Fragment for JSX fragment syntax
|
|
7
|
-
export { Fragment } from
|
|
7
|
+
export { Fragment } from "inferno";
|