mates
v0.0.21
Published
Mates is a front end framework for building web applications
Maintainers
Readme
Mates
Mates is a lightweight framework that focuses on developer experience. It lets you build great web applications easily with it's awesome state management system. It's typescript First Framework. supports typescript all the way.
🚀 Features (MATES)
- Mutable State: Mutate state directly through setters to update components (views)
- Actions: Function utilities to help with business logic
- Templates: Template functions that return lit-html result
- Events: Event Utilites for event-driven development
- Setup functions: These are functions to intialise state needed for the view
📦 Installation
npm install mates
# or
yarn add mates
🧠 Core Concepts
👁 Views: Components of Mates
Views are similar to components in react. They are closure functions with outer function aka Setup function (the S from the MATES), and inner function is called Template function (the T from MATES) which returns html template string.
import { renderView, setter, html } from "mates";
const CounterView = (props) => {
// setup function: initialise state
let count = 0;
const incr = setter(() => count++);
// template function: returns html template strings.
return () => html` <div>
<h1>Count: ${count}</h1>
<button @click=${incr}>Increment</button>
</div>`;
};
// Render the view in any html element by passing it's id
renderView(CounterView, "app");count is a local let variable that holds value. which can only be changed froma setter function. incr is a setter function, that when called, updates the view. it has to be non-async.
Scopes
Mates introduces a new feature called Scopes. an amazing way to share state with child views (components). using Scopes, child components can access parent's state directly without using props.
Formatting Support:
you can install lit-html plugin on your IDE like vs code or cursor for proper formatting for your template strings.
props()
In Mates, props can be passed to child views as object. props is a funciton and not an object but it's passed as object to child component. the view will have to call props() to get the projects object.
const CounterView = () => {
let count = 0;
let incr = setter(() => count++);
return () => html`
<div>count is: ${count}</div>
<div>${view(ChildView, { count })}</div>
`;
};
const ChildView = (props: Props<{ count: number }>) => {
return html`parent count is : ${props().count}`;
};note please don't destructure props into local variables in the outer function, as it breaks connection from the parent object. but you can do this in the inner function as inner function gets executed everytime the parent view is updated. so you will have always the latest value.
⚛️ Atoms: Simple Reactive State
Atoms hold mutable values. they can hold any Javascript value like primitive or objects or maps or sets.etc They store a single value that can be read, set (replaced with new value), or updated (if it's an object). They support typescript fully.
import { atom } from "mates";
// Create an atom with initial value
const username = atom("guest");
// Read the value
console.log(username()); // "guest"
// or
console.log(username.get()); // "guest"
// set the value
username.set("alice");
// you can also Use setter function
username.set((val) => val.toUpperCase());
// you can also update the value if it's of object type
const address = atom({ street: "" });
// you are updating just the street value in an object.
address.update((s) => (s.street = "newstreet"));
// supports maps or sets:
const nums = atom(new Map());
// modify the map through update.
nums.update(m=>m.set(1, "one"));
const nums = atom(new Set([1,2,3]);
nums.update(s=>s.add(4));
// get the value
nums(); // Set(4) [1,2,3,4]Counter app using atoms
const CounterView = (props) => {
// setup function: initialise state
const count = atom(0);
const incr = count.set(count() + 1);
// template function: returns html template strings.
return () => html` <div>
<h1>Count: ${count}</h1>
<button @click=${incr}>Increment</button>
</div>`;
};Units: Independent Object-Based State
Units are perfect for managing object-based state with methods and building store utilities. Units have the following pieces
- Data: any type of javascript value
- Setters: methods whose name start with (_)
- Getters: methods that returns data without changing it
- Actions: async or non-async methods that call getters or setters for getting, setting data.
import { unit } from "mates";
// Create a users unit
const todoList = unit({
users: [],
isLoading = false,
// setter
_setIsLoading(value) {
this.isLoading = value;
},
// setter
_setUsers(newUsers) {
this.users = newUsers;
},
// async action that calls setters to set state
async loadUsers() {
this._setIsLoading(true);
this._setUsers(await fetch("/users").then((d) => d.json()));
this._setIsLoading(false);
},
getUsersCount() {
return this.users.length;
},
});📊 Getters: Computed Values
Getters create computed values that only recalculate when their dependencies change.
import { atom, getter } from "mates";
const firstName = atom("John");
const lastName = atom("Doe");
const fullName = getter(() => {
return `${firstName()} ${lastName()}`;
});
console.log(fullName()); // "John Doe"
// Only recalculates when dependencies change
firstName.set("Jane");
console.log(fullName()); // "Jane Doe"🧬 Molecules: group atoms or units or getters into one molecule
import { molecule, atom } from "mates";
class UserStore {
name = atom("Guest");
isLoggedIn = atom(false);
login(username) {
this.name.set(username);
this.isLoggedIn.set(true);
}
logout() {
this.name.set("Guest");
this.isLoggedIn.set(false);
}
}
// Create a molecule from the class
const userStore = molecule(UserStore);
// Use the molecule
console.log(userStore().name()); // "Guest"
userStore().login("Alice");
console.log(userStore().isLoggedIn()); // true🔄 XProvider: Context Management
XProvider allows you to provide and consume context across your application.
import { html } from "lit-html";
import { view, useContext } from "mates";
// Create a context class
class ThemeContext {
theme = "light";
toggleTheme() {
this.theme = this.theme === "light" ? "dark" : "light";
}
}
// Provider component
const ThemeProvider = view(
(props) => {
const themeContext = new ThemeContext();
return () => html`
<x-provider .value=${themeContext}> ${props().children} </x-provider>
`;
},
{ children: [] }
);
// Consumer component
const ThemedButton = view(() => {
// Get context instance
const theme = useContext(ThemeContext);
return () => html`
<button class="${theme.theme}-theme" @click=${() => theme.toggleTheme()}>
Toggle Theme (Current: ${theme.theme})
</button>
`;
}, {});🎮 Complete Example
Here's a complete todo list example that showcases Mates' features:
import { html } from "lit-html";
import { view, bubble, atom } from "mates";
// Create state with a bubble
const todos = bubble((setter) => {
let items = [];
let newTodoText = "";
const setNewTodoText = setter((text) => {
newTodoText = text;
});
const addTodo = setter(() => {
if (newTodoText.trim()) {
items.push({ text: newTodoText, completed: false });
newTodoText = "";
}
});
const toggleTodo = setter((index) => {
items[index].completed = !items[index].completed;
});
const deleteTodo = setter((index) => {
items.splice(index, 1);
});
return () => ({
items,
newTodoText,
setNewTodoText,
addTodo,
toggleTodo,
deleteTodo,
});
});
// Create a view for the todo app
const TodoApp = view(() => {
return () => {
const {
items,
newTodoText,
setNewTodoText,
addTodo,
toggleTodo,
deleteTodo,
} = todos();
return html`
<div class="todo-app">
<h1>Todo List</h1>
<div class="add-todo">
<input
value=${newTodoText}
@input=${(e) => setNewTodoText(e.target.value)}
@keypress=${(e) => e.key === "Enter" && addTodo()}
placeholder="Add new todo"
/>
<button @click=${addTodo}>Add</button>
</div>
<ul class="todo-list">
${items.map(
(item, index) => html`
<li class=${item.completed ? "completed" : ""}>
<input
type="checkbox"
.checked=${item.completed}
@change=${() => toggleTodo(index)}
/>
<span>${item.text}</span>
<button @click=${() => deleteTodo(index)}>Delete</button>
</li>
`
)}
</ul>
<div class="todo-stats">
<p>${items.filter((item) => !item.completed).length} items left</p>
</div>
</div>
`;
};
}, {});
// Mount the app
document.body.appendChild(TodoApp);🔄 Why Mates?
Mates gives you the power and simplicity of React hooks without the React! As a complete framework, it's perfect for:
Building lightweight web apps without other heavy frameworks
Adding reactivity to existing applications
Creating reusable, reactive components
Prototyping ideas quickly
📚 Learn More
Check out our examples to see more usage patterns and advanced framework features.
📄 License
MIT
