@twobirds/microcomponents 0.1.9 → 0.1.11

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.
@@ -1,191 +1,173 @@
1
1
  'use strict';
2
2
 
3
- import {
4
- LooseObject,
5
- debounce,
6
- deepEqual,
7
- flattenObject,
8
- copyGettersSetters,
9
- } from './helpers.js';
3
+ import { LooseObject, debounce, deepEqual, flattenObject, copyGettersSetters } from './helpers.js';
10
4
 
11
5
  import { DC, HTMLMcElement, trigger } from './MC.js';
12
6
 
13
7
  function isObservableObject(value: any): boolean {
14
- return value != null && typeof value === 'object';
8
+ return value != null && typeof value === 'object';
15
9
  }
16
10
 
17
11
  type Callback = (value: any) => void;
18
12
 
19
13
  function getPlaceholders(target: HTMLElement): string[] {
20
- let placeholders: Set<string> = new Set(),
21
- regEx = /\{[^\{\}]*\}/g;
22
-
23
- // scan attributes
24
- ( !!target.getAttributeNames ? target.getAttributeNames() : [] )
25
- .filter((attr) => target.getAttribute(attr)?.match(regEx)) // extract placeholder(s)
26
- .forEach((attr) => {
27
- let placeholders: Set<string> = new Set(target.getAttribute(attr)?.match(regEx));
28
- placeholders.forEach((placeholder) =>
29
- placeholders.add(placeholder.replace(/[\{\}]/g, ''))
30
- );
31
- });
32
-
33
- // scan children text nodes
34
- [...target.childNodes]
35
- .filter((childNode) => {
36
- return (
37
- // is text node and has placeholders
38
- childNode.nodeType === 3 &&
39
- ((childNode as any).nodeValue || '').match(regEx)
40
- );
41
- })
42
- .forEach((childNode) => {
43
- ((childNode as any).nodeValue || '')
44
- .match(regEx)
45
- .forEach((placeholder: string) => {
46
- placeholders.add(placeholder.replace(/[\{\}]/g, ''));
47
- });
48
- });
49
-
50
- // extract {} and return result
51
- return [...placeholders].map((placeholder) =>
52
- placeholder.replace(/[\{\}]/g, '')
53
- );
14
+ let placeholders: Set<string> = new Set(),
15
+ regEx = /\{[^\{\}]*\}/g;
16
+
17
+ // scan attributes
18
+ (target.getAttributeNames ? target.getAttributeNames() : [])
19
+ .filter((attr) => target.getAttribute(attr)?.match(regEx)) // extract placeholder(s)
20
+ .forEach((attr) => {
21
+ let placeholders: Set<string> = new Set(target.getAttribute(attr)?.match(regEx));
22
+ placeholders.forEach((placeholder) =>
23
+ placeholders.add(placeholder.replace(/[\{\}]/g, ''))
24
+ );
25
+ });
26
+
27
+ // scan children text nodes
28
+ [...target.childNodes]
29
+ .filter((childNode) => {
30
+ return (
31
+ // is text node and has placeholders
32
+ childNode.nodeType === 3 && ((childNode as any).nodeValue || '').match(regEx)
33
+ );
34
+ })
35
+ .forEach((childNode) => {
36
+ ((childNode as any).nodeValue || '').match(regEx).forEach((placeholder: string) => {
37
+ placeholders.add(placeholder.replace(/[\{\}]/g, ''));
38
+ });
39
+ });
40
+
41
+ // extract {} and return result
42
+ return [...placeholders].map((placeholder) => placeholder.replace(/[\{\}]/g, ''));
54
43
  }
55
44
 
56
45
  const inputs = [HTMLInputElement, HTMLSelectElement, HTMLTextAreaElement];
57
46
 
58
47
  class Placeholders extends DC {
59
- data: LooseObject = {};
60
- oldDisplay?: string = '';
61
-
62
- constructor(target: HTMLElement) {
63
- super(target);
64
- getPlaceholders(target).forEach((property) => (this.data[property] = ''));
65
- observe(this);
66
- this.#attachCallbacks();
67
- }
68
-
69
- #attachCallbacks() {
70
- let target = this.target,
71
- hide: boolean = false;
72
-
73
- //console.log('attachCallbacks', this.target);
74
-
75
- // scan attributes
76
- ( !!target.getAttributeNames ? target.getAttributeNames() : [] )
77
- .filter((attr: string) =>
78
- target.getAttribute(attr)?.match(/\{[^\{\}]*\}/g)
79
- ) // extract placeholder(s)
80
- .forEach((attr: string) => {
81
- let placeholders: Set<string> = new Set(
82
- target.getAttribute(attr)?.match(/\{[^\{\}]*\}/g)
83
- );
84
- // attach callback
85
- this.data.observe(
86
- ((template: string) => (data: LooseObject) => {
87
- let result: string = template;
88
- placeholders.forEach((placeholder) => {
89
- result = result.replace(
90
- placeholder,
91
- data[placeholder.replace(/[\{\}]/g, '')]
92
- );
93
- });
94
- target.setAttribute(attr, result);
95
- //console.log('cb setAttribute()', data, template, attr, result);
96
- })(target.getAttribute(attr))
97
- );
98
- });
99
-
100
- // scan children text nodes
101
- [...target.childNodes]
102
- .filter((childNode) => {
103
- return (
104
- // is text node and has placeholders
105
- childNode.nodeType === 3 &&
106
- ((childNode as Node).nodeValue || '').match(/\{[^\{\}]*\}/g)
107
- );
108
- })
109
- .forEach((childNode) => {
110
- hide = true;
111
- let placeholders: Set<string> = new Set(
112
- childNode.nodeValue.match(/\{[^\{\}]*\}/g)
113
- );
114
-
115
- // attach callback
116
- this.data.observe(
117
- ((template: string) => (data: LooseObject) => {
118
- let result: string = template;
119
- //console.log('text node "', template, '" callback data:', data);
120
- placeholders.forEach((placeholder) => {
121
- result = result.replace(
122
- placeholder,
123
- data[placeholder.replace(/[\{\}]/g, '')]
124
- );
125
- });
126
-
127
- if (!!this.oldDisplay) {
128
- target.style = this.oldDisplay;
129
- } else {
130
- target.removeAttribute('style');
131
- }
132
- delete this.oldDisplay;
133
-
134
- childNode.nodeValue = result;
135
- })(childNode.nodeValue)
136
- );
137
-
138
- // hide for now
139
- this.oldDisplay = target.getAttribute('style') || ''; // TBD allow null to indicate there was no old style attribute
140
-
141
- if (
142
- hide &&
143
- !inputs.includes((target as any).constructor) &&
144
- target.tagName !== 'FIELDSET'
145
- ) {
146
- target.style.display = 'none';
147
- }
148
- });
149
- }
150
-
151
- onData(ev: CustomEvent) {
152
- let data: LooseObject = {};
153
- Object.keys(ev.detail.data).forEach((key) => {
154
- if (this.data.hasOwnProperty(key)) data[key] = ev.detail.data[key];
155
- });
156
- //console.log( 'Placeholder onData', ev, data );
157
- if (JSON.stringify(data) !== JSON.stringify(this.data))
158
- Object.assign(this.data, data); // will trigger callbacks
159
- }
48
+ data: LooseObject = {};
49
+ oldDisplay?: string = '';
50
+
51
+ constructor(target: HTMLElement) {
52
+ super(target);
53
+ getPlaceholders(target).forEach((property) => (this.data[property] = ''));
54
+ observe(this);
55
+ this.#attachCallbacks();
56
+ }
57
+
58
+ #attachCallbacks() {
59
+ let target = this.target,
60
+ hide: boolean = false;
61
+
62
+ //console.log('attachCallbacks', this.target);
63
+
64
+ // scan attributes
65
+ (target.getAttributeNames ? target.getAttributeNames() : [])
66
+ .filter((attr: string) => target.getAttribute(attr)?.match(/\{[^\{\}]*\}/g)) // extract placeholder(s)
67
+ .forEach((attr: string) => {
68
+ let placeholders: Set<string> = new Set(
69
+ target.getAttribute(attr)?.match(/\{[^\{\}]*\}/g)
70
+ );
71
+ // attach callback
72
+ this.data.observe(
73
+ ((template: string) => (data: LooseObject) => {
74
+ let result: string = template;
75
+ placeholders.forEach((placeholder) => {
76
+ result = result.replace(
77
+ placeholder,
78
+ data[placeholder.replace(/[\{\}]/g, '')]
79
+ );
80
+ });
81
+ target.setAttribute(attr, result);
82
+ //console.log('cb setAttribute()', data, template, attr, result);
83
+ })(target.getAttribute(attr))
84
+ );
85
+ });
86
+
87
+ // scan children text nodes
88
+ [...target.childNodes]
89
+ .filter((childNode) => {
90
+ return (
91
+ // is text node and has placeholders
92
+ childNode.nodeType === 3 &&
93
+ ((childNode as Node).nodeValue || '').match(/\{[^\{\}]*\}/g)
94
+ );
95
+ })
96
+ .forEach((childNode) => {
97
+ hide = true;
98
+ let placeholders: Set<string> = new Set(childNode.nodeValue.match(/\{[^\{\}]*\}/g));
99
+
100
+ // attach callback
101
+ this.data.observe(
102
+ ((template: string) => (data: LooseObject) => {
103
+ let result: string = template;
104
+ //console.log('text node "', template, '" callback data:', data);
105
+ placeholders.forEach((placeholder) => {
106
+ result = result.replace(
107
+ placeholder,
108
+ data[placeholder.replace(/[\{\}]/g, '')]
109
+ );
110
+ });
111
+
112
+ if (this.oldDisplay) {
113
+ target.style = this.oldDisplay;
114
+ } else {
115
+ target.removeAttribute('style');
116
+ }
117
+ delete this.oldDisplay;
118
+
119
+ childNode.nodeValue = result;
120
+ })(childNode.nodeValue)
121
+ );
122
+
123
+ // hide for now
124
+ this.oldDisplay = target.getAttribute('style') || ''; // TBD allow null to indicate there was no old style attribute
125
+
126
+ if (
127
+ hide &&
128
+ !inputs.includes((target as any).constructor) &&
129
+ target.tagName !== 'FIELDSET'
130
+ ) {
131
+ target.style.display = 'none';
132
+ }
133
+ });
134
+ }
135
+
136
+ onData(ev: CustomEvent) {
137
+ let data: LooseObject = {};
138
+ Object.keys(ev.detail.data).forEach((key) => {
139
+ if (this.data.hasOwnProperty(key)) data[key] = ev.detail.data[key];
140
+ });
141
+ //console.log( 'Placeholder onData', ev, data );
142
+ if (JSON.stringify(data) !== JSON.stringify(this.data)) Object.assign(this.data, data); // will trigger callbacks
143
+ }
160
144
  }
161
145
 
162
146
  function getPlaceholderElements(
163
- target: HTMLElement | DocumentFragment | ShadowRoot
147
+ target: HTMLElement | DocumentFragment | ShadowRoot
164
148
  ): HTMLElement[] {
165
- let elements: Array<Node> = [target]
166
- .concat([...(target.querySelectorAll('*') as unknown as HTMLElement[])])
167
- .filter((e) => !!getPlaceholders(e as HTMLElement).length)
168
- .filter((e) => (e as HTMLElement)?.tagName !== 'STYLE')
169
- .reverse(); // to start with inner elements
170
- //console.log('getPlaceholderElements', elements);
171
- return elements as HTMLElement[];
149
+ let elements: Array<Node> = [target]
150
+ .concat([...(target.querySelectorAll('*') as unknown as HTMLElement[])])
151
+ .filter((e) => !!getPlaceholders(e as HTMLElement).length)
152
+ .filter((e) => (e as HTMLElement)?.tagName !== 'STYLE')
153
+ .reverse(); // to start with inner elements
154
+ //console.log('getPlaceholderElements', elements);
155
+ return elements as HTMLElement[];
172
156
  }
173
157
 
174
- function makePlaceholders(
175
- element: HTMLElement | DocumentFragment | ShadowRoot
176
- ): void {
177
- //console.log('makePlaceholders', element);
178
- getPlaceholderElements(element).forEach((e: HTMLElement) => {
179
- if ((e as HTMLMcElement)?._mc?._placeholders) return;
180
- DC.add(e, '_placeholders', new Placeholders(e));
181
- });
158
+ function makePlaceholders(element: HTMLElement | DocumentFragment | ShadowRoot): void {
159
+ //console.log('makePlaceholders', element);
160
+ getPlaceholderElements(element).forEach((e: HTMLElement) => {
161
+ if ((e as HTMLMcElement)?._mc?._placeholders) return;
162
+ DC.add(e, '_placeholders', new Placeholders(e));
163
+ });
182
164
  }
183
165
 
184
166
  export type Observable = {
185
- observe: (cb: Callback) => void;
186
- notify: () => void;
187
- bind: (target: HTMLElement | DocumentFragment | ShadowRoot ) => void;
188
- callbacks: Array<Function>;
167
+ observe: (cb: Callback) => void;
168
+ notify: () => void;
169
+ bind: (target: HTMLElement | DocumentFragment | ShadowRoot) => void;
170
+ callbacks: Array<Function>;
189
171
  };
190
172
 
191
173
  type Class<T> = new (...args: any[]) => T;
@@ -194,223 +176,207 @@ type Class<T> = new (...args: any[]) => T;
194
176
  const classRepo: LooseObject = {};
195
177
 
196
178
  // creates a new observable class wrapper
197
- function makeObservable(val: any): Class<typeof val>{
198
-
199
- const Constructor = val.constructor,
200
- ConstructorName: string = Constructor.name;
201
-
202
- let ObservableClass = class extends Constructor {
203
- #notify: boolean = true;
204
- #callbacks: Array<Function> = [];
205
-
206
- constructor(val: any) {
207
- super(val);
208
- (this.constructor as LooseObject).callbacks = [];
209
- if (isObservableObject(val)) {
210
- // you cannot bind native values to the dom
211
- Object.getOwnPropertyNames(val).forEach((key) => {
212
- this[key] = val[key];
213
- });
214
- }
215
- }
216
-
217
- get callbacks(): Array<Function> {
218
- return this.#callbacks
219
- }
220
-
221
- set callbacks( callbacks: Array<Function> ){
222
- this.#callbacks = callbacks
223
- }
224
-
225
- observe( callback: Function ) {
226
- //console.log('observe');
227
- this.#callbacks.push( callback );
228
- return this;
229
- }
230
-
231
- bind(target: HTMLElement | DocumentFragment | ShadowRoot) {
232
- const iso = isObservableObject(this.valueOf());
233
- //console.log('bind', iso, target);
234
-
235
- if (iso) {
236
- makePlaceholders(target);
237
- const placeholders: unknown =
238
- [
239
- target,
240
- ...target.querySelectorAll('[data-mc]'),
241
- ]
242
- .filter(
243
- (e) => !!(e as LooseObject)?._mc?._placeholders
244
- );
245
-
246
- //console.log('placeholders:', target, placeholders);
247
-
248
- const func = function callback(data: LooseObject) {
249
- // avoid bubbling for speed
250
- let flattenedData = flattenObject(data);
251
- //console.log('inside placeholder callback', target, flattenedData);
252
- (placeholders as Node[]).forEach((node) => {
253
- // console.log('trigger callback', e);
254
- if (!node) return;
255
- trigger(node as HTMLMcElement, 'data', flattenedData);
256
- });
257
- };
258
- (this as any).#callbacks.push(func);
259
- this.notify();
260
- } else {
261
- console.warn(
262
- 'IGNORED - cannot bind a non-Object to the dom, IS:',
263
- typeof this.valueOf(),
264
- this
265
- );
266
- }
267
-
268
- return this;
269
- }
270
-
271
- notify(notify?: boolean) {
272
- let data =
273
- isObservableObject(this.valueOf()) &&
274
- this.valueOf() instanceof Array === false
275
- ? structuredClone(Object.assign({}, this))
276
- : this.valueOf();
277
-
278
- if (notify !== undefined) {
279
- this.#notify = notify;
280
- // return;
281
- }
282
-
283
- //if (this.#notify) console.log('notify', this, data);
284
- (this as any).#callbacks.forEach((f: Function) => {
285
- // console.log(
286
- // (this.constructor as any).callbacks,
287
- // 'notify: exec callback',
288
- // f,
289
- // data
290
- // );
291
- f(data);
292
- });
293
-
294
- return this;
295
- }
296
- };
297
-
298
- (classRepo as any)[ConstructorName] = ObservableClass;
299
-
300
- // console.log( 'classRepo:', classRepo );
301
-
302
- return ObservableClass;
179
+ function makeObservable(val: any): Class<typeof val> {
180
+ const Constructor = val.constructor,
181
+ ConstructorName: string = Constructor.name;
182
+
183
+ let ObservableClass = class extends Constructor {
184
+ #notify: boolean = true;
185
+ #callbacks: Array<Function> = [];
186
+
187
+ constructor(val: any) {
188
+ super(val);
189
+ (this.constructor as LooseObject).callbacks = [];
190
+ if (isObservableObject(val)) {
191
+ // you cannot bind native values to the dom
192
+ Object.getOwnPropertyNames(val).forEach((key) => {
193
+ this[key] = val[key];
194
+ });
195
+ }
196
+ }
197
+
198
+ get callbacks(): Array<Function> {
199
+ return this.#callbacks;
200
+ }
201
+
202
+ set callbacks(callbacks: Array<Function>) {
203
+ this.#callbacks = callbacks;
204
+ }
205
+
206
+ observe(callback: Function) {
207
+ //console.log('observe');
208
+ this.#callbacks.push(callback);
209
+ return this;
210
+ }
211
+
212
+ bind(target: HTMLElement | DocumentFragment | ShadowRoot) {
213
+ const iso = isObservableObject(this.valueOf());
214
+ //console.log('bind', iso, target);
215
+
216
+ if (iso) {
217
+ makePlaceholders(target);
218
+ const placeholders: unknown = [
219
+ target,
220
+ ...target.querySelectorAll('[data-mc]'),
221
+ ].filter((e) => !!(e as LooseObject)?._mc?._placeholders);
222
+
223
+ //console.log('placeholders:', target, placeholders);
224
+
225
+ const func = function callback(data: LooseObject) {
226
+ // avoid bubbling for speed
227
+ let flattenedData = flattenObject(data);
228
+ //console.log('inside placeholder callback', target, flattenedData);
229
+ (placeholders as Node[]).forEach((node) => {
230
+ // console.log('trigger callback', e);
231
+ if (!node) return;
232
+ trigger(node as HTMLMcElement, 'data', flattenedData);
233
+ });
234
+ };
235
+ (this as any).#callbacks.push(func);
236
+ this.notify();
237
+ } else {
238
+ console.warn(
239
+ 'IGNORED - cannot bind a non-Object to the dom, IS:',
240
+ typeof this.valueOf(),
241
+ this
242
+ );
243
+ }
244
+
245
+ return this;
246
+ }
247
+
248
+ notify(notify?: boolean) {
249
+ let data =
250
+ isObservableObject(this.valueOf()) && this.valueOf() instanceof Array === false
251
+ ? structuredClone(Object.assign({}, this))
252
+ : this.valueOf();
253
+
254
+ if (notify !== undefined) {
255
+ this.#notify = notify;
256
+ // return;
257
+ }
258
+
259
+ if (this.#notify) {
260
+ (this as any).#callbacks.forEach((f: Function) => {
261
+ f(data);
262
+ });
263
+ }
264
+
265
+ return this;
266
+ }
267
+ };
268
+
269
+ (classRepo as any)[ConstructorName] = ObservableClass;
270
+
271
+ // console.log( 'classRepo:', classRepo );
272
+
273
+ return ObservableClass;
303
274
  }
304
275
 
305
- function getConstructor( val: any ): Class<typeof val> {
306
- let key: string = val.constructor.name;
307
- return classRepo[key] || makeObservable( val );
276
+ function getConstructor(val: any): Class<typeof val> {
277
+ let key: string = val.constructor.name;
278
+ return classRepo[key] || makeObservable(val);
308
279
  }
309
280
 
310
- export const observe = (
311
- target: LooseObject,
312
- propertyName?: string
313
- ): LooseObject => {
314
- let keys = !propertyName
315
- ? [...Object.getOwnPropertyNames(target)]
316
- : [propertyName];
317
-
318
- keys.forEach((key) => {
319
- if (
320
- target.hasOwnProperty(key)
321
- && typeof target[key] !== 'function'
322
- && !target?.[key]?.constructor?.prototype?.observe
323
- ) {
324
-
325
- // console.log( 'co propname', key, typeof target[key], target[key] );
326
- if (key[0] === '_' || target?.[key] === undefined) return target;
327
- let val: any = target[key];
328
-
329
- let o = new ( getConstructor(val) as Class<typeof val> )( val );
330
-
331
- Object.defineProperty(target, key, {
332
- enumerable: true,
333
- get() {
334
- if (isObservableObject(o.valueOf())) {
335
- let check = structuredClone(Object.assign({}, o));
336
- setTimeout(
337
- debounce(function checkChanges() {
338
- let state = structuredClone(Object.assign({}, o));
339
- //console.log( 'check changes', state, check );
340
- if (!deepEqual(state, check)) {
341
- //console.log( '-> not equal!' );
342
- o.notify();
343
- }
344
- }, 0),
345
- 0
346
- );
347
- }
348
- return o;
349
- },
350
- set(val: any) {
351
- //console.log( 'SET:', isObservableObject( val.valueOf() ), 'set:', val, 'to', isObservableObject( o.valueOf() ), o, );
352
- if (typeof val === typeof o.valueOf()) {
353
- let iso =
354
- isObservableObject(o.valueOf()) &&
355
- o.valueOf() instanceof Array === false;
356
- //console.log('target iso:', iso);
357
- if (iso) {
358
- let old = structuredClone(Object.assign({}, o));
359
- //val = structuredClone(Object.assign({}, val));
360
-
361
- //let callbacks = (o.constructor as LooseObject).callbacks;
362
- if (o?.constructor?.prototype?.observe === undefined ) {
363
- o = new ( getConstructor(val) as Class<typeof val> )( structuredClone(Object.assign({}, val)) );
364
- copyGettersSetters(val, o);
365
- o.seal();
366
- }
367
- Object.assign(o, val);
368
-
369
- // console.log('after set o=', o, 'check', old);
370
- // (o.constructor as LooseObject).callbacks = callbacks;
371
-
372
- setTimeout(
373
- // check for changes
374
- debounce(function checkChanges() {
375
- let state = structuredClone(Object.assign({}, o));
376
- //console.log( 'check changes', state, check );
377
- if (!deepEqual(state, old)) {
378
- //console.log( '-> not equal!' );
379
- o.notify();
380
- }
381
- }, 0),
382
- 0
383
- );
384
- } else {
385
- // simple native var
386
- let old = o.valueOf(),
387
- callbacks = (o as Observable).callbacks;
388
-
389
- // console.log( 'sO', o, o.valueOf(), 'val', val );
390
- if (val !== old) {
391
- // console.log( '-> sO new val', val );
392
- o = new ( getConstructor(val) as Class<typeof val> )( val );
393
- (o as Observable).callbacks = callbacks;
394
- o.notify();
395
- }
396
- }
397
- } else {
398
- console.warn(
399
- 'IGNORED - cannot change observable types, IS:',
400
- typeof o.valueOf(),
401
- o,
402
- '- CANNOT BECOME:',
403
- typeof val
404
- );
405
- }
406
- },
407
- });
408
-
409
- // replace old property with proxy (observable)
410
- target[key] = val;
411
- };
412
-
413
- });
414
-
415
- return target;
281
+ export const observe = (target: LooseObject, propertyName?: string): LooseObject => {
282
+ let keys = !propertyName ? [...Object.getOwnPropertyNames(target)] : [propertyName];
283
+
284
+ keys.forEach((key) => {
285
+ if (
286
+ target.hasOwnProperty(key) &&
287
+ typeof target[key] !== 'function' &&
288
+ !target?.[key]?.constructor?.prototype?.observe
289
+ ) {
290
+ // console.log( 'co propname', key, typeof target[key], target[key] );
291
+ if (key[0] === '_' || target?.[key] === undefined) return target;
292
+ let val: any = target[key];
293
+
294
+ let o = new (getConstructor(val) as Class<typeof val>)(val);
295
+
296
+ Object.defineProperty(target, key, {
297
+ enumerable: true,
298
+ get() {
299
+ if (isObservableObject(o.valueOf())) {
300
+ let check = structuredClone(Object.assign({}, o));
301
+ setTimeout(
302
+ debounce(function checkChanges() {
303
+ let state = structuredClone(Object.assign({}, o));
304
+ //console.log( 'check changes', state, check );
305
+ if (!deepEqual(state, check)) {
306
+ //console.log( '-> not equal!' );
307
+ o.notify();
308
+ }
309
+ }, 0),
310
+ 0
311
+ );
312
+ }
313
+ return o;
314
+ },
315
+ set(val: any) {
316
+ //console.log( 'SET:', isObservableObject( val.valueOf() ), 'set:', val, 'to', isObservableObject( o.valueOf() ), o, );
317
+ if (typeof val === typeof o.valueOf()) {
318
+ let iso =
319
+ isObservableObject(o.valueOf()) &&
320
+ o.valueOf() instanceof Array === false;
321
+ //console.log('target iso:', iso);
322
+ if (iso) {
323
+ let old = structuredClone(Object.assign({}, o));
324
+ //val = structuredClone(Object.assign({}, val));
325
+
326
+ //let callbacks = (o.constructor as LooseObject).callbacks;
327
+ if (o?.constructor?.prototype?.observe === undefined) {
328
+ o = new (getConstructor(val) as Class<typeof val>)(
329
+ structuredClone(Object.assign({}, val))
330
+ );
331
+ copyGettersSetters(val, o);
332
+ o.seal();
333
+ }
334
+ Object.assign(o, val);
335
+
336
+ // console.log('after set o=', o, 'check', old);
337
+ // (o.constructor as LooseObject).callbacks = callbacks;
338
+
339
+ setTimeout(
340
+ // check for changes
341
+ debounce(function checkChanges() {
342
+ let state = structuredClone(Object.assign({}, o));
343
+ //console.log( 'check changes' );
344
+ if (!deepEqual(state, old)) {
345
+ //console.log('-> not equal:', state, old);
346
+ o.notify();
347
+ }
348
+ }, 0),
349
+ 0
350
+ );
351
+ } else {
352
+ // simple native var
353
+ let old = o.valueOf(),
354
+ callbacks = (o as Observable).callbacks;
355
+
356
+ // console.log( 'sO', o, o.valueOf(), 'val', val );
357
+ if (val !== old) {
358
+ // console.log( '-> sO new val', val );
359
+ o = new (getConstructor(val) as Class<typeof val>)(val);
360
+ (o as Observable).callbacks = callbacks;
361
+ o.notify();
362
+ }
363
+ }
364
+ } else {
365
+ console.warn(
366
+ 'IGNORED - cannot change observable types, IS:',
367
+ typeof o.valueOf(),
368
+ o,
369
+ '- CANNOT BECOME:',
370
+ typeof val
371
+ );
372
+ }
373
+ },
374
+ });
375
+
376
+ // replace old property with proxy (observable)
377
+ target[key] = val;
378
+ }
379
+ });
380
+
381
+ return target;
416
382
  };