From f4654a938ae7b341e8d224f6eb2132a4dbeb15f3 Mon Sep 17 00:00:00 2001 From: natxocc Date: Sat, 25 Apr 2026 20:28:38 +0200 Subject: [PATCH] Update docs --- docs/_sidebar.md | 15 +- docs/api/each.md | 160 +++++++++++++++ docs/api/for.md | 83 -------- docs/api/fx.md | 182 +++++++++++++++++ docs/api/global.md | 120 ++++++++--- docs/api/h.md | 159 +++++++++++++++ docs/api/html.md | 104 ---------- docs/api/if.md | 180 ---------------- docs/api/jsx.md | 262 +++++++++++++++++++----- docs/api/mount.md | 141 +++++++++---- docs/api/quick.md | 499 ++++++++++++++++++++++++++++----------------- docs/api/req.md | 182 +++++++++++++++++ docs/api/router.md | 179 ++++++++++++---- docs/api/signal.md | 327 ++++++++++++++++------------- docs/api/tags.md | 208 ++++++++++--------- docs/api/watch.md | 112 ++++++---- docs/api/when.md | 132 ++++++++++++ docs/examples.md | 137 ------------- docs/index.html | 51 ++++- docs/install.md | 89 ++++---- 20 files changed, 2120 insertions(+), 1202 deletions(-) create mode 100644 docs/api/each.md delete mode 100644 docs/api/for.md create mode 100644 docs/api/fx.md create mode 100644 docs/api/h.md delete mode 100644 docs/api/html.md delete mode 100644 docs/api/if.md create mode 100644 docs/api/req.md create mode 100644 docs/api/when.md delete mode 100644 docs/examples.md diff --git a/docs/_sidebar.md b/docs/_sidebar.md index 8361aa2..e823f7a 100644 --- a/docs/_sidebar.md +++ b/docs/_sidebar.md @@ -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) \ No newline at end of file diff --git a/docs/api/each.md b/docs/api/each.md new file mode 100644 index 0000000..a28467d --- /dev/null +++ b/docs/api/each.md @@ -0,0 +1,160 @@ +# Reactive Lists: `each( )` + +The `each` function is a high‑performance 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[], + 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 re‑executed every time the item’s 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 (non‑reactive) 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` | +| :--- | :--- | :--- | +| **Re‑renders on change** | Re‑creates 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 re‑render) | Built‑in, fine‑grained | + +--- + +## 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'); +``` \ No newline at end of file diff --git a/docs/api/for.md b/docs/api/for.md deleted file mode 100644 index 069f43e..0000000 --- a/docs/api/for.md +++ /dev/null @@ -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 | 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)** | \ No newline at end of file diff --git a/docs/api/fx.md b/docs/api/fx.md new file mode 100644 index 0000000..1c07ebe --- /dev/null +++ b/docs/api/fx.md @@ -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 built‑in 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. \ No newline at end of file diff --git a/docs/api/global.md b/docs/api/global.md index 84ee6e2..d357ec4 100644 --- a/docs/api/global.md +++ b/docs/api/global.md @@ -1,67 +1,70 @@ # 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 (`.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. File‑Based Stores (`.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. ```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 (auto‑syncs 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. 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. ```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 SigPro’s `$(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 SigPro’s `$(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, +// This single line creates a global, reactive, // and persistent store for a shopping cart. export const cart = $([], "session-cart"); ``` @@ -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"); +``` diff --git a/docs/api/h.md b/docs/api/h.md new file mode 100644 index 0000000..9cc2295 --- /dev/null +++ b/docs/api/h.md @@ -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 re‑evaluated 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. Two‑Way 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 two‑way 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, DSL‑like | +| **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 low‑level DOM builder used internally by all tag helpers. +- It supports reactive attributes, reactive children, two‑way 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. \ No newline at end of file diff --git a/docs/api/html.md b/docs/api/html.md deleted file mode 100644 index 418493e..0000000 --- a/docs/api/html.md +++ /dev/null @@ -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") ]) -``` diff --git a/docs/api/if.md b/docs/api/if.md deleted file mode 100644 index 8415d82..0000000 --- a/docs/api/if.md +++ /dev/null @@ -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 | 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); } -}; -``` \ No newline at end of file diff --git a/docs/api/jsx.md b/docs/api/jsx.md index dbb1f25..a0d71c6 100644 --- a/docs/api/jsx.md +++ b/docs/api/jsx.md @@ -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 low‑level DOM builder. +2. **Global Tag Helpers** (e.g., `div()`, `button()`, `span()`) – a convenient DSL built on top of `h`. + +Both are reactive, auto‑cleanup, and support SVG, events, two‑way 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 – auto‑removed on cleanup. | +| **`value` / `checked`** | When a signal is passed, creates two‑way 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 compile‑time 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 }) => (