@twobirds/microcomponents 0.2.1 → 0.2.3
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 +45 -20
- package/dist/MC.min.js +1 -1
- package/dist/autoload.min.js +1 -1
- package/dist/core/MC.d.ts +289 -238
- package/dist/core/MC.d.ts.map +1 -1
- package/dist/core/MC.js +445 -184
- package/dist/core/elements.d.ts +58 -231
- package/dist/core/elements.d.ts.map +1 -1
- package/dist/core/elements.js +99 -214
- package/dist/core/index.d.ts +75 -0
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/index.js +72 -0
- package/dist/core/mcGuards.d.ts +7 -7
- package/dist/core/mcGuards.d.ts.map +1 -1
- package/dist/core/mcGuards.js +1 -1
- package/dist/elements.min.js +1 -1
- package/dist/formValues.min.js +1 -1
- package/dist/mcGuards.min.js +1 -1
- package/dist/microcomponents.min.js +1 -1
- package/dist/observables.min.js +1 -1
- package/dist/state/formValues.d.ts +113 -66
- package/dist/state/formValues.d.ts.map +1 -1
- package/dist/state/formValues.js +134 -68
- package/dist/state/index.d.ts +7 -0
- package/dist/state/index.d.ts.map +1 -0
- package/dist/state/index.js +5 -0
- package/dist/state/observables.d.ts +66 -133
- package/dist/state/observables.d.ts.map +1 -1
- package/dist/state/observables.js +185 -83
- package/dist/state/placeholders.d.ts +1 -0
- package/dist/state/placeholders.d.ts.map +1 -1
- package/dist/state/placeholders.js +1 -0
- package/dist/types/index.d.ts +7 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +1 -0
- package/dist/types/mcTypes.d.ts +3 -3
- package/dist/types/mcTypes.js +1 -1
- package/dist/utils/autoload.d.ts +6 -2
- package/dist/utils/autoload.d.ts.map +1 -1
- package/dist/utils/autoload.js +7 -5
- package/dist/utils/events.d.ts +22 -79
- package/dist/utils/events.d.ts.map +1 -1
- package/dist/utils/events.js +63 -1
- package/dist/utils/helpers.d.ts +10 -9
- package/dist/utils/helpers.d.ts.map +1 -1
- package/dist/utils/helpers.js +26 -14
- package/dist/utils/index.d.ts +17 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +12 -0
- package/package.json +13 -15
|
@@ -1,106 +1,60 @@
|
|
|
1
1
|
/**
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
enabling automatic DOM updates when underlying data changes.
|
|
7
|
-
|
|
8
|
-
## Overview
|
|
2
|
+
Reactive data binding via observables — object properties trigger callbacks and update
|
|
3
|
+
DOM placeholders automatically when their values change. Supports primitives, nested objects,
|
|
4
|
+
and deep-change detection with debounced notifications. Most likely you will only need the first
|
|
5
|
+
variant of the examples below
|
|
9
6
|
|
|
10
|
-
|
|
11
|
-
registered callbacks are automatically invoked whenever those properties change.
|
|
12
|
-
It's particularly designed for reactive templating and binding data to HTML elements.
|
|
13
|
-
It can also handle simple object properties like strings, boolean and numbers.
|
|
14
|
-
|
|
15
|
-
## How `observe()` Works
|
|
16
|
-
|
|
17
|
-
The main function is {@link observe}, which takes an object and makes its properties
|
|
18
|
-
or a specific property observable.
|
|
19
|
-
|
|
20
|
-
### Simple properties
|
|
7
|
+
@module Observables
|
|
21
8
|
|
|
22
|
-
|
|
23
|
-
```
|
|
24
|
-
|
|
9
|
+
@example Plugin — chainable instance methods on DC components
|
|
10
|
+
```ts
|
|
11
|
+
import { DC, defineCE, observePlugin } from '@twobirds/microcomponents';
|
|
25
12
|
|
|
26
|
-
|
|
13
|
+
observePlugin(DC);
|
|
27
14
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
});
|
|
15
|
+
class Counter extends DC {
|
|
16
|
+
data = { clicks: 1 };
|
|
31
17
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
18
|
+
oneAssets() {
|
|
19
|
+
this
|
|
20
|
+
.subscribe('data', (v) => console.log('data →', v))
|
|
21
|
+
.bind('data', this.target);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
35
24
|
|
|
36
|
-
|
|
37
|
-
person.count += 10; // > Count changed: 10
|
|
25
|
+
defineCE('mc-counter', Counter);
|
|
38
26
|
```
|
|
39
27
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
javascript
|
|
45
|
-
```js
|
|
46
|
-
let obj = { data: { name: "John", count: 0 } };
|
|
47
|
-
|
|
48
|
-
observe( obj, 'data' ); // make the data property observable
|
|
49
|
-
|
|
50
|
-
obj.data.subscribe( (data) => {
|
|
51
|
-
console.log("Data changed:", data);
|
|
52
|
-
});
|
|
28
|
+
@example Basic observe + subscribe on a plain object
|
|
29
|
+
```ts
|
|
30
|
+
import { observe } from '@twobirds/microcomponents';
|
|
53
31
|
|
|
54
|
-
|
|
55
|
-
|
|
32
|
+
const state = observe({ count: 0, label: 'hi' });
|
|
33
|
+
state.count.subscribe((v) => console.log('count is now', v));
|
|
34
|
+
state.count++; // triggers the callback
|
|
56
35
|
```
|
|
57
36
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
37
|
+
@example Deep change detection on a nested property
|
|
38
|
+
```ts
|
|
39
|
+
import { observe } from '@twobirds/microcomponents';
|
|
61
40
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
for (let i = 0; i < 100; i++) {
|
|
66
|
-
obj.value++; // Only triggers one callback after all changes complete
|
|
67
|
-
}
|
|
41
|
+
const state = observe({ data: { name: 'John', age: 30 } });
|
|
42
|
+
state.data.subscribe((d) => console.log('data changed:', d));
|
|
43
|
+
state.data.name = 'Jane'; // triggers callback with updated object
|
|
68
44
|
```
|
|
69
45
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
- Elements with `{placeholder}` syntax are scanned for observable data.
|
|
75
|
-
- When observed data changes, matching placeholders in the DOM are updated automatically.
|
|
76
|
-
|
|
77
|
-
@example
|
|
46
|
+
@example DOM binding — placeholder tokens update live
|
|
47
|
+
```ts
|
|
48
|
+
import { observe } from '@twobirds/microcomponents';
|
|
78
49
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
<
|
|
82
|
-
Hello {name}! You have {count} messages.
|
|
83
|
-
</div>
|
|
50
|
+
const state = observe({ user: { name: 'Alice', count: 0 } });
|
|
51
|
+
state.user.bind(document.querySelector('.model'));
|
|
52
|
+
// <span>{user.name}</span> updates automatically when state.user.name changes
|
|
84
53
|
```
|
|
85
54
|
|
|
86
|
-
Javascript
|
|
87
|
-
```js
|
|
88
|
-
const div = document.querySelector('.model');
|
|
89
|
-
|
|
90
|
-
const model = observe({
|
|
91
|
-
data: { name: "Alice", count: 0 },
|
|
92
|
-
visibility: ''
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
if (div) {
|
|
96
|
-
model.data.bind( div );
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
model.data.count++; // Automatically updates the DOM with new count value
|
|
100
|
-
model.visibility = 'hidden'; // adds the hidden class to the DOM element
|
|
101
|
-
```
|
|
102
55
|
*/
|
|
103
56
|
/**
|
|
57
|
+
@hidden
|
|
104
58
|
A callback invoked with the current observable value when notified.
|
|
105
59
|
|
|
106
60
|
@example
|
|
@@ -111,6 +65,7 @@ observable.subscribe(cb);
|
|
|
111
65
|
*/
|
|
112
66
|
export type Callback<T = unknown> = (value: T) => void;
|
|
113
67
|
/**
|
|
68
|
+
@hidden
|
|
114
69
|
Interface representing an observable object that supports subscription, notification, and DOM binding.
|
|
115
70
|
|
|
116
71
|
Observable objects track registered callbacks and invoke them when their internal state changes.
|
|
@@ -132,6 +87,7 @@ export type Observable<T = unknown> = T & {
|
|
|
132
87
|
callbacks: Array<Function>;
|
|
133
88
|
};
|
|
134
89
|
/**
|
|
90
|
+
@hidden
|
|
135
91
|
Every property of `T` mapped to an observable of its value.
|
|
136
92
|
|
|
137
93
|
KNOWN LIMIT: the runtime property *setter* accepts a plain value
|
|
@@ -161,6 +117,7 @@ export type Observed<T> = {
|
|
|
161
117
|
[K in keyof T]: Observable<T[K]>;
|
|
162
118
|
};
|
|
163
119
|
/**
|
|
120
|
+
@hidden
|
|
164
121
|
Only `K` of `T` mapped to an observable; other properties keep their type.
|
|
165
122
|
Carries the same write limitation as {@link Observed} for the mapped key.
|
|
166
123
|
|
|
@@ -174,6 +131,7 @@ export type ObservedKey<T, K extends keyof T> = {
|
|
|
174
131
|
[P in keyof T]: P extends K ? Observable<T[P]> : T[P];
|
|
175
132
|
};
|
|
176
133
|
/**
|
|
134
|
+
@hidden
|
|
177
135
|
The inverse of a hand-declared observable state interface: strips the
|
|
178
136
|
`Observable` wrapper from every property, yielding the plain seed shape.
|
|
179
137
|
`Unobserved<CounterState>` → `{ count: number }`.
|
|
@@ -201,6 +159,7 @@ export type Unobserved<S> = {
|
|
|
201
159
|
} ? V : S[K];
|
|
202
160
|
};
|
|
203
161
|
/**
|
|
162
|
+
@hidden
|
|
204
163
|
Whether a value is already an observable wrapper — `subscribe` is reachable on its prototype chain.
|
|
205
164
|
Used by {@link observe} to skip re-wrapping and to detect the "not yet observed" state on reassignment.
|
|
206
165
|
|
|
@@ -212,65 +171,39 @@ isAlreadyObserved(42); // false
|
|
|
212
171
|
```
|
|
213
172
|
*/
|
|
214
173
|
export declare function isAlreadyObserved(value: unknown): boolean;
|
|
174
|
+
export declare function observe<T extends object, K extends keyof T>(target: T, propertyName: K): ObservedKey<T, K>;
|
|
175
|
+
export declare function observe<T extends object>(target: T): Observed<T>;
|
|
176
|
+
export declare function observe<S extends object>(target: Unobserved<S>): S;
|
|
215
177
|
/**
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
Wraps each property of `target` that is not already observable (and meets criteria)
|
|
219
|
-
in an observable wrapper. Also sets up deep change detection for objects and
|
|
220
|
-
debounced notifications when the data changes.
|
|
178
|
+
Attaches convenience methods `notify()`, `subscribe()`, and `bind()` to the prototype of a
|
|
179
|
+
microcomponent class so that observable properties can be controlled directly on the instance.
|
|
221
180
|
|
|
222
|
-
|
|
181
|
+
All three methods return the component instance, so you can chain them fluently.
|
|
223
182
|
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
constructor. The declaration needs the double cast once (`0 as unknown as
|
|
227
|
-
Observable<number>`): the field starts life as a plain value and only becomes
|
|
228
|
-
the wrapper when `observe()` runs. In exchange, every later use is fully typed —
|
|
229
|
-
arithmetic, `subscribe`, `bind` — with no casts anywhere else.
|
|
183
|
+
Call it once at module level before defining any component classes. The plugin is idempotent —
|
|
184
|
+
subsequent calls are no-ops.
|
|
230
185
|
|
|
231
|
-
|
|
232
|
-
the fact — no declarations, the return type is inferred: every property comes
|
|
233
|
-
back as `Observable<PropertyType>`.
|
|
186
|
+
@param Class - A constructor function whose prototype chain includes {@link Core.McBase}.
|
|
234
187
|
|
|
235
|
-
@
|
|
236
|
-
@param propertyName - Optional specific property name to make one property observable instead of all properties.
|
|
237
|
-
@returns The original target object, now with observable properties.
|
|
238
|
-
|
|
239
|
-
@example Imperative usage — wrap an existing plain object:
|
|
240
|
-
```ts
|
|
241
|
-
const state = observe({ count: 0, label: 'hi' });
|
|
242
|
-
state.count.subscribe((v) => console.log('count is now', v));
|
|
243
|
-
state.count++; // triggers the callback
|
|
244
|
-
```
|
|
245
|
-
|
|
246
|
-
@example Declarative usage — typed class field with double cast:
|
|
188
|
+
@example
|
|
247
189
|
```ts
|
|
248
|
-
class Counter {
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
constructor() {
|
|
252
|
-
observe(this, 'clicks');
|
|
253
|
-
}
|
|
190
|
+
class Counter extends DC {
|
|
191
|
+
data = { clicks: 1 };
|
|
254
192
|
|
|
255
|
-
|
|
256
|
-
this
|
|
193
|
+
oneAssets() {
|
|
194
|
+
this
|
|
195
|
+
.subscribe('data', (v) => console.log('data →', v))
|
|
196
|
+
.bind('data', this.target);
|
|
257
197
|
}
|
|
258
198
|
}
|
|
259
199
|
```
|
|
260
|
-
|
|
261
|
-
@example Schema-first usage — divergent read/write types via hand-declared interface:
|
|
262
|
-
```ts
|
|
263
|
-
interface CounterState {
|
|
264
|
-
get count(): Observable<number>;
|
|
265
|
-
set count(v: number);
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
const state = observe<CounterState>({ count: 0 }); // seed validated against schema
|
|
269
|
-
state.count.subscribe((v) => console.log(v)); // fully typed
|
|
270
|
-
state.count = 5; // plain assignment — the declared setter type permits it
|
|
271
|
-
```
|
|
272
200
|
*/
|
|
273
|
-
export declare function
|
|
274
|
-
|
|
275
|
-
|
|
201
|
+
export declare function observePlugin(Class: any): void;
|
|
202
|
+
declare module '../core/MC' {
|
|
203
|
+
interface McBase<TTarget extends object = object> {
|
|
204
|
+
notify(propName: string): this;
|
|
205
|
+
subscribe(propName: string, callback: Function): this;
|
|
206
|
+
bind(propName: string, target: HTMLElement | ShadowRoot): this;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
276
209
|
//# sourceMappingURL=observables.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"observables.d.ts","sourceRoot":"","sources":["../../src/state/observables.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"observables.d.ts","sourceRoot":"","sources":["../../src/state/observables.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAsDE;AAkBF;;;;;;;;;EASE;AACF,MAAM,MAAM,QAAQ,CAAC,CAAC,GAAG,OAAO,IAAI,CAAC,KAAK,EAAE,CAAC,KAAK,IAAI,CAAC;AAEvD;;;;;;;;;;;;;EAaE;AACF,MAAM,MAAM,UAAU,CAAC,CAAC,GAAG,OAAO,IAAI,CAAC,GAAG;IACzC,SAAS,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC,CAAC,GAAG,UAAU,CAAC,CAAC,CAAC,CAAC;IAChD,WAAW,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC,CAAC,GAAG,UAAU,CAAC,CAAC,CAAC,CAAC;IAClD,MAAM,CAAC,MAAM,CAAC,EAAE,OAAO,GAAG,UAAU,CAAC,CAAC,CAAC,CAAC;IACxC,IAAI,CAAC,MAAM,EAAE,WAAW,GAAG,gBAAgB,GAAG,UAAU,GAAG,UAAU,CAAC,CAAC,CAAC,CAAC;IACzE,cAAc;IACd,SAAS,EAAE,KAAK,CAAC,QAAQ,CAAC,CAAC;CAC3B,CAAC;AAyNF;;;;;;;;;;;;;;;;;;;;;;;;;;EA0BE;AACF,MAAM,MAAM,QAAQ,CAAC,CAAC,IAAI;KAAG,CAAC,IAAI,MAAM,CAAC,GAAG,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;CAAE,CAAC;AAE/D;;;;;;;;;;EAUE;AACF,MAAM,MAAM,WAAW,CAAC,CAAC,EAAE,CAAC,SAAS,MAAM,CAAC,IAAI;KAC9C,CAAC,IAAI,MAAM,CAAC,GAAG,CAAC,SAAS,CAAC,GAAG,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;CACrD,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;EAsBE;AACF,MAAM,MAAM,UAAU,CAAC,CAAC,IAAI;KAC1B,CAAC,IAAI,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS;QAAE,SAAS,CAAC,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,CAAC,KAAK,IAAI,GAAG,OAAO,CAAA;KAAE,GACpF,CAAC,GACD,CAAC,CAAC,CAAC,CAAC;CACP,CAAC;AAKF;;;;;;;;;;;EAWE;AACF,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAMzD;AAED,wBAAgB,OAAO,CAAC,CAAC,SAAS,MAAM,EAAE,CAAC,SAAS,MAAM,CAAC,EAC1D,MAAM,EAAE,CAAC,EACT,YAAY,EAAE,CAAC,GACb,WAAW,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;AACrB,wBAAgB,OAAO,CAAC,CAAC,SAAS,MAAM,EAAE,MAAM,EAAE,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC;AAClE,wBAAgB,OAAO,CAAC,CAAC,SAAS,MAAM,EAAE,MAAM,EAAE,UAAU,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;AAyIpE;;;;;;;;;;;;;;;;;;;;;;;EAuBE;AACF,wBAAgB,aAAa,CAAC,KAAK,EAAE,GAAG,QAoEvC;AAED,OAAO,QAAQ,YAAY,CAAC;IACxB,UAAiB,MAAM,CAAC,OAAO,SAAS,MAAM,GAAG,MAAM;QACnD,MAAM,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;QAC/B,SAAS,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,QAAQ,GAAG,IAAI,CAAC;QACtD,IAAI,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,GAAG,UAAU,GAAG,IAAI,CAAC;KAClE;CACJ"}
|
|
@@ -1,109 +1,63 @@
|
|
|
1
1
|
/**
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
enabling automatic DOM updates when underlying data changes.
|
|
7
|
-
|
|
8
|
-
## Overview
|
|
9
|
-
|
|
10
|
-
The observable system allows you to make object properties "observable" so that
|
|
11
|
-
registered callbacks are automatically invoked whenever those properties change.
|
|
12
|
-
It's particularly designed for reactive templating and binding data to HTML elements.
|
|
13
|
-
It can also handle simple object properties like strings, boolean and numbers.
|
|
2
|
+
Reactive data binding via observables — object properties trigger callbacks and update
|
|
3
|
+
DOM placeholders automatically when their values change. Supports primitives, nested objects,
|
|
4
|
+
and deep-change detection with debounced notifications. Most likely you will only need the first
|
|
5
|
+
variant of the examples below
|
|
14
6
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
The main function is {@link observe}, which takes an object and makes its properties
|
|
18
|
-
or a specific property observable.
|
|
19
|
-
|
|
20
|
-
### Simple properties
|
|
7
|
+
@module Observables
|
|
21
8
|
|
|
22
|
-
|
|
23
|
-
```
|
|
24
|
-
|
|
9
|
+
@example Plugin — chainable instance methods on DC components
|
|
10
|
+
```ts
|
|
11
|
+
import { DC, defineCE, observePlugin } from '@twobirds/microcomponents';
|
|
25
12
|
|
|
26
|
-
|
|
13
|
+
observePlugin(DC);
|
|
27
14
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
});
|
|
15
|
+
class Counter extends DC {
|
|
16
|
+
data = { clicks: 1 };
|
|
31
17
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
18
|
+
oneAssets() {
|
|
19
|
+
this
|
|
20
|
+
.subscribe('data', (v) => console.log('data →', v))
|
|
21
|
+
.bind('data', this.target);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
35
24
|
|
|
36
|
-
|
|
37
|
-
person.count += 10; // > Count changed: 10
|
|
25
|
+
defineCE('mc-counter', Counter);
|
|
38
26
|
```
|
|
39
27
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
javascript
|
|
45
|
-
```js
|
|
46
|
-
let obj = { data: { name: "John", count: 0 } };
|
|
47
|
-
|
|
48
|
-
observe( obj, 'data' ); // make the data property observable
|
|
49
|
-
|
|
50
|
-
obj.data.subscribe( (data) => {
|
|
51
|
-
console.log("Data changed:", data);
|
|
52
|
-
});
|
|
28
|
+
@example Basic observe + subscribe on a plain object
|
|
29
|
+
```ts
|
|
30
|
+
import { observe } from '@twobirds/microcomponents';
|
|
53
31
|
|
|
54
|
-
|
|
55
|
-
|
|
32
|
+
const state = observe({ count: 0, label: 'hi' });
|
|
33
|
+
state.count.subscribe((v) => console.log('count is now', v));
|
|
34
|
+
state.count++; // triggers the callback
|
|
56
35
|
```
|
|
57
36
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
37
|
+
@example Deep change detection on a nested property
|
|
38
|
+
```ts
|
|
39
|
+
import { observe } from '@twobirds/microcomponents';
|
|
61
40
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
for (let i = 0; i < 100; i++) {
|
|
66
|
-
obj.value++; // Only triggers one callback after all changes complete
|
|
67
|
-
}
|
|
41
|
+
const state = observe({ data: { name: 'John', age: 30 } });
|
|
42
|
+
state.data.subscribe((d) => console.log('data changed:', d));
|
|
43
|
+
state.data.name = 'Jane'; // triggers callback with updated object
|
|
68
44
|
```
|
|
69
45
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
- Elements with `{placeholder}` syntax are scanned for observable data.
|
|
75
|
-
- When observed data changes, matching placeholders in the DOM are updated automatically.
|
|
76
|
-
|
|
77
|
-
@example
|
|
46
|
+
@example DOM binding — placeholder tokens update live
|
|
47
|
+
```ts
|
|
48
|
+
import { observe } from '@twobirds/microcomponents';
|
|
78
49
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
<
|
|
82
|
-
Hello {name}! You have {count} messages.
|
|
83
|
-
</div>
|
|
50
|
+
const state = observe({ user: { name: 'Alice', count: 0 } });
|
|
51
|
+
state.user.bind(document.querySelector('.model'));
|
|
52
|
+
// <span>{user.name}</span> updates automatically when state.user.name changes
|
|
84
53
|
```
|
|
85
54
|
|
|
86
|
-
Javascript
|
|
87
|
-
```js
|
|
88
|
-
const div = document.querySelector('.model');
|
|
89
|
-
|
|
90
|
-
const model = observe({
|
|
91
|
-
data: { name: "Alice", count: 0 },
|
|
92
|
-
visibility: ''
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
if (div) {
|
|
96
|
-
model.data.bind( div );
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
model.data.count++; // Automatically updates the DOM with new count value
|
|
100
|
-
model.visibility = 'hidden'; // adds the hidden class to the DOM element
|
|
101
|
-
```
|
|
102
55
|
*/
|
|
103
56
|
import { flattenObject, deepEqual, copyGettersSetters, isBrowser } from '../utils/helpers.js';
|
|
104
57
|
import { makePlaceholders } from './placeholders.js';
|
|
105
58
|
import { trigger } from '../utils/events.js';
|
|
106
59
|
/**
|
|
60
|
+
@hidden
|
|
107
61
|
Determines if a value is an observable object.
|
|
108
62
|
|
|
109
63
|
@param value - The value to check.
|
|
@@ -115,6 +69,7 @@ function isObservableObject(value) {
|
|
|
115
69
|
// repository for observable class wrappers
|
|
116
70
|
const classRepo = {};
|
|
117
71
|
/**
|
|
72
|
+
@hidden
|
|
118
73
|
Creates a new observable wrapper class for the given value's constructor.
|
|
119
74
|
|
|
120
75
|
Returns a subclass of the original constructor that implements the Observable interface,
|
|
@@ -301,6 +256,7 @@ function getConstructor(val) {
|
|
|
301
256
|
return classRepo[key] || makeObservable(val);
|
|
302
257
|
}
|
|
303
258
|
/**
|
|
259
|
+
@hidden
|
|
304
260
|
Whether a value is already an observable wrapper — `subscribe` is reachable on its prototype chain.
|
|
305
261
|
Used by {@link observe} to skip re-wrapping and to detect the "not yet observed" state on reassignment.
|
|
306
262
|
|
|
@@ -316,6 +272,65 @@ export function isAlreadyObserved(value) {
|
|
|
316
272
|
value !== null &&
|
|
317
273
|
'subscribe' in value);
|
|
318
274
|
}
|
|
275
|
+
/**
|
|
276
|
+
@hidden
|
|
277
|
+
Makes a target object's properties observable by replacing them with wrapped values.
|
|
278
|
+
|
|
279
|
+
Wraps each property of `target` that is not already observable (and meets criteria)
|
|
280
|
+
in an observable wrapper. Also sets up deep change detection for objects and
|
|
281
|
+
debounced notifications when the data changes.
|
|
282
|
+
|
|
283
|
+
There are two sides to using it — pick per call site:
|
|
284
|
+
|
|
285
|
+
**Declarative (typed class fields).** Declare the field as `Observable<T>` up
|
|
286
|
+
front, then let `observe(this, 'field')` install the live wrapper in the
|
|
287
|
+
constructor. The declaration needs the double cast once (`0 as unknown as
|
|
288
|
+
Observable<number>`): the field starts life as a plain value and only becomes
|
|
289
|
+
the wrapper when `observe()` runs. In exchange, every later use is fully typed —
|
|
290
|
+
arithmetic, `subscribe`, `bind` — with no casts anywhere else.
|
|
291
|
+
|
|
292
|
+
**Imperative (wrapping existing data).** Hand any object to `observe()` after
|
|
293
|
+
the fact — no declarations, the return type is inferred: every property comes
|
|
294
|
+
back as `Observable<PropertyType>`.
|
|
295
|
+
|
|
296
|
+
@param target - The object whose properties should be made observable.
|
|
297
|
+
@param propertyName - Optional specific property name to make one property observable instead of all properties.
|
|
298
|
+
@returns The original target object, now with observable properties.
|
|
299
|
+
|
|
300
|
+
@example Imperative usage — wrap an existing plain object:
|
|
301
|
+
```ts
|
|
302
|
+
const state = observe({ count: 0, label: 'hi' });
|
|
303
|
+
state.count.subscribe((v) => console.log('count is now', v));
|
|
304
|
+
state.count++; // triggers the callback
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
@example Declarative usage — typed class field with double cast:
|
|
308
|
+
```ts
|
|
309
|
+
class Counter {
|
|
310
|
+
clicks: Observable<number> = 0 as unknown as Observable<number>;
|
|
311
|
+
|
|
312
|
+
constructor() {
|
|
313
|
+
observe(this, 'clicks');
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
bump() {
|
|
317
|
+
this.clicks++; // plain arithmetic — the declaration carries the type
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
@example Schema-first usage — divergent read/write types via hand-declared interface:
|
|
323
|
+
```ts
|
|
324
|
+
interface CounterState {
|
|
325
|
+
get count(): Observable<number>;
|
|
326
|
+
set count(v: number);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
const state = observe<CounterState>({ count: 0 }); // seed validated against schema
|
|
330
|
+
state.count.subscribe((v) => console.log(v)); // fully typed
|
|
331
|
+
state.count = 5; // plain assignment — the declared setter type permits it
|
|
332
|
+
```
|
|
333
|
+
*/
|
|
319
334
|
export function observe(target, propertyName) {
|
|
320
335
|
const source = target;
|
|
321
336
|
const keys = propertyName ? [propertyName] : [...Object.getOwnPropertyNames(target)];
|
|
@@ -380,3 +395,90 @@ export function observe(target, propertyName) {
|
|
|
380
395
|
});
|
|
381
396
|
return target;
|
|
382
397
|
}
|
|
398
|
+
/**
|
|
399
|
+
Attaches convenience methods `notify()`, `subscribe()`, and `bind()` to the prototype of a
|
|
400
|
+
microcomponent class so that observable properties can be controlled directly on the instance.
|
|
401
|
+
|
|
402
|
+
All three methods return the component instance, so you can chain them fluently.
|
|
403
|
+
|
|
404
|
+
Call it once at module level before defining any component classes. The plugin is idempotent —
|
|
405
|
+
subsequent calls are no-ops.
|
|
406
|
+
|
|
407
|
+
@param Class - A constructor function whose prototype chain includes {@link Core.McBase}.
|
|
408
|
+
|
|
409
|
+
@example
|
|
410
|
+
```ts
|
|
411
|
+
class Counter extends DC {
|
|
412
|
+
data = { clicks: 1 };
|
|
413
|
+
|
|
414
|
+
oneAssets() {
|
|
415
|
+
this
|
|
416
|
+
.subscribe('data', (v) => console.log('data →', v))
|
|
417
|
+
.bind('data', this.target);
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
```
|
|
421
|
+
*/
|
|
422
|
+
export function observePlugin(Class) {
|
|
423
|
+
const McBaseProto = Object.getPrototypeOf(Class.prototype);
|
|
424
|
+
function makePropObservable(obj, propName) {
|
|
425
|
+
const obs = obj[propName];
|
|
426
|
+
if (!isAlreadyObserved(obs)) {
|
|
427
|
+
observe(obj, propName);
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
if (!Object.hasOwn(McBaseProto, 'notify')) {
|
|
431
|
+
Object.assign(McBaseProto, {
|
|
432
|
+
/**
|
|
433
|
+
* Manually trigger a notification on an observable property.
|
|
434
|
+
* Ensures the property is observed first if it hasn't been yet.
|
|
435
|
+
* @param propName - The name of the observable property to notify.
|
|
436
|
+
* @returns This instance for chaining.
|
|
437
|
+
|
|
438
|
+
@example
|
|
439
|
+
```ts
|
|
440
|
+
this.notify('count');
|
|
441
|
+
```
|
|
442
|
+
*/
|
|
443
|
+
notify: function (propName) {
|
|
444
|
+
makePropObservable(this, propName);
|
|
445
|
+
this[propName].notify();
|
|
446
|
+
return this; // chainable
|
|
447
|
+
},
|
|
448
|
+
/**
|
|
449
|
+
* Subscribe a callback to changes on an observable property.
|
|
450
|
+
* Ensures the property is observed first if it hasn't been yet.
|
|
451
|
+
* @param propName - The name of the observable property.
|
|
452
|
+
* @param callback - Function invoked with the new value on every change.
|
|
453
|
+
* @returns This instance for chaining.
|
|
454
|
+
|
|
455
|
+
@example
|
|
456
|
+
```ts
|
|
457
|
+
this.subscribe('count', (v) => console.log('count →', v));
|
|
458
|
+
```
|
|
459
|
+
*/
|
|
460
|
+
subscribe: function (propName, callback) {
|
|
461
|
+
makePropObservable(this, propName);
|
|
462
|
+
this[propName].subscribe(callback);
|
|
463
|
+
return this; // chainable
|
|
464
|
+
},
|
|
465
|
+
/**
|
|
466
|
+
* Bind an observable property to a DOM target so that `{placeholder}` tokens are updated live.
|
|
467
|
+
* Ensures the property is observed first if it hasn't been yet.
|
|
468
|
+
* @param propName - The name of the observable property.
|
|
469
|
+
* @param target - HTMLElement, ShadowRoot, or DocumentFragment containing placeholders.
|
|
470
|
+
* @returns This instance for chaining.
|
|
471
|
+
|
|
472
|
+
@example
|
|
473
|
+
```ts
|
|
474
|
+
this.bind('data', this.target);
|
|
475
|
+
```
|
|
476
|
+
*/
|
|
477
|
+
bind: function (propName, target) {
|
|
478
|
+
makePropObservable(this, propName);
|
|
479
|
+
this[propName].bind(target);
|
|
480
|
+
return this; // chainable
|
|
481
|
+
},
|
|
482
|
+
});
|
|
483
|
+
}
|
|
484
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"placeholders.d.ts","sourceRoot":"","sources":["../../src/state/placeholders.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"placeholders.d.ts","sourceRoot":"","sources":["../../src/state/placeholders.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;EAUE;AAEF,OAAO,EAAE,EAAE,EAAE,KAAK,QAAQ,EAAE,KAAK,MAAM,EAAE,MAAM,eAAe,CAAC;AAC/D,OAAO,EAAW,KAAK,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAoD5D;;;;;;EAME;AACF,cAAM,YAAa,SAAQ,EAAE;;IAE5B,IAAI,EAAE,UAAU,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAwD;IACjG,UAAU,CAAC,EAAE,MAAM,CAAM;IAEzB;;;;;;;;MAQE;gBACU,MAAM,EAAE,WAAW;IAkF/B;;;;;;;MAOE;IACF,MAAM,CAAC,EAAE,EAAE,WAAW,CAAC,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;CAOzD;AAmBD;;;;;;;EAOE;AACF,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,WAAW,GAAG,gBAAgB,GAAG,UAAU,GAAG,IAAI,CAK3F;AAED;;;;EAIE;AACF,MAAM,MAAM,gBAAgB,GAAG,MAAM,CAAC,WAAW,EAAE;IAAE,aAAa,CAAC,EAAE,YAAY,CAAA;CAAE,CAAC,CAAC"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
Types module — pure type layer for the microcomponent system.
|
|
3
|
+
|
|
4
|
+
@module Types
|
|
5
|
+
*/
|
|
6
|
+
export type { McBubbleController, BubbleDirection, BubbleDirections, McDetail, McEventHandler, McEvent, McPayloadOf, McController, McNativeEventHandlers, McHost, McHostOf, McEntriesOf, McMerged, McRemoved, } from './mcTypes.js';
|
|
7
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"AAAA;;;;EAIE;AACF,YAAY,EACX,kBAAkB,EAClB,eAAe,EACf,gBAAgB,EAChB,QAAQ,EACR,cAAc,EACd,OAAO,EACP,WAAW,EACX,YAAY,EACZ,qBAAqB,EACrB,MAAM,EACN,QAAQ,EACR,WAAW,EACX,QAAQ,EACR,SAAS,GACT,MAAM,cAAc,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/types/mcTypes.d.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
@module Type layer
|
|
3
2
|
|
|
4
3
|
Pure type layer of the microcomponent system.
|
|
5
4
|
|
|
6
5
|
RULE: types only — no runtime exports, no imports. It sits at the bottom of the
|
|
7
6
|
dependency graph so every other module can import from it without cycles.
|
|
7
|
+
@module Types
|
|
8
8
|
*/
|
|
9
9
|
/** The bubble control handle passed to event handlers; `BubbleController` in events.ts implements it. */
|
|
10
10
|
export interface McBubbleController {
|
|
@@ -66,7 +66,7 @@ export type McPayloadOf<EM, K extends string, D = unknown> = K extends keyof EM
|
|
|
66
66
|
declare global {
|
|
67
67
|
/**
|
|
68
68
|
App-wide map of registry key names to component types. Augment it next to a
|
|
69
|
-
component definition and keyed lookups (`
|
|
69
|
+
component definition and keyed lookups (`isMcHost(el, 'cart')`, `closest('cart')`,
|
|
70
70
|
`ancestors` / `children` / `descendants`) type themselves from it.
|
|
71
71
|
|
|
72
72
|
Presence is checked at runtime; the key → type mapping is a convention. Where
|
|
@@ -81,7 +81,7 @@ declare global {
|
|
|
81
81
|
}
|
|
82
82
|
|
|
83
83
|
const el = document.querySelector('form');
|
|
84
|
-
if (el &&
|
|
84
|
+
if (el && isMcHost(el, 'cart')) {
|
|
85
85
|
el._mc.cart.addItem(item); // Cart
|
|
86
86
|
}
|
|
87
87
|
```
|