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 ⚑ - [![npm version](https://img.shields.io/npm/v/sigpro.svg)](https://www.npmjs.com/package/sigpro) [![bundle size](https://img.shields.io/bundlephobia/minzip/sigpro)](https://bundlephobia.com/package/sigpro) [![license](https://img.shields.io/npm/l/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` -
- - 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)

- - -
-`; -``` - -### 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}

+ +
+ `; +}); ``` -### 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
` : ''} - - - - - - ''} @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}

- -
-`; - -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` - - - - - 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'}

- - - - - - - - - - - -` -``` - -##### `?attribute` - Boolean Attributes - -```typescript -import { $, html } from 'sigpro'; - -const isDisabled = $(true); -const isChecked = $(false); -const hasError = $(false); - -html` - - - - -
!hasError()} class="error"> - An error occurred -
- - - -` -``` - -##### `.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` - - `} -` - -// 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` - -` - -// 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 - `; - }} -` -``` - -#### Template Composition - -```typescript -import { $, html } from 'sigpro'; - -const Header = () => html`
Header
`; -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}

- - - - ${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('header')} -
- -
- ${slot()} -
- - -
- `; -}, []); -``` - -Usage: -```html - -

Card Title

- -

This goes to default slot

- Also default slot - -
- -
-
-``` - -#### 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')} - - ${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?" - /> - -
- - -
- - - -
- - -
- ${() => 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

- - ` - }, - { - 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` -
-

Dashboard

- - ${subRouter} -
- `; - } - } -]); -``` - -#### 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

- - ` - } -]); -``` -## πŸ” `$.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}

+ +
+`; ``` -### 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