kopular 0.11.2 → 0.11.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/LLM.md CHANGED
@@ -128,6 +128,14 @@ w.Bump(); // re-render
128
128
  reconciliation. Compose independent components into stable slots (see `Router`'s own
129
129
  pattern of keeping page instances alive) to avoid this rather than nesting components
130
130
  that both re-render.
131
+ - **`Update()` before `Mount()` is a safe no-op**, not an error. This matters for two
132
+ sibling `Component`s (e.g. two `Router` pages) that share one injected service's
133
+ `state<T>` and both `Subscribe()` it — every route's page is constructed eagerly (see
134
+ `Router.AddRoute`), so at any given time most of them are constructed but never
135
+ `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/replace
137
+ for those, since `Mount()` will run a fresh `Render()` anyway whenever one of them
138
+ actually becomes routed.
131
139
  - `RenderError(string message)`: `virtual`, called by `Mount()`/`Update()` (via a private
132
140
  `SafeRender()` wrapper) when `Render()` throws, instead of letting the exception
133
141
  propagate uncaught and crash whatever triggered the render (a click handler, a `Router`
package/README.md CHANGED
@@ -543,3 +543,5 @@ mounted children, those children aren't automatically re-mounted into the parent
543
543
  tree (real reconciliation, the way React/Vue handle this, is real vdom-diffing work well
544
544
  beyond v1). Compose independent components into stable slots (see `Router`'s own pattern
545
545
  of keeping page instances alive rather than rebuilding them) to avoid the issue.
546
+ `Update()` called before `Mount()` is a safe no-op — see `LLM.md`'s `Component` section
547
+ for exactly when that happens (sibling pages sharing one injected service's `state<T>`).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kopular",
3
- "version": "0.11.2",
3
+ "version": "0.11.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",
package/src/component.js CHANGED
@@ -1,6 +1,10 @@
1
1
  import { Event, Element, Document, Location, History, Window, document, location, history, window } from "./dom.js";
2
2
 
3
3
  export class Component {
4
+ constructor() {
5
+ this.IsMounted = false;
6
+ }
7
+
4
8
  Render() {
5
9
  return document.createElement("div");
6
10
  }
@@ -21,9 +25,13 @@ export class Component {
21
25
  this.ParentElement = parent;
22
26
  this.Root = this.SafeRender();
23
27
  parent.appendChild(this.Root);
28
+ this.IsMounted = true;
24
29
  }
25
30
 
26
31
  Update() {
32
+ if (!this.IsMounted) {
33
+ return;
34
+ }
27
35
  let newRoot = this.SafeRender();
28
36
  this.ParentElement.replaceChild(newRoot, this.Root);
29
37
  this.Root = newRoot;
package/src/component.ks CHANGED
@@ -16,6 +16,19 @@ using "./dom";
16
16
  class Component {
17
17
  protected Element Root;
18
18
  private Element ParentElement;
19
+ // Set true only once Mount() actually runs. A page Component is commonly
20
+ // constructed eagerly (e.g. Router.AddRoute takes an already-built
21
+ // instance — see Router's own header comment) long before it's ever
22
+ // Mount()ed, and two sibling pages sharing one injected service's
23
+ // state<T> (Pure DI — both Subscribe() the same field) both get notified
24
+ // on any change regardless of which one is actually the currently-routed,
25
+ // mounted page. Update() guards on this so that notification is a safe
26
+ // no-op for the unmounted one, instead of a crash (see Update() below).
27
+ private bool IsMounted;
28
+
29
+ constructor() {
30
+ this.IsMounted = false;
31
+ }
19
32
 
20
33
  public virtual Element Render() {
21
34
  return document.createElement("div");
@@ -45,9 +58,21 @@ class Component {
45
58
  this.ParentElement = parent;
46
59
  this.Root = this.SafeRender();
47
60
  parent.appendChild(this.Root);
61
+ this.IsMounted = true;
48
62
  }
49
63
 
64
+ // A no-op, not an error, when called before Mount() — see the class-level
65
+ // comment on IsMounted for exactly when this happens for real (a
66
+ // shared-service state change reaching a sibling page that isn't the one
67
+ // currently routed/mounted). Nothing is lost: SafeRender() would only be
68
+ // thrown away unread since there's no live DOM parent to put it in, and
69
+ // Mount() itself always calls SafeRender() fresh whenever this component
70
+ // does become the routed page, picking up whatever the current state is
71
+ // at that point.
50
72
  protected void Update() {
73
+ if (!this.IsMounted) {
74
+ return;
75
+ }
51
76
  Element newRoot = this.SafeRender();
52
77
  this.ParentElement.replaceChild(newRoot, this.Root);
53
78
  this.Root = newRoot;