Update print statement from 'Hello' to 'Goodbye'

This commit is contained in:
Natxo
2026-03-16 00:49:51 +01:00
committed by GitHub
parent 191983dc8e
commit b1776bc66b

188
Readme.md
View File

@@ -2,7 +2,7 @@
A minimalist reactive library for building web interfaces with signals, effects, and native web components. No compilation, no virtual DOM, just pure JavaScript and intelligent reactivity. A minimalist reactive library for building web interfaces with signals, effects, and native web components. No compilation, no virtual DOM, just pure JavaScript and intelligent reactivity.
**~2KB** gzipped ⚡ **~3KB** gzipped ⚡
[![npm version](https://img.shields.io/npm/v/sigpro.svg)](https://www.npmjs.com/package/sigpro) [![npm version](https://img.shields.io/npm/v/sigpro.svg)](https://www.npmjs.com/package/sigpro)
@@ -101,51 +101,23 @@ html`
# SigPro API - Quick Reference # SigPro API - Quick Reference
## Option 1: Individual Imports
| Function | Description | Example | | Function | Description | Example |
|----------|-------------|---------| |----------|-------------|---------|
| **`$`** | Reactive signal (getter/setter) | `const count = $(0); count(5); count()` | | **`$`** | Reactive signal (getter/setter) | `const count = $(0); count(5); count()` |
| **`$effect`** | Runs effect when dependencies change | `$effect(() => console.log(count()))` | | **`$.effect`** | Runs effect when dependencies change | `$.effect(() => console.log(count()))` |
| **`$component`** | Creates reactive Web Component | `$component('my-menu', setup, ['items'])` | | **`$.component`** | Creates reactive Web Component | `$.component('my-menu', setup, ['items'])` |
| **`$fetch`** | Fetch wrapper with loading signal | `const data = await $fetch('/api', data, loading)` | | **`$.fetch`** | Fetch wrapper with loading signal | `const data = await $.fetch('/api', data, loading)` |
| **`$router`** | Hash-based router with params | `$router([{path:'/', component:Home}])` | | **`$.router`** | Hash-based router with params | `$.router([{path:'/', component:Home}])` |
| **`$ws`** | WebSocket with reactive state | `const {status, messages} = $ws(url)` | | **`$.ws`** | WebSocket with reactive state | `const {status, messages} = $.ws(url)` |
| **`$storage`** | Persistent signal (localStorage) | `const theme = $storage('theme', 'light')` | | **`$.storage`** | Persistent signal (localStorage) | `const theme = $.storage('theme', 'light')` |
| **`html`** | Template literal for reactive HTML | `` html`<div>${count}</div>` `` | | **`html`** | Template literal for reactive HTML | `` html`<div>${count}</div>` `` |
```javascript
import { $, $effect, $component, $fetch, $router, $ws, $storage, html } from "sigpro";
```
---
## Option 2: Single Import with Namespace
| Function | Equivalent | Description |
|----------|------------|-------------|
| **`$()`** | `$()` | Reactive signal (getter/setter) |
| **`$.effect()`** | `$effect()` | Runs effect when dependencies change |
| **`$.component()`** | `$component()` | Creates reactive Web Component |
| **`$.fetch()`** | `$fetch()` | Fetch wrapper with loading signal |
| **`$.router()`** | `$router()` | Hash-based router with params |
| **`$.ws()`** | `$ws()` | WebSocket with reactive state |
| **`$.storage()`** | `$storage()` | Persistent signal (localStorage) |
| **`html`** | `html` | Template literal for reactive HTML |
```javascript ```javascript
import { $, html } from "sigpro"; import { $, html } from "sigpro";
// Everything available via $.methodName
// Example: $.effect() instead of $effect()
``` ```
--- ---
## Both approaches are valid and work the same
Choose the one you prefer: `$effect()` or `$.effect()`
---
## 📚 API Reference ## 📚 API Reference
--- ---
@@ -170,7 +142,7 @@ count(5);
count(prev => prev + 1); // Use function for previous value count(prev => prev + 1); // Use function for previous value
// Read with dependency tracking (inside effect) // Read with dependency tracking (inside effect)
$effect(() => { $.effect(() => {
console.log(count()); // Will be registered as dependency console.log(count()); // Will be registered as dependency
}); });
``` ```
@@ -178,7 +150,7 @@ $effect(() => {
#### Computed Signal #### Computed Signal
```typescript ```typescript
import { $, $effect } from 'sigpro'; import { $, $.effect } from 'sigpro';
const firstName = $('John'); const firstName = $('John');
const lastName = $('Doe'); const lastName = $('Doe');
@@ -226,16 +198,16 @@ type Signal<T> = {
--- ---
## 💾 `$storage(key, initialValue, [storage])` - Persistent Signal ## 💾 `$.storage(key, initialValue, [storage])` - Persistent Signal
Signal that automatically syncs with localStorage or sessionStorage. Signal that automatically syncs with localStorage or sessionStorage.
### Basic Persistent State ### Basic Persistent State
```js ```js
import { $storage } from 'sigpro'; import { $.storage } from 'sigpro';
// Automatically saves to localStorage // Automatically saves to localStorage
const theme = $storage('theme', 'light'); const theme = $.storage('theme', 'light');
const user = $storage('user', null); const user = $.storage('user', null);
theme('dark'); // Saved to localStorage theme('dark'); // Saved to localStorage
// Page refresh... theme() returns 'dark' // Page refresh... theme() returns 'dark'
@@ -244,15 +216,15 @@ theme('dark'); // Saved to localStorage
### Session Storage ### Session Storage
```js ```js
// Use sessionStorage instead // Use sessionStorage instead
const tempData = $storage('temp', {}, sessionStorage); const tempData = $.storage('temp', {}, sessionStorage);
``` ```
### Real-World Example ### Real-World Example
```js ```js
import { $storage, html } from 'sigpro'; import { $.storage, html } from 'sigpro';
// User preferences persist across sessions // User preferences persist across sessions
const settings = $storage('app-settings', { const settings = $.storage('app-settings', {
darkMode: false, darkMode: false,
fontSize: 16, fontSize: 16,
language: 'en' language: 'en'
@@ -278,9 +250,9 @@ const view = html`
### Shopping Cart Example ### Shopping Cart Example
```js ```js
import { $storage, html } from 'sigpro'; import { $.storage, html } from 'sigpro';
const cart = $storage('shopping-cart', []); const cart = $.storage('shopping-cart', []);
const addToCart = (item) => { const addToCart = (item) => {
cart([...cart(), item]); cart([...cart(), item]);
@@ -291,7 +263,7 @@ const CartView = html`
<h3>Cart (${() => cart().length} items)</h3> <h3>Cart (${() => cart().length} items)</h3>
<ul> <ul>
${() => cart().map(item => html` ${() => cart().map(item => html`
<li>${item.name} - $storage{item.price}</li> <li>${item.name} - $.storage{item.price}</li>
`)} `)}
</ul> </ul>
<button @click=${() => cart([])}>Clear Cart</button> <button @click=${() => cart([])}>Clear Cart</button>
@@ -302,7 +274,7 @@ const CartView = html`
### Auto-Cleanup ### Auto-Cleanup
```js ```js
// Remove from storage when value is null/undefined // Remove from storage when value is null/undefined
$storage('temp', null); // Removes 'temp' from storage $.storage('temp', null); // Removes 'temp' from storage
``` ```
**Parameters:** **Parameters:**
@@ -314,20 +286,20 @@ $storage('temp', null); // Removes 'temp' from storage
--- ---
### `$effectffect(effect)` - Effects ### `$.effectffect(effect)` - Effects
Executes a function and automatically re-runs it when its dependencies change. Executes a function and automatically re-runs it when its dependencies change.
#### Basic Effect #### Basic Effect
```typescript ```typescript
import { $, $effectffect } from 'sigpro'; import { $, $.effectffect } from 'sigpro';
const count = $(0); const count = $(0);
const name = $('World'); const name = $('World');
// Effect runs immediately and on dependency changes // Effect runs immediately and on dependency changes
$effectffect(() => { $.effectffect(() => {
console.log(`Count is: ${count()}`); // Only depends on count console.log(`Count is: ${count()}`); // Only depends on count
}); });
// Log: "Count is: 0" // Log: "Count is: 0"
@@ -341,11 +313,11 @@ name('Universe'); // No log (name is not a dependency)
#### Effect with Cleanup #### Effect with Cleanup
```typescript ```typescript
import { $, $effectffect } from 'sigpro'; import { $, $.effectffect } from 'sigpro';
const userId = $(1); const userId = $(1);
$effectffect(() => { $.effectffect(() => {
const id = userId(); const id = userId();
let isSubscribed = true; let isSubscribed = true;
@@ -369,17 +341,17 @@ userId(2); // Previous subscription cleaned up, new one created
#### Nested Effects #### Nested Effects
```typescript ```typescript
import { $, $effectffect } from 'sigpro'; import { $, $.effectffect } from 'sigpro';
const show = $(true); const show = $(true);
const count = $(0); const count = $(0);
$effectffect(() => { $.effectffect(() => {
if (!show()) return; if (!show()) return;
// This effect is nested inside the conditional // This effect is nested inside the conditional
// It will only be active when show() is true // It will only be active when show() is true
$effectffect(() => { $.effectffect(() => {
console.log('Count changed:', count()); console.log('Count changed:', count());
}); });
}); });
@@ -392,12 +364,12 @@ show(true); // Inner effect recreated, logs "Count changed: 1"
#### Manual Effect Control #### Manual Effect Control
```typescript ```typescript
import { $, $effectffect } from 'sigpro'; import { $, $.effectffect } from 'sigpro';
const count = $(0); const count = $(0);
// Stop effect manually // Stop effect manually
const stop = $effectffect(() => { const stop = $.effectffect(() => {
console.log('Effect running:', count()); console.log('Effect running:', count());
}); });
@@ -413,28 +385,28 @@ count(2); // No log
--- ---
## 📡 `$fetch(data, url, [loading])` - Fetch ## 📡 `$.fetch(data, url, [loading])` - Fetch
Simple fetch wrapper with automatic JSON handling and optional loading signal. Perfect for API calls. Simple fetch wrapper with automatic JSON handling and optional loading signal. Perfect for API calls.
### Basic Fetch ### Basic Fetch
```js ```js
import { $, $fetch } from 'sigpro'; import { $, $.fetch } from 'sigpro';
const userData = $(null); const userData = $(null);
// Simple POST request // Simple POST request
const result = await $fetch('/api/users', { name: 'John' }); const result = await $.fetch('/api/users', { name: 'John' });
``` ```
### Fetch with Loading State ### Fetch with Loading State
```js ```js
import { $, $fetch } from 'sigpro'; import { $, $.fetch } from 'sigpro';
const loading = $(false); const loading = $(false);
const userData = $(null); const userData = $(null);
async function loadUser(id) { async function loadUser(id) {
const data = await $fetch(`/api/users/${id}`, null, loading); const data = await $.fetch(`/api/users/${id}`, null, loading);
if (data) userData(data); if (data) userData(data);
} }
@@ -451,7 +423,7 @@ html`
### Error Handling ### Error Handling
```js ```js
const data = await $fetch('/api/users', { id: 123 }); const data = await $.fetch('/api/users', { id: 123 });
if (!data) { if (!data) {
// Handle error silently (returns null on failure) // Handle error silently (returns null on failure)
console.error('Request failed'); console.error('Request failed');
@@ -467,14 +439,14 @@ if (!data) {
--- ---
## 🔌 `$ws(url, [options])` - WebSocket ## 🔌 `$.ws(url, [options])` - WebSocket
Reactive WebSocket wrapper with automatic reconnection and signal-based state management. Reactive WebSocket wrapper with automatic reconnection and signal-based state management.
### Basic WebSocket ### Basic WebSocket
```js ```js
import { $ws } from 'sigpro'; import { $.ws } from 'sigpro';
const socket = $ws('wss://echo.websocket.org'); const socket = $.ws('wss://echo.websocket.org');
// Reactive status (disconnected/connecting/connected/error) // Reactive status (disconnected/connecting/connected/error)
socket.status() // 'connected' socket.status() // 'connected'
@@ -489,7 +461,7 @@ socket.send({ type: 'ping', data: 'test' });
### Auto-Reconnect Configuration ### Auto-Reconnect Configuration
```js ```js
const socket = $ws('wss://api.example.com', { const socket = $.ws('wss://api.example.com', {
reconnect: true, // Enable auto-reconnect reconnect: true, // Enable auto-reconnect
maxReconnect: 5, // Max attempts (default: 5) maxReconnect: 5, // Max attempts (default: 5)
reconnectInterval: 1000 // Base interval in ms (uses exponential backoff) reconnectInterval: 1000 // Base interval in ms (uses exponential backoff)
@@ -498,9 +470,9 @@ const socket = $ws('wss://api.example.com', {
### Reactive UI Integration ### Reactive UI Integration
```js ```js
import { $ws, html } from 'sigpro'; import { $.ws, html } from 'sigpro';
const chat = $ws('wss://chat.example.com'); const chat = $.ws('wss://chat.example.com');
const view = html` const view = html`
<div> <div>
@@ -528,7 +500,7 @@ const view = html`
### Manual Control ### Manual Control
```js ```js
const socket = $ws('wss://api.example.com'); const socket = $.ws('wss://api.example.com');
// Send data // Send data
socket.send({ action: 'subscribe', channel: 'updates' }); socket.send({ action: 'subscribe', channel: 'updates' });
@@ -719,7 +691,7 @@ html`<div class=${className}></div>`
html`<a href="${href}" class="link ${className}">Link</a>` html`<a href="${href}" class="link ${className}">Link</a>`
// Reactive attributes update when signal changes // Reactive attributes update when signal changes
$effectffect(() => { $.effectffect(() => {
// The attribute updates automatically // The attribute updates automatically
console.log('Class changed:', className()); console.log('Class changed:', className());
}); });
@@ -863,16 +835,16 @@ const Page = () => html`
--- ---
### `$component(tagName, setupFunction, observedAttributes)` - Web Components ### `$.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. Creates Custom Elements with reactive properties. Uses Light DOM (no Shadow DOM) and a slot system based on node filtering.
#### Basic Component #### Basic Component
```javascript ```javascript
import { $, $component, html } from 'sigpro'; import { $, $.component, html } from 'sigpro';
$component('my-counter', (props, context) => { $.component('my-counter', (props, context) => {
// props contains signals for each observed attribute // props contains signals for each observed attribute
// context: { slot, emit, host, onUnmount } // context: { slot, emit, host, onUnmount }
@@ -909,9 +881,9 @@ Usage:
#### Component with Named Slots #### Component with Named Slots
```javascript ```javascript
import { $, $component, html } from 'sigpro'; import { $, $.component, html } from 'sigpro';
$component('my-card', (props, { slot }) => { $.component('my-card', (props, { slot }) => {
return html` return html`
<div class="card"> <div class="card">
<div class="header"> <div class="header">
@@ -947,9 +919,9 @@ Usage:
#### Component with Props and Events #### Component with Props and Events
```javascript ```javascript
import { $, $component, html } from 'sigpro'; import { $, $.component, html } from 'sigpro';
$component('todo-item', (props, { emit, host }) => { $.component('todo-item', (props, { emit, host }) => {
const handleToggle = () => { const handleToggle = () => {
props.completed(c => !c); props.completed(c => !c);
emit('toggle', { id: props.id(), completed: props.completed() }); emit('toggle', { id: props.id(), completed: props.completed() });
@@ -989,13 +961,13 @@ Usage:
#### Component with Cleanup #### Component with Cleanup
```javascript ```javascript
import { $, $component, html, $effect } from 'sigpro'; import { $, $.component, html, $.effect } from 'sigpro';
$component('timer-widget', (props, { onUnmount }) => { $.component('timer-widget', (props, { onUnmount }) => {
const seconds = $(0); const seconds = $(0);
// Effect with automatic cleanup // Effect with automatic cleanup
$effect(() => { $.effect(() => {
const interval = setInterval(() => { const interval = setInterval(() => {
seconds(s => s + 1); seconds(s => s + 1);
}, 1000); }, 1000);
@@ -1021,9 +993,9 @@ $component('timer-widget', (props, { onUnmount }) => {
#### Complete Context API #### Complete Context API
```javascript ```javascript
import { $, $component, html } from 'sigpro'; import { $, $.component, html } from 'sigpro';
$component('context-demo', (props, context) => { $.component('context-demo', (props, context) => {
// Context properties: // Context properties:
// - slot(name) - Gets child nodes with matching slot attribute // - slot(name) - Gets child nodes with matching slot attribute
// - emit(name, detail) - Dispatches custom event // - emit(name, detail) - Dispatches custom event
@@ -1065,9 +1037,9 @@ $component('context-demo', (props, context) => {
#### Practical Example: Todo App Component #### Practical Example: Todo App Component
```javascript ```javascript
import { $, $component, html } from 'sigpro'; import { $, $.component, html } from 'sigpro';
$component('todo-app', () => { $.component('todo-app', () => {
const todos = $([]); const todos = $([]);
const newTodo = $(''); const newTodo = $('');
const filter = $('all'); const filter = $('all');
@@ -1154,7 +1126,7 @@ $component('todo-app', () => {
}, []); }, []);
``` ```
#### Key Points About `$component`: #### Key Points About `$.component`:
1. **Light DOM only** - No Shadow DOM, children are accessible and styleable from outside 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 2. **Slot system** - `slot()` function filters child nodes by `slot` attribute
@@ -1165,16 +1137,16 @@ $component('todo-app', () => {
--- ---
### `$router(routes)` - Router ### `$.router(routes)` - Router
Hash-based router for SPAs with reactive integration. Hash-based router for SPAs with reactive integration.
#### Basic Routing #### Basic Routing
```typescript ```typescript
import { $router, html } from 'sigpro'; import { $.router, html } from 'sigpro';
const router = $router([ const router = $.router([
{ {
path: '/', path: '/',
component: () => html` component: () => html`
@@ -1197,9 +1169,9 @@ document.body.appendChild(router);
#### Route Parameters #### Route Parameters
```typescript ```typescript
import { $router, html } from 'sigpro'; import { $.router, html } from 'sigpro';
const router = $router([ const router = $.router([
{ {
path: '/user/:id', path: '/user/:id',
component: (params) => html` component: (params) => html`
@@ -1226,9 +1198,9 @@ const router = $router([
#### Nested Routes #### Nested Routes
```typescript ```typescript
import { $router, html, $ } from 'sigpro'; import { $.router, html, $ } from 'sigpro';
const router = $router([ const router = $.router([
{ {
path: '/', path: '/',
component: () => html` component: () => html`
@@ -1242,7 +1214,7 @@ const router = $router([
path: '/dashboard', path: '/dashboard',
component: () => { component: () => {
// Nested router // Nested router
const subRouter = $router([ const subRouter = $.router([
{ {
path: '/', path: '/',
component: () => html`<h2>Dashboard Home</h2>` component: () => html`<h2>Dashboard Home</h2>`
@@ -1275,19 +1247,19 @@ const router = $router([
#### Route Guards #### Route Guards
```typescript ```typescript
import { $router, html, $ } from 'sigpro'; import { $.router, html, $ } from 'sigpro';
const isAuthenticated = $(false); const isAuthenticated = $(false);
const requireAuth = (component) => (params) => { const requireAuth = (component) => (params) => {
if (!isAuthenticated()) { if (!isAuthenticated()) {
$router.go('/login'); $.router.go('/login');
return null; return null;
} }
return component(params); return component(params);
}; };
const router = $router([ const router = $.router([
{ {
path: '/', path: '/',
component: () => html`<h1>Public Home</h1>` component: () => html`<h1>Public Home</h1>`
@@ -1311,25 +1283,25 @@ const router = $router([
#### Navigation #### Navigation
```typescript ```typescript
import { $router } from 'sigpro'; import { $.router } from 'sigpro';
// Navigate to path // Navigate to path
$router.go('/user/42'); $.router.go('/user/42');
// Navigate with replace // Navigate with replace
$router.go('/dashboard', { replace: true }); $.router.go('/dashboard', { replace: true });
// Go back // Go back
$router.back(); $.router.back();
// Go forward // Go forward
$router.forward(); $.router.forward();
// Get current path // Get current path
const currentPath = $router.getCurrentPath(); const currentPath = $.router.getCurrentPath();
// Listen to navigation // Listen to navigation
$router.listen((path, oldPath) => { $.router.listen((path, oldPath) => {
console.log(`Navigated from ${oldPath} to ${path}`); console.log(`Navigated from ${oldPath} to ${path}`);
}); });
``` ```
@@ -1337,9 +1309,9 @@ $router.listen((path, oldPath) => {
#### Route Transitions #### Route Transitions
```typescript ```typescript
import { $router, html, $effect } from 'sigpro'; import { $.router, html, $.effect } from 'sigpro';
const router = $router([ const router = $.router([
{ {
path: '/', path: '/',
component: () => html`<div class="page home">Home</div>` component: () => html`<div class="page home">Home</div>`
@@ -1351,7 +1323,7 @@ const router = $router([
]); ]);
// Add transitions // Add transitions
$effect(() => { $.effect(() => {
const currentPath = router.getCurrentPath(); const currentPath = router.getCurrentPath();
const pages = document.querySelectorAll('.page'); const pages = document.querySelectorAll('.page');