@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.
@@ -11,4 +11,5 @@ declare const STATE_CHECK: number;
11
11
  declare const STATE_DIRTY: number;
12
12
  declare const STATE_RECOMPUTING: number;
13
13
  declare const STATE_IN_HEAP: number;
14
- export { COMPUTED, REACTIVE_ARRAY, REACTIVE_OBJECT, SIGNAL, STABILIZER_IDLE, STABILIZER_RESCHEDULE, STABILIZER_RUNNING, STABILIZER_SCHEDULED, STATE_CHECK, STATE_DIRTY, STATE_IN_HEAP, STATE_NONE, STATE_RECOMPUTING };
14
+ declare const STATE_NOTIFY_MASK: number;
15
+ export { COMPUTED, REACTIVE_ARRAY, REACTIVE_OBJECT, SIGNAL, STABILIZER_IDLE, STATE_NOTIFY_MASK, STABILIZER_RESCHEDULE, STABILIZER_RUNNING, STABILIZER_SCHEDULED, STATE_CHECK, STATE_DIRTY, STATE_IN_HEAP, STATE_NONE, STATE_RECOMPUTING };
@@ -11,4 +11,5 @@ const STATE_CHECK = 1 << 0;
11
11
  const STATE_DIRTY = 1 << 1;
12
12
  const STATE_RECOMPUTING = 1 << 2;
13
13
  const STATE_IN_HEAP = 1 << 3;
14
- export { COMPUTED, REACTIVE_ARRAY, REACTIVE_OBJECT, SIGNAL, STABILIZER_IDLE, STABILIZER_RESCHEDULE, STABILIZER_RUNNING, STABILIZER_SCHEDULED, STATE_CHECK, STATE_DIRTY, STATE_IN_HEAP, STATE_NONE, STATE_RECOMPUTING };
14
+ const STATE_NOTIFY_MASK = (STATE_CHECK | STATE_DIRTY);
15
+ export { COMPUTED, REACTIVE_ARRAY, REACTIVE_OBJECT, SIGNAL, STABILIZER_IDLE, STATE_NOTIFY_MASK, STABILIZER_RESCHEDULE, STABILIZER_RUNNING, STABILIZER_SCHEDULED, STATE_CHECK, STATE_DIRTY, STATE_IN_HEAP, STATE_NONE, STATE_RECOMPUTING };
@@ -1,64 +1,7 @@
1
- import { REACTIVE_ARRAY } from '../constants.js';
2
- import { Computed, Infer } from '../types.js';
3
- import { ReactiveObject } from './object.js';
4
- type API<T> = Infer<T>[] & ReactiveArray<T>;
5
- type Events<T> = {
6
- pop: {
7
- item: Item<T>;
8
- };
9
- push: {
10
- items: Item<T>[];
11
- };
12
- reverse: undefined;
13
- set: {
14
- index: number;
15
- item: Item<T>;
16
- };
17
- shift: {
18
- item: Item<T>;
19
- };
20
- sort: undefined;
21
- splice: {
22
- deleteCount: number;
23
- items: Item<T>[];
24
- start: number;
25
- };
26
- unshift: {
27
- items: Item<T>[];
28
- };
1
+ import { Infer } from '../types.js';
2
+ type API<T> = Infer<T[]> & {
3
+ clear: () => void;
4
+ dispose: () => void;
29
5
  };
30
- type Item<T> = T | Computed<T> | API<T> | ReactiveObject<T extends Record<PropertyKey, unknown> ? T : never>;
31
- type Listener<V> = {
32
- once?: boolean;
33
- (value: V): void;
34
- };
35
- type Value<T> = T extends Record<PropertyKey, unknown> ? ReactiveObject<T> : T extends Array<infer U> ? API<U> : T;
36
- declare class ReactiveArray<T> {
37
- [REACTIVE_ARRAY]: boolean;
38
- disposables: number;
39
- private data;
40
- private listeners;
41
- private proxy;
42
- constructor(data: Item<T>[], proxy: API<T>);
43
- get length(): number;
44
- set length(n: number);
45
- private cleanup;
46
- at(i: number): T | API<T> | ReactiveObject<T extends Record<PropertyKey, unknown> ? T : never>;
47
- dispatch<K extends keyof Events<T>, V>(event: K, value?: V): void;
48
- dispose(): void;
49
- map<R>(fn: (this: API<T>, value: Value<T>, i: number) => R, i?: number, n?: number): R[];
50
- on<K extends keyof Events<T>>(event: K, listener: Listener<Events<T>[K]>): void;
51
- once<K extends keyof Events<T>>(event: K, listener: Listener<Events<T>[K]>): void;
52
- pop(): Item<T> | undefined;
53
- push(...input: T[]): number;
54
- reverse(): this;
55
- shift(): Item<T> | undefined;
56
- sort(fn: (a: Value<T>, b: Value<T>) => number): this;
57
- splice(start: number, deleteCount?: number, ...input: T[]): Item<T>[];
58
- unshift(...input: T[]): number;
59
- }
60
- declare const isReactiveArray: (value: any) => value is ReactiveArray<any>;
61
- declare const _default: <T>(input: T[]) => API<T>;
6
+ declare const _default: <T>(data: T[]) => API<T>;
62
7
  export default _default;
63
- export { isReactiveArray };
64
- export type { API as ReactiveArray };
@@ -1,203 +1,217 @@
1
- import { isFunction, isNumber, isObject } from '@esportsplus/utilities';
1
+ import { isNumber } from '@esportsplus/utilities';
2
2
  import { REACTIVE_ARRAY } from '../constants.js';
3
- import { computed, dispose, isComputed, read } from '../system.js';
4
- import object, { isReactiveObject } from './object.js';
5
- class ReactiveArray {
6
- [REACTIVE_ARRAY] = true;
7
- disposables = 0;
8
- data;
9
- listeners = null;
10
- proxy;
11
- constructor(data, proxy) {
12
- this.data = data;
13
- this.proxy = proxy;
14
- }
15
- get length() {
16
- return this.data.length;
17
- }
18
- set length(n) {
19
- if (n > this.data.length) {
20
- return;
21
- }
22
- this.splice(n);
23
- }
24
- cleanup(item) {
25
- if (isReactiveObject(item)) {
26
- item.dispose();
27
- }
28
- else if (isComputed(item)) {
29
- dispose(item);
30
- }
3
+ import { dispose as d, isComputed, read } from '../system.js';
4
+ import { isReactiveObject } from './object.js';
5
+ function at(data, i) {
6
+ let value = data[i];
7
+ if (isComputed(value)) {
8
+ return read(value);
9
+ }
10
+ return value;
11
+ }
12
+ function cleanup(item) {
13
+ if (isReactiveObject(item)) {
14
+ item.dispose();
31
15
  }
32
- at(i) {
33
- let value = this.data[i];
34
- if (isComputed(value)) {
35
- return read(value);
36
- }
37
- return value;
16
+ else if (isComputed(item)) {
17
+ d(item);
38
18
  }
39
- dispatch(event, value) {
40
- if (this.listeners === null || this.listeners[event] === undefined) {
41
- return;
42
- }
43
- let listeners = this.listeners[event];
44
- for (let i = 0, n = listeners.length; i < n; i++) {
45
- let listener = listeners[i];
46
- if (listener === null) {
47
- continue;
48
- }
49
- try {
50
- listener(value);
51
- if (listener.once !== undefined) {
52
- listeners[i] = null;
53
- }
54
- }
55
- catch {
56
- listeners[i] = null;
19
+ }
20
+ function clear(data, listeners) {
21
+ dispose(data);
22
+ dispatch(listeners, 'clear');
23
+ }
24
+ function dispatch(listeners, event, value) {
25
+ if (listeners === null || listeners[event] === undefined) {
26
+ return;
27
+ }
28
+ let bucket = listeners[event];
29
+ for (let i = 0, n = bucket.length; i < n; i++) {
30
+ let listener = bucket[i];
31
+ if (listener === null) {
32
+ continue;
33
+ }
34
+ try {
35
+ listener(value);
36
+ if (listener.once !== undefined) {
37
+ bucket[i] = null;
57
38
  }
58
39
  }
59
- }
60
- dispose() {
61
- for (let i = 0, n = this.data.length; i < n; i++) {
62
- this.cleanup(this.data[i]);
63
- }
64
- }
65
- map(fn, i, n) {
66
- let { data, proxy } = this, values = [];
67
- if (i === undefined) {
68
- i = 0;
69
- }
70
- if (n === undefined) {
71
- n = data.length;
72
- }
73
- n = Math.min(n, data.length);
74
- for (; i < n; i++) {
75
- let item = data[i];
76
- values.push(fn.call(proxy, (isComputed(item) ? item.value : item), i));
77
- }
78
- return values;
79
- }
80
- on(event, listener) {
81
- if (this.listeners === null) {
82
- this.listeners = { [event]: [listener] };
83
- }
84
- else {
85
- let listeners = this.listeners[event];
86
- if (listeners === undefined) {
87
- this.listeners[event] = [listener];
88
- }
89
- else if (listeners.indexOf(listener) === -1) {
90
- let i = listeners.indexOf(null);
91
- if (i === -1) {
92
- listeners.push(listener);
93
- }
94
- else {
95
- listeners[i] = listener;
96
- }
97
- }
40
+ catch {
41
+ bucket[i] = null;
98
42
  }
99
43
  }
100
- once(event, listener) {
101
- listener.once = true;
102
- this.on(event, listener);
103
- }
104
- pop() {
105
- let item = this.data.pop();
106
- if (item !== undefined) {
107
- this.cleanup(item);
108
- this.dispatch('pop', { item });
109
- }
110
- return item;
44
+ }
45
+ function dispose(data) {
46
+ let item;
47
+ while (item = data.pop()) {
48
+ cleanup(item);
111
49
  }
112
- push(...input) {
113
- let items = factory(input), n = this.data.push(...items);
114
- this.dispatch('push', { items });
115
- return n;
50
+ }
51
+ function map(data, proxy, fn, i, n) {
52
+ if (i === undefined) {
53
+ i = 0;
116
54
  }
117
- reverse() {
118
- this.data.reverse();
119
- this.dispatch('reverse');
120
- return this;
55
+ if (n === undefined) {
56
+ n = data.length;
121
57
  }
122
- shift() {
123
- let item = this.data.shift();
124
- if (item !== undefined) {
125
- this.cleanup(item);
126
- this.dispatch('shift', { item });
127
- }
128
- return item;
58
+ n = Math.min(n, data.length);
59
+ let values = new Array(n - i);
60
+ for (; i < n; i++) {
61
+ let item = data[i];
62
+ values[i] = fn.call(proxy, (isComputed(item) ? item.value : item), i);
129
63
  }
130
- sort(fn) {
131
- this.data.sort((a, b) => fn((isComputed(a) ? a.value : a), (isComputed(b) ? b.value : b)));
132
- this.dispatch('sort');
133
- return this;
134
- }
135
- splice(start, deleteCount = this.data.length, ...input) {
136
- let items = factory(input), removed = this.data.splice(start, deleteCount, ...items);
137
- if (items.length > 0 || removed.length > 0) {
138
- for (let i = 0, n = removed.length; i < n; i++) {
139
- this.cleanup(removed[i]);
64
+ return values;
65
+ }
66
+ function on(listeners, event, listener) {
67
+ let bucket = listeners[event];
68
+ if (bucket === undefined) {
69
+ listeners[event] = [listener];
70
+ }
71
+ else {
72
+ let hole = bucket.length;
73
+ for (let i = 0, n = hole; i < n; i++) {
74
+ let l = bucket[i];
75
+ if (l === listener) {
76
+ return;
77
+ }
78
+ else if (l === null && hole === n) {
79
+ hole = i;
140
80
  }
141
- this.dispatch('splice', {
142
- deleteCount,
143
- items,
144
- start
145
- });
146
81
  }
147
- return removed;
82
+ bucket[hole] = listener;
148
83
  }
149
- unshift(...input) {
150
- let items = factory(input), length = this.data.unshift(...items);
151
- this.dispatch('unshift', { items });
152
- return length;
84
+ }
85
+ function once(listeners, event, listener) {
86
+ listener.once = true;
87
+ on(listeners, event, listener);
88
+ }
89
+ function pop(data, listeners) {
90
+ let item = data.pop();
91
+ if (item !== undefined) {
92
+ cleanup(item);
93
+ dispatch(listeners, 'pop', { item });
153
94
  }
95
+ return item;
154
96
  }
155
- function factory(input) {
156
- let items = [];
157
- for (let i = 0, n = input.length; i < n; i++) {
158
- let value = input[i];
159
- if (isFunction(value)) {
160
- items[i] = computed(value);
161
- }
162
- else if (isObject(value)) {
163
- items[i] = object(value);
164
- }
165
- else {
166
- items[i] = value;
167
- }
97
+ function push(data, listeners, items) {
98
+ let n = data.push(...items);
99
+ dispatch(listeners, 'push', { items });
100
+ return n;
101
+ }
102
+ function reverse(data, listeners) {
103
+ data.reverse();
104
+ dispatch(listeners, 'reverse');
105
+ }
106
+ function shift(data, listeners) {
107
+ let item = data.shift();
108
+ if (item !== undefined) {
109
+ cleanup(item);
110
+ dispatch(listeners, 'shift', { item });
168
111
  }
169
- return items;
112
+ return item;
170
113
  }
171
- const isReactiveArray = (value) => {
172
- return isObject(value) && REACTIVE_ARRAY in value;
173
- };
174
- export default (input) => {
175
- let proxy = new Proxy({}, {
114
+ function sort(data, listeners, fn) {
115
+ data.sort((a, b) => fn((isComputed(a) ? a.value : a), (isComputed(b) ? b.value : b)));
116
+ dispatch(listeners, 'sort');
117
+ }
118
+ function splice(data, listeners, start, deleteCount = data.length, items = []) {
119
+ let removed = data.splice(start, deleteCount, ...items);
120
+ if (items.length > 0 || removed.length > 0) {
121
+ for (let i = 0, n = removed.length; i < n; i++) {
122
+ cleanup(removed[i]);
123
+ }
124
+ dispatch(listeners, 'splice', {
125
+ deleteCount,
126
+ items,
127
+ start
128
+ });
129
+ }
130
+ return removed;
131
+ }
132
+ function unshift(data, listeners, items) {
133
+ let length = data.unshift(...items);
134
+ dispatch(listeners, 'unshift', { items });
135
+ return length;
136
+ }
137
+ export default (data) => {
138
+ let listeners = {}, proxy = new Proxy({}, {
176
139
  get(_, key) {
177
140
  if (isNumber(key)) {
178
- let value = wrapped[key];
141
+ let value = data[key];
179
142
  if (isComputed(value)) {
180
143
  return read(value);
181
144
  }
182
145
  return value;
183
146
  }
184
- else if (key in array) {
185
- return array[key];
147
+ else if (key in wrapper) {
148
+ return wrapper[key];
149
+ }
150
+ else if (key === 'length') {
151
+ return data.length;
186
152
  }
187
- return wrapped[key];
153
+ return data[key];
188
154
  },
189
155
  set(_, key, value) {
190
156
  if (isNumber(key)) {
191
- array.splice(key, 1, value);
192
- return true;
157
+ splice(data, listeners, key, 1, value);
193
158
  }
194
159
  else if (key === 'length') {
195
- return array.length = value;
160
+ if (value >= data.length) {
161
+ }
162
+ else if (value === 0) {
163
+ clear(data, listeners);
164
+ }
165
+ else {
166
+ splice(data, listeners, value);
167
+ }
196
168
  }
197
- return false;
198
- }
199
- }), wrapped = factory(input);
200
- let array = new ReactiveArray(wrapped, proxy);
169
+ else {
170
+ return false;
171
+ }
172
+ return true;
173
+ }
174
+ }), wrapper = {
175
+ [REACTIVE_ARRAY]: true,
176
+ at: (i) => at(data, i),
177
+ clear: () => {
178
+ clear(data, listeners);
179
+ return proxy;
180
+ },
181
+ dispatch: (event, value) => {
182
+ dispatch(listeners, event, value);
183
+ return proxy;
184
+ },
185
+ dispose: () => {
186
+ dispose(data);
187
+ return proxy;
188
+ },
189
+ map: (fn, i, n) => {
190
+ return map(data, proxy, fn, i, n);
191
+ },
192
+ on: (event, listener) => {
193
+ on(listeners, event, listener);
194
+ return proxy;
195
+ },
196
+ once: (event, listener) => {
197
+ once(listeners, event, listener);
198
+ return proxy;
199
+ },
200
+ pop: () => pop(data, listeners),
201
+ push: (...items) => push(data, listeners, items),
202
+ reverse: () => {
203
+ reverse(data, listeners);
204
+ return proxy;
205
+ },
206
+ shift: () => shift(data, listeners),
207
+ sort: (fn) => {
208
+ sort(data, listeners, fn);
209
+ return proxy;
210
+ },
211
+ splice: (start, deleteCount, ...items) => {
212
+ return splice(data, listeners, start, deleteCount, items);
213
+ },
214
+ unshift: (...items) => unshift(data, listeners, items),
215
+ };
201
216
  return proxy;
202
217
  };
203
- export { isReactiveArray };
@@ -3,7 +3,9 @@ import object from './object.js';
3
3
  type API<T> = T extends Record<PropertyKey, unknown> ? ReturnType<typeof object<T>> : T extends unknown[] ? ReturnType<typeof array<T>> : never;
4
4
  type Input<T> = T extends {
5
5
  dispose: any;
6
- } ? never : T extends Record<PropertyKey, unknown> ? T : T extends unknown[] ? Input<T[number]>[] : never;
7
- declare const _default: <T extends Record<PropertyKey, unknown> | unknown[]>(input: Input<T>) => API<T>;
6
+ } ? {
7
+ never: '[ dispose, signals ] are reserved keys';
8
+ } : T extends Record<PropertyKey, unknown> | unknown[] ? T : never;
9
+ declare const _default: <T>(input: Input<T>) => API<T>;
8
10
  export default _default;
9
11
  export type { Input };
@@ -1,11 +1,14 @@
1
1
  import { Prettify } from '@esportsplus/utilities';
2
2
  import { Infer } from '../types.js';
3
3
  import { REACTIVE_OBJECT } from '../constants.js';
4
- type API<T extends Record<PropertyKey, unknown>> = Prettify<{
4
+ type API<T> = Prettify<{
5
5
  [K in keyof T]: Infer<T[K]>;
6
- }> & ReactiveObject<T>;
6
+ } & {
7
+ dispose: VoidFunction;
8
+ }>;
7
9
  declare class ReactiveObject<T extends Record<PropertyKey, unknown>> {
8
10
  [REACTIVE_OBJECT]: boolean;
11
+ private disposers;
9
12
  constructor(data: T);
10
13
  dispose(): void;
11
14
  }
@@ -1,72 +1,73 @@
1
1
  import { defineProperty, isArray, isFunction, isObject, isPromise } from '@esportsplus/utilities';
2
- import { computed, dispose, effect, isComputed, read, root, set, signal } from '../system.js';
2
+ import { computed, dispose, effect, read, root, set, signal } from '../system.js';
3
3
  import { REACTIVE_OBJECT } from '../constants.js';
4
- import array, { isReactiveArray } from './array.js';
4
+ import array from './array.js';
5
5
  class ReactiveObject {
6
6
  [REACTIVE_OBJECT] = true;
7
+ disposers = null;
7
8
  constructor(data) {
8
- for (let key in data) {
9
- let value = data[key];
9
+ let keys = Object.keys(data);
10
+ for (let i = 0, n = keys.length; i < n; i++) {
11
+ let key = keys[i], value = data[key];
10
12
  if (isArray(value)) {
11
- let a = array(value);
13
+ let node = array(value);
14
+ (this.disposers ??= []).push(() => node.dispose());
12
15
  defineProperty(this, key, {
13
16
  enumerable: true,
14
- get() {
15
- return a;
16
- }
17
+ value: node
17
18
  });
18
19
  }
19
20
  else if (isFunction(value)) {
20
- let c;
21
+ let node;
21
22
  defineProperty(this, key, {
22
23
  enumerable: true,
23
- get() {
24
- if (c === undefined) {
24
+ get: () => {
25
+ if (node === undefined) {
25
26
  root(() => {
26
- c = computed(value);
27
- if (isPromise(c.value)) {
28
- let factory = c, version = 0;
29
- c = signal(undefined);
30
- effect(() => {
27
+ node = computed(value);
28
+ if (isPromise(node.value)) {
29
+ let factory = node, version = 0;
30
+ node = signal(undefined);
31
+ (this.disposers ??= []).push(effect(() => {
31
32
  let id = ++version;
32
- read(factory).then((value) => {
33
+ read(factory).then((v) => {
33
34
  if (id !== version) {
34
35
  return;
35
36
  }
36
- set(c, value);
37
+ set(node, v);
37
38
  });
38
- });
39
+ }));
40
+ }
41
+ else {
42
+ (this.disposers ??= []).push(() => dispose(node));
39
43
  }
40
44
  });
41
45
  }
42
- return read(c);
46
+ return read(node);
43
47
  }
44
48
  });
45
49
  }
46
50
  else {
47
- let s = signal(value);
51
+ let node = signal(value);
48
52
  defineProperty(this, key, {
49
53
  enumerable: true,
50
54
  get() {
51
- return read(s);
55
+ return read(node);
52
56
  },
53
57
  set(v) {
54
- set(s, v);
58
+ set(node, v);
55
59
  }
56
60
  });
57
61
  }
58
62
  }
59
63
  }
60
64
  dispose() {
61
- let value;
62
- for (let key in this) {
63
- value = this[key];
64
- if (isReactiveArray(value) || isReactiveObject(value)) {
65
- value.dispose();
66
- }
67
- else if (isComputed(value)) {
68
- dispose(value);
69
- }
65
+ let disposers = this.disposers, disposer;
66
+ if (!disposers) {
67
+ return;
68
+ }
69
+ while (disposer = disposers.pop()) {
70
+ disposer();
70
71
  }
71
72
  }
72
73
  }