jac-client 0.1.0__py3-none-any.whl → 0.2.1__py3-none-any.whl

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.
Files changed (141) hide show
  1. jac_client/docs/README.md +232 -172
  2. jac_client/docs/advanced-state.md +1012 -452
  3. jac_client/docs/asset-serving/intro.md +209 -0
  4. jac_client/docs/assets/pipe_line-v2.svg +32 -0
  5. jac_client/docs/assets/pipe_line.png +0 -0
  6. jac_client/docs/file-system/intro.md +90 -0
  7. jac_client/docs/guide-example/intro.md +117 -0
  8. jac_client/docs/guide-example/step-01-setup.md +260 -0
  9. jac_client/docs/guide-example/step-02-components.md +416 -0
  10. jac_client/docs/guide-example/step-03-styling.md +478 -0
  11. jac_client/docs/guide-example/step-04-todo-ui.md +477 -0
  12. jac_client/docs/guide-example/step-05-local-state.md +530 -0
  13. jac_client/docs/guide-example/step-06-events.md +750 -0
  14. jac_client/docs/guide-example/step-07-effects.md +469 -0
  15. jac_client/docs/guide-example/step-08-walkers.md +534 -0
  16. jac_client/docs/guide-example/step-09-authentication.md +586 -0
  17. jac_client/docs/guide-example/step-10-routing.md +540 -0
  18. jac_client/docs/guide-example/step-11-final.md +964 -0
  19. jac_client/docs/imports.md +538 -46
  20. jac_client/docs/lifecycle-hooks.md +517 -297
  21. jac_client/docs/routing.md +487 -357
  22. jac_client/docs/styling/intro.md +250 -0
  23. jac_client/docs/styling/js-styling.md +373 -0
  24. jac_client/docs/styling/material-ui.md +346 -0
  25. jac_client/docs/styling/pure-css.md +305 -0
  26. jac_client/docs/styling/sass.md +409 -0
  27. jac_client/docs/styling/styled-components.md +401 -0
  28. jac_client/docs/styling/tailwind.md +303 -0
  29. jac_client/examples/asset-serving/css-with-image/.babelrc +9 -0
  30. jac_client/examples/asset-serving/css-with-image/README.md +91 -0
  31. jac_client/examples/asset-serving/css-with-image/app.jac +67 -0
  32. jac_client/examples/asset-serving/css-with-image/assets/burger.png +0 -0
  33. jac_client/examples/asset-serving/css-with-image/package.json +28 -0
  34. jac_client/examples/asset-serving/css-with-image/styles.css +27 -0
  35. jac_client/examples/asset-serving/css-with-image/vite.config.js +29 -0
  36. jac_client/examples/asset-serving/image-asset/.babelrc +9 -0
  37. jac_client/examples/asset-serving/image-asset/README.md +119 -0
  38. jac_client/examples/asset-serving/image-asset/app.jac +43 -0
  39. jac_client/examples/asset-serving/image-asset/assets/burger.png +0 -0
  40. jac_client/examples/asset-serving/image-asset/package.json +28 -0
  41. jac_client/examples/asset-serving/image-asset/styles.css +27 -0
  42. jac_client/examples/asset-serving/image-asset/vite.config.js +29 -0
  43. jac_client/examples/asset-serving/import-alias/.babelrc +9 -0
  44. jac_client/examples/asset-serving/import-alias/README.md +83 -0
  45. jac_client/examples/asset-serving/import-alias/app.jac +57 -0
  46. jac_client/examples/asset-serving/import-alias/assets/burger.png +0 -0
  47. jac_client/examples/asset-serving/import-alias/package.json +28 -0
  48. jac_client/examples/asset-serving/import-alias/vite.config.js +29 -0
  49. jac_client/examples/basic/.babelrc +9 -0
  50. jac_client/examples/basic/README.md +16 -0
  51. jac_client/examples/basic/app.jac +16 -0
  52. jac_client/examples/basic/package.json +27 -0
  53. jac_client/examples/basic/vite.config.js +28 -0
  54. jac_client/examples/basic-auth/.babelrc +9 -0
  55. jac_client/examples/basic-auth/README.md +16 -0
  56. jac_client/examples/basic-auth/app.jac +308 -0
  57. jac_client/examples/basic-auth/package.json +27 -0
  58. jac_client/examples/basic-auth/vite.config.js +28 -0
  59. jac_client/examples/basic-auth-with-router/.babelrc +9 -0
  60. jac_client/examples/basic-auth-with-router/README.md +60 -0
  61. jac_client/examples/basic-auth-with-router/app.jac +464 -0
  62. jac_client/examples/basic-auth-with-router/package.json +28 -0
  63. jac_client/examples/basic-auth-with-router/vite.config.js +28 -0
  64. jac_client/examples/basic-full-stack/.babelrc +9 -0
  65. jac_client/examples/basic-full-stack/README.md +18 -0
  66. jac_client/examples/basic-full-stack/app.jac +320 -0
  67. jac_client/examples/basic-full-stack/package.json +28 -0
  68. jac_client/examples/basic-full-stack/vite.config.js +28 -0
  69. jac_client/examples/css-styling/js-styling/.babelrc +9 -0
  70. jac_client/examples/css-styling/js-styling/README.md +183 -0
  71. jac_client/examples/css-styling/js-styling/app.jac +63 -0
  72. jac_client/examples/css-styling/js-styling/package.json +28 -0
  73. jac_client/examples/css-styling/js-styling/styles.js +100 -0
  74. jac_client/examples/css-styling/js-styling/vite.config.js +28 -0
  75. jac_client/examples/css-styling/material-ui/.babelrc +9 -0
  76. jac_client/examples/css-styling/material-ui/README.md +16 -0
  77. jac_client/examples/css-styling/material-ui/app.jac +82 -0
  78. jac_client/examples/css-styling/material-ui/package.json +32 -0
  79. jac_client/examples/css-styling/material-ui/vite.config.js +28 -0
  80. jac_client/examples/css-styling/pure-css/.babelrc +9 -0
  81. jac_client/examples/css-styling/pure-css/README.md +16 -0
  82. jac_client/examples/css-styling/pure-css/app.jac +63 -0
  83. jac_client/examples/css-styling/pure-css/package.json +28 -0
  84. jac_client/examples/css-styling/pure-css/styles.css +112 -0
  85. jac_client/examples/css-styling/pure-css/vite.config.js +28 -0
  86. jac_client/examples/css-styling/sass-example/.babelrc +9 -0
  87. jac_client/examples/css-styling/sass-example/README.md +16 -0
  88. jac_client/examples/css-styling/sass-example/app.jac +63 -0
  89. jac_client/examples/css-styling/sass-example/package.json +29 -0
  90. jac_client/examples/css-styling/sass-example/styles.scss +158 -0
  91. jac_client/examples/css-styling/sass-example/vite.config.js +28 -0
  92. jac_client/examples/css-styling/styled-components/.babelrc +9 -0
  93. jac_client/examples/css-styling/styled-components/README.md +16 -0
  94. jac_client/examples/css-styling/styled-components/app.jac +66 -0
  95. jac_client/examples/css-styling/styled-components/package.json +29 -0
  96. jac_client/examples/css-styling/styled-components/styled.js +91 -0
  97. jac_client/examples/css-styling/styled-components/vite.config.js +28 -0
  98. jac_client/examples/css-styling/tailwind-example/.babelrc +9 -0
  99. jac_client/examples/css-styling/tailwind-example/README.md +16 -0
  100. jac_client/examples/css-styling/tailwind-example/app.jac +64 -0
  101. jac_client/examples/css-styling/tailwind-example/global.css +1 -0
  102. jac_client/examples/css-styling/tailwind-example/package.json +30 -0
  103. jac_client/examples/css-styling/tailwind-example/vite.config.js +30 -0
  104. jac_client/examples/full-stack-with-auth/.babelrc +9 -0
  105. jac_client/examples/full-stack-with-auth/README.md +16 -0
  106. jac_client/examples/full-stack-with-auth/app.jac +735 -0
  107. jac_client/examples/full-stack-with-auth/package.json +28 -0
  108. jac_client/examples/full-stack-with-auth/vite.config.js +30 -0
  109. jac_client/examples/with-router/.babelrc +9 -0
  110. jac_client/examples/with-router/README.md +17 -0
  111. jac_client/examples/with-router/app.jac +323 -0
  112. jac_client/examples/with-router/package.json +28 -0
  113. jac_client/examples/with-router/vite.config.js +28 -0
  114. jac_client/plugin/cli.py +95 -179
  115. jac_client/plugin/client.py +111 -2
  116. jac_client/plugin/client_runtime.jac +183 -890
  117. jac_client/plugin/vite_client_bundle.py +185 -205
  118. jac_client/tests/__init__.py +0 -1
  119. jac_client/tests/fixtures/{client_app.jac → basic-app/app.jac} +1 -1
  120. jac_client/tests/fixtures/cl_file/app.cl.jac +38 -0
  121. jac_client/tests/fixtures/cl_file/app.jac +15 -0
  122. jac_client/tests/fixtures/{client_app_with_antd.jac → client_app_with_antd/app.jac} +7 -0
  123. jac_client/tests/fixtures/{js_import.jac → js_import/app.jac} +2 -2
  124. jac_client/tests/fixtures/{relative_import.jac → relative_import/app.jac} +1 -1
  125. jac_client/tests/fixtures/{button.jac → relative_import/button.jac} +2 -2
  126. jac_client/tests/fixtures/spawn_test/app.jac +133 -0
  127. jac_client/tests/fixtures/{test_fragments_spread.jac → test_fragments_spread/app.jac} +11 -2
  128. jac_client/tests/test_asset_examples.py +339 -0
  129. jac_client/tests/test_cl.py +345 -151
  130. jac_client/tests/test_create_jac_app.py +41 -45
  131. {jac_client-0.1.0.dist-info → jac_client-0.2.1.dist-info}/METADATA +72 -16
  132. jac_client-0.2.1.dist-info/RECORD +140 -0
  133. jac_client/examples/little-x/package-lock.json +0 -2840
  134. jac_client/examples/todo-app/README.md +0 -82
  135. jac_client/examples/todo-app/app.jac +0 -683
  136. jac_client/examples/todo-app/package-lock.json +0 -999
  137. jac_client/examples/todo-app/package.json +0 -22
  138. jac_client-0.1.0.dist-info/RECORD +0 -33
  139. /jac_client/tests/fixtures/{utils.js → js_import/utils.js} +0 -0
  140. {jac_client-0.1.0.dist-info → jac_client-0.2.1.dist-info}/WHEEL +0 -0
  141. {jac_client-0.1.0.dist-info → jac_client-0.2.1.dist-info}/entry_points.txt +0 -0
@@ -1,941 +1,234 @@
1
1
  """Client-side runtime for Jac JSX and walker interactions."""
2
2
 
3
- cl import from 'react' {* as React}
4
- cl import from 'react-dom/client' {* as ReactDOM}
5
-
6
- cl {
7
- # JSX factory function - uses React.createElement
8
- def __jacJsx(tag: any, props: dict = {}, children: any = []) -> any {
9
- # Handle fragments: when tag is None/null, use React.Fragment
10
- if tag == None {
11
- tag = React.Fragment;
12
- }
13
-
14
- childrenArray = [];
15
- if children != None {
16
- if Array.isArray(children) {
17
- childrenArray = children;
18
- } else {
19
- childrenArray = [children];
20
- }
21
- }
22
-
23
- # Filter out null/undefined children
24
- reactChildren = [];
25
- for child in childrenArray {
26
- if child != None {
27
- reactChildren.push(child);
28
- }
29
- }
30
-
31
- if reactChildren.length > 0 {
32
- args = [tag, props];
33
- for child in reactChildren {
34
- args.push(child);
35
- }
36
- return React.createElement.apply(React, args);
37
- } else {
38
- return React.createElement(tag, props);
39
- }
40
- }
41
-
42
- def renderJsxTree(node: any, container: any) -> None {
43
- try {
44
- ReactDOM.createRoot(container).render(node);
45
- } except Exception as err {
46
- console.error("[Jac] Error in renderJsxTree:", err);
47
- }
48
- }
49
-
50
- # ============================================================================
51
- # Reactive State Management System
52
- # ============================================================================
53
-
54
- # Global reactive context for managing signals, effects, and re-renders
55
- let __jacReactiveContext = {
56
- "signals": [], # Global signal storage (enables closures)
57
- "pendingRenders": [], # Batched re-renders queue
58
- "flushScheduled": False, # Debounce flag for batching
59
- "rootComponent": None, # Root function to re-render
60
- "reactRoot": None, # Store the React 18 root instance
61
- "currentComponent": None, # Current component ID being rendered
62
- "currentEffect": None, # Current effect for dependency tracking
63
- "router": None, # Global router instance
64
- "mountedComponents": {} # Track which components have mounted (for onMount)
65
- };
66
-
67
- # Create a reactive signal (primitive value)
68
- # Returns [getter, setter] tuple for reactive primitive value
69
- def createSignal(initialValue: any) -> list {
70
- signalId = __jacReactiveContext.signals.length;
71
- signalData = {"value": initialValue, "subscribers": []};
72
- __jacReactiveContext.signals.push(signalData);
73
-
74
- def getter() -> any {
75
- __jacTrackDependency(signalData.subscribers);
76
- return signalData.value;
77
- }
78
-
79
- def setter(newValue: any) -> None {
80
- if newValue != signalData.value {
81
- signalData.value = newValue;
82
- __jacNotifySubscribers(signalData.subscribers);
83
- }
84
- }
85
-
86
- return [getter, setter];
87
- }
88
-
89
- # Create reactive state (object/dict)
90
- # Returns [getter, setter] tuple for reactive object
91
- def createState(initialState: dict) -> list {
92
- signalId = __jacReactiveContext.signals.length;
93
- signalData = {"value": initialState, "subscribers": []};
94
- __jacReactiveContext.signals.push(signalData);
95
-
96
- def getter() -> dict {
97
- __jacTrackDependency(signalData.subscribers);
98
- return signalData.value;
99
- }
100
-
101
- def setter(updates: dict) -> None {
102
- # Shallow merge
103
- newState = {};
104
- stateValue = signalData.value;
105
- for key in __objectKeys(stateValue) {
106
- newState[key] = stateValue[key];
107
- }
108
- for key in __objectKeys(updates) {
109
- newState[key] = updates[key];
110
- }
111
- signalData.value = newState;
112
- __jacNotifySubscribers(signalData.subscribers);
113
- }
114
-
115
- return [getter, setter];
116
- }
117
-
118
- # Run effect when dependencies change
119
- # Executes effectFn and re-runs when tracked signals change
120
- def createEffect(effectFn: any) -> None {
121
- __jacRunEffect(effectFn);
122
- }
123
-
124
- # Run effect once when component mounts (similar to useEffect with empty deps)
125
- # Executes mountFn once when the component first renders
126
- def onMount(mountFn: any) -> None {
127
- currentComponent = __jacReactiveContext.currentComponent;
128
- if not currentComponent {
129
- # Not in a component context, run immediately
130
- __jacRunEffect(mountFn);
131
- return;
132
- }
133
-
134
- # Track mounted components
135
- if not __jacReactiveContext.mountedComponents {
136
- __jacReactiveContext.mountedComponents = {};
137
- }
138
-
139
- # Check if this component has already mounted
140
- componentId = f"{currentComponent}";
141
- if not __jacHasOwn(__jacReactiveContext.mountedComponents, componentId) {
142
- # Mark as mounted and run the effect
143
- __jacReactiveContext.mountedComponents[componentId] = True;
144
-
145
- # Run in next tick to ensure component is fully rendered
146
- try {
147
- setTimeout(lambda -> None {
148
- __jacRunEffect(mountFn);
149
- }, 0);
150
- } except Exception {
151
- # Fallback if setTimeout not available
152
- __jacRunEffect(mountFn);
153
- }
154
- }
3
+ cl import from 'react' { * as React }
4
+ cl import from 'react-dom/client' { * as ReactDOM }
5
+ cl import from 'react-router-dom' {
6
+ HashRouter as ReactRouterHashRouter,
7
+ Routes as ReactRouterRoutes,
8
+ Route as ReactRouterRoute,
9
+ Link as ReactRouterLink,
10
+ Navigate as ReactRouterNavigate,
11
+ useNavigate as reactRouterUseNavigate,
12
+ useLocation as reactRouterUseLocation,
13
+ useParams as reactRouterUseParams
155
14
  }
156
15
 
157
- # Internal: Track component dependencies
158
- def __jacTrackDependency(subscribers: list) -> None {
159
- currentEffect = __jacReactiveContext.currentEffect;
160
- if currentEffect {
161
- alreadySubscribed = False;
162
- for sub in subscribers {
163
- if sub == currentEffect {
164
- alreadySubscribed = True;
16
+ cl {
17
+ # JSX factory function - uses React.createElement
18
+ def __jacJsx(tag: any, props: dict = {}, children: any = []) -> any {
19
+ # Handle fragments: when tag is None/null, use React.Fragment
20
+ if tag == None {
21
+ tag = React.Fragment;
22
+ }
23
+
24
+ childrenArray = [];
25
+ if children != None {
26
+ if Array.isArray(children) {
27
+ childrenArray = children;
28
+ } else {
29
+ childrenArray = [children];
165
30
  }
166
31
  }
167
- if not alreadySubscribed {
168
- subscribers.push(currentEffect);
169
- }
170
- }
171
32
 
172
- currentComponent = __jacReactiveContext.currentComponent;
173
- if currentComponent {
174
- alreadySubscribed = False;
175
- for sub in subscribers {
176
- if sub == currentComponent {
177
- alreadySubscribed = True;
33
+ # Filter out null/undefined children
34
+ reactChildren = [];
35
+ for child in childrenArray {
36
+ if child != None {
37
+ reactChildren.push(child);
178
38
  }
179
39
  }
180
- if not alreadySubscribed {
181
- subscribers.push(currentComponent);
182
- }
183
- }
184
- }
185
-
186
- # Internal: Notify subscribers of state change
187
- def __jacNotifySubscribers(subscribers: list) -> None {
188
- for subscriber in subscribers {
189
- if __isFunction(subscriber) {
190
- # It's an effect function - re-run it
191
- __jacRunEffect(subscriber);
192
- } else {
193
- # It's a component ID - schedule re-render
194
- __jacScheduleRerender(subscriber);
195
- }
196
- }
197
- }
198
-
199
- # Internal: Run an effect function with dependency tracking
200
- def __jacRunEffect(effectFn: any) -> None {
201
- previousEffect = __jacReactiveContext.currentEffect;
202
- __jacReactiveContext.currentEffect = effectFn;
203
-
204
- try {
205
- effectFn();
206
- } except Exception as err {
207
- console.error("[Jac] Error in effect:", err);
208
- }
209
-
210
- __jacReactiveContext.currentEffect = previousEffect;
211
- }
212
-
213
- # Schedule a re-render (batched)
214
- def __jacScheduleRerender(componentId: any) -> None {
215
- pending = __jacReactiveContext.pendingRenders;
216
-
217
- # Check if already scheduled
218
- alreadyScheduled = False;
219
- for item in pending {
220
- if item == componentId {
221
- alreadyScheduled = True;
222
- }
223
- }
224
-
225
- if not alreadyScheduled {
226
- pending.push(componentId);
227
- __jacScheduleFlush();
228
- }
229
- }
230
-
231
- # Schedule a flush of pending renders
232
- def __jacScheduleFlush() -> None {
233
- if not __jacReactiveContext.flushScheduled {
234
- __jacReactiveContext.flushScheduled = True;
235
-
236
- # Use requestAnimationFrame for batching, fallback to setTimeout
237
- try {
238
- requestAnimationFrame(__jacFlushRenders);
239
- } except Exception {
240
- setTimeout(__jacFlushRenders, 0);
241
- }
242
- }
243
- }
244
-
245
- # Flush all pending renders
246
- def __jacFlushRenders() -> None {
247
- pending = __jacReactiveContext.pendingRenders;
248
- __jacReactiveContext.pendingRenders = [];
249
- __jacReactiveContext.flushScheduled = False;
250
-
251
- for componentId in pending {
252
- __jacRerenderComponent(componentId);
253
- }
254
- }
255
-
256
- # Re-render the root component
257
- def __jacRerenderComponent(componentId: any) -> None {
258
- # Use the stored React 18 root for re-rendering
259
- reactRoot = __jacReactiveContext.reactRoot;
260
- if not reactRoot {
261
- console.error("[Jac] React root not initialized. Cannot re-render.");
262
- return;
263
- }
264
-
265
- rootComponent = __jacReactiveContext.rootComponent;
266
- if not rootComponent {
267
- return;
268
- }
269
-
270
- try {
271
- previousComponent = __jacReactiveContext.currentComponent;
272
- __jacReactiveContext.currentComponent = componentId;
273
-
274
- component = rootComponent();
275
-
276
- # FIXED: Use the stored React 18 root's render method
277
- reactRoot.render(component);
278
-
279
- __jacReactiveContext.currentComponent = previousComponent;
280
- } except Exception as err {
281
- console.error("[Jac] Error re-rendering component:", err);
282
- }
283
- }
284
-
285
- # ============================================================================
286
- # Declarative Routing System
287
- # ============================================================================
288
-
289
- # Route configuration object
290
- obj RouteConfig {
291
- has path: str;
292
- has component: any;
293
- has guard: any = None;
294
- }
295
-
296
- # Create a router instance
297
- def initRouter(routes: list, defaultRoute: str = "/") -> dict {
298
- # Get initial path from hash or use default
299
- initialPath = __jacGetHashPath();
300
- if not initialPath {
301
- initialPath = defaultRoute;
302
- }
303
-
304
- # Create reactive signal for current path
305
- [currentPath, setCurrentPath] = createSignal(initialPath);
306
-
307
- # Listen to hash changes
308
- window.addEventListener("hashchange", lambda event: any -> None {
309
- newPath = __jacGetHashPath();
310
- if not newPath {
311
- newPath = defaultRoute;
312
- }
313
- setCurrentPath(newPath);
314
- });
315
40
 
316
- # Listen to popstate (back/forward buttons)
317
- window.addEventListener("popstate", lambda event: any -> None {
318
- newPath = __jacGetHashPath();
319
- if not newPath {
320
- newPath = defaultRoute;
321
- }
322
- setCurrentPath(newPath);
323
- });
324
-
325
- # Render method - returns component for current route
326
- def render() -> any {
327
- path = currentPath(); # Track dependency!
328
-
329
- # Find matching route
330
- for route in routes {
331
- if route.path == path {
332
- # Check guard if present
333
- if route.guard and not route.guard() {
334
- return __jacJsx("div", {}, ["Access Denied"]);
335
- }
336
- return route.component();
41
+ if reactChildren.length > 0 {
42
+ args = [tag, props];
43
+ for child in reactChildren {
44
+ args.push(child);
337
45
  }
338
- }
339
-
340
- # No match - show 404
341
- return __jacJsx("div", {}, ["404 - Route not found: ", path]);
342
- }
343
-
344
- # Navigate method
345
- def navigateTo(path: str) -> None {
346
- window.location.hash = "#" + path;
347
- setCurrentPath(path);
348
- }
349
-
350
- # Store router in global context
351
- router = {
352
- "path": currentPath,
353
- "render": render,
354
- "navigate": navigateTo
355
- };
356
- __jacReactiveContext.router = router;
357
-
358
- return router;
359
- }
360
-
361
- # Route config factory
362
- def Route(path: str, component: any, guard: any = None) -> dict {
363
- return {"path": path, "component": component, "guard": guard};
364
- }
365
-
366
- # Link component for declarative navigation
367
- def Link(props: dict) -> any {
368
- href = props["href"] if "href" in props else "/";
369
- children = props["children"] if "children" in props else [];
370
-
371
- def handleClick(event: any) -> None {
372
- console.log("Link clicked, navigating to:", href);
373
- event.preventDefault();
374
- navigate(href);
375
- }
376
-
377
- # Ensure children is properly handled - convert to array if it's not already
378
- childrenArray = [];
379
- if children != None {
380
- if Array.isArray(children) {
381
- childrenArray = children;
46
+ return React.createElement.apply(React, args);
382
47
  } else {
383
- childrenArray = [children];
384
- }
48
+ return React.createElement(tag, props);
49
+ }
50
+ }
51
+
52
+ # ============================================================================
53
+ # React Router Integration (using react-router-dom v6)
54
+ # ============================================================================
55
+ # Direct re-exports of React Router components for seamless integration
56
+ # Router uses HashRouter for hash-based routing (#/path)
57
+ # See: https://reactrouter.com/6.30.1/router-components/hash-router
58
+ let Router = ReactRouterHashRouter;
59
+ let Routes = ReactRouterRoutes;
60
+ let Route = ReactRouterRoute;
61
+ let Link = ReactRouterLink;
62
+ let Navigate = ReactRouterNavigate;
63
+
64
+ # React Router Hooks - wrapped for Jac compatibility
65
+ let useNavigate = reactRouterUseNavigate;
66
+ let useLocation = reactRouterUseLocation;
67
+ let useParams = reactRouterUseParams;
68
+
69
+ # useRouter Hook - convenience hook that combines common router utilities
70
+ def useRouter() -> dict {
71
+ navigate = reactRouterUseNavigate();
72
+ location = reactRouterUseLocation();
73
+ params = reactRouterUseParams();
74
+
75
+ return {
76
+ "navigate": navigate,
77
+ "location": location,
78
+ "params": params,
79
+ "pathname": location.pathname,
80
+ "search": location.search,
81
+ "hash": location.hash
82
+ };
385
83
  }
386
84
 
387
- # Return JSX node manually to properly spread children
388
- return __jacJsx("a", {"href": "#" + href, "onclick": handleClick}, childrenArray);
389
- }
390
-
391
- # Navigate programmatically
392
- def navigate(path: str) -> None {
393
- console.log("navigate() called with path:", path);
394
- router = __jacReactiveContext.router;
395
- if router {
396
- console.log("Router found, calling router.navigate()");
397
- router.navigate(path);
398
- } else {
399
- console.log("No router, setting hash directly");
85
+ # navigate function - programmatic navigation (backward compatibility)
86
+ # Note: Use useNavigate() hook inside components instead
87
+ def navigate(path: str) -> None {
400
88
  window.location.hash = "#" + path;
401
89
  }
402
- }
403
-
404
- # Hook to access router in components
405
- def useRouter() -> dict {
406
- return __jacReactiveContext.router;
407
- }
408
-
409
- # Internal: Get current hash path
410
- def __jacGetHashPath() -> str {
411
- hash = window.location.hash;
412
- if hash {
413
- return hash[1:]; # Remove '#'
414
- }
415
- return "";
416
- }
417
90
 
418
- # ============================================================================
419
- # Walker spawn function
420
- # ============================================================================
421
-
422
- async def __jacSpawn(walker: str, fields: dict = {}, nd: str | None = None) -> any {
423
- token = __getLocalStorage("jac_token");
91
+ # ============================================================================
92
+ # Walker spawn function
93
+ # ============================================================================
94
+ async def __jacSpawn(left: str, right: str = "", fields: dict = {}) -> any {
95
+ token = __getLocalStorage("jac_token");
96
+ url = f"/walker/{left}";
97
+ if right != "" {
98
+ url = f"/walker/{left}/{right}";
99
+ }
100
+ response = await fetch(
101
+ url,
102
+ {
103
+ "method": "POST",
104
+ "accept": "application/json",
105
+ "headers": {
106
+ "Content-Type": "application/json",
107
+ "Authorization": f"Bearer {token}" if token else ""
108
+ },
109
+ "body": JSON.stringify({"fields": fields})
110
+ }
111
+ );
424
112
 
425
- response = await fetch(
426
- f"/walker/{walker}",
427
- {
428
- "method": "POST",
429
- "headers": {
430
- "Content-Type": "application/json",
431
- "Authorization": f"Bearer {token}" if token else ""
432
- },
433
- "body": JSON.stringify({"nd": nd if nd else "root", **fields})
113
+ if not response.ok {
114
+ error_text = await response.json();
115
+ raise Exception(f"Walker {walker} failed: {error_text}") ;
434
116
  }
435
- );
436
117
 
437
- if not response.ok {
438
- error_text = await response.text();
439
- raise Exception(f"Walker {walker} failed: {error_text}");
118
+ return await response.json();
440
119
  }
441
120
 
442
- return JSON.parse(await response.text());
443
- }
444
-
445
- # Function call function - calls server-side functions from client
446
- async def __jacCallFunction(function_name: str, args: dict = {}) -> any {
447
- token = __getLocalStorage("jac_token");
448
-
449
- response = await fetch(
450
- f"/function/{function_name}",
451
- {
452
- "method": "POST",
453
- "headers": {
454
- "Content-Type": "application/json",
455
- "Authorization": f"Bearer {token}" if token else ""
456
- },
457
- "body": JSON.stringify({"args": args})
458
- }
459
- );
460
-
461
- if not response.ok {
462
- error_text = await response.text();
463
- raise Exception(f"Function {function_name} failed: {error_text}");
121
+ def jacSpawn(left: str, right: str = "", fields: dict = {}) -> any {
122
+ return __jacSpawn(left, right, fields);
464
123
  }
465
124
 
466
- data = JSON.parse(await response.text());
467
- return data["result"];
468
- }
469
-
470
- # Authentication helpers
471
- async def jacSignup(username: str, password: str) -> dict {
472
- response = await fetch(
473
- "/user/create",
474
- {
475
- "method": "POST",
476
- "headers": {"Content-Type": "application/json"},
477
- "body": JSON.stringify({"username": username, "password": password})
478
- }
479
- );
125
+ # Function call function - calls server-side functions from client
126
+ async def __jacCallFunction(function_name: str, args: dict = {}) -> any {
127
+ token = __getLocalStorage("jac_token");
480
128
 
481
- if response.ok {
482
- data = JSON.parse(await response.text());
483
- token = data["token"];
484
- if token {
485
- __setLocalStorage("jac_token", token);
486
- return {"success": True, "token": token, "username": username};
487
- }
488
- return {"success": False, "error": "No token received"};
489
- } else {
490
- error_text = await response.text();
491
- try {
492
- error_data = JSON.parse(error_text);
493
- return {"success": False, "error": error_data["error"] if error_data["error"] != None else "Signup failed"};
494
- } except Exception {
495
- return {"success": False, "error": error_text};
496
- }
497
- }
498
- }
129
+ response = await fetch(
130
+ f"/function/{function_name}",
131
+ {
132
+ "method": "POST",
133
+ "headers": {
134
+ "Content-Type": "application/json",
135
+ "Authorization": f"Bearer {token}" if token else ""
136
+ },
137
+ "body": JSON.stringify({"args": args})
138
+ }
139
+ );
499
140
 
500
- async def jacLogin(username: str, password: str) -> bool {
501
- response = await fetch(
502
- "/user/login",
503
- {
504
- "method": "POST",
505
- "headers": {"Content-Type": "application/json"},
506
- "body": JSON.stringify({"username": username, "password": password})
141
+ if not response.ok {
142
+ error_text = await response.text();
143
+ raise Exception(f"Function {function_name} failed: {error_text}") ;
507
144
  }
508
- );
509
145
 
510
- if response.ok {
511
146
  data = JSON.parse(await response.text());
512
- token = data["token"];
513
- if token {
514
- __setLocalStorage("jac_token", token);
515
- return True;
516
- }
517
- }
518
- return False;
519
- }
520
-
521
- def jacLogout() -> None {
522
- __removeLocalStorage("jac_token");
523
- }
524
-
525
- def jacIsLoggedIn() -> bool {
526
- token = __getLocalStorage("jac_token");
527
- return token != None and token != "";
528
- }
529
-
530
- # Browser API shims
531
- def __getLocalStorage(key: str) -> str {
532
- storage = globalThis.localStorage;
533
- return storage.getItem(key) if storage else "";
534
- }
535
-
536
- def __setLocalStorage(key: str, value: str) -> None {
537
- storage = globalThis.localStorage;
538
- if storage {
539
- storage.setItem(key, value);
540
- }
541
- }
542
-
543
- def __removeLocalStorage(key: str) -> None {
544
- storage = globalThis.localStorage;
545
- if storage {
546
- storage.removeItem(key);
547
- }
548
- }
549
-
550
- def __isObject(value: any) -> bool {
551
- if value == None {
552
- return False;
553
- }
554
- return Object.prototype.toString.call(value) == "[object Object]";
555
- }
556
-
557
- def __isFunction(value: any) -> bool {
558
- return Object.prototype.toString.call(value) == "[object Function]";
559
- }
560
-
561
- def __objectKeys(obj: any) -> list {
562
- if obj == None {
563
- return [];
564
- }
565
- return Object.keys(obj);
566
- }
567
-
568
- # Low-level helpers
569
- def __jacHasOwn(obj: any, key: any) -> bool {
570
- try {
571
- return Object.prototype.hasOwnProperty.call(obj, key);
572
- } except Exception {
573
- return False;
574
- }
575
- }
576
-
577
- # Internal polyfill for Python-style dict.get on plain JS objects
578
- def __jacEnsureObjectGetPolyfill() -> None {
579
- # No longer needed - we use standard JavaScript object access instead of .get() method
580
- return;
581
- }
582
-
583
- # Common utility helpers
584
- def __jacGetDocument(scope: any) -> any {
585
- try {
586
- return scope.document;
587
- } except Exception {}
588
- try {
589
- return window.document;
590
- } except Exception {}
591
- return None;
592
- }
593
-
594
- def __jacParseJsonObject(text: str) -> any {
595
- try {
596
- parsed = JSON.parse(text);
597
- if __isObject(parsed) {
598
- return parsed;
599
- }
600
- console.error("[Jac] Hydration payload is not an object");
601
- return None;
602
- } except Exception as err {
603
- console.error("[Jac] Failed to parse hydration payload", err);
604
- return None;
147
+ return data["result"];
605
148
  }
606
- }
607
-
608
- def __jacBuildOrderedArgs(order: list, argsDict: dict) -> list {
609
- result = [];
610
- if not order {
611
- return result;
612
- }
613
- values = argsDict if __isObject(argsDict) else {};
614
- for name in order {
615
- result.push(values[name]);
616
- }
617
- return result;
618
- }
619
-
620
- def __jacResolveRenderer(scope: any) -> any {
621
- if scope.renderJsxTree {
622
- return scope.renderJsxTree;
623
- }
624
- if __isFunction(renderJsxTree) {
625
- return renderJsxTree;
626
- }
627
- return None;
628
- }
629
-
630
- def __jacResolveTarget(
631
- moduleRecord: dict,
632
- registry: dict,
633
- name: str
634
- ) -> any {
635
- moduleFunctions = (
636
- moduleRecord.moduleFunctions
637
- if moduleRecord and moduleRecord.moduleFunctions
638
- else {}
639
- );
640
- if __jacHasOwn(moduleFunctions, name) {
641
- return moduleFunctions[name];
642
- }
643
- registryFunctions = (
644
- registry.functions
645
- if registry and registry.functions
646
- else {}
647
- );
648
- if __jacHasOwn(registryFunctions, name) {
649
- return registryFunctions[name];
650
- }
651
- return None;
652
- }
653
-
654
- def __jacSafeCallTarget(
655
- target: any,
656
- scope: any,
657
- orderedArgs: list,
658
- targetName: str
659
- ) -> dict {
660
- try {
661
- result = target.apply(scope, orderedArgs);
662
- return {"ok": True, "value": result};
663
- } except Exception as err {
664
- console.error("[Jac] Error executing client function " + targetName, err);
665
- return {"ok": False, "value": None};
666
- }
667
- }
668
149
 
669
- # Runtime support helpers for client module registration / hydration
670
- def __jacGlobalScope() -> any {
671
- try {
672
- return globalThis;
673
- } except Exception {}
674
- try {
675
- return window;
676
- } except Exception {}
677
- try {
678
- return self;
679
- } except Exception {}
680
- return {};
681
- }
682
-
683
- def __jacEnsureRegistry() -> dict {
684
- scope = __jacGlobalScope();
685
- registry = scope.__jacClient;
686
- if not registry {
687
- registry = {
688
- "functions": {},
689
- "globals": {},
690
- "modules": {},
691
- "state": {"globals": {}},
692
- "__hydration": {"registered": False},
693
- "lastModule": None
694
- };
695
- scope.__jacClient = registry;
696
- return registry;
697
- }
698
- if not registry.functions {
699
- registry.functions = {};
700
- }
701
- if not registry.globals {
702
- registry.globals = {};
703
- }
704
- if not registry.modules {
705
- registry.modules = {};
706
- }
707
- if not registry.state {
708
- registry.state = {"globals": {}};
709
- } elif not registry.state.globals {
710
- registry.state.globals = {};
711
- }
712
- if not registry.__hydration {
713
- registry.__hydration = {"registered": False};
714
- }
715
- return registry;
716
- }
150
+ # Authentication helpers
151
+ async def jacSignup(username: str, password: str) -> dict {
152
+ response = await fetch(
153
+ "/user/create",
154
+ {
155
+ "method": "POST",
156
+ "headers": {"Content-Type": "application/json"},
157
+ "body": JSON.stringify({"username": username, "password": password})
158
+ }
159
+ );
717
160
 
718
- def __jacApplyRender(renderer: any, container: any, node: any) -> None {
719
- if not container {
720
- return;
721
- }
722
- try {
723
- # This function is now mostly legacy/fallback.
724
- # The main hydration path in __jacHydrateFromDom handles React 18.
725
- if renderer {
726
- renderer(node, container);
161
+ if response.ok {
162
+ data = JSON.parse(await response.text());
163
+ token = data["token"];
164
+ if token {
165
+ __setLocalStorage("jac_token", token);
166
+ return {"success": True, "token": token, "username": username};
167
+ }
168
+ return {"success": False, "error": "No token received"};
727
169
  } else {
728
- console.warn("[Jac] No JSX renderer available.");
170
+ error_text = await response.text();
171
+ try {
172
+ error_data = JSON.parse(error_text);
173
+ return {
174
+ "success": False,
175
+ "error": error_data["error"]
176
+ if error_data["error"] != None
177
+ else "Signup failed"
178
+ };
179
+ } except Exception {
180
+ return {"success": False, "error": error_text};
181
+ }
729
182
  }
730
- } except Exception as err {
731
- console.error("[Jac] Failed to render JSX tree (fallback path)", err);
732
- }
733
- }
734
-
735
- def __jacHydrateFromDom(defaultModuleName: str) -> None {
736
- __jacEnsureObjectGetPolyfill();
737
- scope = __jacGlobalScope();
738
- documentRef = __jacGetDocument(scope);
739
- if not documentRef {
740
- return;
741
- }
742
-
743
- initEl = documentRef.getElementById("__jac_init__");
744
- rootEl = documentRef.getElementById("__jac_root");
745
- if not initEl or not rootEl {
746
- return;
747
- }
748
-
749
- dataset = initEl.dataset if initEl.dataset else None;
750
- if dataset and dataset.jacHydrated == "true" {
751
- return;
752
- }
753
- if dataset {
754
- dataset.jacHydrated = "true";
755
- }
756
-
757
- payloadText = initEl.textContent if initEl.textContent else "{}";
758
- payload = __jacParseJsonObject(payloadText);
759
- if not payload {
760
- return;
761
- }
762
-
763
- targetName = payload["function"];
764
- if not targetName {
765
- return;
766
- }
767
-
768
- fallbackModule = defaultModuleName if defaultModuleName else "";
769
- moduleCandidate = payload["module"];
770
- moduleName = moduleCandidate if moduleCandidate else fallbackModule;
771
-
772
- registry = __jacEnsureRegistry();
773
- modulesStore = registry.modules if registry.modules else {};
774
- moduleRecord = modulesStore[moduleName] if __jacHasOwn(modulesStore, moduleName) else None;
775
- if not moduleRecord {
776
- console.error("[Jac] Client module not registered: " + moduleName);
777
- return;
778
183
  }
779
184
 
780
- argOrderRaw = payload["argOrder"] if payload["argOrder"] != None else [];
781
- argOrder = argOrderRaw if Array.isArray(argOrderRaw) else [];
782
- argsDictRaw = payload["args"] if payload["args"] != None else {};
783
- argsDict = argsDictRaw if __isObject(argsDictRaw) else {};
784
- orderedArgs = __jacBuildOrderedArgs(argOrder, argsDict);
785
-
786
- payloadGlobalsRaw = payload["globals"] if payload["globals"] != None else {};
787
- payloadGlobals = payloadGlobalsRaw if __isObject(payloadGlobalsRaw) else {};
788
- registry.state.globals[moduleName] = payloadGlobals;
789
- for gName in __objectKeys(payloadGlobals) {
790
- gValue = payloadGlobals[gName];
791
- scope[gName] = gValue;
792
- registry.globals[gName] = gValue;
793
- }
794
-
795
- target = __jacResolveTarget(moduleRecord, registry, targetName);
796
- if not target {
797
- console.error("[Jac] Client function not found: " + targetName);
798
- return;
799
- }
800
-
801
- # Set up reactive root component for automatic re-rendering
802
- __jacReactiveContext.rootComponent = lambda -> any {
803
- __jacReactiveContext.currentComponent = "__root__";
804
- result = target.apply(scope, orderedArgs);
805
- __jacReactiveContext.currentComponent = None;
806
- return result;
807
- };
808
-
809
- renderer = __jacResolveRenderer(scope);
810
- if not renderer {
811
- console.warn("[Jac] renderJsxTree is not available in client bundle");
812
- }
813
-
814
- # FIX: Declare reactRoot in the function scope
815
- let reactRoot = None;
816
-
817
- # 1. Create and store the React 18 root instance
818
- try {
819
- reactRoot = ReactDOM.createRoot(rootEl);
820
- __jacReactiveContext.reactRoot = reactRoot; # Store the root instance
821
- } except Exception as err {
822
- console.error("[Jac] Failed to create React root for hydration:", err);
823
- return;
824
- }
825
-
826
- # 2. Initial render - call the root component
827
- value = __jacReactiveContext.rootComponent();
828
-
829
- # 3. Use the stored root for the initial render (hydration)
830
- if value and __isObject(value) and __isFunction(value.then) {
831
- value.then(
832
- lambda node: any -> None {
833
- reactRoot.render(node);
834
- }
835
- ).catch(
836
- lambda err: any -> None {
837
- console.error("[Jac] Error resolving client function promise", err);
185
+ async def jacLogin(username: str, password: str) -> bool {
186
+ response = await fetch(
187
+ "/user/login",
188
+ {
189
+ "method": "POST",
190
+ "headers": {"Content-Type": "application/json"},
191
+ "body": JSON.stringify({"username": username, "password": password})
838
192
  }
839
193
  );
840
- } else {
841
- reactRoot.render(value);
842
- }
843
- }
844
-
845
- def __jacExecuteHydration() -> None {
846
- registry = __jacEnsureRegistry();
847
- defaultModule = registry.lastModule if registry.lastModule else "";
848
- __jacHydrateFromDom(defaultModule);
849
- }
850
-
851
- def __jacEnsureHydration(moduleName: str) -> None {
852
- __jacEnsureObjectGetPolyfill();
853
- registry = __jacEnsureRegistry();
854
- registry.lastModule = moduleName;
855
194
 
856
- existingHydration = registry.__hydration if registry.__hydration else None;
857
- hydration = existingHydration if existingHydration else {"registered": False};
858
- registry.__hydration = hydration;
859
-
860
- scope = __jacGlobalScope();
861
- documentRef = __jacGetDocument(scope);
862
- if not documentRef {
863
- return;
195
+ if response.ok {
196
+ data = JSON.parse(await response.text());
197
+ token = data["token"];
198
+ if token {
199
+ __setLocalStorage("jac_token", token);
200
+ return True;
201
+ }
202
+ }
203
+ return False;
864
204
  }
865
205
 
866
- alreadyRegistered = hydration.registered if hydration.registered else False;
867
- if not alreadyRegistered {
868
- hydration.registered = True;
869
- documentRef.addEventListener(
870
- "DOMContentLoaded",
871
- lambda _event: any -> None {
872
- __jacExecuteHydration();
873
- },
874
- {"once": True}
875
- );
206
+ def jacLogout() -> None {
207
+ __removeLocalStorage("jac_token");
876
208
  }
877
- }
878
209
 
879
- def __jacRegisterClientModule(
880
- moduleName: str,
881
- clientFunctions: list = [],
882
- clientGlobals: dict = {}
883
- ) -> None {
884
- __jacEnsureObjectGetPolyfill();
885
- scope = __jacGlobalScope();
886
- registry = __jacEnsureRegistry();
210
+ def jacIsLoggedIn() -> bool {
211
+ token = __getLocalStorage("jac_token");
212
+ return token != None and token != "";
213
+ }
887
214
 
888
- moduleFunctions = {};
889
- registeredFunctions = [];
890
- if clientFunctions {
891
- for funcName in clientFunctions {
892
- funcRef = scope[funcName];
893
- if not funcRef {
894
- console.error("[Jac] Client function not found during registration: " + funcName);
895
- continue;
896
- }
897
- moduleFunctions[funcName] = funcRef;
898
- registry.functions[funcName] = funcRef;
899
- scope[funcName] = funcRef;
900
- registeredFunctions.push(funcName);
901
- }
215
+ # Browser API shims
216
+ def __getLocalStorage(key: str) -> str {
217
+ storage = globalThis.localStorage;
218
+ return storage.getItem(key) if storage else "";
902
219
  }
903
220
 
904
- moduleGlobals = {};
905
- globalNames = [];
906
- globalsMap = clientGlobals if clientGlobals else {};
907
- for gName in __objectKeys(globalsMap) {
908
- globalNames.push(gName);
909
- defaultValue = globalsMap[gName];
910
- existing = scope[gName];
911
- if existing == None {
912
- scope[gName] = defaultValue;
913
- moduleGlobals[gName] = defaultValue;
914
- } else {
915
- moduleGlobals[gName] = existing;
221
+ def __setLocalStorage(key: str, value: str) -> None {
222
+ storage = globalThis.localStorage;
223
+ if storage {
224
+ storage.setItem(key, value);
916
225
  }
917
- registry.globals[gName] = scope[gName];
918
226
  }
919
227
 
920
- modulesStore = registry.modules if registry.modules else {};
921
- existingRecord = modulesStore[moduleName] if __jacHasOwn(modulesStore, moduleName) else None;
922
- moduleRecord = existingRecord if existingRecord else {};
923
- moduleRecord.moduleFunctions = moduleFunctions;
924
- moduleRecord.moduleGlobals = moduleGlobals;
925
- moduleRecord.functions = registeredFunctions;
926
- moduleRecord.globals = globalNames;
927
- moduleRecord.defaults = globalsMap;
928
-
929
-
930
- # 1. Ensure the module is written to the registry FIRST.
931
- registry.modules[moduleName] = moduleRecord;
932
-
933
- stateGlobals = registry.state.globals;
934
- if not __jacHasOwn(stateGlobals, moduleName) {
935
- stateGlobals[moduleName] = {};
228
+ def __removeLocalStorage(key: str) -> None {
229
+ storage = globalThis.localStorage;
230
+ if storage {
231
+ storage.removeItem(key);
232
+ }
936
233
  }
937
-
938
- # 2. ONLY THEN, trigger the hydration/lookup process.
939
- __jacEnsureHydration(moduleName);
940
234
  }
941
- }