grainjs 1.0.1 → 1.1.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/README.md +23 -26
- package/dist/cjs/index.js +28 -17
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/lib/PriorityQueue.d.ts +2 -2
- package/dist/cjs/lib/PriorityQueue.js +1 -0
- package/dist/cjs/lib/PriorityQueue.js.map +1 -1
- package/dist/cjs/lib/_computed_queue.js +4 -3
- package/dist/cjs/lib/_computed_queue.js.map +1 -1
- package/dist/cjs/lib/binding.d.ts +11 -4
- package/dist/cjs/lib/binding.js +6 -5
- package/dist/cjs/lib/binding.js.map +1 -1
- package/dist/cjs/lib/browserGlobals.d.ts +4 -1
- package/dist/cjs/lib/browserGlobals.js +2 -0
- package/dist/cjs/lib/browserGlobals.js.map +1 -1
- package/dist/cjs/lib/computed.d.ts +49 -28
- package/dist/cjs/lib/computed.js +38 -51
- package/dist/cjs/lib/computed.js.map +1 -1
- package/dist/cjs/lib/dispose.d.ts +109 -96
- package/dist/cjs/lib/dispose.js +106 -79
- package/dist/cjs/lib/dispose.js.map +1 -1
- package/dist/cjs/lib/dom.d.ts +40 -18
- package/dist/cjs/lib/dom.js +63 -29
- package/dist/cjs/lib/dom.js.map +1 -1
- package/dist/cjs/lib/domComponent.d.ts +56 -51
- package/dist/cjs/lib/domComponent.js +46 -44
- package/dist/cjs/lib/domComponent.js.map +1 -1
- package/dist/cjs/lib/domComputed.d.ts +50 -20
- package/dist/cjs/lib/domComputed.js +37 -7
- package/dist/cjs/lib/domComputed.js.map +1 -1
- package/dist/cjs/lib/domDispose.d.ts +27 -12
- package/dist/cjs/lib/domDispose.js +27 -11
- package/dist/cjs/lib/domDispose.js.map +1 -1
- package/dist/cjs/lib/domForEach.d.ts +5 -4
- package/dist/cjs/lib/domForEach.js +41 -41
- package/dist/cjs/lib/domForEach.js.map +1 -1
- package/dist/cjs/lib/domImpl.d.ts +33 -10
- package/dist/cjs/lib/domImpl.js +29 -9
- package/dist/cjs/lib/domImpl.js.map +1 -1
- package/dist/cjs/lib/domMethods.d.ts +93 -47
- package/dist/cjs/lib/domMethods.js +91 -47
- package/dist/cjs/lib/domMethods.js.map +1 -1
- package/dist/cjs/lib/domevent.d.ts +87 -62
- package/dist/cjs/lib/domevent.js +85 -59
- package/dist/cjs/lib/domevent.js.map +1 -1
- package/dist/cjs/lib/emit.d.ts +62 -32
- package/dist/cjs/lib/emit.js +68 -53
- package/dist/cjs/lib/emit.js.map +1 -1
- package/dist/cjs/lib/kowrap.d.ts +6 -3
- package/dist/cjs/lib/kowrap.js +7 -3
- package/dist/cjs/lib/kowrap.js.map +1 -1
- package/dist/cjs/lib/obsArray.d.ts +91 -53
- package/dist/cjs/lib/obsArray.js +87 -54
- package/dist/cjs/lib/obsArray.js.map +1 -1
- package/dist/cjs/lib/observable.d.ts +25 -15
- package/dist/cjs/lib/observable.js +31 -19
- package/dist/cjs/lib/observable.js.map +1 -1
- package/dist/cjs/lib/pureComputed.d.ts +12 -15
- package/dist/cjs/lib/pureComputed.js +16 -18
- package/dist/cjs/lib/pureComputed.js.map +1 -1
- package/dist/cjs/lib/styled.d.ts +78 -61
- package/dist/cjs/lib/styled.js +27 -79
- package/dist/cjs/lib/styled.js.map +1 -1
- package/dist/cjs/lib/subscribe.d.ts +41 -37
- package/dist/cjs/lib/subscribe.js +31 -39
- package/dist/cjs/lib/subscribe.js.map +1 -1
- package/dist/cjs/lib/util.js +2 -0
- package/dist/cjs/lib/util.js.map +1 -1
- package/dist/cjs/lib/widgets/input.d.ts +3 -1
- package/dist/cjs/lib/widgets/input.js +7 -4
- package/dist/cjs/lib/widgets/input.js.map +1 -1
- package/dist/cjs/lib/widgets/select.d.ts +4 -2
- package/dist/cjs/lib/widgets/select.js +8 -5
- package/dist/cjs/lib/widgets/select.js.map +1 -1
- package/dist/esm/lib/_computed_queue.js +3 -3
- package/dist/esm/lib/_computed_queue.js.map +1 -1
- package/dist/esm/lib/binding.js +2 -2
- package/dist/esm/lib/binding.js.map +1 -1
- package/dist/esm/lib/browserGlobals.js +1 -0
- package/dist/esm/lib/browserGlobals.js.map +1 -1
- package/dist/esm/lib/computed.js +36 -50
- package/dist/esm/lib/computed.js.map +1 -1
- package/dist/esm/lib/dispose.js +104 -78
- package/dist/esm/lib/dispose.js.map +1 -1
- package/dist/esm/lib/dom.js +40 -18
- package/dist/esm/lib/dom.js.map +1 -1
- package/dist/esm/lib/domComponent.js +45 -44
- package/dist/esm/lib/domComponent.js.map +1 -1
- package/dist/esm/lib/domComputed.js +32 -5
- package/dist/esm/lib/domComputed.js.map +1 -1
- package/dist/esm/lib/domDispose.js +26 -11
- package/dist/esm/lib/domDispose.js.map +1 -1
- package/dist/esm/lib/domForEach.js +40 -41
- package/dist/esm/lib/domForEach.js.map +1 -1
- package/dist/esm/lib/domImpl.js +26 -7
- package/dist/esm/lib/domImpl.js.map +1 -1
- package/dist/esm/lib/domMethods.js +77 -35
- package/dist/esm/lib/domMethods.js.map +1 -1
- package/dist/esm/lib/domevent.js +84 -59
- package/dist/esm/lib/domevent.js.map +1 -1
- package/dist/esm/lib/emit.js +67 -53
- package/dist/esm/lib/emit.js.map +1 -1
- package/dist/esm/lib/kowrap.js +5 -2
- package/dist/esm/lib/kowrap.js.map +1 -1
- package/dist/esm/lib/obsArray.js +82 -50
- package/dist/esm/lib/obsArray.js.map +1 -1
- package/dist/esm/lib/observable.js +26 -15
- package/dist/esm/lib/observable.js.map +1 -1
- package/dist/esm/lib/pureComputed.js +15 -18
- package/dist/esm/lib/pureComputed.js.map +1 -1
- package/dist/esm/lib/styled.js +24 -77
- package/dist/esm/lib/styled.js.map +1 -1
- package/dist/esm/lib/subscribe.js +27 -36
- package/dist/esm/lib/subscribe.js.map +1 -1
- package/dist/esm/lib/util.js +1 -0
- package/dist/esm/lib/util.js.map +1 -1
- package/dist/esm/lib/widgets/input.js +3 -1
- package/dist/esm/lib/widgets/input.js.map +1 -1
- package/dist/esm/lib/widgets/select.js +3 -1
- package/dist/esm/lib/widgets/select.js.map +1 -1
- package/dist/grain-full.debug.js +2138 -3052
- package/dist/grain-full.debug.js.map +7 -0
- package/dist/grain-full.min.js +6 -2
- package/dist/grain-full.min.js.map +7 -1
- package/lib/binding.ts +9 -2
- package/lib/browserGlobals.ts +3 -1
- package/lib/computed.ts +56 -56
- package/lib/dispose.ts +110 -85
- package/lib/dom.ts +41 -20
- package/lib/domComponent.ts +68 -70
- package/lib/domComputed.ts +66 -21
- package/lib/domDispose.ts +28 -11
- package/lib/domForEach.ts +13 -12
- package/lib/domImpl.ts +30 -7
- package/lib/domMethods.ts +101 -46
- package/lib/domevent.ts +86 -61
- package/lib/emit.ts +64 -50
- package/lib/kowrap.ts +5 -2
- package/lib/obsArray.ts +89 -54
- package/lib/observable.ts +26 -15
- package/lib/pureComputed.ts +16 -22
- package/lib/styled.ts +85 -71
- package/lib/subscribe.ts +41 -45
- package/lib/util.ts +1 -0
- package/lib/widgets/input.ts +3 -1
- package/lib/widgets/select.ts +3 -1
- package/package.json +48 -38
package/lib/binding.ts
CHANGED
|
@@ -10,6 +10,13 @@ import {IKnockoutReadObservable, InferKoType} from './kowrap';
|
|
|
10
10
|
import {BaseObservable} from './observable';
|
|
11
11
|
import {subscribe, UseCBOwner} from './subscribe';
|
|
12
12
|
|
|
13
|
+
/**
|
|
14
|
+
* Any of the value types that DOM methods know how to subscribe to: a plain value (like a
|
|
15
|
+
* string); an Observable (including a Computed); a knockout observable; a function.
|
|
16
|
+
*
|
|
17
|
+
* If a function, it's used to create a `Computed`, and will be called with a context function
|
|
18
|
+
* `use`, allowing it to depend on other observable values (see documentation for `Computed`).
|
|
19
|
+
*/
|
|
13
20
|
export type BindableValue<T> = BaseObservable<T> | ComputedCallback<T> | T | IKnockoutReadObservable<T>;
|
|
14
21
|
|
|
15
22
|
export type ComputedCallback<T> = (use: UseCBOwner, ...args: any[]) => T;
|
|
@@ -64,8 +71,8 @@ export function subscribeBindable<T>(
|
|
|
64
71
|
}
|
|
65
72
|
|
|
66
73
|
/**
|
|
67
|
-
* Subscribes a callback to valueObs (which may be a value, observable, or function) using
|
|
68
|
-
*
|
|
74
|
+
* Subscribes a callback to `valueObs` (which may be a value, observable, or function) using
|
|
75
|
+
* `subscribeBindable()`, and ties the disposal of this subscription to the passed-in element.
|
|
69
76
|
*/
|
|
70
77
|
export function subscribeElem<T>(elem: Node, valueObs: BindableValue<T>,
|
|
71
78
|
callback: (newVal: T, oldVal?: T) => void): void {
|
package/lib/browserGlobals.ts
CHANGED
|
@@ -27,6 +27,8 @@ export interface IBrowserGlobals {
|
|
|
27
27
|
window: typeof window;
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
+
export interface IBrowserGlobalsLax extends IBrowserGlobals {window: any};
|
|
31
|
+
|
|
30
32
|
function _updateGlobals(dest: IBrowserGlobals, source: IBrowserGlobals): void {
|
|
31
33
|
dest.DocumentFragment = source.DocumentFragment;
|
|
32
34
|
dest.Element = source.Element;
|
|
@@ -48,7 +50,7 @@ const _globalsStack: IBrowserGlobals[] = [initial];
|
|
|
48
50
|
/**
|
|
49
51
|
* Replace globals with those from the given object. Use popGlobals() to restore previous values.
|
|
50
52
|
*/
|
|
51
|
-
export function pushGlobals(globals:
|
|
53
|
+
export function pushGlobals(globals: IBrowserGlobalsLax): void {
|
|
52
54
|
_globalsStack.push(globals);
|
|
53
55
|
_updateGlobals(G, globals);
|
|
54
56
|
}
|
package/lib/computed.ts
CHANGED
|
@@ -1,51 +1,68 @@
|
|
|
1
|
+
import {DepItem} from './_computed_queue';
|
|
2
|
+
import {IDisposableOwnerT, setDisposeOwner} from './dispose';
|
|
3
|
+
import {BaseObservable as Obs, Observable} from './observable';
|
|
4
|
+
import {ISubscribable, Subscription, UseCBOwner as UseCB} from './subscribe';
|
|
5
|
+
|
|
6
|
+
function _noWrite(): never {
|
|
7
|
+
throw new Error("Can't write to non-writable computed");
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/** @internal */
|
|
11
|
+
type Owner<T> = IDisposableOwnerT<Computed<T>>|null;
|
|
12
|
+
|
|
1
13
|
/**
|
|
2
|
-
*
|
|
14
|
+
* `Computed` implements a computed observable, whose value depends on other observables and gets
|
|
3
15
|
* recalculated automatically when they change.
|
|
4
16
|
*
|
|
5
|
-
* E.g. if we have some existing observables (which may themselves be instances of `
|
|
17
|
+
* E.g. if we have some existing observables (which may themselves be instances of `Computed`),
|
|
6
18
|
* we can create a computed that subscribes to them explicitly:
|
|
7
|
-
*
|
|
8
|
-
*
|
|
19
|
+
* ```ts
|
|
20
|
+
* const obs1 = Observable.create(null, 5), obs2 = Observable.create(null, 12);
|
|
21
|
+
* const computed1 = Computed.create(null, obs1, obs2, (use, v1, v2) => v1 + v2);
|
|
22
|
+
* ```
|
|
9
23
|
*
|
|
10
24
|
* or implicitly by using `use(obs)` function:
|
|
11
|
-
*
|
|
25
|
+
* ```ts
|
|
26
|
+
* const computed2 = Computed.create(null, use => use(obs1) + use(obs2));
|
|
27
|
+
* ```
|
|
12
28
|
*
|
|
13
|
-
* In either case, computed1.get() and computed2.get() will have the value 17. If obs1 or
|
|
14
|
-
* changed, computed1 and computed2 will get recomputed automatically.
|
|
29
|
+
* In either case, `computed1.get()` and `computed2.get()` will have the value 17. If `obs1` or
|
|
30
|
+
* `obs2` is changed, `computed1` and `computed2` will get recomputed automatically.
|
|
15
31
|
*
|
|
16
32
|
* Creating a computed allows any number of dependencies to be specified explicitly, and their
|
|
17
|
-
* values will be passed to the read() callback. These may be combined with automatic dependencies
|
|
18
|
-
* detected using use()
|
|
19
|
-
*
|
|
20
|
-
*
|
|
33
|
+
* values will be passed to the `read()` callback. These may be combined with automatic dependencies
|
|
34
|
+
* detected using `use()`. Note that constructor dependencies have less overhead.
|
|
35
|
+
* ```ts
|
|
36
|
+
* const val = Computed.create(null, ...deps, ((use, ...depValues) => READ_CALLBACK));
|
|
37
|
+
* ```
|
|
21
38
|
*
|
|
22
39
|
* You may specify a `write` callback by calling `onWrite(WRITE_CALLBACK)`, which will be called
|
|
23
|
-
* whenever set() is called on the computed by its user. If a `write` bacllback is not specified,
|
|
40
|
+
* whenever `set()` is called on the computed by its user. If a `write` bacllback is not specified,
|
|
24
41
|
* calling `set` on a computed observable will throw an exception.
|
|
25
42
|
*
|
|
26
|
-
* Note that
|
|
43
|
+
* Note that `PureComputed` offers a variation of `Computed` with the same interface, but which
|
|
27
44
|
* stays unsubscribed from dependencies while it itself has no subscribers.
|
|
28
45
|
*
|
|
29
46
|
* A computed may be used with a disposable value using `use.owner` as the value's owner. E.g.
|
|
30
|
-
*
|
|
47
|
+
* ```ts
|
|
48
|
+
* const val = Computed.create(null, ((use) => Foo.create(use.owner, use(a), use(b)));
|
|
49
|
+
* ```
|
|
31
50
|
*
|
|
32
|
-
* When the
|
|
33
|
-
* owned value. Note that only the pattern above works, i.e. use.owner may only be used to take
|
|
51
|
+
* When the `Computed` is re-evaluated, and when it itself is disposed, it disposes the previously
|
|
52
|
+
* owned value. Note that only the pattern above works, i.e. `use.owner` may only be used to take
|
|
34
53
|
* ownership of the same disposable that the callback returns.
|
|
35
54
|
*/
|
|
36
|
-
|
|
37
|
-
import {DepItem} from './_computed_queue';
|
|
38
|
-
import {IDisposableOwnerT, setDisposeOwner} from './dispose';
|
|
39
|
-
import {BaseObservable as Obs, Observable} from './observable';
|
|
40
|
-
import {ISubscribable, Subscription, UseCBOwner as UseCB} from './subscribe';
|
|
41
|
-
|
|
42
|
-
function _noWrite(): never {
|
|
43
|
-
throw new Error("Can't write to non-writable computed");
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
type Owner<T> = IDisposableOwnerT<Computed<T>>|null;
|
|
47
|
-
|
|
48
55
|
export class Computed<T> extends Observable<T> {
|
|
56
|
+
/**
|
|
57
|
+
* Creates a new Computed, owned by the given owner.
|
|
58
|
+
* @param owner - Object to own this Computed, or null to handle disposal manually.
|
|
59
|
+
* @param observables - Zero or more observables on which this computes depends. The callback
|
|
60
|
+
* will get called when any of these changes.
|
|
61
|
+
* @param callback - Read callback that will be called with `(use, ...values)`,
|
|
62
|
+
* i.e. the `use` function and values for all of the `...observables`. The callback is called
|
|
63
|
+
* immediately and whenever any dependency changes.
|
|
64
|
+
* @returns The newly created `Computed` observable.
|
|
65
|
+
*/
|
|
49
66
|
// Still need repetitive declarations to support varargs that are not the final argument.
|
|
50
67
|
public static create<T>(
|
|
51
68
|
owner: Owner<T>, cb: (use: UseCB) => T): Computed<T>;
|
|
@@ -64,17 +81,6 @@ export class Computed<T> extends Observable<T> {
|
|
|
64
81
|
public static create<T, A, B, C, D, E>(
|
|
65
82
|
owner: Owner<T>, a: Obs<A>, b: Obs<B>, c: Obs<C>, d: Obs<D>, e: Obs<E>,
|
|
66
83
|
cb: (use: UseCB, a: A, b: B, c: C, d: D, e: E) => T): Computed<T>;
|
|
67
|
-
|
|
68
|
-
/**
|
|
69
|
-
* Creates a new Computed, owned by the given owner.
|
|
70
|
-
* @param owner: Object to own this Computed, or null to handle disposal manually.
|
|
71
|
-
* @param ...observables: Zero or more observables on which this computes depends. The callback
|
|
72
|
-
* will get called when any of these changes.
|
|
73
|
-
* @param callback: Read callback that will be called with (use, ...values),
|
|
74
|
-
* i.e. the `use` function and values for all of the ...observables. The callback is called
|
|
75
|
-
* immediately and whenever any dependency changes.
|
|
76
|
-
* @returns {Computed} The newly created computed observable.
|
|
77
|
-
*/
|
|
78
84
|
public static create<T>(owner: IDisposableOwnerT<Computed<T>>|null, ...args: any[]): Computed<T> {
|
|
79
85
|
const readCb = args.pop();
|
|
80
86
|
return setDisposeOwner(owner, new Computed<T>(readCb, args));
|
|
@@ -84,9 +90,7 @@ export class Computed<T> extends Observable<T> {
|
|
|
84
90
|
private _write: (value: T) => void;
|
|
85
91
|
private _sub: Subscription;
|
|
86
92
|
|
|
87
|
-
|
|
88
|
-
* Internal constructor for a Computed observable. You should use computed() function instead.
|
|
89
|
-
*/
|
|
93
|
+
// Internal constructor for a Computed observable. You should use computed() function instead.
|
|
90
94
|
constructor(callback: (use: UseCB, ...args: any[]) => T, dependencies: ISubscribable[]) {
|
|
91
95
|
// At initialization we force an undefined value even though it's not of type T: it gets set
|
|
92
96
|
// to a proper value during the creation of new Subscription, which calls this._read.
|
|
@@ -98,6 +102,7 @@ export class Computed<T> extends Observable<T> {
|
|
|
98
102
|
|
|
99
103
|
/**
|
|
100
104
|
* Used by subscriptions to keep track of dependencies.
|
|
105
|
+
* @internal
|
|
101
106
|
*/
|
|
102
107
|
public _getDepItem(): DepItem {
|
|
103
108
|
return this._sub._getDepItem();
|
|
@@ -106,7 +111,7 @@ export class Computed<T> extends Observable<T> {
|
|
|
106
111
|
/**
|
|
107
112
|
* "Sets" the value of the computed by calling the write() callback if one was provided in the
|
|
108
113
|
* constructor. Throws an error if there was no such callback (not a "writable" computed).
|
|
109
|
-
* @param
|
|
114
|
+
* @param value - The value to pass to the write() callback.
|
|
110
115
|
*/
|
|
111
116
|
public set(value: T): void { this._write(value); }
|
|
112
117
|
|
|
@@ -133,9 +138,14 @@ export class Computed<T> extends Observable<T> {
|
|
|
133
138
|
}
|
|
134
139
|
|
|
135
140
|
/**
|
|
136
|
-
*
|
|
137
|
-
*
|
|
138
|
-
*
|
|
141
|
+
* Creates a new Computed.
|
|
142
|
+
* @param observables - The initial params, of which there may be zero or more, are
|
|
143
|
+
* observables on which this computed depends. When any of them change, the `read()` callback
|
|
144
|
+
* will be called with the values of these observables as arguments.
|
|
145
|
+
* @param readCallback - Read callback that will be called with `(use, ...values)`,
|
|
146
|
+
* i.e. the `use` function and values for all of the `...observables`. The callback is called
|
|
147
|
+
* immediately and whenever any dependency changes.
|
|
148
|
+
* @returns The newly created `Computed` observable.
|
|
139
149
|
*/
|
|
140
150
|
export function computed<T>(cb: (use: UseCB) => T): Computed<T>;
|
|
141
151
|
|
|
@@ -159,16 +169,6 @@ export function computed<T, A, B, C, D, E>(
|
|
|
159
169
|
a: Obs<A>, b: Obs<B>, c: Obs<C>, d: Obs<D>, e: Obs<E>,
|
|
160
170
|
cb: (use: UseCB, a: A, b: B, c: C, d: D, e: E) => T): Computed<T>;
|
|
161
171
|
|
|
162
|
-
/**
|
|
163
|
-
* Creates a new Computed.
|
|
164
|
-
* @param {Observable} ...observables: The initial params, of which there may be zero or more, are
|
|
165
|
-
* observables on which this computed depends. When any of them change, the read() callback
|
|
166
|
-
* will be called with the values of these observables as arguments.
|
|
167
|
-
* @param {Function} readCallback: Read callback that will be called with (use, ...values),
|
|
168
|
-
* i.e. the `use` function and values for all of the ...observables. The callback is called
|
|
169
|
-
* immediately and whenever any dependency changes.
|
|
170
|
-
* @returns {Computed} The newly created computed observable.
|
|
171
|
-
*/
|
|
172
172
|
export function computed(...args: any[]): Computed<any> {
|
|
173
173
|
const readCb = args.pop();
|
|
174
174
|
return new Computed<any>(readCb, args);
|
package/lib/dispose.ts
CHANGED
|
@@ -1,75 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* dispose.js provides tools to objects that needs to dispose resources, such as destroy DOM, and
|
|
3
|
-
* unsubscribe from events. The motivation with examples is presented here:
|
|
4
|
-
*
|
|
5
|
-
* https://phab.getgrist.com/w/disposal/
|
|
6
|
-
*
|
|
7
|
-
* Disposable is a class for components that need cleanup (e.g. maintain DOM, listen to events,
|
|
8
|
-
* subscribe to anything). It provides a .dispose() method that should be called to destroy the
|
|
9
|
-
* component, and .onDispose()/.autoDispose() methods that the component should use to take
|
|
10
|
-
* responsibility for other pieces that require cleanup.
|
|
11
|
-
*
|
|
12
|
-
* To define a disposable class:
|
|
13
|
-
* class Foo extends Disposable { ... }
|
|
14
|
-
*
|
|
15
|
-
* To create Foo:
|
|
16
|
-
* const foo = Foo.create(owner, ...args);
|
|
17
|
-
* This is better than `new Foo` for two reasons:
|
|
18
|
-
* 1. If Foo's constructor throws an exception, any disposals registered in that constructor
|
|
19
|
-
* before the exception are honored.
|
|
20
|
-
* 2. It ensures you specify the owner of the new instance (but you can use null to skip it).
|
|
21
|
-
*
|
|
22
|
-
* In Foo's constructor (or rarely methods), take ownership of other Disposable objects:
|
|
23
|
-
* this.bar = Bar.create(this, ...);
|
|
24
|
-
*
|
|
25
|
-
* For objects that are not instances of Disposable but have a .dispose() methods, use:
|
|
26
|
-
* this.bar = this.autoDispose(createSomethingDisposable());
|
|
27
|
-
*
|
|
28
|
-
* To call a function on disposal (e.g. to add custom disposal logic):
|
|
29
|
-
* this.onDispose(() => this.myUnsubscribeAllMethod());
|
|
30
|
-
* this.onDispose(this.myUnsubscribeAllMethod, this); // slightly more efficient
|
|
31
|
-
*
|
|
32
|
-
* To mark this object to be wiped out on disposal (i.e. set all properties to null):
|
|
33
|
-
* this.wipeOnDispose();
|
|
34
|
-
* See the documentation of that method for more info.
|
|
35
|
-
*
|
|
36
|
-
* To dispose Foo directly:
|
|
37
|
-
* foo.dispose();
|
|
38
|
-
* To determine if an object has already been disposed:
|
|
39
|
-
* foo.isDisposed()
|
|
40
|
-
*
|
|
41
|
-
* If you need to replace an owned object, or release, or dispose it early, use a Holder:
|
|
42
|
-
* this._holder = Holder.create(this);
|
|
43
|
-
* Bar.create(this._holder, 1); // creates new Bar(1)
|
|
44
|
-
* Bar.create(this._holder, 2); // creates new Bar(2) and disposes previous object
|
|
45
|
-
* this._holder.clear(); // disposes contained object
|
|
46
|
-
* this._holder.release(); // releases contained object
|
|
47
|
-
*
|
|
48
|
-
* If you need a container for multiple objects and dispose them all together, use a MultiHolder:
|
|
49
|
-
* this._mholder = MultiHolder.create(null);
|
|
50
|
-
* Bar.create(this._mholder, 1); // create new Bar(1)
|
|
51
|
-
* Bar.create(this._mholder, 2); // create new Bar(2)
|
|
52
|
-
* this._mholder.dispose(); // disposes both objects
|
|
53
|
-
*
|
|
54
|
-
* If creating your own class with a dispose() method, do NOT throw exceptions from dispose().
|
|
55
|
-
* These cannot be handled properly in all cases. Read here about the same issue in C++:
|
|
56
|
-
* http://bin-login.name/ftp/pub/docs/programming_languages/cpp/cffective_cpp/MAGAZINE/SU_FRAME.HTM#destruct
|
|
57
|
-
*
|
|
58
|
-
* Using a parametrized (generic) class as a Disposable is tricky. E.g.
|
|
59
|
-
* class Bar<T> extends Disposable { ... }
|
|
60
|
-
* // Bar<T>.create(...) <-- doesn't work
|
|
61
|
-
* // Bar.create<T>(...) <-- doesn't work
|
|
62
|
-
* // Bar.create(...) <-- works, but with {} for Bar's type parameters
|
|
63
|
-
*
|
|
64
|
-
* The solution is to expose the constructor type using a helper method:
|
|
65
|
-
* class Bar<T> extends Disposable {
|
|
66
|
-
* // Note the tuple below which must match the constructor parameters of Bar<U>.
|
|
67
|
-
* public static ctor<U>(): IDisposableCtor<Bar<U>, [U, boolean]> { return this; }
|
|
68
|
-
* constructor(a: T, b: boolean) { ... }
|
|
69
|
-
* }
|
|
70
|
-
* Bar.ctor<T>().create(...) // <-- works, creates Bar<T>, and does type-checking!
|
|
71
|
-
*/
|
|
72
|
-
|
|
73
1
|
import {LLink} from './emit';
|
|
74
2
|
|
|
75
3
|
/**
|
|
@@ -80,7 +8,7 @@ export interface IDisposable {
|
|
|
80
8
|
}
|
|
81
9
|
|
|
82
10
|
/**
|
|
83
|
-
* Anything with
|
|
11
|
+
* Anything with `.autoDispose()` can be the owner of a disposable object. This is a type-specific
|
|
84
12
|
* class that can only own a disposable object of type T.
|
|
85
13
|
*/
|
|
86
14
|
export interface IDisposableOwnerT<T extends IDisposable> {
|
|
@@ -114,16 +42,87 @@ export interface IDisposableCtor<Derived, CtorArgs extends any[]> {
|
|
|
114
42
|
}
|
|
115
43
|
|
|
116
44
|
/**
|
|
117
|
-
* Base class for disposable objects that can own other objects.
|
|
45
|
+
* Base class for disposable objects that can own other objects.
|
|
46
|
+
*
|
|
47
|
+
* For background and motivation, see [Disposables](/dispose).
|
|
48
|
+
*
|
|
49
|
+
* `Disposable` is a class for components that need cleanup (e.g. maintain DOM, listen to events,
|
|
50
|
+
* subscribe to anything). It provides a `.dispose()` method that should be called to destroy the
|
|
51
|
+
* component, and `.onDispose()` / `.autoDispose()` methods that the component should use to take
|
|
52
|
+
* responsibility for other pieces that require cleanup.
|
|
53
|
+
*
|
|
54
|
+
* To define a disposable class:
|
|
55
|
+
* ```ts
|
|
56
|
+
* class Foo extends Disposable { ... }
|
|
57
|
+
* ```
|
|
58
|
+
*
|
|
59
|
+
* To create `Foo`:
|
|
60
|
+
* ```ts
|
|
61
|
+
* const foo = Foo.create(owner, ...args);
|
|
62
|
+
* ```
|
|
63
|
+
* This is better than `new Foo` for two reasons:
|
|
64
|
+
* 1. If `Foo`'s constructor throws an exception, any disposals registered in that constructor
|
|
65
|
+
* before the exception are honored.
|
|
66
|
+
* 2. It ensures you specify the owner of the new instance (but you can use null to skip it).
|
|
67
|
+
*
|
|
68
|
+
* In `Foo`'s constructor (or rarely methods), take ownership of other Disposable objects:
|
|
69
|
+
* ```ts
|
|
70
|
+
* this.bar = Bar.create(this, ...);
|
|
71
|
+
* ```
|
|
72
|
+
*
|
|
73
|
+
* For objects that are not instances of Disposable but have a .dispose() methods, use:
|
|
74
|
+
* ```ts
|
|
75
|
+
* this.bar = this.autoDispose(createSomethingDisposable());
|
|
76
|
+
* ```
|
|
77
|
+
*
|
|
78
|
+
* To call a function on disposal (e.g. to add custom disposal logic):
|
|
79
|
+
* ```ts
|
|
80
|
+
* this.onDispose(() => this.myUnsubscribeAllMethod());
|
|
81
|
+
* this.onDispose(this.myUnsubscribeAllMethod, this);
|
|
82
|
+
* ```
|
|
83
|
+
*
|
|
84
|
+
* To mark this object to be wiped out on disposal (i.e. set all properties to null):
|
|
85
|
+
* ```ts
|
|
86
|
+
* this.wipeOnDispose();
|
|
87
|
+
* ```
|
|
88
|
+
* See the documentation of that method for more info.
|
|
89
|
+
*
|
|
90
|
+
* To dispose Foo directly: `foo.dispose()`.
|
|
91
|
+
*
|
|
92
|
+
* To determine if an object has already been disposed: `foo.isDisposed()`.
|
|
93
|
+
*
|
|
94
|
+
* If you need to replace an owned object, or release, or dispose it early, use a
|
|
95
|
+
* [`Holder`](#Holder) or [`MultiHolder`](#MultiHolder).
|
|
96
|
+
*
|
|
97
|
+
* If creating your own class with a `dispose()` method, do NOT throw exceptions from `dispose()`.
|
|
98
|
+
* These cannot be handled properly in all cases.
|
|
99
|
+
*
|
|
100
|
+
* Using a parametrized (generic) class as a Disposable is tricky. E.g.
|
|
101
|
+
* ```ts
|
|
102
|
+
* class Bar<T> extends Disposable { ... }
|
|
103
|
+
* // Bar<T>.create(...) <-- doesn't work
|
|
104
|
+
* // Bar.create<T>(...) <-- doesn't work
|
|
105
|
+
* // Bar.create(...) <-- works, but with {} for Bar's type parameters
|
|
106
|
+
* ```
|
|
107
|
+
*
|
|
108
|
+
* The solution is to expose the constructor type using a helper method:
|
|
109
|
+
* ```ts
|
|
110
|
+
* class Bar<T> extends Disposable {
|
|
111
|
+
* // Note the tuple below which must match the constructor parameters of Bar<U>.
|
|
112
|
+
* public static ctor<U>(): IDisposableCtor<Bar<U>, [U, boolean]> { return this; }
|
|
113
|
+
* constructor(a: T, b: boolean) { ... }
|
|
114
|
+
* }
|
|
115
|
+
* Bar.ctor<T>().create(...) // <-- works, creates Bar<T>, and does type-checking!
|
|
116
|
+
* ```
|
|
118
117
|
*/
|
|
119
118
|
export abstract class Disposable implements IDisposable, IDisposableOwner {
|
|
120
119
|
/**
|
|
121
120
|
* Create Disposable instances using `Class.create(owner, ...)` rather than `new Class(...)`.
|
|
122
121
|
*
|
|
123
122
|
* This reminds you to provide an owner, and ensures that if the constructor throws an
|
|
124
|
-
* exception, dispose() gets called to clean up the partially-constructed object.
|
|
123
|
+
* exception, `dispose()` gets called to clean up the partially-constructed object.
|
|
125
124
|
*
|
|
126
|
-
* Owner may be null if intend to ensure disposal some other way.
|
|
125
|
+
* Owner may be `null` if you intend to ensure disposal some other way.
|
|
127
126
|
*/
|
|
128
127
|
public static create<T extends new (...args: any[]) => any>(
|
|
129
128
|
this: T, owner: IDisposableOwnerT<InstanceType<T>>|null, ...args: ConstructorParameters<T>): InstanceType<T> {
|
|
@@ -161,14 +160,14 @@ export abstract class Disposable implements IDisposable, IDisposableOwner {
|
|
|
161
160
|
_defaultDisposableOwner = _noopOwner;
|
|
162
161
|
}
|
|
163
162
|
|
|
164
|
-
/** Take ownership of obj
|
|
163
|
+
/** Take ownership of `obj`, and dispose it when `this.dispose()` is called. */
|
|
165
164
|
public autoDispose<T extends IDisposable>(obj: T): T {
|
|
166
165
|
this.onDispose(obj.dispose, obj);
|
|
167
166
|
return obj;
|
|
168
167
|
}
|
|
169
168
|
|
|
170
|
-
/** Call the given callback when this.dispose() is called. */
|
|
171
|
-
public onDispose<T>(callback: (this: T) => void, context?: T):
|
|
169
|
+
/** Call the given callback when `this.dispose()` is called. */
|
|
170
|
+
public onDispose<T>(callback: (this: T) => void, context?: T): IDisposable {
|
|
172
171
|
return this._disposalList.addListener(callback, context);
|
|
173
172
|
}
|
|
174
173
|
|
|
@@ -177,10 +176,13 @@ export abstract class Disposable implements IDisposable, IDisposableOwner {
|
|
|
177
176
|
* recommended to call this early in the constructor.
|
|
178
177
|
*
|
|
179
178
|
* This makes disposal more costly, but has certain benefits:
|
|
179
|
+
*
|
|
180
180
|
* - If anything still refers to the object and uses it, we'll get an early error, rather than
|
|
181
181
|
* silently keep going, potentially doing useless work (or worse) and wasting resources.
|
|
182
|
+
*
|
|
182
183
|
* - If anything still refers to the object (even without using it), the fields of the object
|
|
183
184
|
* can still be garbage-collected.
|
|
185
|
+
*
|
|
184
186
|
* - If there are circular references involving this object, they get broken, making the job
|
|
185
187
|
* easier for the garbage collector.
|
|
186
188
|
*
|
|
@@ -199,7 +201,7 @@ export abstract class Disposable implements IDisposable, IDisposableOwner {
|
|
|
199
201
|
}
|
|
200
202
|
|
|
201
203
|
/**
|
|
202
|
-
* Clean up `this` by disposing all owned objects, and calling onDispose() callbacks, in reverse
|
|
204
|
+
* Clean up `this` by disposing all owned objects, and calling `onDispose()` callbacks, in reverse
|
|
203
205
|
* order to that in which they were added.
|
|
204
206
|
*/
|
|
205
207
|
public dispose(): void {
|
|
@@ -229,19 +231,33 @@ export abstract class Disposable implements IDisposable, IDisposableOwner {
|
|
|
229
231
|
|
|
230
232
|
/**
|
|
231
233
|
* Holder keeps a single disposable object. If given responsibility for another object using
|
|
232
|
-
* holder.autoDispose() or Foo.create(holder, ...)
|
|
234
|
+
* `holder.autoDispose()` or `Foo.create(holder, ...)`, it automatically disposes the currently held
|
|
233
235
|
* object. It also disposes it when the holder itself is disposed.
|
|
234
236
|
*
|
|
235
|
-
* If the object is an instance of Disposable
|
|
237
|
+
* If the object is an instance of `Disposable`, the holder will also notice when the object gets
|
|
236
238
|
* disposed from outside of it, in which case the holder will become empty again.
|
|
239
|
+
*
|
|
240
|
+
* If you need a container for multiple objects and dispose them all together, use a `MultiHolder`:
|
|
241
|
+
*
|
|
242
|
+
* :::info Example
|
|
243
|
+
* ```ts
|
|
244
|
+
* this._holder = Holder.create(this);
|
|
245
|
+
* Bar.create(this._holder, 1); // creates new Bar(1), assuming it's a Disposable
|
|
246
|
+
* Bar.create(this._holder, 2); // creates new Bar(2) and disposes previous object
|
|
247
|
+
* this._holder.clear(); // disposes contained object
|
|
248
|
+
* this._holder.release(); // releases contained object
|
|
249
|
+
* ```
|
|
250
|
+
* :::
|
|
237
251
|
*/
|
|
238
252
|
export class Holder<T extends IDisposable> implements IDisposable, IDisposableOwner {
|
|
253
|
+
/** `Holder.create(owner)` creates a new `Holder`. */
|
|
239
254
|
public static create<T extends IDisposable>(owner: IDisposableOwnerT<Holder<T>>|null): Holder<T> {
|
|
240
255
|
return setDisposeOwner(owner, new Holder<T>());
|
|
241
256
|
}
|
|
242
257
|
|
|
258
|
+
/** @internal */
|
|
243
259
|
protected _owned: T|null = null;
|
|
244
|
-
private _disposalListener:
|
|
260
|
+
private _disposalListener: IDisposable|undefined = undefined;
|
|
245
261
|
|
|
246
262
|
/** Take ownership of a new object, disposing the previously held one. */
|
|
247
263
|
public autoDispose(obj: T): T {
|
|
@@ -296,9 +312,18 @@ export class Holder<T extends IDisposable> implements IDisposable, IDisposableOw
|
|
|
296
312
|
}
|
|
297
313
|
|
|
298
314
|
/**
|
|
299
|
-
* MultiHolder keeps multiple disposable
|
|
300
|
-
* itself is disposed. It's actually nothing more than the Disposable base class itself, just
|
|
315
|
+
* `MultiHolder` keeps multiple disposable objects. It disposes all held object when the holder
|
|
316
|
+
* itself is disposed. It's actually nothing more than the `Disposable` base class itself, just
|
|
301
317
|
* exposed with a clearer name that describes its purpose.
|
|
318
|
+
*
|
|
319
|
+
* :::info Example
|
|
320
|
+
* ```ts
|
|
321
|
+
* this._mholder = MultiHolder.create(null);
|
|
322
|
+
* Bar.create(this._mholder, 1); // create new Bar(1)
|
|
323
|
+
* Bar.create(this._mholder, 2); // create new Bar(2)
|
|
324
|
+
* this._mholder.dispose(); // disposes both objects
|
|
325
|
+
* ```
|
|
326
|
+
* :::
|
|
302
327
|
*/
|
|
303
328
|
export class MultiHolder extends Disposable {}
|
|
304
329
|
|
package/lib/dom.ts
CHANGED
|
@@ -1,22 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* dom.js provides a way to build a DOM tree easily.
|
|
3
|
-
*
|
|
4
|
-
* E.g.
|
|
5
|
-
* import {dom} from 'grainjs';
|
|
6
|
-
* dom('a#link.c1.c2', {'href': url}, 'Hello ', dom('span', 'world'));
|
|
7
|
-
* creates Node <a id="link" class="c1 c2" href={{url}}Hello <span>world</span></a>.
|
|
8
|
-
*
|
|
9
|
-
* dom.frag(dom('span', 'Hello'), ['blah', dom('div', 'world')])
|
|
10
|
-
* creates document fragment with <span>Hello</span>blah<div>world</div>.
|
|
11
|
-
*
|
|
12
|
-
* DOM can also be created and modified inline during creation:
|
|
13
|
-
* dom('a#id.c1',
|
|
14
|
-
* dom.cls('c2'), dom.attr('href', url),
|
|
15
|
-
* dom.text('Hello '), dom('span', dom.text('world')))
|
|
16
|
-
* creates Node <a id="link" class="c1 c2" href={{url}}Hello <span>world</span></a>,
|
|
17
|
-
* identical to the first example above.
|
|
18
|
-
*/
|
|
19
|
-
|
|
20
1
|
// We keep various dom-related functions organized in private modules, but they are exposed here.
|
|
21
2
|
export * from './domImpl';
|
|
22
3
|
export * from './domComponent';
|
|
@@ -37,6 +18,44 @@ import * as domevent from './domevent';
|
|
|
37
18
|
|
|
38
19
|
import {dom as _dom, IDomArgs, TagElem, TagName} from './domImpl';
|
|
39
20
|
|
|
21
|
+
/**
|
|
22
|
+
* `dom()` provides a way to build a DOM tree easily.
|
|
23
|
+
*
|
|
24
|
+
* The first argument is a string consisting of a lowercase tag name (e.g. `"div"`), with optional
|
|
25
|
+
* `"#foo"` suffix to add the ID `'foo'`, and zero or more `".bar"` suffixes to add a CSS class
|
|
26
|
+
* `'bar'`.
|
|
27
|
+
*
|
|
28
|
+
* The rest of the arguments are optional and may be any number, of these types:
|
|
29
|
+
*
|
|
30
|
+
* @param Nodes - become children of the created element
|
|
31
|
+
* @param strings - become text node children
|
|
32
|
+
* @param objects - Literal objects to set string attributes on the element.
|
|
33
|
+
* E.g. `{title: "foo"}`.
|
|
34
|
+
* @param null - The values `null` and `undefined` values are ignored completely.
|
|
35
|
+
* @param Arrays - flattened with each item processed recursively
|
|
36
|
+
* @param functions - called with the element being built as the argument, for a chance to modify
|
|
37
|
+
* the element as it's being created. Return values are processed recursively.
|
|
38
|
+
* @param functions - "dom methods" are a expressions such as `dom.attr('href', url)` or
|
|
39
|
+
* `dom.hide(obs)`, which are special cases of the "functions" category.
|
|
40
|
+
*
|
|
41
|
+
* @example
|
|
42
|
+
* ```ts
|
|
43
|
+
* import {dom} from 'grainjs';
|
|
44
|
+
* dom('a', {href: url, className: 'myclass'}, 'Hello ', dom('strong', 'world'));
|
|
45
|
+
* ```
|
|
46
|
+
* creates HTML element `<a href={{url}} class="myclass">Hello <strong>world</strong></a>`.
|
|
47
|
+
*
|
|
48
|
+
* @example
|
|
49
|
+
* Here's an example equivalent to the one above, using dom methods `dom.cls`, `dom.attr`,
|
|
50
|
+
* `dom.text`. In reality, these methods are useful with observable values rather than constant
|
|
51
|
+
* strings.
|
|
52
|
+
* ```ts
|
|
53
|
+
* dom('a', dom.attr('href', url), dom.cls('myclass'),
|
|
54
|
+
* dom.text('Hello '), dom('strong', dom.text('world')));
|
|
55
|
+
* ```
|
|
56
|
+
*
|
|
57
|
+
* @see [DOM & Observables](/basics).
|
|
58
|
+
*/
|
|
40
59
|
// We just want to re-export _domImpl.dom, but to allow adding methods to it in a typesafe way,
|
|
41
60
|
// TypeScript wants us to declare a real function in the same file.
|
|
42
61
|
export function dom<Tag extends TagName>(tagString: Tag, ...args: IDomArgs<TagElem<Tag>>): TagElem<Tag> {
|
|
@@ -44,7 +63,7 @@ export function dom<Tag extends TagName>(tagString: Tag, ...args: IDomArgs<TagEl
|
|
|
44
63
|
}
|
|
45
64
|
|
|
46
65
|
// Additionally export all methods as properties of dom() function.
|
|
47
|
-
export namespace dom { //
|
|
66
|
+
export namespace dom { // eslint-disable-line @typescript-eslint/no-namespace
|
|
48
67
|
export const svg = _domImpl.svg;
|
|
49
68
|
export const frag = _domImpl.frag;
|
|
50
69
|
export const update = _domImpl.update;
|
|
@@ -81,7 +100,9 @@ export namespace dom { // tslint:disable-line:no-namespace
|
|
|
81
100
|
export const getData = _domMethods.getData;
|
|
82
101
|
export const replaceContent = _domComputed.replaceContent;
|
|
83
102
|
export const domComputed = _domComputed.domComputed;
|
|
103
|
+
export const domComputedOwned = _domComputed.domComputedOwned;
|
|
84
104
|
export const maybe = _domComputed.maybe;
|
|
105
|
+
export const maybeOwned = _domComputed.maybeOwned;
|
|
85
106
|
|
|
86
107
|
export const forEach = _domForEach.forEach;
|
|
87
108
|
|