mates 0.0.2 โ 0.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +356 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -0,0 +1,356 @@
|
|
|
1
|
+
# ๐งช Mates
|
|
2
|
+
|
|
3
|
+
**Mates** is a lightweight, reactive state management framework for web applications that makes managing state a breeze! Think React hooks, but for any framework (or no framework at all)!
|
|
4
|
+
|
|
5
|
+
## ๐ Features
|
|
6
|
+
|
|
7
|
+
- ๐ **Reactive State**: Automatic UI updates when state changes
|
|
8
|
+
- ๐งฉ **Composable**: Mix and match different state types
|
|
9
|
+
- ๐ **Transparent**: See exactly how data flows through your app
|
|
10
|
+
- ๐๏ธ **Fast**: Minimal re-renders, optimized updates
|
|
11
|
+
- ๐ชถ **Lightweight**: Tiny footprint, big capabilities
|
|
12
|
+
- ๐ **Framework-Agnostic**: Works with any framework or vanilla JS/TS
|
|
13
|
+
|
|
14
|
+
## ๐ฆ Installation
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
npm install mates
|
|
18
|
+
# or
|
|
19
|
+
yarn add mates
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## ๐ง Core Concepts
|
|
23
|
+
|
|
24
|
+
Mates offers several types of state management tools that work together seamlessly as part of the framework:
|
|
25
|
+
|
|
26
|
+
### ๐ Views: Building Reactive UIs
|
|
27
|
+
|
|
28
|
+
Views are the building blocks of your UI. They're functions that return HTML templates and automatically subscribe to state changes.
|
|
29
|
+
|
|
30
|
+
```typescript
|
|
31
|
+
import { view, atom } from "mates";
|
|
32
|
+
import { html } from "lit-html";
|
|
33
|
+
|
|
34
|
+
// Create a view that uses this state
|
|
35
|
+
const CounterView = (props) => {
|
|
36
|
+
let count = 0;
|
|
37
|
+
const incr = setter(() => count++);
|
|
38
|
+
return () => {
|
|
39
|
+
// This function renders the template
|
|
40
|
+
return html`
|
|
41
|
+
<div>
|
|
42
|
+
<h1>Count: ${count}</h1>
|
|
43
|
+
<button @click=${incr}>Increment</button>
|
|
44
|
+
</div>
|
|
45
|
+
`;
|
|
46
|
+
};
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
// Render the view in your app in an element
|
|
50
|
+
// second param should be id of the element
|
|
51
|
+
renderView(CounterView, "app");
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### โ๏ธ Atoms: Simple Reactive State
|
|
55
|
+
|
|
56
|
+
Atoms are the simplest form of state. They store a single value that can be read and updated.
|
|
57
|
+
|
|
58
|
+
```typescript
|
|
59
|
+
import { atom } from "mates";
|
|
60
|
+
|
|
61
|
+
// Create an atom with initial value
|
|
62
|
+
const username = atom("guest");
|
|
63
|
+
|
|
64
|
+
// Read the value
|
|
65
|
+
console.log(username()); // "guest"
|
|
66
|
+
|
|
67
|
+
// Update the value
|
|
68
|
+
username.set("alice");
|
|
69
|
+
|
|
70
|
+
// Use setter function
|
|
71
|
+
username.set((prev) => prev.toUpperCase());
|
|
72
|
+
|
|
73
|
+
// you can also update the values in an object or array using .update()
|
|
74
|
+
|
|
75
|
+
const address = atom({ street: "" });
|
|
76
|
+
address.update((s) => (s.street = "newstreet"));
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### Counter app using atoms
|
|
80
|
+
|
|
81
|
+
```typescript
|
|
82
|
+
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### ๐งช Units: Object-Based State
|
|
86
|
+
|
|
87
|
+
Units are perfect for managing object-based state with methods.
|
|
88
|
+
|
|
89
|
+
```typescript
|
|
90
|
+
import { unit } from "mates";
|
|
91
|
+
|
|
92
|
+
// Create a unit
|
|
93
|
+
const todoList = unit({
|
|
94
|
+
items: [],
|
|
95
|
+
|
|
96
|
+
// Methods with _ prefix automatically trigger updates
|
|
97
|
+
_addItem(text) {
|
|
98
|
+
this.items.push({ text, completed: false });
|
|
99
|
+
},
|
|
100
|
+
|
|
101
|
+
_toggleItem(index) {
|
|
102
|
+
this.items[index].completed = !this.items[index].completed;
|
|
103
|
+
},
|
|
104
|
+
|
|
105
|
+
// Computed property (cached and recalculated when dependencies change)
|
|
106
|
+
get completedCount() {
|
|
107
|
+
return this.items.filter((item) => item.completed).length;
|
|
108
|
+
},
|
|
109
|
+
|
|
110
|
+
async loadData() {
|
|
111
|
+
const data = await fetchData();
|
|
112
|
+
this._addItem(data); // updates items
|
|
113
|
+
},
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
// Use the unit
|
|
117
|
+
todoList().items; // []
|
|
118
|
+
todoList()._addItem("Learn Mates");
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### ๐ซง Bubbles: Encapsulated Logic
|
|
122
|
+
|
|
123
|
+
Bubbles help you encapsulate complex state logic into a reusable entity.
|
|
124
|
+
|
|
125
|
+
```typescript
|
|
126
|
+
import { bubble } from "mates";
|
|
127
|
+
|
|
128
|
+
const counterBubble = bubble((setter) => {
|
|
129
|
+
let count = 0;
|
|
130
|
+
|
|
131
|
+
// Create functions that update state with setter
|
|
132
|
+
const increment = setter(() => count++);
|
|
133
|
+
const decrement = setter(() => count--);
|
|
134
|
+
const reset = setter(() => (count = 0));
|
|
135
|
+
|
|
136
|
+
// Return a function that returns the state object
|
|
137
|
+
return () => ({
|
|
138
|
+
count,
|
|
139
|
+
increment,
|
|
140
|
+
decrement,
|
|
141
|
+
reset,
|
|
142
|
+
});
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
// Use the bubble
|
|
146
|
+
const { count, increment } = counterBubble();
|
|
147
|
+
console.log(count); // 0
|
|
148
|
+
increment();
|
|
149
|
+
console.log(counterBubble().count); // 1
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
### ๐ Getters: Computed Values
|
|
153
|
+
|
|
154
|
+
Getters create computed values that only recalculate when their dependencies change.
|
|
155
|
+
|
|
156
|
+
```typescript
|
|
157
|
+
import { atom, getter } from "mates";
|
|
158
|
+
|
|
159
|
+
const firstName = atom("John");
|
|
160
|
+
const lastName = atom("Doe");
|
|
161
|
+
|
|
162
|
+
const fullName = getter(() => {
|
|
163
|
+
return `${firstName()} ${lastName()}`;
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
console.log(fullName()); // "John Doe"
|
|
167
|
+
|
|
168
|
+
// Only recalculates when dependencies change
|
|
169
|
+
firstName.set("Jane");
|
|
170
|
+
console.log(fullName()); // "Jane Doe"
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
### ๐งฌ Molecules: Organizing State
|
|
174
|
+
|
|
175
|
+
Molecules help you organize your state into classes for better structure.
|
|
176
|
+
|
|
177
|
+
```typescript
|
|
178
|
+
import { molecule, atom } from "mates";
|
|
179
|
+
|
|
180
|
+
class UserStore {
|
|
181
|
+
name = atom("Guest");
|
|
182
|
+
isLoggedIn = atom(false);
|
|
183
|
+
|
|
184
|
+
login(username) {
|
|
185
|
+
this.name.set(username);
|
|
186
|
+
this.isLoggedIn.set(true);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
logout() {
|
|
190
|
+
this.name.set("Guest");
|
|
191
|
+
this.isLoggedIn.set(false);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Create a molecule from the class
|
|
196
|
+
const userStore = molecule(UserStore);
|
|
197
|
+
|
|
198
|
+
// Use the molecule
|
|
199
|
+
console.log(userStore().name()); // "Guest"
|
|
200
|
+
userStore().login("Alice");
|
|
201
|
+
console.log(userStore().isLoggedIn()); // true
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
### ๐ XProvider: Context Management
|
|
205
|
+
|
|
206
|
+
XProvider allows you to provide and consume context across your application.
|
|
207
|
+
|
|
208
|
+
```typescript
|
|
209
|
+
import { html } from "lit-html";
|
|
210
|
+
import { view, useContext } from "mates";
|
|
211
|
+
|
|
212
|
+
// Create a context class
|
|
213
|
+
class ThemeContext {
|
|
214
|
+
theme = "light";
|
|
215
|
+
|
|
216
|
+
toggleTheme() {
|
|
217
|
+
this.theme = this.theme === "light" ? "dark" : "light";
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// Provider component
|
|
222
|
+
const ThemeProvider = view(
|
|
223
|
+
(props) => {
|
|
224
|
+
const themeContext = new ThemeContext();
|
|
225
|
+
|
|
226
|
+
return () => html`
|
|
227
|
+
<x-provider .value=${themeContext}> ${props().children} </x-provider>
|
|
228
|
+
`;
|
|
229
|
+
},
|
|
230
|
+
{ children: [] }
|
|
231
|
+
);
|
|
232
|
+
|
|
233
|
+
// Consumer component
|
|
234
|
+
const ThemedButton = view(() => {
|
|
235
|
+
// Get context instance
|
|
236
|
+
const theme = useContext(ThemeContext);
|
|
237
|
+
|
|
238
|
+
return () => html`
|
|
239
|
+
<button class="${theme.theme}-theme" @click=${() => theme.toggleTheme()}>
|
|
240
|
+
Toggle Theme (Current: ${theme.theme})
|
|
241
|
+
</button>
|
|
242
|
+
`;
|
|
243
|
+
}, {});
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
## ๐ฎ Complete Example
|
|
247
|
+
|
|
248
|
+
Here's a complete todo list example that showcases Mates' features:
|
|
249
|
+
|
|
250
|
+
```typescript
|
|
251
|
+
import { html } from "lit-html";
|
|
252
|
+
import { view, bubble, atom } from "mates";
|
|
253
|
+
|
|
254
|
+
// Create state with a bubble
|
|
255
|
+
const todos = bubble((setter) => {
|
|
256
|
+
let items = [];
|
|
257
|
+
let newTodoText = "";
|
|
258
|
+
|
|
259
|
+
const setNewTodoText = setter((text) => {
|
|
260
|
+
newTodoText = text;
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
const addTodo = setter(() => {
|
|
264
|
+
if (newTodoText.trim()) {
|
|
265
|
+
items.push({ text: newTodoText, completed: false });
|
|
266
|
+
newTodoText = "";
|
|
267
|
+
}
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
const toggleTodo = setter((index) => {
|
|
271
|
+
items[index].completed = !items[index].completed;
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
const deleteTodo = setter((index) => {
|
|
275
|
+
items.splice(index, 1);
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
return () => ({
|
|
279
|
+
items,
|
|
280
|
+
newTodoText,
|
|
281
|
+
setNewTodoText,
|
|
282
|
+
addTodo,
|
|
283
|
+
toggleTodo,
|
|
284
|
+
deleteTodo,
|
|
285
|
+
});
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
// Create a view for the todo app
|
|
289
|
+
const TodoApp = view(() => {
|
|
290
|
+
return () => {
|
|
291
|
+
const {
|
|
292
|
+
items,
|
|
293
|
+
newTodoText,
|
|
294
|
+
setNewTodoText,
|
|
295
|
+
addTodo,
|
|
296
|
+
toggleTodo,
|
|
297
|
+
deleteTodo,
|
|
298
|
+
} = todos();
|
|
299
|
+
|
|
300
|
+
return html`
|
|
301
|
+
<div class="todo-app">
|
|
302
|
+
<h1>Todo List</h1>
|
|
303
|
+
<div class="add-todo">
|
|
304
|
+
<input
|
|
305
|
+
value=${newTodoText}
|
|
306
|
+
@input=${(e) => setNewTodoText(e.target.value)}
|
|
307
|
+
@keypress=${(e) => e.key === "Enter" && addTodo()}
|
|
308
|
+
placeholder="Add new todo"
|
|
309
|
+
/>
|
|
310
|
+
<button @click=${addTodo}>Add</button>
|
|
311
|
+
</div>
|
|
312
|
+
|
|
313
|
+
<ul class="todo-list">
|
|
314
|
+
${items.map(
|
|
315
|
+
(item, index) => html`
|
|
316
|
+
<li class=${item.completed ? "completed" : ""}>
|
|
317
|
+
<input
|
|
318
|
+
type="checkbox"
|
|
319
|
+
.checked=${item.completed}
|
|
320
|
+
@change=${() => toggleTodo(index)}
|
|
321
|
+
/>
|
|
322
|
+
<span>${item.text}</span>
|
|
323
|
+
<button @click=${() => deleteTodo(index)}>Delete</button>
|
|
324
|
+
</li>
|
|
325
|
+
`
|
|
326
|
+
)}
|
|
327
|
+
</ul>
|
|
328
|
+
|
|
329
|
+
<div class="todo-stats">
|
|
330
|
+
<p>${items.filter((item) => !item.completed).length} items left</p>
|
|
331
|
+
</div>
|
|
332
|
+
</div>
|
|
333
|
+
`;
|
|
334
|
+
};
|
|
335
|
+
}, {});
|
|
336
|
+
|
|
337
|
+
// Mount the app
|
|
338
|
+
document.body.appendChild(TodoApp);
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
## ๐ Why Mates?
|
|
342
|
+
|
|
343
|
+
Mates gives you the power and simplicity of React hooks without the React! As a complete framework, it's perfect for:
|
|
344
|
+
|
|
345
|
+
- Building lightweight web apps without other heavy frameworks
|
|
346
|
+
- Adding reactivity to existing applications
|
|
347
|
+
- Creating reusable, reactive components
|
|
348
|
+
- Prototyping ideas quickly
|
|
349
|
+
|
|
350
|
+
## ๐ Learn More
|
|
351
|
+
|
|
352
|
+
Check out our [examples](https://github.com/yourusername/mates/tree/main/examples) to see more usage patterns and advanced framework features.
|
|
353
|
+
|
|
354
|
+
## ๐ License
|
|
355
|
+
|
|
356
|
+
MIT
|