mantle-lit 0.1.0
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/LICENSE +21 -0
- package/README.md +600 -0
- package/dist/index.cjs +544 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +358 -0
- package/dist/index.d.ts +358 -0
- package/dist/index.js +500 -0
- package/dist/index.js.map +1 -0
- package/package.json +60 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Craig
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,600 @@
|
|
|
1
|
+
# Mantle Lit
|
|
2
|
+
|
|
3
|
+
A lightweight library for building Lit web components with a simpler class-based API and MobX reactivity built in.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install mantle-lit
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Requires Lit 3+ and MobX 6+.
|
|
12
|
+
|
|
13
|
+
## Basic Example
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
import { View, createView } from 'mantle-lit';
|
|
17
|
+
import { html } from 'lit';
|
|
18
|
+
|
|
19
|
+
interface CounterProps {
|
|
20
|
+
initial: number;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
class CounterView extends View<CounterProps> {
|
|
24
|
+
count = 0;
|
|
25
|
+
|
|
26
|
+
onCreate() {
|
|
27
|
+
this.count = this.props.initial;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
increment() {
|
|
31
|
+
this.count++;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
render() {
|
|
35
|
+
return html`
|
|
36
|
+
<button @click=${this.increment}>
|
|
37
|
+
Count: ${this.count}
|
|
38
|
+
</button>
|
|
39
|
+
`;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export const Counter = createView(CounterView, { tag: 'x-counter' });
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
**Usage in HTML (property binding with `.`):**
|
|
47
|
+
```html
|
|
48
|
+
<x-counter .initial=${5}></x-counter>
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
**Everything is reactive by default.** All properties become observable, getters become computed, and methods become auto-bound actions. No annotations needed.
|
|
52
|
+
|
|
53
|
+
> Want explicit control? See [Decorators](#decorators) below to opt into manual annotations.
|
|
54
|
+
|
|
55
|
+
## Property Binding
|
|
56
|
+
|
|
57
|
+
This library is designed for **property binding** (`.prop=${value}`) rather than attribute binding (`attr="value"`). This allows passing complex objects, arrays, and functions as props.
|
|
58
|
+
|
|
59
|
+
```ts
|
|
60
|
+
// Parent component
|
|
61
|
+
render() {
|
|
62
|
+
return html`
|
|
63
|
+
<x-todo-list
|
|
64
|
+
.items=${this.todos}
|
|
65
|
+
.onDelete=${this.handleDelete}
|
|
66
|
+
></x-todo-list>
|
|
67
|
+
`;
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## What You Get
|
|
72
|
+
|
|
73
|
+
**Direct mutation:**
|
|
74
|
+
```ts
|
|
75
|
+
this.items.push(item); // not [...items, item]
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
**Computed values via getters:**
|
|
79
|
+
```ts
|
|
80
|
+
get completed() { // automatically memoized
|
|
81
|
+
return this.items.filter(i => i.done);
|
|
82
|
+
}
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
**Stable methods (auto-bound):**
|
|
86
|
+
```ts
|
|
87
|
+
toggle(id: number) { // automatically bound to this
|
|
88
|
+
const item = this.items.find(i => i.id === id);
|
|
89
|
+
if (item) item.done = !item.done;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// use directly, no wrapper needed
|
|
93
|
+
render() {
|
|
94
|
+
return html`<button @click=${this.toggle}>Toggle</button>`;
|
|
95
|
+
}
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
**React to changes explicitly:**
|
|
99
|
+
```ts
|
|
100
|
+
onCreate() {
|
|
101
|
+
this.watch(
|
|
102
|
+
() => this.props.filter,
|
|
103
|
+
(filter) => this.applyFilter(filter)
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
## Lifecycle
|
|
109
|
+
|
|
110
|
+
| Method | When |
|
|
111
|
+
|--------|------|
|
|
112
|
+
| `onCreate()` | Instance created, props available |
|
|
113
|
+
| `onMount()` | Component connected to DOM. Return a cleanup function (optional). |
|
|
114
|
+
| `onUnmount()` | Component disconnected from DOM. Called after cleanups (optional). |
|
|
115
|
+
| `render()` | On mount and updates. Return Lit `TemplateResult`. |
|
|
116
|
+
|
|
117
|
+
### Watching State
|
|
118
|
+
|
|
119
|
+
Use `this.watch` to react to state changes. Watchers are automatically disposed on unmount.
|
|
120
|
+
|
|
121
|
+
```ts
|
|
122
|
+
this.watch(
|
|
123
|
+
() => expr, // reactive expression (getter)
|
|
124
|
+
(value, prev) => {}, // callback when expression result changes
|
|
125
|
+
options? // optional: { delay, fireImmediately }
|
|
126
|
+
)
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
**Options:**
|
|
130
|
+
|
|
131
|
+
| Option | Type | Default | Description |
|
|
132
|
+
|--------|------|---------|-------------|
|
|
133
|
+
| `delay` | `number` | — | Debounce the callback by N milliseconds |
|
|
134
|
+
| `fireImmediately` | `boolean` | `false` | Run callback immediately with current value |
|
|
135
|
+
|
|
136
|
+
**Basic example:**
|
|
137
|
+
|
|
138
|
+
```ts
|
|
139
|
+
class SearchView extends View<Props> {
|
|
140
|
+
query = '';
|
|
141
|
+
results: string[] = [];
|
|
142
|
+
|
|
143
|
+
onCreate() {
|
|
144
|
+
this.watch(
|
|
145
|
+
() => this.query,
|
|
146
|
+
async (query) => {
|
|
147
|
+
if (query.length > 2) {
|
|
148
|
+
this.results = await searchApi(query);
|
|
149
|
+
}
|
|
150
|
+
},
|
|
151
|
+
{ delay: 300 }
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
**Multiple watchers:**
|
|
158
|
+
|
|
159
|
+
```ts
|
|
160
|
+
onCreate() {
|
|
161
|
+
this.watch(() => this.props.filter, (filter) => this.applyFilter(filter));
|
|
162
|
+
this.watch(() => this.props.sort, (sort) => this.applySort(sort));
|
|
163
|
+
this.watch(() => this.props.page, (page) => this.fetchPage(page));
|
|
164
|
+
}
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
**Early disposal:**
|
|
168
|
+
|
|
169
|
+
```ts
|
|
170
|
+
onCreate() {
|
|
171
|
+
const stop = this.watch(() => this.props.token, (token) => {
|
|
172
|
+
this.authenticate(token);
|
|
173
|
+
stop(); // only needed once
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
`this.watch` wraps MobX's `reaction` with automatic lifecycle disposal. For advanced MobX patterns (`autorun`, `when`, custom schedulers), use `reaction` directly and return a dispose function from `onMount`.
|
|
179
|
+
|
|
180
|
+
### Props Reactivity
|
|
181
|
+
|
|
182
|
+
`this.props` is reactive: your component re-renders when accessed props change.
|
|
183
|
+
|
|
184
|
+
**Option 1: `this.watch`** — the recommended way to react to state changes:
|
|
185
|
+
|
|
186
|
+
```ts
|
|
187
|
+
onCreate() {
|
|
188
|
+
this.watch(
|
|
189
|
+
() => this.props.filter,
|
|
190
|
+
(filter) => this.applyFilter(filter)
|
|
191
|
+
);
|
|
192
|
+
}
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
Watchers are automatically disposed on unmount. No cleanup needed.
|
|
196
|
+
|
|
197
|
+
**Option 2: `reaction`** — for advanced MobX patterns (autorun, when, custom schedulers):
|
|
198
|
+
|
|
199
|
+
```ts
|
|
200
|
+
onMount() {
|
|
201
|
+
return reaction(
|
|
202
|
+
() => this.props.filter,
|
|
203
|
+
(filter) => this.applyFilter(filter)
|
|
204
|
+
);
|
|
205
|
+
}
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
Or access props directly in `render()` and MobX handles re-renders when they change.
|
|
209
|
+
|
|
210
|
+
## Patterns
|
|
211
|
+
|
|
212
|
+
### Combined (default)
|
|
213
|
+
|
|
214
|
+
State, logic, and template in one class:
|
|
215
|
+
|
|
216
|
+
```ts
|
|
217
|
+
import { View, createView } from 'mantle-lit';
|
|
218
|
+
import { html } from 'lit';
|
|
219
|
+
|
|
220
|
+
interface TodoItem {
|
|
221
|
+
id: number;
|
|
222
|
+
text: string;
|
|
223
|
+
done: boolean;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
class TodoView extends View {
|
|
227
|
+
todos: TodoItem[] = [];
|
|
228
|
+
input = '';
|
|
229
|
+
|
|
230
|
+
add() {
|
|
231
|
+
this.todos.push({ id: Date.now(), text: this.input, done: false });
|
|
232
|
+
this.input = '';
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
setInput(e: Event) {
|
|
236
|
+
this.input = (e.target as HTMLInputElement).value;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
render() {
|
|
240
|
+
return html`
|
|
241
|
+
<div>
|
|
242
|
+
<input .value=${this.input} @input=${this.setInput} />
|
|
243
|
+
<button @click=${this.add}>Add</button>
|
|
244
|
+
<ul>${this.todos.map(t => html`<li>${t.text}</li>`)}</ul>
|
|
245
|
+
</div>
|
|
246
|
+
`;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
export const Todo = createView(TodoView, { tag: 'x-todo' });
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
### Separated
|
|
254
|
+
|
|
255
|
+
ViewModel and template separate:
|
|
256
|
+
|
|
257
|
+
```ts
|
|
258
|
+
import { ViewModel, createView } from 'mantle-lit';
|
|
259
|
+
import { html } from 'lit';
|
|
260
|
+
|
|
261
|
+
class TodoViewModel extends ViewModel {
|
|
262
|
+
todos: TodoItem[] = [];
|
|
263
|
+
input = '';
|
|
264
|
+
|
|
265
|
+
add() {
|
|
266
|
+
this.todos.push({ id: Date.now(), text: this.input, done: false });
|
|
267
|
+
this.input = '';
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
setInput(e: Event) {
|
|
271
|
+
this.input = (e.target as HTMLInputElement).value;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// Template as a separate function
|
|
276
|
+
const template = (vm: TodoViewModel) => html`
|
|
277
|
+
<div>
|
|
278
|
+
<input .value=${vm.input} @input=${vm.setInput} />
|
|
279
|
+
<button @click=${vm.add}>Add</button>
|
|
280
|
+
<ul>${vm.todos.map(t => html`<li>${t.text}</li>`)}</ul>
|
|
281
|
+
</div>
|
|
282
|
+
`;
|
|
283
|
+
|
|
284
|
+
// Note: For separated templates, extend the View class with a render method
|
|
285
|
+
// that calls the template function
|
|
286
|
+
class TodoView extends TodoViewModel {
|
|
287
|
+
render() {
|
|
288
|
+
return template(this);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
export const Todo = createView(TodoView, { tag: 'x-todo' });
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
## Decorators
|
|
296
|
+
|
|
297
|
+
For teams that prefer explicit annotations over auto-observable, Mantle provides its own decorators. These are lightweight metadata collectors. No `accessor` keyword required.
|
|
298
|
+
|
|
299
|
+
```ts
|
|
300
|
+
import { View, createView, observable, action, computed } from 'mantle-lit';
|
|
301
|
+
import { html } from 'lit';
|
|
302
|
+
|
|
303
|
+
class TodoView extends View {
|
|
304
|
+
@observable todos: TodoItem[] = [];
|
|
305
|
+
@observable input = '';
|
|
306
|
+
|
|
307
|
+
@computed get remaining() {
|
|
308
|
+
return this.todos.filter(t => !t.done).length;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
@action add() {
|
|
312
|
+
this.todos.push({ id: Date.now(), text: this.input, done: false });
|
|
313
|
+
this.input = '';
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
render() {
|
|
317
|
+
return html`<!-- ... -->`;
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
export const Todo = createView(TodoView, { tag: 'x-todo' });
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
**Key differences from auto-observable mode:**
|
|
325
|
+
- Only decorated fields are reactive (undecorated fields are inert)
|
|
326
|
+
- Methods are still auto-bound for stable `this` references
|
|
327
|
+
|
|
328
|
+
### Available Decorators
|
|
329
|
+
|
|
330
|
+
| Decorator | Purpose |
|
|
331
|
+
|-----------|---------|
|
|
332
|
+
| `@observable` | Deep observable field |
|
|
333
|
+
| `@observable.ref` | Reference-only observation |
|
|
334
|
+
| `@observable.shallow` | Shallow observation (add/remove only) |
|
|
335
|
+
| `@observable.struct` | Structural equality comparison |
|
|
336
|
+
| `@action` | Action method (auto-bound) |
|
|
337
|
+
| `@computed` | Computed getter (optional; getters are computed by default) |
|
|
338
|
+
|
|
339
|
+
### MobX Decorators (Legacy)
|
|
340
|
+
|
|
341
|
+
If you prefer using MobX's own decorators (requires `accessor` keyword for TC39):
|
|
342
|
+
|
|
343
|
+
```ts
|
|
344
|
+
import { observable, action } from 'mobx';
|
|
345
|
+
import { configure } from 'mantle-lit';
|
|
346
|
+
|
|
347
|
+
// Disable auto-observable globally
|
|
348
|
+
configure({ autoObservable: false });
|
|
349
|
+
|
|
350
|
+
class TodoView extends View {
|
|
351
|
+
@observable accessor todos: TodoItem[] = []; // note: accessor required
|
|
352
|
+
@action add() { /* ... */ }
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
export const Todo = createView(TodoView, { tag: 'x-todo' });
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
Note: `this.props` is always reactive regardless of decorator mode.
|
|
359
|
+
|
|
360
|
+
## Error Handling
|
|
361
|
+
|
|
362
|
+
Render errors propagate to the browser as usual. Lifecycle errors (`onMount`, `onUnmount`, `watch`) in both Views and Behaviors are caught and routed through a configurable handler.
|
|
363
|
+
|
|
364
|
+
By default, errors are logged to `console.error`. Configure a global handler to integrate with your error reporting:
|
|
365
|
+
|
|
366
|
+
```ts
|
|
367
|
+
import { configure } from 'mantle-lit';
|
|
368
|
+
|
|
369
|
+
configure({
|
|
370
|
+
onError: (error, context) => {
|
|
371
|
+
// context.phase: 'onCreate' | 'onMount' | 'onUnmount' | 'watch'
|
|
372
|
+
// context.name: class name of the View or Behavior
|
|
373
|
+
// context.isBehavior: true if the error came from a Behavior
|
|
374
|
+
Sentry.captureException(error, {
|
|
375
|
+
tags: { phase: context.phase, component: context.name },
|
|
376
|
+
});
|
|
377
|
+
},
|
|
378
|
+
});
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
Behavior errors are isolated. A failing Behavior won't prevent sibling Behaviors or the parent View from mounting.
|
|
382
|
+
|
|
383
|
+
## Behaviors (Experimental)
|
|
384
|
+
|
|
385
|
+
> ⚠️ **Experimental:** The Behaviors API is still evolving and may change in future releases.
|
|
386
|
+
|
|
387
|
+
Behaviors are reusable pieces of state and logic that can be shared across views. Define them as classes, wrap with `createBehavior()`, and use the resulting factory function in your Views.
|
|
388
|
+
|
|
389
|
+
### Defining a Behavior
|
|
390
|
+
|
|
391
|
+
```ts
|
|
392
|
+
import { Behavior, createBehavior } from 'mantle-lit';
|
|
393
|
+
|
|
394
|
+
class WindowSizeBehavior extends Behavior {
|
|
395
|
+
width = window.innerWidth;
|
|
396
|
+
height = window.innerHeight;
|
|
397
|
+
breakpoint!: number;
|
|
398
|
+
|
|
399
|
+
onCreate(breakpoint = 768) {
|
|
400
|
+
this.breakpoint = breakpoint;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
get isMobile() {
|
|
404
|
+
return this.width < this.breakpoint;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
handleResize() {
|
|
408
|
+
this.width = window.innerWidth;
|
|
409
|
+
this.height = window.innerHeight;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
onMount() {
|
|
413
|
+
window.addEventListener('resize', this.handleResize);
|
|
414
|
+
return () => window.removeEventListener('resize', this.handleResize);
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
export const withWindowSize = createBehavior(WindowSizeBehavior);
|
|
419
|
+
```
|
|
420
|
+
|
|
421
|
+
The naming convention:
|
|
422
|
+
- **Class**: PascalCase (`WindowSizeBehavior`)
|
|
423
|
+
- **Factory**: camelCase with `with` prefix (`withWindowSize`)
|
|
424
|
+
|
|
425
|
+
### Using Behaviors
|
|
426
|
+
|
|
427
|
+
Call the factory function (no `new` keyword) in your View. The `with` prefix signals that the View manages this behavior's lifecycle:
|
|
428
|
+
|
|
429
|
+
```ts
|
|
430
|
+
import { View, createView } from 'mantle-lit';
|
|
431
|
+
import { html } from 'lit';
|
|
432
|
+
import { withWindowSize } from './withWindowSize';
|
|
433
|
+
|
|
434
|
+
class ResponsiveView extends View {
|
|
435
|
+
windowSize = withWindowSize(768);
|
|
436
|
+
|
|
437
|
+
render() {
|
|
438
|
+
return html`
|
|
439
|
+
<div>
|
|
440
|
+
${this.windowSize.isMobile
|
|
441
|
+
? html`<mobile-layout></mobile-layout>`
|
|
442
|
+
: html`<desktop-layout></desktop-layout>`}
|
|
443
|
+
<p>Window: ${this.windowSize.width}x${this.windowSize.height}</p>
|
|
444
|
+
</div>
|
|
445
|
+
`;
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
export const Responsive = createView(ResponsiveView, { tag: 'x-responsive' });
|
|
450
|
+
```
|
|
451
|
+
|
|
452
|
+
### Watching in Behaviors
|
|
453
|
+
|
|
454
|
+
Behaviors can use `this.watch` just like Views:
|
|
455
|
+
|
|
456
|
+
```ts
|
|
457
|
+
class FetchBehavior extends Behavior {
|
|
458
|
+
url!: string;
|
|
459
|
+
data: any[] = [];
|
|
460
|
+
loading = false;
|
|
461
|
+
|
|
462
|
+
onCreate(url: string) {
|
|
463
|
+
this.url = url;
|
|
464
|
+
this.watch(() => this.url, () => this.fetchData(), { fireImmediately: true });
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
async fetchData() {
|
|
468
|
+
this.loading = true;
|
|
469
|
+
this.data = await fetch(this.url).then(r => r.json());
|
|
470
|
+
this.loading = false;
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
export const withFetch = createBehavior(FetchBehavior);
|
|
475
|
+
```
|
|
476
|
+
|
|
477
|
+
### Multiple Behaviors
|
|
478
|
+
|
|
479
|
+
Behaviors compose naturally:
|
|
480
|
+
|
|
481
|
+
```ts
|
|
482
|
+
import { View, createView } from 'mantle-lit';
|
|
483
|
+
import { html } from 'lit';
|
|
484
|
+
import { withFetch } from './FetchBehavior';
|
|
485
|
+
import { withWindowSize } from './WindowSizeBehavior';
|
|
486
|
+
|
|
487
|
+
class DashboardView extends View {
|
|
488
|
+
users = withFetch('/api/users');
|
|
489
|
+
posts = withFetch('/api/posts');
|
|
490
|
+
windowSize = withWindowSize(768);
|
|
491
|
+
|
|
492
|
+
render() {
|
|
493
|
+
return html`
|
|
494
|
+
<div>
|
|
495
|
+
${this.users.loading ? 'Loading...' : `${this.users.data.length} users`}
|
|
496
|
+
${this.windowSize.isMobile ? html`<mobile-nav></mobile-nav>` : ''}
|
|
497
|
+
</div>
|
|
498
|
+
`;
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
export const Dashboard = createView(DashboardView, { tag: 'x-dashboard' });
|
|
503
|
+
```
|
|
504
|
+
|
|
505
|
+
### Behavior Lifecycle
|
|
506
|
+
|
|
507
|
+
Behaviors support the same lifecycle methods as Views:
|
|
508
|
+
|
|
509
|
+
| Method | When |
|
|
510
|
+
|--------|------|
|
|
511
|
+
| `onCreate(...args)` | Called during construction with the factory arguments |
|
|
512
|
+
| `onMount()` | Called when parent View connects to DOM. Return cleanup (optional). |
|
|
513
|
+
| `onUnmount()` | Called when parent View disconnects from DOM. |
|
|
514
|
+
|
|
515
|
+
|
|
516
|
+
## API
|
|
517
|
+
|
|
518
|
+
### `configure(config)`
|
|
519
|
+
|
|
520
|
+
Set global defaults for all views. Settings can still be overridden per-view in `createView` options.
|
|
521
|
+
|
|
522
|
+
```ts
|
|
523
|
+
import { configure } from 'mantle-lit';
|
|
524
|
+
|
|
525
|
+
// Disable auto-observable globally (for decorator users)
|
|
526
|
+
configure({ autoObservable: false });
|
|
527
|
+
```
|
|
528
|
+
|
|
529
|
+
| Option | Default | Description |
|
|
530
|
+
|--------|---------|-------------|
|
|
531
|
+
| `autoObservable` | `true` | Whether to automatically make View instances observable |
|
|
532
|
+
| `onError` | `console.error` | Global error handler for lifecycle errors (see [Error Handling](#error-handling)) |
|
|
533
|
+
|
|
534
|
+
### `View<P>` / `ViewModel<P>`
|
|
535
|
+
|
|
536
|
+
Base class for view components. `ViewModel` is an alias for `View`. Use it when separating the ViewModel from the template for semantic clarity.
|
|
537
|
+
|
|
538
|
+
| Property/Method | Description |
|
|
539
|
+
|-----------------|-------------|
|
|
540
|
+
| `props` | Current props (reactive) |
|
|
541
|
+
| `onCreate()` | Called when instance created |
|
|
542
|
+
| `onMount()` | Called when connected to DOM, return cleanup (optional) |
|
|
543
|
+
| `onUnmount()` | Called when disconnected from DOM (optional) |
|
|
544
|
+
| `render()` | Return Lit `TemplateResult` |
|
|
545
|
+
| `watch(expr, callback, options?)` | Watch reactive expression, auto-disposed on unmount |
|
|
546
|
+
|
|
547
|
+
### `Behavior`
|
|
548
|
+
|
|
549
|
+
Base class for behaviors. Extend it and wrap with `createBehavior()`.
|
|
550
|
+
|
|
551
|
+
| Method | Description |
|
|
552
|
+
|--------|-------------|
|
|
553
|
+
| `onCreate(...args)` | Called during construction with constructor args |
|
|
554
|
+
| `onMount()` | Called when parent View mounts, return cleanup (optional) |
|
|
555
|
+
| `onUnmount()` | Called when parent View unmounts |
|
|
556
|
+
| `watch(expr, callback, options?)` | Watch reactive expression, auto-disposed on unmount |
|
|
557
|
+
|
|
558
|
+
### `createBehavior(Class)`
|
|
559
|
+
|
|
560
|
+
Creates a factory function from a behavior class. Returns a callable (no `new` needed).
|
|
561
|
+
|
|
562
|
+
```ts
|
|
563
|
+
class MyBehavior extends Behavior {
|
|
564
|
+
onCreate(value: string) { /* ... */ }
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
export const withMyBehavior = createBehavior(MyBehavior);
|
|
568
|
+
|
|
569
|
+
// Usage: withMyBehavior('hello')
|
|
570
|
+
```
|
|
571
|
+
|
|
572
|
+
### `createView(ViewClass, options)`
|
|
573
|
+
|
|
574
|
+
Function that creates and registers a Lit custom element from a View class.
|
|
575
|
+
|
|
576
|
+
```ts
|
|
577
|
+
// Basic
|
|
578
|
+
createView(MyView, { tag: 'x-my-view' })
|
|
579
|
+
|
|
580
|
+
// With options
|
|
581
|
+
createView(MyView, { tag: 'x-my-view', autoObservable: false })
|
|
582
|
+
```
|
|
583
|
+
|
|
584
|
+
| Option | Default | Description |
|
|
585
|
+
|--------|---------|-------------|
|
|
586
|
+
| `tag` | (required) | Custom element tag name (must contain a hyphen) |
|
|
587
|
+
| `autoObservable` | `true` | Make all fields observable. Set to `false` when using decorators. |
|
|
588
|
+
| `shadow` | `true` | Use Shadow DOM. Set to `false` to render in light DOM (allows external CSS). |
|
|
589
|
+
|
|
590
|
+
## Who This Is For
|
|
591
|
+
|
|
592
|
+
- Teams using MobX for state management
|
|
593
|
+
- Developers who prefer class-based components
|
|
594
|
+
- Projects building standards-compliant web components
|
|
595
|
+
- Anyone integrating vanilla JS libraries
|
|
596
|
+
- Teams wanting to share components across frameworks
|
|
597
|
+
|
|
598
|
+
## License
|
|
599
|
+
|
|
600
|
+
MIT
|