kopular 1.1.4 → 1.2.0

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 CHANGED
@@ -43,6 +43,8 @@ both scripts). `ks check src/app.ks` type-checks without building, if you just w
43
43
  - `try { } catch (string e) { } finally { }` and `throw "message";` all exist — one `catch`
44
44
  per `try`, and its parameter type is your choice (it is *not* checked against what was
45
45
  actually thrown).
46
+ - **Comments are `//` only** — there is no `/* ... */` block comment, and a `/*` is a parse
47
+ error (`KS2020`), not an ignored region.
46
48
  - **Reserved words**: naming a local or parameter one of these is a parse error, not
47
49
  shadowing — `raw`, `state`, `task`, `from`, `as`, `get`, `set`, `in`, `base` and `match`
48
50
  are the ones easy to pick by accident. Full list: `using extern raw template styles from
@@ -57,6 +59,9 @@ both scripts). `ks check src/app.ks` type-checks without building, if you just w
57
59
  `.Split(sep)`, `.Replace(from, to)`, `.CompareTo(other)` (sort comparator), `+` concatenates.
58
60
  Both `"double"` and `'single'` quotes work — use whichever the other isn't, e.g. inside
59
61
  a template attribute (`*if="Name != ''"`).
62
+ - Arrays are created with a literal and nothing else: `number[] xs = [];`, `[1, 2, 3]`,
63
+ `[new Todo("a")]`. **There is no `new number[3]` form** — that's a parse error, not an
64
+ empty array.
60
65
  - Arrays: `xs.Length`, `.Map()/.Filter()/.ForEach()/.Find()/.FindIndex()/.Includes()/.IndexOf()`,
61
66
  `.Sort(cmp)/.Reverse()/.Push(x)` (all three **non-mutating**, return a new array),
62
67
  `.Slice(a, b)/.Concat(ys)/.Join(sep)/.Reduce(fn, initial)`.
package/LLM.md CHANGED
@@ -135,10 +135,10 @@ extern class VElement {
135
135
  static VElement Mount(Component component);
136
136
  } from "kopular/velement";
137
137
 
138
- // Only needed if you use `styles from "./x.css";` in a class body (see
139
- // "Scoped styles" below) — the compiler splices a call to this into the
140
- // constructor automatically, but (like every other Kopular export) it
141
- // still needs its own `extern` declaration; it is NOT auto-imported.
138
+ // The runtime half of `styles from "./x.css";` (see "Scoped styles" below)
139
+ // — the compiler splices a call to this into the constructor. Listed here
140
+ // for reference: `using "kopular";` already brings it in, and declaring it
141
+ // yourself on top of that is a real KS4002 conflict.
142
142
  extern class ScopedStyles {
143
143
  static void Inject(string id, string css);
144
144
  } from "kopular/vdom";
@@ -171,7 +171,7 @@ extern class Router {
171
171
  // is in flight — default: `<div class="router-loading">Loading...</div>`.
172
172
  // Only declare this if you actually override it (a class extending
173
173
  // Router) — see "Lazy routes" below.
174
- protected virtual VElement BuildLoadingPlaceholder();
174
+ virtual VElement BuildLoadingPlaceholder();
175
175
  void Navigate(string path);
176
176
  void Mount(Element parent);
177
177
  // One guard for the whole Router, not per-route — see "Router" below.
@@ -320,6 +320,7 @@ w.Bump(); // re-render + diff + patch
320
320
  component, applied once the handler returns:
321
321
  ```ks
322
322
  private void Save() {
323
+ Item newItem = new Item(this.Draft); // whatever this handler just built
323
324
  this.Draft = ""; // a plain field Render() also reads
324
325
  this.Items.Value = this.Items.Value.Push(newItem); // triggers the ONE render, via Subscribe
325
326
  }
@@ -503,7 +504,7 @@ Real URLs via the History API (`pushState`/`popstate`), not hash routing.
503
504
 
504
505
  ```ks
505
506
  class NotFoundPage : Component {
506
- public override VElement Render() { /* ... */ }
507
+ public override VElement Render() { return VElement.Create("h1"); }
507
508
  }
508
509
  class HomePage : Component {
509
510
  private Router Nav;
package/README.md CHANGED
@@ -166,11 +166,11 @@ class Widget : Component {
166
166
  ```
167
167
 
168
168
  Unlike `template from`, this **did** need real framework code — `ScopedStyles.Inject`
169
- (`vdom.ks`) is the runtime half, and (like every other Kopular export) it needs its own
170
- `extern` declaration in your project `extern class ScopedStyles { static void
171
- Inject(string id, string css); } from "kopular/vdom";` it is not auto-imported just
172
- because you wrote `styles from`; omitting it is a real `KS4048 Undefined identifier
173
- 'ScopedStyles'` at the constructor the compiler spliced the call into. It's idempotent,
169
+ (`vdom.ks`) is the runtime half, and `using "kopular";` brings it in along with everything
170
+ else do **not** write your own `extern class ScopedStyles ... from "kopular/vdom";`, which
171
+ is now a real `KS4002 Declaration 'ScopedStyles' conflicts with a name brought in by
172
+ 'using'`. (Before package `using` existed, every Kopular export did need a hand-written
173
+ `extern` in your project; that is what `using "kopular";` replaced.) It's idempotent,
174
174
  injecting one real `<style>` per component *type* into `document.head` the first time any
175
175
  instance of that type is constructed
176
176
  (dedup is per-type, not per-instance — every instance's constructor calls `Inject` with
@@ -595,8 +595,17 @@ class Card : Component {
595
595
  }
596
596
  }
597
597
 
598
- // Usage — the parent decides what's inside the card, Card decides the chrome around it:
599
- Card card = new Card(() => this.BuildCardBody());
598
+ // Usage, from inside the parent's own Render() — the parent decides what's inside the
599
+ // card, Card decides the chrome around it. `this` only means anything in a method, so
600
+ // that is where the callback is built:
601
+ class Page : Component {
602
+ constructor() : base() { }
603
+ public override VElement Render() {
604
+ Card card = new Card(() => this.BuildCardBody());
605
+ return VElement.Mount(card);
606
+ }
607
+ private VElement BuildCardBody() { return VElement.Create("p"); }
608
+ }
600
609
  ```
601
610
 
602
611
  Because the callback is invoked fresh on every one of `Card`'s own renders (not a value
@@ -641,7 +650,7 @@ own `header.ks`/`nav.ks` use it exactly this way):
641
650
  ```ks
642
651
  VElement header = VElement.Create("header");
643
652
  header.RawHtml = SiteHeaderHtml; // a raw string ... from "./header.html"; constant
644
- header.OnClick = (Event e) => { /* one delegated listener over the whole subtree */ };
653
+ header.OnClick = (Event e) => { }; // one delegated listener over the whole subtree
645
654
  ```
646
655
 
647
656
  **It's unescaped, real `innerHTML` — never assign it anything reachable from user input.**
@@ -655,7 +664,7 @@ escaped) for any dynamic string; `RawHtml` is for static markup only.
655
664
  ## HTTP
656
665
 
657
666
  ```ks
658
- using "./http";
667
+ using "kopular";
659
668
 
660
669
  Response r = await Http.Get("/api/dogs");
661
670
  if (r.ok) {
@@ -706,7 +715,7 @@ else avoids the problem by only wrapping JS APIs that take plain positional argu
706
715
  ## Forms
707
716
 
708
717
  ```ks
709
- using "./forms";
718
+ using "kopular";
710
719
 
711
720
  FormField<string> email = new FormField<string>("", (string v) => {
712
721
  string? required = Validators.Required(v);
@@ -719,6 +728,7 @@ print(email.Error.Value); // "Must be a valid email"
719
728
  print(email.Valid()); // false
720
729
 
721
730
  // inside a hand-written Render(), building emailInput as a VElement:
731
+ VElement emailInput = VElement.Create("input");
722
732
  emailInput.OnInput = (Event e) => {
723
733
  email.Value.Value = e.target.value; // revalidates automatically
724
734
  };
@@ -769,7 +779,7 @@ React's `useMemo`/Angular signals' `computed()` equivalent — a read-only value
769
779
  from one or more `state<T>` sources, recomputed and re-notified whenever a source changes:
770
780
 
771
781
  ```ks
772
- using "./computed";
782
+ using "kopular";
773
783
 
774
784
  state<number> price = state(10);
775
785
  state<number> qty = state(2);
@@ -806,18 +816,23 @@ commonly wrapping an `Http` call — as reactive `state<T>` a `Component` can re
806
816
  `Subscribe` to, instead of hand-rolling the same three fields and try/catch every time:
807
817
 
808
818
  ```ks
809
- using "./resource";
819
+ using "kopular";
810
820
 
811
- Resource<Response> dogs = new Resource<Response>(Http.Get("https://dog.ceo/api/breeds/list/all"));
812
- dogs.Status.Subscribe((AsyncStatus s) => this.Update());
821
+ class DogsPage : Component {
822
+ private Resource<Response> dogs;
823
+ constructor() : base() {
824
+ this.dogs = new Resource<Response>(Http.Get("https://dog.ceo/api/breeds/list/all"));
825
+ this.dogs.Status.Subscribe((AsyncStatus s) => this.Update());
826
+ }
827
+ }
813
828
  ```
814
829
 
815
830
  ```ks
816
831
  public override VElement Render() {
817
- return match dogs.Status.Value {
832
+ return match this.dogs.Status.Value {
818
833
  AsyncStatus.Loading => this.BuildSpinner(),
819
- AsyncStatus.Success => this.BuildList(dogs.Data.Value),
820
- AsyncStatus.Failure => this.BuildError(dogs.Error.Value)
834
+ AsyncStatus.Success => this.BuildList(this.dogs.Data.Value),
835
+ AsyncStatus.Failure => this.BuildError(this.dogs.Error.Value)
821
836
  };
822
837
  }
823
838
  ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kopular",
3
- "version": "1.1.4",
3
+ "version": "1.2.0",
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/kopular.ks CHANGED
@@ -156,6 +156,12 @@ extern class Validators {
156
156
  static string? Max(number value, number max);
157
157
  } from "kopular/forms";
158
158
 
159
+ // Chain validators into the single (T) => string? a FormField takes. Real
160
+ // generic free functions in forms.ks; without these declared here, a
161
+ // consumer using `using "kopular";` couldn't reach them at all.
162
+ extern (T) => string? CombineValidators2<T>((T) => string? v1, (T) => string? v2) from "kopular/forms";
163
+ extern (T) => string? CombineValidators3<T>((T) => string? v1, (T) => string? v2, (T) => string? v3) from "kopular/forms";
164
+
159
165
  extern class Computed1<A, R> {
160
166
  constructor(state<A> source, (A) => R compute);
161
167
  state<R> Value { get; }