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