Update Docs

This commit is contained in:
2026-03-26 14:11:32 +01:00
parent 876874c2f0
commit 5ab0837400
26 changed files with 47 additions and 603 deletions

View File

@@ -1,19 +1,20 @@
# ⚡ Quick API Reference
# ⚡ Quick API Reference (V2)
SigPro is a high-performance micro-framework that updates the **Real DOM** surgically. No Virtual DOM, no unnecessary re-renders.
SigPro is a high-performance micro-framework that updates the **Real DOM** surgically. No Virtual DOM, no unnecessary re-renders, and built-in **Saneamiento** (memory cleanup).
## 🟢 Core Functions
| Function | Signature | Description |
| :--- | :--- | :--- |
| `$(val, key?)` | `(any, string?) => Signal` | Creates a **Signal**. If `key` is provided, it persists in `localStorage`. |
| `$(fn)` | `(function) => Computed` | Creates a **Computed Signal** that auto-updates when its dependencies change. |
| `$.effect(fn)` | `(function) => stopFn` | Runs a side-effect that tracks signals. Returns a function to manually stop it. |
| `$.html(tag, props, children)` | `(string, object, any) => HTMLElement` | The low-level DOM factory powering all tag constructors. |
| `$.router(routes)` | `(Array) => HTMLElement` | Initializes the hash-based router for SPAs. |
| `$.go(path)` | `(string) => void` | Programmatic navigation (e.g., `$.go('/home')`). |
| `$.mount(comp, target)` | `(any, string\|Node) => Runtime` | Mounts the application into the specified DOM element. |
| `$.ignore(fn)` | `(function) => any` | Executes code without tracking any signals inside it. |
| **`$(val, key?)`** | `(any, string?) => Signal` | Creates a **Signal**. If `key` is provided, it persists in `localStorage`. |
| **`$(fn)`** | `(function) => Computed` | Creates a **Computed Signal** that auto-updates when its dependencies change. |
| **`$.watch(fn)`** | `(function) => stopFn` | **Automatic Mode:** Tracks any signal touched inside. Returns a stop function. |
| **`$.watch(deps, fn)`** | `(Array, function) => stopFn` | **Explicit Mode:** Only runs when signals in `deps` change. Used for Saneamiento. |
| **`$.html(tag, props, kids)`** | `(string, obj, any) => Element` | The low-level DOM factory. Attaches `._cleanups` to every element. |
| **`$.If(cond, then, else?)`** | `(Signal, fn, fn?) => Node` | Reactive conditional. Automatically destroys the "else" branch memory. |
| **`$.For(list, itemFn)`** | `(Signal, fn) => Node` | Optimized list renderer. Manages individual item lifecycles. |
| **`$.router(routes)`** | `(Array) => Element` | Hash-based SPA router. Uses Explicit Watch to prevent memory leaks. |
| **`$.mount(node, target)`** | `(any, string\|Node) => Runtime` | Entry point. Creates a root instance with `.destroy()` capabilities. |
---
@@ -31,8 +32,18 @@ Tag({ attributes }, [children])
| Pattern | Code Example | Behavior |
| :--- | :--- | :--- |
| **Static** | `class: "text-red"` | Standard HTML attribute string. |
| **Reactive** | `disabled: isLoading` | Updates automatically when `isLoading()` changes. |
| **Two-way** | `$value: username` | Syncs input with signal **both ways** (Binding Operator). |
| **Text** | `P({}, () => count())` | Updates text node whenever `count` changes. |
| **Reactive** | `disabled: isLoading` | Updates automatically via internal `$.watch`. |
| **Two-way** | `$value: username` | **Binding Operator**: Syncs input $\leftrightarrow$ signal both ways. |
| **Text** | `P({}, () => count())` | Updates text node surgically without re-rendering the `P`. |
| **Boolean** | `hidden: isHidden` | Toggles the attribute based on signal truthiness. |
---
## 🧹 Saneamiento (Memory Management)
In SigPro V2, you rarely need to clean up manually, but the tools are there if you build custom components:
* **Automatic**: Anything inside `$.If`, `$.For`, or `$.router` is "swept" when it disappears.
* **Manual**: Use `instance.destroy()` for apps or `$.cleanup(el)` for manual DOM injections.
* **Internal**: Every element carries a `._cleanups` Set with its own reactive "kill-switches".

View File

@@ -1,55 +0,0 @@
# Live Playground
Experience **SigPro's** fine-grained reactivity in real-time. Feel free to tweak the signal values in the editor!
<iframe width="100%" height="600" src="//jsfiddle.net/natxocc/spwran02/4/embedded/" frameborder="0" loading="lazy" allowtransparency="true" allowfullscreen="true"></iframe>
```
---
### 2. Best Practices for Documentation
* **Tab Selection:** You can control which tabs are active by default by changing the URL segment after `/embedded/`.
* `js,result`: Shows the logic and the output.
* `html,js,result`: Shows the base structure, the logic, and the output.
* **Height Management:** For complex Store examples, increase the `height` attribute to `500` or `600` so the code is readable without internal scrolling.
* **Responsive Width:** Keeping `width="100%"` ensures the fiddle scales correctly on tablets and mobile devices.
---
### 3. Advanced: The "Fiddle" Component (Optional)
If you plan to have 10+ examples, you can create a global Vue component in VitePress. This keeps your Markdown files clean and allows you to change the theme or default height for all fiddles at once.
**Create `.vitepress/theme/components/Fiddle.vue`:**
```vue
<template>
<div class="fiddle-wrapper" style="margin: 20px 0;">
<iframe
width="100%"
:height="height"
:src="`//jsfiddle.net/natxocc/${id}/embedded/${tabs}/dark/`"
frameborder="0"
loading="lazy">
</iframe>
</div>
</template>
<script setup>
defineProps({
id: String, // e.g., "spwran02/4"
height: { default: '400' },
tabs: { default: 'js,result' }
})
</script>
```
**Usage in Markdown:**
```markdown
Check out this store example:
<Fiddle id="spwran02/4" height="500" />
```
---
### Why this is perfect for SigPro:
Because SigPro is **zero-dependency** and runs directly in the browser, your JSFiddle code will be exactly what the user copies into their own `index.html`. There is no hidden "build step" confusing the learner.

View File

@@ -1,223 +0,0 @@
/**
* SigPro - Atomic Unified Reactive Engine
* A lightweight, fine-grained reactivity system with built-in routing and plugin support.
* @author Gemini & User
*/
(() => {
/** @type {Function|null} Internal tracker for the currently executing reactive effect. */
let activeEffect = null;
/**
* @typedef {Object} SigPro
* @property {function(any|function, string=): Function} $ - Creates a Signal or Computed. Optional key for localStorage.
* @property {function(string, Object=, any=): HTMLElement} html - Creates a reactive HTML element.
* @property {function((HTMLElement|function), (HTMLElement|string)=): void} mount - Mounts a component to the DOM.
* @property {function(Array<Object>): HTMLElement} router - Initializes a hash-based router.
* @property {function(string): void} router.go - Programmatic navigation to a hash path.
* @property {function((function|string|Array<string>)): (Promise<SigPro>|SigPro)} plugin - Extends SigPro or loads external scripts.
*/
/**
* Creates a Signal (state) or a Computed/Effect (reaction).
* Supports optional persistence in localStorage.
* * @param {any|function} initial - Initial value or a function for computed logic.
* @param {string} [key] - Optional localStorage key for automatic state persistence.
* @returns {Function} A reactive accessor/mutator function.
*/
const $ = (initial, key) => {
const subs = new Set();
if (typeof initial === 'function') {
let cached;
const runner = () => {
const prev = activeEffect;
activeEffect = runner;
try {
const next = initial();
if (!Object.is(cached, next)) {
cached = next;
subs.forEach(s => s());
}
} finally { activeEffect = prev; }
};
runner();
return () => {
if (activeEffect) subs.add(activeEffect);
return cached;
};
}
if (key) {
const saved = localStorage.getItem(key);
if (saved !== null) {
try { initial = JSON.parse(saved); } catch (e) { }
}
}
return (...args) => {
if (args.length) {
const next = typeof args[0] === 'function' ? args[0](initial) : args[0];
if (!Object.is(initial, next)) {
initial = next;
if (key) localStorage.setItem(key, JSON.stringify(initial));
subs.forEach(s => s());
}
}
if (activeEffect) subs.add(activeEffect);
return initial;
};
};
/**
* Hyperscript engine to render reactive HTML nodes.
* @param {string} tag - The HTML tag name (e.g., 'div', 'button').
* @param {Object} [props] - Attributes, events (onclick), or reactive props ($value, $class).
* @param {any} [content] - String, Node, Array of nodes, or reactive function.
* @returns {HTMLElement} A live DOM element linked to SigPro signals.
*/
$.html = (tag, props = {}, content = []) => {
const el = document.createElement(tag);
if (typeof props !== 'object' || props instanceof Node || Array.isArray(props) || typeof props === 'function') {
content = props;
props = {};
}
for (let [key, val] of Object.entries(props)) {
if (key.startsWith('on')) {
el.addEventListener(key.toLowerCase().slice(2), val);
} else if (key.startsWith('$')) {
const attr = key.slice(1);
// Two-way binding for inputs
if ((attr === 'value' || attr === 'checked') && typeof val === 'function') {
const ev = attr === 'checked' ? 'change' : 'input';
el.addEventListener(ev, e => val(attr === 'checked' ? e.target.checked : e.target.value));
}
// Reactive attribute update
$(() => {
const v = typeof val === 'function' ? val() : val;
if (attr === 'value' || attr === 'checked') el[attr] = v;
else if (typeof v === 'boolean') el.toggleAttribute(attr, v);
else el.setAttribute(attr, v ?? '');
});
} else el.setAttribute(key, val);
}
const append = (c) => {
if (Array.isArray(c)) return c.forEach(append);
if (typeof c === 'function') {
const node = document.createTextNode('');
$(() => {
const res = c();
if (res instanceof Node) {
if (node.parentNode) node.replaceWith(res);
} else {
node.textContent = res ?? '';
}
});
return el.appendChild(node);
}
el.appendChild(c instanceof Node ? c : document.createTextNode(c ?? ''));
};
append(content);
return el;
};
const tags = ['div', 'span', 'p', 'button', 'h1', 'h2', 'h3', 'ul', 'ol', 'li', 'a', 'label', 'section', 'nav', 'main', 'header', 'footer', 'input', 'form', 'img', 'select', 'option', 'table', 'thead', 'tbody', 'tr', 'th', 'td', 'canvas', 'video', 'audio'];
tags.forEach(t => window[t] = (p, c) => $.html(t, p, c));
/**
* Application mounter.
* @param {HTMLElement|function} node - Root component or element to mount.
* @param {HTMLElement|string} [target=document.body] - Target element or CSS selector.
*/
$.mount = (node, target = document.body) => {
const el = typeof target === 'string' ? document.querySelector(target) : target;
if (el) {
el.innerHTML = '';
el.appendChild(typeof node === 'function' ? node() : node);
}
};
/**
* Initializes a reactive hash-based router.
* Maps URL hash changes to component rendering and supports Vite's dynamic imports.
* * @param {Array<{path: string, component: Function|Promise|HTMLElement}>} routes - Array of route objects.
* @returns {HTMLElement} A reactive div container that swaps content based on the current hash.
*/
$.router = (routes) => {
const sPath = $(window.location.hash.replace(/^#/, "") || "/");
window.addEventListener("hashchange", () => sPath(window.location.hash.replace(/^#/, "") || "/"));
return $.html('div', [
() => {
const current = sPath();
const cP = current.split('/').filter(Boolean);
const route = routes.find(r => {
const rP = r.path.split('/').filter(Boolean);
if (rP.length !== cP.length) return false;
return rP.every((part, i) => part.startsWith(':') || part === cP[i]);
}) || routes.find(r => r.path === "*");
if (!route) return $.html('h1', "404 - Not Found");
const rP = route.path.split('/').filter(Boolean);
const params = {};
rP.forEach((part, i) => {
if (part.startsWith(':')) params[part.slice(1)] = cP[i];
});
const result = typeof route.component === 'function' ? route.component(params) : route.component;
if (result instanceof Promise) {
const $lazyNode = $($.html('span', "Loading..."));
result.then(m => {
const content = m.default || m;
const finalView = typeof content === 'function' ? content(params) : content;
$lazyNode(finalView);
});
return () => $lazyNode();
}
return result instanceof Node ? result : $.html('span', String(result));
}
]);
};
/**
* Programmatically navigates to a specific path using the hash.
* * @param {string} path - The destination path (e.g., '/home' or 'settings').
* @example
* $.router.go('/profile/42');
*/
$.router.go = (path) => {
window.location.hash = path.startsWith('/') ? path : `/${path}`;
};
/**
* Polymorphic Plugin System.
* Registers internal functions or loads external .js files as plugins.
* @param {function|string|Array<string>} source - Plugin function or URL(s).
* @returns {Promise<SigPro>|SigPro} Resolves with the $ instance after loading or registering.
*/
$.plugin = (source) => {
if (typeof source === 'function') {
source($);
return $;
}
const urls = Array.isArray(source) ? source : [source];
return Promise.all(urls.map(url => new Promise((resolve, reject) => {
const script = document.createElement('script');
script.src = url;
script.async = true;
script.onload = () => {
console.log(`%c[SigPro] Plugin Loaded: ${url}`, "color: #51cf66; font-weight: bold;");
resolve();
};
script.onerror = () => reject(new Error(`[SigPro] Failed to load: ${url}`));
document.head.appendChild(script);
}))).then(() => $);
};
window.$ = $;
})();