import { effect, html, signal, render } from './uhtml.js'; import StateMachine from './fsm.js'; // Application state signals const todos = signal([]); const inputValue = signal(''); const currentFilter = signal('all'); // Todo state machine - models the lifecycle of individual todos const createTodoFSM = (id) => { return new StateMachine({ initial: 'pending', states: { pending: ['completed', 'editing'], completed: ['pending', 'editing'], editing: ['pending', 'completed'] } }); }; // App state machine - models the overall application state const appFSM = new StateMachine({ initial: 'viewing', states: { viewing: ['adding', 'filtering'], adding: ['viewing'], filtering: ['viewing'] } }); // Todo data structure let todoCounter = 0; const createTodo = (text) => ({ id: ++todoCounter, text: text, state: 'pending', fsm: createTodoFSM(todoCounter), created: new Date() }); // State management functions const addTodo = (text) => { console.log('addTodo called with:', text); if (text.trim()) { const newTodo = createTodo(text.trim()); console.log('Creating new todo:', newTodo); todos.value = [...todos.value, newTodo]; inputValue.value = ''; console.log('Todos updated, count:', todos.value.length); } }; const toggleTodo = async (id) => { const todoList = [...todos.value]; const todo = todoList.find(t => t.id === id); if (todo) { const newState = todo.state === 'pending' ? 'completed' : 'pending'; try { await todo.fsm.go(newState); todo.state = newState; todos.value = todoList; } catch (error) { console.error('Invalid state transition:', error); } } }; const deleteTodo = (id) => { todos.value = todos.value.filter(t => t.id !== id); }; const filteredTodos = signal([]); // Reactive effect to update filtered todos when todos or filter changes effect(() => { const filter = currentFilter.value; const todoList = todos.value; switch (filter) { case 'active': filteredTodos.value = todoList.filter(t => t.state === 'pending'); break; case 'completed': filteredTodos.value = todoList.filter(t => t.state === 'completed'); break; default: filteredTodos.value = todoList; } }); // Set up FSM event handlers appFSM.on('*', (prev, next) => { console.log(`App state: ${prev} → ${next}`); }); appFSM.on('adding', () => { const input = document.querySelector('.todo-input'); if (input) input.focus(); }); // Components const TodoInput = ({ value, onAdd, fsm }) => { const onInput = e => value.value = e.target.value; const onKeyPress = async e => { if (e.key === 'Enter') { console.log('Enter pressed, input value:', value.value); try { await fsm.go('adding'); onAdd(value.value); await fsm.go('viewing'); } catch (error) { console.error('FSM transition error:', error); } } }; return html`
`; }; const FilterControls = ({ currentFilter, onFilterChange, fsm }) => { const handleFilterClick = async (filter) => { await fsm.go('filtering'); onFilterChange(filter); await fsm.go('viewing'); }; return html`
`; }; const TodoItem = ({ todo, onToggle, onDelete }) => { console.log('rendering TODO'); const handleToggle = () => onToggle(todo.id); const handleDelete = () => onDelete(todo.id); return html`
  • ${todo.text}
  • `; }; const TodoList = ({ todos, currentFilter, onToggle, onDelete }) => { console.log('Rendering TodoList', todos.length, 'items'); let message = ''; if (todos.length === 0) { message = currentFilter === 'all' ? 'No todos yet. Add one above!' : `No ${currentFilter} todos.`; } const handleFilterChange = (filter) => { currentFilter.value = filter; }; return html` ${FilterControls({ currentFilter, onFilterChange: handleFilterChange, fsm: appFSM })}
    ${message}
    `; }; const StateInfo = ({ appState, currentFilter, todos }) => { const totalTodos = todos.length; const completedTodos = todos.filter(t => t.state === 'completed').length; const activeTodos = totalTodos - completedTodos; return html`
    App State: ${appState} | Filter: ${currentFilter} | Todos: ${totalTodos} total, ${activeTodos} active, ${completedTodos} completed
    `; }; const App = () => { return html`

    FSM + uhtml Todo List

    ${StateInfo({ appState: appFSM.current, currentFilter: currentFilter.value, todos: todos.value })} ${TodoInput({ value: inputValue, onAdd: addTodo, fsm: appFSM })} ${TodoList({ todos: filteredTodos.value, currentFilter: currentFilter.value, onToggle: toggleTodo, onDelete: deleteTodo })}
    `; }; // Initialize the app const appContainer = document.getElementById('app'); appContainer.append( html`<${App} />` ); // Add some sample data for demonstration setTimeout(() => { addTodo('Learn about state machines'); addTodo('Build reactive UIs with uhtml'); addTodo('Combine FSM with reactive programming'); }, 100);