From a50733ee73caa73cf671c8a4236205e279236013 Mon Sep 17 00:00:00 2001
From: Natxo <1172351+natxocc@users.noreply.github.com>
Date: Mon, 16 Mar 2026 18:41:02 +0100
Subject: [PATCH] Update Readme.md
---
Readme.md | 1267 +++++++----------------------------------------------
1 file changed, 169 insertions(+), 1098 deletions(-)
diff --git a/Readme.md b/Readme.md
index dd4abb3..2101a0a 100644
--- a/Readme.md
+++ b/Readme.md
@@ -4,7 +4,6 @@ A minimalist reactive library for building web interfaces with signals, effects,
**~3KB** gzipped β‘
-
[](https://www.npmjs.com/package/sigpro)
[](https://bundlephobia.com/package/sigpro)
[](https://github.com/natxocc/sigpro/blob/main/LICENSE)
@@ -56,16 +55,16 @@ What emerged is a library that proves we've reached a turning point: the web is
## π¦ Installation
-```
+```bash
npm install sigpro
```
or
-```
+```bash
bun add sigpro
```
or more simple:
-copy "sigpro.js" file where you want to use it.
+copy `sigpro.js` file where you want to use it.
## π― Philosophy
@@ -102,12 +101,11 @@ html`
|----------|-------------|---------|
| **`$`** | Reactive signal (getter/setter) | `const count = $(0); count(5); count()` |
| **`$.effect`** | Runs effect when dependencies change | `$.effect(() => console.log(count()))` |
+| **`$.page`** | Creates a page with automatic cleanup | `export default $.page(() => { ... })` |
| **`$.component`** | Creates reactive Web Component | `$.component('my-menu', setup, ['items'])` |
| **`$.fetch`** | Fetch wrapper with loading signal | `const data = await $.fetch('/api', data, loading)` |
| **`$.router`** | Hash-based router with params | `$.router([{path:'/', component:Home}])` |
-| **`$.ws`** | WebSocket with reactive state | `const {status, messages} = $.ws(url)` |
| **`$.storage`** | Persistent signal (localStorage) | `const theme = $.storage('theme', 'light')` |
-| **`$.debug`** | System inspector (console table) | `$.debug()` |
| **`html`** | Template literal for reactive HTML | `` html`
${count}
` `` |
```javascript
@@ -126,13 +124,13 @@ Creates a reactive value that notifies dependents when changed.
#### Basic Signal (Getter/Setter)
-```typescript
+```javascript
import { $ } from 'sigpro';
// Create a signal
const count = $(0);
-// Read value (outside reactive context)
+// Read value
console.log(count()); // 0
// Write value
@@ -147,7 +145,7 @@ $.effect(() => {
#### Computed Signal
-```typescript
+```javascript
import { $ } from 'sigpro';
const firstName = $('John');
@@ -160,272 +158,168 @@ console.log(fullName()); // "John Doe"
firstName('Jane');
console.log(fullName()); // "Jane Doe"
-
-// Computed signals cache until dependencies change
-const expensiveComputation = $(() => {
- console.log('Computing...');
- return firstName().length + lastName().length;
-});
-
-console.log(expensiveComputation()); // "Computing..." 7
-console.log(expensiveComputation()); // 7 (cached, no log)
```
-#### Signal with Custom Equality
-
-```typescript
-import { $ } from 'sigpro';
-
-const user = $({ id: 1, name: 'John' });
-
-// Signals use Object.is comparison
-user({ id: 1, name: 'John' }); // Won't trigger updates (same values, new object)
-user({ id: 1, name: 'Jane' }); // Will trigger updates
-```
-
-**Parameters:**
-- `initialValue`: Initial value or getter function for computed signal
-
-**Returns:** Function that acts as getter/setter with the following signature:
-```typescript
-type Signal = {
- (): T; // Getter
- (value: T | ((prev: T) => T)): void; // Setter
-}
-```
+**Returns:** Function that acts as getter/setter
---
-## πΎ `$.storage(key, initialValue, [storage])` - Persistent Signal
-Signal that automatically syncs with localStorage or sessionStorage.
-
-### Basic Persistent State
-```js
-import { $ } from 'sigpro';
-
-// Automatically saves to localStorage
-const theme = $.storage('theme', 'light');
-const user = $.storage('user', null);
-
-theme('dark'); // Saved to localStorage
-// Page refresh... theme() returns 'dark'
-```
-
-### Session Storage
-```js
-// Use sessionStorage instead
-const tempData = $.storage('temp', {}, sessionStorage);
-```
-
-### Real-World Example
-```js
-import { $, html } from 'sigpro';
-
-// User preferences persist across sessions
-const settings = $.storage('app-settings', {
- darkMode: false,
- fontSize: 16,
- language: 'en'
-});
-
-// Auto-saves on any change
-const toggleDarkMode = () => {
- settings({
- ...settings(),
- darkMode: !settings().darkMode
- });
-};
-
-const view = html`
-
-
- Toggle Dark Mode
-
- Current: ${() => settings().darkMode ? 'π' : 'βοΈ'}
-
-`;
-```
-
-### Shopping Cart Example
-```js
-import { $, html } from 'sigpro';
-
-const cart = $.storage('shopping-cart', []);
-
-const addToCart = (item) => {
- cart([...cart(), item]);
-};
-
-const CartView = html`
-
-
Cart (${() => cart().length} items)
-
- ${() => cart().map(item => html`
- ${item.name} - ${item.price}
- `)}
-
-
cart([])}>Clear Cart
-
-`;
-```
-
-### Auto-Cleanup
-```js
-// Remove from storage when value is null/undefined
-$.storage('temp', null); // Removes 'temp' from storage
-```
-
-**Parameters:**
-- `key`: Storage key name
-- `initialValue`: Default value if none stored
-- `storage`: Storage type (default: `localStorage`, options: `sessionStorage`)
-
-**Returns:** Signal function that persists to storage on changes
-
----
-
-### `$.effect(effect)` - Effects
+### `$.effect(effectFn)` - Effects
Executes a function and automatically re-runs it when its dependencies change.
#### Basic Effect
-```typescript
+```javascript
import { $ } from 'sigpro';
const count = $(0);
-const name = $('World');
-// Effect runs immediately and on dependency changes
$.effect(() => {
- console.log(`Count is: ${count()}`); // Only depends on count
+ console.log(`Count is: ${count()}`);
});
// Log: "Count is: 0"
count(1);
// Log: "Count is: 1"
-
-name('Universe'); // No log (name is not a dependency)
```
#### Effect with Cleanup
-```typescript
+```javascript
import { $ } from 'sigpro';
const userId = $(1);
$.effect(() => {
const id = userId();
- let isSubscribed = true;
- // Simulate API subscription
- const subscription = api.subscribe(id, (data) => {
- if (isSubscribed) {
- console.log('New data:', data);
- }
- });
+ // Simulate subscription
+ const timer = setInterval(() => {
+ console.log('Polling user', id);
+ }, 1000);
// Return cleanup function
- return () => {
- isSubscribed = false;
- subscription.unsubscribe();
- };
+ return () => clearInterval(timer);
});
-userId(2); // Previous subscription cleaned up, new one created
-```
-
-#### Nested Effects
-
-```typescript
-import { $ } from 'sigpro';
-
-const show = $(true);
-const count = $(0);
-
-$.effect(() => {
- if (!show()) return;
-
- // This effect is nested inside the conditional
- // It will only be active when show() is true
- $.effect(() => {
- console.log('Count changed:', count());
- });
-});
-
-show(false); // Inner effect is automatically cleaned up
-count(1); // No log (inner effect not active)
-show(true); // Inner effect recreated, logs "Count changed: 1"
-```
-
-#### Manual Effect Control
-
-```typescript
-import { $ } from 'sigpro';
-
-const count = $(0);
-
-// Stop effect manually
-const stop = $.effect(() => {
- console.log('Effect running:', count());
-});
-
-count(1); // Log: "Effect running: 1"
-stop();
-count(2); // No log
+userId(2); // Previous timer cleared, new one created
```
**Parameters:**
-- `effect`: Function to execute. Can return a cleanup function
+- `effectFn`: Function to execute. Can return a cleanup function
**Returns:** Function to stop the effect
---
-## π‘ `$.fetch(url , data, [loading])` - Fetch
-Simple fetch wrapper with automatic JSON handling and optional loading signal. Perfect for API calls.
+### `$.page(setupFunction)` - Pages
-### Basic Fetch
-```js
-import { $ } from 'sigpro';
+Creates a page with automatic cleanup of all signals and effects when navigated away.
-const userData = $(null);
+```javascript
+// pages/about.js
+import { html, $ } from "sigpro";
-// Simple POST request
-const result = await $.fetch('/api/users', { name: 'John' });
+export default $.page(() => {
+ const count = $(0);
+ const loading = $(false);
+
+ $.effect(() => {
+ if (loading()) {
+ // Fetch data...
+ }
+ });
+
+ return html`
+
+
About Page
+
Count: ${count}
+
count(c => c + 1)}>Increment
+
+ `;
+});
```
-### Fetch with Loading State
-```js
+**With parameters:**
+```javascript
+// pages/user.js
+export default $.page(({ params }) => {
+ const userId = params.id;
+ const userData = $(null);
+
+ $.effect(() => {
+ fetch(`/api/users/${userId}`)
+ .then(r => r.json())
+ .then(userData);
+ });
+
+ return html`User: ${userData}
`;
+});
+```
+
+**Parameters:**
+- `setupFunction`: Function that returns the page content. Receives `{ params, onUnmount }`
+
+**Returns:** A function that creates page instances with props
+
+---
+
+### `$.component(tagName, setupFunction, observedAttributes)` - Web Components
+
+Creates Custom Elements with reactive properties.
+
+#### Basic Component
+
+```javascript
+import { $, html } from 'sigpro';
+
+$.component('my-counter', (props, { slot, emit, onUnmount }) => {
+ const increment = () => {
+ props.value(v => (parseInt(v) || 0) + 1);
+ emit('change', props.value());
+ };
+
+ return html`
+
+
Value: ${props.value}
+
+
+ ${slot()}
+
+ `;
+}, ['value']); // Observed attributes
+```
+
+Usage:
+```html
+ console.log(e.detail)}>
+ Child content
+
+```
+
+**Parameters:**
+- `tagName`: Custom element tag name (must contain a hyphen)
+- `setupFunction`: Component setup function
+- `observedAttributes`: Array of attribute names to observe
+
+---
+
+### `$.fetch(url, data, [loading])` - Fetch
+
+Simple fetch wrapper with automatic JSON handling and optional loading signal.
+
+```javascript
import { $ } from 'sigpro';
const loading = $(false);
-const userData = $(null);
async function loadUser(id) {
const data = await $.fetch(`/api/users/${id}`, null, loading);
if (data) userData(data);
}
-// Loading() automatically toggles true/false
-loadUser(123);
-
// In your UI
-html`
-
- ${() => loading() ? 'Loading...' : userData()?.name}
-
-`;
-```
-
-### Error Handling
-```js
-const data = await $.fetch('/api/users', { id: 123 });
-if (!data) {
- // Handle error silently (returns null on failure)
- console.error('Request failed');
-}
+html`${() => loading() ? 'Loading...' : userData()?.name}
`;
```
**Parameters:**
@@ -437,924 +331,101 @@ if (!data) {
---
-## π `$.ws(url, [options])` - WebSocket
-Reactive WebSocket wrapper with automatic reconnection and signal-based state management.
+### `$.storage(key, initialValue, [storage])` - Persistent Signal
-### Basic WebSocket
-```js
+Signal that automatically syncs with localStorage or sessionStorage.
+
+```javascript
import { $ } from 'sigpro';
-const socket = $.ws('wss://echo.websocket.org');
+// Automatically saves to localStorage
+const theme = $.storage('theme', 'light');
+const user = $.storage('user', null);
-// Reactive status (disconnected/connecting/connected/error)
-socket.status() // 'connected'
+theme('dark'); // Saved to localStorage
+// Page refresh... theme() returns 'dark'
-// Incoming messages as reactive array
-socket.messages() // ['Hello', 'World']
-
-// Send messages
-socket.send('Hello Server!');
-socket.send({ type: 'ping', data: 'test' });
-```
-
-### Auto-Reconnect Configuration
-```js
-const socket = $.ws('wss://api.example.com', {
- reconnect: true, // Enable auto-reconnect
- maxReconnect: 5, // Max attempts (default: 5)
- reconnectInterval: 1000 // Base interval in ms (uses exponential backoff)
-});
-```
-
-### Reactive UI Integration
-```js
-import { $, html } from 'sigpro';
-
-const chat = $.ws('wss://chat.example.com');
-
-const view = html`
-
-
-
- Status: ${() => chat.status()}
-
-
-
- ${() => chat.error() ? html`
Connection failed
` : ''}
-
-
-
- ${() => chat.messages().map(msg => html`${msg} `)}
-
-
-
-
''} @keydown.enter=${(e) => {
- chat.send(e.target.value);
- e.target.value = '';
- }}/>
-
-`;
-```
-
-### Manual Control
-```js
-const socket = $.ws('wss://api.example.com');
-
-// Send data
-socket.send({ action: 'subscribe', channel: 'updates' });
-
-// Close connection manually
-socket.close();
-
-// Check connection status
-if (socket.status() === 'connected') {
- // Do something
-}
+// Use sessionStorage instead
+const tempData = $.storage('temp', {}, sessionStorage);
```
**Parameters:**
-- `url`: WebSocket server URL
-- `options`: Configuration object
- - `reconnect`: Enable auto-reconnect (default: true)
- - `maxReconnect`: Maximum reconnection attempts (default: 5)
- - `reconnectInterval`: Base interval for exponential backoff (default: 1000ms)
+- `key`: Storage key name
+- `initialValue`: Default value if none stored
+- `storage`: Storage type (default: `localStorage`, options: `sessionStorage`)
-**Returns:** Object with reactive properties and methods
-- `status`: Signal with connection state
-- `messages`: Signal array of received messages
-- `error`: Signal with last error
-- `send(data)`: Function to send data
-- `close()`: Function to close connection
-
----
-
-### `html` - Template Literal Tag
-
-Creates reactive DOM fragments using template literals with intelligent binding.
-
-#### Basic Usage
-
-```typescript
-import { $, html } from 'sigpro';
-
-const count = $(0);
-const name = $('World');
-
-const fragment = html`
-
-
Hello ${name}
-
Count: ${count}
-
count(c => c + 1)}>
- Increment
-
-
-`;
-
-document.body.appendChild(fragment);
-```
-
-#### Directive Reference
-
-##### `@event` - Event Listeners
-
-```typescript
-import { html } from 'sigpro';
-
-const handleClick = (event) => console.log('Clicked!', event);
-const handleInput = (value) => console.log('Input:', value);
-
-html`
-
- Click me
-
-
- console.log(e.target.value)} />
-
-
-
-`
-```
-
-##### `:property` - Two-way Binding
-
-Automatically syncs between signal and DOM element.
-
-```typescript
-import { $, html } from 'sigpro';
-
-const text = $('');
-const checked = $(false);
-const selected = $('option1');
-
-html`
-
-
- You typed: ${text}
-
-
-
- Checkbox is: ${() => checked() ? 'checked' : 'unchecked'}
-
-
-
- Option 1
- Option 2
-
-
-
-
-
-
-
- text('New value')}>Set from code
-
-`
-```
-
-##### `?attribute` - Boolean Attributes
-
-```typescript
-import { $, html } from 'sigpro';
-
-const isDisabled = $(true);
-const isChecked = $(false);
-const hasError = $(false);
-
-html`
-
- ${() => isDisabled() ? 'Disabled' : 'Enabled'}
-
-
-
-
- !hasError()} class="error">
- An error occurred
-
-
-
-
- Option 1
- Option 2
-
-`
-```
-
-##### `.property` - Property Binding
-
-Directly binds to DOM properties, not attributes.
-
-```typescript
-import { $, html } from 'sigpro';
-
-const scrollTop = $(0);
-const user = $({ name: 'John', age: 30 });
-const items = $([1, 2, 3]);
-
-html`
-
-
- Content...
-
-
-
-
-
-
-
-
-
-
-
-
-
-`
-```
-
-##### Regular Attributes
-
-```typescript
-import { $, html } from 'sigpro';
-
-const className = $('big red');
-const href = $('#section');
-const style = $('color: blue');
-
-// Static attributes
-html`
`
-
-// Dynamic attributes (non-directive)
-html`
`
-
-// Mix of static and dynamic
-html`Link `
-
-// Reactive attributes update when signal changes
-$.effect(() => {
- // The attribute updates automatically
- console.log('Class changed:', className());
-});
-```
-
-#### Conditional Rendering
-
-```typescript
-import { $, html } from 'sigpro';
-
-const show = $(true);
-const user = $({ name: 'John', role: 'admin' });
-
-// Using ternary
-html`
- ${() => show() ? html`
- Content is visible
- ` : html`
- Content is hidden
- `}
-`
-
-// Using logical AND
-html`
- ${() => user().role === 'admin' && html`
- Admin Panel
- `}
-`
-
-// Complex conditions
-html`
- ${() => {
- if (!show()) return null;
- if (user().role === 'admin') {
- return html`Admin view
`;
- }
- return html`User view
`;
- }}
-`
-```
-
-#### List Rendering
-
-```typescript
-import { $, html } from 'sigpro';
-
-const items = $([1, 2, 3, 4, 5]);
-const todos = $([
- { text: 'Learn SigPro', done: true },
- { text: 'Build an app', done: false }
-]);
-
-// Basic list
-html`
-
- ${() => items().map(item => html`
- Item ${item}
- `)}
-
-`
-
-// List with conditional styling
-html`
-
-`
-
-// Nested lists
-const matrix = $([[1, 2], [3, 4], [5, 6]]);
-
-html`
-
- ${() => matrix().map(row => html`
-
- ${() => row.map(cell => html`
- ${cell}
- `)}
-
- `)}
-
-`
-```
-
-#### Dynamic Tag Names
-
-```typescript
-import { $, html } from 'sigpro';
-
-const tagName = $('h1');
-const level = $(1);
-
-html`
-
-
- This will be wrapped in ${tagName} tags
-
-
-
- ${() => {
- const Tag = `h${level()}`;
- return html`
- <${Tag}>Level ${level()} Heading${Tag}>
- `;
- }}
-`
-```
-
-#### Template Composition
-
-```typescript
-import { $, html } from 'sigpro';
-
-const Header = () => html``;
-const Footer = () => html``;
-
-const Layout = ({ children }) => html`
- ${Header()}
-
- ${children}
-
- ${Footer()}
-`
-
-const Page = () => html`
- ${Layout({
- children: html`
- Page Content
- Some content here
- `
- })}
-`
-```
-
----
-
-### `$.component(tagName, setupFunction, observedAttributes)` - Web Components
-
-Creates Custom Elements with reactive properties. Uses Light DOM (no Shadow DOM) and a slot system based on node filtering.
-
-#### Basic Component
-
-```javascript
-import { $, html } from 'sigpro';
-
-$.component('my-counter', (props, context) => {
- // props contains signals for each observed attribute
- // context: { slot, emit, host, onUnmount }
-
- const increment = () => {
- props.value(v => (parseInt(v) || 0) + 1);
- };
-
- return html`
-
-
Value: ${props.value}
-
Increment
-
-
- ${context.slot()}
-
- `;
-}, ['value']); // Observed attributes
-```
-
-Usage:
-```html
-
- βΌ This is the default slot
- More content in the slot
-
-
-
-```
-
-#### Component with Named Slots
-
-```javascript
-import { $, html } from 'sigpro';
-
-$.component('my-card', (props, { slot }) => {
- return html`
-
-
-
-
- ${slot()}
-
-
-
-
- `;
-}, []);
-```
-
-Usage:
-```html
-
- Card Title
-
- This goes to default slot
- Also default slot
-
-
- Action
-
-
-```
-
-#### Component with Props and Events
-
-```javascript
-import { $, html } from 'sigpro';
-
-$.component('todo-item', (props, { emit, host }) => {
- const handleToggle = () => {
- props.completed(c => !c);
- emit('toggle', { id: props.id(), completed: props.completed() });
- };
-
- const handleDelete = () => {
- emit('delete', { id: props.id() });
- };
-
- return html`
-
-
- props.completed() ? 'text-decoration: line-through' : ''}>
- ${props.text}
-
- β
-
- `;
-}, ['id', 'text', 'completed']);
-```
-
-Usage:
-```html
- console.log('Toggled:', e.detail)}
- @delete=${(e) => console.log('Deleted:', e.detail)}
->
-```
-
-#### Component with Cleanup
-
-```javascript
-import { $, html } from 'sigpro';
-
-$.component('timer-widget', (props, { onUnmount }) => {
- const seconds = $(0);
-
- // Effect with automatic cleanup
- $.effect(() => {
- const interval = setInterval(() => {
- seconds(s => s + 1);
- }, 1000);
-
- // Return cleanup function
- return () => clearInterval(interval);
- });
-
- // Register unmount hook
- onUnmount(() => {
- console.log('Timer widget unmounted');
- });
-
- return html`
-
-
Seconds: ${seconds}
-
Initial value: ${props.initial}
-
- `;
-}, ['initial']);
-```
-
-#### Complete Context API
-
-```javascript
-import { $, html } from 'sigpro';
-
-$.component('context-demo', (props, context) => {
- // Context properties:
- // - slot(name) - Gets child nodes with matching slot attribute
- // - emit(name, detail) - Dispatches custom event
- // - host - Reference to the custom element instance
- // - onUnmount(callback) - Register cleanup function
-
- const {
- slot, // Function: (name?: string) => Node[]
- emit, // Function: (name: string, detail?: any) => void
- host, // HTMLElement: the custom element itself
- onUnmount // Function: (callback: () => void) => void
- } = context;
-
- // Access host directly
- console.log('Host element:', host);
- console.log('Host attributes:', host.getAttribute('my-attr'));
-
- // Handle events
- const handleClick = () => {
- emit('my-event', { message: 'Hello from component' });
- };
-
- // Register cleanup
- onUnmount(() => {
- console.log('Cleaning up...');
- });
-
- return html`
-
- ${slot('header')}
- Emit Event
- ${slot()}
- ${slot('footer')}
-
- `;
-}, []);
-```
-
-#### Practical Example: Todo App Component
-
-```javascript
-import { $, html } from 'sigpro';
-
-$.component('todo-app', () => {
- const todos = $([]);
- const newTodo = $('');
- const filter = $('all');
-
- const addTodo = () => {
- if (newTodo().trim()) {
- todos([...todos(), {
- id: Date.now(),
- text: newTodo(),
- completed: false
- }]);
- newTodo('');
- }
- };
-
- const filteredTodos = $(() => {
- const currentFilter = filter();
- const allTodos = todos();
-
- if (currentFilter === 'active') {
- return allTodos.filter(t => !t.completed);
- }
- if (currentFilter === 'completed') {
- return allTodos.filter(t => t.completed);
- }
- return allTodos;
- });
-
- return html`
-
-
π Todo App
-
-
-
- e.key === 'Enter' && addTodo()}
- placeholder="What needs to be done?"
- />
- Add
-
-
-
-
- filter('all')}>All
- filter('active')}>Active
- filter('completed')}>Completed
-
-
-
-
- ${() => filteredTodos().map(todo => html`
- {
- const { id, completed } = e.detail;
- todos(todos().map(t =>
- t.id === id ? { ...t, completed } : t
- ));
- }}
- @delete=${(e) => {
- todos(todos().filter(t => t.id !== e.detail.id));
- }}
- >
- `)}
-
-
-
-
- ${() => {
- const total = todos().length;
- const completed = todos().filter(t => t.completed).length;
- return html`
- Total: ${total}
- Completed: ${completed}
- Remaining: ${total - completed}
- `;
- }}
-
-
- `;
-}, []);
-```
-
-#### Key Points About `$.component`:
-
-1. **Light DOM only** - No Shadow DOM, children are accessible and styleable from outside
-2. **Slot system** - `slot()` function filters child nodes by `slot` attribute
-3. **Reactive props** - Each observed attribute becomes a signal in the `props` object
-4. **Event emission** - `emit()` dispatches custom events with `detail` payload
-5. **Cleanup** - `onUnmount()` registers functions called when component is removed
-6. **Host access** - `host` gives direct access to the custom element instance
+**Returns:** Signal function that persists to storage on changes
---
### `$.router(routes)` - Router
-Hash-based router for SPAs with reactive integration.
+Hash-based router for SPAs with automatic page cleanup.
-#### Basic Routing
-
-```typescript
+```javascript
import { $, html } from 'sigpro';
+import HomePage from './pages/index.js';
+import AboutPage from './pages/about.js';
+import UserPage from './pages/user.js';
-const router = $.router([
- {
- path: '/',
- component: () => html`
- Home Page
- About
- `
- },
- {
- path: '/about',
- component: () => html`
- About Page
- Home
- `
- }
-]);
+const routes = [
+ { path: '/', component: (params) => HomePage(params) },
+ { path: '/about', component: (params) => AboutPage(params) },
+ { path: /^\/user\/(?\d+)$/, component: (params) => UserPage(params) },
+];
+const router = $.router(routes);
document.body.appendChild(router);
+
+// Navigate programmatically
+$.router.go('/about');
```
-#### Route Parameters
+**Parameters:**
+- `routes`: Array of route objects with `path` (string or RegExp) and `component` function
-```typescript
-import { $, html } from 'sigpro';
+**Returns:** Container element with the current page
-const router = $.router([
- {
- path: '/user/:id',
- component: (params) => html`
- User Profile
- User ID: ${params.id}
- Edit
- `
- },
- {
- path: '/user/:id/posts/:postId',
- component: (params) => html`
- Post ${params.postId} by User ${params.id}
- `
- },
- {
- path: /^\/product\/(?\w+)\/(?\d+)$/,
- component: (params) => html`
- Product ${params.id} in ${params.category}
- `
- }
-]);
-```
+---
-#### Nested Routes
+### `html` - Template Literal Tag
-```typescript
-import { $, html } from 'sigpro';
+Creates reactive DOM fragments using template literals.
-const router = $.router([
- {
- path: '/',
- component: () => html`
- Home
-
- Dashboard
-
- `
- },
- {
- path: '/dashboard',
- component: () => {
- // Nested router
- const subRouter = $.router([
- {
- path: '/',
- component: () => html`Dashboard Home `
- },
- {
- path: '/settings',
- component: () => html`Dashboard Settings `
- },
- {
- path: '/profile/:id',
- component: (params) => html`Profile ${params.id} `
- }
- ]);
-
- return html`
-
- `;
- }
- }
-]);
-```
-
-#### Route Guards
-
-```typescript
-import { $, html } from 'sigpro';
-
-const isAuthenticated = $(false);
-
-const requireAuth = (component) => (params) => {
- if (!isAuthenticated()) {
- $.router.go('/login');
- return null;
- }
- return component(params);
-};
-
-const router = $.router([
- {
- path: '/',
- component: () => html`Public Home `
- },
- {
- path: '/dashboard',
- component: requireAuth((params) => html`
- Protected Dashboard
- `)
- },
- {
- path: '/login',
- component: () => html`
- Login
- isAuthenticated(true)}>Login
- `
- }
-]);
-```
-## π `$.debug()` - System Inspector
-
-Inspects the entire reactive system and displays a clean table of all signals and effects in the console.
+#### Basic Usage
```javascript
-import { $ } from 'sigpro';
+import { $, html } from 'sigpro';
-// Create some signals and effects
const count = $(0);
-const name = $('World');
-const fullName = $(() => `${name()} SigPro`);
-$.effect(() => {
- console.log('Count changed:', count());
-});
-
-$.effect(() => {
- console.log('Name changed:', name());
-});
-
-// Inspect the system
-$.debug();
+const fragment = html`
+
+
Count: ${count}
+ count(c => c + 1)}>+
+
+`;
```
-### Console Output
+#### Directive Reference
-```
-π SigPro
-π SIGNALS (3)
-βββββββββββ¬βββββββ¬ββββββββββ¬βββββββ
-β (index) β ID β Value β Subs β
-βββββββββββΌβββββββΌββββββββββΌβββββββ€
-β 0 β "a1b2"β 5 β 1 β
-β 1 β "c3d4"β "World" β 2 β
-β 2 β "e5f6"β "Worldβ¦"β 0 β
-βββββββββββ΄βββββββ΄ββββββββββ΄βββββββ
-
-β‘ EFFECTS (2)
-βββββββββββ¬βββββββ¬βββββββ¬βββββββββ
-β (index) β ID β Deps β Cleanupβ
-βββββββββββΌβββββββΌβββββββΌβββββββββ€
-β 0 β "x7y8"β 1 β β β
-β 1 β "z9w0"β 1 β β β
-βββββββββββ΄βββββββ΄βββββββ΄βββββββββ
-
-π Queue: 0
-```
-
-### What it shows
-
-| Column | Description |
-|--------|-------------|
-| **ID** | Unique identifier for each signal/effect |
-| **Value** | Current value of the signal |
-| **Subs** | Number of subscribers (effects watching this signal) |
-| **Deps** | Number of dependencies this effect watches |
-| **Cleanup** | Whether the effect has a cleanup function |
-| **Queue** | Pending effects waiting to run |
-
-### Debug at any time
+| Directive | Example | Description |
+|-----------|---------|-------------|
+| `@event` | `@click=${handler}` | Event listener |
+| `:property` | `:value=${signal}` | Two-way binding |
+| `?attribute` | `?disabled=${signal}` | Boolean attribute |
+| `.property` | `.scrollTop=${signal}` | Property binding |
+**Two-way binding example:**
```javascript
-// After user interaction
-button.addEventListener('click', () => {
- count(count() + 1);
- $.debug(); // See how subscribers and dependencies changed
-});
+const text = $('');
-// When things get weird
-setTimeout(() => {
- $.debug(); // Check for memory leaks (signals/effects that should be gone)
-}, 5000);
+html`
+
+ You typed: ${text}
+`;
```
-**Perfect for:** detecting memory leaks, understanding reactivity, and debugging complex signal interactions.
-
-
-
-
-
+## π License
+MIT Β© natxocc