Update print statement from 'Hello' to 'Goodbye'
This commit is contained in:
463
Readme.md
463
Readme.md
@@ -1318,469 +1318,6 @@ $e(() => {
|
|||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🎮 Complete Examples
|
|
||||||
|
|
||||||
### Real-time Todo Application
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
import { $, $e, html, $c } from 'sigpro';
|
|
||||||
|
|
||||||
// Styles
|
|
||||||
const styles = html`
|
|
||||||
<style>
|
|
||||||
.todo-app {
|
|
||||||
max-width: 500px;
|
|
||||||
margin: 2rem auto;
|
|
||||||
font-family: system-ui, sans-serif;
|
|
||||||
}
|
|
||||||
|
|
||||||
.todo-input {
|
|
||||||
display: flex;
|
|
||||||
gap: 0.5rem;
|
|
||||||
margin-bottom: 2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.todo-input input {
|
|
||||||
flex: 1;
|
|
||||||
padding: 0.5rem;
|
|
||||||
border: 2px solid #e0e0e0;
|
|
||||||
border-radius: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.todo-input button {
|
|
||||||
padding: 0.5rem 1rem;
|
|
||||||
background: #0070f3;
|
|
||||||
color: white;
|
|
||||||
border: none;
|
|
||||||
border-radius: 4px;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.todo-item {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 0.5rem;
|
|
||||||
padding: 0.5rem;
|
|
||||||
border-bottom: 1px solid #e0e0e0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.todo-item.completed span {
|
|
||||||
text-decoration: line-through;
|
|
||||||
color: #999;
|
|
||||||
}
|
|
||||||
|
|
||||||
.todo-item button {
|
|
||||||
margin-left: auto;
|
|
||||||
padding: 0.25rem 0.5rem;
|
|
||||||
background: #ff4444;
|
|
||||||
color: white;
|
|
||||||
border: none;
|
|
||||||
border-radius: 4px;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.filters {
|
|
||||||
display: flex;
|
|
||||||
gap: 0.5rem;
|
|
||||||
margin: 1rem 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.filters button {
|
|
||||||
padding: 0.25rem 0.5rem;
|
|
||||||
background: #f0f0f0;
|
|
||||||
border: 1px solid #ccc;
|
|
||||||
border-radius: 4px;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.filters button.active {
|
|
||||||
background: #0070f3;
|
|
||||||
color: white;
|
|
||||||
border-color: #0070f3;
|
|
||||||
}
|
|
||||||
|
|
||||||
.stats {
|
|
||||||
margin-top: 1rem;
|
|
||||||
padding: 0.5rem;
|
|
||||||
background: #f5f5f5;
|
|
||||||
border-radius: 4px;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
`;
|
|
||||||
|
|
||||||
$c('todo-app', () => {
|
|
||||||
// State
|
|
||||||
const todos = $(() => {
|
|
||||||
const saved = localStorage.getItem('todos');
|
|
||||||
return saved ? JSON.parse(saved) : [];
|
|
||||||
});
|
|
||||||
|
|
||||||
const newTodo = $('');
|
|
||||||
const filter = $('all'); // 'all', 'active', 'completed'
|
|
||||||
const editingId = $(null);
|
|
||||||
const editText = $('');
|
|
||||||
|
|
||||||
// Save to localStorage on changes
|
|
||||||
$e(() => {
|
|
||||||
localStorage.setItem('todos', JSON.stringify(todos()));
|
|
||||||
});
|
|
||||||
|
|
||||||
// Filtered todos
|
|
||||||
const filteredTodos = $(() => {
|
|
||||||
const currentFilter = filter();
|
|
||||||
const allTodos = todos();
|
|
||||||
|
|
||||||
switch (currentFilter) {
|
|
||||||
case 'active':
|
|
||||||
return allTodos.filter(t => !t.completed);
|
|
||||||
case 'completed':
|
|
||||||
return allTodos.filter(t => t.completed);
|
|
||||||
default:
|
|
||||||
return allTodos;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Stats
|
|
||||||
const stats = $(() => {
|
|
||||||
const all = todos();
|
|
||||||
return {
|
|
||||||
total: all.length,
|
|
||||||
completed: all.filter(t => t.completed).length,
|
|
||||||
active: all.filter(t => !t.completed).length
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
// Actions
|
|
||||||
const addTodo = () => {
|
|
||||||
const text = newTodo().trim();
|
|
||||||
if (!text) return;
|
|
||||||
|
|
||||||
todos([
|
|
||||||
...todos(),
|
|
||||||
{
|
|
||||||
id: Date.now(),
|
|
||||||
text,
|
|
||||||
completed: false,
|
|
||||||
createdAt: new Date().toISOString()
|
|
||||||
}
|
|
||||||
]);
|
|
||||||
newTodo('');
|
|
||||||
};
|
|
||||||
|
|
||||||
const toggleTodo = (id) => {
|
|
||||||
todos(todos().map(todo =>
|
|
||||||
todo.id === id
|
|
||||||
? { ...todo, completed: !todo.completed }
|
|
||||||
: todo
|
|
||||||
));
|
|
||||||
};
|
|
||||||
|
|
||||||
const deleteTodo = (id) => {
|
|
||||||
todos(todos().filter(todo => todo.id !== id));
|
|
||||||
if (editingId() === id) {
|
|
||||||
editingId(null);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const startEdit = (todo) => {
|
|
||||||
editingId(todo.id);
|
|
||||||
editText(todo.text);
|
|
||||||
};
|
|
||||||
|
|
||||||
const saveEdit = (id) => {
|
|
||||||
const text = editText().trim();
|
|
||||||
if (!text) {
|
|
||||||
deleteTodo(id);
|
|
||||||
} else {
|
|
||||||
todos(todos().map(todo =>
|
|
||||||
todo.id === id ? { ...todo, text } : todo
|
|
||||||
));
|
|
||||||
}
|
|
||||||
editingId(null);
|
|
||||||
};
|
|
||||||
|
|
||||||
const clearCompleted = () => {
|
|
||||||
todos(todos().filter(t => !t.completed));
|
|
||||||
};
|
|
||||||
|
|
||||||
return html`
|
|
||||||
${styles}
|
|
||||||
<div class="todo-app">
|
|
||||||
<h1>📝 Todo App</h1>
|
|
||||||
|
|
||||||
<!-- Add Todo -->
|
|
||||||
<div class="todo-input">
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
:value=${newTodo}
|
|
||||||
placeholder="What needs to be done?"
|
|
||||||
@keydown=${(e) => e.key === 'Enter' && addTodo()}
|
|
||||||
/>
|
|
||||||
<button @click=${addTodo}>Add</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Filters -->
|
|
||||||
<div class="filters">
|
|
||||||
<button
|
|
||||||
class=${() => filter() === 'all' ? 'active' : ''}
|
|
||||||
@click=${() => filter('all')}
|
|
||||||
>
|
|
||||||
All
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
class=${() => filter() === 'active' ? 'active' : ''}
|
|
||||||
@click=${() => filter('active')}
|
|
||||||
>
|
|
||||||
Active
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
class=${() => filter() === 'completed' ? 'active' : ''}
|
|
||||||
@click=${() => filter('completed')}
|
|
||||||
>
|
|
||||||
Completed
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Todo List -->
|
|
||||||
<div class="todo-list">
|
|
||||||
${() => filteredTodos().map(todo => html`
|
|
||||||
<div class="todo-item ${todo.completed ? 'completed' : ''}" key=${todo.id}>
|
|
||||||
${editingId() === todo.id ? html`
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
:value=${editText}
|
|
||||||
@keydown=${(e) => {
|
|
||||||
if (e.key === 'Enter') saveEdit(todo.id);
|
|
||||||
if (e.key === 'Escape') editingId(null);
|
|
||||||
}}
|
|
||||||
@blur=${() => saveEdit(todo.id)}
|
|
||||||
autofocus
|
|
||||||
/>
|
|
||||||
` : html`
|
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
?checked=${todo.completed}
|
|
||||||
@change=${() => toggleTodo(todo.id)}
|
|
||||||
/>
|
|
||||||
<span @dblclick=${() => startEdit(todo)}>
|
|
||||||
${todo.text}
|
|
||||||
</span>
|
|
||||||
<button @click=${() => deleteTodo(todo.id)}>✕</button>
|
|
||||||
`}
|
|
||||||
</div>
|
|
||||||
`)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Stats -->
|
|
||||||
<div class="stats">
|
|
||||||
${() => {
|
|
||||||
const s = stats();
|
|
||||||
return html`
|
|
||||||
<span>Total: ${s.total}</span> |
|
|
||||||
<span>Active: ${s.active}</span> |
|
|
||||||
<span>Completed: ${s.completed}</span>
|
|
||||||
${s.completed > 0 ? html`
|
|
||||||
<button @click=${clearCompleted}>Clear completed</button>
|
|
||||||
` : ''}
|
|
||||||
`;
|
|
||||||
}}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
}, []);
|
|
||||||
```
|
|
||||||
|
|
||||||
### Data Dashboard with Real-time Updates
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
import { $, $e, html, $c } from 'sigpro';
|
|
||||||
|
|
||||||
// Simulated WebSocket connection
|
|
||||||
class DataStream {
|
|
||||||
constructor() {
|
|
||||||
this.listeners = new Set();
|
|
||||||
this.interval = setInterval(() => {
|
|
||||||
const data = {
|
|
||||||
timestamp: Date.now(),
|
|
||||||
value: Math.random() * 100,
|
|
||||||
category: ['A', 'B', 'C'][Math.floor(Math.random() * 3)]
|
|
||||||
};
|
|
||||||
this.listeners.forEach(fn => fn(data));
|
|
||||||
}, 1000);
|
|
||||||
}
|
|
||||||
|
|
||||||
subscribe(listener) {
|
|
||||||
this.listeners.add(listener);
|
|
||||||
return () => this.listeners.delete(listener);
|
|
||||||
}
|
|
||||||
|
|
||||||
destroy() {
|
|
||||||
clearInterval(this.interval);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$c('data-dashboard', () => {
|
|
||||||
const stream = new DataStream();
|
|
||||||
const dataPoints = $([]);
|
|
||||||
const selectedCategory = $('all');
|
|
||||||
const timeWindow = $(60); // seconds
|
|
||||||
|
|
||||||
// Subscribe to data stream
|
|
||||||
$e(() => {
|
|
||||||
const unsubscribe = stream.subscribe((newData) => {
|
|
||||||
dataPoints(prev => {
|
|
||||||
const updated = [...prev, newData];
|
|
||||||
const maxAge = timeWindow() * 1000;
|
|
||||||
const cutoff = Date.now() - maxAge;
|
|
||||||
return updated.filter(d => d.timestamp > cutoff);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
return unsubscribe;
|
|
||||||
});
|
|
||||||
|
|
||||||
// Filtered data
|
|
||||||
const filteredData = $(() => {
|
|
||||||
const data = dataPoints();
|
|
||||||
const category = selectedCategory();
|
|
||||||
|
|
||||||
if (category === 'all') return data;
|
|
||||||
return data.filter(d => d.category === category);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Statistics
|
|
||||||
const statistics = $(() => {
|
|
||||||
const data = filteredData();
|
|
||||||
if (data.length === 0) return null;
|
|
||||||
|
|
||||||
const values = data.map(d => d.value);
|
|
||||||
return {
|
|
||||||
count: data.length,
|
|
||||||
avg: values.reduce((a, b) => a + b, 0) / values.length,
|
|
||||||
min: Math.min(...values),
|
|
||||||
max: Math.max(...values),
|
|
||||||
last: values[values.length - 1]
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
// Cleanup on unmount
|
|
||||||
onUnmount(() => {
|
|
||||||
stream.destroy();
|
|
||||||
});
|
|
||||||
|
|
||||||
return html`
|
|
||||||
<div class="dashboard">
|
|
||||||
<h2>📊 Real-time Dashboard</h2>
|
|
||||||
|
|
||||||
<!-- Controls -->
|
|
||||||
<div class="controls">
|
|
||||||
<select :value=${selectedCategory}>
|
|
||||||
<option value="all">All Categories</option>
|
|
||||||
<option value="A">Category A</option>
|
|
||||||
<option value="B">Category B</option>
|
|
||||||
<option value="C">Category C</option>
|
|
||||||
</select>
|
|
||||||
|
|
||||||
<input
|
|
||||||
type="range"
|
|
||||||
min="10"
|
|
||||||
max="300"
|
|
||||||
step="10"
|
|
||||||
:value=${timeWindow}
|
|
||||||
/>
|
|
||||||
<span>Time window: ${timeWindow}s</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Statistics -->
|
|
||||||
${() => {
|
|
||||||
const stats = statistics();
|
|
||||||
if (!stats) return html`<p>Waiting for data...</p>`;
|
|
||||||
|
|
||||||
return html`
|
|
||||||
<div class="stats">
|
|
||||||
<div>Points: ${stats.count}</div>
|
|
||||||
<div>Average: ${stats.avg.toFixed(2)}</div>
|
|
||||||
<div>Min: ${stats.min.toFixed(2)}</div>
|
|
||||||
<div>Max: ${stats.max.toFixed(2)}</div>
|
|
||||||
<div>Last: ${stats.last.toFixed(2)}</div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
}}
|
|
||||||
|
|
||||||
<!-- Chart (simplified) -->
|
|
||||||
<div class="chart">
|
|
||||||
${() => filteredData().map(point => html`
|
|
||||||
<div
|
|
||||||
class="bar"
|
|
||||||
style="
|
|
||||||
height: ${point.value}px;
|
|
||||||
background: ${point.category === 'A' ? '#ff4444' :
|
|
||||||
point.category === 'B' ? '#44ff44' : '#4444ff'};
|
|
||||||
"
|
|
||||||
title="${new Date(point.timestamp).toLocaleTimeString()}: ${point.value.toFixed(2)}"
|
|
||||||
></div>
|
|
||||||
`)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
}, []);
|
|
||||||
```
|
|
||||||
|
|
||||||
## 🔧 Advanced Patterns
|
|
||||||
|
|
||||||
### Custom Hooks
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
import { $, $e } from 'sigpro';
|
|
||||||
|
|
||||||
// useLocalStorage hook
|
|
||||||
function useLocalStorage(key, initialValue) {
|
|
||||||
const stored = localStorage.getItem(key);
|
|
||||||
const signal = $(stored ? JSON.parse(stored) : initialValue);
|
|
||||||
|
|
||||||
$e(() => {
|
|
||||||
localStorage.setItem(key, JSON.stringify(signal()));
|
|
||||||
});
|
|
||||||
|
|
||||||
return signal;
|
|
||||||
}
|
|
||||||
|
|
||||||
// useDebounce hook
|
|
||||||
function useDebounce(signal, delay) {
|
|
||||||
const debounced = $(signal());
|
|
||||||
let timeout;
|
|
||||||
|
|
||||||
$e(() => {
|
|
||||||
const value = signal();
|
|
||||||
clearTimeout(timeout);
|
|
||||||
timeout = setTimeout(() => {
|
|
||||||
debounced(value);
|
|
||||||
}, delay);
|
|
||||||
});
|
|
||||||
|
|
||||||
return debounced;
|
|
||||||
}
|
|
||||||
|
|
||||||
// useFetch hook
|
|
||||||
function useFetch(url) {
|
|
||||||
const data = $(null);
|
|
||||||
const error = $(null);
|
|
||||||
const loading = $(true);
|
|
||||||
|
|
||||||
const fetchData = async () => {
|
|
||||||
loading(true);
|
|
||||||
error(null);
|
|
||||||
try {
|
|
||||||
const response = await fetch(url());
|
|
||||||
const json = await response.json();
|
|
||||||
data(json);
|
|
||||||
} catch (e) {
|
|
||||||
error(e);
|
|
||||||
} finally {
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user