@tmbr/component 1.0.0 → 1.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/README.md +31 -13
- package/bind.js +4 -4
- package/index.js +23 -15
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -62,6 +62,24 @@ class Counter extends Component {
|
|
|
62
62
|
new Counter('#counter');
|
|
63
63
|
```
|
|
64
64
|
|
|
65
|
+
## State
|
|
66
|
+
|
|
67
|
+
State is deeply reactive so mutations to nested objects and arrays trigger a render.
|
|
68
|
+
|
|
69
|
+
```js
|
|
70
|
+
class Form extends Component {
|
|
71
|
+
static state = {
|
|
72
|
+
fields: {name: '', email: ''},
|
|
73
|
+
errors: {}
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
```html
|
|
79
|
+
<input type="text" :model="fields.name" />
|
|
80
|
+
<span :text="errors.name"></span>
|
|
81
|
+
```
|
|
82
|
+
|
|
65
83
|
## Directives
|
|
66
84
|
|
|
67
85
|
Directives are expressions evaluated with the current state.
|
|
@@ -136,16 +154,16 @@ this.dom.items // [li, li]
|
|
|
136
154
|
|
|
137
155
|
## Instance API
|
|
138
156
|
|
|
139
|
-
| Property or Method
|
|
140
|
-
|
|
|
141
|
-
|
|
|
142
|
-
|
|
|
143
|
-
|
|
|
144
|
-
|
|
|
145
|
-
|
|
|
146
|
-
|
|
|
147
|
-
|
|
|
148
|
-
|
|
|
149
|
-
|
|
|
150
|
-
|
|
|
151
|
-
|
|
|
157
|
+
| Property or Method | Description |
|
|
158
|
+
| --------------------------------- | --------------------------------------------------- |
|
|
159
|
+
| `el` | root element |
|
|
160
|
+
| `dom` | child elements collected from `ref` attributes |
|
|
161
|
+
| `props` | parsed from `data-props` attribute |
|
|
162
|
+
| `state` | reactive proxy where changes trigger a rerender |
|
|
163
|
+
| `findOne(selector)` | scoped `querySelector` |
|
|
164
|
+
| `findAll(selector)` | scoped `querySelectorAll` |
|
|
165
|
+
| `init()` | called after constructor — override in subclass |
|
|
166
|
+
| `update(state)` | called after each render — override in subclass |
|
|
167
|
+
| `on(event, target, fn)` | delegate event listener, cleaned up on `.destroy()` |
|
|
168
|
+
| `dispatch(type, detail, options)` | dispatches a `CustomEvent` from `.el` |
|
|
169
|
+
| `destroy()` | removes all listeners and directives |
|
package/bind.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { cx, isFunction } from '@tmbr/utils';
|
|
1
|
+
import { cx, dot, isFunction } from '@tmbr/utils';
|
|
2
2
|
|
|
3
3
|
const BOOLEANS = new Set([
|
|
4
4
|
'allowfullscreen',
|
|
@@ -60,7 +60,7 @@ export function bindDirective(component, node, attr, expression) {
|
|
|
60
60
|
|
|
61
61
|
node.addEventListener(type, event => {
|
|
62
62
|
const value = isCheckbox ? event.target.checked : event.target.value;
|
|
63
|
-
component.state
|
|
63
|
+
dot(component.state, expression, isNumber ? Number(value) : value);
|
|
64
64
|
}, {signal: component.controller.signal});
|
|
65
65
|
|
|
66
66
|
} else if (attr === 'text') {
|
|
@@ -70,8 +70,8 @@ export function bindDirective(component, node, attr, expression) {
|
|
|
70
70
|
} else if (attr === 'value') {
|
|
71
71
|
apply = value => node.value = value;
|
|
72
72
|
} else if (attr === 'show') {
|
|
73
|
-
const
|
|
74
|
-
apply = value => node.style.display = value ?
|
|
73
|
+
const initial = node.style.display || '';
|
|
74
|
+
apply = value => node.style.display = value ? initial : 'none';
|
|
75
75
|
} else if (attr === 'class') {
|
|
76
76
|
const initial = node.className;
|
|
77
77
|
apply = value => { node.className = initial; cx(node, value); };
|
package/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { on, findOne, findAll, isDefined, isString, toJSON, traverse } from '@tmbr/utils';
|
|
1
|
+
import { on, findOne, findAll, isArray, isDefined, isObject, isString, toJSON, traverse } from '@tmbr/utils';
|
|
2
2
|
import { bindDirective, bindEvent } from './bind.js';
|
|
3
3
|
|
|
4
4
|
let queue;
|
|
@@ -25,11 +25,12 @@ function render(component) {
|
|
|
25
25
|
|
|
26
26
|
const context = proto === Component.prototype ? state : new Proxy(state, {
|
|
27
27
|
has(target, key) {
|
|
28
|
-
|
|
28
|
+
const own = Object.getOwnPropertyDescriptor(proto, key);
|
|
29
|
+
return own?.get ? true : key in target;
|
|
29
30
|
},
|
|
30
31
|
get(target, key, receiver) {
|
|
31
|
-
const
|
|
32
|
-
return
|
|
32
|
+
const own = Object.getOwnPropertyDescriptor(proto, key);
|
|
33
|
+
return own?.get ? own.get.call(component) : Reflect.get(target, key, receiver);
|
|
33
34
|
}
|
|
34
35
|
});
|
|
35
36
|
|
|
@@ -80,6 +81,23 @@ export default class Component {
|
|
|
80
81
|
|
|
81
82
|
#state() {
|
|
82
83
|
|
|
84
|
+
const state = this.el.dataset.state
|
|
85
|
+
? toJSON(this.el.dataset.state)
|
|
86
|
+
: structuredClone(this.constructor.state);
|
|
87
|
+
|
|
88
|
+
const instance = this;
|
|
89
|
+
const reactive = (obj) => new Proxy(obj, {
|
|
90
|
+
get(target, key, receiver) {
|
|
91
|
+
const value = Reflect.get(target, key, receiver);
|
|
92
|
+
return isArray(value) || isObject(value) ? reactive(value) : value;
|
|
93
|
+
},
|
|
94
|
+
set(target, key, value) {
|
|
95
|
+
target[key] = value;
|
|
96
|
+
enqueue(instance);
|
|
97
|
+
return true;
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
|
|
83
101
|
traverse(this.el, child => {
|
|
84
102
|
for (const {name, value} of [...child.attributes]) {
|
|
85
103
|
if (name.startsWith(':')) {
|
|
@@ -92,18 +110,8 @@ export default class Component {
|
|
|
92
110
|
}
|
|
93
111
|
});
|
|
94
112
|
|
|
95
|
-
const state = this.el.dataset.state
|
|
96
|
-
? toJSON(this.el.dataset.state)
|
|
97
|
-
: structuredClone(this.constructor.state);
|
|
98
|
-
|
|
99
|
-
const set = (target, key, value) => {
|
|
100
|
-
target[key] = value;
|
|
101
|
-
enqueue(this);
|
|
102
|
-
return true;
|
|
103
|
-
};
|
|
104
|
-
|
|
105
113
|
enqueue(this);
|
|
106
|
-
return
|
|
114
|
+
return reactive(state);
|
|
107
115
|
}
|
|
108
116
|
|
|
109
117
|
findOne(s) {
|