jac-client 0.2.2__py3-none-any.whl → 0.2.6__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 (202) hide show
  1. jac_client/examples/all-in-one/assets/workers/worker.py +5 -0
  2. jac_client/examples/all-in-one/src/app.jac +841 -0
  3. jac_client/examples/all-in-one/{button.jac → src/button.jac} +1 -1
  4. jac_client/examples/all-in-one/{components → src/components}/button.jac +1 -1
  5. jac_client/examples/asset-serving/css-with-image/{app.jac → src/app.jac} +2 -2
  6. jac_client/examples/asset-serving/image-asset/{app.jac → src/app.jac} +2 -2
  7. jac_client/examples/asset-serving/import-alias/{app.jac → src/app.jac} +7 -7
  8. jac_client/examples/basic/{app.jac → src/app.jac} +2 -2
  9. jac_client/examples/basic-auth/src/app.jac +377 -0
  10. jac_client/examples/basic-auth-with-router/{app.jac → src/app.jac} +18 -18
  11. jac_client/examples/basic-full-stack/{app.jac → src/app.jac} +175 -130
  12. jac_client/examples/css-styling/js-styling/{app.jac → src/app.jac} +6 -6
  13. jac_client/examples/css-styling/material-ui/{app.jac → src/app.jac} +5 -5
  14. jac_client/examples/css-styling/pure-css/{app.jac → src/app.jac} +6 -6
  15. jac_client/examples/css-styling/sass-example/{app.jac → src/app.jac} +6 -6
  16. jac_client/examples/css-styling/styled-components/{app.jac → src/app.jac} +5 -5
  17. jac_client/examples/css-styling/tailwind-example/{app.jac → src/app.jac} +6 -6
  18. jac_client/examples/full-stack-with-auth/{app.jac → src/app.jac} +37 -37
  19. jac_client/examples/little-x/{app.jac → src/app.jac} +27 -32
  20. jac_client/examples/little-x/src/submit-button.jac +16 -0
  21. jac_client/examples/nested-folders/nested-advance/{ButtonRoot.jac → src/ButtonRoot.jac} +1 -1
  22. jac_client/examples/nested-folders/nested-advance/{app.jac → src/app.jac} +1 -1
  23. jac_client/examples/nested-folders/nested-advance/{level1 → src/level1}/ButtonSecondL.jac +1 -1
  24. jac_client/examples/nested-folders/nested-advance/{level1 → src/level1}/Card.jac +1 -1
  25. jac_client/examples/nested-folders/nested-advance/{level1 → src/level1}/level2/ButtonThirdL.jac +1 -1
  26. jac_client/examples/nested-folders/nested-basic/{app.jac → src/app.jac} +2 -2
  27. jac_client/examples/nested-folders/nested-basic/{button.jac → src/button.jac} +1 -1
  28. jac_client/examples/nested-folders/nested-basic/{components → src/components}/button.jac +1 -1
  29. jac_client/examples/ts-support/src/app.jac +35 -0
  30. jac_client/examples/with-router/{app.jac → src/app.jac} +11 -11
  31. jac_client/plugin/cli.jac +547 -0
  32. jac_client/plugin/client.jac +52 -0
  33. jac_client/plugin/client_runtime.cl.jac +38 -0
  34. jac_client/plugin/impl/client.impl.jac +134 -0
  35. jac_client/plugin/impl/client_runtime.impl.jac +177 -0
  36. jac_client/plugin/impl/vite_client_bundle.impl.jac +72 -0
  37. jac_client/plugin/plugin_config.jac +195 -0
  38. jac_client/plugin/src/__init__.jac +20 -0
  39. jac_client/plugin/src/asset_processor.jac +33 -0
  40. jac_client/plugin/src/babel_processor.jac +18 -0
  41. jac_client/plugin/src/compiler.jac +66 -0
  42. jac_client/plugin/src/config_loader.jac +32 -0
  43. jac_client/plugin/src/impl/asset_processor.impl.jac +127 -0
  44. jac_client/plugin/src/impl/babel_processor.impl.jac +84 -0
  45. jac_client/plugin/src/impl/compiler.impl.jac +251 -0
  46. jac_client/plugin/src/impl/config_loader.impl.jac +119 -0
  47. jac_client/plugin/src/impl/import_processor.impl.jac +33 -0
  48. jac_client/plugin/src/impl/jac_to_js.impl.jac +41 -0
  49. jac_client/plugin/src/impl/package_installer.impl.jac +105 -0
  50. jac_client/plugin/src/impl/vite_bundler.impl.jac +513 -0
  51. jac_client/plugin/src/import_processor.jac +19 -0
  52. jac_client/plugin/src/jac_to_js.jac +35 -0
  53. jac_client/plugin/src/package_installer.jac +26 -0
  54. jac_client/plugin/src/vite_bundler.jac +36 -0
  55. jac_client/plugin/vite_client_bundle.jac +31 -0
  56. jac_client/tests/conftest.py +281 -0
  57. jac_client/tests/fixtures/basic-app/app.jac +2 -2
  58. jac_client/tests/fixtures/cl_file/app.cl.jac +2 -2
  59. jac_client/tests/fixtures/client_app_with_antd/app.jac +1 -1
  60. jac_client/tests/fixtures/js_import/app.jac +5 -5
  61. jac_client/tests/fixtures/spawn_test/app.jac +7 -7
  62. jac_client/tests/fixtures/with-ts/app.jac +35 -0
  63. jac_client/tests/test_cli.py +755 -0
  64. jac_client/tests/test_it.py +347 -67
  65. {jac_client-0.2.2.dist-info → jac_client-0.2.6.dist-info}/METADATA +30 -24
  66. jac_client-0.2.6.dist-info/RECORD +74 -0
  67. {jac_client-0.2.2.dist-info → jac_client-0.2.6.dist-info}/WHEEL +2 -1
  68. jac_client-0.2.6.dist-info/entry_points.txt +4 -0
  69. jac_client-0.2.6.dist-info/top_level.txt +1 -0
  70. jac_client/docs/README.md +0 -689
  71. jac_client/docs/advanced-state.md +0 -1265
  72. jac_client/docs/asset-serving/intro.md +0 -209
  73. jac_client/docs/assets/pipe_line-v2.svg +0 -32
  74. jac_client/docs/assets/pipe_line.png +0 -0
  75. jac_client/docs/file-system/app.jac.md +0 -121
  76. jac_client/docs/file-system/backend-frontend.md +0 -217
  77. jac_client/docs/file-system/intro.md +0 -72
  78. jac_client/docs/file-system/nested-imports.md +0 -348
  79. jac_client/docs/guide-example/intro.md +0 -115
  80. jac_client/docs/guide-example/step-01-setup.md +0 -270
  81. jac_client/docs/guide-example/step-02-components.md +0 -416
  82. jac_client/docs/guide-example/step-03-styling.md +0 -478
  83. jac_client/docs/guide-example/step-04-todo-ui.md +0 -477
  84. jac_client/docs/guide-example/step-05-local-state.md +0 -530
  85. jac_client/docs/guide-example/step-06-events.md +0 -749
  86. jac_client/docs/guide-example/step-07-effects.md +0 -468
  87. jac_client/docs/guide-example/step-08-walkers.md +0 -534
  88. jac_client/docs/guide-example/step-09-authentication.md +0 -586
  89. jac_client/docs/guide-example/step-10-routing.md +0 -539
  90. jac_client/docs/guide-example/step-11-final.md +0 -963
  91. jac_client/docs/imports.md +0 -1141
  92. jac_client/docs/lifecycle-hooks.md +0 -773
  93. jac_client/docs/routing.md +0 -659
  94. jac_client/docs/styling/intro.md +0 -249
  95. jac_client/docs/styling/js-styling.md +0 -367
  96. jac_client/docs/styling/material-ui.md +0 -341
  97. jac_client/docs/styling/pure-css.md +0 -299
  98. jac_client/docs/styling/sass.md +0 -403
  99. jac_client/docs/styling/styled-components.md +0 -395
  100. jac_client/docs/styling/tailwind.md +0 -298
  101. jac_client/examples/all-in-one/.babelrc +0 -9
  102. jac_client/examples/all-in-one/README.md +0 -16
  103. jac_client/examples/all-in-one/app.jac +0 -426
  104. jac_client/examples/all-in-one/assets/burger.png +0 -0
  105. jac_client/examples/all-in-one/package.json +0 -29
  106. jac_client/examples/all-in-one/styles.css +0 -26
  107. jac_client/examples/all-in-one/vite.config.js +0 -28
  108. jac_client/examples/asset-serving/css-with-image/.babelrc +0 -9
  109. jac_client/examples/asset-serving/css-with-image/README.md +0 -91
  110. jac_client/examples/asset-serving/css-with-image/assets/burger.png +0 -0
  111. jac_client/examples/asset-serving/css-with-image/package.json +0 -28
  112. jac_client/examples/asset-serving/css-with-image/styles.css +0 -26
  113. jac_client/examples/asset-serving/css-with-image/vite.config.js +0 -28
  114. jac_client/examples/asset-serving/image-asset/.babelrc +0 -9
  115. jac_client/examples/asset-serving/image-asset/README.md +0 -119
  116. jac_client/examples/asset-serving/image-asset/assets/burger.png +0 -0
  117. jac_client/examples/asset-serving/image-asset/package.json +0 -28
  118. jac_client/examples/asset-serving/image-asset/styles.css +0 -26
  119. jac_client/examples/asset-serving/image-asset/vite.config.js +0 -28
  120. jac_client/examples/asset-serving/import-alias/.babelrc +0 -9
  121. jac_client/examples/asset-serving/import-alias/README.md +0 -83
  122. jac_client/examples/asset-serving/import-alias/assets/burger.png +0 -0
  123. jac_client/examples/asset-serving/import-alias/package.json +0 -28
  124. jac_client/examples/asset-serving/import-alias/vite.config.js +0 -28
  125. jac_client/examples/basic/.babelrc +0 -9
  126. jac_client/examples/basic/README.md +0 -16
  127. jac_client/examples/basic/package.json +0 -27
  128. jac_client/examples/basic/vite.config.js +0 -27
  129. jac_client/examples/basic-auth/.babelrc +0 -9
  130. jac_client/examples/basic-auth/README.md +0 -16
  131. jac_client/examples/basic-auth/app.jac +0 -308
  132. jac_client/examples/basic-auth/package.json +0 -27
  133. jac_client/examples/basic-auth/vite.config.js +0 -27
  134. jac_client/examples/basic-auth-with-router/.babelrc +0 -9
  135. jac_client/examples/basic-auth-with-router/README.md +0 -60
  136. jac_client/examples/basic-auth-with-router/package.json +0 -28
  137. jac_client/examples/basic-auth-with-router/vite.config.js +0 -27
  138. jac_client/examples/basic-full-stack/.babelrc +0 -9
  139. jac_client/examples/basic-full-stack/README.md +0 -18
  140. jac_client/examples/basic-full-stack/package.json +0 -28
  141. jac_client/examples/basic-full-stack/vite.config.js +0 -27
  142. jac_client/examples/css-styling/js-styling/.babelrc +0 -9
  143. jac_client/examples/css-styling/js-styling/README.md +0 -183
  144. jac_client/examples/css-styling/js-styling/package.json +0 -28
  145. jac_client/examples/css-styling/js-styling/styles.js +0 -100
  146. jac_client/examples/css-styling/js-styling/vite.config.js +0 -27
  147. jac_client/examples/css-styling/material-ui/.babelrc +0 -9
  148. jac_client/examples/css-styling/material-ui/README.md +0 -16
  149. jac_client/examples/css-styling/material-ui/package.json +0 -32
  150. jac_client/examples/css-styling/material-ui/vite.config.js +0 -27
  151. jac_client/examples/css-styling/pure-css/.babelrc +0 -9
  152. jac_client/examples/css-styling/pure-css/README.md +0 -16
  153. jac_client/examples/css-styling/pure-css/package.json +0 -28
  154. jac_client/examples/css-styling/pure-css/styles.css +0 -111
  155. jac_client/examples/css-styling/pure-css/vite.config.js +0 -27
  156. jac_client/examples/css-styling/sass-example/.babelrc +0 -9
  157. jac_client/examples/css-styling/sass-example/README.md +0 -16
  158. jac_client/examples/css-styling/sass-example/package.json +0 -29
  159. jac_client/examples/css-styling/sass-example/styles.scss +0 -153
  160. jac_client/examples/css-styling/sass-example/vite.config.js +0 -27
  161. jac_client/examples/css-styling/styled-components/.babelrc +0 -9
  162. jac_client/examples/css-styling/styled-components/README.md +0 -16
  163. jac_client/examples/css-styling/styled-components/package.json +0 -29
  164. jac_client/examples/css-styling/styled-components/styled.js +0 -90
  165. jac_client/examples/css-styling/styled-components/vite.config.js +0 -27
  166. jac_client/examples/css-styling/tailwind-example/.babelrc +0 -9
  167. jac_client/examples/css-styling/tailwind-example/README.md +0 -16
  168. jac_client/examples/css-styling/tailwind-example/global.css +0 -1
  169. jac_client/examples/css-styling/tailwind-example/package.json +0 -30
  170. jac_client/examples/css-styling/tailwind-example/vite.config.js +0 -29
  171. jac_client/examples/full-stack-with-auth/.babelrc +0 -9
  172. jac_client/examples/full-stack-with-auth/README.md +0 -16
  173. jac_client/examples/full-stack-with-auth/package.json +0 -28
  174. jac_client/examples/full-stack-with-auth/vite.config.js +0 -29
  175. jac_client/examples/little-x/package.json +0 -23
  176. jac_client/examples/little-x/submit-button.jac +0 -8
  177. jac_client/examples/nested-folders/nested-advance/.babelrc +0 -9
  178. jac_client/examples/nested-folders/nested-advance/README.md +0 -77
  179. jac_client/examples/nested-folders/nested-advance/package.json +0 -29
  180. jac_client/examples/nested-folders/nested-advance/vite.config.js +0 -28
  181. jac_client/examples/nested-folders/nested-basic/.babelrc +0 -9
  182. jac_client/examples/nested-folders/nested-basic/README.md +0 -183
  183. jac_client/examples/nested-folders/nested-basic/app.js +0 -7
  184. jac_client/examples/nested-folders/nested-basic/package.json +0 -28
  185. jac_client/examples/nested-folders/nested-basic/vite.config.js +0 -27
  186. jac_client/examples/with-router/.babelrc +0 -9
  187. jac_client/examples/with-router/README.md +0 -17
  188. jac_client/examples/with-router/package.json +0 -28
  189. jac_client/examples/with-router/vite.config.js +0 -27
  190. jac_client/plugin/cli.py +0 -244
  191. jac_client/plugin/client.py +0 -152
  192. jac_client/plugin/client_runtime.jac +0 -234
  193. jac_client/plugin/vite_client_bundle.py +0 -503
  194. jac_client/tests/fixtures/js_import/utils.js +0 -21
  195. jac_client/tests/fixtures/package-lock.json +0 -329
  196. jac_client/tests/fixtures/package.json +0 -11
  197. jac_client/tests/test_asset_examples.py +0 -322
  198. jac_client/tests/test_cl.py +0 -530
  199. jac_client/tests/test_create_jac_app.py +0 -131
  200. jac_client/tests/test_nested_file.py +0 -374
  201. jac_client-0.2.2.dist-info/RECORD +0 -171
  202. jac_client-0.2.2.dist-info/entry_points.txt +0 -4
@@ -1,749 +0,0 @@
1
- # Step 6: Event Handlers
2
-
3
- > ** Quick Tip:** Each step has two parts. **Part 1** shows you what to build. **Part 2** explains why it works. Want to just build? Skip all Part 2 sections!
4
-
5
- In this step, you'll learn how to handle user interactions like clicks, typing, and key presses to make your app fully interactive!
6
-
7
- ---
8
-
9
- ## Part 1: Building the App
10
-
11
- ### Step 6.1: Handle Input Changes (onChange)
12
-
13
- Let's make the input field track what you type:
14
-
15
- ```jac
16
- cl import from react {useState}
17
-
18
- cl {
19
- def TodoInput(props: any) -> any {
20
- return <div style={{
21
- "display": "flex",
22
- "gap": "8px",
23
- "marginBottom": "16px"
24
- }}>
25
- <input
26
- type="text"
27
- value={props.input}
28
- onChange={lambda e: any -> None {
29
- props.setInput(e.target.value);
30
- }}
31
- placeholder="What needs to be done?"
32
- style={{
33
- "flex": "1",
34
- "padding": "8px",
35
- "border": "1px solid #ddd",
36
- "borderRadius": "4px"
37
- }}
38
- />
39
- <button style={{
40
- "padding": "8px 16px",
41
- "background": "#3b82f6",
42
- "color": "white",
43
- "border": "none",
44
- "borderRadius": "4px",
45
- "cursor": "pointer"
46
- }}>
47
- Add
48
- </button>
49
- </div>;
50
- }
51
-
52
- def app() -> any {
53
- let [input, setInput] = useState("");
54
-
55
- return <div style={{
56
- "maxWidth": "600px",
57
- "margin": "20px auto",
58
- "padding": "20px"
59
- }}>
60
- <h1>My Todos</h1>
61
- <TodoInput input={input} setInput={setInput} />
62
- <p>You typed: "{input}"</p>
63
- </div>;
64
- }
65
- }
66
- ```
67
-
68
- **Try it!** Type in the input field - you'll see the text appear below!
69
-
70
- ### Step 6.2: Handle Button Clicks (onClick)
71
-
72
- Now let's make the "Add" button work:
73
-
74
- ```jac
75
- cl import from react {useState}
76
-
77
- cl {
78
- def TodoInput(props: any) -> any {
79
- return <div style={{
80
- "display": "flex",
81
- "gap": "8px",
82
- "marginBottom": "16px"
83
- }}>
84
- <input
85
- type="text"
86
- value={props.input}
87
- onChange={lambda e: any -> None {
88
- props.setInput(e.target.value);
89
- }}
90
- placeholder="What needs to be done?"
91
- style={{
92
- "flex": "1",
93
- "padding": "8px",
94
- "border": "1px solid #ddd",
95
- "borderRadius": "4px"
96
- }}
97
- />
98
- <button
99
- onClick={lambda e: any -> None {
100
- props.addTodo();
101
- }}
102
- style={{
103
- "padding": "8px 16px",
104
- "background": "#3b82f6",
105
- "color": "white",
106
- "border": "none",
107
- "borderRadius": "4px",
108
- "cursor": "pointer"
109
- }}
110
- >
111
- Add
112
- </button>
113
- </div>;
114
- }
115
-
116
- def app() -> any {
117
- let [todos, setTodos] = useState([]);
118
- let [input, setInput] = useState("");
119
-
120
- # Function to add a new todo
121
- def addTodo() -> None {
122
- if not input.trim() {
123
- return; # Don't add empty todos
124
- }
125
-
126
- let newTodo = {
127
- "text": input.trim(),
128
- "done": false
129
- };
130
-
131
- setTodos(todos.concat([newTodo]));
132
- setInput(""); # Clear input
133
- }
134
-
135
- return <div style={{
136
- "maxWidth": "600px",
137
- "margin": "20px auto",
138
- "padding": "20px"
139
- }}>
140
- <h1>My Todos</h1>
141
- <TodoInput
142
- input={input}
143
- setInput={setInput}
144
- addTodo={addTodo}
145
- />
146
-
147
- # Display todos
148
- <div>
149
- {todos.map(lambda todo: any -> any {
150
- return <div style={{"padding": "8px"}}>
151
- {todo.text}
152
- </div>;
153
- })}
154
- </div>
155
- </div>;
156
- }
157
- }
158
- ```
159
-
160
- **Try it!** Type a todo and click "Add" - it should appear in the list!
161
-
162
- ### Step 6.3: Handle Enter Key (onKeyPress)
163
-
164
- Let's add the ability to press Enter to add a todo:
165
-
166
- ```jac
167
- cl import from react {useState}
168
-
169
- cl {
170
- def TodoInput(props: any) -> any {
171
- return <div style={{
172
- "display": "flex",
173
- "gap": "8px",
174
- "marginBottom": "16px"
175
- }}>
176
- <input
177
- type="text"
178
- value={props.input}
179
- onChange={lambda e: any -> None {
180
- props.setInput(e.target.value);
181
- }}
182
- onKeyPress={lambda e: any -> None {
183
- if e.key == "Enter" {
184
- props.addTodo();
185
- }
186
- }}
187
- placeholder="What needs to be done?"
188
- style={{
189
- "flex": "1",
190
- "padding": "8px",
191
- "border": "1px solid #ddd",
192
- "borderRadius": "4px"
193
- }}
194
- />
195
- <button
196
- onClick={lambda e: any -> None {
197
- props.addTodo();
198
- }}
199
- style={{
200
- "padding": "8px 16px",
201
- "background": "#3b82f6",
202
- "color": "white",
203
- "border": "none",
204
- "borderRadius": "4px",
205
- "cursor": "pointer"
206
- }}
207
- >
208
- Add
209
- </button>
210
- </div>;
211
- }
212
-
213
- # ... rest of code
214
- }
215
- ```
216
-
217
- **Try it!** Now you can press Enter to add todos!
218
-
219
- ### Step 6.4: Toggle and Delete Todos
220
-
221
- Let's add the complete functionality:
222
-
223
- ```jac
224
- cl import from react {useState}
225
-
226
- cl {
227
- # ... (keep TodoInput and TodoFilters)
228
-
229
- def TodoItem(props: any) -> any {
230
- return <div style={{
231
- "display": "flex",
232
- "alignItems": "center",
233
- "gap": "10px",
234
- "padding": "10px",
235
- "borderBottom": "1px solid #e5e7eb"
236
- }}>
237
- <input
238
- type="checkbox"
239
- checked={props.done}
240
- onChange={lambda e: any -> None {
241
- props.toggleTodo(props.id);
242
- }}
243
- style={{"cursor": "pointer"}}
244
- />
245
- <span style={{
246
- "flex": "1",
247
- "textDecoration": ("line-through" if props.done else "none"),
248
- "color": ("#999" if props.done else "#000")
249
- }}>
250
- {props.text}
251
- </span>
252
- <button
253
- onClick={lambda e: any -> None {
254
- props.deleteTodo(props.id);
255
- }}
256
- style={{
257
- "padding": "4px 8px",
258
- "background": "#ef4444",
259
- "color": "white",
260
- "border": "none",
261
- "borderRadius": "4px",
262
- "cursor": "pointer",
263
- "fontSize": "12px"
264
- }}
265
- >
266
- Delete
267
- </button>
268
- </div>;
269
- }
270
-
271
- def app() -> any {
272
- let [todos, setTodos] = useState([]);
273
- let [input, setInput] = useState("");
274
-
275
- # Add todo
276
- def addTodo() -> None {
277
- if not input.trim() {
278
- return;
279
- }
280
-
281
- let newTodo = {
282
- "id": Date.now(), # Use timestamp as unique ID
283
- "text": input.trim(),
284
- "done": false
285
- };
286
-
287
- setTodos(todos.concat([newTodo]));
288
- setInput("");
289
- }
290
-
291
- # Toggle todo
292
- def toggleTodo(id: any) -> None {
293
- setTodos(todos.map(lambda todo: any -> any {
294
- if todo["id"] == id {
295
- return {
296
- "id": todo["id"],
297
- "text": todo["text"],
298
- "done": not todo["done"]
299
- };
300
- }
301
- return todo;
302
- }));
303
- }
304
-
305
- # Delete todo
306
- def deleteTodo(id: any) -> None {
307
- setTodos(todos.filter(lambda todo: any -> bool {
308
- return todo["id"] != id;
309
- }));
310
- }
311
-
312
- return <div style={{
313
- "maxWidth": "600px",
314
- "margin": "20px auto",
315
- "padding": "20px"
316
- }}>
317
- <h1>My Todos</h1>
318
- <TodoInput
319
- input={input}
320
- setInput={setInput}
321
- addTodo={addTodo}
322
- />
323
-
324
- <div>
325
- {todos.map(lambda todo: any -> any {
326
- return <TodoItem
327
- key={todo["id"]}
328
- id={todo["id"]}
329
- text={todo["text"]}
330
- done={todo["done"]}
331
- toggleTodo={toggleTodo}
332
- deleteTodo={deleteTodo}
333
- />;
334
- })}
335
- </div>
336
- </div>;
337
- }
338
- }
339
- ```
340
-
341
- **Try it!** You can now:
342
- - Add todos
343
- - Check/uncheck them
344
- - Delete them
345
-
346
- ### Step 6.5: Add Filter Functionality
347
-
348
- Final step - make the filter buttons work:
349
-
350
- ```jac
351
- cl import from react {useState}
352
-
353
- cl {
354
- def TodoFilters(props: any) -> any {
355
- return <div style={{
356
- "display": "flex",
357
- "gap": "8px",
358
- "marginBottom": "16px"
359
- }}>
360
- <button
361
- onClick={lambda e: any -> None {
362
- props.setFilter("all");
363
- }}
364
- style={{
365
- "padding": "6px 12px",
366
- "background": ("#3b82f6" if props.filter == "all" else "#e5e7eb"),
367
- "color": ("#ffffff" if props.filter == "all" else "#000000"),
368
- "border": "none",
369
- "borderRadius": "4px",
370
- "cursor": "pointer"
371
- }}
372
- >
373
- All
374
- </button>
375
- <button
376
- onClick={lambda e: any -> None {
377
- props.setFilter("active");
378
- }}
379
- style={{
380
- "padding": "6px 12px",
381
- "background": ("#3b82f6" if props.filter == "active" else "#e5e7eb"),
382
- "color": ("#ffffff" if props.filter == "active" else "#000000"),
383
- "border": "none",
384
- "borderRadius": "4px",
385
- "cursor": "pointer"
386
- }}
387
- >
388
- Active
389
- </button>
390
- <button
391
- onClick={lambda e: any -> None {
392
- props.setFilter("completed");
393
- }}
394
- style={{
395
- "padding": "6px 12px",
396
- "background": ("#3b82f6" if props.filter == "completed" else "#e5e7eb"),
397
- "color": ("#ffffff" if props.filter == "completed" else "#000000"),
398
- "border": "none",
399
- "borderRadius": "4px",
400
- "cursor": "pointer"
401
- }}
402
- >
403
- Completed
404
- </button>
405
- </div>;
406
- }
407
-
408
- def app() -> any {
409
- let [todos, setTodos] = useState([]);
410
- let [input, setInput] = useState("");
411
- let [filter, setFilter] = useState("all");
412
-
413
- # ... (keep addTodo, toggleTodo, deleteTodo functions)
414
-
415
- # Filter todos based on current filter
416
- def getFilteredTodos() -> list {
417
- if filter == "active" {
418
- return todos.filter(lambda todo: any -> bool {
419
- return not todo["done"];
420
- });
421
- } elif filter == "completed" {
422
- return todos.filter(lambda todo: any -> bool {
423
- return todo["done"];
424
- });
425
- }
426
- return todos;
427
- }
428
-
429
- filteredTodos = getFilteredTodos();
430
-
431
- return <div style={{
432
- "maxWidth": "600px",
433
- "margin": "20px auto",
434
- "padding": "20px"
435
- }}>
436
- <h1>My Todos</h1>
437
- <TodoInput input={input} setInput={setInput} addTodo={addTodo} />
438
- <TodoFilters filter={filter} setFilter={setFilter} />
439
-
440
- <div>
441
- {filteredTodos.map(lambda todo: any -> any {
442
- return <TodoItem
443
- key={todo["id"]}
444
- id={todo["id"]}
445
- text={todo["text"]}
446
- done={todo["done"]}
447
- toggleTodo={toggleTodo}
448
- deleteTodo={deleteTodo}
449
- />;
450
- })}
451
- </div>
452
- </div>;
453
- }
454
- }
455
- ```
456
-
457
- **Try it!** Now you have a fully functional todo app!
458
-
459
- ---
460
-
461
- **⏭ Want to skip the theory?** Jump to [Step 7: Effects](./step-07-effects.md)
462
-
463
- ---
464
-
465
- ## Part 2: Understanding the Concepts
466
-
467
- ### What are Event Handlers?
468
-
469
- Event handlers are functions that run when something happens (user clicks, types, etc.).
470
-
471
- **Common events:**
472
- - `onClick` - User clicks an element
473
- - `onChange` - Input value changes
474
- - `onKeyPress` - User presses a key
475
- - `onSubmit` - Form is submitted
476
- - `onFocus` - Element gains focus
477
- - `onBlur` - Element loses focus
478
-
479
- ### Event Handler Syntax
480
-
481
- ```jac
482
- <button onClick={lambda e: any -> None {
483
- # Code runs when button is clicked
484
- console.log("Clicked!");
485
- }}>
486
- Click me
487
- </button>
488
- ```
489
-
490
- **Breakdown:**
491
- - `onClick={}` - The event attribute
492
- - `lambda e: any -> None { }` - Anonymous function
493
- - `e` - Event object (contains info about the event)
494
-
495
- ### The Event Object (`e`)
496
-
497
- ```jac
498
- onChange={lambda e: any -> None {
499
- console.log(e.target); # The element that triggered the event
500
- console.log(e.target.value); # For inputs: the current value
501
- console.log(e.key); # For key events: which key was pressed
502
- }}
503
- ```
504
-
505
- **Common properties:**
506
- - `e.target` - The element that triggered the event
507
- - `e.target.value` - Current value (for inputs)
508
- - `e.key` - Which key was pressed
509
- - `e.preventDefault()` - Prevent default behavior
510
-
511
- ### Passing Functions as Props
512
-
513
- You can pass functions down to child components:
514
-
515
- ```jac
516
- def Parent() -> any {
517
- def handleClick() -> None {
518
- console.log("Clicked!");
519
- }
520
-
521
- # Pass function to child
522
- return <Child onClick={handleClick} />;
523
- }
524
-
525
- def Child(props: any) -> any {
526
- # Call parent's function
527
- return <button onClick={props.onClick}>
528
- Click me
529
- </button>;
530
- }
531
- ```
532
-
533
- This lets children trigger parent behavior!
534
-
535
- ### Updating State in Event Handlers
536
-
537
- ```jac
538
- def app() -> any {
539
- let [count, setCount] = useState(0);
540
-
541
- def increment() -> None {
542
- setCount(count + 1); # Update state
543
- }
544
-
545
- return <button onClick={increment}>
546
- Count: {count}
547
- </button>;
548
- }
549
- ```
550
-
551
- When state updates, React re-renders the component with the new value!
552
-
553
- ### Array Methods for State Updates
554
-
555
- **`.concat()` - Add items**
556
-
557
- ```jac
558
- # Correct way to add
559
- setTodos(todos.concat([newTodo]));
560
-
561
- # Wrong (modifies original)
562
- todos.push(newTodo);
563
- setTodos(todos);
564
- ```
565
-
566
- **`.map()` - Update items**
567
-
568
- ```jac
569
- # Toggle a todo
570
- setTodos(todos.map(lambda todo: any -> any {
571
- if todo["id"] == targetId {
572
- return {"id": todo["id"], "done": not todo["done"]};
573
- }
574
- return todo;
575
- }));
576
- ```
577
-
578
- **`.filter()` - Remove items**
579
-
580
- ```jac
581
- # Delete a todo
582
- setTodos(todos.filter(lambda todo: any -> bool {
583
- return todo["id"] != targetId;
584
- }));
585
- ```
586
-
587
- ### Inline vs Named Functions
588
-
589
- **Inline (good for simple logic):**
590
-
591
- ```jac
592
- <button onClick={lambda e: any -> None {
593
- setCount(count + 1);
594
- }}>
595
- Click
596
- </button>
597
- ```
598
-
599
- **Named (good for complex logic):**
600
-
601
- ```jac
602
- def handleClick() -> None {
603
- if count < 10 {
604
- setCount(count + 1);
605
- } else {
606
- alert("Max reached!");
607
- }
608
- }
609
-
610
- <button onClick={handleClick}>Click</button>
611
- ```
612
-
613
- ### Event Handler Common Patterns
614
-
615
- **Pattern 1: Toggle Boolean**
616
-
617
- ```jac
618
- let [isOpen, setIsOpen] = useState(false);
619
-
620
- def toggle() -> None {
621
- setIsOpen(not isOpen);
622
- }
623
-
624
- <button onClick={toggle}>Toggle</button>
625
- ```
626
-
627
- **Pattern 2: Update Input**
628
-
629
- ```jac
630
- let [text, setText] = useState("");
631
-
632
- <input
633
- value={text}
634
- onChange={lambda e: any -> None {
635
- setText(e.target.value);
636
- }}
637
- />
638
- ```
639
-
640
- **Pattern 3: Add to List**
641
-
642
- ```jac
643
- let [items, setItems] = useState([]);
644
-
645
- def addItem(newItem: any) -> None {
646
- setItems(items.concat([newItem]));
647
- }
648
- ```
649
-
650
- **Pattern 4: Remove from List**
651
-
652
- ```jac
653
- def removeItem(id: any) -> None {
654
- setItems(items.filter(lambda item: any -> bool {
655
- return item.id != id;
656
- }));
657
- }
658
- ```
659
-
660
- ---
661
-
662
- ## What You've Learned
663
-
664
- - What event handlers are
665
- - Common events (onClick, onChange, onKeyPress)
666
- - Event handler syntax with lambda functions
667
- - The event object (`e`)
668
- - Passing functions as props
669
- - Updating state in event handlers
670
- - Array methods (concat, map, filter)
671
- - Inline vs named functions
672
-
673
- ---
674
-
675
- ## Common Issues
676
-
677
- ### Issue: Event handler not firing
678
-
679
- **Check:**
680
- - Did you use `onClick` not `onclick`? (capital C)
681
- - Did you pass a function? `onClick={myFunction}` not `onClick={myFunction()}`
682
-
683
- ### Issue: Input not updating
684
-
685
- **Check:**
686
- - Did you add both `value` and `onChange`?
687
- - Is `onChange` calling the state setter?
688
-
689
- ```jac
690
- # Correct
691
- <input
692
- value={text}
693
- onChange={lambda e: any -> None {
694
- setText(e.target.value);
695
- }}
696
- />
697
-
698
- # Missing onChange
699
- <input value={text} />
700
- ```
701
-
702
- ### Issue: State not updating
703
-
704
- **Check:** Are you creating a new array/object?
705
-
706
- ```jac
707
- # Wrong (modifying original)
708
- todos.push(newTodo);
709
- setTodos(todos);
710
-
711
- # Correct (creating new array)
712
- setTodos(todos.concat([newTodo]));
713
- ```
714
-
715
- ---
716
-
717
- ## Quick Exercise
718
-
719
- Try adding a "Clear All" button:
720
-
721
- ```jac
722
- def clearAll() -> None {
723
- setTodos([]);
724
- }
725
-
726
- <button onClick={clearAll}>Clear All</button>
727
- ```
728
-
729
- And a "Clear Completed" button:
730
-
731
- ```jac
732
- def clearCompleted() -> None {
733
- setTodos(todos.filter(lambda todo: any -> bool {
734
- return not todo["done"];
735
- }));
736
- }
737
-
738
- <button onClick={clearCompleted}>Clear Completed</button>
739
- ```
740
-
741
- ---
742
-
743
- ## Next Step
744
-
745
- Excellent! Your app is now fully interactive with local state. But when you refresh the page, all your todos disappear!
746
-
747
- In the next step, we'll use **useEffect** to load data when the app starts!
748
-
749
- **[Continue to Step 7: Effects](./step-07-effects.md)**