@tinyfx/runtime 0.1.3 → 0.1.6

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/dist/di.d.ts CHANGED
@@ -2,7 +2,7 @@
2
2
  export type Token<T> = (new (...args: any[]) => T) | symbol;
3
3
  /** Create a unique symbol token with a debug description. */
4
4
  export declare function createToken<T>(description: string): Token<T>;
5
- /** Context passed to every component's init() function. */
5
+ /** Context passed to every component's mount() factory function. */
6
6
  export interface TinyFxContext {
7
7
  container: Container;
8
8
  }
package/dist/http/http.js CHANGED
@@ -52,7 +52,7 @@ export function createHttp(config = {}) {
52
52
  fetchOptions.signal = combinedSignal;
53
53
  let res;
54
54
  try {
55
- res = await fetch(base + requestUrl, fetchOptions);
55
+ res = await fetch(requestUrl, fetchOptions);
56
56
  }
57
57
  catch (err) {
58
58
  clearTimeout(timeoutId);
package/dist/index.d.ts CHANGED
@@ -4,3 +4,8 @@ export { createHttp } from "./http/http";
4
4
  export type { HttpConfig } from "./http/data";
5
5
  export { Container, createToken } from "./di";
6
6
  export type { Token, TinyFxContext } from "./di";
7
+ export { initRouter } from "./router/index";
8
+ export { getParam } from "./router/params";
9
+ export { onMount, onDestroy } from "./router/lifecycle";
10
+ export { navigate, goBack } from "./router/navigate";
11
+ export type { RouteMap, RouteMeta } from "./router/types";
package/dist/index.js CHANGED
@@ -3,3 +3,8 @@ export { signal, effect, Signal } from "./signals";
3
3
  export { bindText, bindAttr, bindClass } from "./dom";
4
4
  export { createHttp } from "./http/http";
5
5
  export { Container, createToken } from "./di";
6
+ // Router — hard file-based router
7
+ export { initRouter } from "./router/index";
8
+ export { getParam } from "./router/params";
9
+ export { onMount, onDestroy } from "./router/lifecycle";
10
+ export { navigate, goBack } from "./router/navigate";
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Mark anchor tags whose `href` pathname matches `pathname` with
3
+ * `data-active="true"`, removing the attribute from all others.
4
+ *
5
+ * Only the pathname portion of each href is compared (query strings and
6
+ * fragments are ignored).
7
+ *
8
+ * @example
9
+ * // In a nav: <a href="/blog">Blog</a>
10
+ * // On the /blog page this element will get data-active="true"
11
+ * highlightActiveLinks("/blog");
12
+ */
13
+ export declare function highlightActiveLinks(pathname: string): void;
@@ -0,0 +1,31 @@
1
+ // @tinyfx/runtime — active link highlighting
2
+ /**
3
+ * Mark anchor tags whose `href` pathname matches `pathname` with
4
+ * `data-active="true"`, removing the attribute from all others.
5
+ *
6
+ * Only the pathname portion of each href is compared (query strings and
7
+ * fragments are ignored).
8
+ *
9
+ * @example
10
+ * // In a nav: <a href="/blog">Blog</a>
11
+ * // On the /blog page this element will get data-active="true"
12
+ * highlightActiveLinks("/blog");
13
+ */
14
+ export function highlightActiveLinks(pathname) {
15
+ const links = document.querySelectorAll("a[href]");
16
+ links.forEach((link) => {
17
+ try {
18
+ // Resolve href against the current origin to normalise relative URLs
19
+ const url = new URL(link.getAttribute("href"), window.location.origin);
20
+ if (url.pathname === pathname) {
21
+ link.setAttribute("data-active", "true");
22
+ }
23
+ else {
24
+ link.removeAttribute("data-active");
25
+ }
26
+ }
27
+ catch (_a) {
28
+ // Ignore malformed / external hrefs
29
+ }
30
+ });
31
+ }
@@ -0,0 +1,21 @@
1
+ import type { RouteMap } from "./types";
2
+ export type { RouteMap };
3
+ /**
4
+ * Initialise the TinyFX router for the current page.
5
+ *
6
+ * Called automatically from the compiler-generated `tinyfx.gen.ts` on every
7
+ * page load. Matches `window.location.pathname` against the compiled route
8
+ * map, resolves URL params, starts lifecycle hooks, and highlights active
9
+ * navigation links.
10
+ *
11
+ * @param routes - The route map emitted by `tinyfx build`.
12
+ *
13
+ * @example
14
+ * // tinyfx.gen.ts (auto-generated — do not edit)
15
+ * initRouter({
16
+ * "/": { page: "home", path: "/" },
17
+ * "/blog": { page: "blog", path: "/blog" },
18
+ * "/blog/:slug":{ page: "blog-post", path: "/blog/:slug" },
19
+ * });
20
+ */
21
+ export declare function initRouter(routes: RouteMap): void;
@@ -0,0 +1,36 @@
1
+ // @tinyfx/runtime — router entry point
2
+ import { resolveParams } from "./params";
3
+ import { initLifecycle } from "./lifecycle";
4
+ import { highlightActiveLinks } from "./active-links";
5
+ /**
6
+ * Initialise the TinyFX router for the current page.
7
+ *
8
+ * Called automatically from the compiler-generated `tinyfx.gen.ts` on every
9
+ * page load. Matches `window.location.pathname` against the compiled route
10
+ * map, resolves URL params, starts lifecycle hooks, and highlights active
11
+ * navigation links.
12
+ *
13
+ * @param routes - The route map emitted by `tinyfx build`.
14
+ *
15
+ * @example
16
+ * // tinyfx.gen.ts (auto-generated — do not edit)
17
+ * initRouter({
18
+ * "/": { page: "home", path: "/" },
19
+ * "/blog": { page: "blog", path: "/blog" },
20
+ * "/blog/:slug":{ page: "blog-post", path: "/blog/:slug" },
21
+ * });
22
+ */
23
+ export function initRouter(routes) {
24
+ const pathname = window.location.pathname;
25
+ for (const pattern of Object.keys(routes)) {
26
+ if (resolveParams(pattern, pathname)) {
27
+ // Match found
28
+ initLifecycle();
29
+ highlightActiveLinks(pathname);
30
+ return;
31
+ }
32
+ }
33
+ // No route matched — log a warning and do nothing else.
34
+ console.warn(`[tinyfx] No route matched for pathname: "${pathname}". ` +
35
+ "Check that a page file exists for this URL in src/pages/.");
36
+ }
@@ -0,0 +1,24 @@
1
+ type LifecycleCallback = () => void;
2
+ /**
3
+ * Register a callback to run when the page has mounted (DOM ready).
4
+ *
5
+ * - If the document is still loading, the callback runs after DOMContentLoaded.
6
+ * - If the document is already loaded, the callback runs immediately.
7
+ */
8
+ export declare function onMount(fn: LifecycleCallback): void;
9
+ /**
10
+ * Register a callback to run when the user navigates away from this page.
11
+ * Fires on the browser's `pagehide` event.
12
+ */
13
+ export declare function onDestroy(fn: LifecycleCallback): void;
14
+ /**
15
+ * Trigger all registered `onMount` callbacks.
16
+ * Called by `initRouter` after matching the current route.
17
+ */
18
+ export declare function flushMount(): void;
19
+ /**
20
+ * Wire up registered `onDestroy` callbacks to the `pagehide` event.
21
+ * Called by `initRouter` after matching the current route.
22
+ */
23
+ export declare function initLifecycle(): void;
24
+ export {};
@@ -0,0 +1,40 @@
1
+ // @tinyfx/runtime — page lifecycle hooks
2
+ const mountCallbacks = [];
3
+ const destroyCallbacks = [];
4
+ /**
5
+ * Register a callback to run when the page has mounted (DOM ready).
6
+ *
7
+ * - If the document is still loading, the callback runs after DOMContentLoaded.
8
+ * - If the document is already loaded, the callback runs immediately.
9
+ */
10
+ export function onMount(fn) {
11
+ mountCallbacks.push(fn);
12
+ }
13
+ /**
14
+ * Register a callback to run when the user navigates away from this page.
15
+ * Fires on the browser's `pagehide` event.
16
+ */
17
+ export function onDestroy(fn) {
18
+ destroyCallbacks.push(fn);
19
+ }
20
+ /**
21
+ * Trigger all registered `onMount` callbacks.
22
+ * Called by `initRouter` after matching the current route.
23
+ */
24
+ export function flushMount() {
25
+ const run = () => mountCallbacks.forEach((fn) => fn());
26
+ if (document.readyState === "loading") {
27
+ document.addEventListener("DOMContentLoaded", run, { once: true });
28
+ }
29
+ else {
30
+ run();
31
+ }
32
+ }
33
+ /**
34
+ * Wire up registered `onDestroy` callbacks to the `pagehide` event.
35
+ * Called by `initRouter` after matching the current route.
36
+ */
37
+ export function initLifecycle() {
38
+ flushMount();
39
+ window.addEventListener("pagehide", () => destroyCallbacks.forEach((fn) => fn()), { once: true });
40
+ }
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Navigate to the given path.
3
+ * This is a full browser navigation — not a DOM swap.
4
+ *
5
+ * @example
6
+ * navigate("/blog/hello-world");
7
+ */
8
+ export declare function navigate(path: string): void;
9
+ /**
10
+ * Go back one step in the browser history.
11
+ */
12
+ export declare function goBack(): void;
@@ -0,0 +1,18 @@
1
+ // @tinyfx/runtime — navigation helpers
2
+ // Hard router: navigation is always a real browser page load.
3
+ /**
4
+ * Navigate to the given path.
5
+ * This is a full browser navigation — not a DOM swap.
6
+ *
7
+ * @example
8
+ * navigate("/blog/hello-world");
9
+ */
10
+ export function navigate(path) {
11
+ window.location.href = path;
12
+ }
13
+ /**
14
+ * Go back one step in the browser history.
15
+ */
16
+ export function goBack() {
17
+ window.history.go(-1);
18
+ }
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Match `pattern` (e.g. "/blog/:slug") against `pathname` (e.g. "/blog/hello").
3
+ *
4
+ * Returns true if they match, and stores any `:param` captures in the
5
+ * module-level store so `getParam()` can retrieve them.
6
+ *
7
+ * Static segments must match exactly; `:param` segments match any non-empty
8
+ * path segment.
9
+ */
10
+ export declare function resolveParams(pattern: string, pathname: string): boolean;
11
+ /**
12
+ * Retrieve a URL parameter resolved for the current page.
13
+ *
14
+ * @example
15
+ * // On the page with route "/blog/:slug" and URL "/blog/hello-world"
16
+ * getParam("slug"); // → "hello-world"
17
+ */
18
+ export declare function getParam(key: string): string | undefined;
@@ -0,0 +1,42 @@
1
+ // @tinyfx/runtime — route param resolution
2
+ /** Module-level store for the current page's resolved params. */
3
+ let currentParams = {};
4
+ /**
5
+ * Match `pattern` (e.g. "/blog/:slug") against `pathname` (e.g. "/blog/hello").
6
+ *
7
+ * Returns true if they match, and stores any `:param` captures in the
8
+ * module-level store so `getParam()` can retrieve them.
9
+ *
10
+ * Static segments must match exactly; `:param` segments match any non-empty
11
+ * path segment.
12
+ */
13
+ export function resolveParams(pattern, pathname) {
14
+ const patternParts = pattern.split("/").filter(Boolean);
15
+ const pathParts = pathname.split("/").filter(Boolean);
16
+ if (patternParts.length !== pathParts.length)
17
+ return false;
18
+ const captured = {};
19
+ for (let i = 0; i < patternParts.length; i++) {
20
+ const seg = patternParts[i];
21
+ if (seg.startsWith(":")) {
22
+ // Dynamic segment — capture the value
23
+ captured[seg.slice(1)] = decodeURIComponent(pathParts[i]);
24
+ }
25
+ else if (seg !== pathParts[i]) {
26
+ // Static segment mismatch
27
+ return false;
28
+ }
29
+ }
30
+ currentParams = captured;
31
+ return true;
32
+ }
33
+ /**
34
+ * Retrieve a URL parameter resolved for the current page.
35
+ *
36
+ * @example
37
+ * // On the page with route "/blog/:slug" and URL "/blog/hello-world"
38
+ * getParam("slug"); // → "hello-world"
39
+ */
40
+ export function getParam(key) {
41
+ return currentParams[key];
42
+ }
@@ -0,0 +1,9 @@
1
+ /** Metadata for a single route, produced by the compiler. */
2
+ export interface RouteMeta {
3
+ /** The page name, derived from the source filename. */
4
+ page: string;
5
+ /** The declared URL pattern, e.g. "/blog/:slug". */
6
+ path: string;
7
+ }
8
+ /** The full route map emitted by `tinyfx build` into tinyfx.gen.ts. */
9
+ export type RouteMap = Record<string, RouteMeta>;
@@ -0,0 +1,2 @@
1
+ // @tinyfx/runtime — router types
2
+ export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tinyfx/runtime",
3
- "version": "0.1.3",
3
+ "version": "0.1.6",
4
4
  "description": "Minimal frontend runtime — signals, DOM helpers, typed HTTP, DTO mapping",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -10,7 +10,8 @@
10
10
  "./signals": "./dist/signals.js",
11
11
  "./dom": "./dist/dom.js",
12
12
  "./http": "./dist/http.js",
13
- "./di": "./dist/di.js"
13
+ "./di": "./dist/di.js",
14
+ "./router": "./dist/router/index.js"
14
15
  },
15
16
  "files": [
16
17
  "dist"