Update docs
All checks were successful
Deploy Docs to Synology / deploy (push) Successful in 3s

This commit is contained in:
2026-04-25 20:28:38 +02:00
parent de4b09324d
commit f4654a938a
20 changed files with 2120 additions and 1202 deletions

View File

@@ -2,18 +2,19 @@
* **Introduction**
* [Installation](install.md)
* [Examples](examples.md)
* [Vite Plugin](vite/plugin.md)
* **API Reference**
* [Quick Start](api/quick.md)
* [Signals & Proxies](api/signal.md)
* [Watch](api/watch.md)
* [If](api/if.md)
* [For](api/for.md)
* [Router](api/router.md)
* [Mount](api/mount.md)
* [Tag](api/html.md)
* [watch](api/watch.md)
* [when](api/when.md)
* [each](api/each.md)
* [router](api/router.md)
* [fx](api/fx.md)
* [req](api/req.md)
* [mount](api/mount.md)
* [h](api/h.md)
* [Tags](api/tags.md)
* [Global Store](api/global.md)
* [JSX Style](api/jsx.md)

160
docs/api/each.md Normal file
View File

@@ -0,0 +1,160 @@
# Reactive Lists: `each( )`
The `each` function is a highperformance keyed list renderer. It maps a reactive array to DOM nodes and surgically updates only the items that changed (added, removed, or reordered). Unlike a simple `.map()`, `each` reuses DOM nodes and preserves internal state.
## Function Signature
```typescript
each(
source: Signal<any[]> | (() => any[]) | any[],
itemFn: (item: any, index: number) => Node | (() => Node),
keyFn?: (item: any, index: number) => string | number
): HTMLElement
```
| Parameter | Type | Required | Description |
| :--- | :--- | :--- | :--- |
| **`source`** | `Signal`, `() => any[]`, or `any[]` | Yes | The reactive array to iterate over. |
| **`itemFn`** | `(item, index) => Node` | Yes | Returns a DOM node (or a function that returns a node) for each item. |
| **`keyFn`** | `(item, index) => string/number` | No | Extracts a unique key. Default: `item?.id ?? index`. |
**Returns:** A `div` with `style="display: contents"` that contains the live list.
---
## Usage Patterns
### 1. Basic Keyed List (Recommended)
Always provide a unique `id` as the key. This allows SigPro to reuse DOM nodes when the list is reordered or filtered.
```javascript
const users = $([
{ id: 1, name: "Alice" },
{ id: 2, name: "Bob" }
]);
ul({ class: "list" }, [
each(users,
(user) => li({ class: "p-2" }, user.name),
(user) => user.id // stable unique key
)
]);
```
### 2. Automatic Key (Simple Lists)
If you omit the `keyFn`, `each` defaults to `item?.id ?? index`. For primitive arrays or objects without an `id`, the index is used.
```javascript
const tags = $(["Tech", "JS", "Web"]);
div({ class: "flex gap-1" }, [
each(tags, (tag) => span({ class: "badge" }, tag))
// key defaults to index (0,1,2) fine for static order
]);
```
### 3. Dynamic Content Using Functions
If your `itemFn` returns a **function**, that function is reexecuted every time the items data changes (but the node is reused).
```javascript
const todos = $([
{ id: 1, text: "Learn SigPro", done: false }
]);
each(todos,
(todo) => div([
input({ type: "checkbox", checked: () => todo.done, onInput: e => todo.done = e.target.checked }),
span(() => todo.done ? s(todo.text) : todo.text)
]),
(todo) => todo.id
);
```
### 4. Source as a Plain Array or Function
`source` can be a plain array (nonreactive) or a function that returns an array it will still react to changes if signals are read inside the function.
```javascript
const filter = $("all");
const filteredTodos = () => {
const all = todos();
if (filter() === "active") return all.filter(t => !t.done);
return all;
};
each(filteredTodos, (todo) => li(todo.text), (todo) => todo.id);
```
---
## How It Works (Reconciliation)
When the `source` changes, `each`:
1. **Compares keys** between the old and new items using the `keyFn`.
2. **Reuses existing DOM nodes** for keys that stay the same.
3. **Moves nodes** if order changed (no recreation).
4. **Creates new nodes** for new keys.
5. **Destroys nodes** for removed keys cleans up all effects, event listeners, and child components.
> This is much more efficient than destroying and rebuilding the whole list on every update.
---
## Performance Tips
- **Stable keys** Use a real `id` (like a database primary key). Avoid `Math.random()` or array `index` for lists that can be reordered.
- **State preservation** If a list item contains an input or a local state, using a stable key ensures that state is preserved even when the list is filtered or sorted.
- **Lazy item functions** If an item is expensive to render, wrap it in a function: `() => ExpensiveComponent(item)`. The component is only created when the item actually appears in the DOM.
---
## Summary Comparison
| Feature | Standard `Array.map` | SigPro `each` |
| :--- | :--- | :--- |
| **Rerenders on change** | Recreates entire list | Only adds/removes/moves changed items |
| **DOM nodes** | New nodes every time | **Reused via keys** |
| **Memory cleanup** | Manual (or leak) | **Automatic** (destroy on removal) |
| **Internal state per item** | Lost on every update | **Preserved** (if key stable) |
| **Reactivity** | None (manual rerender) | Builtin, finegrained |
---
## Complete Example
```javascript
const items = $([
{ id: 1, name: "Apple", price: 1.2 },
{ id: 2, name: "Banana", price: 0.8 }
]);
const addItem = () => {
const newId = Date.now();
items([...items(), { id: newId, name: `Item ${newId}`, price: 1.0 }]);
};
const removeItem = (id) => {
items(items().filter(i => i.id !== id));
};
const App = () =>
div([
button({ onClick: addItem }, "Add item"),
ul(
each(items,
(item) => li([
span(`${item.name} $${item.price}`),
button({ onClick: () => removeItem(item.id) }, "X")
]),
(item) => item.id
)
)
]);
mount(App, '#app');
```

View File

@@ -1,83 +0,0 @@
# Reactive Lists: `For( )`
The `For` function is a high-performance list renderer. It maps an array (or a Signal containing an array) to DOM nodes. Unlike a simple `.map()`, `For` is **keyed**, meaning it only updates, moves, or deletes the specific items that changed.
## Function Signature
```typescript
For(
source: Signal<any[]> | Function | any[],
render: (item: any, index: number) => HTMLElement,
keyFn?: (item: any, index: number) => string | number
): HTMLElement
```
| Parameter | Type | Required | Description |
| :--- | :--- | :--- | :--- |
| **`source`** | `Signal` | Yes | The reactive array to iterate over. |
| **`render`** | `Function` | Yes | A function that returns a component or Node for each item. |
| **`keyFn`** | `Function` | **No** | A function to extract a **unique ID**. If omitted, it defaults to the `index`. |
**Returns:** A `div` element with `display: contents` containing the live list.
---
## Usage Patterns
### 1. Basic Keyed List (Recommended)
Always use a unique property (like an `id`) as a key to ensure SigPro doesn't recreate nodes unnecessarily when reordering or filtering.
```javascript
const users = $([
{ id: 1, name: "Alice" },
{ id: 2, name: "Bob" }
]);
Ul({ class: "list" }, [
For(users,
(user) => Li({ class: "p-2" }, user.name),
(user) => user.id // Stable and unique key
)
]);
```
### 2. Simplified Usage (Automatic Key)
If you omit the third parameter, `For` will automatically use the array index as the key. This is ideal for simple lists that don't change order frequently.
```javascript
const tags = $(["Tech", "JS", "Web"]);
// No need to pass keyFn if the index is sufficient
Div({ class: "flex gap-1" }, [
For(tags, (tag) => Badge(tag))
]);
```
---
## How it Works (The Reconciliation)
When the `source` signal changes, `For` performs the following steps:
1. **Key Diffing**: It compares the new keys with the previous ones stored in an internal `Map`.
2. **Node Reuse**: If a key already exists, the DOM node is **reused** and moved to its new position. No new elements are created.
3. **Physical Cleanup**: If a key disappears from the list, SigPro calls `.destroy()` to stop reactivity and physically removes the node from the DOM to prevent memory leaks.
---
## Performance Tips
* **Stable Keys**: Never use `Math.random()` as a key. This will force SigPro to destroy and recreate the entire list on every update, killing performance.
* **State Preservation**: If your list items have internal state (like an input with text), `For` ensures that state is preserved even if the list is reordered, as long as the key (`id`) remains the same.
---
## Summary Comparison
| Feature | Standard `Array.map` | SigPro `For` |
| :--- | :--- | :--- |
| **Re-render** | Re-renders everything | Only updates changes |
| **DOM Nodes** | Re-created every time | **Reused via Keys** |
| **Memory** | Potential leaks | **Automatic Cleanup** |
| **State** | Lost on re-render | **Preserved per item** |
| **Ease of Use** | Manual logic required | **Optional (fallback to index)** |

182
docs/api/fx.md Normal file
View File

@@ -0,0 +1,182 @@
# Animation Helper: `fx( )`
The `fx` function applies simple **enter animations** to DOM elements. You can either use a predefined CSS keyframes animation or declare inline transition effects (scale, slide, rotate, blur). It is designed to be used when dynamically creating elements especially inside `when` or `each` branches.
## Function Signature
```typescript
fx(
options: {
name?: string; // CSS keyframes animation name (will append '-in')
duration?: number; // Animation duration in ms (default: 200)
scale?: boolean; // Start with scale(0.95) → none
slide?: boolean; // Start with translateY(-10px) → none
rotate?: boolean; // Start with rotate(-2deg) → none
blur?: boolean; // Start with blur(4px) → none
},
child: Node | (() => Node)
): Node
```
| Parameter | Type | Required | Description |
| :--- | :--- | :--- | :--- |
| **`options`** | `object` | Yes | Animation configuration. |
| **`options.name`** | `string` | No | Name of a CSS `@keyframes` animation. The actual animation name becomes `${name}-in`. |
| **`options.duration`** | `number` | No | Duration in milliseconds (default `200`). |
| **`options.scale`** | `boolean` | No | Add a scale transform from `0.95` to `none`. |
| **`options.slide`** | `boolean` | No | Add a vertical slide from `translateY(-10px)` to `none`. |
| **`options.rotate`** | `boolean` | No | Add a small rotation from `rotate(-2deg)` to `none`. |
| **`options.blur`** | `boolean` | No | Add a blur filter from `blur(4px)` to `none`. |
| **`child`** | `Node` or `() => Node` | Yes | The element to animate. If a function is passed, it is called to obtain the node. |
**Returns:** The same DOM node (or the child if it is not a `Node`), after applying the animation setup.
---
## Usage Patterns
### 1. Named CSS Keyframes Animation
Define a `@keyframes` rule in your CSS, for example:
```css
@keyframes fade-in {
from { opacity: 0; }
to { opacity: 1; }
}
```
Then apply it with `fx`:
```javascript
const MyComponent = () =>
fx({ name: "fade", duration: 300 },
div("I will fade in")
);
```
> The animation name used is `${name}-in`. In this example: `fade-in`.
### 2. Inline Transition (Scale + Opacity)
No CSS keyframes needed. The element starts with `opacity: 0` and `transform: scale(0.95)`, then transitions to `opacity: 1` and `transform: none`.
```javascript
fx({ scale: true, duration: 200 },
button({ onClick: () => alert("Hi") }, "Click me")
);
```
### 3. Combining Multiple Effects
You can combine `scale`, `slide`, `rotate`, and `blur` at the same time.
```javascript
fx({ scale: true, slide: true, blur: true, duration: 250 },
div({ class: "card" }, "Smooth enter")
);
```
### 4. Using with `when` (Conditional Rendering)
Wrap the branch content with `fx` to animate entering elements.
```javascript
when(show,
() => fx({ slide: true },
div("This slides in when visible")
)
);
```
### 5. Using a Function as Child
If the element is created inside a function (e.g. to avoid recreation until needed), pass a function that returns the node.
```javascript
fx({ scale: true },
() => div("Lazy created and then animated")
);
```
---
## What Happens Under the Hood
### With `name` (CSS animation)
- Sets `el.style.animation = `${name}-in ${duration}ms``.
- The element animates according to your keyframes.
- No further inline style changes are applied.
### Without `name` (transition effects)
- Sets `el.style.transition = `all ${duration}ms ease``.
- Sets initial `opacity: 0`.
- Applies initial transforms (`scale`, `slide`, `rotate`) if selected.
- Applies initial `filter: blur(4px)` if `blur: true`.
- In the next animation frame (via `requestAnimationFrame`), sets:
- `opacity: 1`
- `transform: none`
- `filter: none`
- The element transitions smoothly from the start state to the final state.
> **Important:** The element must be **in the DOM** when the animation starts. `fx` does **not** automatically mount the node you must already have appended it or be about to mount it. In practice, when you call `fx` inside a component that is being mounted, the element will be added to the DOM shortly after, and the animation runs correctly.
---
## Complete Example
```javascript
const App = () =>
div([
fx({ name: "fade", duration: 400 },
h1("Welcome to SigPro")
),
fx({ scale: true, slide: true, duration: 250 },
button({ onClick: () => alert("Animated!") }, "Animated button")
)
]);
mount(App, "#app");
```
With accompanying CSS:
```css
@keyframes fade-in {
from { opacity: 0; transform: translateY(10px); }
to { opacity: 1; transform: translateY(0); }
}
```
---
## Notes
- `fx` is **not** required for basic reactivity it is purely a visual helper for enter animations.
- For exit animations (when an element is removed), use CSS transitions on the element itself combined with `when` or consider adding a wrapper that delays removal. SigPro does not include builtin exit animations.
- The function returns the same node you passed, so you can inline it inside `h` or tag helpers:
```javascript
div([
fx({ scale: true }, span("Hello"))
])
```
- If `child` is not a DOM node (e.g., a string or number), `fx` returns it unchanged no animation is applied.
---
## Summary
| Option | Effect |
| :--- | :--- |
| `name` | Uses `@keyframes ${name}-in` CSS animation. |
| `duration` | Controls animation/transition length (ms). |
| `scale` | Start scale `0.95``none`. |
| `slide` | Start `translateY(-10px)``none`. |
| `rotate` | Start `rotate(-2deg)``none`. |
| `blur` | Start `blur(4px)``none`. |
Combine options to create smooth, modern entrance effects without writing extra CSS.

View File

@@ -1,64 +1,67 @@
# Global State Management: Atomic & Modular
SigPro leverages the native power and efficiency of **Signals** to create robust global stores with **0% complexity**. While other frameworks force you into heavy libraries and rigid boilerplate (Redux, Pinia, or Svelte Stores), SigPro treats "The Store" as a simple architectural choice: **defining a Signal outside of a component.**
SigPro leverages the native power and efficiency of **signals** to create robust global stores with **zero complexity**. While other frameworks force you into heavy libraries and rigid boilerplate (Redux, Pinia, or Svelte stores), SigPro treats “the store as a simple architectural choice: **defining a signal outside of a component.**
## Modular Organization (Zero Constraints)
You are not restricted to a single `store.js`. You can organize your state by **feature**, **domain**, or **page**. Since a SigPro store is just a standard JavaScript module exporting Signals, you can name your files whatever you like (`auth.js`, `cart.js`, `settings.js`) to keep your logic clean.
You are not restricted to a single `store.js`. You can organize your state by **feature**, **domain**, or **page**. Since a SigPro store is just a standard JavaScript module exporting signals, you can name your files whatever you like (`auth.js`, `cart.js`, `settings.js`) to keep your logic clean.
### 1. File-Based Stores (`<any-name>.js`)
Creating a dedicated file allows you to export only what you need. This modularity ensures **Tree Shaking** works perfectly—you never load state that isn't imported.
### 1. FileBased Stores (`<any-name>.js`)
Creating a dedicated file allows you to export only what you need. This modularity ensures **tree shaking** works perfectly you never load state that isnt imported.
```javascript
// auth.js
import SigPro from "sigpro";
import { $ } from 'sigpro'; // or just rely on global `$` after import
// A simple global signal
export const user = $({ name: "Guest", loggedIn: false });
// A persistent global signal (auto-syncs with localStorage via native key)
// A persistent global signal (autosyncs with localStorage)
export const theme = $("light", "app-theme-pref");
// A computed global signal that reacts to the 'user' signal
export const welcomeMessage = $(() => `Welcome back, ${user().name}!`);
```
### 2. Cross-Component Consumption
Once exported, these signals act as a **Single Source of Truth**. SigPro ensures that if a signal changes in one file, every component importing it across the entire app updates **atomically** without a full re-render.
### 2. CrossComponent Consumption
Once exported, these signals act as a **single source of truth**. SigPro ensures that if a signal changes in one file, every component importing it across the entire app updates **atomically** without a full rerender.
```javascript
// Profile.js
import { user } from "./auth.js";
const Profile = () => Div([
H2(user().name),
Button({ onclick: () => user({ name: "John Doe", loggedIn: true }) }, "Log In")
const Profile = () => div([
h2(() => user().name),
button({ onclick: () => user({ name: "John Doe", loggedIn: true }) }, "Log In")
]);
// Navbar.js
import { welcomeMessage, theme } from "./auth.js";
const Navbar = () => Nav({ class: theme }, [
Span(welcomeMessage)
const Navbar = () => nav({ class: () => theme() }, [
span(() => welcomeMessage())
]);
```
---
## Why SigPro Stores are Superior
## Why SigPro Stores Are Superior
| Feature | SigPro | Redux / Pinia / Svelte |
| :--- | :--- | :--- |
| **Boilerplate** | **0%** (Just a variable) | High (Actions, Reducers, Wrappers) |
| **Organization** | **Unlimited** (Any filename) | Often strictly "Store" or "Actions" |
| **Persistence** | **Native** (Just add a key) | Requires Middleware / Plugins |
| **Learning Curve** | **Instant** | Steep / Complex |
| **Bundle Size** | **0KB** (Part of the core) | 10KB - 30KB+ |
| Feature | SigPro | Redux / Pinia / Svelte |
| :-------------------- | :---------------------------- | :------------------------------ |
| **Boilerplate** | **0%** (just a variable) | High (actions, reducers, stores)|
| **Organization** | **Unlimited** (any filename) | Often strictly “store” files |
| **Persistence** | **Native** (just add a key) | Requires middleware / plugins |
| **Learning Curve** | **Instant** | Steep / complex |
| **Bundle Size** | **0KB** (part of core) | 10KB 30KB+ |
---
## The "Persistence" Advantage
The magic of SigPros `$(value, "key")` is that it works identically for local and global states. By simply adding a second argument, your Modular Store survives browser refreshes automatically. No manual `localStorage.getItem` or `JSON.parse` logic is ever required.
## The Persistence Advantage
The magic of SigPros `$(value, "key")` is that it works identically for local and global states. By simply adding a second argument, your modular store survives browser refreshes automatically. No manual `localStorage.getItem` or `JSON.parse` logic is ever required.
```javascript
// This single line creates a global, reactive,
@@ -69,7 +72,68 @@ export const cart = $([], "session-cart");
---
## Summary of Scopes
* **Local Scope:** Signal defined **inside** a component. Unique to every instance created.
* **Module Scope:** Signal defined **outside** a component (same file). Shared by all instances within that specific file.
* **Global Scope:** Signal defined in a **separate file**. Shared across the entire application by any importing module.
* **Persistent Scope:** Any Signal defined with a **key**. Shared globally and remembered after a page reload.
| Scope | Definition | Behaviour |
| :-------------- | :-------------------------------------------------------------- | :-------------------------------------------- |
| **Local** | Signal defined **inside** a component | Unique to every component instance |
| **Module** | Signal defined **outside** a component (same file) | Shared by all instances within that file |
| **Global** | Signal defined in a **separate file** and imported | Shared across the entire application |
| **Persistent** | Any Signal defined with a **key** (e.g., `$([], "cart")`) | Shared globally and persisted in `localStorage` |
---
## Complete Example Todo Store
```javascript
// store/todos.js
import { $ } from 'sigpro';
export const todos = $([], "todos");
export const filter = $("all");
export const addTodo = (text) => {
todos([...todos(), { id: Date.now(), text, done: false }]);
};
export const toggleTodo = (id) => {
todos(todos().map(t => t.id === id ? { ...t, done: !t.done } : t));
};
export const filteredTodos = $(() => {
const all = todos();
if (filter() === "active") return all.filter(t => !t.done);
if (filter() === "completed") return all.filter(t => t.done);
return all;
});
```
```javascript
// components/TodoApp.js
import { todos, filter, addTodo, toggleTodo, filteredTodos } from "../store/todos.js";
const TodoApp = () =>
div({ class: "todo-app" }, [
input({ placeholder: "Add todo...", onKeyDown: (e) => {
if (e.key === "Enter" && e.target.value) {
addTodo(e.target.value);
e.target.value = "";
}
}}),
div({ class: "filters" }, [
button({ onClick: () => filter("all") }, "All"),
button({ onClick: () => filter("active") }, "Active"),
button({ onClick: () => filter("completed") }, "Completed")
]),
ul(
each(filteredTodos,
(todo) => li([
input({ type: "checkbox", checked: () => todo.done, onInput: () => toggleTodo(todo.id) }),
span(() => todo.done ? s(todo.text) : todo.text)
]),
(todo) => todo.id
)
)
]);
mount(TodoApp, "#app");
```

159
docs/api/h.md Normal file
View File

@@ -0,0 +1,159 @@
# Hyperscript Function: `h( )`
The `h` function is the **core DOM builder** of SigPro. It creates DOM elements from a tag name, props, and children. While the global tag helpers (`div()`, `button()`, etc.) are built on top of `h`, you may need `h` directly for dynamic tag names or when you prefer an explicit function style.
## Function Signature
```typescript
h(
tag: string | Function,
props?: object | Node | any[],
children?: any
): Node
```
| Parameter | Type | Description |
| :--- | :--- | :--- |
| **`tag`** | `string` or `Function` | HTML tag name (e.g., `"div"`) or a component function. |
| **`props`** | `object` | Optional. Attributes, event handlers, refs, etc. If not an object, it becomes `children`. |
| **`children`** | `any` | Optional. Text, nodes, arrays, or reactive functions. |
**Returns:** A DOM node (or an array of nodes when the tag is a component that returns an array).
---
## Usage Patterns
### 1. Basic Element Creation
```javascript
// Simple div with text
h('div', {}, 'Hello world');
// With attributes
h('button', { class: 'btn', onclick: () => alert('clicked') }, 'Click me');
```
### 2. Nested Children
Children can be a single node, an array, or a function.
```javascript
h('div', { class: 'container' }, [
h('h1', {}, 'Title'),
h('p', {}, 'Paragraph text')
]);
```
### 3. Reactive Children
Pass a **function** as a child it will be reevaluated whenever any signal inside changes, and the DOM will be patched surgically.
```javascript
const count = $(0);
h('div', {}, [
h('p', {}, () => `Count: ${count()}`),
h('button', { onclick: () => count(count() + 1) }, '+1')
]);
```
### 4. Reactive Attributes
Pass a function as an attribute value to keep it dynamic.
```javascript
const theme = $('dark');
h('div', { class: () => `box ${theme()}` }, 'Themed box');
```
### 5. TwoWay Binding
Assign a signal directly to `value` or `checked` on form elements SigPro automatically syncs both ways.
```javascript
const name = $('');
h('input', {
type: 'text',
value: name, // two-way binding
placeholder: 'Your name'
});
h('p', {}, () => `Hello, ${name()}`);
```
### 6. Component Functions as `tag`
You can pass a component function directly to `h`. SigPro will execute it with the provided props and an `emit` helper for custom events.
```javascript
const Button = (props, { children }) =>
h('button', { class: 'btn', onclick: props.onClick }, children);
const App = () =>
h('div', {}, [
h(Button, { onClick: () => alert('clicked') }, 'Custom button')
]);
```
### 7. SVG Elements
Use `h` with SVG tag names SigPro automatically applies the correct namespace.
```javascript
h('svg', { width: 100, height: 100 }, [
h('circle', { cx: 50, cy: 50, r: 40, fill: 'red' })
]);
```
---
## Special Props
| Prop | Behaviour |
| :--- | :--- |
| **`ref`** | `ref: (el) => ...` or `ref: { current: null }` provides direct access to the DOM node after creation. |
| **`onEvent`** | Any prop starting with `on` (e.g., `onClick`, `onInput`) is treated as an event listener. Automatically removed on cleanup. |
| **`value` / `checked`** | When a signal is passed, creates twoway binding for inputs, textareas, and selects. |
| **`class`** | You can use `class` (not `className`). Accepts a string or a reactive function. |
---
## `h` vs Global Tag Helpers
| Feature | `h('div', ...)` | `div(...)` |
| :--- | :--- | :--- |
| **Dynamic tag names** | ✅ `h(tagName, ...)` | ❌ Must know tag name at write time |
| **Explicit style** | More verbose | Cleaner, DSLlike |
| **Tree shaking** | Same | Same (helpers are generated once) |
| **Performance** | Identical | Identical (helpers call `h` internally) |
> **Recommendation:** Use global tag helpers (`div()`, `button()`, etc.) for most cases they are shorter and more readable. Use `h` directly only when the tag name is dynamic (e.g., `h(props.tag, ...)`).
---
## Complete Example
```javascript
import { $, h, mount } from 'sigpro';
const dynamicTag = $('h1');
const App = () =>
h('div', { class: 'demo' }, [
h(dynamicTag(), {}, () => `Current tag: ${dynamicTag()}`),
h('button', { onclick: () => dynamicTag(dynamicTag() === 'h1' ? 'h2' : 'h1') }, 'Toggle heading size')
]);
mount(App, '#app');
```
---
## Summary
- `h` is the lowlevel DOM builder used internally by all tag helpers.
- It supports reactive attributes, reactive children, twoway binding, event listeners, and SVG.
- Use `h` directly when you need a **dynamic tag name**; otherwise, prefer the convenient global helpers.
- Components written with `h` are fully reactive and automatically cleaned up.

View File

@@ -1,104 +0,0 @@
# The DOM Factory: `Tag( )`
`Tag` is the internal engine that creates, attributes, and attaches reactivity to DOM elements. It uses `Watch` to maintain a live, high-performance link between your Signals and the Document Object Model.
## Function Signature
```typescript
Tag(tagName: string, props?: Object, children?: any[] | any): HTMLElement
```
| Parameter | Type | Required | Description |
| :--- | :--- | :--- | :--- |
| **`tagName`** | `string` | Yes | Valid HTML tag name (e.g., `"div"`, `"button"`). |
| **`props`** | `Object` | No | Attributes, Events, Two-way bindings, and **Refs**. |
| **`children`** | `any` | No | Nested elements, text strings, or reactive functions. |
---
## Key Features
### 1. Manual DOM Access: `ref`
The `ref` property allows you to capture the underlying `HTMLElement` as soon as it is created. This is essential for integrating 3rd-party libraries (like AG Grid or Chart.js) or managing focus and measurements.
* **Callback Ref**: A function that receives the `HTMLElement` immediately.
* **Object Ref**: An object with a `.current` property assigned to the `HTMLElement`.
```javascript
// Auto-focus on mount
Input({ ref: (el) => el.focus() });
// Capturing a node for an external library
const gridDiv = { current: null };
Div({ ref: gridDiv, class: "ag-theme-quartz" });
```
### 2. Attribute Handling
SigPro intelligently decides how to apply each property:
* **Standard Props**: Applied via `setAttribute` or direct property assignment.
* **Class Names**: Supports `class` or `className` interchangeably.
* **Boolean Props**: Automatic handling for `checked`, `disabled`, `hidden`, etc.
* **Note**: The `ref` property is intercepted and **never** rendered as an attribute in the HTML.
### 3. Event Listeners
Events are defined by the `on` prefix. SigPro automatically registers the listener and ensures it is cleaned up when the element is destroyed.
```javascript
Button({
onclick: (e) => console.log("Clicked!", e),
}, "Click Me");
```
### 4. Reactive Attributes (One-Way)
If an attribute value is a **function** (like a Signal), `Tag` creates an internal **`Watch`** to keep the DOM in sync with the state.
```javascript
Div({
// Updates the class whenever 'theme()' changes
class: () => theme() === "dark" ? "bg-black" : "bg-white"
});
```
### 5. Smart Two-Way Binding (Automatic)
SigPro automatically enables **bidirectional synchronization** when it detects a **Signal** assigned to a form-capable attribute (`value` or `checked`) on an input element (`input`, `textarea`, `select`).
```javascript
// Syncs input value <-> signal automatically
Input({
type: "text",
value: username // No special symbols needed!
})
```
> **Note:** To use a Signal as **read-only** in an input, wrap it in an anonymous function: `value: () => username()`.
### 6. Reactive Children
Children can be static or dynamic. When a child is a function, SigPro creates a reactive boundary using `Watch` for that specific part of the DOM.
```javascript
Div({}, [
H1("Static Title"),
// Only this text node re-renders when 'count' changes
() => `Current count: ${count()}`
]);
```
---
## Memory Management (Internal)
Every element created with `Tag` is "self-aware" regarding its reactive dependencies.
* **`._cleanups`**: A hidden `Set` attached to the element that stores all `stop()` functions from its internal `Watch` calls and event listeners.
* **Lifecycle**: When an element is removed by a Controller (`If`, `For`, or `Router`), SigPro performs a recursive **"sweep"** to execute these cleanups, ensuring **zero memory leaks**.
---
## Tag Constructors (The Shortcuts)
Instead of writing `Tag("div", ...)` every time, SigPro provides PascalCase global functions for all standard HTML tags. These are direct mappings to the `Tag` factory.
```javascript
// This:
Div({ class: "wrapper" }, [ Span("Hello") ])
// Is exactly equivalent to:
Tag("div", { class: "wrapper" }, [ Tag("span", {}, "Hello") ])
```

View File

@@ -1,180 +0,0 @@
# Reactive Branching: `If( )`
The `If` function is a reactive control flow operator. It manages the conditional rendering of components with optional smooth transitions, ensuring that only the active branch exists in the DOM and in memory.
## Function Signature
```typescript
If(
condition: Signal<boolean> | Function,
thenVal: Component | Node,
otherwiseVal?: Component | Node,
transition?: Transition
): HTMLElement
```
| Parameter | Type | Required | Description |
| :--- | :--- | :--- | :--- |
| **`condition`** | `Signal` | Yes | A reactive source that determines which branch to render. |
| **`thenVal`** | `any` | Yes | The content to show when the condition is **truthy**. |
| **`otherwiseVal`** | `any` | No | The content to show when the condition is **falsy** (defaults to null). |
| **`transition`** | `Transition` | No | Optional animation hooks for enter/exit transitions. |
**Returns:** A `div` element with `display: contents` that acts as a reactive portal for the branches.
---
## Transition Interface
```typescript
interface Transition {
/** Called when branch enters. Use for fade-in, slide-in, etc. */
in: (el: HTMLElement) => void;
/** Called when branch leaves. Call `done()` when animation completes. */
out: (el: HTMLElement, done: () => void) => void;
}
```
### Example: Fade Transition
```javascript
const fade = {
in: (el) => {
el.style.opacity = "0";
el.style.transition = "opacity 0.3s";
requestAnimationFrame(() => {
el.style.opacity = "1";
});
},
out: (el, done) => {
el.style.transition = "opacity 0.3s";
el.style.opacity = "0";
setTimeout(done, 300);
}
};
If(show, Modal, null, fade);
```
---
## Usage Patterns
### 1. Simple Toggle
```javascript
const isVisible = $(false);
Div([
Button({ onclick: () => isVisible(!isVisible()) }, "Toggle Message"),
If(isVisible,
P("Now you see me!"),
P("Now you don't...")
)
]);
```
### 2. With Smooth Animation
```javascript
const showModal = $(false);
Div([
Button({ onclick: () => showModal(true) }, "Open Modal"),
If(showModal,
() => Modal({ onClose: () => showModal(false) }),
null,
fade // ← Smooth enter/exit animation
)
]);
```
### 3. Lazy Component Loading
Unlike CSS `display: none`, `If` is **lazy**. The inactive branch is never created, saving memory.
```javascript
If(() => user.isLogged(),
() => Dashboard(), // Only executed if logged in
() => LoginGate() // Only executed if guest
)
```
### 4. Complex Conditions
```javascript
If(() => count() > 10 && status() === 'ready',
Span("Threshold reached!")
)
```
### 5. If.not Helper
```javascript
If.not(loading,
() => Content(), // Shows when loading is FALSE
() => Spinner() // Shows when loading is TRUE
)
```
---
## Automatic Cleanup
One of the core strengths of `If` is its integrated **Cleanup** logic. SigPro ensures that when a branch is swapped out, it is completely purged.
1. **Stop Watchers**: All `Watch` calls inside the inactive branch are permanently stopped.
2. **Unbind Events**: Event listeners attached via `Tag` are removed.
3. **Recursive Sweep**: SigPro performs a deep "sweep" of the removed branch.
4. **Transition Respect**: When using transitions, destruction only happens AFTER the `out` animation completes.
---
## Best Practices
- **Function Wrappers**: For heavy components, use `() => MyComponent()` to prevent initialization until needed.
- **Reusable Transitions**: Define common transitions (fade, slide, scale) in a shared module.
- **Cleanup**: No manual cleanup needed. SigPro handles everything automatically.
---
## Technical Comparison
| Feature | Standard CSS `hidden` | SigPro `If` |
| :--- | :--- | :--- |
| **DOM Presence** | Always present | Only if active |
| **Reactivity** | Still processing | **Paused/Destroyed** |
| **Memory usage** | Higher | **Optimized** |
| **Cleanup** | Manual | **Automatic** |
| **Smooth Transitions** | Manual | **Built-in hook** |
| **Animation Timing** | You manage | **Respected by core** |
---
## Complete Transition Examples
### Fade
```javascript
const fade = {
in: (el) => { el.style.opacity = "0"; requestAnimationFrame(() => { el.style.transition = "opacity 0.3s"; el.style.opacity = "1"; }); },
out: (el, done) => { el.style.transition = "opacity 0.3s"; el.style.opacity = "0"; setTimeout(done, 300); }
};
```
### Slide
```javascript
const slide = {
in: (el) => { el.style.transform = "translateX(-100%)"; requestAnimationFrame(() => { el.style.transition = "transform 0.3s"; el.style.transform = "translateX(0)"; }); },
out: (el, done) => { el.style.transition = "transform 0.3s"; el.style.transform = "translateX(-100%)"; setTimeout(done, 300); }
};
```
### Scale
```javascript
const scale = {
in: (el) => { el.style.transform = "scale(0)"; requestAnimationFrame(() => { el.style.transition = "transform 0.2s"; el.style.transform = "scale(1)"; }); },
out: (el, done) => { el.style.transition = "transform 0.2s"; el.style.transform = "scale(0)"; setTimeout(done, 200); }
};
```

View File

@@ -1,23 +1,174 @@
# JSX with SigPro
Aquí tienes el archivo `h.md` actualizado, que ahora incluye:
SigPro works seamlessly with JSX.
- La función `h` (hyperscript)
- Los helpers globales de etiquetas (lowercase)
- Una nueva sección sobre **JSX con SigPro** (configuración, ejemplos y alternativas como `htm`).
## Configuration
El documento está en inglés, como los originales, y listo para integrarse en tu documentación.
### TypeScript
---
```markdown
# Hyperscript & Tag Helpers
SigPro provides two complementary ways to create DOM elements:
1. **The `h` function** the lowlevel DOM builder.
2. **Global Tag Helpers** (e.g., `div()`, `button()`, `span()`) a convenient DSL built on top of `h`.
Both are reactive, autocleanup, and support SVG, events, twoway binding, and dynamic children.
---
## `h( )` Hyperscript Function
The `h` function is the **core DOM builder** of SigPro. Use it directly when you need a dynamic tag name or prefer an explicit style.
### Function Signature
```typescript
h(
tag: string | Function,
props?: object | Node | any[],
children?: any
): Node
```
| Parameter | Type | Description |
| :--- | :--- | :--- |
| **`tag`** | `string` or `Function` | HTML tag name (e.g., `"div"`) or a component function. |
| **`props`** | `object` | Optional. Attributes, event handlers, refs, etc. |
| **`children`** | `any` | Optional. Text, nodes, arrays, or reactive functions. |
**Returns:** A DOM node (or array of nodes when the tag is a component that returns an array).
### Usage Examples
```js
// Basic element
h('div', {}, 'Hello world');
// With attributes and events
h('button', { class: 'btn', onclick: () => alert('clicked') }, 'Click me');
// Nested children
h('div', { class: 'container' }, [
h('h1', {}, 'Title'),
h('p', {}, 'Paragraph')
]);
// Reactive child (function)
const count = $(0);
h('div', {}, [
h('p', {}, () => `Count: ${count()}`),
h('button', { onclick: () => count(count() + 1) }, '+1')
]);
// Reactive attribute
const theme = $('dark');
h('div', { class: () => `box ${theme()}` }, 'Themed box');
// Two-way binding on input
const name = $('');
h('input', { type: 'text', value: name, placeholder: 'Your name' });
h('p', {}, () => `Hello, ${name()}`);
// Component as tag
const Button = (props, { children }) =>
h('button', { class: 'btn', onclick: props.onClick }, children);
h(Button, { onClick: () => alert('clicked') }, 'Click me');
// SVG (auto-namespace)
h('svg', { width: 100, height: 100 }, [
h('circle', { cx: 50, cy: 50, r: 40, fill: 'red' })
]);
```
### Special Props
| Prop | Behaviour |
| :--- | :--- |
| **`ref`** | `ref: (el) => ...` or `ref: { current: null }` direct DOM node access. |
| **`onEvent`** | Any prop starting with `on` (e.g., `onClick`) is an event listener autoremoved on cleanup. |
| **`value` / `checked`** | When a signal is passed, creates twoway binding for form elements. |
| **`class`** | Use `class` (not `className`). Accepts a string or reactive function. |
---
## Global Tag Helpers (Lowercase)
When you import SigPro (either via `import 'sigpro'` or the CDN), it automatically injects a helper function for **every standard HTML tag** directly onto `window`. These helpers are **lowercase** and work exactly like `h`, but with a cleaner syntax.
### Available Helpers
All standard HTML5 tags: `div`, `span`, `p`, `section`, `nav`, `header`, `footer`, `h1``h6`, `ul`, `ol`, `li`, `button`, `a`, `input`, `form`, `table`, `svg`, `circle`, etc.
### Usage Examples
```js
// Instead of h('div', ...)
div({ class: 'container' }, 'Content');
// Children only (skip props)
section([
h2('Title'),
p('Paragraph')
]);
// Reactive attribute
const theme = $('light');
div({ class: () => `app-${theme()}` }, 'Themed');
// Two-way binding
const search = $('');
input({ type: 'text', value: search, placeholder: 'Search...' });
p(() => `You typed: ${search()}`);
// Dynamic children
const count = $(0);
div([
p(() => `Count: ${count()}`),
button({ onClick: () => count(count() + 1) }, '+1')
]);
```
### Complete Example
```js
const App = () =>
div({ class: 'app' }, [
h1('Welcome'),
input({ value: name, placeholder: 'Your name' }),
p(() => `Hello, ${name() || 'stranger'}!`),
button({ onClick: () => alert('Hi') }, 'Click me')
]);
mount(App, '#app');
```
---
## JSX with SigPro
SigPro works seamlessly with JSX. You can use JSX as a compiletime syntax sugar for `h` calls.
### Configuration
#### TypeScript
```json
// tsconfig.json
{
"compilerOptions": {
"jsx": "react",
"jsxFactory": "Tag",
"jsxFactory": "h",
"jsxFragmentFactory": "Fragment"
}
}
```
### Vite
#### Vite
```js
// vite.config.js
@@ -25,31 +176,33 @@ import { defineConfig } from 'vite'
export default defineConfig({
esbuild: {
jsxFactory: 'Tag',
jsxFactory: 'h',
jsxFragmentFactory: 'Fragment'
}
})
```
### Babel
#### Babel
```js
// babel.config.js
export default {
plugins: [
['@babel/plugin-transform-react-jsx', {
pragma: 'Tag',
pragma: 'h',
pragmaFrag: 'Fragment'
}]
]
}
```
## Usage Example
> **Note:** You need to import `h` and `Fragment` from SigPro in every JSX file, or make them global.
### JSX Example
```jsx
// App.jsx
import { $, Mount, Fragment } from 'sigpro';
import { $, h, Fragment, mount } from 'sigpro';
const Button = ({ onClick, children }) => (
<button class="btn btn-primary" onclick={onClick}>
@@ -76,10 +229,10 @@ const App = () => {
);
};
Mount(App, '#app');
mount(App, '#app');
```
## What Gets Compiled
### What Gets Compiled
Your JSX:
```jsx
@@ -89,59 +242,53 @@ Your JSX:
```
Compiles to:
```javascript
Tag('div', { class: "container" },
Tag(Button, {}, "Click")
```js
h('div', { class: "container" },
h(Button, {}, "Click")
)
```
## Without Build Step (CDN)
---
SigPro automatically injects `Div()`, `Button()`, `Span()`, and all other HTML tag helpers globally when loaded via CDN. `Fragment` is also available.
## Without a Build Step (CDN + Tag Helpers)
If you dont want to configure a JSX compiler, you can use the global tag helpers directly. They are available after loading SigPro via CDN.
```html
<!DOCTYPE html>
<html>
<head>
<script src="https://unpkg.com/sigpro"></script>
<script type="module">
import 'https://cdn.jsdelivr.net/npm/sigpro@1.2.18/+esm';
// Now $, $$, watch, h, mount, div, button, etc. are global
const count = $(0);
const App = () =>
div({ class: 'container' }, [
h1(() => `Count: ${count()}`),
button({ onClick: () => count(count() + 1) }, 'Increment')
]);
mount(App, '#app');
</script>
</head>
<body>
<div id="app"></div>
<script>
const { $, Mount, Fragment } = SigPro;
const App = () => {
const count = $(0);
return Div({ class: "container p-8" }, [
H1({ class: "text-2xl font-bold" }, "SigPro Demo"),
Button({
class: "btn-primary",
onclick: () => count(count() + 1)
}, () => `Clicks: ${count()}`),
Fragment({}, [
P({}, "Multiple elements"),
P({}, "Without wrapper")
])
]);
};
Mount(App, '#app');
</script>
</body>
</html>
```
## Template Literals Alternative (htm)
---
For a JSX-like syntax without a build step, use `htm`:
## Template Literals Alternative (`htm`)
```javascript
import { $, Mount } from 'https://unpkg.com/sigpro';
For a JSXlike syntax without a build step, you can combine SigPro with [`htm`](https://github.com/developit/htm).
```js
import { $, h, mount } from 'https://cdn.jsdelivr.net/npm/sigpro@1.2.18/+esm';
import htm from 'https://esm.sh/htm';
const html = htm.bind(Tag);
const html = htm.bind(h); // bind to SigPro's h function
const App = () => {
const count = $(0);
@@ -156,16 +303,21 @@ const App = () => {
`;
};
Mount(App, '#app');
mount(App, '#app');
```
## Summary
---
| Method | Build Step | Syntax |
|--------|------------|--------|
| JSX | Required | `<div>...</div>` |
| CDN (Tag Helpers) | Optional | `Div({}, ...)` |
| htm | Optional | `` html`<div>...</div>` `` |
## Summary Comparison
> [!TIP]
> **Recommendation:** Use JSX for large projects, CDN tag helpers (`Div()`, `Button()`) for simple projects, or htm for buildless projects that want HTML-like syntax.
| Method | Build Step | Syntax | Recommended for |
| :--- | :--- | :--- | :--- |
| **`h` function** | Optional | `h('div', ...)` | Dynamic tag names, lowlevel control |
| **Tag Helpers** | Optional | `div(...)` | Most cases clean, simple, no build step |
| **JSX** | Required | `<div>...</div>` | Large projects, teams familiar with React syntax |
| **`htm`** | Optional | `` html`<div>...</div>` `` | Buildless but HTMLlike syntax |
> **Tip:** All approaches are fully reactive, support twoway binding, events, SVG, and automatic cleanup. Choose the one that fits your workflow.
```
```

View File

@@ -1,85 +1,148 @@
# Application Mounter: `Mount( )`
# Application Mounter: `mount( )`
The `Mount` function is the entry point of your reactive world. It bridges the gap between your SigPro logic and the browser's Real DOM by injecting a component into the document and initializing its reactive lifecycle.
The `mount` function is the entry point of your reactive world. It bridges the gap between your SigPro logic and the browser's real DOM by rendering a component into a target element and managing its full reactive lifecycle.
## Function Signature
```typescript
Mount(node: Function | HTMLElement, target?: string | HTMLElement): RuntimeObject
mount(component: Function | Node, target: string | HTMLElement): RuntimeObject
```
| Parameter | Type | Default | Description |
| Parameter | Type | Required | Description |
| :--- | :--- | :--- | :--- |
| **`node`** | `Function` or `Node` | **Required** | The component function or DOM element to render. |
| **`target`** | `string` or `Node` | `document.body` | CSS selector or DOM element where the app will live. |
| **`component`** | `Function` or `Node` | Yes | A component function (returns a Node) or a direct DOM node. |
| **`target`** | `string` or `HTMLElement` | Yes | CSS selector (e.g., `"#app"`) or DOM element where the app will be mounted. |
**Returns:** A `Runtime` object containing the `container` and a `destroy()` method to wipe all reactivity and DOM nodes.
**Returns:** A `Runtime` object with:
- `container`: The actual DOM element created by the renderer.
- `destroy()`: A method to completely unmount and clean up the application.
---
## Common Usage Scenarios
## Usage Patterns
### 1. The SPA Entry Point
In a Single Page Application, you typically mount your main component to the body or a root div. SigPro manages the entire view from that point.
### 1. Main Application Entry Point
```javascript
import SigPro from 'sigpro';
import App from './App.js';
import { mount } from 'sigpro';
// Mounts your main App component
Mount(App, '#app-root');
const App = () => div({ class: "app" }, [
h1("Hello SigPro"),
button("Click me")
]);
mount(App, '#app');
```
### 2. Reactive "Islands"
SigPro is perfect for adding reactivity to static pages. You can mount small widgets into specific parts of an existing HTML layout.
### 2. Reactive Widget (Island Architecture)
Mount small reactive components into static HTML pages.
```javascript
const Counter = () => {
const count = $(0);
return Button({ onclick: () => count(c => c + 1) }, [
"Clicks: ", count
]);
return button({ onclick: () => count(count() + 1) }, () => `Clicks: ${count()}`);
};
// Mount only the counter into a specific sidebar div
Mount(Counter, '#sidebar-widget');
mount(Counter, '#sidebar-widget');
```
### 3. Direct Node Mounting
You can also mount an already existing DOM node.
```javascript
const myDiv = div("I am already a node");
mount(myDiv, '#container');
```
---
## How it Works (Lifecycle & Cleanup)
## How It Works (Lifecycle & Cleanup)
When `Mount` is executed, it performs these critical steps to ensure a leak-free environment:
When you call `mount`, SigPro performs these steps:
1. **Duplicate Detection**: If you call `Mount` on a target that already has a SigPro instance, it automatically calls `.destroy()` on the previous instance. This prevents "Zombie Effects" from stacking in memory.
2. **Internal Scoping**: It executes the component function inside an internal **Reactive Owner**. This captures every `Watch` and event listener created during the render.
3. **Target Injection**: It clears the target using `replaceChildren()` and appends the new component.
4. **Runtime Creation**: It returns a control object:
* `container`: The actual DOM element created.
* `destroy()`: The "kill switch" that runs all cleanups, stops all watchers, and removes the element from the DOM.
1. **Duplicate Detection**
SigPro keeps a `WeakMap` (`MOUNTED_NODES`) that tracks which DOM target already has a mounted runtime. If you mount a new component to the same target, the previous instance is **automatically destroyed** before the new one is rendered. This prevents memory leaks and “zombie effects”.
2. **Render Phase**
The `render` function creates a **cleanup container** (a `div` with `style="display: contents"`), and executes the component inside a fresh reactive owner. All effects (`watch`), event listeners, and child components created during this render are captured.
3. **DOM Injection**
The target element is cleared using `replaceChildren()`, and the container (which holds the rendered content) is appended.
4. **Runtime Object**
Returns an object `{ _isRuntime: true, container, destroy }`. The `destroy` function recursively disposes all effects, cleans up DOM nodes, and removes the container from the parent.
---
## Manual Unmounting
While SigPro handles most cleanups automatically (via `If`, `For`, and `Router`), you can manually destroy any mounted instance. This is vital for imperatively managed UI like **Toasts** or **Modals**.
You can call `destroy()` at any time to tear down the application. This is essential for imperatively managed UI like **modals**, **toasts**, or **dynamic panels**.
```javascript
const instance = Mount(MyToast, '#toast-container');
const widget = mount(MyToast, '#toast-container');
// Later, to remove the toast and kill its reactivity:
instance.destroy();
// Later, remove it completely:
widget.destroy();
```
---
## Automatic Remount on Same Target
If you call `mount` a second time on the same target, SigPro automatically destroys the previous instance and replaces it with the new one. No manual cleanup required.
```javascript
mount(LoginScreen, '#app');
// ... later, after login
mount(Dashboard, '#app'); // LoginScreen is destroyed automatically
```
---
## What is Automatically Cleaned Up
When `destroy()` is called (or when a new mount replaces an old one), everything is purged:
- All `watch` effects
- All event listeners added via SigPro (`onClick`, `onInput`, etc.)
- All child components created with `when`, `each`, or nested `mount` calls
- Any custom cleanups registered with `onUnmount`
> **You only need manual cleanup** for external resources not managed by SigPro (e.g., `setInterval`, thirdparty libraries, WebSocket connections). Use `onUnmount` for that.
---
## Complete Example
```javascript
import { $, mount, div, h1, button } from 'sigpro';
const App = () => {
const count = $(0);
return div({ class: "demo" }, [
h1(() => `Count: ${count()}`),
button({ onClick: () => count(count() + 1) }, "Increment")
]);
};
const runtime = mount(App, '#app');
// Destroy after 10 seconds
setTimeout(() => runtime.destroy(), 10000);
```
---
## Summary Cheat Sheet
| Goal | Code Pattern |
| Goal | Code |
| :--- | :--- |
| **Mount to body** | `Mount(App)` |
| **Mount to CSS Selector** | `Mount(App, '#root')` |
| **Mount to DOM Node** | `Mount(App, myElement)` |
| **Clean & Re-mount** | Calling `Mount` again on the same target |
| **Total Cleanup** | `const app = Mount(App); app.destroy();` |
| Mount to a CSS selector | `mount(App, '#root')` |
| Mount to a DOM element | `mount(App, document.getElementById('root'))` |
| Mount a static node | `mount(div("Hello"), '#target')` |
| Manual destruction | `const app = mount(App, '#app'); app.destroy();` |
| Autoreplace on same target | Just call `mount` again SigPro handles cleanup. |
> **Note:** The function name is `mount` (lowercase). It is exported from SigPro and also available globally after importing the library. The target must exist in the DOM at the time of mounting.

View File

@@ -1,238 +1,353 @@
# ⚡ Quick API Reference
# ⚡ SigPro 1.2.18 Complete API Reference
SigPro is a high-performance micro-framework that updates the **Real DOM** surgically. No Virtual DOM, no unnecessary re-renders, and built-in **Cleanup** (memory cleanup).
SigPro is a **RealDOM first** reactive microframework. No virtual DOM, no diffing overhead it updates the DOM directly with surgical precision. Builtin automatic cleanup prevents memory leaks, and the API is designed to be both tiny and powerful.
<div class="text-center my-8">
<div class="flex justify-center gap-2 flex-wrap mb-4">
<span class="badge badge-primary badge-lg font-mono text-lg">$-$$</span>
<span class="badge badge-secondary badge-lg font-mono text-lg">Watch</span>
<span class="badge badge-accent badge-lg font-mono text-lg">Tag</span>
<span class="badge badge-info badge-lg font-mono text-lg">If</span>
<span class="badge badge-success badge-lg font-mono text-lg">For</span>
<span class="badge badge-warning badge-lg font-mono text-lg">Router</span>
<span class="badge badge-error badge-lg font-mono text-lg">Mount</span>
</div>
<h1 class="text-5xl font-black bg-gradient-to-r from-primary via-secondary to-accent bg-clip-text text-transparent">
⚡ All the power! ⚡
</h1>
</div>
## Core Functions
Explore the reactive building blocks of SigPro.
<div class="overflow-x-auto my-8 border border-base-300 rounded-xl shadow-sm">
<table class="table table-zebra w-full">
<thead class="bg-base-200 text-base-content">
<tr>
<th>Function</th>
<th>Signature</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code class="text-primary font-bold">$(val, key?)</code></td>
<td class="font-mono text-xs opacity-70">(any, string?) => Signal</td>
<td>Creates a <b>Signal</b>. If <code>key</code> is provided, it persists in <code>localStorage</code>.</td>
</tr>
<tr>
<td><code class="text-primary font-bold">$(fn)</code></td>
<td class="font-mono text-xs opacity-70">(function) => Computed</td>
<td>Creates a <b>Computed Signal</b> that auto-updates when dependencies change.</td>
</tr>
<tr>
<td><code class="text-primary font-bold">$$(obj)</code></td>
<td class="font-mono text-xs opacity-70">(object) => Proxy</td>
<td>Creates a <b>Deep Reactive Proxy</b>. Track nested property access automatically. No need for manual signals.</td>
</tr>
<tr>
<td><code class="text-secondary font-bold">Watch(fn)</code></td>
<td class="font-mono text-xs opacity-70">(function) => stopFn</td>
<td><b>Auto Mode:</b> Tracks any signal touched inside. Returns a stop function.</td>
</tr>
<tr>
<td><code class="text-secondary font-bold">Watch(deps, fn)</code></td>
<td class="font-mono text-xs opacity-70">(Array, function) => stopFn</td>
<td><b>Explicit Mode:</b> Only runs when signals in <code>deps</code> change.</td>
</tr>
<tr>
<td><code class="text-accent font-bold">If(cond, then, else?)</code></td>
<td class="font-mono text-xs opacity-70">(Signal|bool, fn, fn?) => Node</td>
<td>Reactive conditional. Automatically destroys "else" branch memory.</td>
</tr>
<tr>
<td><code class="text-accent font-bold">For(src, render, key)</code></td>
<td class="font-mono text-xs opacity-70">(Signal, fn, fn) => Node</td>
<td><b>Keyed Loop:</b> Optimized list renderer. Uses the key function for surgical DOM moves.</td>
</tr>
<tr>
<td><code class="text-info font-bold">Router(routes)</code></td>
<td class="font-mono text-xs opacity-70">(Array) => Node</td>
<td><b>SPA Router:</b> Hash-based routing with dynamic params (<code>:id</code>) and auto-cleanup.</td>
</tr>
<tr>
<td><code class="font-bold">Mount(node, target)</code></td>
<td class="font-mono text-xs opacity-70">(any, string|Node) => Runtime</td>
<td>Entry point. Cleans the target and mounts the app with full lifecycle management.</td>
</tr>
</tbody>
</table>
</div>
```javascript
import { $, $$, watch, h, when, each, fx, router, req, mount, batch } from 'sigpro'
// or use globally as window.$ etc.
```
---
## Element Constructors (Tags)
## 🔁 Core Reactivity
SigPro provides **PascalCase** wrappers for all standard HTML5 tags (e.g., `Div`, `Span`, `Button`).
### `$(value, localStorageKey?)` Signal & Computed
### Syntax Pattern
Creates a reactive signal. If a function is passed, it becomes a **computed** signal that caches its result until dependencies change.
<div class="mockup-code bg-base-300 text-base-content">
<pre data-prefix=""><code>Tag({ attributes }, [children])</code></pre>
</div>
| Usage | Description |
|-------|-------------|
| `const count = $(0)` | Basic signal, returns a getter/setter: `count()` reads, `count(5)` writes. |
| `const double = $( () => count() * 2 )` | Computed signal updates automatically when `count` changes. |
| `const stored = $('hello', 'myKey')` | Persisted signal reads/writes to `localStorage`. |
### Special Attributes & Routing
**Example**
```javascript
const count = $(0)
const double = $( () => count() * 2 )
<div class="grid grid-cols-1 md:grid-cols-2 gap-6 my-10">
<div class="card bg-base-200 border border-base-300 shadow-sm">
<div class="card-body p-5">
<h3 class="text-xs font-black uppercase tracking-widest opacity-60">Two-way Binding</h3>
<code class="text-primary font-bold text-sm bg-base-300/50 p-2 rounded-lg">value: mySignal</code>
<p class="text-xs mt-3 leading-relaxed">Automatic sync for <code>Input</code>, <code>Textarea</code>, and <code>Select</code>. Updates the signal on 'input' or 'change'.</p>
</div>
</div>
watch(() => {
console.log(`count = ${count()}, double = ${double()}`)
}) // logs on every change
<div class="card bg-base-200 border border-base-300 shadow-sm">
<div class="card-body p-5">
<h3 class="text-xs font-black uppercase tracking-widest opacity-60">Dynamic Routing</h3>
<code class="text-info font-bold text-sm bg-base-300/50 p-2 rounded-lg">Router.to('/user/1')</code>
<p class="text-xs mt-3 leading-relaxed">Navigate programmatically. Access params via <code>Router.params().id</code>.</p>
</div>
</div>
<div class="card bg-base-200 border border-base-300 shadow-sm">
<div class="card-body p-5">
<h3 class="text-xs font-black uppercase tracking-widest opacity-60">Refs & DOM</h3>
<code class="text-accent font-bold text-sm bg-base-300/50 p-2 rounded-lg">ref: (el) => ...</code>
<p class="text-xs mt-3 leading-relaxed">Get direct access to the DOM node once it is created.</p>
</div>
</div>
<div class="card bg-base-200 border border-base-300 shadow-sm">
<div class="card-body p-5">
<h3 class="text-xs font-black uppercase tracking-widest opacity-60">Event Handling</h3>
<code class="text-secondary font-bold text-sm bg-base-300/50 p-2 rounded-lg">onClick: (e) => ...</code>
<p class="text-xs mt-3 leading-relaxed">Standard events with automatic <code>removeEventListener</code> on destruction.</p>
</div>
</div>
</div>
count(5) // triggers log: count=5, double=10
```
---
## Custom API (Bring Your Own Syntax)
### `$$(object)` Deep Reactive Proxy
SigPro's core functions are intentionally simple and can be easily renamed in **one line** to match your preferred coding style.
### One-Line Renaming
Makes a plain object (and all nested objects) deeply reactive. Any property access is tracked, any mutation triggers updates.
```javascript
import { $ as signal, Mount as render, Tag as tag, If as when, For as each, Watch as effect } from 'sigpro';
const state = $$({ user: { name: 'Alice', age: 30 }, items: [1,2,3] })
// Now use your custom names
const count = signal(0);
effect(() => console.log(count()));
watch(() => {
console.log(state.user.name) // tracks `user.name`
})
render(() =>
tag('div', {}, [
when(count,
() => tag('span', {}, 'Positive'),
() => tag('span', {}, 'Zero or negative')
)
]),
'#app'
);
state.user.name = 'Bob' // triggers the effect
```
### Create React-like Hooks
> **Note**: `$$` caches proxies per original object, so calling `$$` multiple times on the same object returns the same proxy.
---
### `watch(source, callback?)` Reactive Effect
Two modes:
1. **Autotrack mode** pass a function: `watch(() => { /* reads signals */ })`
Automatically reruns whenever any signal read inside changes.
2. **Explicit mode** pass an array of signals and a callback:
`watch([count, double], () => { ... })`
Runs the callback when any of the listed signals change. The callback receives the new values.
Both modes return a `stop` function that disposes the effect.
```javascript
import * as SigPro from 'sigpro';
// auto mode
const stop = watch(() => console.log(count()))
const useState = (initial) => {
const signal = SigPro.$(initial);
return [signal, (value) => signal(value)];
};
const useEffect = (fn, deps) => {
deps ? SigPro.Watch(deps, fn) : SigPro.Watch(fn);
};
// Usage
const Counter = () => {
const [count, setCount] = useState(0);
useEffect(() => console.log(count()), [count]);
return SigPro.Tag('button', { onClick: () => setCount(count() + 1) }, count);
};
// explicit mode
watch([count, double], ([newCount, newDouble]) => {
console.log(newCount, newDouble)
})
```
### Create Vue-like API
> **Important**: Effects are depthaware they run in topological order, parents before children.
---
## 🧱 Components & DOM (Hyperscript)
### `h(tag, props, children)` Create DOM Nodes
The universal builder. `props` can be omitted. Children can be strings, numbers, nodes, arrays, or **dynamic functions**.
| Feature | Example |
|---------|---------|
| Standard attributes | `h('div', { class: 'box', id: 'main' })` |
| Events | `onClick: (e) => ...` (automatically cleaned up) |
| Reactive attributes | `class: () => count() > 0 ? 'positive' : 'negative'` |
| Twoway binding | `value: mySignal` (works on `input`, `textarea`, `select`) |
| Refs | `ref: (el) => ...` or `ref: { current: null }` |
| SVG support | tag names like `svg`, `circle`, `path` sets correct namespace |
| Dangerous URL sanitising | `href` / `src` with `javascript:` or `data:` are blocked → `'#'` |
**Dynamic children** pass a function as a child, it will be reexecuted and the DOM patched automatically:
```javascript
import { $ as ref, Watch as watch, Mount as mount } from 'sigpro';
const computed = (fn) => ref(fn);
const createApp = (component) => ({ mount: (selector) => mount(component, selector) });
// Usage
const count = ref(0);
const double = computed(() => count() * 2);
watch([count], () => console.log(count()));
h('div', {}, [
() => count() > 0 ? h('span', {}, 'positive') : h('span', {}, 'zero or negative')
])
```
### Global Custom API with sigpro.config.js
### Tag shortcuts
Create a central configuration file to reuse your custom naming across the entire project:
SigPro defines **all standard HTML5 tags** as PascalCase globals (when run in browser) and also exports them as named exports. Example:
```javascript
// config/sigpro.config.js
import { $ as signal, Mount as render, Tag as tag, If as when, For as each, Watch as effect } from 'sigpro';
// Re-export everything with your custom names
export { signal, render, tag, when, each, effect };
// Also re-export the original functions if needed
export * from 'sigpro';
Div({ class: 'container' }, [
H1({}, 'Title'),
Button({ onClick: () => alert('hi') }, 'Click me')
])
```
Available tags: `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``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`.
---
## 🧩 Flow Control Components
### `when(condition, thenComponent, elseComponent?)`
Reactive conditional rendering. `condition` can be a boolean, a signal, or any function that returns a boolean. Both branches can be `Node`, `() => Node`, or `null`. Automatically disposes the unmounted branch.
```javascript
// app.js - Import your custom API globally
import { signal, render, tag, when, each, effect } from './config/sigpro.config.js';
when(
() => user.loggedIn(),
() => Div({}, 'Welcome back!'),
() => Button({ onClick: () => login() }, 'Login')
)
```
// Use your preferred syntax everywhere
const count = signal(0);
const double = signal(() => count() * 2);
---
effect(() => console.log(`Count: ${count()}, Double: ${double()}`));
### `each(source, itemRenderer, keyFn)`
Optimised keyed list rendering. `source` can be an array or a signal/function returning an array. `itemRenderer(item, index)` returns a Node (or a function that returns Nodes). `keyFn(item, index)` returns a unique identifier **required** for efficient DOM reuse.
```javascript
const items = $([{ id: 1, text: 'a' }, { id: 2, text: 'b' }])
each(items,
(item) => Li({}, item.text),
(item) => item.id
)
```
When the array changes, elements are added, removed, or reordered with minimal DOM operations.
---
## 💥 Effects & Lifecycle
### `onUnmount(fn)`
Inside a component (function called from `h`), registers a cleanup function that runs when that component is removed from the DOM.
```javascript
const Timer = () => {
const interval = setInterval(() => console.log('tick'), 1000)
onUnmount(() => clearInterval(interval))
return Div({}, 'Timer running')
}
```
### `batch(fn)`
Batch multiple reactive updates into a single flush, improving performance.
```javascript
batch(() => {
count(1)
name('John')
// effects run only once after the batch ends
})
```
### `untrack(fn)`
Run a function without tracking any signal reads.
```javascript
const logCount = () => {
untrack(() => console.log('count is', count()))
}
```
---
## ✨ Animations `fx(options, child)`
Applies smooth enter animations (CSS transitions / keyframes). Returns the modified element.
```javascript
fx({ name: 'fade', duration: 300 },
Div({}, 'Hello')
)
```
**Options**
- `name` uses predefined keyframes `${name}-in` (you must define them in your CSS)
- `duration` in ms (default 200)
- `scale` adds `scale(0.95)``none`
- `slide` adds `translateY(-10px)``none`
- `rotate` adds `rotate(-2deg)``none`
- `blur` adds `blur(4px)``none`
If `name` is given, it sets `animation: ${name}-in ${duration}ms`. Otherwise it applies a smooth transition from the initial transform/filter to the final state.
---
## 🧭 Router `router(routes)`
Hashbased SPA router. Returns a DOM node that renders the current route.
```javascript
const routes = [
{ path: '/', component: () => Div({}, 'Home') },
{ path: '/user/:id', component: (params) => Div({}, `User ${params.id}`) },
{ path: '*', component: () => Div({}, '404') }
]
const App = () => Div({}, [
A({ href: '#/' }, 'Home'),
A({ href: '#/user/42' }, 'User 42'),
router(routes)
])
```
**API**
| Method | Description |
|--------|-------------|
| `router.params()` | Returns a reactive signal of current route params (e.g., `{ id: '42' }`). |
| `router.to(path)` | Navigate to a new hash (e.g., `router.to('/user/5')`). Prepend `#` automatically. |
| `router.back()` | Go back in history. |
| `router.path()` | Returns current hash path without `#` (e.g., `/user/42`). |
---
## 🌐 HTTP Requests `req(config)`
Creates a reactive request controller with builtin loading/error/data signals and abort support.
```javascript
const fetchUser = req({ url: '/api/user/1', method: 'GET' })
// start the request
fetchUser.run().catch(console.error)
// reactively display state
watch(() => {
if (fetchUser.loading()) console.log('loading...')
if (fetchUser.error()) console.error(fetchUser.error())
if (fetchUser.data()) console.log(fetchUser.data())
})
// abort if needed
fetchUser.abort()
```
**Options**
- `url` (required)
- `method` (default `'GET'`)
- `headers` (object, default `{}`)
**Return value**
- `run(body?)` initiates the request, returns a promise.
- `abort()` aborts the current request (AbortController).
- `loading` signal (boolean)
- `error` signal (`null` or error message)
- `data` signal (`null` or parsed JSON)
> **Note**: Automatically sets `Content-Type: application/json` unless `body` is a `FormData`. Timeout after 10 seconds aborts the request.
---
## 🚀 Mounting `mount(component, target)`
Clears the target element and mounts the application. Returns the runtime instance (which has a `.destroy()` method).
```javascript
mount(() => App(), '#app')
// or
mount(App, document.body)
```
If you mount again on the same target, the previous instance is automatically destroyed.
---
## 🧹 Global Cleanup & Memory
SigPro tracks every effect, DOM event listener, and nested component. When a component is unmounted:
- All its effects are disposed.
- All DOM event listeners are removed.
- All `onUnmount` callbacks run.
- Child components are recursively destroyed.
You never need to manually clean up just write reactive code.
---
## 📦 Full Example Counter with Persistence
```javascript
import { $, watch, h, mount } from 'sigpro'
const count = $(0, 'counter') // persists in localStorage
const App = () =>
tag('div', { class: 'p-4' }, [
tag('h1', {}, () => `Count: ${count()}`),
tag('button', { onclick: () => count(count() + 1) }, 'Increment')
]);
Div({ class: 'counter' }, [
H1({}, () => `Count: ${count()}`),
Button({ onClick: () => count(count() + 1) }, '+'),
Button({ onClick: () => count(count() - 1) }, '-'),
Button({ onClick: () => count(0) }, 'Reset')
])
render(App, '#app');
mount(App, '#app')
```
> [!TIP]
> **Why rename?** Team preferences, framework migration, or just personal taste. SigPro adapts to you, not the other way around.
---
> [!IMPORTANT]
> **Performance Hint:** For lists (`For`), always provide a unique key function `(item) => item.id` to prevent unnecessary node creation and enable reordering.
## 🔧 Customising the API (Renaming)
> [!TIP]
> **Pro Tip:** Use `$$()` for complex nested state objects instead of multiple `$()` signals. It's cleaner and automatically tracks deep properties.
You can rename everything in one line:
> [!TIP]
> **Performance Hint:** Always use functions `() => signal()` for dynamic children to ensure SigPro only updates the specific node and not the whole container.
```javascript
import { $ as signal, watch as effect, h as element, mount as render } from 'sigpro'
```
Or assign globally:
```javascript
window.myReactive = $
```
All functions are also exposed on the global `window` object when included via `<script>`.
---
## 📜 License & Version
Current version: **1.2.18**
Released under MIT.
Zero dependencies, ~3KB gzipped.
---
> **Need legacy IE support?** Not supported requires modern JavaScript (Proxy, WeakMap, etc.).

182
docs/api/req.md Normal file
View File

@@ -0,0 +1,182 @@
# HTTP Requests: `req( )`
The `req` function creates a **reactive HTTP request controller**. It returns signals for `loading`, `error`, and `data`, plus a `run` method to execute the request and an `abort` method to cancel it. All signals update automatically as the request progresses.
## Function Signature
```typescript
req(config: {
url: string;
method?: string; // default: 'GET'
headers?: Record<string, string>;
}): {
run: (body?: any) => Promise<any>;
abort: () => void;
loading: Signal<boolean>;
error: Signal<string | null>;
data: Signal<any | null>;
}
```
| Parameter | Type | Required | Description |
| :--- | :--- | :--- | :--- |
| **`url`** | `string` | Yes | The endpoint to call. |
| **`method`** | `string` | No | HTTP method (`'GET'`, `'POST'`, etc.). Default `'GET'`. |
| **`headers`** | `object` | No | Custom headers (will be merged with defaults). |
**Returns:** A controller object with reactive signals and methods.
---
## Usage Patterns
### 1. Basic GET Request
```javascript
const users = req({ url: '/api/users' });
// Start the request
users.run().catch(console.error);
// React to the response
watch(() => {
if (users.loading()) console.log('Loading...');
if (users.error()) console.error(users.error());
if (users.data()) console.log('Data:', users.data());
});
```
### 2. POST Request with Body
```javascript
const createUser = req({ url: '/api/users', method: 'POST' });
const handleSubmit = async (formData) => {
try {
await createUser.run(formData);
alert('User created!');
} catch (err) {
// Error already in createUser.error()
}
};
```
### 3. Aborting a Request
```javascript
const search = req({ url: '/api/search' });
// Abort if the user types again quickly
let timeout;
input({ onInput: (e) => {
clearTimeout(timeout);
search.abort(); // cancel previous in-flight request
timeout = setTimeout(() => search.run({ q: e.target.value }), 300);
}});
```
### 4. Reactive UI with Signals
```javascript
const profile = req({ url: '/api/me' });
const App = () =>
div([
when(() => profile.loading(),
() => div("Loading...")
),
when(() => profile.error(),
() => div("Error: " + profile.error())
),
when(() => profile.data(),
() => div([
h2(profile.data().name),
p(profile.data().email)
])
)
]);
profile.run();
mount(App, '#app');
```
---
## Request Lifecycle
When you call `run(body?)`:
1. Any previous request is **aborted** automatically.
2. `loading` becomes `true`.
3. `error` is cleared.
4. A 10second timeout is set (autoabort).
5. The request is sent using `fetch`.
6. On success: `data` is set with the parsed JSON, `loading` becomes `false`.
7. On error: `error` is set with the message, `data` is cleared, `loading` becomes `false`.
> **Important:** The response body is parsed as JSON. If the response is not OK or the JSON parsing fails, `error` is set and the promise rejects.
---
## Automatic Headers
- If `body` is a plain object or array, the request automatically includes `Content-Type: application/json` (unless you override it in `headers`).
- If `body` is a `FormData` instance, the `Content-Type` is not set (browser will set it automatically with the correct boundary).
- Other headers can be added via the `headers` option.
```javascript
const upload = req({
url: '/upload',
method: 'POST',
headers: { 'X-Custom': 'value' }
});
const formData = new FormData();
formData.append('file', fileInput.files[0]);
upload.run(formData);
```
---
## Error Handling
- Network errors, timeouts, and HTTP error statuses (4xx, 5xx) all set `error` and cause the promise to reject.
- The `error` signal contains a humanreadable message.
- Abort errors (calling `abort()` or timeout) are **silent** they do not set `error` or reject the promise?
Actually, according to the source: if `e.name === 'AbortError'`, it does **not** call `error(e.message)`, but the promise still rejects with the AbortError. You can handle it with `.catch()` if needed.
---
## Complete Example
```javascript
const api = req({ url: 'https://jsonplaceholder.typicode.com/posts/1' });
const App = () =>
div({ class: "demo" }, [
when(() => api.loading(), () => p("⏳ Loading...")),
when(() => api.error(), () => p("❌ " + api.error())),
when(() => api.data(), () => div([
h2(api.data().title),
p(api.data().body)
])),
button({
onClick: () => api.run(),
disabled: () => api.loading()
}, "Fetch")
]);
mount(App, '#app');
```
---
## Summary
| Member | Type | Description |
| :--- | :--- | :--- |
| `run(body?)` | `(any) => Promise` | Starts the request. Returns a promise. |
| `abort()` | `() => void` | Cancels the current request. |
| `loading` | `Signal<boolean>` | `true` while request is in flight. |
| `error` | `Signal<string\|null>` | Contains an error message, or `null`. |
| `data` | `Signal<any\|null>` | Contains the parsed response (JSON), or `null`. |

View File

@@ -1,96 +1,187 @@
# Routing: `Router()` & Utilities
# Routing: `router( )` & Utilities
SigPro includes a built-in, lightweight **Hash Router** to create Single Page Applications (SPA). It manages the URL state, matches components to paths, and handles the lifecycle of your pages automatically.
SigPro includes a builtin, lightweight **hash router** to create singlepage applications (SPA). It manages the URL hash, matches components to routes with dynamic segments (`:id`), and automatically cleans up each page when you navigate away.
## Router Signature
## Function Signature
```typescript
Router(routes: Route[]): HTMLElement
router(routes: Route[]): HTMLElement
```
### Route Object
| Property | Type | Description |
| :--- | :--- | :--- |
| **`path`** | `string` | The URL fragment (e.g., `"/"`, `"/user/:id"`, or `"*"`). |
| **`component`** | `Function` | A function that returns a Node, a String, or a reactive View. |
| **`path`** | `string` | The URL fragment pattern (e.g. `"/"`, `"/user/:id"`, or `"*"` for catchall). |
| **`component`** | `Function` | A function that returns a Node, a string, or a reactive view. Receives `params` object as argument. |
**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.
---
## Usage Patterns
### 1. Defining Routes
The `Router` returns a `div` element with the class `.router-transition`. When the hash changes, the router destroys the previous view and mounts the new one inside this container.
Place the `router` element where you want the page content to appear. Inside the routes array, define your routes.
```javascript
const App = () => Div({ class: "app-layout" }, [
Navbar(),
// The router outlet is placed here
Router([
{ path: "/", component: Home },
{ path: "/profile/:id", component: (params) => UserProfile(params.id) },
{ path: "*", component: () => H1("404 Not Found") }
])
]);
const Home = () => h1("Home Page");
const UserProfile = (params) => h1(`User ID: ${params.id}`);
const NotFound = () => h1("404 Page not found");
const App = () =>
div({ class: "app-layout" }, [
nav([
a({ href: "#/" }, "Home"),
a({ href: "#/user/42" }, "User 42")
]),
router([
{ path: "/", component: Home },
{ path: "/user/:id", component: UserProfile },
{ path: "*", component: NotFound }
])
]);
mount(App, "#app");
```
### 2. Dynamic Segments (`:id`)
The router automatically parses URL parameters (like `:id`) and passes them as an object to the component function. You can also access them globally via `Router.params()`.
When a route contains a colonprefixed segment (like `:id`), the router extracts the corresponding value from the current hash and passes it as a property inside the `params` object to the component function.
```javascript
// If the URL is #/profile/42
// If the hash is #/user/42
const UserProfile = (params) => {
return H1(`User ID is: ${params.id}`); // Displays "User ID is: 42"
console.log(params.id); // "42"
return div(`User ${params.id}`);
};
```
### 3. Accessing Route Parameters Anywhere
The router maintains a reactive signal `router.params` that always holds the parameters of the currently matched route. You can read it anywhere in your app.
```javascript
watch(() => {
const params = router.params();
console.log("Current route params:", params);
});
```
---
## Navigation Utilities
SigPro provides a set of programmatic methods to control the history and read the state.
SigPro provides several helper functions to control navigation and read the router state.
### `router.to(path)`
Navigates to the given path. It automatically formats the hash (e.g. `"/dashboard"` becomes `"#/dashboard"`). You can pass either a full hash string or a path without the `#`.
### `Router.to(path)`
Navigates to a specific path. It automatically formats the hash (e.g., `/home` becomes `#/home`).
```javascript
Button({ onclick: () => Router.to("/dashboard") }, "Go to Dashboard")
button({ onclick: () => router.to("/dashboard") }, "Go to Dashboard")
```
### `Router.back()`
Goes back to the previous page in the browser history.
### `router.back()`
Goes back one step in the browsers history, just like calling `history.back()`.
```javascript
Button({ onclick: () => Router.back() }, "Back")
button({ onclick: () => router.back() }, "Back")
```
### `Router.path()`
Returns the current path (without the `#`).
### `router.path()`
Returns the current route path **without the leading `#`**. This is a plain string, not a signal.
```javascript
Watch(() => {
console.log("Current path is:", Router.path());
console.log(router.path()); // e.g. "/user/42"
```
---
## Automatic Cleanup
Every time you navigate to a new route, the router calls `.destroy()` on the previous view. This recursively disposes of:
- All `watch` effects created inside that page
- All event listeners attached via SigPros event binding
- Any nested `when`, `each`, or `router` instances
**No manual cleanup is required** memory leaks are prevented automatically.
---
## Reactive Route Parameters
`router.params` is a **reactive signal** (created with `$({})`). You can watch it to react to parameter changes without remounting the whole router outlet.
```javascript
watch(() => router.params(), (params) => {
console.log("Params changed:", params);
// e.g. fetch new data when the :id changes
});
```
---
## Technical Behavior
## Styling the Router Outlet
* **Automatic Cleanup**: Every time you navigate, the router calls `.destroy()` on the previous view. This ensures that all **signals, effects, and event listeners** from the old page are purged from memory.
* **Reactive Params**: `Router.params` is a signal (`$`). You can react to parameter changes without re-mounting the entire router outlet.
* **Hash-Based**: By using `window.location.hash`, SigPro works out-of-the-box on any static hosting (like GitHub Pages or Vercel) without needing server-side redirects.
---
## Styling the transition of Router
The router returns a standard `div` with the `.router-transition` class. You can easily style it or add transitions:
The router returns a `div` with the class `"router-hook"`. You can style it just like any other element:
```css
.router-transition {
.router-hook {
display: block;
min-height: 100vh;
animation: fadeIn 0.3s ease;
min-height: 60vh;
animation: fadeIn 0.2s ease;
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(10px); }
from { opacity: 0; transform: translateY(8px); }
to { opacity: 1; transform: translateY(0); }
}
```
If you want the router outlet to have no layout impact, you can set `display: contents` on it.
---
## Complete Example
```javascript
import { $, router, mount } from "sigpro";
const Home = () => div("Welcome home");
const About = () => div("About us");
const User = (params) => div(`User profile: ${params.id}`);
const App = () =>
div([
nav([
a({ href: "#/" }, "Home"),
a({ href: "#/about" }, "About"),
a({ href: "#/user/5" }, "User 5")
]),
router([
{ path: "/", component: Home },
{ path: "/about", component: About },
{ path: "/user/:id", component: User },
{ path: "*", component: () => div("404 Not found") }
])
]);
mount(App, "#app");
```
---
## Summary
| Function | Description |
| :--- | :--- |
| `router(routes)` | Creates a router outlet. |
| `router.to(path)` | Navigates to a new hash route. |
| `router.back()` | Goes back in history. |
| `router.path()` | Returns the current path without `#`. |
| `router.params()` | Reactive signal of the current route parameters. |

View File

@@ -25,21 +25,34 @@ $(computation: Function): ComputedSignal
**`$(value)`**
Creates a writable signal. It returns a function that acts as both **getter** and **setter**.
```javascript
const count = $(0);
<div id="demo-signal-simple"></div>
count(); // Read (0)
count(10); // Write (10)
```js
{
const count = $(0);
const App = () => div({ class: "example" }, [
p(() => `Count: ${count()}`),
button({ onClick: () => count(count() + 1) }, "+1")
]);
setTimeout(() => mount(App, '#demo-signal-simple'), 50);
}
```
### 2. Persistent State
**`$(value, key)`**
Creates a writable signal that syncs with the browser's storage.
```javascript
const theme = $("light", "app-theme");
<div id="demo-signal-persist"></div>
theme("dark"); // Automatically calls localStorage.setItem("app-theme", '"dark"')
```js
{
const theme = $("light", "theme-persist-demo");
const App = () => div([
p(() => `Current theme: ${theme()}`),
button({ onClick: () => theme(theme() === "light" ? "dark" : "light") }, "Toggle theme")
]);
setTimeout(() => mount(App, '#demo-signal-persist'), 50);
}
```
*Note: On page load, SigPro will prioritize the value found in `localStorage` over the `initialValue`.*
@@ -47,12 +60,23 @@ theme("dark"); // Automatically calls localStorage.setItem("app-theme", '"dark"'
**`$(function)`**
Creates a read-only signal that updates automatically when any signal used inside it changes.
```javascript
const price = $(100);
const tax = $(0.21);
<div id="demo-signal-computed"></div>
// This tracks both 'price' and 'tax' automatically
const total = $(() => price() * (1 + tax()));
```js
{
const price = $(100);
const tax = $(0.21);
const total = $(() => price() * (1 + tax()));
const App = () => div([
p(() => `Price: €${price()}`),
p(() => `Tax rate: ${tax() * 100}%`),
p(() => `Total: €${total().toFixed(2)}`),
button({ onClick: () => price(price() + 10) }, "+€10"),
button({ onClick: () => price(price() - 10) }, "-€10")
]);
setTimeout(() => mount(App, '#demo-signal-computed'), 50);
}
```
---
@@ -60,11 +84,17 @@ const total = $(() => price() * (1 + tax()));
## Updating with Logic
When calling the setter, you can pass an **updater function** to access the current value safely.
```javascript
const list = $(["A", "B"]);
<div id="demo-signal-updater"></div>
// Adds "C" using the previous state
list(prev => [...prev, "C"]);
```js
{
const list = $(["A", "B"]);
const App = () => div([
ul(() => list().map(item => li(item))),
button({ onClick: () => list(prev => [...prev, "C"]) }, "Add C")
]);
setTimeout(() => mount(App, '#demo-signal-updater'), 50);
}
```
---
@@ -91,82 +121,92 @@ $$<T extends object>(obj: T): T
### 1. Simple Object
```javascript
const state = $$({ count: 0, name: "Juan" });
<div id="demo-dollar-simple"></div>
Watch(() => state.count, () => {
console.log(`Count is now ${state.count}`);
});
```js
{
const state = $$({ count: 0, name: "Juan" });
watch(() => console.log(`Count is now ${state.count}`));
state.count++; // ✅ Triggers update
state.name = "Ana"; // ✅ Also reactive
const App = () => div([
p(() => `Count: ${state.count}, Name: ${state.name}`),
button({ onClick: () => state.count++ }, "Increment count"),
button({ onClick: () => state.name = state.name === "Juan" ? "Ana" : "Juan" }, "Toggle name")
]);
setTimeout(() => mount(App, '#demo-dollar-simple'), 50);
}
```
### 2. Deep Reactivity
Unlike `$()`, `$$()` tracks nested properties automatically.
<div id="demo-dollar-deep"></div>
```javascript
const user = $$({
profile: {
name: "Juan",
address: {
city: "Madrid",
zip: "28001"
```js
{
const user = $$({
profile: {
name: "Juan",
address: { city: "Madrid", zip: "28001" }
}
}
});
});
// This works! Tracks deep property access
Watch(() => user.profile.address.city, () => {
console.log("City changed");
});
watch(() => user.profile.address.city, () => console.log("City changed"));
user.profile.address.city = "Barcelona"; // ✅ Triggers update
const App = () => div([
p(() => `City: ${user.profile.address.city}`),
button({ onClick: () => user.profile.address.city = "Barcelona" }, "Change to Barcelona")
]);
setTimeout(() => mount(App, '#demo-dollar-deep'), 50);
}
```
### 3. Arrays
`$$()` works with arrays and array methods.
<div id="demo-dollar-array"></div>
```javascript
const todos = $$([
{ id: 1, text: "Learn SigPro", done: false },
{ id: 2, text: "Build an app", done: false }
]);
```js
{
const todos = $$([
{ id: 1, text: "Learn SigPro", done: false },
{ id: 2, text: "Build an app", done: false }
]);
Watch(() => todos.length, () => {
console.log(`You have ${todos.length} todos`);
});
watch(() => todos.length, () => console.log(`You have ${todos.length} todos`));
// Array methods are reactive
todos.push({ id: 3, text: "Deploy", done: false }); // ✅ Triggers
todos[0].done = true; // ✅ Deep reactivity works
todos.splice(1, 1); // ✅ Triggers
const App = () => div([
ul(() => todos.map(todo => li(todo.text + (todo.done ? " ✓" : "")))),
button({ onClick: () => todos.push({ id: Date.now(), text: "New todo", done: false }) }, "Add todo"),
button({ onClick: () => todos[0].done = !todos[0].done }, "Toggle first todo")
]);
setTimeout(() => mount(App, '#demo-dollar-array'), 50);
}
```
### 4. Mixed with Signals
`$$()` works seamlessly with `$()` signals.
<div id="demo-dollar-mixed"></div>
```javascript
const form = $$({
fields: {
email: "",
password: ""
},
isValid: $(false) // Signal inside reactive object
});
```js
{
const form = $$({
fields: { email: "", password: "" },
isValid: $(false)
});
// Computed using both
const canSubmit = $(() =>
form.fields.email.includes("@") &&
form.fields.password.length > 6
);
const canSubmit = $(() =>
form.fields.email.includes("@") &&
form.fields.password.length > 6
);
Watch(canSubmit, (valid) => {
form.isValid(valid); // Update signal inside reactive object
});
watch(canSubmit, valid => form.isValid(valid));
const App = () => div([
input({ type: "email", placeholder: "Email", value: () => form.fields.email, onInput: e => form.fields.email = e.target.value }),
input({ type: "password", placeholder: "Password", value: () => form.fields.password, onInput: e => form.fields.password = e.target.value }),
p(() => `Form valid: ${form.isValid() ? "Yes" : "No"}`)
]);
setTimeout(() => mount(App, '#demo-dollar-mixed'), 50);
}
```
---
@@ -189,27 +229,45 @@ Watch(canSubmit, (valid) => {
## When to Use Each
### Use `$()` when:
- Working with primitives (numbers, strings, booleans)
- Need localStorage persistence
- Creating computed values
- Want explicit control over updates
```javascript
const count = $(0);
const user = $(null);
const fullName = $(() => `${firstName()} ${lastName()}`);
<div id="demo-use-dollar"></div>
```js
{
const count = $(0);
const firstName = $("John");
const lastName = $("Doe");
const fullName = $(() => `${firstName()} ${lastName()}`);
const App = () => div([
p(() => `Count: ${count()}`),
button({ onClick: () => count(count() + 1) }, "Count up"),
p(() => `Full name: ${fullName()}`),
input({ value: firstName, placeholder: "First name" }),
input({ value: lastName, placeholder: "Last name" })
]);
setTimeout(() => mount(App, '#demo-use-dollar'), 50);
}
```
### Use `$$()` when:
- Working with complex nested objects
- Managing forms with multiple fields
- Using arrays with mutations (push, pop, splice)
- Want natural object syntax (no function calls)
```javascript
const form = $$({ email: "", password: "" });
const settings = $$({ theme: "dark", notifications: true });
const store = $$({ users: [], filters: {}, pagination: { page: 1 } });
<div id="demo-use-dollar-dollar"></div>
```js
{
const form = $$({ email: "", password: "" });
const settings = $$({ theme: "dark", notifications: true });
const App = () => div([
input({ placeholder: "Email", onInput: e => form.email = e.target.value }),
input({ placeholder: "Password", type: "password", onInput: e => form.password = e.target.value }),
p(() => `Email: ${form.email}, Password: ${form.password}`),
button({ onClick: () => settings.theme = settings.theme === "dark" ? "light" : "dark" }, "Toggle theme"),
p(() => `Current theme: ${settings.theme}`)
]);
setTimeout(() => mount(App, '#demo-use-dollar-dollar'), 50);
}
```
---
@@ -217,19 +275,19 @@ const store = $$({ users: [], filters: {}, pagination: { page: 1 } });
## Important Notes
### ✅ DO:
```javascript
```js
// Access properties directly
state.count = 10;
state.user.name = "Ana";
todos.push(newItem);
// Track in effects
Watch(() => state.count, () => {});
Watch(() => state.user.name, () => {});
watch(() => state.count, () => {});
watch(() => state.user.name, () => {});
```
### ❌ DON'T:
```javascript
```js
// Destructuring breaks reactivity
const { count, user } = state; // ❌ count and user are not reactive
@@ -248,7 +306,7 @@ Like all SigPro reactive primitives, `$$()` integrates with the cleanup system:
- Effects tracking reactive properties are automatically disposed
- No manual cleanup needed
- Works with `Watch`, `If`, and `For`
- Works with `watch`, `when`, and `each`
---
@@ -266,56 +324,46 @@ Like all SigPro reactive primitives, `$$()` integrates with the cleanup system:
## Complete Example
```javascript
// Combining both approaches
const app = {
// Simple primitives with persistence
theme: $("dark", "theme"),
sidebarOpen: $(true),
<div id="demo-complete"></div>
// Complex state with $$()
user: $$({
name: "",
email: "",
preferences: {
notifications: true,
language: "es"
```js
{
const app = {
theme: $("dark", "theme_complete"),
sidebarOpen: $(true),
user: $$({ name: "", email: "", preferences: { notifications: true, language: "es" } }),
isLoggedIn: $(() => !!app.user.name),
login(name, email) {
app.user.name = name;
app.user.email = email;
},
logout() {
app.user.name = "";
app.user.email = "";
app.user.preferences.notifications = true;
}
}),
};
// Computed values
isLoggedIn: $(() => !!app.user.name),
// Actions
login(name, email) {
app.user.name = name;
app.user.email = email;
},
logout() {
app.user.name = "";
app.user.email = "";
app.user.preferences.notifications = true;
}
};
// UI component
const UserProfile = () => {
return Div({}, [
If(() => app.isLoggedIn(),
() => Div({}, [
H2(`Welcome ${app.user.name}`),
P(`Email: ${app.user.email}`),
P(`Notifications: ${app.user.preferences.notifications ? "ON" : "OFF"}`),
Button({ onclick: () => app.user.preferences.notifications = !app.user.preferences.notifications },
"Toggle Notifications"
),
Button({ onclick: app.logout }, "Logout")
]),
() => LoginForm()
)
const LoginForm = () => div([
input({ placeholder: "Name", onInput: e => app.user.name = e.target.value }),
input({ placeholder: "Email", onInput: e => app.user.email = e.target.value }),
button({ onClick: () => app.login(app.user.name, app.user.email) }, "Login")
]);
};
const UserProfile = () => div([
h2(() => `Welcome ${app.user.name}`),
p(() => `Email: ${app.user.email}`),
p(() => `Notifications: ${app.user.preferences.notifications ? "ON" : "OFF"}`),
button({ onClick: () => app.user.preferences.notifications = !app.user.preferences.notifications }, "Toggle Notifications"),
button({ onClick: app.logout }, "Logout")
]);
const App = () => div({ class: "complete-example" }, [
when(() => app.isLoggedIn(), () => UserProfile(), () => LoginForm())
]);
setTimeout(() => mount(App, '#demo-complete'), 50);
}
```
---
@@ -324,7 +372,7 @@ const UserProfile = () => {
If you have code using nested signals:
```javascript
```js
// Before - Manual nesting
const user = $({
name: $(""),
@@ -339,3 +387,4 @@ const user = $$({
});
user.name = "Juan"; // Direct assignment
```

View File

@@ -1,49 +1,56 @@
# Global Tag Helpers
In **SigPro**, you don't need to manually type `Tag('div', ...)` for every element. To keep your code declarative and readable, the engine automatically generates **Global Helper Functions** for all standard HTML5 tags upon initialization.
In **SigPro**, you don't need to manually type `h('div', ...)` for every element. To keep your code declarative and readable, the engine automatically generates **helper functions** for all standard HTML5 tags upon initialization.
## 1. How it Works
SigPro iterates through a manifest of standard HTML tags and attaches a wrapper function for each one directly to the `window` object. This creates a specialized **DSL** (Domain Specific Language) that looks like a template engine but is **100% standard JavaScript**.
SigPro iterates through a list of standard HTML tags and attaches a wrapper function for each one directly to the `window` object. This creates a specialized **DSL** (Domain Specific Language) that looks like a template engine but is **100% standard JavaScript**.
* **Under the hood:** `Tag('button', { onclick: ... }, 'Click')`
* **SigPro Style:** `Button({ onclick: ... }, 'Click')`
* **Under the hood:** `h('button', { onclick: ... }, 'Click')`
* **SigPro Style:** `button({ onclick: ... }, 'Click')`
> **Note:** All tag helpers are **lowercase** (e.g. `div`, `span`, `button`). PascalCase versions (`Div`, `Span`, `Button`) are **not** created. This keeps the syntax close to raw HTML.
---
## 2. The Complete Global Registry
The following functions are injected into the global scope using **PascalCase** to prevent naming collisions with common JS variables:
The following functions are injected into the global scope using **lowercase** names to match HTML tags:
| Category | Available Global Functions |
| :--- | :--- |
| **Structure** | `Div`, `Span`, `P`, `Section`, `Nav`, `Main`, `Header`, `Footer`, `Article`, `Aside` |
| **Typography** | `H1` to `H6`, `Ul`, `Ol`, `Li`, `Dl`, `Dt`, `Dd`, `Strong`, `Em`, `Code`, `Pre`, `Small`, `B`, `U`, `Mark` |
| **Interactive** | `Button`, `A`, `Label`, `Br`, `Hr`, `Details`, `Summary`, `Dialog` |
| **Forms** | `Form`, `Input`, `Select`, `Option`, `Textarea`, `Fieldset`, `Legend` |
| **Tables** | `Table`, `Thead`, `Tbody`, `Tr`, `Th`, `Td`, `Tfoot`, `Caption` |
| **Media** | `Img`, `Canvas`, `Video`, `Audio`, `Svg`, `Iframe`, `Picture`, `Source` |
| **Structure** | `div`, `span`, `p`, `section`, `nav`, `main`, `header`, `footer`, `article`, `aside` |
| **Typography** | `h1`, `h2`, `h3`, `h4`, `h5`, `h6`, `ul`, `ol`, `li`, `dl`, `dt`, `dd`, `strong`, `em`, `code`, `pre`, `small`, `b`, `u`, `mark` |
| **Interactive** | `button`, `a`, `label`, `br`, `hr`, `details`, `summary`, `dialog` |
| **Forms** | `form`, `input`, `select`, `option`, `textarea`, `fieldset`, `legend` |
| **Tables** | `table`, `thead`, `tbody`, `tr`, `th`, `td`, `tfoot`, `caption` |
| **Media** | `img`, `canvas`, `video`, `audio`, `svg`, `iframe`, `picture`, `source` |
Full list includes: `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``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`.
---
## 3. Usage Patterns (Smart Arguments)
## 3. Usage Patterns
SigPro tag helpers are flexible. They automatically detect if you are passing attributes, children, or both.
### A. Attributes + Children
```javascript
Div({ class: 'container', id: 'main' }, [
H1("Welcome to SigPro"),
P("The zero-VDOM framework.")
div({ class: 'container', id: 'main' }, [
h1("Welcome to SigPro"),
p("The zero-VDOM framework.")
]);
```
### B. Children Only (The "Skipper")
### B. Children Only
If you don't need attributes, you can pass the content directly as the first argument.
```javascript
Section([
H2("Clean Syntax"),
Button("I have no props!")
section([
h2("Clean Syntax"),
button("I have no props!")
]);
```
@@ -51,139 +58,146 @@ Section([
## 4. Reactive Power
These helpers are natively wired into SigPro's **`Watch`** engine.
These helpers are natively wired into SigPro's reactivity system.
### Reactive Attributes (OneWay)
Pass a **function** that returns the value. SigPro creates an internal effect to keep the DOM in sync.
### Reactive Attributes (One-Way)
Simply pass a Signal (function) to any attribute. SigPro creates an internal `Watch` to keep the DOM in sync.
```javascript
const theme = $("light");
Div({
div({
class: () => `app-box ${theme()}`
}, "Themeable Box");
```
### Smart Two-Way Binding (Automatic)
SigPro automatically bridges the **Signal** and the **Input** element bi-directionally when you assign a Signal to `value` or `checked`. No special operators are required.
### TwoWay Binding (Automatic)
SigPro automatically bridges the **signal** and the **input element** bidirectionally when you assign a signal to `value` or `checked`.
```javascript
const search = $("");
// UI updates Signal AND Signal updates UI automatically
Input({
input({
type: "text",
placeholder: "Search...",
value: search
});
```
> **Pro Tip:** If you want an input to be **read-only** but still reactive, wrap the signal in an anonymous function: `value: () => search()`. This prevents the "backwards" synchronization.
> **Pro Tip:** If you want an input to be **readonly** but still reactive, wrap the signal in an anonymous function: `value: () => search()`. This prevents backward synchronization.
### Dynamic Children
You can pass a function as a child it will be reexecuted whenever any signal inside changes, and the DOM will be patched surgically.
### Dynamic Flow & Cleanup
Combine tags with Core controllers. SigPro automatically cleans up the `Watch` instances and event listeners when nodes are removed from the DOM.
```javascript
const items = $(["Apple", "Banana", "Cherry"]);
const count = $(0);
Ul({ class: "list-disc" }, [
For(items, (item) => Li(item), (item) => item)
div([
p(() => `Count is ${count()}`),
button({ onClick: () => count(count() + 1) }, "Increment")
]);
```
---
<div class="alert alert-error shadow-lg my-8 border-l-8 border-error bg-error/10 text-error-content">
<div>
<svg xmlns="http://www.w3.org/2000/svg" class="stroke-current flex-shrink-0 h-6 w-6" fill="none" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" /></svg>
<div>
<h3 class="font-bold text-lg">Important: Naming Conventions</h3>
<div class="text-sm opacity-90">
<ol class="list-decimal ml-4 mt-2 space-y-2">
<li><b>Avoid Shadowing:</b> Don't name your local variables like the tags (e.g., <code>const Div = ...</code>). This will "hide" the global SigPro helper.</li>
<li><b>Custom Components:</b> Always use <b>PascalCase</b> for your own component functions (e.g., <code>UserCard</code>, <code>NavMenu</code>) to distinguish them from built-in Tag Helpers.</li>
</ol>
</div>
</div>
</div>
</div>
## 5. Custom Components with `h()`
---
While the global tag helpers cover all standard HTML tags, you can create reusable components using the `h` function directly (or by returning the result of tag helpers).
## 5. Logic to UI Comparison
Here is how a dynamic **User Status** component translates from SigPro logic to the final DOM structure.
```javascript
const UserStatus = (name, online) => (
Div({ class: 'flex items-center gap-2' }, [
Span({
hidden: () => !online(),
class: 'w-3 h-3 bg-green-500 rounded-full'
}),
P({
class: () => online() ? "text-bold" : "text-gray-400"
}, name)
])
);
```
---
## 6. Custom Tags with `Tag`
Create reusable components using `Tag`. All reactivity auto-cleans itself.
### Basic Example
### Basic Component
```javascript
const UserCard = (props, children) =>
Tag('div', { class: 'card p-4', 'data-id': props.id }, children);
div({ class: 'card p-4', 'data-id': props.id }, children);
UserCard({ id: 123 }, [H3("John Doe"), P("john@example.com")]);
UserCard({ id: 123 }, [h3("John Doe"), p("john@example.com")]);
```
### Reactive Component (Auto-Cleaned)
### Reactive Component
```javascript
const Counter = (initial = 0) => {
const count = $(initial);
return Tag('div', { class: 'flex gap-2' }, [
Button({ onclick: () => count(count() - 1) }, '-'),
Span(() => count()),
Button({ onclick: () => count(count() + 1) }, '+')
const Counter = () => {
const count = $(0);
return div({ class: 'flex gap-2' }, [
button({ onClick: () => count(count() - 1) }, '-'),
span(() => count()),
button({ onClick: () => count(count() + 1) }, '+')
]);
};
```
### When Manual Cleanup is Needed
### Manual Cleanup for External Resources
Only for external resources (intervals, sockets, third-party libs):
Only needed for intervals, sockets, thirdparty libraries:
```javascript
const Timer = () => {
const time = $(new Date().toLocaleTimeString());
const el = Tag('span', {}, () => time());
const el = span(() => time());
const interval = setInterval(() => time(new Date().toLocaleTimeString()), 1000);
el._cleanups.add(() => clearInterval(interval)); // Manual cleanup
onUnmount(() => clearInterval(interval));
return el;
};
```
### `Tag` vs Tag Helpers
---
| Use | Recommendation |
|:---|:---|
| Standard tags (`div`, `span`) | `Div()`, `Span()` helpers |
| Reusable components | Function returning `Tag` |
| Dynamic tag names | `Tag(tagName, props, children)` |
## 6. Comparison with `h()`
> **Auto-cleanup**: `Tag` automatically destroys watchers, events, and nested components. Only add to `_cleanups` for external resources.
| Use case | Recommendation |
| :--- | :--- |
| Standard tags (`div`, `span`, `button`) | Use global helpers: `div()`, `span()`, `button()` |
| Dynamic tag names (unknown at write time) | Use `h(tagName, props, children)` |
| Components returning a single node | Any function that returns a node (using helpers or `h`) |
> **Autocleanup:** All tag helpers and `h` automatically dispose effects, event listeners, and nested components when removed from the DOM.
---
| State (`online`) | Rendered HTML | Memory Management |
| :--- | :--- | :--- |
| **`true`** | `<div class="flex..."><span class="w-3..."></span><p class="text-bold">John</p></div>` | Watcher active |
| **`false`** | `<div class="flex..."><span hidden class="w-3..."></span><p class="text-gray-400">John</p></div>` | Attribute synced |
## 7. Complete Example
```javascript
const App = () =>
div({ class: "app" }, [
h1("Welcome"),
input({
placeholder: "Your name",
value: nameSignal,
onInput: (e) => nameSignal(e.target.value)
}),
p(() => `Hello, ${nameSignal() || "stranger"}!`),
button({ onClick: () => alert("Clicked") }, "Click me")
]);
mount(App, '#app');
```
---
<div class="alert alert-info">
<div>
<h3>Important Notes</h3>
<ul>
<li><b>Naming:</b> All tag helpers are <b>lowercase</b>. There are no PascalCase helpers (<code>Div</code>, <code>Button</code>).</li>
<li><b>Global availability:</b> After importing SigPro (via <code>import 'sigpro'</code> or CDN), all helpers are on <code>window</code>. You can use them anywhere without importing.</li>
<li><b>Custom components:</b> Use PascalCase for your own component functions to visually distinguish them from builtin tags (e.g., <code>UserCard</code>).</li>
</ul>
</div>
</div>
---
## 8. Summary
| Feature | Description |
| :--- | :--- |
| **Tag helpers** | Lowercase functions for every HTML element (e.g., `div()`, `button()`). |
| **Reactive attributes** | Pass a function to any attribute to keep it synced. |
| **Twoway binding** | Assign a signal directly to `value` or `checked` on form elements. |
| **Dynamic children** | Pass a function as a child for live updating content. |
| **Autocleanup** | All effects, events, and children are disposed when the element is removed. |

View File

@@ -1,21 +1,22 @@
# Reactivity Control: `Watch( )`
# Reactivity Control: `watch( )`
The `Watch` function is the reactive engine of SigPro. It allows you to execute code automatically when signals change. `Watch` is **polymorphic**: it can track dependencies automatically or follow an explicit list.
The `watch` function is the reactive engine of SigPro. It allows you to execute code automatically when signals change. `watch` is **polymorphic**: it can track dependencies automatically or follow an explicit list.
## Function Signature
```typescript
// Automatic Mode (Magic Tracking)
Watch(callback: Function): StopFunction
watch(callback: Function): StopFunction
// Explicit Mode (Isolated Dependencies)
Watch(deps: Signal[], callback: Function): StopFunction
watch(deps: Signal[], callback: (values: any[]) => void): StopFunction
```
| Parameter | Type | Required | Description |
| :--- | :--- | :--- | :--- |
| **`target / deps`** | `Function` | `Array` | Yes | Either the code to run (Auto) or an array of signals to watch (Explicit). |
| **`callback`** | `Function` | Only in Explicit | The code that will run when the `deps` change. |
| **`callback`** (auto mode) | `Function` | Yes | The code to run. Any signal accessed inside becomes a dependency. |
| **`deps`** (explicit mode) | `Signal[]` | Yes | An array of signals to watch explicitly. |
| **`callback`** (explicit mode) | `Function` | Yes | Runs when any of the `deps` change. Receives an array of their current values. |
**Returns:** A `StopFunction` that, when called, destroys the watcher and releases memory.
@@ -24,68 +25,99 @@ Watch(deps: Signal[], callback: Function): StopFunction
## Usage Patterns
### 1. Automatic Mode (Default)
Any signal you "touch" inside the callback becomes a dependency. SigPro tracks them behind the scenes.
Any signal you **touch** inside the callback becomes a dependency. SigPro tracks them behind the scenes.
```javascript
const count = $(0);
Watch(() => {
// Re-runs every time 'count' changes
watch(() => {
// Reruns every time 'count' changes
console.log(`Count is: ${count()}`);
});
```
### 2. Explicit Mode (Advanced Cleanup)
This mode **isolates** execution. The callback only triggers when the signals in the array change. Any other signal accessed *inside* the callback will NOT trigger a re-run. This is the "gold standard" for Routers and heavy components.
### 2. Explicit Mode (Isolated)
This mode **isolates** execution. The callback only triggers when the signals in the array change. Any other signal accessed *inside* the callback will **not** trigger a rerun. This is ideal for routers or performancecritical components.
```javascript
const sPath = $("/home");
const path = $("/home");
const user = $("Admin");
Watch([sPath], () => {
// Only triggers when 'sPath' changes.
// Changes to 'user' will NOT trigger this, preventing accidental re-renders.
console.log(`Navigating to ${sPath()} as ${user()}`);
watch([path], ([newPath]) => {
// Only triggers when 'path' changes.
// Changes to 'user' will NOT trigger this.
console.log(`Navigating to ${newPath} as ${user()}`);
});
```
### 3. Automatic Cleanup
If your logic creates timers, event listeners, or other reactive effects, SigPro tracks them as "children" of the current watch. When the watcher re-runs or stops, it kills everything inside automatically.
In explicit mode, the callback receives an array of current values corresponding to the `deps` order.
### 3. Stopping a Watcher
Call the returned function to kill the watcher manually.
```javascript
Watch(() => {
const timer = setInterval(() => console.log("Tick"), 1000);
// Register a manual cleanup if needed
// Or simply rely on SigPro to kill nested Watch() calls
return () => clearInterval(timer);
});
```
---
## Stopping a Watcher
Call the returned function to manually kill the watcher. This is essential for manual DOM injections (like Toasts) or long-lived background processes.
```javascript
const stop = Watch(() => console.log(count()));
const stop = watch(() => console.log(count()));
// Later...
stop(); // The link between the signal and this code is physically severed.
stop(); // Disconnects the watcher completely.
```
### 4. Automatic Cleanup Inside Effects
If your watcher creates timers, event listeners, or nested effects, SigPro tracks them as children and cleans them up automatically before rerunning or when stopped.
```javascript
watch(() => {
const timer = setInterval(() => console.log("tick"), 1000);
// No need to manually clear SigPro will dispose it when the watcher reruns or stops.
// (But you can also return a cleanup function if needed)
});
```
---
## Pro Tip: The Microtask Queue
SigPro batches updates. If you update multiple signals in the same execution block, the watcher will only fire **once** at the end of the task.
## Batching & Microtask Queue
SigPro batches reactive updates. If you modify several signals in the same synchronous block, the watcher will fire **only once**, after the task completes.
```javascript
const a = $(0);
const b = $(0);
Watch(() => console.log(a(), b()));
watch(() => console.log(a(), b()));
// This triggers only ONE re-run.
// Triggers only ONE log: "1 2"
a(1);
b(2);
```
This is achieved via `queueMicrotask`, ensuring optimal performance.
---
## Key Points
- **Function name:** `watch` (lowercase) exported from SigPro and also available globally.
- **Auto mode:** `watch(fn)` automatically tracks any signals read inside `fn`.
- **Explicit mode:** `watch([sig1, sig2], (values) => {...})` only reruns when listed signals change; callback receives an array of their new values.
- **Stop function:** returned by both modes; call it to dispose the effect and its children.
- **Batching:** multiple signal writes in one event loop tick trigger a single execution (microtask).
---
## Complete Example
```javascript
const count = $(0);
const step = $(1);
watch(() => {
console.log(`Count changed to ${count()}`);
});
watch([count, step], ([newCount, newStep]) => {
console.log(`Count=${newCount}, step=${newStep} (explicit)`);
});
count(5); // logs: auto + explicit
step(2); // logs: explicit only (auto does not track step)
```

132
docs/api/when.md Normal file
View File

@@ -0,0 +1,132 @@
# Reactive Branching: `when( )`
The `when` function is the reactive control flow operator in SigPro. It conditionally renders one of two branches (or nothing) based on a reactive condition. The inactive branch is completely removed from the DOM and its effects are destroyed, saving memory and CPU.
## Function Signature
```typescript
when(
condition: boolean | Signal<boolean> | (() => boolean),
thenBranch: Node | (() => Node),
elseBranch?: Node | (() => Node) | null
): HTMLElement
```
| Parameter | Type | Required | Description |
| :--- | :--- | :--- | :--- |
| **`condition`** | `boolean` / `Signal` / `() => boolean` | Yes | A value or reactive expression that determines which branch to show. |
| **`thenBranch`** | `Node` / `() => Node` | Yes | Content rendered when the condition is **truthy**. |
| **`elseBranch`** | `Node` / `() => Node` | No | Content rendered when the condition is **falsy**. Defaults to nothing. |
**Returns:** A `div` with `style="display:contents"` that acts as an anchor for the dynamic content. This element is part of the DOM and will be replaced/updated automatically.
> **Note:** The function name is `when` (lowercase). It is exported from SigPro and also available globally after importing.
---
## Usage Patterns
### 1. Simple Toggle
```javascript
const isVisible = $(false);
div([
button({ onclick: () => isVisible(!isVisible()) }, "Toggle"),
when(isVisible,
p("Now you see me!"),
p("Now you don't...")
)
]);
```
### 2. With Functions (Lazy Initialization)
To avoid creating heavy components until they are actually needed, pass a function that returns the branch.
```javascript
when(() => user.isLoggedIn(),
() => DashboardComponent(), // Only created when logged in
() => LoginForm() // Only created when guest
);
```
### 3. Complex Conditions
`condition` can be any expression that returns a boolean it can read signals, computed values, or plain booleans.
```javascript
when(() => count() > 10 && status() === 'ready',
span("Threshold reached!")
);
```
### 4. Without an `else` branch
If no `elseBranch` is provided, nothing is rendered when the condition is falsy.
```javascript
when(loading,
div({ class: "spinner" }, "Loading...")
);
```
---
## Automatic Cleanup
`when` automatically manages the lifecycle of each branch:
- When the condition changes, the current branch is destroyed.
- All effects (`watch`), event listeners, and child `when`/`each` inside the destroyed branch are recursively disposed.
- The new branch is created and mounted.
- Memory leaks are prevented without any manual intervention.
---
## Best Practices
- **Use functions for expensive branches** `() => Component()` ensures the component is only created when the branch becomes active.
- **Avoid inline complex logic** keep conditions readable; extract to computed signals if needed.
- **No manual cleanup required** SigPro handles everything.
---
## Technical Comparison
| Feature | CSS `display: none` | `when` |
| :--- | :--- | :--- |
| **DOM presence** | Always present | Only active branch exists |
| **Event listeners** | Still attached | Removed |
| **Effects (`watch`)** | Still running | Destroyed |
| **Memory usage** | Higher | Optimised (only one branch alive) |
| **Cleanup** | Manual | Automatic |
---
## Complete Example
```javascript
const loggedIn = $(false);
const username = $("Guest");
const Profile = () => div([
h2(`Welcome, ${username()}`),
button({ onclick: () => loggedIn(false) }, "Logout")
]);
const LoginForm = () => div([
input({ placeholder: "Name", onInput: e => username(e.target.value) }),
button({ onclick: () => loggedIn(true) }, "Login")
]);
const App = () =>
div([
when(loggedIn,
() => Profile(),
() => LoginForm()
)
]);
mount(App, '#app');
```

View File

@@ -1,137 +0,0 @@
# Interactive Examples
Explore the power of SigPro through practical patterns. From basic reactivity to advanced composition.
NOTE: Here we use DaisyUI for styles.
---
## 1. Basic Reactivity
The classic counter. Notice how we use `$(0)` to create a signal and the `Button` tag helper to update it.
<div class="card bg-base-200 border border-base-300 shadow-sm my-6">
<div class="card-body">
<h3 class="card-title text-sm uppercase opacity-50 mb-4">Live Demo</h3>
<div id="demo-counter" class="bg-base-100 p-6 rounded-xl border border-base-300 flex items-center justify-center"></div>
</div>
</div>
```javascript
const Counter = () => {
const $count = $(0);
return Div({ class: 'flex gap-4 items-center' }, [
Button({ class: 'btn btn-circle btn-outline', onclick: () => $count(c => c - 1) }, "-"),
Span({ class: 'text-2xl font-mono w-12 text-center' }, $count),
Button({ class: 'btn btn-circle btn-primary', onclick: () => $count(c => c + 1) }, "+")
]);
};
Mount(Counter, '#demo-counter');
```
---
## 2. Derived State (Computed)
Signals can depend on other signals. The "Double" value updates automatically.
<div class="card bg-base-200 border border-base-300 shadow-sm my-6">
<div class="card-body">
<h3 class="card-title text-sm uppercase opacity-50 mb-4">Live Demo</h3>
<div id="demo-computed" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
</div>
</div>
```javascript
const ComputedDemo = () => {
const $count = $(10);
const $double = $(() => $count() * 2);
return Div({ class: 'space-y-4' }, [
Input({ type: 'range', min: 1, max: 100, class: 'range range-primary', value: $count }),
P({ class: 'text-center' }, ["Base: ", $count, " ⮕ ", Span({class: 'text-primary font-bold'}, ["Double: ", $double])])
]);
};
Mount(ComputedDemo, '#demo-computed');
```
---
## 3. Lists and Loops (For)
SigPro handles lists efficiently, only updating specific DOM nodes.
<div class="card bg-base-200 border border-base-300 shadow-sm my-6">
<div class="card-body">
<h3 class="card-title text-sm uppercase opacity-50 mb-4">Live Demo</h3>
<div id="demo-list" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
</div>
</div>
```javascript
const ListDemo = () => {
const $todos = $(['Learn SigPro', 'Build an App']);
const $input = $("");
const addTodo = () => {
if ($input()) {
$todos(prev => [...prev, $input()]);
$input("");
}
};
return Div([
Div({ class: 'flex gap-2 mb-4' }, [
Input({ class: 'input input-bordered flex-1', value: $input, placeholder: 'New task...' }),
Button({ class: 'btn btn-primary', onclick: addTodo }, "Add")
]),
Ul({ class: 'menu bg-base-200 rounded-box p-2' },
For($todos, (item) => Li([A(item)]), (item) => item)
)
]);
};
Mount(ListDemo, '#demo-list');
```
---
## 4. Conditional Rendering (If)
Toggle visibility without re-rendering the entire parent.
<div class="card bg-base-200 border border-base-300 shadow-sm my-6">
<div class="card-body">
<h3 class="card-title text-sm uppercase opacity-50 mb-4">Live Demo</h3>
<div id="demo-if" class="bg-base-100 p-6 rounded-xl border border-base-300 flex justify-center"></div>
</div>
</div>
```javascript
const ConditionalDemo = () => {
const $isVisible = $(false);
return Div({ class: 'text-center w-full' }, [
Button({ class: 'btn btn-outline mb-4', onclick: () => $isVisible(! $isVisible()) }, "Toggle Secret"),
If($isVisible,
() => Div({ class: 'p-4 bg-warning text-warning-content rounded-lg shadow-inner' }, "🤫 SigPro is Awesome!"),
() => Div({ class: 'p-4 opacity-50' }, "Nothing to see here...")
)
]);
};
Mount(ConditionalDemo, '#demo-if');
```
---
## 5. Persistent State
Pass a string as a second argument to `$(val, key)` to sync with `localStorage`.
<div class="card bg-base-200 border border-base-300 shadow-sm my-6">
<div class="card-body">
<h3 class="card-title text-sm uppercase opacity-50 mb-4">Live Demo</h3>
<div id="demo-persist" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
</div>
</div>
```javascript
const PersistDemo = () => {
const $name = $("Guest", "sigpro-demo-name");
return Div({ class: 'flex flex-col gap-2' }, [
H3({ class: 'text-lg font-bold' }, ["Hello, ", $name]),
Input({ class: 'input input-bordered', value: $name, placeholder: 'Type your name...' }),
P({ class: 'text-xs opacity-50' }, "Refresh the page to see magic!")
]);
};
Mount(PersistDemo, '#demo-persist');
```

View File

@@ -37,19 +37,25 @@
successText:
'<svg stroke="currentColor" fill="none" stroke-width="2" viewBox="0 0 24 24" height="1em" width="1em" xmlns="http://www.w3.org/2000/svg"><polyline points="20 6 9 17 4 12"></polyline></svg>',
},
search: {
placeholder: "Type to search",
noData: "No Results!",
depth: 3,
hideOtherSidebarContent: true,
},
plugins: [
function (hook, vm) {
hook.doneEach(function () {
const codeBlocks = document.querySelectorAll(
'pre[data-lang="javascript"] code',
'pre[data-lang="js"] code',
);
codeBlocks.forEach((code) => {
try {
const runDemo = new Function(code.innerText);
const scriptContent = `(function() { ${code.innerText} })();`;
const runDemo = new Function(scriptContent);
runDemo();
} catch (err) {
console.error("Error en la demo de SigPro:", err);
console.error("Error ejecutando demo de SigPro:", err);
}
});
});
@@ -57,9 +63,44 @@
],
};
</script>
<script src="//cdn.jsdelivr.net/npm/docsify/lib/plugins/search.min.js"></script>
<script src="./sigpro.js"></script>
<script src="//cdn.jsdelivr.net/npm/docsify/lib/docsify.min.js"></script>
<script src="//cdn.jsdelivr.net/npm/docsify-copy-code/dist/docsify-copy-code.min.js"></script>
<style>
button, input {
font-family: inherit;
font-size: 1rem;
}
button {
background-color: #3b82f6;
color: white;
padding: 0.5rem 1rem;
border-radius: 0.375rem;
border: none;
cursor: pointer;
margin: 0.25rem;
transition: background-color 0.2s;
}
button:hover {
background-color: #2563eb;
}
input {
padding: 0.5rem;
border: 1px solid #ccc;
border-radius: 0.375rem;
margin: 0.25rem;
background-color: white;
}
input:focus {
outline: none;
border-color: #3b82f6;
box-shadow: 0 0 0 2px rgba(59,130,246,0.2);
}
/* Opcional: para listas y párrafos dentro de demos */
.example, div:has(> button) {
padding: 0.5rem;
}
</style>
</body>
</html>

View File

@@ -1,4 +1,4 @@
# Installation & Setup
# Installation & Setup (SigPro 1.2.18)
SigPro is designed to be drop-in ready. Whether you are building a complex application with a bundler or a simple reactive widget in a single HTML file, SigPro scales with your needs.
@@ -47,36 +47,20 @@ bun add sigpro
```html
<script type="module">
// Import the core and UI components
import SigPro from "[https://cdn.jsdelivr.net/npm/sigpro@latest/+esm](https://cdn.jsdelivr.net/npm/sigpro@latest/+esm)";
import { UI } from "[https://cdn.jsdelivr.net/npm/sigpro@latest/ui/+esm](https://cdn.jsdelivr.net/npm/sigpro@latest/ui/+esm)";
// Initialize UI components globally
UI($);
// Import the core it auto-installs itself globally
import 'https://cdn.jsdelivr.net/npm/sigpro@1.2.18/+esm';
// Now $, $$, watch, h, when, each, fx, router, req, mount, batch and all lowercase tag helpers (div, button, etc.) are available
</script>
```
</div>
</div>
<input type="radio" name="install_method" class="tab border-base-300 whitespace-nowrap" aria-label="CDN (ESM)" />
<div class="tab-content bg-base-100 border-base-300 rounded-box p-6">
<pre class="bg-base-200 p-4 rounded-lg"><code class="language-html">&lt;script type="module"&gt;
// Import the core and UI components
import SigPro from 'https://cdn.jsdelivr.net/npm/sigpro@latest/+esm';
import { UI } from 'https://cdn.jsdelivr.net/npm/sigpro@latest/ui/+esm';
// Initialize UI components globally
UI($);
&lt;/script&gt;</code></pre>
</div>
</div>
---
## 2. Quick Start Examples
SigPro uses **PascalCase** for Tag Helpers (e.g., `Div`, `Button`) to provide a clean, component-like syntax without needing JSX.
SigPro uses **lowercase** Tag Helpers (e.g., `div`, `button`) to keep the syntax close to raw HTML, while still being pure JavaScript functions.
<div class="tabs tabs-box w-full mt-8 mb-12 bg-base-200/50 p-2 rounded-xl border border-base-300">
<input type="radio" name="quick_start_tabs" class="tab !rounded-lg" aria-label="Mainstream (Bundlers)" checked />
@@ -84,29 +68,29 @@ SigPro uses **PascalCase** for Tag Helpers (e.g., `Div`, `Button`) to provide a
```javascript
// File: App.js
import "sigpro";
import 'sigpro'; // auto-installs globals
export const App = () => {
const $count = $(0);
const count = $(0);
// Tag Helpers like Div, H1, Button are available globally
return Div({ class: "card p-4" }, [
H1(["Count is: ", $count]),
Button(
// Tag helpers like div, h1, button are available globally (lowercase)
return div({ class: "card p-4" }, [
h1(() => `Count is: ${count()}`),
button(
{
class: "btn btn-primary",
onclick: () => $count((c) => c + 1),
onclick: () => count(count() + 1),
},
"Increment",
"Increment"
),
]);
};
// File: main.js
import "sigpro";
import { App } from "./App.js";
import 'sigpro';
import { App } from './App.js';
Mount(App, "#app");
mount(App, '#app');
```
</div>
@@ -121,23 +105,23 @@ Mount(App, "#app");
<div id="app"></div>
<script type="module">
import SigPro from "https://cdn.jsdelivr.net/npm/sigpro@latest/+esm";
import 'https://cdn.jsdelivr.net/npm/sigpro@1.2.18/+esm';
const $name = $("Developer");
const name = $('Developer');
// No need to import Div, Section, H2, Input... they are global!
// Lowercase tag helpers: section, h2, input
const App = () =>
Section({ class: "container" }, [
H2(["Welcome, ", $name]),
Input({
section({ class: "container" }, [
h2(() => `Welcome, ${name()}`),
input({
type: "text",
class: "input input-bordered",
$value: $name, // Automatic two-way binding
value: name, // ✅ Two-way binding: signal as value + automatic input event
placeholder: "Type your name...",
}),
]);
Mount(App, "#app");
mount(App, '#app');
</script>
</body>
</html>
@@ -152,17 +136,16 @@ Mount(App, "#app");
One of SigPro's core strengths is its **Global API**, which eliminates "Import Hell" while remaining ESM-compatible.
- **The "Zero-Config" Import:** By simply adding `import SigPro from "sigpro"`, the framework automatically "hydrates" the global `window` object.
- **Core Functions:** You get immediate access to `$`, `Watch`, `Tag`, `If`, `For`, and `Router` anywhere in your scripts without using the `SigPro.` prefix.
- **Auto-Installation:** This happens instantly upon import thanks to its built-in `install()` routine, making it "Plug & Play" for both local projects and CDN usage.
- **The "Zero-Config" Import:** By simply adding `import 'sigpro'` (or importing from the CDN), the framework automatically "hydrates" the global `window` object.
- **Core Functions:** You get immediate access to `$`, `$$`, `watch`, `h`, `when`, `each`, `fx`, `router`, `req`, `mount`, `batch` anywhere in your scripts.
- **Auto-Installation:** This happens instantly upon import thanks to its built-in selfinstallation, making it "Plug & Play" for both local projects and CDN usage.
- **PascalCase Tag Helpers:** Standard HTML tags are pre-registered as global functions (`Div`, `Span`, `Button`, `Section`, etc.).
- **Clean UI Syntax:** This allows you to write UI structures that look like HTML but are pure, reactive JavaScript: `Div({ class: "card" }, [ H1("Title") ])`.
- **Lowercase Tag Helpers:** All standard HTML tags are pre-registered as global functions (`div`, `span`, `button`, `section`, `input`, `h1`, `h2`, etc.).
- **Clean UI Syntax:** Write UI structures that look almost like HTML but are pure, reactive JavaScript: `div({ class: "card" }, [ h1("Title") ])`.
- **Hybrid Tree Shaking:** \* For **Maximum Speed**, use `import SigPro from "sigpro"`.
- For **Maximum Optimization**, you can still use Named Imports: `import { $, Tag } from "sigpro"`. This allows modern bundlers like Vite to prune unused code while keeping your core reactive.
- **Tree Shaking Friendly:** For maximum optimization, you can still use named imports: `import { $, watch, mount } from 'sigpro'`. Modern bundlers (Vite, esbuild) will prune unused code.
- **Custom Components:** We recommend using **PascalCase** for your own components (e.g., `UserCard()`) to maintain visual consistency with the built-in Tag Helpers and distinguish them from standard logic.
- **Custom Components:** We recommend using **PascalCase** for your own components (e.g., `UserCard()`) to distinguish them from built-in lowercase tag helpers.
---
@@ -179,7 +162,7 @@ SigPro stands out by removing the "Build Step" tax and the "Virtual DOM" overhea
| Feature | **SigPro** | **SolidJS** | **Svelte** | **React** | **Vue** |
| :----------------- | :--------------- | :----------- | :----------- | :---------- | :---------- |
| **Bundle Size** | **~2KB** | ~7KB | ~4KB | ~40KB+ | ~30KB |
| **Bundle Size** | **~3KB** | ~7KB | ~4KB | ~40KB+ | ~30KB |
| **DOM Strategy** | **Direct DOM** | Direct DOM | Compiled DOM | Virtual DOM | Virtual DOM |
| **Reactivity** | **Fine-grained** | Fine-grained | Compiled | Re-renders | Proxies |
| **Build Step** | **Optional** | Required | Required | Required | Optional |
@@ -190,14 +173,16 @@ SigPro stands out by removing the "Build Step" tax and the "Virtual DOM" overhea
## 6. Key Advantages
- **Extreme Performance**: No Virtual DOM reconciliation. SigPro updates the specific node or attribute instantly when a Signal changes.
- **Extreme Performance**: No Virtual DOM reconciliation. SigPro updates the specific node or attribute instantly when a signal changes.
- **Fine-Grained Reactivity**: State changes only trigger updates where the data is actually used, not on the entire component.
- **Native Web Standards**: Everything is a standard JS function. No custom template syntax to learn.
- **Zero Magic**: No hidden compilers. What you write is what runs in the browser.
- **Global by Design**: Tag Helpers and the `$` function are available globally to eliminate "Import Hell" and keep your code clean.
- **Global by Design**: Tag helpers and core functions are available globally to eliminate "Import Hell" and keep your code clean.
---
## 7. Summary
SigPro isn't just another framework; it's a bridge to the native web. By using standard ES Modules and functional DOM generation, you gain the benefits of a modern library with the weight of a utility script.
SigPro isn't just another framework; it's a bridge to the native web. By using standard ES Modules and functional DOM generation, you get the benefits of a modern reactive library with the weight of a utility script.
**Because, in the end... why fight the web when we can embrace it?**