Update Readme.md

This commit is contained in:
Natxo
2026-03-13 13:45:14 +01:00
committed by GitHub
parent 62d96fc0cc
commit bd7bc6a10e

418
Readme.md
View File

@@ -572,30 +572,30 @@ const Page = () => html`
--- ---
### `$component(tag, setup, observedAttributes)` - Web Components ### `$component(tagName, setupFunction, observedAttributes)` - Web Components
Creates Custom Elements with automatic reactive properties. Creates Custom Elements with reactive properties. Uses Light DOM (no Shadow DOM) and a slot system based on node filtering.
#### Basic Component #### Basic Component
```typescript ```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 provides component utilities // context: { slot, emit, host, onUnmount }
const increment = () => { const increment = () => {
props.value(v => v + 1); props.value(v => (parseInt(v) || 0) + 1);
}; };
return html` return html`
<div class="counter"> <div>
<p>Count: ${props.value}</p> <p>Value: ${props.value}</p>
<button @click=${increment}> <button @click=${increment}>Increment</button>
Increment
</button> <!-- Slots: renders filtered child content -->
<slot></slot> ${context.slot()}
</div> </div>
`; `;
}, ['value']); // Observed attributes }, ['value']); // Observed attributes
@@ -604,147 +604,36 @@ $component('my-counter', (props, context) => {
Usage: Usage:
```html ```html
<my-counter value="5"> <my-counter value="5">
<span>Additional content</span> <span>▼ This is the default slot</span>
<p>More content in the slot</p>
</my-counter> </my-counter>
<script> <script>
const counter = document.querySelector('my-counter'); const counter = document.querySelector('my-counter');
console.log(counter.value); // 5 console.log(counter.value); // "5"
counter.value = 10; // Reactive update counter.value = "10"; // Reactive update
</script> </script>
``` ```
#### Component with Complex Props #### Component with Named Slots
```typescript ```javascript
import { $, $component, html } from 'sigpro'; import { $, $component, html } from 'sigpro';
$component('user-profile', (props, context) => { $component('my-card', (props, { slot }) => {
// Transform string attributes to appropriate types
const user = $(() => ({
id: parseInt(props.id()),
name: props.name(),
age: parseInt(props.age()),
active: props.active() === 'true'
}));
return html` return html`
<div class="profile"> <div class="card">
<h2>${user().name}</h2> <div class="header">
<p>ID: ${user().id}</p> ${slot('header')} <!-- Named slot: header -->
<p>Age: ${user().age}</p> </div>
<p>Status: ${() => user().active ? 'Active' : 'Inactive'}</p>
<button @click=${() => props.age(a => parseInt(a) + 1)}> <div class="content">
Birthday! ${slot()} <!-- Default slot (no name) -->
</button> </div>
</div>
`; <div class="footer">
}, ['id', 'name', 'age', 'active']); ${slot('footer')} <!-- Named slot: footer -->
``` </div>
#### Component Lifecycle & Context
```typescript
import { $, $component, html } from 'sigpro';
$component('lifecycle-demo', (props, {
select, // Query selector scoped to component
selectAll, // Query selector all scoped to component
slot, // Access slots
emit, // Dispatch custom events
host, // Reference to the host element
onMount, // Register mount callback
onUnmount, // Register unmount callback
onAttribute, // Listen to attribute changes
getAttribute, // Get raw attribute value
setAttribute, // Set raw attribute value
}) => {
// Access slots
const defaultSlot = slot(); // Unnamed slot
const headerSlot = slot('header'); // Named slot
// Query internal elements
const button = select('button');
const allSpans = selectAll('span');
// Lifecycle hooks
onMount(() => {
console.log('Component mounted');
// Access DOM after mount
button?.classList.add('mounted');
});
onUnmount(() => {
console.log('Component unmounting');
// Cleanup resources
});
// Listen to specific attribute changes
onAttribute('value', (newValue, oldValue) => {
console.log(`Value changed from ${oldValue} to ${newValue}`);
});
// Emit custom events
const handleClick = () => {
emit('button-click', { timestamp: Date.now() });
emit('value-change', props.value(), { bubbles: true });
};
// Access host directly
host.style.display = 'block';
return html`
<div>
${headerSlot}
<button @click=${handleClick}>
Click me
</button>
${defaultSlot}
</div>
`;
}, ['value']);
```
#### Component with Methods
```typescript
import { $, $component, html } from 'sigpro';
$component('timer-widget', (props, { host }) => {
const seconds = $(0);
let intervalId;
// Expose methods to the host element
Object.assign(host, {
start() {
if (intervalId) return;
intervalId = setInterval(() => {
seconds(s => s + 1);
}, 1000);
},
stop() {
clearInterval(intervalId);
intervalId = null;
},
reset() {
seconds(0);
},
get currentTime() {
return seconds();
}
});
return html`
<div>
<p>${seconds} seconds</p>
<button @click=${() => host.start()}>Start</button>
<button @click=${() => host.stop()}>Stop</button>
<button @click=${() => host.reset()}>Reset</button>
</div> </div>
`; `;
}, []); }, []);
@@ -752,41 +641,237 @@ $component('timer-widget', (props, { host }) => {
Usage: Usage:
```html ```html
<timer-widget id="timer"></timer-widget> <my-card>
<script> <h3 slot="header">Card Title</h3>
const timer = document.getElementById('timer');
timer.start(); <p>This goes to default slot</p>
timer.stop(); <span>Also default slot</span>
console.log(timer.currentTime);
</script> <div slot="footer">
<button>Action</button>
</div>
</my-card>
``` ```
#### Component Inheritance #### Component with Props and Events
```typescript ```javascript
import { $, $component, html } from 'sigpro'; import { $, $component, html } from 'sigpro';
// Base component $component('todo-item', (props, { emit, host }) => {
$component('base-button', (props, { slot }) => { const handleToggle = () => {
props.completed(c => !c);
emit('toggle', { id: props.id(), completed: props.completed() });
};
const handleDelete = () => {
emit('delete', { id: props.id() });
};
return html` return html`
<button class="base-button" ?disabled=${props.disabled}> <div class="todo-item">
${slot()} <input
</button> type="checkbox"
?checked=${props.completed}
@change=${handleToggle}
/>
<span style=${() => props.completed() ? 'text-decoration: line-through' : ''}>
${props.text}
</span>
<button @click=${handleDelete}>✕</button>
</div>
`; `;
}, ['disabled']); }, ['id', 'text', 'completed']);
// Extended component
$component('primary-button', (props, context) => {
// Reuse base component
return html`
<base-button ?disabled=${props.disabled} class="primary">
${context.slot()}
</base-button>
`;
}, ['disabled']);
``` ```
--- Usage:
```html
<todo-item
id="1"
text="Learn SigPro"
completed="false"
@toggle=${(e) => console.log('Toggled:', e.detail)}
@delete=${(e) => console.log('Deleted:', e.detail)}
></todo-item>
```
#### Component with Cleanup
```javascript
import { $, $component, html, $$ } from 'sigpro';
$component('timer-widget', (props, { onUnmount }) => {
const seconds = $(0);
// Effect with automatic cleanup
$$(() => {
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`
<div>
<p>Seconds: ${seconds}</p>
<p>Initial value: ${props.initial}</p>
</div>
`;
}, ['initial']);
```
#### Complete Context API
```javascript
import { $, $component, 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`
<div>
${slot('header')}
<button @click=${handleClick}>Emit Event</button>
${slot()}
${slot('footer')}
</div>
`;
}, []);
```
#### Practical Example: Todo App Component
```javascript
import { $, $component, 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`
<div class="todo-app">
<h1>📝 Todo App</h1>
<!-- Input Area -->
<div class="add-todo">
<input
:value=${newTodo}
@keydown=${(e) => e.key === 'Enter' && addTodo()}
placeholder="What needs to be done?"
/>
<button @click=${addTodo}>Add</button>
</div>
<!-- Filters -->
<div class="filters">
<button @click=${() => filter('all')}>All</button>
<button @click=${() => filter('active')}>Active</button>
<button @click=${() => filter('completed')}>Completed</button>
</div>
<!-- Todo List -->
<div class="todo-list">
${() => filteredTodos().map(todo => html`
<todo-item
id=${todo.id}
text=${todo.text}
?completed=${todo.completed}
@toggle=${(e) => {
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));
}}
></todo-item>
`)}
</div>
<!-- Stats -->
<div class="stats">
${() => {
const total = todos().length;
const completed = todos().filter(t => t.completed).length;
return html`
<span>Total: ${total}</span>
<span>Completed: ${completed}</span>
<span>Remaining: ${total - completed}</span>
`;
}}
</div>
</div>
`;
}, []);
```
#### 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
### `$router(routes)` - Router ### `$router(routes)` - Router
@@ -1454,3 +1539,4 @@ function useFetch(url) {