@oscarpalmer/atoms 0.21.0 → 0.22.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/js/index.js CHANGED
@@ -310,14 +310,17 @@ var _getDiffs = function(first, second, prefix) {
310
310
  const to = second?.[key];
311
311
  if (!Object.is(from, to)) {
312
312
  const prefixed = _getKey(prefix, key);
313
- changes.push({
313
+ const change = {
314
314
  from,
315
315
  to,
316
316
  key: prefixed
317
- });
318
- if (isArrayOrPlainObject(from) && isArrayOrPlainObject(to)) {
319
- changes.push(..._getDiffs(from, to, prefixed));
317
+ };
318
+ const nested = isArrayOrPlainObject(from) || isArrayOrPlainObject(to);
319
+ const diffs = nested ? _getDiffs(from, to, prefixed) : [];
320
+ if (!nested || nested && diffs.length > 0) {
321
+ changes.push(change);
320
322
  }
323
+ changes.push(...diffs);
321
324
  }
322
325
  checked.add(key);
323
326
  }
@@ -449,7 +452,17 @@ var _createProxy = function(existing, value2) {
449
452
  return property === "$" ? manager : Reflect.get(target, property);
450
453
  },
451
454
  set(target, property, value3) {
452
- return property === "$" || Reflect.set(target, property, _createProxy(manager, value3));
455
+ if (property === "$") {
456
+ return true;
457
+ }
458
+ const isSubscribed = manager.subscribed;
459
+ const original = isSubscribed && !cloned.has(manager) ? clone(merge(manager.owner)) : undefined;
460
+ const actual = _createProxy(manager, value3);
461
+ const result = Reflect.set(target, property, actual);
462
+ if (result && isSubscribed) {
463
+ _onChange(manager, original);
464
+ }
465
+ return result;
453
466
  }
454
467
  });
455
468
  const manager = existing ?? new Manager(proxy);
@@ -465,25 +478,90 @@ var _createProxy = function(existing, value2) {
465
478
  }
466
479
  return proxy;
467
480
  };
481
+ var _emit = function(manager) {
482
+ const difference = diff(cloned.get(manager) ?? {}, clone(merge(manager.owner)));
483
+ const keys = Object.keys(difference.values);
484
+ const { length } = keys;
485
+ let index = 0;
486
+ for (;index < length; index += 1) {
487
+ const key = keys[index];
488
+ const subscribers = manager.subscribers.get(key);
489
+ if (subscribers === undefined || subscribers.size === 0) {
490
+ continue;
491
+ }
492
+ const { from } = difference.values[key];
493
+ const to = get(manager.owner, key);
494
+ for (const subscriber of subscribers) {
495
+ subscriber(to, from);
496
+ }
497
+ }
498
+ cloned.delete(manager);
499
+ };
468
500
  var _isProxy = function(value2) {
469
501
  return value2?.$ instanceof Manager;
470
502
  };
503
+ var _onChange = function(manager, value2) {
504
+ cancelAnimationFrame(frames.get(manager));
505
+ if (!cloned.has(manager)) {
506
+ cloned.set(manager, value2);
507
+ }
508
+ frames.set(manager, requestAnimationFrame(() => {
509
+ _emit(manager);
510
+ }));
511
+ };
512
+ function cloneProxy(proxy) {
513
+ if (!_isProxy(proxy)) {
514
+ throw new Error("Value must be a proxy");
515
+ }
516
+ return proxy.$.clone();
517
+ }
471
518
  function proxy(value2) {
472
519
  if (!isArrayOrPlainObject(value2)) {
473
520
  throw new Error("Proxy value must be an array or object");
474
521
  }
475
522
  return _createProxy(undefined, value2);
476
523
  }
524
+ function subscribe(proxy2, key, subscriber) {
525
+ if (_isProxy(proxy2)) {
526
+ proxy2.$.on(key, subscriber);
527
+ }
528
+ }
529
+ function unsubscribe(proxy2, key, subscriber) {
530
+ if (_isProxy(proxy2)) {
531
+ proxy2.$.off(key, subscriber);
532
+ }
533
+ }
477
534
 
478
535
  class Manager {
479
536
  owner;
537
+ count = 0;
538
+ subscribers = new Map;
539
+ get subscribed() {
540
+ return this.count > 0;
541
+ }
480
542
  constructor(owner) {
481
543
  this.owner = owner;
482
544
  }
483
545
  clone() {
484
- return clone(merge(this.owner));
546
+ return _createProxy(undefined, clone(merge(this.owner)));
547
+ }
548
+ off(key, subscriber) {
549
+ if (this.subscribers.get(key)?.delete(subscriber) ?? false) {
550
+ this.count -= 1;
551
+ }
552
+ }
553
+ on(key, subscriber) {
554
+ let subscribers = this.subscribers.get(key);
555
+ if (subscribers === undefined) {
556
+ subscribers = new Set;
557
+ this.subscribers.set(key, subscribers);
558
+ }
559
+ subscribers.add(subscriber);
560
+ this.count += 1;
485
561
  }
486
562
  }
563
+ var cloned = new Map;
564
+ var frames = new Map;
487
565
  // src/js/timer.ts
488
566
  function repeat(callback, options) {
489
567
  const count = typeof options?.count === "number" ? options.count : Infinity;
@@ -569,7 +647,9 @@ class Timer {
569
647
  }
570
648
  export {
571
649
  wait,
650
+ unsubscribe,
572
651
  unique,
652
+ subscribe,
573
653
  splice,
574
654
  set,
575
655
  repeat,
@@ -593,6 +673,7 @@ export {
593
673
  exists,
594
674
  diff,
595
675
  createUuid,
676
+ cloneProxy,
596
677
  clone,
597
678
  clamp,
598
679
  chunk,
package/dist/js/proxy.js CHANGED
@@ -1,3 +1,12 @@
1
+ // src/js/string.ts
2
+ function getString(value) {
3
+ if (typeof value === "string") {
4
+ return value;
5
+ }
6
+ const result = value?.toString?.() ?? value;
7
+ return result?.toString?.() ?? String(result);
8
+ }
9
+
1
10
  // src/js/is.ts
2
11
  function isArrayOrPlainObject(value) {
3
12
  return Array.isArray(value) || isPlainObject(value);
@@ -11,9 +20,100 @@ function isPlainObject(value) {
11
20
  }
12
21
 
13
22
  // src/js/value.ts
23
+ var _getDiffs = function(first, second, prefix) {
24
+ const changes = [];
25
+ const checked = new Set;
26
+ let outer = 0;
27
+ for (;outer < 2; outer += 1) {
28
+ const value = outer === 0 ? first : second;
29
+ if (!value) {
30
+ continue;
31
+ }
32
+ const keys = Object.keys(value);
33
+ const { length } = keys;
34
+ let inner = 0;
35
+ for (;inner < length; inner += 1) {
36
+ const key = keys[inner];
37
+ if (checked.has(key)) {
38
+ continue;
39
+ }
40
+ const from = first?.[key];
41
+ const to = second?.[key];
42
+ if (!Object.is(from, to)) {
43
+ const prefixed = _getKey(prefix, key);
44
+ const change = {
45
+ from,
46
+ to,
47
+ key: prefixed
48
+ };
49
+ const nested = isArrayOrPlainObject(from) || isArrayOrPlainObject(to);
50
+ const diffs = nested ? _getDiffs(from, to, prefixed) : [];
51
+ if (!nested || nested && diffs.length > 0) {
52
+ changes.push(change);
53
+ }
54
+ changes.push(...diffs);
55
+ }
56
+ checked.add(key);
57
+ }
58
+ }
59
+ return changes;
60
+ };
61
+ var _getKey = function(...parts) {
62
+ return parts.filter((part) => part !== undefined).join(".");
63
+ };
64
+ var _getValue = function(data, key) {
65
+ if (typeof data !== "object" || data === null || /^(__proto__|constructor|prototype)$/i.test(key)) {
66
+ return;
67
+ }
68
+ return data instanceof Map ? data.get(key) : data[key];
69
+ };
14
70
  function clone(value) {
15
71
  return structuredClone(value);
16
72
  }
73
+ function diff(first, second) {
74
+ const result = {
75
+ original: {
76
+ from: first,
77
+ to: second
78
+ },
79
+ type: "partial",
80
+ values: {}
81
+ };
82
+ const same = Object.is(first, second);
83
+ const firstIsArrayOrObject = isArrayOrPlainObject(first);
84
+ const secondIsArrayOrObject = isArrayOrPlainObject(second);
85
+ if (same || !firstIsArrayOrObject && !secondIsArrayOrObject) {
86
+ result.type = same ? "none" : "full";
87
+ return result;
88
+ }
89
+ if (firstIsArrayOrObject !== secondIsArrayOrObject) {
90
+ result.type = "full";
91
+ }
92
+ const diffs = _getDiffs(first, second);
93
+ const { length } = diffs;
94
+ if (length === 0) {
95
+ result.type = "none";
96
+ }
97
+ let index = 0;
98
+ for (;index < length; index += 1) {
99
+ const diff2 = diffs[index];
100
+ result.values[diff2.key] = { from: diff2.from, to: diff2.to };
101
+ }
102
+ return result;
103
+ }
104
+ function get(data, key) {
105
+ const parts = getString(key).split(".");
106
+ const { length } = parts;
107
+ let index = 0;
108
+ let value = typeof data === "object" ? data ?? {} : {};
109
+ for (;index < length; index += 1) {
110
+ value = _getValue(value, parts[index]);
111
+ if (value == null) {
112
+ return;
113
+ }
114
+ }
115
+ return value;
116
+ }
17
117
  function merge(...values) {
18
118
  if (values.length === 0) {
19
119
  return {};
@@ -52,7 +152,17 @@ var _createProxy = function(existing, value2) {
52
152
  return property === "$" ? manager : Reflect.get(target, property);
53
153
  },
54
154
  set(target, property, value3) {
55
- return property === "$" || Reflect.set(target, property, _createProxy(manager, value3));
155
+ if (property === "$") {
156
+ return true;
157
+ }
158
+ const isSubscribed = manager.subscribed;
159
+ const original = isSubscribed && !cloned.has(manager) ? clone(merge(manager.owner)) : undefined;
160
+ const actual = _createProxy(manager, value3);
161
+ const result = Reflect.set(target, property, actual);
162
+ if (result && isSubscribed) {
163
+ _onChange(manager, original);
164
+ }
165
+ return result;
56
166
  }
57
167
  });
58
168
  const manager = existing ?? new Manager(proxy);
@@ -68,25 +178,93 @@ var _createProxy = function(existing, value2) {
68
178
  }
69
179
  return proxy;
70
180
  };
181
+ var _emit = function(manager) {
182
+ const difference = diff(cloned.get(manager) ?? {}, clone(merge(manager.owner)));
183
+ const keys = Object.keys(difference.values);
184
+ const { length } = keys;
185
+ let index = 0;
186
+ for (;index < length; index += 1) {
187
+ const key = keys[index];
188
+ const subscribers = manager.subscribers.get(key);
189
+ if (subscribers === undefined || subscribers.size === 0) {
190
+ continue;
191
+ }
192
+ const { from } = difference.values[key];
193
+ const to = get(manager.owner, key);
194
+ for (const subscriber of subscribers) {
195
+ subscriber(to, from);
196
+ }
197
+ }
198
+ cloned.delete(manager);
199
+ };
71
200
  var _isProxy = function(value2) {
72
201
  return value2?.$ instanceof Manager;
73
202
  };
203
+ var _onChange = function(manager, value2) {
204
+ cancelAnimationFrame(frames.get(manager));
205
+ if (!cloned.has(manager)) {
206
+ cloned.set(manager, value2);
207
+ }
208
+ frames.set(manager, requestAnimationFrame(() => {
209
+ _emit(manager);
210
+ }));
211
+ };
212
+ function cloneProxy(proxy) {
213
+ if (!_isProxy(proxy)) {
214
+ throw new Error("Value must be a proxy");
215
+ }
216
+ return proxy.$.clone();
217
+ }
74
218
  function proxy(value2) {
75
219
  if (!isArrayOrPlainObject(value2)) {
76
220
  throw new Error("Proxy value must be an array or object");
77
221
  }
78
222
  return _createProxy(undefined, value2);
79
223
  }
224
+ function subscribe(proxy2, key, subscriber) {
225
+ if (_isProxy(proxy2)) {
226
+ proxy2.$.on(key, subscriber);
227
+ }
228
+ }
229
+ function unsubscribe(proxy2, key, subscriber) {
230
+ if (_isProxy(proxy2)) {
231
+ proxy2.$.off(key, subscriber);
232
+ }
233
+ }
80
234
 
81
235
  class Manager {
82
236
  owner;
237
+ count = 0;
238
+ subscribers = new Map;
239
+ get subscribed() {
240
+ return this.count > 0;
241
+ }
83
242
  constructor(owner) {
84
243
  this.owner = owner;
85
244
  }
86
245
  clone() {
87
- return clone(merge(this.owner));
246
+ return _createProxy(undefined, clone(merge(this.owner)));
247
+ }
248
+ off(key, subscriber) {
249
+ if (this.subscribers.get(key)?.delete(subscriber) ?? false) {
250
+ this.count -= 1;
251
+ }
252
+ }
253
+ on(key, subscriber) {
254
+ let subscribers = this.subscribers.get(key);
255
+ if (subscribers === undefined) {
256
+ subscribers = new Set;
257
+ this.subscribers.set(key, subscribers);
258
+ }
259
+ subscribers.add(subscriber);
260
+ this.count += 1;
88
261
  }
89
262
  }
263
+ var cloned = new Map;
264
+ var frames = new Map;
90
265
  export {
91
- proxy
266
+ unsubscribe,
267
+ subscribe,
268
+ proxy,
269
+ cloneProxy
92
270
  };
package/dist/js/proxy.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
  // src/js/proxy.ts
2
2
  import {isArrayOrPlainObject} from "./is";
3
- import {clone, merge} from "./value";
3
+ import {clone, diff, get, merge} from "./value";
4
4
  var _createProxy = function(existing, value2) {
5
5
  if (!isArrayOrPlainObject(value2) || _isProxy(value2) && value2.$ === existing) {
6
6
  return value2;
@@ -11,7 +11,17 @@ var _createProxy = function(existing, value2) {
11
11
  return property === "$" ? manager : Reflect.get(target, property);
12
12
  },
13
13
  set(target, property, value3) {
14
- return property === "$" || Reflect.set(target, property, _createProxy(manager, value3));
14
+ if (property === "$") {
15
+ return true;
16
+ }
17
+ const isSubscribed = manager.subscribed;
18
+ const original = isSubscribed && !cloned.has(manager) ? clone(merge(manager.owner)) : undefined;
19
+ const actual = _createProxy(manager, value3);
20
+ const result = Reflect.set(target, property, actual);
21
+ if (result && isSubscribed) {
22
+ _onChange(manager, original);
23
+ }
24
+ return result;
15
25
  }
16
26
  });
17
27
  const manager = existing ?? new Manager(proxy);
@@ -27,25 +37,93 @@ var _createProxy = function(existing, value2) {
27
37
  }
28
38
  return proxy;
29
39
  };
40
+ var _emit = function(manager) {
41
+ const difference = diff(cloned.get(manager) ?? {}, clone(merge(manager.owner)));
42
+ const keys = Object.keys(difference.values);
43
+ const { length } = keys;
44
+ let index = 0;
45
+ for (;index < length; index += 1) {
46
+ const key = keys[index];
47
+ const subscribers = manager.subscribers.get(key);
48
+ if (subscribers === undefined || subscribers.size === 0) {
49
+ continue;
50
+ }
51
+ const { from } = difference.values[key];
52
+ const to = get(manager.owner, key);
53
+ for (const subscriber of subscribers) {
54
+ subscriber(to, from);
55
+ }
56
+ }
57
+ cloned.delete(manager);
58
+ };
30
59
  var _isProxy = function(value2) {
31
60
  return value2?.$ instanceof Manager;
32
61
  };
62
+ var _onChange = function(manager, value2) {
63
+ cancelAnimationFrame(frames.get(manager));
64
+ if (!cloned.has(manager)) {
65
+ cloned.set(manager, value2);
66
+ }
67
+ frames.set(manager, requestAnimationFrame(() => {
68
+ _emit(manager);
69
+ }));
70
+ };
71
+ function cloneProxy(proxy) {
72
+ if (!_isProxy(proxy)) {
73
+ throw new Error("Value must be a proxy");
74
+ }
75
+ return proxy.$.clone();
76
+ }
33
77
  function proxy(value2) {
34
78
  if (!isArrayOrPlainObject(value2)) {
35
79
  throw new Error("Proxy value must be an array or object");
36
80
  }
37
81
  return _createProxy(undefined, value2);
38
82
  }
83
+ function subscribe(proxy2, key, subscriber) {
84
+ if (_isProxy(proxy2)) {
85
+ proxy2.$.on(key, subscriber);
86
+ }
87
+ }
88
+ function unsubscribe(proxy2, key, subscriber) {
89
+ if (_isProxy(proxy2)) {
90
+ proxy2.$.off(key, subscriber);
91
+ }
92
+ }
39
93
 
40
94
  class Manager {
41
95
  owner;
96
+ count = 0;
97
+ subscribers = new Map;
98
+ get subscribed() {
99
+ return this.count > 0;
100
+ }
42
101
  constructor(owner) {
43
102
  this.owner = owner;
44
103
  }
45
104
  clone() {
46
- return clone(merge(this.owner));
105
+ return _createProxy(undefined, clone(merge(this.owner)));
106
+ }
107
+ off(key, subscriber) {
108
+ if (this.subscribers.get(key)?.delete(subscriber) ?? false) {
109
+ this.count -= 1;
110
+ }
111
+ }
112
+ on(key, subscriber) {
113
+ let subscribers = this.subscribers.get(key);
114
+ if (subscribers === undefined) {
115
+ subscribers = new Set;
116
+ this.subscribers.set(key, subscribers);
117
+ }
118
+ subscribers.add(subscriber);
119
+ this.count += 1;
47
120
  }
48
121
  }
122
+ var cloned = new Map;
123
+ var frames = new Map;
49
124
  export {
50
- proxy
125
+ unsubscribe,
126
+ subscribe,
127
+ proxy,
128
+ cloneProxy
51
129
  };
package/dist/js/value.js CHANGED
@@ -41,14 +41,17 @@ var _getDiffs = function(first, second, prefix) {
41
41
  const to = second?.[key];
42
42
  if (!Object.is(from, to)) {
43
43
  const prefixed = _getKey(prefix, key);
44
- changes.push({
44
+ const change = {
45
45
  from,
46
46
  to,
47
47
  key: prefixed
48
- });
49
- if (isArrayOrPlainObject(from) && isArrayOrPlainObject(to)) {
50
- changes.push(..._getDiffs(from, to, prefixed));
48
+ };
49
+ const nested = isArrayOrPlainObject(from) || isArrayOrPlainObject(to);
50
+ const diffs = nested ? _getDiffs(from, to, prefixed) : [];
51
+ if (!nested || nested && diffs.length > 0) {
52
+ changes.push(change);
51
53
  }
54
+ changes.push(...diffs);
52
55
  }
53
56
  checked.add(key);
54
57
  }
package/dist/js/value.mjs CHANGED
@@ -22,14 +22,17 @@ var _getDiffs = function(first, second, prefix) {
22
22
  const to = second?.[key];
23
23
  if (!Object.is(from, to)) {
24
24
  const prefixed = _getKey(prefix, key);
25
- changes.push({
25
+ const change = {
26
26
  from,
27
27
  to,
28
28
  key: prefixed
29
- });
30
- if (isArrayOrPlainObject(from) && isArrayOrPlainObject(to)) {
31
- changes.push(..._getDiffs(from, to, prefixed));
29
+ };
30
+ const nested = isArrayOrPlainObject(from) || isArrayOrPlainObject(to);
31
+ const diffs = nested ? _getDiffs(from, to, prefixed) : [];
32
+ if (!nested || nested && diffs.length > 0) {
33
+ changes.push(change);
32
34
  }
35
+ changes.push(...diffs);
33
36
  }
34
37
  checked.add(key);
35
38
  }
package/package.json CHANGED
@@ -105,5 +105,5 @@
105
105
  },
106
106
  "type": "module",
107
107
  "types": "./types/index.d.ts",
108
- "version": "0.21.0"
108
+ "version": "0.22.0"
109
109
  }
package/src/js/proxy.ts CHANGED
@@ -1,18 +1,54 @@
1
1
  import {ArrayOrPlainObject, PlainObject, isArrayOrPlainObject} from './is';
2
- import {clone, merge} from './value';
2
+ import {clone, diff, get, merge} from './value';
3
3
 
4
4
  class Manager<T extends ArrayOrPlainObject = PlainObject> {
5
+ private count = 0;
6
+
7
+ readonly subscribers = new Map<string, Set<Subscriber>>();
8
+
9
+ get subscribed(): boolean {
10
+ return this.count > 0;
11
+ }
12
+
5
13
  constructor(readonly owner: Proxied<T>) {}
6
14
 
7
- clone(): Proxied<T> {
8
- return clone(merge(this.owner)) as Proxied<T>;
15
+ clone(): T {
16
+ return _createProxy(undefined, clone(merge(this.owner))) as T;
17
+ }
18
+
19
+ off<T1 = unknown, T2 = T1>(
20
+ key: string,
21
+ subscriber: Subscriber<T1, T2>,
22
+ ): void {
23
+ if (this.subscribers.get(key)?.delete(subscriber as never) ?? false) {
24
+ this.count -= 1;
25
+ }
26
+ }
27
+
28
+ on<T1 = unknown, T2 = T1>(key: string, subscriber: Subscriber<T1, T2>): void {
29
+ let subscribers = this.subscribers.get(key);
30
+
31
+ if (subscribers === undefined) {
32
+ subscribers = new Set<Subscriber>();
33
+
34
+ this.subscribers.set(key, subscribers);
35
+ }
36
+
37
+ subscribers.add(subscriber as never);
38
+
39
+ this.count += 1;
9
40
  }
10
41
  }
11
42
 
12
- export type Proxied<T extends ArrayOrPlainObject = PlainObject> = {
43
+ type Proxied<T extends ArrayOrPlainObject = PlainObject> = {
13
44
  $: Manager<T>;
14
45
  } & T;
15
46
 
47
+ export type Subscriber<T1 = unknown, T2 = T1> = (to: T1, from: T2) => void;
48
+
49
+ const cloned = new Map<Manager, unknown>();
50
+ const frames = new Map<Manager, number>();
51
+
16
52
  function _createProxy<T extends ArrayOrPlainObject>(
17
53
  existing: Manager | undefined,
18
54
  value: T,
@@ -31,10 +67,25 @@ function _createProxy<T extends ArrayOrPlainObject>(
31
67
  return property === '$' ? manager : Reflect.get(target, property);
32
68
  },
33
69
  set(target, property, value) {
34
- return (
35
- property === '$' ||
36
- Reflect.set(target, property, _createProxy(manager, value))
37
- );
70
+ if (property === '$') {
71
+ return true;
72
+ }
73
+
74
+ const isSubscribed = manager.subscribed;
75
+
76
+ const original =
77
+ isSubscribed && !cloned.has(manager)
78
+ ? clone(merge(manager.owner))
79
+ : undefined;
80
+
81
+ const actual = _createProxy(manager, value);
82
+ const result = Reflect.set(target, property, actual);
83
+
84
+ if (result && isSubscribed) {
85
+ _onChange(manager, original);
86
+ }
87
+
88
+ return result;
38
89
  },
39
90
  }) as Proxied;
40
91
 
@@ -58,14 +109,99 @@ function _createProxy<T extends ArrayOrPlainObject>(
58
109
  return proxy;
59
110
  }
60
111
 
112
+ function _emit(manager: Manager): void {
113
+ const difference = diff(
114
+ cloned.get(manager) ?? {},
115
+ clone(merge(manager.owner)),
116
+ );
117
+
118
+ const keys = Object.keys(difference.values);
119
+ const {length} = keys;
120
+
121
+ let index = 0;
122
+
123
+ for (; index < length; index += 1) {
124
+ const key = keys[index];
125
+ const subscribers = manager.subscribers.get(key);
126
+
127
+ if (subscribers === undefined || subscribers.size === 0) {
128
+ continue;
129
+ }
130
+
131
+ const {from} = difference.values[key];
132
+ const to = get(manager.owner, key);
133
+
134
+ for (const subscriber of subscribers) {
135
+ subscriber(to, from);
136
+ }
137
+ }
138
+
139
+ cloned.delete(manager);
140
+ }
141
+
61
142
  function _isProxy(value: unknown): value is Proxied {
62
143
  return (value as Proxied)?.$ instanceof Manager;
63
144
  }
64
145
 
65
- export function proxy<T extends PlainObject>(value: T): Proxied<T> {
146
+ function _onChange(manager: Manager, value: unknown): void {
147
+ cancelAnimationFrame(frames.get(manager) as number);
148
+
149
+ if (!cloned.has(manager)) {
150
+ cloned.set(manager, value);
151
+ }
152
+
153
+ frames.set(
154
+ manager,
155
+ requestAnimationFrame(() => {
156
+ _emit(manager);
157
+ }),
158
+ );
159
+ }
160
+
161
+ /**
162
+ * Clones and creates a new proxy
163
+ */
164
+ export function cloneProxy<T extends ArrayOrPlainObject>(proxy: T): T {
165
+ if (!_isProxy(proxy)) {
166
+ throw new Error('Value must be a proxy');
167
+ }
168
+
169
+ return proxy.$.clone() as T;
170
+ }
171
+
172
+ /**
173
+ * Creates a proxy for an array or object
174
+ */
175
+ export function proxy<T extends PlainObject>(value: T): T {
66
176
  if (!isArrayOrPlainObject(value)) {
67
177
  throw new Error('Proxy value must be an array or object');
68
178
  }
69
179
 
70
- return _createProxy(undefined, value) as Proxied<T>;
180
+ return _createProxy(undefined, value) as T;
181
+ }
182
+
183
+ /**
184
+ * Subscribes to changes for a key in a proxy
185
+ */
186
+ export function subscribe<T1 = ArrayOrPlainObject, T2 = unknown, T3 = T2>(
187
+ proxy: T1,
188
+ key: string,
189
+ subscriber: Subscriber<T2, T3>,
190
+ ): void {
191
+ if (_isProxy(proxy)) {
192
+ proxy.$.on(key, subscriber);
193
+ }
194
+ }
195
+
196
+ /**
197
+ * Unsubscribes from changes for a key in a proxy
198
+ */
199
+ export function unsubscribe<T1 = ArrayOrPlainObject, T2 = unknown, T3 = T2>(
200
+ proxy: T1,
201
+ key: string,
202
+ subscriber: Subscriber<T2, T3>,
203
+ ): void {
204
+ if (_isProxy(proxy)) {
205
+ proxy.$.off(key, subscriber);
206
+ }
71
207
  }
package/src/js/value.ts CHANGED
@@ -58,15 +58,20 @@ function _getDiffs(
58
58
  if (!Object.is(from, to)) {
59
59
  const prefixed = _getKey(prefix, key);
60
60
 
61
- changes.push({
61
+ const change = {
62
62
  from,
63
63
  to,
64
64
  key: prefixed,
65
- });
65
+ };
66
66
 
67
- if (isArrayOrPlainObject(from) && isArrayOrPlainObject(to)) {
68
- changes.push(..._getDiffs(from, to, prefixed));
67
+ const nested = isArrayOrPlainObject(from) || isArrayOrPlainObject(to);
68
+ const diffs = nested ? _getDiffs(from, to, prefixed) : [];
69
+
70
+ if (!nested || (nested && diffs.length > 0)) {
71
+ changes.push(change);
69
72
  }
73
+
74
+ changes.push(...diffs);
70
75
  }
71
76
 
72
77
  checked.add(key);
package/types/proxy.d.ts CHANGED
@@ -1,11 +1,18 @@
1
1
  import { ArrayOrPlainObject, PlainObject } from './is';
2
- declare class Manager<T extends ArrayOrPlainObject = PlainObject> {
3
- readonly owner: Proxied<T>;
4
- constructor(owner: Proxied<T>);
5
- clone(): Proxied<T>;
6
- }
7
- export type Proxied<T extends ArrayOrPlainObject = PlainObject> = {
8
- $: Manager<T>;
9
- } & T;
10
- export declare function proxy<T extends PlainObject>(value: T): Proxied<T>;
11
- export {};
2
+ export type Subscriber<T1 = unknown, T2 = T1> = (to: T1, from: T2) => void;
3
+ /**
4
+ * Clones and creates a new proxy
5
+ */
6
+ export declare function cloneProxy<T extends ArrayOrPlainObject>(proxy: T): T;
7
+ /**
8
+ * Creates a proxy for an array or object
9
+ */
10
+ export declare function proxy<T extends PlainObject>(value: T): T;
11
+ /**
12
+ * Subscribes to changes for a key in a proxy
13
+ */
14
+ export declare function subscribe<T1 = ArrayOrPlainObject, T2 = unknown, T3 = T2>(proxy: T1, key: string, subscriber: Subscriber<T2, T3>): void;
15
+ /**
16
+ * Unsubscribes from changes for a key in a proxy
17
+ */
18
+ export declare function unsubscribe<T1 = ArrayOrPlainObject, T2 = unknown, T3 = T2>(proxy: T1, key: string, subscriber: Subscriber<T2, T3>): void;