kopular 0.1.0 → 0.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/README.md CHANGED
@@ -19,9 +19,11 @@ dependency-injection container, no template DSL.
19
19
  constructor argument. No injector hierarchy, no provider tokens, no decorators — and a
20
20
  service stays fully testable with zero `Component`/DOM involvement, since it's just a
21
21
  class.
22
- - **`Router`**: hash-based (`#/path`, driven by the browser's native `hashchange` event)
23
- page-swapping, with route registration as plain method calls, not a config DSL. No
24
- server-side fallback route needed, unlike History-API routing.
22
+ - **`Router`**: real URLs (`/about`, not `#/about`) via the History API
23
+ (`pushState`/`popstate`), with route registration as plain method calls, not a config
24
+ DSL. Needs a server that falls back to the app shell for unrecognized paths — see
25
+ [KopularDemo](https://dev.azure.com/koppinator/Koppindependence/_git/KopularDemo)'s
26
+ `scripts/serve.mjs`.
25
27
 
26
28
  ## What's here
27
29
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kopular",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Kopular: a small component framework for KopScript — components, reactive state, constructor-injected services, and routing, with no template DSL and no DI container",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -9,7 +9,12 @@
9
9
  "type": "git",
10
10
  "url": "https://dev.azure.com/koppinator/Koppindependence/_git/Kopular"
11
11
  },
12
- "keywords": ["kopscript", "framework", "components", "ui"],
12
+ "keywords": [
13
+ "kopscript",
14
+ "framework",
15
+ "components",
16
+ "ui"
17
+ ],
13
18
  "main": "./src/component.js",
14
19
  "exports": {
15
20
  ".": "./src/component.js",
@@ -17,7 +22,9 @@
17
22
  "./router": "./src/router.js",
18
23
  "./dom": "./src/dom.js"
19
24
  },
20
- "files": ["src"],
25
+ "files": [
26
+ "src"
27
+ ],
21
28
  "scripts": {
22
29
  "build": "ks build src/router.ks",
23
30
  "prepublishOnly": "npm run build",
package/src/component.js CHANGED
@@ -1,4 +1,4 @@
1
- import { Event, Element, Document, Location, Window, document, location, window } from "./dom.js";
1
+ import { Event, Element, Document, Location, History, Window, document, location, history, window } from "./dom.js";
2
2
 
3
3
  export class Component {
4
4
  Render() {
package/src/dom.js CHANGED
@@ -2,7 +2,9 @@ export const Event = globalThis.Event;
2
2
  export const Element = globalThis.Element;
3
3
  export const Document = globalThis.Document;
4
4
  export const Location = globalThis.Location;
5
+ export const History = globalThis.History;
5
6
  export const Window = globalThis.Window;
6
7
  export const document = globalThis.document;
7
8
  export const location = globalThis.location;
9
+ export const history = globalThis.history;
8
10
  export const window = globalThis.window;
package/src/dom.ks CHANGED
@@ -27,7 +27,13 @@ extern class Document {
27
27
  };
28
28
 
29
29
  extern class Location {
30
- string hash { get; set; }
30
+ string pathname { get; }
31
+ };
32
+
33
+ extern class History {
34
+ // Param named `historyState`, not `state` — `state` is a KopScript
35
+ // keyword (state<T>), not a valid parameter name.
36
+ void pushState(string historyState, string title, string url);
31
37
  };
32
38
 
33
39
  extern class Window {
@@ -36,4 +42,5 @@ extern class Window {
36
42
 
37
43
  extern Document document;
38
44
  extern Location location;
45
+ extern History history;
39
46
  extern Window window;
package/src/router.js CHANGED
@@ -1,4 +1,4 @@
1
- import { Event, Element, Document, Location, Window, document, location, window } from "./dom.js";
1
+ import { Event, Element, Document, Location, History, Window, document, location, history, window } from "./dom.js";
2
2
  import { Component } from "./component.js";
3
3
 
4
4
  export class Router extends Component {
@@ -7,7 +7,7 @@ export class Router extends Component {
7
7
  (this.Paths = []);
8
8
  (this.Pages = []);
9
9
  (this.NotFoundPage = notFoundPage);
10
- window.addEventListener("hashchange", (e) => (this.Update()));
10
+ window.addEventListener("popstate", (e) => (this.Update()));
11
11
  }
12
12
 
13
13
  AddRoute(path, page) {
@@ -16,7 +16,8 @@ export class Router extends Component {
16
16
  }
17
17
 
18
18
  Navigate(path) {
19
- (location.hash = ("#" + path));
19
+ history.pushState("", "", path);
20
+ this.Update();
20
21
  }
21
22
 
22
23
  Match(path) {
@@ -32,7 +33,7 @@ export class Router extends Component {
32
33
  Render() {
33
34
  let outlet = document.createElement("div");
34
35
  (outlet.className = "router-outlet");
35
- let path = location.hash.replaceAll("#", "");
36
+ let path = location.pathname;
36
37
  let page = this.Match(path);
37
38
  page.Mount(outlet);
38
39
  return outlet;
package/src/router.ks CHANGED
@@ -1,12 +1,15 @@
1
1
  using "./dom";
2
2
  using "./component";
3
3
 
4
- // Hash-based routing (`#/path`, driven by the browser's native `hashchange`
5
- // event) rather than the History API — it needs no server-side fallback
6
- // (a plain static file server, like scripts/serve.mjs, has nothing special
7
- // to do for a hash), and there's no `pushState(state, title, url)` signature
8
- // to model through `extern`. Route registration is imperative (`AddRoute`
9
- // calls), not a config object — one thing to learn, not a routing DSL.
4
+ // History-API routing (real paths `/about`, not `#/about`) via
5
+ // `history.pushState`/the browser's native `popstate` event. This needs a
6
+ // server that falls back to the app shell for any path it doesn't
7
+ // recognize as a real file (see KopularDemo's scripts/serve.mjs) a plain
8
+ // static file server has nothing to serve at `/about` on a direct
9
+ // visit/refresh otherwise, unlike hash-based routing, which never leaves
10
+ // the one HTML file the server actually has. Route registration is
11
+ // imperative (`AddRoute` calls), not a config object — one thing to learn,
12
+ // not a routing DSL.
10
13
  //
11
14
  // Routes are registered as already-constructed Component instances, not
12
15
  // factories — `Component[]`, not `(() => Component)[]` (KopScript's type
@@ -29,7 +32,12 @@ class Router : Component {
29
32
  this.Paths = [];
30
33
  this.Pages = [];
31
34
  this.NotFoundPage = notFoundPage;
32
- window.addEventListener("hashchange", (Event e) => this.Update());
35
+ // 'popstate' only fires on browser back/forward (or history.go/back/
36
+ // forward) — never on pushState itself, unlike hashchange firing
37
+ // whenever location.hash is set. Navigate() below calls Update()
38
+ // directly for that reason; this listener only covers the back/forward
39
+ // case, where the URL changes without any of our own code running.
40
+ window.addEventListener("popstate", (Event e) => this.Update());
33
41
  }
34
42
 
35
43
  public void AddRoute(string path, Component page) {
@@ -37,11 +45,9 @@ class Router : Component {
37
45
  this.Pages = this.Pages.Push(page);
38
46
  }
39
47
 
40
- // Setting location.hash fires a real 'hashchange' event, which the
41
- // constructor already subscribed to — no direct call to Update() needed
42
- // here, the same "set the value, listeners react" shape as state<T>.
43
48
  public void Navigate(string path) {
44
- location.hash = "#" + path;
49
+ history.pushState("", "", path);
50
+ this.Update();
45
51
  }
46
52
 
47
53
  private Component Match(string path) {
@@ -62,7 +68,7 @@ class Router : Component {
62
68
  public override Element Render() {
63
69
  Element outlet = document.createElement("div");
64
70
  outlet.className = "router-outlet";
65
- string path = location.hash.Replace("#", "");
71
+ string path = location.pathname;
66
72
  Component page = this.Match(path);
67
73
  page.Mount(outlet);
68
74
  return outlet;