grainjs 0.1.0 → 1.0.2
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 +54 -9
- package/dist/cjs/index.d.ts +6 -2
- package/dist/cjs/index.js +24 -17
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/lib/PriorityQueue.d.ts +1 -1
- package/dist/cjs/lib/PriorityQueue.js +1 -0
- package/dist/cjs/lib/PriorityQueue.js.map +1 -1
- package/dist/cjs/lib/_computed_queue.d.ts +18 -0
- package/dist/cjs/lib/_computed_queue.js +6 -1
- package/dist/cjs/lib/_computed_queue.js.map +1 -1
- package/dist/cjs/lib/binding.d.ts +16 -10
- package/dist/cjs/lib/binding.js +22 -27
- 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 +11 -7
- package/dist/cjs/lib/computed.js +16 -0
- package/dist/cjs/lib/computed.js.map +1 -1
- package/dist/cjs/lib/dispose.d.ts +106 -14
- package/dist/cjs/lib/dispose.js +76 -11
- package/dist/cjs/lib/dispose.js.map +1 -1
- package/dist/cjs/lib/dom.d.ts +21 -17
- package/dist/cjs/lib/dom.js +33 -26
- package/dist/cjs/lib/dom.js.map +1 -1
- package/dist/cjs/lib/domComponent.d.ts +71 -0
- package/dist/cjs/lib/domComponent.js +15 -0
- package/dist/cjs/lib/domComponent.js.map +1 -0
- package/dist/cjs/lib/domComputed.d.ts +89 -0
- package/dist/cjs/lib/domComputed.js +92 -0
- package/dist/cjs/lib/domComputed.js.map +1 -0
- package/dist/cjs/lib/{_domDispose.d.ts → domDispose.d.ts} +12 -2
- package/dist/cjs/lib/{_domDispose.js → domDispose.js} +21 -8
- package/dist/cjs/lib/domDispose.js.map +1 -0
- package/dist/cjs/lib/{_domForEach.d.ts → domForEach.d.ts} +2 -2
- package/dist/cjs/lib/domForEach.js +72 -0
- package/dist/cjs/lib/domForEach.js.map +1 -0
- package/dist/cjs/lib/{_domImpl.d.ts → domImpl.d.ts} +15 -12
- package/dist/cjs/lib/{_domImpl.js → domImpl.js} +23 -6
- package/dist/cjs/lib/domImpl.js.map +1 -0
- package/dist/cjs/lib/{_domMethods.d.ts → domMethods.d.ts} +27 -62
- package/dist/cjs/lib/{_domMethods.js → domMethods.js} +21 -76
- package/dist/cjs/lib/domMethods.js.map +1 -0
- package/dist/cjs/lib/domevent.d.ts +32 -21
- package/dist/cjs/lib/domevent.js +33 -12
- package/dist/cjs/lib/domevent.js.map +1 -1
- package/dist/cjs/lib/emit.d.ts +25 -2
- package/dist/cjs/lib/emit.js +3 -1
- package/dist/cjs/lib/emit.js.map +1 -1
- package/dist/cjs/lib/kowrap.d.ts +45 -3
- package/dist/cjs/lib/kowrap.js +93 -10
- package/dist/cjs/lib/kowrap.js.map +1 -1
- package/dist/cjs/lib/obsArray.d.ts +8 -8
- package/dist/cjs/lib/obsArray.js +1 -0
- package/dist/cjs/lib/obsArray.js.map +1 -1
- package/dist/cjs/lib/observable.d.ts +6 -1
- package/dist/cjs/lib/observable.js +11 -2
- package/dist/cjs/lib/observable.js.map +1 -1
- package/dist/cjs/lib/pureComputed.d.ts +3 -3
- package/dist/cjs/lib/pureComputed.js +2 -1
- package/dist/cjs/lib/pureComputed.js.map +1 -1
- package/dist/cjs/lib/styled.d.ts +76 -11
- package/dist/cjs/lib/styled.js +55 -23
- package/dist/cjs/lib/styled.js.map +1 -1
- package/dist/cjs/lib/subscribe.d.ts +15 -6
- package/dist/cjs/lib/subscribe.js +6 -2
- package/dist/cjs/lib/subscribe.js.map +1 -1
- package/dist/cjs/lib/util.js +1 -0
- package/dist/cjs/lib/util.js.map +1 -1
- package/dist/cjs/lib/widgets/input.d.ts +2 -2
- package/dist/cjs/lib/widgets/input.js +2 -2
- package/dist/cjs/lib/widgets/input.js.map +1 -1
- package/dist/cjs/lib/widgets/select.d.ts +1 -1
- package/dist/cjs/lib/widgets/select.js +1 -0
- package/dist/cjs/lib/widgets/select.js.map +1 -1
- package/dist/esm/index.js +6 -2
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/lib/PriorityQueue.js.map +1 -1
- package/dist/esm/lib/_computed_queue.js +5 -1
- package/dist/esm/lib/_computed_queue.js.map +1 -1
- package/dist/esm/lib/binding.js +20 -27
- 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 +15 -0
- package/dist/esm/lib/computed.js.map +1 -1
- package/dist/esm/lib/dispose.js +74 -11
- package/dist/esm/lib/dispose.js.map +1 -1
- package/dist/esm/lib/dom.js +21 -17
- package/dist/esm/lib/dom.js.map +1 -1
- package/dist/esm/lib/domComponent.js +11 -0
- package/dist/esm/lib/domComponent.js.map +1 -0
- package/dist/esm/lib/domComputed.js +84 -0
- package/dist/esm/lib/domComputed.js.map +1 -0
- package/dist/esm/lib/{_domDispose.js → domDispose.js} +19 -8
- package/dist/esm/lib/domDispose.js.map +1 -0
- package/dist/esm/lib/domForEach.js +68 -0
- package/dist/esm/lib/domForEach.js.map +1 -0
- package/dist/esm/lib/{_domImpl.js → domImpl.js} +20 -4
- package/dist/esm/lib/domImpl.js.map +1 -0
- package/dist/esm/lib/{_domMethods.js → domMethods.js} +8 -63
- package/dist/esm/lib/domMethods.js.map +1 -0
- package/dist/esm/lib/domevent.js +30 -11
- package/dist/esm/lib/domevent.js.map +1 -1
- package/dist/esm/lib/emit.js +2 -1
- package/dist/esm/lib/emit.js.map +1 -1
- package/dist/esm/lib/kowrap.js +90 -10
- package/dist/esm/lib/kowrap.js.map +1 -1
- package/dist/esm/lib/obsArray.js.map +1 -1
- package/dist/esm/lib/observable.js +9 -1
- package/dist/esm/lib/observable.js.map +1 -1
- package/dist/esm/lib/pureComputed.js +1 -1
- package/dist/esm/lib/pureComputed.js.map +1 -1
- package/dist/esm/lib/styled.js +52 -22
- package/dist/esm/lib/styled.js.map +1 -1
- package/dist/esm/lib/subscribe.js +5 -2
- package/dist/esm/lib/subscribe.js.map +1 -1
- package/dist/esm/lib/util.js.map +1 -1
- package/dist/esm/lib/widgets/input.js +1 -2
- package/dist/esm/lib/widgets/input.js.map +1 -1
- package/dist/esm/lib/widgets/select.js.map +1 -1
- package/dist/grain-full.debug.js +1627 -1222
- package/dist/grain-full.min.js +1 -1
- package/dist/grain-full.min.js.map +1 -1
- package/index.ts +6 -2
- package/lib/_computed_queue.ts +7 -1
- package/lib/binding.ts +33 -28
- package/lib/browserGlobals.ts +3 -1
- package/lib/computed.ts +37 -7
- package/lib/dispose.ts +81 -33
- package/lib/dom.ts +24 -18
- package/lib/domComponent.ts +89 -0
- package/lib/domComputed.ts +146 -0
- package/lib/{_domDispose.ts → domDispose.ts} +26 -8
- package/lib/{_domForEach.ts → domForEach.ts} +12 -11
- package/lib/{_domImpl.ts → domImpl.ts} +36 -30
- package/lib/{_domMethods.ts → domMethods.ts} +33 -103
- package/lib/domevent.ts +59 -22
- package/lib/emit.ts +2 -1
- package/lib/kowrap.ts +109 -11
- package/lib/obsArray.ts +2 -2
- package/lib/observable.ts +10 -2
- package/lib/pureComputed.ts +7 -6
- package/lib/styled.ts +65 -39
- package/lib/subscribe.ts +24 -8
- package/lib/widgets/input.ts +9 -7
- package/lib/widgets/select.ts +3 -3
- package/package.json +41 -42
- package/dist/cjs/lib/_domComponent.d.ts +0 -84
- package/dist/cjs/lib/_domComponent.js +0 -160
- package/dist/cjs/lib/_domComponent.js.map +0 -1
- package/dist/cjs/lib/_domDispose.js.map +0 -1
- package/dist/cjs/lib/_domForEach.js +0 -71
- package/dist/cjs/lib/_domForEach.js.map +0 -1
- package/dist/cjs/lib/_domImpl.js.map +0 -1
- package/dist/cjs/lib/_domMethods.js.map +0 -1
- package/dist/esm/lib/_domComponent.js +0 -155
- package/dist/esm/lib/_domComponent.js.map +0 -1
- package/dist/esm/lib/_domDispose.js.map +0 -1
- package/dist/esm/lib/_domForEach.js +0 -68
- package/dist/esm/lib/_domForEach.js.map +0 -1
- package/dist/esm/lib/_domImpl.js.map +0 -1
- package/dist/esm/lib/_domMethods.js.map +0 -1
- package/lib/_domComponent.ts +0 -167
package/index.ts
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
|
-
export
|
|
1
|
+
export * from './lib/binding';
|
|
2
|
+
export * from './lib/computed';
|
|
2
3
|
export * from './lib/dispose';
|
|
3
4
|
export * from './lib/dom';
|
|
4
5
|
export * from './lib/emit';
|
|
5
6
|
export * from './lib/kowrap';
|
|
6
7
|
export * from './lib/obsArray';
|
|
7
8
|
export * from './lib/observable';
|
|
9
|
+
export * from './lib/pureComputed';
|
|
8
10
|
export * from './lib/styled';
|
|
9
|
-
export
|
|
11
|
+
export * from './lib/subscribe';
|
|
10
12
|
export * from './lib/util';
|
|
13
|
+
export * from './lib/widgets/input';
|
|
14
|
+
export * from './lib/widgets/select';
|
package/lib/_computed_queue.ts
CHANGED
|
@@ -24,7 +24,7 @@ import {PriorityQueue} from './PriorityQueue';
|
|
|
24
24
|
*/
|
|
25
25
|
export class DepItem {
|
|
26
26
|
public static isPrioritySmaller(a: DepItem, b: DepItem): boolean {
|
|
27
|
-
return a._priority < b._priority;
|
|
27
|
+
return a._priority < b._priority || (a._priority === b._priority && a._creation < b._creation);
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
private _priority: number = 0;
|
|
@@ -32,6 +32,9 @@ export class DepItem {
|
|
|
32
32
|
private _callback: () => void;
|
|
33
33
|
private _context?: object;
|
|
34
34
|
|
|
35
|
+
// Order of creation, used for ordering items at same priority.
|
|
36
|
+
private _creation: number = ++_nextCreationNum;
|
|
37
|
+
|
|
35
38
|
/**
|
|
36
39
|
* Callback should call depItem.useDep(dep) for each DepInput it depends on.
|
|
37
40
|
*/
|
|
@@ -73,6 +76,9 @@ export class DepItem {
|
|
|
73
76
|
// The main compute queue.
|
|
74
77
|
const queue = new PriorityQueue<DepItem>(DepItem.isPrioritySmaller);
|
|
75
78
|
|
|
79
|
+
// Counter for creation order, used to create a stable ordering of DepItems at same priority.
|
|
80
|
+
let _nextCreationNum = 0;
|
|
81
|
+
|
|
76
82
|
// Array to keep track of items recomputed during this call to compute(). It could be a local
|
|
77
83
|
// variable in compute(), but is made global to minimize allocations.
|
|
78
84
|
const _seen: any[] = [];
|
package/lib/binding.ts
CHANGED
|
@@ -5,18 +5,14 @@
|
|
|
5
5
|
|
|
6
6
|
import {computed} from './computed';
|
|
7
7
|
import {IDisposable} from './dispose';
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
8
|
+
import {autoDisposeElem} from './domDispose';
|
|
9
|
+
import {IKnockoutReadObservable, InferKoType} from './kowrap';
|
|
10
|
+
import {BaseObservable} from './observable';
|
|
11
|
+
import {subscribe, UseCBOwner} from './subscribe';
|
|
10
12
|
|
|
11
|
-
export type BindableValue<T> =
|
|
13
|
+
export type BindableValue<T> = BaseObservable<T> | ComputedCallback<T> | T | IKnockoutReadObservable<T>;
|
|
12
14
|
|
|
13
|
-
export type ComputedCallback<T> = (use:
|
|
14
|
-
|
|
15
|
-
export interface IKnockoutObservable<T> {
|
|
16
|
-
(): T;
|
|
17
|
-
peek(): T;
|
|
18
|
-
subscribe(callback: (newValue: T) => void, target?: any, event?: "change"): IDisposable;
|
|
19
|
-
}
|
|
15
|
+
export type ComputedCallback<T> = (use: UseCBOwner, ...args: any[]) => T;
|
|
20
16
|
|
|
21
17
|
/**
|
|
22
18
|
* Subscribes a callback to valueObs, which may be one a plain value, an observable, a knockout
|
|
@@ -29,20 +25,20 @@ export interface IKnockoutObservable<T> {
|
|
|
29
25
|
*
|
|
30
26
|
* Returns an object which should be disposed to remove the created subscriptions, or null.
|
|
31
27
|
*/
|
|
32
|
-
|
|
33
|
-
|
|
28
|
+
// The overload below is annoying, but needed for correct type inference; see test/types/kowrap.ts.
|
|
29
|
+
export function subscribeBindable<KObs extends IKnockoutReadObservable<any>>(
|
|
30
|
+
valueObs: KObs, callback: (val: InferKoType<KObs>) => void): IDisposable|null;
|
|
31
|
+
export function subscribeBindable<T>(
|
|
32
|
+
valueObs: BindableValue<T>, callback: (val: T) => void): IDisposable|null;
|
|
33
|
+
export function subscribeBindable<T>(
|
|
34
|
+
valueObs: BindableValue<T>, callback: (val: T) => void): IDisposable|null {
|
|
34
35
|
// A plain function (to make a computed from), or a knockout observable.
|
|
35
36
|
if (typeof valueObs === 'function') {
|
|
36
37
|
// Knockout observable.
|
|
37
|
-
const koValue = valueObs as
|
|
38
|
+
const koValue = valueObs as IKnockoutReadObservable<T>;
|
|
38
39
|
if (typeof koValue.peek === 'function') {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
const old = savedValue;
|
|
42
|
-
savedValue = val;
|
|
43
|
-
callback(val, old);
|
|
44
|
-
});
|
|
45
|
-
callback(savedValue, undefined);
|
|
40
|
+
const sub = koValue.subscribe((val) => callback(val));
|
|
41
|
+
callback(koValue.peek());
|
|
46
42
|
return sub;
|
|
47
43
|
}
|
|
48
44
|
|
|
@@ -50,19 +46,28 @@ export function subscribe<T>(valueObs: BindableValue<T>,
|
|
|
50
46
|
// let sub = subscribe(use => callback(valueObs(use)));
|
|
51
47
|
// The difference is that when valueObs() evaluates to unchanged value, callback would be
|
|
52
48
|
// called in the version above, but not in the version below.
|
|
53
|
-
const comp = computed(valueObs);
|
|
54
|
-
comp.addListener(callback);
|
|
55
|
-
callback(comp.get()
|
|
49
|
+
const comp = computed(valueObs as ComputedCallback<T>);
|
|
50
|
+
comp.addListener((val) => callback(val));
|
|
51
|
+
callback(comp.get());
|
|
56
52
|
return comp; // Disposing this will dispose its one listener.
|
|
57
53
|
}
|
|
58
54
|
|
|
59
55
|
// An observable.
|
|
60
|
-
if (valueObs instanceof
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
return
|
|
56
|
+
if (valueObs instanceof BaseObservable) {
|
|
57
|
+
// Use subscribe() rather than addListener(), so that bundling of changes (implicit and with
|
|
58
|
+
// bundleChanges()) is respected. This matters when callback also uses observables.
|
|
59
|
+
return subscribe(valueObs, (use, val) => callback(val));
|
|
64
60
|
}
|
|
65
61
|
|
|
66
|
-
callback(valueObs
|
|
62
|
+
callback(valueObs);
|
|
67
63
|
return null;
|
|
68
64
|
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Subscribes a callback to valueObs (which may be a value, observable, or function) using
|
|
68
|
+
* subscribe(), and disposes the subscription with the passed-in element.
|
|
69
|
+
*/
|
|
70
|
+
export function subscribeElem<T>(elem: Node, valueObs: BindableValue<T>,
|
|
71
|
+
callback: (newVal: T, oldVal?: T) => void): void {
|
|
72
|
+
autoDisposeElem(elem, subscribeBindable(valueObs, callback));
|
|
73
|
+
}
|
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
|
@@ -35,21 +35,51 @@
|
|
|
35
35
|
*/
|
|
36
36
|
|
|
37
37
|
import {DepItem} from './_computed_queue';
|
|
38
|
-
import {
|
|
38
|
+
import {IDisposableOwnerT, setDisposeOwner} from './dispose';
|
|
39
39
|
import {BaseObservable as Obs, Observable} from './observable';
|
|
40
|
-
import {ISubscribable, Subscription} from './subscribe';
|
|
40
|
+
import {ISubscribable, Subscription, UseCBOwner as UseCB} from './subscribe';
|
|
41
41
|
|
|
42
42
|
function _noWrite(): never {
|
|
43
43
|
throw new Error("Can't write to non-writable computed");
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
-
|
|
47
|
-
export interface UseCB { // tslint:disable-line:interface-name
|
|
48
|
-
<U>(obs: Obs<U>): U;
|
|
49
|
-
owner: IDisposableOwner;
|
|
50
|
-
}
|
|
46
|
+
type Owner<T> = IDisposableOwnerT<Computed<T>>|null;
|
|
51
47
|
|
|
52
48
|
export class Computed<T> extends Observable<T> {
|
|
49
|
+
// Still need repetitive declarations to support varargs that are not the final argument.
|
|
50
|
+
public static create<T>(
|
|
51
|
+
owner: Owner<T>, cb: (use: UseCB) => T): Computed<T>;
|
|
52
|
+
public static create<T, A>(
|
|
53
|
+
owner: Owner<T>, a: Obs<A>,
|
|
54
|
+
cb: (use: UseCB, a: A) => T): Computed<T>;
|
|
55
|
+
public static create<T, A, B>(
|
|
56
|
+
owner: Owner<T>, a: Obs<A>, b: Obs<B>,
|
|
57
|
+
cb: (use: UseCB, a: A, b: B) => T): Computed<T>;
|
|
58
|
+
public static create<T, A, B, C>(
|
|
59
|
+
owner: Owner<T>, a: Obs<A>, b: Obs<B>, c: Obs<C>,
|
|
60
|
+
cb: (use: UseCB, a: A, b: B, c: C) => T): Computed<T>;
|
|
61
|
+
public static create<T, A, B, C, D>(
|
|
62
|
+
owner: Owner<T>, a: Obs<A>, b: Obs<B>, c: Obs<C>, d: Obs<D>,
|
|
63
|
+
cb: (use: UseCB, a: A, b: B, c: C, d: D) => T): Computed<T>;
|
|
64
|
+
public static create<T, A, B, C, D, E>(
|
|
65
|
+
owner: Owner<T>, a: Obs<A>, b: Obs<B>, c: Obs<C>, d: Obs<D>, e: Obs<E>,
|
|
66
|
+
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
|
+
public static create<T>(owner: IDisposableOwnerT<Computed<T>>|null, ...args: any[]): Computed<T> {
|
|
79
|
+
const readCb = args.pop();
|
|
80
|
+
return setDisposeOwner(owner, new Computed<T>(readCb, args));
|
|
81
|
+
}
|
|
82
|
+
|
|
53
83
|
private _callback: (use: UseCB, ...args: any[]) => T;
|
|
54
84
|
private _write: (value: T) => void;
|
|
55
85
|
private _sub: Subscription;
|
package/lib/dispose.ts
CHANGED
|
@@ -45,9 +45,29 @@
|
|
|
45
45
|
* this._holder.clear(); // disposes contained object
|
|
46
46
|
* this._holder.release(); // releases contained object
|
|
47
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
|
+
*
|
|
48
54
|
* If creating your own class with a dispose() method, do NOT throw exceptions from dispose().
|
|
49
55
|
* These cannot be handled properly in all cases. Read here about the same issue in C++:
|
|
50
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!
|
|
51
71
|
*/
|
|
52
72
|
|
|
53
73
|
import {LLink} from './emit';
|
|
@@ -84,6 +104,15 @@ const _noopOwner: IDisposableOwner = {
|
|
|
84
104
|
// is used by create() for the safe creation of Disposables.
|
|
85
105
|
let _defaultDisposableOwner = _noopOwner;
|
|
86
106
|
|
|
107
|
+
/**
|
|
108
|
+
* The static portion of class Disposable.
|
|
109
|
+
*/
|
|
110
|
+
export interface IDisposableCtor<Derived, CtorArgs extends any[]> {
|
|
111
|
+
new(...args: CtorArgs): Derived;
|
|
112
|
+
create<T extends new(...args: any[]) => any>(
|
|
113
|
+
this: T, owner: IDisposableOwnerT<InstanceType<T>>|null, ...args: ConstructorParameters<T>): InstanceType<T>;
|
|
114
|
+
}
|
|
115
|
+
|
|
87
116
|
/**
|
|
88
117
|
* Base class for disposable objects that can own other objects. See the module documentation.
|
|
89
118
|
*/
|
|
@@ -95,27 +124,9 @@ export abstract class Disposable implements IDisposable, IDisposableOwner {
|
|
|
95
124
|
* exception, dispose() gets called to clean up the partially-constructed object.
|
|
96
125
|
*
|
|
97
126
|
* Owner may be null if intend to ensure disposal some other way.
|
|
98
|
-
*
|
|
99
|
-
* TODO: create() needs more unittests, including to ensure that TypeScript types are done
|
|
100
|
-
* correctly.
|
|
101
127
|
*/
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
// require (owner, number, string) as arguments.
|
|
105
|
-
public static create<T extends IDisposable>(
|
|
106
|
-
this: new () => T, owner: IDisposableOwnerT<T>|null): T;
|
|
107
|
-
public static create<T extends IDisposable, A>(
|
|
108
|
-
this: new (a: A) => T, owner: IDisposableOwnerT<T>|null, a: A): T;
|
|
109
|
-
public static create<T extends IDisposable, A, B>(
|
|
110
|
-
this: new (a: A, b: B) => T, owner: IDisposableOwnerT<T>|null, a: A, b: B): T;
|
|
111
|
-
public static create<T extends IDisposable, A, B, C>(
|
|
112
|
-
this: new (a: A, b: B, c: C) => T, owner: IDisposableOwnerT<T>|null, a: A, b: B, c: C): T;
|
|
113
|
-
public static create<T extends IDisposable, A, B, C, D>(
|
|
114
|
-
this: new (a: A, b: B, c: C, d: D) => T, owner: IDisposableOwnerT<T>|null, a: A, b: B, c: C, d: D): T;
|
|
115
|
-
public static create<T extends IDisposable, A, B, C, D, E>(
|
|
116
|
-
this: new (a: A, b: B, c: C, d: D, e: E) => T, owner: IDisposableOwnerT<T>|null, a: A, b: B, c: C, d: D, e: E): T;
|
|
117
|
-
public static create<T extends IDisposable>(
|
|
118
|
-
this: new (...args: any[]) => T, owner: IDisposableOwnerT<T>|null, ...args: any[]): T {
|
|
128
|
+
public static create<T extends new (...args: any[]) => any>(
|
|
129
|
+
this: T, owner: IDisposableOwnerT<InstanceType<T>>|null, ...args: ConstructorParameters<T>): InstanceType<T> {
|
|
119
130
|
|
|
120
131
|
const origDefaultOwner = _defaultDisposableOwner;
|
|
121
132
|
const holder = new Holder();
|
|
@@ -145,6 +156,9 @@ export abstract class Disposable implements IDisposable, IDisposableOwner {
|
|
|
145
156
|
constructor() {
|
|
146
157
|
// This registers with a temp Holder when using create(), and is a no-op when using `new Foo`.
|
|
147
158
|
_defaultDisposableOwner.autoDispose(this);
|
|
159
|
+
// Be sure to reset to no-op, so that a (non-recommended) direct call like 'new Bar()', from
|
|
160
|
+
// inside Foo's constructor doesn't use the same Holder that's temporarily holding Foo.
|
|
161
|
+
_defaultDisposableOwner = _noopOwner;
|
|
148
162
|
}
|
|
149
163
|
|
|
150
164
|
/** Take ownership of obj, and dispose it when this.dispose() is called. */
|
|
@@ -154,8 +168,8 @@ export abstract class Disposable implements IDisposable, IDisposableOwner {
|
|
|
154
168
|
}
|
|
155
169
|
|
|
156
170
|
/** Call the given callback when this.dispose() is called. */
|
|
157
|
-
public onDispose<T>(callback: (this: T) => void, context?: T):
|
|
158
|
-
this._disposalList.addListener(callback, context);
|
|
171
|
+
public onDispose<T>(callback: (this: T) => void, context?: T): DisposeListener {
|
|
172
|
+
return this._disposalList.addListener(callback, context);
|
|
159
173
|
}
|
|
160
174
|
|
|
161
175
|
/**
|
|
@@ -190,8 +204,13 @@ export abstract class Disposable implements IDisposable, IDisposableOwner {
|
|
|
190
204
|
*/
|
|
191
205
|
public dispose(): void {
|
|
192
206
|
const disposalList = this._disposalList;
|
|
193
|
-
|
|
194
|
-
|
|
207
|
+
if (!disposalList) {
|
|
208
|
+
// tslint:disable-next-line:no-console
|
|
209
|
+
console.error("Error disposing %s which is already disposed", _describe(this));
|
|
210
|
+
} else {
|
|
211
|
+
this._disposalList = null!;
|
|
212
|
+
disposalList.callAndDispose(this);
|
|
213
|
+
}
|
|
195
214
|
}
|
|
196
215
|
|
|
197
216
|
/**
|
|
@@ -215,28 +234,28 @@ export abstract class Disposable implements IDisposable, IDisposableOwner {
|
|
|
215
234
|
*
|
|
216
235
|
* If the object is an instance of Disposable, the holder will also notice when the object gets
|
|
217
236
|
* disposed from outside of it, in which case the holder will become empty again.
|
|
218
|
-
*
|
|
219
|
-
* TODO Holder needs unittests.
|
|
220
237
|
*/
|
|
221
238
|
export class Holder<T extends IDisposable> implements IDisposable, IDisposableOwner {
|
|
222
|
-
public static create<T extends IDisposable>(owner:
|
|
239
|
+
public static create<T extends IDisposable>(owner: IDisposableOwnerT<Holder<T>>|null): Holder<T> {
|
|
223
240
|
return setDisposeOwner(owner, new Holder<T>());
|
|
224
241
|
}
|
|
225
242
|
|
|
226
243
|
protected _owned: T|null = null;
|
|
244
|
+
private _disposalListener: DisposeListener|undefined = undefined;
|
|
227
245
|
|
|
228
246
|
/** Take ownership of a new object, disposing the previously held one. */
|
|
229
247
|
public autoDispose(obj: T): T {
|
|
230
|
-
|
|
248
|
+
this.clear();
|
|
231
249
|
this._owned = obj;
|
|
232
250
|
if (obj instanceof Disposable) {
|
|
233
|
-
obj.onDispose(this.
|
|
251
|
+
this._disposalListener = obj.onDispose(this._onOutsideDispose, this);
|
|
234
252
|
}
|
|
235
253
|
return obj;
|
|
236
254
|
}
|
|
237
255
|
|
|
238
256
|
/** Releases the held object without disposing it, emptying the holder. */
|
|
239
257
|
public release(): IDisposable|null {
|
|
258
|
+
this._unlisten();
|
|
240
259
|
const ret = this._owned;
|
|
241
260
|
this._owned = null;
|
|
242
261
|
return ret;
|
|
@@ -244,9 +263,11 @@ export class Holder<T extends IDisposable> implements IDisposable, IDisposableOw
|
|
|
244
263
|
|
|
245
264
|
/** Disposes the held object and empties the holder. */
|
|
246
265
|
public clear(): void {
|
|
247
|
-
|
|
248
|
-
|
|
266
|
+
this._unlisten();
|
|
267
|
+
const owned = this._owned;
|
|
268
|
+
if (owned) {
|
|
249
269
|
this._owned = null;
|
|
270
|
+
owned.dispose();
|
|
250
271
|
}
|
|
251
272
|
}
|
|
252
273
|
|
|
@@ -258,8 +279,29 @@ export class Holder<T extends IDisposable> implements IDisposable, IDisposableOw
|
|
|
258
279
|
|
|
259
280
|
/** When the holder is disposed, it disposes the held object if any. */
|
|
260
281
|
public dispose(): void { this.clear(); }
|
|
282
|
+
|
|
283
|
+
/** Stop listening for the disposal of this._owned. */
|
|
284
|
+
private _unlisten() {
|
|
285
|
+
const disposalListener = this._disposalListener;
|
|
286
|
+
if (disposalListener) {
|
|
287
|
+
this._disposalListener = undefined;
|
|
288
|
+
disposalListener.dispose();
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
private _onOutsideDispose() {
|
|
293
|
+
this._disposalListener = undefined;
|
|
294
|
+
this._owned = null;
|
|
295
|
+
}
|
|
261
296
|
}
|
|
262
297
|
|
|
298
|
+
/**
|
|
299
|
+
* MultiHolder keeps multiple disposable object. It disposes all held object when the holder
|
|
300
|
+
* itself is disposed. It's actually nothing more than the Disposable base class itself, just
|
|
301
|
+
* exposed with a clearer name that describes its purpose.
|
|
302
|
+
*/
|
|
303
|
+
export class MultiHolder extends Disposable {}
|
|
304
|
+
|
|
263
305
|
/**
|
|
264
306
|
* Sets owner of obj (i.e. calls owner.autoDispose(obj)) unless owner is null. Returns obj.
|
|
265
307
|
*/
|
|
@@ -282,9 +324,10 @@ function _describe(obj: any) {
|
|
|
282
324
|
class DisposalList extends LLink {
|
|
283
325
|
constructor() { super(); }
|
|
284
326
|
|
|
285
|
-
public addListener<T>(callback: (this: T) => void, optContext?: T):
|
|
327
|
+
public addListener<T>(callback: (this: T) => void, optContext?: T): DisposeListener {
|
|
286
328
|
const lis = new DisposeListener(callback, optContext);
|
|
287
329
|
this._insertBefore(this._next!, lis);
|
|
330
|
+
return lis;
|
|
288
331
|
}
|
|
289
332
|
|
|
290
333
|
/**
|
|
@@ -304,7 +347,7 @@ class DisposalList extends LLink {
|
|
|
304
347
|
* Internal class that keeps track of one item of the DisposalList. It mimicks emit.Listener, but
|
|
305
348
|
* reports and swallows erros when it calls the callbacks in the list.
|
|
306
349
|
*/
|
|
307
|
-
class DisposeListener extends LLink {
|
|
350
|
+
class DisposeListener extends LLink implements IDisposable {
|
|
308
351
|
public static callAll(begin: LLink, end: LLink, owner: Disposable): void {
|
|
309
352
|
while (begin !== end) {
|
|
310
353
|
const lis = begin as DisposeListener;
|
|
@@ -319,4 +362,9 @@ class DisposeListener extends LLink {
|
|
|
319
362
|
}
|
|
320
363
|
|
|
321
364
|
constructor(private callback: () => void, private context?: any) { super(); }
|
|
365
|
+
|
|
366
|
+
public dispose(): void {
|
|
367
|
+
if (this.isDisposed()) { return; }
|
|
368
|
+
this._removeNode(this);
|
|
369
|
+
}
|
|
322
370
|
}
|
package/lib/dom.ts
CHANGED
|
@@ -18,24 +18,29 @@
|
|
|
18
18
|
*/
|
|
19
19
|
|
|
20
20
|
// We keep various dom-related functions organized in private modules, but they are exposed here.
|
|
21
|
-
export
|
|
22
|
-
export * from './
|
|
23
|
-
export * from './
|
|
24
|
-
export * from './
|
|
25
|
-
export * from './
|
|
21
|
+
export * from './domImpl';
|
|
22
|
+
export * from './domComponent';
|
|
23
|
+
export * from './domComputed';
|
|
24
|
+
export * from './domDispose';
|
|
25
|
+
export * from './domForEach';
|
|
26
|
+
export * from './domMethods';
|
|
26
27
|
export * from './domevent';
|
|
27
28
|
|
|
28
|
-
import * as _domComponent from './
|
|
29
|
-
import * as
|
|
30
|
-
import * as
|
|
31
|
-
import * as
|
|
32
|
-
import * as
|
|
29
|
+
import * as _domComponent from './domComponent';
|
|
30
|
+
import * as _domComputed from './domComputed';
|
|
31
|
+
import * as _domDispose from './domDispose';
|
|
32
|
+
import * as _domForEach from './domForEach';
|
|
33
|
+
import * as _domImpl from './domImpl';
|
|
34
|
+
import * as _domMethods from './domMethods';
|
|
35
|
+
|
|
33
36
|
import * as domevent from './domevent';
|
|
34
37
|
|
|
38
|
+
import {dom as _dom, IDomArgs, TagElem, TagName} from './domImpl';
|
|
39
|
+
|
|
35
40
|
// We just want to re-export _domImpl.dom, but to allow adding methods to it in a typesafe way,
|
|
36
41
|
// TypeScript wants us to declare a real function in the same file.
|
|
37
|
-
export function dom(tagString:
|
|
38
|
-
return
|
|
42
|
+
export function dom<Tag extends TagName>(tagString: Tag, ...args: IDomArgs<TagElem<Tag>>): TagElem<Tag> {
|
|
43
|
+
return _dom(tagString, ...args);
|
|
39
44
|
}
|
|
40
45
|
|
|
41
46
|
// Additionally export all methods as properties of dom() function.
|
|
@@ -74,20 +79,21 @@ export namespace dom { // tslint:disable-line:no-namespace
|
|
|
74
79
|
export const dataElem = _domMethods.dataElem;
|
|
75
80
|
export const data = _domMethods.data;
|
|
76
81
|
export const getData = _domMethods.getData;
|
|
77
|
-
export const replaceContent =
|
|
78
|
-
export const domComputed =
|
|
79
|
-
export const
|
|
82
|
+
export const replaceContent = _domComputed.replaceContent;
|
|
83
|
+
export const domComputed = _domComputed.domComputed;
|
|
84
|
+
export const domComputedOwned = _domComputed.domComputedOwned;
|
|
85
|
+
export const maybe = _domComputed.maybe;
|
|
86
|
+
export const maybeOwned = _domComputed.maybeOwned;
|
|
80
87
|
|
|
81
88
|
export const forEach = _domForEach.forEach;
|
|
82
89
|
|
|
83
|
-
export const Component = _domComponent.Component;
|
|
84
90
|
export const create = _domComponent.create;
|
|
85
|
-
export const createInit = _domComponent.createInit;
|
|
86
91
|
|
|
87
92
|
export const onElem = domevent.onElem;
|
|
88
93
|
export const on = domevent.on;
|
|
89
94
|
export const onMatchElem = domevent.onMatchElem;
|
|
90
95
|
export const onMatch = domevent.onMatch;
|
|
91
|
-
export const
|
|
96
|
+
export const onKeyElem = domevent.onKeyElem;
|
|
92
97
|
export const onKeyPress = domevent.onKeyPress;
|
|
98
|
+
export const onKeyDown = domevent.onKeyDown;
|
|
93
99
|
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* UI components that can be inserted into dom().
|
|
3
|
+
*
|
|
4
|
+
* Components are created and inserted using dom.create():
|
|
5
|
+
*
|
|
6
|
+
* dom('div',
|
|
7
|
+
* dom.create(MyWidget, ...myArgs), // Calls MyWidget.create(owner, ...myArgs)
|
|
8
|
+
* dom.create(createMyWidget, ...myArgs), // Calls createMyWidget(owner, ...myArgs)
|
|
9
|
+
* )
|
|
10
|
+
*
|
|
11
|
+
* The first argument may be a function, which is called directly, or a class with a .create()
|
|
12
|
+
* static method, in which case that's what gets called.
|
|
13
|
+
*
|
|
14
|
+
* In both cases, the call gets a first argument of `owner` followed by the rest of the arguments
|
|
15
|
+
* to dom.create(). The `owner` is a MultiHolder that will own this component. This works
|
|
16
|
+
* naturally with any class that derives from Disposable, since it then has a suitable static
|
|
17
|
+
* create() method.
|
|
18
|
+
*
|
|
19
|
+
* Function-based components may use owner to easily handle disposal. For example:
|
|
20
|
+
*
|
|
21
|
+
* dom.create(createMyWidget)
|
|
22
|
+
* function createMyWidget(owner) {
|
|
23
|
+
* const foo = Foo.create(owner);
|
|
24
|
+
* return dom('div', foo.getTitle());
|
|
25
|
+
* }
|
|
26
|
+
*
|
|
27
|
+
* The `owner` argument is the main benefit of dom.create(). Logically, the owner is the DOM where
|
|
28
|
+
* the component is attached. When the parent DOM element is disposed, so is the component.
|
|
29
|
+
*
|
|
30
|
+
* [Explanation] To understand why the syntax is such, consider a potential alternative such as:
|
|
31
|
+
*
|
|
32
|
+
* dom('div', _insert_(new Comp1()), _insert_(new Comp2(...args)))
|
|
33
|
+
*
|
|
34
|
+
* In both cases, the constructor for Comp1 runs before the constructor for Comp2. What happens
|
|
35
|
+
* when Comp2's constructor throws an exception? In the second case, nothing yet owns the
|
|
36
|
+
* created Comp1 component, and it will never get cleaned up. With dom.create(), the DOM
|
|
37
|
+
* gets ownership of Comp1 early enough and will dispose it.
|
|
38
|
+
*
|
|
39
|
+
* A function component may return DOM directly. A class component returns the class instance,
|
|
40
|
+
* which must have a .buildDom() method which will be called right after the constructor to get
|
|
41
|
+
* the DOM. Note that buildDom is only called once.
|
|
42
|
+
*
|
|
43
|
+
* A function component may also return an object with .buildDom(). So these are equivalent:
|
|
44
|
+
*
|
|
45
|
+
* dom.create(MyWidget)
|
|
46
|
+
* dom.create((owner) => MyWidget.create(owner))
|
|
47
|
+
*
|
|
48
|
+
* Note that ownership should be handled using the `owner` argument. Don't do this:
|
|
49
|
+
*
|
|
50
|
+
* // NON-EXAMPLE: Nothing will dispose the created object:
|
|
51
|
+
* // dom.create(() => new MyWidget());
|
|
52
|
+
*
|
|
53
|
+
* The returned DOM may includes Nodes, strings, and domComputed() values, as well as arrays of
|
|
54
|
+
* any of these. In other words, any DomArg goes except DomMethods. All the DOM returned will be
|
|
55
|
+
* disposed when the containing element is disposed, followed by the `owner` itself.
|
|
56
|
+
*/
|
|
57
|
+
import {MultiHolder} from './dispose';
|
|
58
|
+
import {domComputedOwned, DomContents} from './domComputed';
|
|
59
|
+
|
|
60
|
+
export interface IDomComponent {
|
|
61
|
+
buildDom(): DomContents;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export type DomComponentReturn = DomContents | IDomComponent;
|
|
65
|
+
|
|
66
|
+
export type IDomCreateFunc<Args extends any[]> = (owner: MultiHolder, ...args: Args) => DomComponentReturn;
|
|
67
|
+
|
|
68
|
+
// It's not that we must have a constructor matching create(), but specifying type of new allows
|
|
69
|
+
// type checking of classes that derive from Disposable, whereas matching only create() does not
|
|
70
|
+
// (presumably because of the too much magic that Disposable does for the type of create()).
|
|
71
|
+
export interface IDomCreateClass<Args extends any[]> {
|
|
72
|
+
create: IDomCreateFunc<Args>;
|
|
73
|
+
new (...args: Args): DomComponentReturn;
|
|
74
|
+
}
|
|
75
|
+
export type IDomCreator<Args extends any[]> = IDomCreateFunc<Args> | IDomCreateClass<Args>;
|
|
76
|
+
|
|
77
|
+
type DomCreatorArgs<T> =
|
|
78
|
+
T extends (owner: MultiHolder, ...args: infer P) => any ? P :
|
|
79
|
+
(T extends new (...args: infer P) => any ? P : never);
|
|
80
|
+
|
|
81
|
+
export function create<Fn extends IDomCreator<any[]>>(fn: Fn, ...args: DomCreatorArgs<Fn>): DomContents {
|
|
82
|
+
return domComputedOwned(null, (owner) => {
|
|
83
|
+
const value: DomComponentReturn = ('create' in fn) ?
|
|
84
|
+
(fn as IDomCreateClass<any[]>).create(owner, ...args) :
|
|
85
|
+
(fn as IDomCreateFunc<any[]>)(owner, ...args);
|
|
86
|
+
return (value && typeof value === 'object' && 'buildDom' in value) ?
|
|
87
|
+
value.buildDom() : value;
|
|
88
|
+
});
|
|
89
|
+
}
|