@twobirds/microcomponents 0.1.21 → 0.2.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.
Files changed (71) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +91 -92
  3. package/dist/MC.min.js +1 -345
  4. package/dist/autoload.min.js +1 -0
  5. package/dist/core/MC.d.ts +401 -0
  6. package/dist/core/MC.d.ts.map +1 -0
  7. package/dist/core/MC.js +471 -0
  8. package/dist/core/autoload.d.ts +54 -0
  9. package/dist/core/autoload.d.ts.map +1 -0
  10. package/dist/core/autoload.js +158 -0
  11. package/dist/core/elements.d.ts +232 -0
  12. package/dist/core/elements.d.ts.map +1 -0
  13. package/dist/core/elements.js +123 -0
  14. package/dist/core/events.d.ts +231 -0
  15. package/dist/core/events.d.ts.map +1 -0
  16. package/dist/core/events.js +228 -0
  17. package/dist/core/mcGuards.d.ts +49 -0
  18. package/dist/core/mcGuards.d.ts.map +1 -0
  19. package/dist/core/mcGuards.js +19 -0
  20. package/dist/elements.min.js +1 -1
  21. package/dist/events.min.js +1 -0
  22. package/dist/formValues.min.js +1 -1
  23. package/dist/mcGuards.min.js +1 -0
  24. package/dist/microcomponents.d.ts +8 -5
  25. package/dist/microcomponents.d.ts.map +1 -1
  26. package/dist/microcomponents.js +8 -5
  27. package/dist/microcomponents.min.js +1 -1
  28. package/dist/observables.min.js +1 -1
  29. package/dist/state/formValues.d.ts +70 -0
  30. package/dist/state/formValues.d.ts.map +1 -0
  31. package/dist/state/formValues.js +334 -0
  32. package/dist/state/observables.d.ts +276 -0
  33. package/dist/state/observables.d.ts.map +1 -0
  34. package/dist/state/observables.js +382 -0
  35. package/dist/state/placeholders.d.ts +62 -0
  36. package/dist/state/placeholders.d.ts.map +1 -0
  37. package/dist/state/placeholders.js +181 -0
  38. package/dist/types/mcTypes.d.ts +201 -0
  39. package/dist/types/mcTypes.d.ts.map +1 -0
  40. package/dist/types/mcTypes.js +9 -0
  41. package/dist/utils/helpers.d.ts +195 -0
  42. package/dist/utils/helpers.d.ts.map +1 -0
  43. package/dist/utils/helpers.js +340 -0
  44. package/package.json +96 -62
  45. package/dist/MC.d.ts +0 -31
  46. package/dist/MC.d.ts.map +0 -1
  47. package/dist/MC.js +0 -249
  48. package/dist/MC.ts +0 -613
  49. package/dist/MicroComponents.svg +0 -15
  50. package/dist/elements.d.ts +0 -5
  51. package/dist/elements.d.ts.map +0 -1
  52. package/dist/elements.js +0 -49
  53. package/dist/elements.ts +0 -114
  54. package/dist/events.d.ts +0 -11
  55. package/dist/events.d.ts.map +0 -1
  56. package/dist/events.js +0 -55
  57. package/dist/events.ts +0 -158
  58. package/dist/formValues.d.ts +0 -3
  59. package/dist/formValues.d.ts.map +0 -1
  60. package/dist/formValues.js +0 -156
  61. package/dist/formValues.ts +0 -371
  62. package/dist/helpers.d.ts +0 -15
  63. package/dist/helpers.d.ts.map +0 -1
  64. package/dist/helpers.js +0 -113
  65. package/dist/helpers.ts +0 -219
  66. package/dist/main.mjs +0 -1
  67. package/dist/microcomponents.ts +0 -6
  68. package/dist/observables.d.ts +0 -11
  69. package/dist/observables.d.ts.map +0 -1
  70. package/dist/observables.js +0 -228
  71. package/dist/observables.ts +0 -449
@@ -0,0 +1,70 @@
1
+ /**
2
+ * @module Form Values
3
+ *
4
+ * This module exports a single function {@link formValues} that traverses a DOM element
5
+ * (usually an HTMLFormElement), collecting all input fields and creating a two-way bound
6
+ * observable inside the target object:
7
+ * - When input values change, the observable object updates accordingly.
8
+ * - When the observable property values change, the input field values update in the DOM.
9
+ *
10
+ * **Prerequisites:**
11
+ * - Input fields must have a `name` attribute.
12
+ * - Names must be unique within the form.
13
+ *
14
+ * @example
15
+ * ```ts
16
+ * import { DC } from '@twobirds/microcomponents';
17
+ * import { formValues } from '@twobirds/microcomponents/formValues';
18
+ *
19
+ * class MyMC extends DC {
20
+ * constructor(target: HTMLElement) {
21
+ * super(target);
22
+ * this.target.innerHTML = '<form><input name="textinput" value="" /></form>';
23
+ *
24
+ * // link the form property to the DOM input fields
25
+ * formValues(this, 'form', this.target);
26
+ *
27
+ * // changing the form property updates the DOM automatically
28
+ * this.form.textinput = 'Hello world!';
29
+ * }
30
+ * }
31
+ * ```
32
+ */
33
+ /**
34
+ * Attaches a form-values observable to a target object property, allowing two-way binding
35
+ * between the DOM form controls and the object's state.
36
+ *
37
+ * @param targetObject - The object to which the form values will be attached as an observable.
38
+ * @param propertyName - The name of the property on the target object that will hold the form data.
39
+ * @param dom - The DOM element or shadow root (typically a form) whose inputs should be observed and bound.
40
+ *
41
+ * @example
42
+ * ```html
43
+ * <form id="myForm">
44
+ * <input type="text" name="username" value="">
45
+ * <select name="role"><option value="admin">Admin</option><option value="user">User</option></select>
46
+ * <textarea name="bio"></textarea>
47
+ * <input type="checkbox" name="active">
48
+ * <input type="radio" name="gender" value="male"> Male
49
+ * <input type="radio" name="gender" value="female"> Female
50
+ * </form>
51
+ * ```
52
+ *
53
+ * ```ts
54
+ * import { formValues } from '@twobirds/microcomponents/formValues';
55
+ *
56
+ * const state = {};
57
+ * formValues(state, 'formData', document.getElementById('myForm'));
58
+ *
59
+ * // Read values from the form
60
+ * console.log(state.formData.username); // string
61
+ * console.log(state.formData.role); // "admin" or "user"
62
+ * console.log(state.formData.active); // boolean
63
+ *
64
+ * // Write values to the form (updates DOM automatically)
65
+ * state.formData.username = 'john_doe';
66
+ * state.formData.gender = 'male';
67
+ * ```
68
+ */
69
+ export declare function formValues<T extends object>(targetObject: T, propertyName: keyof T & string, dom: HTMLElement | ShadowRoot): void;
70
+ //# sourceMappingURL=formValues.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"formValues.d.ts","sourceRoot":"","sources":["../../src/state/formValues.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AA4SH;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AACH,wBAAgB,UAAU,CAAC,CAAC,SAAS,MAAM,EAC1C,YAAY,EAAE,CAAC,EACf,YAAY,EAAE,MAAM,CAAC,GAAG,MAAM,EAC9B,GAAG,EAAE,WAAW,GAAG,UAAU,QAuB7B"}
@@ -0,0 +1,334 @@
1
+ /**
2
+ * @module Form Values
3
+ *
4
+ * This module exports a single function {@link formValues} that traverses a DOM element
5
+ * (usually an HTMLFormElement), collecting all input fields and creating a two-way bound
6
+ * observable inside the target object:
7
+ * - When input values change, the observable object updates accordingly.
8
+ * - When the observable property values change, the input field values update in the DOM.
9
+ *
10
+ * **Prerequisites:**
11
+ * - Input fields must have a `name` attribute.
12
+ * - Names must be unique within the form.
13
+ *
14
+ * @example
15
+ * ```ts
16
+ * import { DC } from '@twobirds/microcomponents';
17
+ * import { formValues } from '@twobirds/microcomponents/formValues';
18
+ *
19
+ * class MyMC extends DC {
20
+ * constructor(target: HTMLElement) {
21
+ * super(target);
22
+ * this.target.innerHTML = '<form><input name="textinput" value="" /></form>';
23
+ *
24
+ * // link the form property to the DOM input fields
25
+ * formValues(this, 'form', this.target);
26
+ *
27
+ * // changing the form property updates the DOM automatically
28
+ * this.form.textinput = 'Hello world!';
29
+ * }
30
+ * }
31
+ * ```
32
+ */
33
+ import { DC } from '../core/MC.js';
34
+ import { copyGettersSetters, isBrowser } from '../utils/helpers.js';
35
+ import { observe } from './observables.js';
36
+ /**
37
+ Extracts the lowercase tag name of an element (e.g., 'input', 'select', 'textarea').
38
+ @param inputElement - The HTML element to extract the tag name from.
39
+ @returns The lowercase tag name string.
40
+ */
41
+ const getInputName = (inputElement) => {
42
+ return inputElement.tagName.toLowerCase();
43
+ };
44
+ /**
45
+ Abstract base class for form input value handlers.
46
+ Defines the interface that all handler classes must implement.
47
+ */
48
+ class InputBase {
49
+ /**
50
+ Creates a new InputBase instance.
51
+ @param e - The HTML element being handled (input, select, or textarea).
52
+ */
53
+ constructor(e) { }
54
+ }
55
+ /**
56
+ Handler class specifically for radio button groups.
57
+ Provides synchronized access to the selected value in a group of related radio buttons.
58
+ */
59
+ class RadioHandler extends InputBase {
60
+ #e;
61
+ /**
62
+ Creates a new RadioHandler instance.
63
+ @param e - The first radio button element in the group (used to determine name and form scope).
64
+ */
65
+ constructor(e) {
66
+ super(e);
67
+ this.#e = e;
68
+ }
69
+ /**
70
+ Gets the currently selected value of the radio button group using FormData API.
71
+ @returns The string value of the selected radio button, or null if none is selected.
72
+ */
73
+ get val() {
74
+ if (!this.#e.form)
75
+ return null;
76
+ const ret = new FormData(this.#e.form).get(this.#e.name);
77
+ return ret ?? null;
78
+ }
79
+ /**
80
+ Sets the selected radio button by value.
81
+ Finds and checks the first radio input matching the specified value within the same group.
82
+ @param value - The value to match against radio inputs in this group.
83
+ */
84
+ set val(value) {
85
+ /* v8 ignore next */
86
+ const arr = [
87
+ ...(this.#e.form?.querySelectorAll(`input[type="radio"][name="${this.#e.name}"]`) ?? []),
88
+ ];
89
+ arr.filter((e) => e.getAttribute('value') === value).shift().checked =
90
+ true;
91
+ }
92
+ }
93
+ /**
94
+ * Handler class for checkbhox inputs.
95
+ * Provides synchronized get/set access to the checked state of a single checkbox.
96
+ */
97
+ class CheckboxHandler extends InputBase {
98
+ #e;
99
+ /**
100
+ Creates a new CheckboxHandler instance.
101
+ @param e - The checkbox element to handle.
102
+ */
103
+ constructor(e) {
104
+ super(e);
105
+ this.#e = e;
106
+ }
107
+ /**
108
+ Gets the checked state of the checkbox.
109
+ @returns True if the checkbox is checked, false otherwise.
110
+ */
111
+ get val() {
112
+ return this.#e.checked;
113
+ }
114
+ /**
115
+ Sets the checked state of the checkbox.
116
+ @param value - The boolean value to assign as the new checked state.
117
+ */
118
+ set val(value) {
119
+ this.#e.checked = value;
120
+ }
121
+ }
122
+ /**
123
+ Handler class for standard single-line input fields (text, password, email, etc.).
124
+ Provides synchronized get/set access to the value of an input field.
125
+ */
126
+ class InputHandler extends InputBase {
127
+ #e;
128
+ /**
129
+ * Creates a new InputHandler instance.
130
+ * @param e - The HTML input element to handle.
131
+ */
132
+ constructor(e) {
133
+ super(e);
134
+ this.#e = e;
135
+ }
136
+ /**
137
+ * Gets the current value of the input field.
138
+ * @returns The string content of the input element.
139
+ */
140
+ get val() {
141
+ return this.#e.value;
142
+ }
143
+ /**
144
+ * Sets the value of the input field.
145
+ * @param value - The new string value for the input element.
146
+ */
147
+ set val(value) {
148
+ this.#e.value = value;
149
+ }
150
+ }
151
+ /**
152
+ Handler class for select dropdown elements, supporting both single and multiple selection modes.
153
+ Provides synchronized get/set access to selected option values.
154
+ */
155
+ class SelectHandler extends InputBase {
156
+ #e;
157
+ #multi = false;
158
+ /**
159
+ * Creates a new SelectHandler instance.
160
+ * @param e - The select element to handle.
161
+ */
162
+ constructor(e) {
163
+ super(e);
164
+ this.#e = e;
165
+ this.#multi = e.multiple;
166
+ }
167
+ /**
168
+ * Gets the currently selected option value(s).
169
+ * For single selection, returns a string. For multiple selection, returns an json.stringify()'ied array of strings.
170
+ * @returns A string or JSON.stringify array of strings representing selected values.
171
+ */
172
+ get val() {
173
+ const ret = [];
174
+ // get all options
175
+ [...this.#e.querySelectorAll('option')]
176
+ .filter((o) => o.selected)
177
+ .forEach((o) => {
178
+ ret.push(o.value);
179
+ });
180
+ return this.#multi ? ret.join(',') : ret[0];
181
+ }
182
+ /**
183
+ * Sets the selected option(s) by value.
184
+ * @param value - A string or array of strings representing values to select.
185
+ */
186
+ set val(value) {
187
+ value = typeof value === 'string' ? [value] : value;
188
+ // set all options /* v8 ignore next */
189
+ [...this.#e.querySelectorAll('option')].forEach((o) => {
190
+ if (value.indexOf(o.value) > -1) {
191
+ o.selected = true;
192
+ }
193
+ else {
194
+ o.selected = false;
195
+ }
196
+ });
197
+ }
198
+ }
199
+ /**
200
+ Handler class for textarea elements.
201
+ Provides synchronized get/set access to the multi-line text content of a textarea.
202
+ */
203
+ class TextAreaHandler extends InputBase {
204
+ #e;
205
+ /**
206
+ * Creates a new TextAreaHandler instance.
207
+ * @param e - The textarea element to handle.
208
+ */
209
+ constructor(e) {
210
+ super(e);
211
+ this.#e = e;
212
+ }
213
+ /**
214
+ * Gets the current value of the textarea (defaults to empty string if null/undefined).
215
+ * @returns The string content of the textarea element.
216
+ */
217
+ get val() {
218
+ return this.#e.value || '';
219
+ }
220
+ /**
221
+ * Sets the value of the textarea.
222
+ * @param value - The new string value for the textarea element.
223
+ */
224
+ set val(value) {
225
+ this.#e.value = value;
226
+ }
227
+ }
228
+ /**
229
+ Collects all form controls within a given DOM scope and creates handler objects
230
+ that provide synchronized get/set access to their values.
231
+
232
+ @param e - Optional root DOM element or shadow root to search within; defaults to document if omitted.
233
+ @returns An object where each key is the name attribute of an input, select, or textarea,
234
+ and each value is a proxy-like accessor object with `val` getter/setter methods.
235
+ */
236
+ const getInputHandlers = (e) => {
237
+ const inputList = [...(e || document).querySelectorAll('input,select,textarea')], handlers = {};
238
+ inputList.forEach((input) => {
239
+ const name = input.getAttribute('name');
240
+ if (name) {
241
+ let handler = false;
242
+ switch (getInputName(input)) {
243
+ case 'input':
244
+ switch (input.type) {
245
+ case 'radio':
246
+ handler = new RadioHandler(input);
247
+ break;
248
+ case 'checkbox':
249
+ handler = new CheckboxHandler(input);
250
+ break;
251
+ default:
252
+ handler = new InputHandler(input);
253
+ }
254
+ break;
255
+ case 'select':
256
+ handler = new SelectHandler(input);
257
+ break;
258
+ case 'textarea':
259
+ handler = new TextAreaHandler(input);
260
+ break;
261
+ }
262
+ /* v8 ignore next */
263
+ if (handler) {
264
+ const h = handler;
265
+ DC.add(input, name, h);
266
+ Object.defineProperty(handlers, name, {
267
+ enumerable: true,
268
+ configurable: true,
269
+ get: () => h.val,
270
+ set: (value) => {
271
+ if (h.val !== value) {
272
+ h.val = value;
273
+ }
274
+ },
275
+ });
276
+ }
277
+ }
278
+ });
279
+ return handlers;
280
+ };
281
+ /**
282
+ * Attaches a form-values observable to a target object property, allowing two-way binding
283
+ * between the DOM form controls and the object's state.
284
+ *
285
+ * @param targetObject - The object to which the form values will be attached as an observable.
286
+ * @param propertyName - The name of the property on the target object that will hold the form data.
287
+ * @param dom - The DOM element or shadow root (typically a form) whose inputs should be observed and bound.
288
+ *
289
+ * @example
290
+ * ```html
291
+ * <form id="myForm">
292
+ * <input type="text" name="username" value="">
293
+ * <select name="role"><option value="admin">Admin</option><option value="user">User</option></select>
294
+ * <textarea name="bio"></textarea>
295
+ * <input type="checkbox" name="active">
296
+ * <input type="radio" name="gender" value="male"> Male
297
+ * <input type="radio" name="gender" value="female"> Female
298
+ * </form>
299
+ * ```
300
+ *
301
+ * ```ts
302
+ * import { formValues } from '@twobirds/microcomponents/formValues';
303
+ *
304
+ * const state = {};
305
+ * formValues(state, 'formData', document.getElementById('myForm'));
306
+ *
307
+ * // Read values from the form
308
+ * console.log(state.formData.username); // string
309
+ * console.log(state.formData.role); // "admin" or "user"
310
+ * console.log(state.formData.active); // boolean
311
+ *
312
+ * // Write values to the form (updates DOM automatically)
313
+ * state.formData.username = 'john_doe';
314
+ * state.formData.gender = 'male';
315
+ * ```
316
+ */
317
+ export function formValues(targetObject, propertyName, dom) {
318
+ /* v8 ignore next */
319
+ if (!isBrowser())
320
+ return; // DOM does not exist on the server side
321
+ const handlers = getInputHandlers(dom);
322
+ const source = targetObject;
323
+ source[propertyName] = Object.assign({}, handlers);
324
+ observe(targetObject, propertyName);
325
+ // After observe(), the property getter returns the live observable wrapper.
326
+ const observable = source[propertyName];
327
+ copyGettersSetters(handlers, observable);
328
+ //Object.seal(source[propertyName]); // object structure may not be changed from now on
329
+ [...dom.querySelectorAll('input, textarea, select')].forEach((input) => {
330
+ input.addEventListener('input', () => {
331
+ observable.notify();
332
+ });
333
+ });
334
+ }
@@ -0,0 +1,276 @@
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.
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
21
+
22
+ javascript
23
+ ```js
24
+ let person = { name: "John", count: 0 };
25
+
26
+ observe( person ); // all properties inside are now observables
27
+
28
+ person.name.subscribe( (name) => {
29
+ console.log("Name changed:", name);
30
+ });
31
+
32
+ person.count.subscribe( (count) => {
33
+ console.log("Count changed:", count);
34
+ });
35
+
36
+ person.name = "Jane"; // > Name changed: Jane
37
+ person.count += 10; // > Count changed: 10
38
+ ```
39
+
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
+ });
53
+
54
+ obj.data.name = "Jane"; // > { data: { name: "Jane", count: 0 } }
55
+ obj.data.count += 10; // > { data: { name: "Jane", count: 10 } }
56
+ ```
57
+
58
+ ### Debounced Notifications
59
+
60
+ To prevent excessive updates, notifications are debounced briefly:
61
+
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
+ }
68
+ ```
69
+
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
78
+
79
+ HTML
80
+ ```html
81
+ <div class="model {visibility}">
82
+ Hello {name}! You have {count} messages.
83
+ </div>
84
+ ```
85
+
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
+ */
103
+ /**
104
+ A callback invoked with the current observable value when notified.
105
+
106
+ @example
107
+ ```ts
108
+ const cb: Callback<number> = (v) => console.log('new value:', v);
109
+ observable.subscribe(cb);
110
+ ```
111
+ */
112
+ export type Callback<T = unknown> = (value: T) => void;
113
+ /**
114
+ Interface representing an observable object that supports subscription, notification, and DOM binding.
115
+
116
+ Observable objects track registered callbacks and invoke them when their internal state changes.
117
+ They can also be bound to a DOM target to automatically update placeholder values in the view layer.
118
+
119
+ @example
120
+ ```ts
121
+ const state = observe({ count: 0 });
122
+ state.count.subscribe((v) => console.log('count is now', v));
123
+ state.count++; // logs: count is now 1
124
+ ```
125
+ */
126
+ export type Observable<T = unknown> = T & {
127
+ subscribe(callback: Callback<T>): Observable<T>;
128
+ unsubscribe(callback: Callback<T>): Observable<T>;
129
+ notify(enable?: boolean): Observable<T>;
130
+ bind(target: HTMLElement | DocumentFragment | ShadowRoot): Observable<T>;
131
+ /** @hidden */
132
+ callbacks: Array<Function>;
133
+ };
134
+ /**
135
+ Every property of `T` mapped to an observable of its value.
136
+
137
+ KNOWN LIMIT: the runtime property *setter* accepts a plain value
138
+ (`state.count = 5` re-wraps it), but this mapped type cannot say so — mapped
139
+ types cannot declare divergent read/write (get/set) types, so the wrapper type
140
+ governs writes too and plain assignment to a primitive observable is rejected.
141
+ Tracked upstream as https://github.com/microsoft/TypeScript/issues/43826
142
+ (variant accessors for mapped types); when it lands, the write type here
143
+ becomes `T[K] | Observable<T[K]>` and the limit disappears.
144
+
145
+ Until then, use one of the supported write paths:
146
+ - the `observed` variant, a single container whose `.value` gives
147
+ typed reads and writes today (`state.value.count = 5`, deep
148
+ writes included) — the limitation never applies;
149
+ - `state.count++` (compiles today; TS does not re-check the `++` write-back);
150
+ - the schema-first `observe<S>()` overload with a hand-declared get/set
151
+ interface, where the divergence IS expressible (and plain `=` becomes
152
+ legal) — see {@link Unobserved}.
153
+
154
+ @example
155
+ ```ts
156
+ type State = Observed<{ count: number; name: string }>;
157
+ // { count: Observable<number>, name: Observable<string> }
158
+ ```
159
+ */
160
+ export type Observed<T> = {
161
+ [K in keyof T]: Observable<T[K]>;
162
+ };
163
+ /**
164
+ Only `K` of `T` mapped to an observable; other properties keep their type.
165
+ Carries the same write limitation as {@link Observed} for the mapped key.
166
+
167
+ @example
168
+ ```ts
169
+ type State = ObservedKey<{ count: number; label: string }, 'count'>;
170
+ // { count: Observable<number>, label: string }
171
+ ```
172
+ */
173
+ export type ObservedKey<T, K extends keyof T> = {
174
+ [P in keyof T]: P extends K ? Observable<T[P]> : T[P];
175
+ };
176
+ /**
177
+ The inverse of a hand-declared observable state interface: strips the
178
+ `Observable` wrapper from every property, yielding the plain seed shape.
179
+ `Unobserved<CounterState>` → `{ count: number }`.
180
+
181
+ Extraction goes through the `subscribe` callback parameter, so it works for
182
+ both accessor-declared members (`get count(): Observable<number>`) and plain
183
+ `Observable<T>` properties; non-observable members pass through unchanged.
184
+
185
+ Used by the schema-first `observe<S>()` overload to validate the seed object
186
+ against the declared interface — see that overload's example.
187
+
188
+ @example
189
+ ```ts
190
+ interface CounterState {
191
+ get count(): Observable<number>;
192
+ set count(v: number);
193
+ }
194
+ type Seed = Unobserved<CounterState>; // { count: number }
195
+ const state = observe<CounterState>({ count: 0 }); // seed validated against schema
196
+ ```
197
+ */
198
+ export type Unobserved<S> = {
199
+ [K in keyof S]: S[K] extends {
200
+ subscribe(callback: (value: infer V) => void): unknown;
201
+ } ? V : S[K];
202
+ };
203
+ /**
204
+ Whether a value is already an observable wrapper — `subscribe` is reachable on its prototype chain.
205
+ Used by {@link observe} to skip re-wrapping and to detect the "not yet observed" state on reassignment.
206
+
207
+ @example
208
+ ```ts
209
+ const state = observe({ count: 0 });
210
+ isAlreadyObserved(state.count); // true
211
+ isAlreadyObserved(42); // false
212
+ ```
213
+ */
214
+ export declare function isAlreadyObserved(value: unknown): boolean;
215
+ /**
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.
221
+
222
+ There are two sides to using it — pick per call site:
223
+
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.
230
+
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>`.
234
+
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:
247
+ ```ts
248
+ class Counter {
249
+ clicks: Observable<number> = 0 as unknown as Observable<number>;
250
+
251
+ constructor() {
252
+ observe(this, 'clicks');
253
+ }
254
+
255
+ bump() {
256
+ this.clicks++; // plain arithmetic — the declaration carries the type
257
+ }
258
+ }
259
+ ```
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
+ */
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;
276
+ //# sourceMappingURL=observables.d.ts.map
@@ -0,0 +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"}