kopular 1.1.3 → 1.1.4
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/GUIDE.md +66 -5
- package/package.json +1 -1
package/GUIDE.md
CHANGED
|
@@ -34,6 +34,21 @@ both scripts). `ks check src/app.ks` type-checks without building, if you just w
|
|
|
34
34
|
for an enum subject, by naming every member). For a `bool`, use `if`/`else` instead —
|
|
35
35
|
there's no equivalent shorthand.
|
|
36
36
|
- Lambdas need explicit parameter types: `(number x) => x * 2`.
|
|
37
|
+
- Operators: `+ - * / %`, `== != < > <= >=`, `&& || !` — `!` is a real unary operator on a
|
|
38
|
+
`bool` (`if (!done)`, `bool flipped = !done;`). Precedence, low → high: `=` → `||` →
|
|
39
|
+
`&&` → `==` `!=` → `<` `>` `<=` `>=` → `+` `-` → `*` `/` `%` → unary `-` `!` →
|
|
40
|
+
`.member`/`(call)`/`[index]`. **No `++`/`--`/`+=`** — write `i = i + 1;`.
|
|
41
|
+
- **number → string**: `+` with a string on either side (`"count: " + n`), or interpolation
|
|
42
|
+
(`$"{n} items left"`). There is no `.ToString()` — calling it is `KS4085`.
|
|
43
|
+
- `try { } catch (string e) { } finally { }` and `throw "message";` all exist — one `catch`
|
|
44
|
+
per `try`, and its parameter type is your choice (it is *not* checked against what was
|
|
45
|
+
actually thrown).
|
|
46
|
+
- **Reserved words**: naming a local or parameter one of these is a parse error, not
|
|
47
|
+
shadowing — `raw`, `state`, `task`, `from`, `as`, `get`, `set`, `in`, `base` and `match`
|
|
48
|
+
are the ones easy to pick by accident. Full list: `using extern raw template styles from
|
|
49
|
+
as const class interface enum constructor public private protected static virtual override
|
|
50
|
+
get set return if else while for foreach in break continue match this base new void true
|
|
51
|
+
false null task state async await try catch finally throw`.
|
|
37
52
|
- Nullable: `string? name` — a nullable field forces `if (x != null) { ... }` before use;
|
|
38
53
|
narrowing is scoped to that `if` block, not reachability-based (an early
|
|
39
54
|
`if (x == null) { return; }` does NOT narrow `x` afterward — wrap the rest in
|
|
@@ -126,6 +141,12 @@ public override VElement Render() {
|
|
|
126
141
|
escape hatch for anything without a named field). Both styles compile to the same thing and
|
|
127
142
|
mix freely across a project.
|
|
128
143
|
|
|
144
|
+
**Conditional value**: there's no ternary, so to pick between two elements use `If()` (from
|
|
145
|
+
`kopular/directives`, already covered by `using "kopular"`):
|
|
146
|
+
```ks
|
|
147
|
+
root.AppendChild(If(this.On.Value, () => this.Yes(), () => this.No()));
|
|
148
|
+
```
|
|
149
|
+
|
|
129
150
|
**Error boundary**: override `RenderError(string message)` to show a fallback instead of an
|
|
130
151
|
uncaught crash if `Render()` throws.
|
|
131
152
|
|
|
@@ -166,7 +187,7 @@ Router nav = new Router(new NotFoundPage()); // fallback page, required
|
|
|
166
187
|
nav.AddRoute("/", new HomePage(nav));
|
|
167
188
|
nav.AddRoute("/dogs/:id", new DogDetailPage(nav)); // nav.Param inside that page
|
|
168
189
|
nav.SetGuard("/login", (string path) => {
|
|
169
|
-
if (path == "/admin") { return
|
|
190
|
+
if (path == "/admin") { return auth.LoggedIn.Value; } // .Value — a state<bool> is not a bool
|
|
170
191
|
return true;
|
|
171
192
|
});
|
|
172
193
|
nav.Navigate("/dogs/1");
|
|
@@ -176,8 +197,12 @@ nav.Mount(document.body);
|
|
|
176
197
|
navigating away and back.
|
|
177
198
|
- `nav.Param` — the first `:name` segment, a plain `string` (not `state<T>`; the outlet
|
|
178
199
|
re-renders on every navigation already).
|
|
179
|
-
- `SetGuard(redirectPath, guard)`: one guard for the whole router
|
|
180
|
-
|
|
200
|
+
- `SetGuard(redirectPath, guard)`: one guard for the whole router, called with the target
|
|
201
|
+
path before **every** navigation — in-app, a direct load/refresh, and back/forward alike,
|
|
202
|
+
so a guarded page is covered however it's reached. Returning `false` redirects to
|
|
203
|
+
`redirectPath` (via `pushState`, so the URL changes too). `guard` itself decides which
|
|
204
|
+
paths it cares about, and `redirectPath` is never guard-checked, so pick one the guard
|
|
205
|
+
always allows. Defaults to always-allow.
|
|
181
206
|
- `AddLazyRoute(path, loader)` for real code-splitting — see "Lazy routes" below.
|
|
182
207
|
- Every deploy target needs its own SPA fallback (serve `index.html` for any route with no
|
|
183
208
|
matching file) — a plain HTTP limitation, not a Kopular one.
|
|
@@ -203,8 +228,39 @@ Response r = await Http.Get("/api/dogs");
|
|
|
203
228
|
if (r.ok) { string body = await r.text(); }
|
|
204
229
|
await Http.Post("/api/dogs", "{\"name\":\"Rex\"}");
|
|
205
230
|
```
|
|
206
|
-
No typed JSON deserialization built in — describe the shape as `extern class` and
|
|
207
|
-
|
|
231
|
+
No typed JSON deserialization built in — describe the shape as an `extern class` and bind
|
|
232
|
+
`JSON.parse`. Note the `;` after an `extern class` body, and never write `async` on an
|
|
233
|
+
`extern` signature (give it a `task<T>` return type instead):
|
|
234
|
+
|
|
235
|
+
```ks
|
|
236
|
+
extern class User {
|
|
237
|
+
number id { get; }
|
|
238
|
+
string name { get; }
|
|
239
|
+
string email { get; }
|
|
240
|
+
};
|
|
241
|
+
extern User[] ParseUsers(string json) as "JSON.parse";
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
A complete load-with-states method. `async` methods return `task` or `task<T>`, never a
|
|
245
|
+
bare value type:
|
|
246
|
+
|
|
247
|
+
```ks
|
|
248
|
+
public async task Load() {
|
|
249
|
+
this.Status.Value = "loading";
|
|
250
|
+
try {
|
|
251
|
+
Response r = await Http.Get("/api/users");
|
|
252
|
+
if (r.ok) {
|
|
253
|
+
string body = await r.text();
|
|
254
|
+
this.Users.Value = ParseUsers(body);
|
|
255
|
+
this.Status.Value = "ok";
|
|
256
|
+
} else {
|
|
257
|
+
this.Status.Value = "error";
|
|
258
|
+
}
|
|
259
|
+
} catch (string e) {
|
|
260
|
+
this.Status.Value = "error";
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
```
|
|
208
264
|
|
|
209
265
|
## Async: `Delay`, `Computed`, `Resource<T>`
|
|
210
266
|
|
|
@@ -217,6 +273,8 @@ Computed2<number, number, number> total = new Computed2<number, number, number>(
|
|
|
217
273
|
total.Value.Subscribe((number v) => this.Update()); // total.Value is itself state<number>
|
|
218
274
|
|
|
219
275
|
Resource<Response> r = new Resource<Response>(Http.Get(url)); // task already in flight
|
|
276
|
+
// Data is state<T?> and Error is state<string?> — both null until the task settles, so
|
|
277
|
+
// whatever these arms call has to accept the nullable type (or null-check first).
|
|
220
278
|
match r.Status.Value {
|
|
221
279
|
AsyncStatus.Loading => BuildSpinner(),
|
|
222
280
|
AsyncStatus.Success => BuildContent(r.Data.Value),
|
|
@@ -228,6 +286,7 @@ match r.Status.Value {
|
|
|
228
286
|
|
|
229
287
|
```ks
|
|
230
288
|
class Widget : Component {
|
|
289
|
+
constructor() : base() { } // required: 'styles from' has to have one to initialize from
|
|
231
290
|
template from "./widget.html";
|
|
232
291
|
styles from "./widget.css";
|
|
233
292
|
}
|
|
@@ -268,6 +327,8 @@ Real jsdom, real compile, real DOM assertions — not a mock.
|
|
|
268
327
|
|
|
269
328
|
- Forgetting `this.` on a member reference inside a lambda — it's a real undefined-identifier
|
|
270
329
|
error, not automatic.
|
|
330
|
+
- A `state<T>` used where a plain `T` is expected needs `.Value` — including a `bool` one
|
|
331
|
+
returned from a router guard or passed to `If()` (`KS4038`).
|
|
271
332
|
- `if (x == null) { return; } use(x);` does NOT narrow `x` — see "Nullable" above.
|
|
272
333
|
- A two-way binding target must be a field path (`Field`, `this.Field`, `Field.Value`), never
|
|
273
334
|
a method call.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "kopular",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.4",
|
|
4
4
|
"description": "Kopular: a small component framework for KopScript — components, reactive state, constructor-injected services, routing, real compiled templates, and HTTP, with no DI container",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|