@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.
Files changed (50) hide show
  1. package/README.md +45 -20
  2. package/dist/MC.min.js +1 -1
  3. package/dist/autoload.min.js +1 -1
  4. package/dist/core/MC.d.ts +289 -238
  5. package/dist/core/MC.d.ts.map +1 -1
  6. package/dist/core/MC.js +445 -184
  7. package/dist/core/elements.d.ts +58 -231
  8. package/dist/core/elements.d.ts.map +1 -1
  9. package/dist/core/elements.js +99 -214
  10. package/dist/core/index.d.ts +75 -0
  11. package/dist/core/index.d.ts.map +1 -0
  12. package/dist/core/index.js +72 -0
  13. package/dist/core/mcGuards.d.ts +7 -7
  14. package/dist/core/mcGuards.d.ts.map +1 -1
  15. package/dist/core/mcGuards.js +1 -1
  16. package/dist/elements.min.js +1 -1
  17. package/dist/formValues.min.js +1 -1
  18. package/dist/mcGuards.min.js +1 -1
  19. package/dist/microcomponents.min.js +1 -1
  20. package/dist/observables.min.js +1 -1
  21. package/dist/state/formValues.d.ts +113 -66
  22. package/dist/state/formValues.d.ts.map +1 -1
  23. package/dist/state/formValues.js +134 -68
  24. package/dist/state/index.d.ts +7 -0
  25. package/dist/state/index.d.ts.map +1 -0
  26. package/dist/state/index.js +5 -0
  27. package/dist/state/observables.d.ts +66 -133
  28. package/dist/state/observables.d.ts.map +1 -1
  29. package/dist/state/observables.js +185 -83
  30. package/dist/state/placeholders.d.ts +1 -0
  31. package/dist/state/placeholders.d.ts.map +1 -1
  32. package/dist/state/placeholders.js +1 -0
  33. package/dist/types/index.d.ts +7 -0
  34. package/dist/types/index.d.ts.map +1 -0
  35. package/dist/types/index.js +1 -0
  36. package/dist/types/mcTypes.d.ts +3 -3
  37. package/dist/types/mcTypes.js +1 -1
  38. package/dist/utils/autoload.d.ts +6 -2
  39. package/dist/utils/autoload.d.ts.map +1 -1
  40. package/dist/utils/autoload.js +7 -5
  41. package/dist/utils/events.d.ts +22 -79
  42. package/dist/utils/events.d.ts.map +1 -1
  43. package/dist/utils/events.js +63 -1
  44. package/dist/utils/helpers.d.ts +10 -9
  45. package/dist/utils/helpers.d.ts.map +1 -1
  46. package/dist/utils/helpers.js +26 -14
  47. package/dist/utils/index.d.ts +17 -0
  48. package/dist/utils/index.d.ts.map +1 -0
  49. package/dist/utils/index.js +12 -0
  50. package/package.json +13 -15
@@ -1,106 +1,60 @@
1
1
  /**
2
- @module Observables
3
-
4
-
5
- This module provides reactive data binding capabilities using observables,
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
- 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.
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
- javascript
23
- ```js
24
- let person = { name: "John", count: 0 };
9
+ @example Plugin — chainable instance methods on DC components
10
+ ```ts
11
+ import { DC, defineCE, observePlugin } from '@twobirds/microcomponents';
25
12
 
26
- observe( person ); // all properties inside are now observables
13
+ observePlugin(DC);
27
14
 
28
- person.name.subscribe( (name) => {
29
- console.log("Name changed:", name);
30
- });
15
+ class Counter extends DC {
16
+ data = { clicks: 1 };
31
17
 
32
- person.count.subscribe( (count) => {
33
- console.log("Count changed:", count);
34
- });
18
+ oneAssets() {
19
+ this
20
+ .subscribe('data', (v) => console.log('data →', v))
21
+ .bind('data', this.target);
22
+ }
23
+ }
35
24
 
36
- person.name = "Jane"; // > Name changed: Jane
37
- person.count += 10; // > Count changed: 10
25
+ defineCE('mc-counter', Counter);
38
26
  ```
39
27
 
40
- ### Deep Change Detection
41
-
42
- This means that if you modify any of the properties inside an observed object -> all callbacks are triggered.
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
- obj.data.name = "Jane"; // > { data: { name: "Jane", count: 0 } }
55
- obj.data.count += 10; // > { data: { name: "Jane", count: 10 } }
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
- ### Debounced Notifications
59
-
60
- To prevent excessive updates, notifications are debounced briefly:
37
+ @example Deep change detection on a nested property
38
+ ```ts
39
+ import { observe } from '@twobirds/microcomponents';
61
40
 
62
- javascript
63
- ```js
64
- let obj = observe({ value: 1 });
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
- ## DOM Binding with Placeholders
71
-
72
- The observables integrate tightly with placeholder-based templating:
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
- HTML
80
- ```html
81
- <div class="model {visibility}">
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
- Makes a target object's properties observable by replacing them with wrapped values.
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
- There are two sides to using it — pick per call site:
181
+ All three methods return the component instance, so you can chain them fluently.
223
182
 
224
- **Declarative (typed class fields).** Declare the field as `Observable<T>` up
225
- front, then let `observe(this, 'field')` install the live wrapper in the
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
- **Imperative (wrapping existing data).** Hand any object to `observe()` after
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
- @param target - The object whose properties should be made observable.
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
- clicks: Observable<number> = 0 as unknown as Observable<number>;
250
-
251
- constructor() {
252
- observe(this, 'clicks');
253
- }
190
+ class Counter extends DC {
191
+ data = { clicks: 1 };
254
192
 
255
- bump() {
256
- this.clicks++; // plain arithmetic — the declaration carries the type
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 observe<T extends object, K extends keyof T>(target: T, propertyName: K): ObservedKey<T, K>;
274
- export declare function observe<T extends object>(target: T): Observed<T>;
275
- export declare function observe<S extends object>(target: Unobserved<S>): S;
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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAqGE;AAiBF;;;;;;;;EAQE;AACF,MAAM,MAAM,QAAQ,CAAC,CAAC,GAAG,OAAO,IAAI,CAAC,KAAK,EAAE,CAAC,KAAK,IAAI,CAAC;AAEvD;;;;;;;;;;;;EAYE;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;AAwNF;;;;;;;;;;;;;;;;;;;;;;;;;EAyBE;AACF,MAAM,MAAM,QAAQ,CAAC,CAAC,IAAI;KAAG,CAAC,IAAI,MAAM,CAAC,GAAG,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;CAAE,CAAC;AAE/D;;;;;;;;;EASE;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;;;;;;;;;;;;;;;;;;;;;EAqBE;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;;;;;;;;;;EAUE;AACF,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAMzD;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAyDE;AAEF,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"}
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
- @module Observables
3
-
4
-
5
- This module provides reactive data binding capabilities using observables,
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
- ## 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
- javascript
23
- ```js
24
- let person = { name: "John", count: 0 };
9
+ @example Plugin — chainable instance methods on DC components
10
+ ```ts
11
+ import { DC, defineCE, observePlugin } from '@twobirds/microcomponents';
25
12
 
26
- observe( person ); // all properties inside are now observables
13
+ observePlugin(DC);
27
14
 
28
- person.name.subscribe( (name) => {
29
- console.log("Name changed:", name);
30
- });
15
+ class Counter extends DC {
16
+ data = { clicks: 1 };
31
17
 
32
- person.count.subscribe( (count) => {
33
- console.log("Count changed:", count);
34
- });
18
+ oneAssets() {
19
+ this
20
+ .subscribe('data', (v) => console.log('data →', v))
21
+ .bind('data', this.target);
22
+ }
23
+ }
35
24
 
36
- person.name = "Jane"; // > Name changed: Jane
37
- person.count += 10; // > Count changed: 10
25
+ defineCE('mc-counter', Counter);
38
26
  ```
39
27
 
40
- ### Deep Change Detection
41
-
42
- This means that if you modify any of the properties inside an observed object -> all callbacks are triggered.
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
- obj.data.name = "Jane"; // > { data: { name: "Jane", count: 0 } }
55
- obj.data.count += 10; // > { data: { name: "Jane", count: 10 } }
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
- ### Debounced Notifications
59
-
60
- To prevent excessive updates, notifications are debounced briefly:
37
+ @example Deep change detection on a nested property
38
+ ```ts
39
+ import { observe } from '@twobirds/microcomponents';
61
40
 
62
- javascript
63
- ```js
64
- let obj = observe({ value: 1 });
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
- ## DOM Binding with Placeholders
71
-
72
- The observables integrate tightly with placeholder-based templating:
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
- HTML
80
- ```html
81
- <div class="model {visibility}">
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,4 +1,5 @@
1
1
  /**
2
+ @hidden
2
3
  DOM placeholder binding: `{name}` templates in attributes and text nodes,
3
4
  kept up to date from observable data.
4
5
 
@@ -1 +1 @@
1
- {"version":3,"file":"placeholders.d.ts","sourceRoot":"","sources":["../../src/state/placeholders.ts"],"names":[],"mappings":"AAAA;;;;;;;;;EASE;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"}
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"}
@@ -1,4 +1,5 @@
1
1
  /**
2
+ @hidden
2
3
  DOM placeholder binding: `{name}` templates in attributes and text nodes,
3
4
  kept up to date from observable data.
4
5
 
@@ -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 {};
@@ -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 (`isMcDOM(el, 'cart')`, `closest('cart')`,
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 && isMcDOM(el, 'cart')) {
84
+ if (el && isMcHost(el, 'cart')) {
85
85
  el._mc.cart.addItem(item); // Cart
86
86
  }
87
87
  ```