@esportsplus/reactivity 0.12.3 → 0.13.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.
@@ -1,304 +1,246 @@
1
- import { isFunction, isNumber, isObject } from '@esportsplus/utilities';
1
+ import { isNumber } from '@esportsplus/utilities';
2
2
  import { REACTIVE_ARRAY } from '~/constants';
3
- import { computed, dispose, isComputed, read } from '~/system';
4
- import { Computed, Infer } from '~/types';
5
- import object, { isReactiveObject, ReactiveObject } from './object';
3
+ import { dispose as d, isComputed, read } from '~/system';
4
+ import { Infer } from '~/types';
5
+ import { isReactiveObject } from './object';
6
6
 
7
7
 
8
- type API<T> = Infer<T>[] & ReactiveArray<T>;
8
+ type API<T> = Infer<T[]> & {
9
+ clear: () => void;
10
+ dispose: () => void;
11
+ };
9
12
 
10
13
  type Events<T> = {
14
+ clear: undefined,
11
15
  pop: {
12
- item: Item<T>;
16
+ item: T;
13
17
  };
14
18
  push: {
15
- items: Item<T>[];
19
+ items: T[];
16
20
  };
17
21
  reverse: undefined;
18
22
  set: {
19
23
  index: number;
20
- item: Item<T>;
24
+ item: T;
21
25
  };
22
26
  shift: {
23
- item: Item<T>;
27
+ item: T;
24
28
  };
25
29
  sort: undefined;
26
30
  splice: {
27
31
  deleteCount: number;
28
- items: Item<T>[];
32
+ items: T[];
29
33
  start: number;
30
34
  };
31
35
  unshift: {
32
- items: Item<T>[];
36
+ items: T[];
33
37
  };
34
38
  };
35
39
 
36
- type Item<T> = T | Computed<T> | API<T> | ReactiveObject< T extends Record<PropertyKey, unknown> ? T : never >;
37
-
38
40
  type Listener<V> = {
39
41
  once?: boolean;
40
42
  (value: V): void;
41
43
  };
42
44
 
43
- type Value<T> =
44
- T extends Record<PropertyKey, unknown>
45
- ? ReactiveObject<T>
46
- : T extends Array<infer U>
47
- ? API<U>
48
- : T;
49
-
45
+ type Listeners = Record<string, (Listener<any> | null)[]>;
50
46
 
51
- class ReactiveArray<T> {
52
- [REACTIVE_ARRAY] = true;
53
- disposables: number = 0;
54
47
 
55
- private data: Item<T>[];
56
- private listeners: Record<string, (Listener<any> | null)[]> | null = null;
57
- private proxy: API<T>;
48
+ function at<T>(data: T[], i: number) {
49
+ let value = data[i];
58
50
 
59
-
60
- constructor(data: Item<T>[], proxy: API<T>) {
61
- this.data = data;
62
- this.proxy = proxy;
51
+ if (isComputed(value)) {
52
+ return read(value);
63
53
  }
64
54
 
55
+ return value;
56
+ }
65
57
 
66
- get length(): number {
67
- return this.data.length;
58
+ function cleanup<T>(item: T) {
59
+ if (isReactiveObject(item)) {
60
+ item.dispose();
68
61
  }
69
-
70
- set length(n: number) {
71
- if (n > this.data.length) {
72
- return;
73
- }
74
-
75
- this.splice(n);
62
+ else if (isComputed(item)) {
63
+ d(item);
76
64
  }
65
+ }
77
66
 
67
+ function clear<T>(data: T[], listeners: Listeners) {
68
+ dispose(data);
69
+ dispatch(listeners, 'clear');
70
+ }
78
71
 
79
- private cleanup(item: Item<T>) {
80
- if (isReactiveObject(item)) {
81
- item.dispose();
82
- }
83
- else if (isComputed(item)) {
84
- dispose(item);
85
- }
72
+ function dispatch<T, K extends keyof Events<T>, V>(listeners: Listeners, event: K, value?: V) {
73
+ if (listeners === null || listeners[event] === undefined) {
74
+ return;
86
75
  }
87
76
 
77
+ let bucket = listeners[event];
88
78
 
89
- at(i: number) {
90
- let value = this.data[i];
79
+ for (let i = 0, n = bucket.length; i < n; i++) {
80
+ let listener = bucket[i];
91
81
 
92
- if (isComputed(value)) {
93
- return read(value);
82
+ if (listener === null) {
83
+ continue;
94
84
  }
95
85
 
96
- return value;
97
- }
86
+ try {
87
+ listener(value);
98
88
 
99
- dispatch<K extends keyof Events<T>, V>(event: K, value?: V) {
100
- if (this.listeners === null || this.listeners[event] === undefined) {
101
- return;
89
+ if (listener.once !== undefined) {
90
+ bucket[i] = null;
91
+ }
102
92
  }
93
+ catch {
94
+ bucket[i] = null;
95
+ }
96
+ }
97
+ }
103
98
 
104
- let listeners = this.listeners[event];
105
-
106
- for (let i = 0, n = listeners.length; i < n; i++) {
107
- let listener = listeners[i];
108
-
109
- if (listener === null) {
110
- continue;
111
- }
99
+ function dispose<T>(data: T[]) {
100
+ let item;
112
101
 
113
- try {
114
- listener(value);
102
+ while (item = data.pop()) {
103
+ cleanup(item);
104
+ }
105
+ }
115
106
 
116
- if (listener.once !== undefined) {
117
- listeners[i] = null;
118
- }
119
- }
120
- catch {
121
- listeners[i] = null;
122
- }
123
- }
107
+ function map<T, R>(
108
+ data: T[],
109
+ proxy: API<T>,
110
+ fn: (this: API<T>, value: T, i: number) => R,
111
+ i?: number,
112
+ n?: number
113
+ ) {
114
+ if (i === undefined) {
115
+ i = 0;
124
116
  }
125
117
 
126
- dispose() {
127
- for (let i = 0, n = this.data.length; i < n; i++) {
128
- this.cleanup(this.data[i]);
129
- }
118
+ if (n === undefined) {
119
+ n = data.length;
130
120
  }
131
121
 
132
- map<R>(
133
- fn: (this: API<T>, value: Value<T>, i: number) => R,
134
- i?: number,
135
- n?: number
136
- ) {
137
- let { data, proxy } = this,
138
- values: R[] = [];
122
+ n = Math.min(n, data.length);
139
123
 
140
- if (i === undefined) {
141
- i = 0;
142
- }
124
+ let values: R[] = new Array(n - i);
143
125
 
144
- if (n === undefined) {
145
- n = data.length;
146
- }
126
+ for (; i < n; i++) {
127
+ let item = data[i];
147
128
 
148
- n = Math.min(n, data.length);
129
+ values[i] = fn.call(
130
+ proxy,
131
+ (isComputed(item) ? item.value : item) as T,
132
+ i
133
+ );
134
+ }
149
135
 
150
- for (; i < n; i++) {
151
- let item = data[i];
136
+ return values;
137
+ }
152
138
 
153
- values.push(
154
- fn.call(
155
- proxy,
156
- (isComputed(item) ? item.value : item) as Value<T>,
157
- i
158
- )
159
- );
160
- }
139
+ function on<T, K extends keyof Events<T>>(listeners: Listeners, event: K, listener: Listener<Events<T>[K]>) {
140
+ let bucket = listeners[event];
161
141
 
162
- return values;
142
+ if (bucket === undefined) {
143
+ listeners[event] = [listener];
163
144
  }
145
+ else {
146
+ let hole = bucket.length;
164
147
 
165
- on<K extends keyof Events<T>>(event: K, listener: Listener<Events<T>[K]>) {
166
- if (this.listeners === null) {
167
- this.listeners = { [event]: [listener] };
168
- }
169
- else {
170
- let listeners = this.listeners[event];
148
+ for (let i = 0, n = hole; i < n; i++) {
149
+ let l = bucket[i];
171
150
 
172
- if (listeners === undefined) {
173
- this.listeners[event] = [listener];
151
+ if (l === listener) {
152
+ return;
174
153
  }
175
- else if (listeners.indexOf(listener) === -1) {
176
- let i = listeners.indexOf(null);
177
-
178
- if (i === -1) {
179
- listeners.push(listener);
180
- }
181
- else {
182
- listeners[i] = listener;
183
- }
154
+ else if (l === null && hole === n) {
155
+ hole = i;
184
156
  }
185
157
  }
186
- }
187
158
 
188
- once<K extends keyof Events<T>>(event: K, listener: Listener<Events<T>[K]>) {
189
- listener.once = true;
190
- this.on(event, listener);
159
+ bucket[hole] = listener;
191
160
  }
161
+ }
192
162
 
193
- pop() {
194
- let item = this.data.pop();
163
+ function once<T, K extends keyof Events<T>>(listeners: Listeners, event: K, listener: Listener<Events<T>[K]>) {
164
+ listener.once = true;
165
+ on(listeners, event, listener);
166
+ }
195
167
 
196
- if (item !== undefined) {
197
- this.cleanup(item);
198
- this.dispatch('pop', { item });
199
- }
168
+ function pop<T>(data: T[], listeners: Listeners) {
169
+ let item = data.pop();
200
170
 
201
- return item;
171
+ if (item !== undefined) {
172
+ cleanup(item);
173
+ dispatch(listeners, 'pop', { item });
202
174
  }
203
175
 
204
- push(...input: T[]) {
205
- let items = factory(input),
206
- n = this.data.push(...items);
176
+ return item;
177
+ }
207
178
 
208
- this.dispatch('push', { items });
179
+ function push<T>(data: T[], listeners: Listeners, items: T[]) {
180
+ let n = data.push(...items);
209
181
 
210
- return n;
211
- }
182
+ dispatch(listeners, 'push', { items });
212
183
 
213
- reverse() {
214
- this.data.reverse();
215
- this.dispatch('reverse');
184
+ return n;
185
+ }
216
186
 
217
- return this;
218
- }
187
+ function reverse<T>(data: T[], listeners: Listeners) {
188
+ data.reverse();
189
+ dispatch(listeners, 'reverse');
190
+ }
219
191
 
220
- shift() {
221
- let item = this.data.shift();
192
+ function shift<T>(data: T[], listeners: Listeners) {
193
+ let item = data.shift();
222
194
 
223
- if (item !== undefined) {
224
- this.cleanup(item);
225
- this.dispatch('shift', { item });
226
- }
227
-
228
- return item;
195
+ if (item !== undefined) {
196
+ cleanup(item);
197
+ dispatch(listeners, 'shift', { item });
229
198
  }
230
199
 
231
- sort(fn: (a: Value<T>, b: Value<T>) => number) {
232
- this.data.sort((a, b) => fn(
233
- (isComputed(a) ? a.value : a) as Value<T>,
234
- (isComputed(b) ? b.value : b) as Value<T>
235
- ));
236
- this.dispatch('sort');
200
+ return item;
201
+ }
237
202
 
238
- return this;
239
- }
203
+ function sort<T>(data: T[], listeners: Listeners, fn: (a: T, b: T) => number) {
204
+ data.sort((a, b) => fn(
205
+ (isComputed(a) ? a.value : a) as T,
206
+ (isComputed(b) ? b.value : b) as T
207
+ ));
208
+ dispatch(listeners, 'sort');
209
+ }
240
210
 
241
- splice(start: number, deleteCount: number = this.data.length, ...input: T[]) {
242
- let items = factory(input),
243
- removed = this.data.splice(start, deleteCount, ...items);
211
+ function splice<T>(data: T[], listeners: Listeners, start: number, deleteCount: number = data.length, items: T[] = []) {
212
+ let removed = data.splice(start, deleteCount, ...items);
244
213
 
245
- if (items.length > 0 || removed.length > 0) {
246
- for (let i = 0, n = removed.length; i < n; i++) {
247
- this.cleanup(removed[i]);
248
- }
249
-
250
- this.dispatch('splice', {
251
- deleteCount,
252
- items,
253
- start
254
- });
214
+ if (items.length > 0 || removed.length > 0) {
215
+ for (let i = 0, n = removed.length; i < n; i++) {
216
+ cleanup(removed[i]);
255
217
  }
256
218
 
257
- return removed;
219
+ dispatch(listeners, 'splice', {
220
+ deleteCount,
221
+ items,
222
+ start
223
+ });
258
224
  }
259
225
 
260
- unshift(...input: T[]) {
261
- let items = factory(input),
262
- length = this.data.unshift(...items);
263
-
264
- this.dispatch('unshift', { items });
265
-
266
- return length;
267
- }
226
+ return removed;
268
227
  }
269
228
 
229
+ function unshift<T>(data: T[], listeners: Listeners, items: T[]) {
230
+ let length = data.unshift(...items);
270
231
 
271
- function factory<T>(input: T[]) {
272
- let items: Item<T>[] = [];
273
-
274
- for (let i = 0, n = input.length; i < n; i++) {
275
- let value = input[i];
276
-
277
- if (isFunction(value)) {
278
- items[i] = computed(value as Computed<T>['fn']);
279
- }
280
- else if (isObject(value)) {
281
- items[i] = object(value) as Item<T>;
282
- }
283
- else {
284
- items[i] = value;
285
- }
286
- }
232
+ dispatch(listeners, 'unshift', { items });
287
233
 
288
- return items;
234
+ return length;
289
235
  }
290
236
 
291
237
 
292
- const isReactiveArray = (value: any): value is ReactiveArray<any> => {
293
- return isObject(value) && REACTIVE_ARRAY in value;
294
- };
295
-
296
-
297
- export default <T>(input: T[]) => {
298
- let proxy = new Proxy({}, {
299
- get(_: any, key: any) {
238
+ export default <T>(data: T[]) => {
239
+ let listeners: Listeners = {},
240
+ proxy = new Proxy({}, {
241
+ get(_, key: any) {
300
242
  if (isNumber(key)) {
301
- let value = wrapped[key];
243
+ let value = data[key];
302
244
 
303
245
  if (isComputed(value)) {
304
246
  return read(value);
@@ -306,29 +248,82 @@ export default <T>(input: T[]) => {
306
248
 
307
249
  return value;
308
250
  }
309
- else if (key in array) {
310
- return array[key as keyof typeof array];
251
+ else if (key in wrapper) {
252
+ return wrapper[key as keyof typeof wrapper];
253
+ }
254
+ else if (key === 'length') {
255
+ return data.length;
311
256
  }
312
257
 
313
- return wrapped[key];
258
+ return data[key];
314
259
  },
315
- set(_: any, key: any, value: any) {
260
+ set(_, key: any, value: any) {
316
261
  if (isNumber(key)) {
317
- array.splice(key, 1, value);
318
- return true;
262
+ splice(data, listeners, key, 1, value);
319
263
  }
320
264
  else if (key === 'length') {
321
- return array.length = value;
265
+ if (value >= data.length) {
266
+ }
267
+ else if (value === 0) {
268
+ clear(data, listeners);
269
+ }
270
+ else {
271
+ splice(data, listeners, value);
272
+ }
273
+ }
274
+ else {
275
+ return false;
322
276
  }
323
277
 
324
- return false;
278
+ return true;
325
279
  }
326
280
  }) as API<T>,
327
- wrapped = factory(input);
328
-
329
- let array = new ReactiveArray(wrapped, proxy);
281
+ wrapper = {
282
+ [REACTIVE_ARRAY]: true,
283
+ at: (i: number) => at(data, i),
284
+ clear: () => {
285
+ clear(data, listeners);
286
+ return proxy;
287
+ },
288
+ dispatch: <K extends keyof Events<T>, V>(event: K, value?: V) => {
289
+ dispatch(listeners, event, value);
290
+ return proxy;
291
+ },
292
+ dispose: () => {
293
+ dispose(data);
294
+ return proxy;
295
+ },
296
+ map: <R>(
297
+ fn: (this: API<T>, value: T, i: number) => R,
298
+ i?: number,
299
+ n?: number
300
+ ) => {
301
+ return map(data, proxy, fn, i, n);
302
+ },
303
+ on: <K extends keyof Events<T>>(event: K, listener: Listener<Events<T>[K]>) => {
304
+ on(listeners, event, listener);
305
+ return proxy;
306
+ },
307
+ once: <K extends keyof Events<T>>(event: K, listener: Listener<Events<T>[K]>) => {
308
+ once(listeners, event, listener);
309
+ return proxy;
310
+ },
311
+ pop: () => pop(data, listeners),
312
+ push: (...items: T[]) => push(data, listeners, items),
313
+ reverse: () => {
314
+ reverse(data, listeners);
315
+ return proxy;
316
+ },
317
+ shift: () => shift(data, listeners),
318
+ sort: (fn: (a: T, b: T) => number) => {
319
+ sort(data, listeners, fn);
320
+ return proxy;
321
+ },
322
+ splice: (start: number, deleteCount?: number, ...items: T[]) => {
323
+ return splice(data, listeners, start, deleteCount, items);
324
+ },
325
+ unshift: (...items: T[]) => unshift(data, listeners, items),
326
+ };
330
327
 
331
328
  return proxy;
332
329
  };
333
- export { isReactiveArray };
334
- export type { API as ReactiveArray };
@@ -13,15 +13,13 @@ type API<T> =
13
13
 
14
14
  type Input<T> =
15
15
  T extends { dispose: any }
16
- ? never
17
- : T extends Record<PropertyKey, unknown>
16
+ ? { never: '[ dispose, signals ] are reserved keys' }
17
+ : T extends Record<PropertyKey, unknown> | unknown[]
18
18
  ? T
19
- : T extends unknown[]
20
- ? Input<T[number]>[]
21
- : never;
19
+ : never;
22
20
 
23
21
 
24
- export default <T extends Record<PropertyKey, unknown> | unknown[]>(input: Input<T>): API<T> => {
22
+ export default <T>(input: Input<T>): API<T> => {
25
23
  let value: API<T> | undefined;
26
24
 
27
25
  return root(() => {