grainjs 1.0.2 → 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.
Files changed (138) hide show
  1. package/README.md +23 -71
  2. package/dist/cjs/index.js +5 -1
  3. package/dist/cjs/index.js.map +1 -1
  4. package/dist/cjs/lib/PriorityQueue.d.ts +1 -1
  5. package/dist/cjs/lib/_computed_queue.js +3 -3
  6. package/dist/cjs/lib/_computed_queue.js.map +1 -1
  7. package/dist/cjs/lib/binding.d.ts +11 -4
  8. package/dist/cjs/lib/binding.js +5 -5
  9. package/dist/cjs/lib/binding.js.map +1 -1
  10. package/dist/cjs/lib/computed.d.ts +49 -28
  11. package/dist/cjs/lib/computed.js +38 -52
  12. package/dist/cjs/lib/computed.js.map +1 -1
  13. package/dist/cjs/lib/dispose.d.ts +109 -96
  14. package/dist/cjs/lib/dispose.js +106 -80
  15. package/dist/cjs/lib/dispose.js.map +1 -1
  16. package/dist/cjs/lib/dom.d.ts +38 -18
  17. package/dist/cjs/lib/dom.js +44 -20
  18. package/dist/cjs/lib/dom.js.map +1 -1
  19. package/dist/cjs/lib/domComponent.d.ts +56 -48
  20. package/dist/cjs/lib/domComponent.js +66 -1
  21. package/dist/cjs/lib/domComponent.js.map +1 -1
  22. package/dist/cjs/lib/domComputed.d.ts +31 -21
  23. package/dist/cjs/lib/domComputed.js +14 -11
  24. package/dist/cjs/lib/domComputed.js.map +1 -1
  25. package/dist/cjs/lib/domDispose.d.ts +27 -12
  26. package/dist/cjs/lib/domDispose.js +26 -11
  27. package/dist/cjs/lib/domDispose.js.map +1 -1
  28. package/dist/cjs/lib/domForEach.d.ts +4 -3
  29. package/dist/cjs/lib/domForEach.js +10 -9
  30. package/dist/cjs/lib/domForEach.js.map +1 -1
  31. package/dist/cjs/lib/domImpl.d.ts +33 -10
  32. package/dist/cjs/lib/domImpl.js +28 -9
  33. package/dist/cjs/lib/domImpl.js.map +1 -1
  34. package/dist/cjs/lib/domMethods.d.ts +93 -47
  35. package/dist/cjs/lib/domMethods.js +88 -46
  36. package/dist/cjs/lib/domMethods.js.map +1 -1
  37. package/dist/cjs/lib/domevent.d.ts +87 -62
  38. package/dist/cjs/lib/domevent.js +84 -59
  39. package/dist/cjs/lib/domevent.js.map +1 -1
  40. package/dist/cjs/lib/emit.d.ts +62 -32
  41. package/dist/cjs/lib/emit.js +67 -53
  42. package/dist/cjs/lib/emit.js.map +1 -1
  43. package/dist/cjs/lib/kowrap.d.ts +6 -3
  44. package/dist/cjs/lib/kowrap.js +6 -3
  45. package/dist/cjs/lib/kowrap.js.map +1 -1
  46. package/dist/cjs/lib/obsArray.d.ts +91 -53
  47. package/dist/cjs/lib/obsArray.js +87 -55
  48. package/dist/cjs/lib/obsArray.js.map +1 -1
  49. package/dist/cjs/lib/observable.d.ts +25 -15
  50. package/dist/cjs/lib/observable.js +29 -18
  51. package/dist/cjs/lib/observable.js.map +1 -1
  52. package/dist/cjs/lib/pureComputed.d.ts +12 -15
  53. package/dist/cjs/lib/pureComputed.js +15 -18
  54. package/dist/cjs/lib/pureComputed.js.map +1 -1
  55. package/dist/cjs/lib/styled.d.ts +78 -61
  56. package/dist/cjs/lib/styled.js +26 -79
  57. package/dist/cjs/lib/styled.js.map +1 -1
  58. package/dist/cjs/lib/subscribe.d.ts +41 -37
  59. package/dist/cjs/lib/subscribe.js +31 -40
  60. package/dist/cjs/lib/subscribe.js.map +1 -1
  61. package/dist/cjs/lib/util.js +1 -0
  62. package/dist/cjs/lib/util.js.map +1 -1
  63. package/dist/cjs/lib/widgets/input.d.ts +3 -1
  64. package/dist/cjs/lib/widgets/input.js +6 -4
  65. package/dist/cjs/lib/widgets/input.js.map +1 -1
  66. package/dist/cjs/lib/widgets/select.d.ts +4 -2
  67. package/dist/cjs/lib/widgets/select.js +7 -5
  68. package/dist/cjs/lib/widgets/select.js.map +1 -1
  69. package/dist/esm/lib/_computed_queue.js +3 -3
  70. package/dist/esm/lib/_computed_queue.js.map +1 -1
  71. package/dist/esm/lib/binding.js +2 -2
  72. package/dist/esm/lib/binding.js.map +1 -1
  73. package/dist/esm/lib/computed.js +36 -50
  74. package/dist/esm/lib/computed.js.map +1 -1
  75. package/dist/esm/lib/dispose.js +104 -78
  76. package/dist/esm/lib/dispose.js.map +1 -1
  77. package/dist/esm/lib/dom.js +38 -18
  78. package/dist/esm/lib/dom.js.map +1 -1
  79. package/dist/esm/lib/domComponent.js +65 -0
  80. package/dist/esm/lib/domComponent.js.map +1 -1
  81. package/dist/esm/lib/domComputed.js +10 -7
  82. package/dist/esm/lib/domComputed.js.map +1 -1
  83. package/dist/esm/lib/domDispose.js +26 -11
  84. package/dist/esm/lib/domDispose.js.map +1 -1
  85. package/dist/esm/lib/domForEach.js +3 -2
  86. package/dist/esm/lib/domForEach.js.map +1 -1
  87. package/dist/esm/lib/domImpl.js +26 -7
  88. package/dist/esm/lib/domImpl.js.map +1 -1
  89. package/dist/esm/lib/domMethods.js +77 -35
  90. package/dist/esm/lib/domMethods.js.map +1 -1
  91. package/dist/esm/lib/domevent.js +84 -59
  92. package/dist/esm/lib/domevent.js.map +1 -1
  93. package/dist/esm/lib/emit.js +67 -53
  94. package/dist/esm/lib/emit.js.map +1 -1
  95. package/dist/esm/lib/kowrap.js +5 -2
  96. package/dist/esm/lib/kowrap.js.map +1 -1
  97. package/dist/esm/lib/obsArray.js +82 -50
  98. package/dist/esm/lib/obsArray.js.map +1 -1
  99. package/dist/esm/lib/observable.js +26 -15
  100. package/dist/esm/lib/observable.js.map +1 -1
  101. package/dist/esm/lib/pureComputed.js +15 -18
  102. package/dist/esm/lib/pureComputed.js.map +1 -1
  103. package/dist/esm/lib/styled.js +24 -77
  104. package/dist/esm/lib/styled.js.map +1 -1
  105. package/dist/esm/lib/subscribe.js +27 -36
  106. package/dist/esm/lib/subscribe.js.map +1 -1
  107. package/dist/esm/lib/util.js +1 -0
  108. package/dist/esm/lib/util.js.map +1 -1
  109. package/dist/esm/lib/widgets/input.js +3 -1
  110. package/dist/esm/lib/widgets/input.js.map +1 -1
  111. package/dist/esm/lib/widgets/select.js +3 -1
  112. package/dist/esm/lib/widgets/select.js.map +1 -1
  113. package/dist/grain-full.debug.js +2146 -3062
  114. package/dist/grain-full.debug.js.map +7 -0
  115. package/dist/grain-full.min.js +6 -2
  116. package/dist/grain-full.min.js.map +7 -1
  117. package/lib/binding.ts +9 -2
  118. package/lib/computed.ts +56 -56
  119. package/lib/dispose.ts +110 -85
  120. package/lib/dom.ts +39 -20
  121. package/lib/domComponent.ts +66 -57
  122. package/lib/domComputed.ts +29 -19
  123. package/lib/domDispose.ts +28 -11
  124. package/lib/domForEach.ts +7 -3
  125. package/lib/domImpl.ts +30 -7
  126. package/lib/domMethods.ts +101 -46
  127. package/lib/domevent.ts +85 -60
  128. package/lib/emit.ts +64 -50
  129. package/lib/kowrap.ts +5 -2
  130. package/lib/obsArray.ts +89 -54
  131. package/lib/observable.ts +26 -15
  132. package/lib/pureComputed.ts +16 -22
  133. package/lib/styled.ts +85 -71
  134. package/lib/subscribe.ts +41 -45
  135. package/lib/util.ts +1 -0
  136. package/lib/widgets/input.ts +3 -1
  137. package/lib/widgets/select.ts +3 -1
  138. package/package.json +38 -27
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
- * subscribe(), and disposes the subscription with the passed-in element.
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/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
- * computed.js implements a computed observable, whose value depends on other observables and gets
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 `computed`),
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
- * let obs1 = observable(5), obs2 = observable(12);
8
- * let computed1 = computed(obs1, obs2, (use, v1, v2) => v1 + v2);
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
- * let computed2 = computed(use => use(obs1) + use(obs2));
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 obs2 is
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(). Note that constructor dependencies have less overhead.
19
- *
20
- * let val = computed(...deps, ((use, ...depValues) => READ_CALLBACK));
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 pureComputed.js offers a variation of computed() with the same interface, but which
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
- * let val = computed((use) => Foo.create(use.owner, use(a), use(b)));
47
+ * ```ts
48
+ * const val = Computed.create(null, ((use) => Foo.create(use.owner, use(a), use(b)));
49
+ * ```
31
50
  *
32
- * When the computed() is re-evaluated, and when it itself is disposed, it disposes the previously
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 {Object} value: The value to pass to the write() callback.
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
- * This is the type-checking interface for computed(), which allows TypeScript to do helpful
137
- * type-checking when using it. We can only support a fixed number of argumnets (explicit
138
- * dependencies), but 5 should almost always be enough.
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 .autoDispose() can be the owner of a disposable object. This is a type-specific
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. See the module documentation.
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, and dispose it when this.dispose() is called. */
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): DisposeListener {
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, ...), it automatically disposes the currently held
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, the holder will also notice when the object gets
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: DisposeListener|undefined = undefined;
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 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
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 { // tslint:disable-line:no-namespace
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;