Modular router db and locale

This commit is contained in:
2026-05-22 23:05:08 +02:00
parent 8481e339cc
commit eb1c81ec26
16 changed files with 143 additions and 107 deletions

1
dist/sigpro.db.js vendored Normal file
View File

@@ -0,0 +1 @@
var o=async(e,n={},t=null)=>{if(t)t(!0);try{let r=await fetch(e,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(n),credentials:"include"});if(!r.ok){let s=await r.text();throw Error(`Error ${r.status}: ${s}`)}return await r.json()}finally{if(t)t(!1)}};export{o as db};

2
dist/sigpro.js vendored

File diff suppressed because one or more lines are too long

1
dist/sigpro.locale.js vendored Normal file
View File

@@ -0,0 +1 @@
var{$:c}=window.SigPro,s=c("en"),n={},e=(t)=>{for(let o of Object.keys(t)){if(!n[o])n[o]={};Object.assign(n[o],t[o])}},r=(t)=>{if(t&&n[t])s(t)},a=(t)=>{return()=>n[s()]?.[t]??t};export{a as t,r as setLocale,e as addLang};

1
dist/sigpro.router.js vendored Normal file
View File

@@ -0,0 +1 @@
var{$:p,h:u,watch:m,render:g,isF:y}=window.SigPro,d=()=>window.location.hash.slice(1)||"/",s=p(d());window.addEventListener("hashchange",()=>s(d()));var w=p({}),c=(n)=>{let i=u("div",{class:"router-hook"}),r=null;return m([s],()=>{let l=s(),t=n.find((o)=>{let e=o.path.split("/").filter(Boolean),a=l.split("/").filter(Boolean);return e.length===a.length&&e.every((h,f)=>h[0]===":"||h===a[f])})||n.find((o)=>o.path==="*");if(t){r?.destroy();let o={};t.path.split("/").filter(Boolean).forEach((e,a)=>{if(e[0]===":")o[e.slice(1)]=l.split("/").filter(Boolean)[a]}),w(o),r=g(()=>y(t.component)?t.component(o):t.component),i.replaceChildren(r.container)}}),i.destroy=()=>{r?.destroy()},i};c.params=w;c.to=(n)=>window.location.hash=n.replace(/^#?\/?/,"#/");c.back=()=>window.history.back();c.path=()=>s();export{w as routerParams,c as router};

View File

@@ -1 +0,0 @@
var{$:d,h:m,watch:g,render:x,isF:b}=window.SigPro,l=(t)=>{let e=()=>window.location.hash.slice(1)||"/",o=d(e()),n=()=>o(e());window.addEventListener("hashchange",n);let s=m("div",{class:"router-hook"}),h=null;return g([o],()=>{let f=o(),a=t.find((r)=>{let c=r.path.split("/").filter(Boolean),p=f.split("/").filter(Boolean);return c.length===p.length&&c.every((w,y)=>w[0]===":"||w===p[y])})||t.find((r)=>r.path==="*");if(a){h?.destroy();let r={};a.path.split("/").filter(Boolean).forEach((c,p)=>{if(c[0]===":")r[c.slice(1)]=f.split("/").filter(Boolean)[p]}),l.params(r),h=x(()=>b(a.component)?a.component(r):a.component),s.replaceChildren(h.container)}}),s.destroy=()=>{window.removeEventListener("hashchange",n),h?.destroy()},s};l.params=d({});l.to=(t)=>window.location.hash=t.replace(/^#?\/?/,"#/");l.back=()=>window.history.back();l.path=()=>window.location.hash.replace(/^#/,"")||"/";var k=async(t,e={},o=null)=>{if(o)o(!0);try{let n=await fetch(t,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(e),credentials:"include"});if(!n.ok){let s=await n.text();throw Error(`Error ${n.status}: ${s}`)}return await n.json()}finally{if(o)o(!1)}},u=d("en"),i={},v=(t)=>{for(let e of Object.keys(t)){if(!i[e])i[e]={};Object.assign(i[e],t[e])}},E=(t)=>{if(t&&i[t])u(t)},L=(t)=>{return()=>i[u()]?.[t]??t};export{L as t,E as setLocale,l as router,k as db,v as addLang};

View File

@@ -17,7 +17,7 @@ router(routes: Route[]): HTMLElement
**Returns:** A `div` element (with class `"router-hook"`) that acts as the router outlet. The router automatically destroys the previous view and mounts the matched component when the hash changes. **Returns:** A `div` element (with class `"router-hook"`) that acts as the router outlet. The router automatically destroys the previous view and mounts the matched component when the hash changes.
> **Availability:** `router` and its helper methods (`router.to`, `router.back`, `router.path`, `router.params`) are exported from the SigPro module. In **ESM** you must import them (`import { router } from 'sigpro/utils'`). In the **IIFE** classic script, they are automatically available on `window`. The examples below assume the functions are already in scope. > You must import them (`import { router } from 'sigpro/router'`). The examples below assume the functions are already in scope.
--- ---
@@ -28,6 +28,9 @@ router(routes: Route[]): HTMLElement
Place the `router` element where you want the page content to appear. Inside the routes array, define your routes. Place the `router` element where you want the page content to appear. Inside the routes array, define your routes.
```javascript ```javascript
// remember import router
import { router } from 'sigpro/router'
const Home = () => h1("Home Page"); const Home = () => h1("Home Page");
const UserProfile = (params) => h1(`User ID: ${params.id}`); const UserProfile = (params) => h1(`User ID: ${params.id}`);
const NotFound = () => h1("404 Page not found"); const NotFound = () => h1("404 Page not found");
@@ -189,6 +192,10 @@ mount(App, "#app");
| `router.params()` | Reactive signal of the current route parameters. | | `router.params()` | Reactive signal of the current route parameters. |
---
# Vite Plugin: File-based Routing # Vite Plugin: File-based Routing
The `sigproRouter` plugin for Vite automates route generation by scanning your `pages` directory. It creates a **virtual module** that you can import directly into your code, eliminating the need to maintain a manual routes array. The `sigproRouter` plugin for Vite automates route generation by scanning your `pages` directory. It creates a **virtual module** that you can import directly into your code, eliminating the need to maintain a manual routes array.

1
docs/sigpro.db.js Normal file
View File

@@ -0,0 +1 @@
var o=async(e,n={},t=null)=>{if(t)t(!0);try{let r=await fetch(e,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(n),credentials:"include"});if(!r.ok){let s=await r.text();throw Error(`Error ${r.status}: ${s}`)}return await r.json()}finally{if(t)t(!1)}};export{o as db};

File diff suppressed because one or more lines are too long

1
docs/sigpro.locale.js Normal file
View File

@@ -0,0 +1 @@
var{$:c}=window.SigPro,s=c("en"),n={},e=(t)=>{for(let o of Object.keys(t)){if(!n[o])n[o]={};Object.assign(n[o],t[o])}},r=(t)=>{if(t&&n[t])s(t)},a=(t)=>{return()=>n[s()]?.[t]??t};export{a as t,r as setLocale,e as addLang};

1
docs/sigpro.router.js Normal file
View File

@@ -0,0 +1 @@
var{$:p,h:u,watch:m,render:g,isF:y}=window.SigPro,d=()=>window.location.hash.slice(1)||"/",s=p(d());window.addEventListener("hashchange",()=>s(d()));var w=p({}),c=(n)=>{let i=u("div",{class:"router-hook"}),r=null;return m([s],()=>{let l=s(),t=n.find((o)=>{let e=o.path.split("/").filter(Boolean),a=l.split("/").filter(Boolean);return e.length===a.length&&e.every((h,f)=>h[0]===":"||h===a[f])})||n.find((o)=>o.path==="*");if(t){r?.destroy();let o={};t.path.split("/").filter(Boolean).forEach((e,a)=>{if(e[0]===":")o[e.slice(1)]=l.split("/").filter(Boolean)[a]}),w(o),r=g(()=>y(t.component)?t.component(o):t.component),i.replaceChildren(r.container)}}),i.destroy=()=>{r?.destroy()},i};c.params=w;c.to=(n)=>window.location.hash=n.replace(/^#?\/?/,"#/");c.back=()=>window.history.back();c.path=()=>s();export{w as routerParams,c as router};

View File

@@ -19,10 +19,33 @@
"import": "./dist/sigpro.js", "import": "./dist/sigpro.js",
"default": "./dist/sigpro.js" "default": "./dist/sigpro.js"
}, },
"./utils": "./dist/sigpro.utils.js", "./db": {
"./grid": "./dist/sigpro.grid.js", "types": "./sigpro.d.ts",
"./editor": "./dist/sigpro.editor.js", "import": "./dist/sigpro.db.js",
"./vite": "./dist/sigpro.vite.js", "default": "./dist/sigpro.db.js"
},
"./router": {
"types": "./sigpro.d.ts",
"import": "./dist/sigpro.router.js",
"default": "./dist/sigpro.router.js"
},
"./locale": {
"types": "./sigpro.d.ts",
"import": "./dist/sigpro.locale.js",
"default": "./dist/sigpro.locale.js"
},
"./grid": {
"import": "./dist/sigpro.grid.js",
"default": "./dist/sigpro.grid.js"
},
"./editor": {
"import": "./dist/sigpro.editor.js",
"default": "./dist/sigpro.editor.js"
},
"./vite": {
"import": "./dist/sigpro.vite.js",
"default": "./dist/sigpro.vite.js"
},
"./css": "./dist/sigpro.ui.css", "./css": "./dist/sigpro.ui.css",
"./ui": { "./ui": {
"types": "./sigpro.ui.d.ts", "types": "./sigpro.ui.d.ts",
@@ -34,7 +57,8 @@
"dist/", "dist/",
"README.md", "README.md",
"LICENSE", "LICENSE",
"sigpro.d.ts" "sigpro.d.ts",
"sigpro.ui.d.ts"
], ],
"homepage": "https://sigpro.natxocc.com/#/", "homepage": "https://sigpro.natxocc.com/#/",
"repository": { "repository": {
@@ -50,14 +74,16 @@
"clean": "rm -rf dist", "clean": "rm -rf dist",
"prebuild": "npm run clean", "prebuild": "npm run clean",
"build:core": "bun build ./src/sigpro.js --bundle --outfile=./dist/sigpro.js --format=esm --minify", "build:core": "bun build ./src/sigpro.js --bundle --outfile=./dist/sigpro.js --format=esm --minify",
"build:utils": "bun build ./src/sigpro.utils.js --bundle --outfile=./dist/sigpro.utils.js --format=esm --external ./src/sigpro.js --minify", "build:db": "bun build ./src/sigpro.db.js --bundle --outfile=./dist/sigpro.db.js --format=esm --minify",
"build:router": "bun build ./src/sigpro.router.js --bundle --outfile=./dist/sigpro.router.js --format=esm --external ./src/sigpro.js --minify",
"build:locale": "bun build ./src/sigpro.locale.js --bundle --outfile=./dist/sigpro.locale.js --format=esm --external ./src/sigpro.js --minify",
"build:ui": "bun build ./src/sigpro.ui.js --bundle --outfile=./dist/sigpro.ui.js --format=esm --external ./src/sigpro.js --minify", "build:ui": "bun build ./src/sigpro.ui.js --bundle --outfile=./dist/sigpro.ui.js --format=esm --external ./src/sigpro.js --minify",
"build:grid": "bun build ./src/sigpro.grid.js --bundle --external sigpro --outfile=./dist/sigpro.grid.js --format=esm --minify", "build:grid": "bun build ./src/sigpro.grid.js --bundle --external sigpro --outfile=./dist/sigpro.grid.js --format=esm --minify",
"build:editor": "bun build ./src/sigpro.editor.js --bundle --external sigpro --outfile=./dist/sigpro.editor.js --format=esm --minify", "build:editor": "bun build ./src/sigpro.editor.js --bundle --external sigpro --outfile=./dist/sigpro.editor.js --format=esm --minify",
"build:vite": "bun build ./src/sigpro.vite.js --bundle --outfile=./dist/sigpro.vite.js --format=esm --external fs --external path --minify", "build:vite": "bun build ./src/sigpro.vite.js --bundle --outfile=./dist/sigpro.vite.js --format=esm --external fs --external path --minify",
"build:css": "tailwindcss -i ./src/sigpro.ui.css -o ./dist/sigpro.ui.css --minify --content './src/tailwind' && du -h ./dist/sigpro.ui.css", "build:css": "tailwindcss -i ./src/sigpro.ui.css -o ./dist/sigpro.ui.css --minify --content './src/tailwind' && du -h ./dist/sigpro.ui.css",
"build:convert": "bun build ./src/sigpro.convert.js --bundle --outfile=./docs/sigpro.convert.js --format=esm --external ./src/sigpro.js --minify", "build:convert": "bun build ./src/sigpro.convert.js --bundle --outfile=./docs/sigpro.convert.js --format=esm --external ./src/sigpro.js --minify",
"build": "bun run build:core && bun run build:utils && bun run build:ui && bun run build:grid && bun run build:editor && bun run build:vite && bun run build:css && bun run build:convert && cp ./dist/* ./docs", "build": "bun run build:core && bun run build:db && bun run build:router && bun run build:locale && bun run build:ui && bun run build:grid && bun run build:editor && bun run build:vite && bun run build:css && bun run build:convert && cp ./dist/* ./docs",
"docs": "bun x serve docs" "docs": "bun x serve docs"
}, },
"devDependencies": { "devDependencies": {
@@ -83,4 +109,4 @@
"lightweight", "lightweight",
"sigpro" "sigpro"
] ]
} }

18
src/sigpro.db.js Normal file
View File

@@ -0,0 +1,18 @@
export const db = async (url, data = {}, loading = null) => {
if (loading) loading(true);
try {
const res = await fetch(url, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data),
credentials: 'include'
});
if (!res.ok) {
const errorText = await res.text();
throw new Error(`Error ${res.status}: ${errorText}`);
}
return await res.json();
} finally {
if (loading) loading(false);
}
};

View File

@@ -242,13 +242,13 @@ export const mount = (c, tgt) => {
t.replaceChildren(i._cnt); MOUNTED.set(t, i); return i; t.replaceChildren(i._cnt); MOUNTED.set(t, i); return i;
}; };
const htmlTags = "a abbr article aside audio b blockquote br button canvas caption cite code col colgroup datalist dd del details dfn dialog div dl dt em embed fieldset figcaption figure footer form h1 h2 h3 h4 h5 h6 header hr i iframe img input ins kbd label legend li main mark meter nav object ol optgroup option output p picture pre progress section select slot small source span strong sub summary sup svg table tbody td template textarea tfoot th thead time tr u ul video"; // const htmlTags = "a abbr article aside audio b blockquote br button canvas caption cite code col colgroup datalist dd del details dfn dialog div dl dt em embed fieldset figcaption figure footer form h1 h2 h3 h4 h5 h6 header hr i iframe img input ins kbd label legend li main mark meter nav object ol optgroup option output p picture pre progress section select slot small source span strong sub summary sup svg table tbody td template textarea tfoot th thead time tr u ul video";
export const SigPro = { $, watch, batch, h, fragment, render, mount, when, each, onUnmount, val, isA, isF, isO }; export const SigPro = { $, watch, batch, h, fragment, render, mount, when, each, onUnmount, val, isA, isF, isO };
if (typeof window !== "undefined") { if (typeof window !== "undefined") {
window.SigPro = SigPro; window.SigPro = SigPro;
htmlTags.split(" ").forEach(tag => { // htmlTags.split(" ").forEach(tag => {
window[tag] = (props, children) => h(tag, props, children); // window[tag] = (props, children) => h(tag, props, children);
}); // });
} }

21
src/sigpro.locale.js Normal file
View File

@@ -0,0 +1,21 @@
const { $ } = window.SigPro;
const currentLocale = $("en");
const translations = {};
export const addLang = obj => {
for (const locale of Object.keys(obj)) {
if (!translations[locale]) translations[locale] = {};
Object.assign(translations[locale], obj[locale]);
}
};
export const setLocale = locale => {
if (locale && translations[locale]) {
currentLocale(locale);
}
};
export const t = key => {
return () => translations[currentLocale()]?.[key] ?? key;
};

49
src/sigpro.router.js Normal file
View File

@@ -0,0 +1,49 @@
const { $, h, watch, render, isF } = window.SigPro;
const getHash = () => window.location.hash.slice(1) || "/";
const currentPath = $(getHash());
window.addEventListener("hashchange", () => currentPath(getHash()));
export const routerParams = $({});
export const router = routes => {
const hook = h("div", { class: "router-hook" });
let currentView = null;
watch([currentPath], () => {
const cur = currentPath();
const route = routes.find(r => {
const p1 = r.path.split("/").filter(Boolean);
const p2 = cur.split("/").filter(Boolean);
return p1.length === p2.length && p1.every((p, i) => p[0] === ":" || p === p2[i]);
}) || routes.find(r => r.path === "*");
if (route) {
currentView?.destroy();
const params = {};
route.path.split("/").filter(Boolean).forEach((p, i) => {
if (p[0] === ":") params[p.slice(1)] = cur.split("/").filter(Boolean)[i];
});
routerParams(params);
currentView = render(() => isF(route.component) ? route.component(params) : route.component);
hook.replaceChildren(currentView.container);
}
});
hook.destroy = () => {
currentView?.destroy();
};
return hook;
};
router.params = routerParams;
router.to = p => window.location.hash = p.replace(/^#?\/?/, "#/");
router.back = () => window.history.back();
router.path = () => currentPath();

View File

@@ -1,90 +0,0 @@
/// <reference path="../sigpro.d.ts" />
const { $, h, watch, render, isF } = window.SigPro;
// router
export const router = routes => {
const getHash = () => window.location.hash.slice(1) || "/";
const path = $(getHash());
const handler = () => path(getHash());
window.addEventListener("hashchange", handler);
const hook = h("div", { class: "router-hook" });
let currentView = null;
watch([path], () => {
const cur = path();
const route = routes.find(r => {
const p1 = r.path.split("/").filter(Boolean);
const p2 = cur.split("/").filter(Boolean);
return p1.length === p2.length && p1.every((p, i) => p[0] === ":" || p === p2[i]);
}) || routes.find(r => r.path === "*");
if (route) {
currentView?.destroy();
const params = {};
route.path.split("/").filter(Boolean).forEach((p, i) => {
if (p[0] === ":") params[p.slice(1)] = cur.split("/").filter(Boolean)[i];
});
router.params(params);
currentView = render(() => isF(route.component) ? route.component(params) : route.component);
hook.replaceChildren(currentView.container);
}
});
hook.destroy = () => {
window.removeEventListener("hashchange", handler);
currentView?.destroy();
};
return hook;
};
router.params = $({});
router.to = p => window.location.hash = p.replace(/^#?\/?/, "#/");
router.back = () => window.history.back();
router.path = () => window.location.hash.replace(/^#/, "") || "/";
// db
export const db = async (url, data = {}, loading = null) => {
if (loading) loading(true);
try {
const res = await fetch(url, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data),
credentials: 'include'
});
if (!res.ok) {
const errorText = await res.text();
throw new Error(`Error ${res.status}: ${errorText}`);
}
return await res.json();
} finally {
if (loading) loading(false);
}
};
const currentLocale = $('en');
const translations = {};
// addLang
export const addLang = (obj) => {
for (const locale of Object.keys(obj)) {
if (!translations[locale]) translations[locale] = {};
Object.assign(translations[locale], obj[locale]);
}
};
// setLocale
export const setLocale = (locale) => {
if (locale && translations[locale]) {
currentLocale(locale);
}
};
// t
export const t = (key) => {
return () => translations[currentLocale()]?.[key] ?? key;
};