kopular 0.13.0 → 0.14.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/LLM.md +134 -72
- package/README.md +95 -68
- package/package.json +2 -1
- package/src/component.js +14 -6
- package/src/component.js.map +1 -1
- package/src/component.ks +37 -15
- package/src/directives.js +1 -0
- package/src/directives.js.map +1 -1
- package/src/directives.ks +5 -4
- package/src/dom.js.map +1 -1
- package/src/dom.ks +18 -1
- package/src/forms.js.map +1 -1
- package/src/forms.ks +1 -1
- package/src/router.js +9 -4
- package/src/router.js.map +1 -1
- package/src/router.ks +23 -10
- package/src/vdom.js +136 -0
- package/src/vdom.js.map +1 -0
- package/src/vdom.ks +221 -0
- package/src/velement.js +39 -0
- package/src/velement.js.map +1 -0
- package/src/velement.ks +95 -0
package/LLM.md
CHANGED
|
@@ -1,15 +1,18 @@
|
|
|
1
1
|
# Kopular — LLM reference
|
|
2
2
|
|
|
3
3
|
Complete reference for generating correct Kopular code. This is a spec, not a tutorial —
|
|
4
|
-
see `README.md` for narrative/rationale. Kopular is
|
|
5
|
-
them. For the host language, see KopScript's own `LLM.md` in the `
|
|
4
|
+
see `README.md` for narrative/rationale. Kopular is 10 files total; this covers all of
|
|
5
|
+
them. For the host language, see KopScript's own `LLM.md` in the `KopScript` repo (or its
|
|
6
6
|
published `LLM.md` on the `kopscript` npm package) — that reference is a prerequisite,
|
|
7
7
|
not repeated here.
|
|
8
8
|
|
|
9
9
|
Published as npm `kopular`. Entry points: `kopular` / `kopular/component` (Component),
|
|
10
|
+
`kopular/velement` (VElement — what `Render()` returns; see "Component" below),
|
|
10
11
|
`kopular/router` (Router), `kopular/dom` (ambient DOM bindings), `kopular/directives`
|
|
11
12
|
(If), `kopular/http` (Http), `kopular/forms` (FormField, Validators), `kopular/testing`
|
|
12
|
-
(runKopularApp, runKopularFixture — see below).
|
|
13
|
+
(runKopularApp, runKopularFixture — see below). `kopular/vdom` (the diff/patch engine
|
|
14
|
+
behind `Update()`) is internal — nothing outside `component.ks` needs to import it
|
|
15
|
+
directly. Also ships a bin, `kp` — `npx kp new
|
|
13
16
|
<dir>` scaffolds a new project (the `extern` bindings below, plus the vendor/serve
|
|
14
17
|
scripts needed to run in a browser) rather than requiring it be reconstructed by hand;
|
|
15
18
|
prefer it over hand-writing the section below for a new project. Templates (`template
|
|
@@ -22,9 +25,25 @@ there is no `kopular/template` entry point to import.
|
|
|
22
25
|
included) always goes through `extern`, re-describing exactly the members you use:
|
|
23
26
|
|
|
24
27
|
```ks
|
|
28
|
+
extern class VElement {
|
|
29
|
+
static VElement Create(string tag);
|
|
30
|
+
string TextContent { get; set; }
|
|
31
|
+
string ClassName { get; set; }
|
|
32
|
+
string Id { get; set; }
|
|
33
|
+
string Value { get; set; }
|
|
34
|
+
string RawHtml { get; set; }
|
|
35
|
+
(Event) => void OnClick { get; set; }
|
|
36
|
+
(Event) => void OnInput { get; set; }
|
|
37
|
+
(Event) => void OnBlur { get; set; }
|
|
38
|
+
(Event) => void OnChange { get; set; }
|
|
39
|
+
void AppendChild(VElement child);
|
|
40
|
+
void SetAttr(string name, string value);
|
|
41
|
+
} from "kopular/velement";
|
|
42
|
+
|
|
25
43
|
extern class Component {
|
|
26
44
|
constructor();
|
|
27
|
-
virtual
|
|
45
|
+
virtual VElement Render(); // `virtual` here is what lets your subclass `override` it
|
|
46
|
+
virtual void AfterRender(Element root); // see "Component" below
|
|
28
47
|
void Mount(Element parent);
|
|
29
48
|
void Update();
|
|
30
49
|
} from "kopular/component";
|
|
@@ -36,7 +55,7 @@ extern class Router {
|
|
|
36
55
|
void Mount(Element parent);
|
|
37
56
|
} from "kopular/router";
|
|
38
57
|
|
|
39
|
-
extern
|
|
58
|
+
extern VElement If(bool condition, () => VElement whenTrue, () => VElement whenFalse) from "kopular/directives";
|
|
40
59
|
|
|
41
60
|
extern class Response {
|
|
42
61
|
bool ok { get; }
|
|
@@ -86,7 +105,7 @@ Kopular's own copy in `dom.ks` isn't reachable across the package boundary; rede
|
|
|
86
105
|
handful of members you actually use. See KopularDemo's `src/kopular_bindings.ks` for a
|
|
87
106
|
complete, real example of both.
|
|
88
107
|
|
|
89
|
-
## `Component` (`component.ks`)
|
|
108
|
+
## `Component` (`component.ks`, `velement.ks`, `vdom.ks`) — real vdom diffing
|
|
90
109
|
|
|
91
110
|
```ks
|
|
92
111
|
class MyWidget : Component {
|
|
@@ -96,46 +115,68 @@ class MyWidget : Component {
|
|
|
96
115
|
this.count = 0;
|
|
97
116
|
}
|
|
98
117
|
|
|
99
|
-
public override
|
|
100
|
-
|
|
101
|
-
el.
|
|
118
|
+
public override VElement Render() {
|
|
119
|
+
VElement el = VElement.Create("div");
|
|
120
|
+
el.TextContent = "Count: " + this.count;
|
|
102
121
|
return el;
|
|
103
122
|
}
|
|
104
123
|
|
|
105
124
|
public void Bump() {
|
|
106
125
|
this.count = this.count + 1;
|
|
107
|
-
this.Update(); // re-runs Render(),
|
|
126
|
+
this.Update(); // re-runs Render(), diffs it against the previous tree, patches real DOM
|
|
108
127
|
}
|
|
109
128
|
}
|
|
110
129
|
|
|
111
130
|
MyWidget w = new MyWidget();
|
|
112
|
-
w.Mount(document.body); // first Render() + append
|
|
113
|
-
w.Bump(); // re-render
|
|
131
|
+
w.Mount(document.body); // first Render() + materialize + append
|
|
132
|
+
w.Bump(); // re-render + diff + patch
|
|
114
133
|
```
|
|
115
134
|
|
|
116
|
-
- `Render()`: `virtual`, override it to
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
- `
|
|
122
|
-
|
|
135
|
+
- `Render()`: `virtual`, override it to describe the current state as a `VElement` tree —
|
|
136
|
+
a lightweight description of a DOM element (`kopular/velement`), not a real one. Called
|
|
137
|
+
by both `Mount()` and `Update()`. Provide it as a real markup file via KopScript's
|
|
138
|
+
`template from "./x.html";` (see "Templates" below) instead of hand-writing it — both
|
|
139
|
+
produce the exact same method; Kopular needed no framework code changes to support this.
|
|
140
|
+
- `VElement`: `Tag`, `TextContent`/`ClassName`/`Id`/`Value` (direct fields — `Value` is the
|
|
141
|
+
one that must be a live DOM *property*, not an attribute: `SetAttr("value", x)` sets the
|
|
142
|
+
default value, not the current one), `RawHtml` (an opaque, undiffed leaf — set instead of
|
|
143
|
+
`TextContent`/children, for a raw-HTML-then-wire-handlers pattern), the four fixed named
|
|
144
|
+
event fields `OnClick`/`OnInput`/`OnBlur`/`OnChange` (each a real no-op by default, never
|
|
145
|
+
null — no nullable function type to fall back on), `AppendChild(child)`, and
|
|
146
|
+
`SetAttr(name, value)` (the escape hatch for any other real HTML attribute — `href`,
|
|
147
|
+
`src`, `alt`, `placeholder`, ...; never `Value`, see above). `Id` doubles as a stable key
|
|
148
|
+
for list-child reconciliation — see `PatchChildren` below.
|
|
149
|
+
- `Mount(parent)`: calls `Render()` once, materializes the returned tree into real DOM
|
|
150
|
+
(`Materialize` in `vdom.ks`), appends it to `parent`, then calls `AfterRender(root)`.
|
|
123
151
|
- `Update()` (protected — called from within the component, not externally): calls
|
|
124
|
-
`Render()` again and `
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
`
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
152
|
+
`Render()` again and `Patch`es the new tree against the previous one (`vdom.ks`), reusing
|
|
153
|
+
a real DOM node wherever a node's tag stays the same instead of rebuilding it — sibling
|
|
154
|
+
nodes untouched by the change keep their exact identity (`===`), not just their content.
|
|
155
|
+
A list child without a stable `Id` still ends up correct after a reorder, but isn't
|
|
156
|
+
guaranteed to keep its own real node (see `PatchChildren`'s keyed-vs-positional matching
|
|
157
|
+
in `vdom.ks`). Then calls `AfterRender(root)`.
|
|
158
|
+
- `AfterRender(root)`: `virtual`, a no-op by default, called at the end of both `Mount()`
|
|
159
|
+
and `Update()` with the real, now-materialized/patched root `Element`. For a component
|
|
160
|
+
that needs to do further imperative work against its own real DOM — most commonly,
|
|
161
|
+
mounting a *nested* `Component` into a slot `Render()`'s tree just describes as data,
|
|
162
|
+
since a live mounted child isn't something `VElement` can express (see `Router`'s own
|
|
163
|
+
use of this to mount its matched page into the outlet, and "Dependency injection" below
|
|
164
|
+
for `RoutedApp` doing the same with `Router` itself).
|
|
165
|
+
- **Known limitation**: reconciliation is per-`Component`, not across nested ones — if a
|
|
166
|
+
*parent* component's own `Render()` output changes shape around a slot where a nested
|
|
167
|
+
Component was `Mount()`ed via `AfterRender`, that nested Component isn't automatically
|
|
168
|
+
re-`Mount()`ed or torn down as part of the parent's diff. Compose independent components
|
|
169
|
+
into a stable, unchanging slot (see `Router`'s own outlet, which never changes tag or
|
|
170
|
+
position) to avoid this rather than nesting components whose *surrounding* structure
|
|
171
|
+
also changes.
|
|
131
172
|
- **`Update()` before `Mount()` is a safe no-op**, not an error. This matters for two
|
|
132
173
|
sibling `Component`s (e.g. two `Router` pages) that share one injected service's
|
|
133
174
|
`state<T>` and both `Subscribe()` it — every route's page is constructed eagerly (see
|
|
134
175
|
`Router.AddRoute`), so at any given time most of them are constructed but never
|
|
135
176
|
`Mount()`ed. Changing that shared state fires `Subscribe` on all of them, including the
|
|
136
|
-
ones that aren't the currently-routed page — `Update()` just skips the render/
|
|
137
|
-
|
|
138
|
-
|
|
177
|
+
ones that aren't the currently-routed page — `Update()` just skips the render/patch for
|
|
178
|
+
those, since `Mount()` will run a fresh `Render()` anyway whenever one of them actually
|
|
179
|
+
becomes routed.
|
|
139
180
|
- `RenderError(string message)`: `virtual`, called by `Mount()`/`Update()` (via a private
|
|
140
181
|
`SafeRender()` wrapper) when `Render()` throws, instead of letting the exception
|
|
141
182
|
propagate uncaught and crash whatever triggered the render (a click handler, a `Router`
|
|
@@ -143,9 +184,9 @@ w.Bump(); // re-render
|
|
|
143
184
|
opt-in error recovery**; a `Component` that never overrides `RenderError` behaves
|
|
144
185
|
exactly as before this existed. Override it to show a fallback UI instead:
|
|
145
186
|
```ks
|
|
146
|
-
protected override
|
|
147
|
-
|
|
148
|
-
el.
|
|
187
|
+
protected override VElement RenderError(string message) {
|
|
188
|
+
VElement el = VElement.Create("div");
|
|
189
|
+
el.TextContent = "Something went wrong: " + message;
|
|
149
190
|
return el;
|
|
150
191
|
}
|
|
151
192
|
```
|
|
@@ -170,16 +211,20 @@ class Counter : Component {
|
|
|
170
211
|
|
|
171
212
|
- `{{ expr }}` interpolation, `(event)="stmt"`, `[prop]="expr"`, `*if="expr"`,
|
|
172
213
|
`*for="Type varName of expr"` — all real KopScript, checked at compile time, desugared
|
|
173
|
-
to the exact same `
|
|
174
|
-
|
|
214
|
+
to the exact same `VElement.Create`/`.AppendChild`/`.TextContent`/named-event-field
|
|
215
|
+
calls a hand-written `Render()` would use. `(event)` only accepts `click`/`input`/
|
|
216
|
+
`blur`/`change` — `VElement`'s own fixed set — anything else is a compile error
|
|
217
|
+
(`KS5016`). `[prop]`/static `attr="..."` assign directly for `id`/`className`/`value`;
|
|
218
|
+
anything else goes through `SetAttr` instead.
|
|
175
219
|
- A `state<T>` field declared directly on the class and referenced directly in the
|
|
176
220
|
template (`Count` above) gets `Subscribe((v) => this.Update())` wired automatically —
|
|
177
221
|
no manual `Subscribe` in the constructor for that field. State reached indirectly
|
|
178
222
|
(through a method, or `this.SomeService.Count`) still needs a manual `Subscribe`, same
|
|
179
223
|
as a hand-written `Render()` always has.
|
|
180
224
|
- One top-level element per template (hard error otherwise); no mixing text and element
|
|
181
|
-
children under one element (no text-node
|
|
182
|
-
two-way binding, no pipes, at most one structural directive per
|
|
225
|
+
children under one element (`VElement` has no text-node sibling concept, only
|
|
226
|
+
`.TextContent`); no two-way binding, no pipes, at most one structural directive per
|
|
227
|
+
element.
|
|
183
228
|
- This is entirely a KopScript compiler feature (parsed/desugared before type-checking
|
|
184
229
|
runs) — Kopular's own framework code (`component.ks`, `dom.ks`) is unmodified and
|
|
185
230
|
unaware templates exist; a template-generated `Render()` is indistinguishable from a
|
|
@@ -191,14 +236,14 @@ Real URLs via the History API (`pushState`/`popstate`), not hash routing.
|
|
|
191
236
|
|
|
192
237
|
```ks
|
|
193
238
|
class NotFoundPage : Component {
|
|
194
|
-
public override
|
|
239
|
+
public override VElement Render() { /* ... */ }
|
|
195
240
|
}
|
|
196
241
|
class HomePage : Component {
|
|
197
242
|
private Router Nav;
|
|
198
243
|
constructor(Router nav) : base() { this.Nav = nav; }
|
|
199
|
-
public override
|
|
200
|
-
|
|
201
|
-
btn.
|
|
244
|
+
public override VElement Render() {
|
|
245
|
+
VElement btn = VElement.Create("button");
|
|
246
|
+
btn.OnClick = (Event e) => { this.Nav.Navigate("/about"); };
|
|
202
247
|
return btn;
|
|
203
248
|
}
|
|
204
249
|
}
|
|
@@ -238,14 +283,14 @@ nav.Navigate("/about"); // pushState + immediate re-ren
|
|
|
238
283
|
`"/search?sort=name"` -> `Query("sort") == "name"`. Values are **not percent-decoded** —
|
|
239
284
|
no `decodeURIComponent` binding exists yet, a deliberate v1 cut; `%20`/`+` arrive
|
|
240
285
|
exactly as written in the URL, not converted to a space.
|
|
241
|
-
- **Why `Param` is a plain field, not `state<T>`**: `
|
|
242
|
-
|
|
286
|
+
- **Why `Param` is a plain field, not `state<T>`**: `Router`'s own `AfterRender` already
|
|
287
|
+
re-`Mount()`s the matched page into its outlet on every `Navigate()`/`popstate`, which
|
|
243
288
|
re-runs that page's own `Render()` (reading the fresh `Param`) with no extra step.
|
|
244
289
|
Making it `state<T>` and having a page `Subscribe()` to it — the pattern every *other*
|
|
245
290
|
piece of state in this framework uses — actually crashes: `Match()` sets it *before*
|
|
246
|
-
`
|
|
247
|
-
that matches a page nothing has `Mount()`ed yet fires that page's subscribed
|
|
248
|
-
while its inherited `Update()` still has no `ParentElement` to
|
|
291
|
+
`AfterRender` finishes mounting the matched page into the outlet, so the very first
|
|
292
|
+
route that matches a page nothing has `Mount()`ed yet fires that page's subscribed
|
|
293
|
+
listener while its inherited `Update()` still has no `ParentElement` to patch into.
|
|
249
294
|
- **Navigation guards**: `SetGuard(redirectPath, (string) => bool guard)` — `guard` is
|
|
250
295
|
called with the target path before every navigation (including a direct load/refresh);
|
|
251
296
|
returning `false` redirects to `redirectPath` (via `pushState`, so the URL updates too —
|
|
@@ -282,11 +327,11 @@ a **hand-written** `Render()`, where `if` being a statement (not an expression)
|
|
|
282
327
|
conditional value needs a helper to get one out of it:
|
|
283
328
|
|
|
284
329
|
```ks
|
|
285
|
-
|
|
330
|
+
VElement If(bool condition, () => VElement whenTrue, () => VElement whenFalse)
|
|
286
331
|
```
|
|
287
332
|
|
|
288
333
|
```ks
|
|
289
|
-
root.
|
|
334
|
+
root.AppendChild(If(this.IsLoggedIn, () => this.BuildProfile(), () => this.BuildLogin()));
|
|
290
335
|
```
|
|
291
336
|
|
|
292
337
|
Both branches always required (no null "nothing" value); only the branch actually taken
|
|
@@ -295,20 +340,22 @@ at all in hand-written `Render()` either:
|
|
|
295
340
|
|
|
296
341
|
```ks
|
|
297
342
|
// *ngFor — plain array method
|
|
298
|
-
this.Items.ForEach((Item item) => { list.
|
|
343
|
+
this.Items.ForEach((Item item) => { list.AppendChild(this.BuildItemRow(item)); });
|
|
299
344
|
|
|
300
345
|
// *ngSwitch — plain KopScript `match` expression (exhaustiveness-checked, unlike *ngSwitch)
|
|
301
|
-
root.
|
|
346
|
+
root.AppendChild(match this.Status {
|
|
302
347
|
"loading" => this.BuildSpinner(),
|
|
303
348
|
"error" => this.BuildError(),
|
|
304
349
|
_ => this.BuildContent()
|
|
305
350
|
});
|
|
306
351
|
```
|
|
307
352
|
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
353
|
+
**Keyed/reuse-existing-DOM-nodes diffing (the `*ngFor trackBy` angle) exists** — give each
|
|
354
|
+
item's `VElement` a stable `.Id` (e.g. the item's own id) and `Update()`'s diff engine
|
|
355
|
+
(`PatchChildren` in `vdom.ks`) matches children by `Id` across a re-render, reusing a
|
|
356
|
+
matched child's real DOM node rather than rebuilding it. Without a stable `Id`, a
|
|
357
|
+
reordered list still renders correctly, but a given item's real node isn't guaranteed to
|
|
358
|
+
follow its data.
|
|
312
359
|
|
|
313
360
|
## `Http` (`http.ks`) — thin wrapper over `fetch`
|
|
314
361
|
|
|
@@ -367,7 +414,7 @@ email.Valid(); // bool — Error.Value == null
|
|
|
367
414
|
after the first as a nested function type, not an array element type, and errors expecting
|
|
368
415
|
`=>`). Combine checks as an if-chain in one lambda (see the `email` example above), or via
|
|
369
416
|
the fixed-arity `CombineValidators2<T>`/`CombineValidators3<T>` free functions (generic,
|
|
370
|
-
inference-only — see `
|
|
417
|
+
inference-only — see `KopScript`'s own "Generics" docs for why they're *free* functions, not
|
|
371
418
|
`Validators` static methods):
|
|
372
419
|
```ks
|
|
373
420
|
CombineValidators2((string v) => Validators.Required(v), (string v) => Validators.Email(v))
|
|
@@ -376,13 +423,10 @@ CombineValidators2((string v) => Validators.Required(v), (string v) => Validator
|
|
|
376
423
|
Fixed-arity (2, 3 — add more the same way if a form ever needs to chain further), not a
|
|
377
424
|
general `Validators.All(...)`, for the same array-of-function-values reason above.
|
|
378
425
|
|
|
379
|
-
**No DOM binding** — wiring `.Value` to a real `<input>` is a plain `
|
|
380
|
-
|
|
381
|
-
`
|
|
382
|
-
|
|
383
|
-
**No DOM binding** — wiring `.Value` to a real `<input>` is a plain `addEventListener`
|
|
384
|
-
call in your own `Render()`, the same as any other event handler; there is no
|
|
385
|
-
`[(ngModel)]`-equivalent.
|
|
426
|
+
**No DOM binding** — wiring `.Value` to a real `<input>` is a plain `VElement.OnInput`
|
|
427
|
+
assignment in your own `Render()` reading `e.target.value` (the same as any other event
|
|
428
|
+
handler; `VElement.Value` itself is one-way, host-to-DOM only), and reading it back out
|
|
429
|
+
via `.Touch()` on `OnBlur`; there is no `[(ngModel)]`-equivalent.
|
|
386
430
|
|
|
387
431
|
## `kopular/testing` (`testing.js`, hand-written JS, not compiled from `.ks`)
|
|
388
432
|
|
|
@@ -435,10 +479,14 @@ class AppContainer {
|
|
|
435
479
|
class RoutedApp : Component {
|
|
436
480
|
private Router Nav;
|
|
437
481
|
constructor(AppContainer services) : base() { this.Nav = services.Nav; }
|
|
438
|
-
public override
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
482
|
+
public override VElement Render() {
|
|
483
|
+
return VElement.Create("div");
|
|
484
|
+
}
|
|
485
|
+
// Mounting Router — a live, nested Component — isn't something a VElement
|
|
486
|
+
// tree can express as data; AfterRender gets the real, now-materialized
|
|
487
|
+
// root Element instead (see "Component" above).
|
|
488
|
+
protected override void AfterRender(Element root) {
|
|
489
|
+
this.Nav.Mount(root);
|
|
442
490
|
}
|
|
443
491
|
}
|
|
444
492
|
|
|
@@ -462,8 +510,8 @@ class CounterService {
|
|
|
462
510
|
- **`Router.Param` is a plain `string`, not `state<T>` — don't `Subscribe()` to it.** A
|
|
463
511
|
`state<T>`-based design for it was tried and genuinely crashes: `Navigate()` sets `Param`
|
|
464
512
|
*before* the newly-matched page finishes mounting, so a page `Subscribe`-ing to it fires
|
|
465
|
-
while its own `Update()` still has no `ParentElement` to
|
|
466
|
-
|
|
513
|
+
while its own `Update()` still has no `ParentElement` to patch into. `Render()` already
|
|
514
|
+
re-reads the fresh `Param` on every navigation with no extra step — just read
|
|
467
515
|
`this.Nav.Param` directly inside `Render()`.
|
|
468
516
|
- **Deploying a `Router`-based app needs SPA/history-fallback configured on the actual host**
|
|
469
517
|
— this bit the real `KopularDemo` production site (worked when navigated to via a link,
|
|
@@ -481,9 +529,20 @@ class CounterService {
|
|
|
481
529
|
`directives.ks`'s `If()` imported or called; `*if`/`*for` compile to real `if`/`for`
|
|
482
530
|
directly. Don't mix a template with a hand-written-`Render()` helper call.
|
|
483
531
|
- **No two-way binding, anywhere.** Wiring a template's `[value]` or a hand-written
|
|
484
|
-
`.Value` to a real `<input>` back and forth always needs an explicit
|
|
485
|
-
|
|
486
|
-
either authoring style.
|
|
532
|
+
`.Value` to a real `<input>` back and forth always needs an explicit `OnInput`
|
|
533
|
+
assignment reading `e.target.value` — there's no `[(ngModel)]`-equivalent to reach for
|
|
534
|
+
in either authoring style.
|
|
535
|
+
- **A `VElement` event binding only accepts `click`/`input`/`blur`/`change`** — both in a
|
|
536
|
+
template's `(event)="..."` and a hand-written `OnClick`/`OnInput`/`OnBlur`/`OnChange`
|
|
537
|
+
assignment. There's no generic `addEventListener` on `VElement` (event handlers are part
|
|
538
|
+
of the tree's own data, not wired against a live DOM node until `Materialize`/`Patch`
|
|
539
|
+
runs) — an event Kopular doesn't have a named field for isn't reachable from `Render()`
|
|
540
|
+
at all yet.
|
|
541
|
+
- **Reconciliation doesn't cross a `Mount()`ed-via-`AfterRender` boundary.** Real vdom
|
|
542
|
+
diffing patches one `Component`'s own subtree; a *nested* Component mounted into a slot
|
|
543
|
+
via `AfterRender` (see `Router`'s outlet, or `RoutedApp` mounting `Router` itself) isn't
|
|
544
|
+
automatically re-`Mount()`ed if the *parent's* surrounding tree shape changes — see
|
|
545
|
+
"Component"'s own "Known limitation" above.
|
|
487
546
|
|
|
488
547
|
## Does not exist
|
|
489
548
|
|
|
@@ -491,8 +550,11 @@ DI container/injector · decorators (`@Injectable`, `@Component`, ...) · a runt
|
|
|
491
550
|
template engine or interpreted expression language — templates compile to the same
|
|
492
551
|
imperative `Render()` code as the hand-written form, checked at compile time, not
|
|
493
552
|
interpreted at runtime (see "Templates" above) · two-way binding (`[(ngModel)]`) ·
|
|
494
|
-
|
|
495
|
-
|
|
553
|
+
reconciliation across a nested-Component boundary (see "Common mistakes" above — real
|
|
554
|
+
vdom diffing exists *within* one Component's own subtree, via `Update()`/`vdom.ks`) ·
|
|
555
|
+
a generic/arbitrary `VElement` event binding — only `click`/`input`/`blur`/`change` ·
|
|
556
|
+
pipes · animations · typed/generic HTTP responses (`Http` returns raw text — see above) ·
|
|
557
|
+
SSR.
|
|
496
558
|
|
|
497
559
|
(`FormField<T>`/`Validators` and `kp new` — see "FormField<T> / Validators" and "Starting
|
|
498
560
|
a new project" above — are real, shipped features; they used to be listed here as gaps
|