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/domMethods.ts CHANGED
@@ -12,9 +12,9 @@ import {G} from './browserGlobals';
12
12
  const _dataMap: WeakMap<Node, {[key: string]: any}> = new WeakMap();
13
13
 
14
14
  /**
15
- * Sets multiple attributes of a DOM element. The `attrs()` variant takes no `elem` argument.
15
+ * Sets multiple attributes of a DOM element.
16
16
  * Null and undefined values are omitted, and booleans are either omitted or set to empty string.
17
- * @param {Object} attrsObj: Object mapping attribute names to attribute values.
17
+ * @param attrsObj - Object mapping attribute names to attribute values.
18
18
  */
19
19
  export function attrsElem(elem: Element, attrsObj: IAttrObj): void {
20
20
  for (const key of Object.keys(attrsObj)) {
@@ -24,53 +24,74 @@ export function attrsElem(elem: Element, attrsObj: IAttrObj): void {
24
24
  }
25
25
  }
26
26
  }
27
+
28
+ /**
29
+ * Sets multiple attributes of a DOM element. Null and undefined values are omitted, and booleans
30
+ * are either omitted or set to empty string.
31
+ */
27
32
  export function attrs(attrsObj: IAttrObj): DomElementMethod {
28
33
  return (elem) => attrsElem(elem, attrsObj);
29
34
  }
30
35
 
31
36
  /**
32
37
  * Sets an attribute of a DOM element to the given value. Removes the attribute when the value is
33
- * null or undefined. The `attr()` variant takes no `elem` argument, and `attrValue` may be an
34
- * observable or function.
35
- * @param {Element} elem: The element to update.
36
- * @param {String} attrName: The name of the attribute to bind, e.g. 'href'.
37
- * @param {String|null} attrValue: The string value or null to remove the attribute.
38
+ * null or undefined.
39
+ * @param elem - The element to update.
40
+ * @param attrName - The name of the attribute to bind, e.g. 'href'.
41
+ * @param attrValue - The string value, or null or undefined to remove the attribute.
38
42
  */
39
- export function attrElem(elem: Element, attrName: string, attrValue: string|null): void {
43
+ export function attrElem(elem: Element, attrName: string, attrValue: string|null|undefined): void {
40
44
  if (attrValue === null || attrValue === undefined) {
41
45
  elem.removeAttribute(attrName);
42
46
  } else {
43
47
  elem.setAttribute(attrName, attrValue);
44
48
  }
45
49
  }
46
- export function attr(attrName: string, attrValueObs: BindableValue<string>): DomElementMethod {
50
+
51
+ /**
52
+ * Sets an attribute of a DOM element to the given value. Removes the attribute when the value is
53
+ * null or undefined.
54
+ *
55
+ * @example
56
+ * ```ts
57
+ * dom('a', dom.attr('href', urlObs))
58
+ * ```
59
+ */
60
+ export function attr(attrName: string, attrValueObs: BindableValue<string|null|undefined>): DomElementMethod {
47
61
  return (elem) => _subscribe(elem, attrValueObs, (val) => attrElem(elem, attrName, val));
48
62
  }
49
63
 
50
64
  /**
51
65
  * Sets or removes a boolean attribute of a DOM element. According to the spec, empty string is a
52
66
  * valid true value for the attribute, and the false value is indicated by the attribute's absence.
53
- * The `boolAttr()` variant takes no `elem`, and `boolValue` may be an observable or function.
54
- * @param {Element} elem: The element to update.
55
- * @param {String} attrName: The name of the attribute to bind, e.g. 'checked'.
56
- * @param {Boolean} boolValue: Boolean value whether to set or unset the attribute.
67
+ * @param elem - The element to update.
68
+ * @param attrName - The name of the attribute to bind, e.g. 'checked'.
69
+ * @param boolValue - Boolean value whether to set or unset the attribute.
57
70
  */
58
71
  export function boolAttrElem(elem: Element, attrName: string, boolValue: boolean): void {
59
72
  attrElem(elem, attrName, boolValue ? '' : null);
60
73
  }
74
+ /**
75
+ * Dom-method that sets or removes a boolean attribute of a DOM element.
76
+ * @param attrName - The name of the attribute to bind, e.g. 'checked'.
77
+ * @param boolValueObs - Value, observable, or function for a whether to set or unset the attribute.
78
+ */
61
79
  export function boolAttr(attrName: string, boolValueObs: BindableValue<boolean>): DomElementMethod {
62
80
  return (elem) => _subscribe(elem, boolValueObs, (val) => boolAttrElem(elem, attrName, val));
63
81
  }
64
82
 
65
83
  /**
66
- * Adds a text node to the element. The `text()` variant takes no `elem`, and `value` may be an
67
- * observable or function.
68
- * @param {Element} elem: The element to update.
69
- * @param {String} value: The text value to add.
84
+ * Adds a text node to the element.
85
+ * @param elem - The element to update.
86
+ * @param value - The text value to add.
70
87
  */
71
88
  export function textElem(elem: Node, value: string): void {
72
89
  elem.appendChild(G.document.createTextNode(value));
73
90
  }
91
+
92
+ /**
93
+ * Sets text content of a DOM element to a value that may be an observable or a function.
94
+ */
74
95
  export function text(valueObs: BindableValue<string>): DomMethod {
75
96
  return (elem) => {
76
97
  const textNode = G.document.createTextNode('');
@@ -80,15 +101,21 @@ export function text(valueObs: BindableValue<string>): DomMethod {
80
101
  }
81
102
 
82
103
  /**
83
- * Sets a style property of a DOM element to the given value. The `style()` variant takes no
84
- * `elem`, and `value` may be an observable or function.
85
- * @param {Element} elem: The element to update.
86
- * @param {String} property: The name of the style property to update, e.g. 'fontWeight'.
87
- * @param {String} value: The value for the property.
104
+ * Sets a style property of a DOM element to the given value.
105
+ * @param elem - The element to update.
106
+ * @param property - The name of the style property to update, e.g. 'fontWeight'.
107
+ * @param value - The value for the property.
88
108
  */
89
109
  export function styleElem(elem: Element, property: string, value: string): void {
90
110
  (elem as any).style[property] = value;
91
111
  }
112
+
113
+ /**
114
+ * Sets a style property of a DOM element to the given value, which may be an observable or a
115
+ * function.
116
+ * @param property - The name of the style property to update, e.g. 'fontWeight'.
117
+ * @param value - The value for the property.
118
+ */
92
119
  export function style(property: string, valueObs: BindableValue<string>): DomElementMethod {
93
120
  return (elem) =>
94
121
  _subscribe(elem, valueObs, (val) => styleElem(elem, property, val));
@@ -96,14 +123,20 @@ export function style(property: string, valueObs: BindableValue<string>): DomEle
96
123
 
97
124
  /**
98
125
  * Sets the property of a DOM element to the given value.
99
- * The `prop()` variant takes no `elem`, and `value` may be an observable or function.
100
- * @param {Element} elem: The element to update.
101
- * @param {String} property: The name of the property to update, e.g. 'disabled'.
102
- * @param {Object} value: The value for the property.
126
+ * @param elem - The element to update.
127
+ * @param property - The name of the property to update, e.g. 'disabled'.
128
+ * @param value - The value for the property.
103
129
  */
104
130
  export function propElem<T>(elem: Node, property: string, value: T): void {
105
131
  (elem as any)[property] = value;
106
132
  }
133
+
134
+ /**
135
+ * Sets the property of a DOM element to the given value, which may be an observable or a
136
+ * function.
137
+ * @param property - The name of the property to update, e.g. 'disabled'.
138
+ * @param value - The value for the property.
139
+ */
107
140
  export function prop<T>(property: string, valueObs: BindableValue<T>): DomMethod {
108
141
  return (elem) => _subscribe(elem, valueObs, (val) => propElem(elem, property, val));
109
142
  }
@@ -111,13 +144,17 @@ export function prop<T>(property: string, valueObs: BindableValue<T>): DomMethod
111
144
  /**
112
145
  * Shows or hides the element depending on a boolean value. Note that the element must be visible
113
146
  * initially (i.e. unsetting style.display should show it).
114
- * The `show()` variant takes no `elem`, and `boolValue` may be an observable or function.
115
- * @param {Element} elem: The element to update.
116
- * @param {Boolean} boolValue: True to show the element, false to hide it.
147
+ * @param elem - The element to update.
148
+ * @param boolValue - True to show the element, false to hide it.
117
149
  */
118
150
  export function showElem(elem: HTMLElement, boolValue: boolean): void {
119
151
  elem.style.display = boolValue ? '' : 'none';
120
152
  }
153
+
154
+ /**
155
+ * Shows or hides the element depending on a boolean value, which may be an observable or a function.
156
+ * Note that the element must be visible by default (i.e. unsetting `style.display` should show it).
157
+ */
121
158
  export function show(boolValueObs: BindableValue<boolean>): DomElementMethod {
122
159
  return (elem) =>
123
160
  _subscribe(elem, boolValueObs, (val) => showElem(elem, val));
@@ -125,13 +162,18 @@ export function show(boolValueObs: BindableValue<boolean>): DomElementMethod {
125
162
 
126
163
  /**
127
164
  * The opposite of show, hiding the element when boolValue is true.
128
- * The `hide()` variant takes no `elem`, and `boolValue` may be an observable or function.
129
- * @param {Element} elem: The element to update.
130
- * @param {Boolean} boolValue: True to hide the element, false to show it.
165
+ * @param elem - The element to update.
166
+ * @param boolValue - True to hide the element, false to show it.
131
167
  */
132
168
  export function hideElem(elem: HTMLElement, boolValue: boolean): void {
133
169
  elem.style.display = boolValue ? 'none' : '';
134
170
  }
171
+
172
+ /**
173
+ * The opposite of show, hiding the element when boolValue is true. `boolValueObs` may be an
174
+ * observable or a function.
175
+ * Note that the element must be visible by default (i.e. unsetting `style.display` should show it).
176
+ */
135
177
  export function hide(boolValueObs: BindableValue<boolean>): DomElementMethod {
136
178
  return (elem) =>
137
179
  _subscribe(elem, boolValueObs, (val) => hideElem(elem, val));
@@ -148,12 +190,13 @@ export function clsElem(elem: Element, className: string, boolValue: boolean = t
148
190
  * Sets or toggles a css class className. If className is an observable, it will be replaced when
149
191
  * the observable changes. If a plain string, then an optional second boolean observable may be
150
192
  * given, which will toggle it.
151
- *
152
- * dom.cls('foo') // Sets className 'foo'
153
- * dom.cls('foo', isFoo); // Toggles 'foo' className according to observable.
154
- * dom.cls('foo', (use) => use(isFoo)); // Toggles 'foo' className according to observable.
155
- * dom.cls(fooClass); // Sets className to the value of fooClass observable
156
- * dom.cls((use) => `prefix-${use(fooClass)}`); // Sets className to prefix- plus fooClass observable.
193
+ * ```ts
194
+ * dom.cls('foo') // Sets className 'foo'
195
+ * dom.cls('foo', isFoo); // Toggles 'foo' className according to observable.
196
+ * dom.cls('foo', (use) => use(isFoo)); // Toggles 'foo' className according to observable.
197
+ * dom.cls(fooClass); // Sets className to the value of fooClass observable
198
+ * dom.cls((use) => `prefix-${use(fooClass)}`); // Sets className to prefix- plus fooClass observable.
199
+ * ```
157
200
  */
158
201
  export function cls(className: string, boolValue?: BindableValue<boolean>): DomElementMethod;
159
202
  export function cls(className: BindableValue<string>): DomElementMethod;
@@ -193,11 +236,10 @@ function _clsDynamicPrefix(prefix: string, className: BindableValue<string>): Do
193
236
  }
194
237
 
195
238
  /**
196
- * Associate arbitrary data with a DOM element. The `data()` variant takes no `elem`, and `value`
197
- * may be an observable or function.
198
- * @param {Element} elem: The element with which to associate data.
199
- * @param {String} key: Key to identify this piece of data among others attached to elem.
200
- * @param {Object} value: Arbitrary value to associate with elem.
239
+ * Associate arbitrary data with a DOM element.
240
+ * @param elem - The element with which to associate data.
241
+ * @param key - Key to identify this piece of data among others attached to elem.
242
+ * @param value - Arbitrary value to associate with elem.
201
243
  */
202
244
  export function dataElem(elem: Node, key: string, value: any): void {
203
245
  const obj = _dataMap.get(elem);
@@ -208,9 +250,19 @@ export function dataElem(elem: Node, key: string, value: any): void {
208
250
  _dataMap.set(elem, {[key]: value});
209
251
  }
210
252
  }
253
+
254
+ /**
255
+ * Associate arbitrary data with a DOM element: `value` may be an observable or a function.
256
+ * @param key - Key to identify this piece of data among others attached to elem.
257
+ * @param value - Arbitrary value to associate with elem.
258
+ */
211
259
  export function data(key: string, valueObs: BindableValue<any>): DomMethod {
212
260
  return (elem) => _subscribe(elem, valueObs, (val) => dataElem(elem, key, val));
213
261
  }
262
+
263
+ /**
264
+ * Retrieve data associated with a DOM element using `data()` or `dataElem()`.
265
+ */
214
266
  export function getData(elem: Node, key: string) {
215
267
  const obj = _dataMap.get(elem);
216
268
  return obj && obj[key];
@@ -219,7 +271,7 @@ export function getData(elem: Node, key: string) {
219
271
  /**
220
272
  * A very simple setup to identify DOM elements for testing purposes. Here's the recommended
221
273
  * usage.
222
- *
274
+ * ```ts
223
275
  * // In the component to be tested.
224
276
  * import {noTestId, TestId} from 'grainjs';
225
277
  *
@@ -228,17 +280,20 @@ export function getData(elem: Node, key: string) {
228
280
  * dom(..., testId("another-name"), ...),
229
281
  * );
230
282
  * }
283
+ * ```
231
284
  *
232
285
  * In the fixture code using this component:
233
- *
286
+ * ```ts
234
287
  * import {makeTestId} from 'grainjs';
235
288
  *
236
289
  * dom(..., myComponent(myArgs, makeTestId('test-mycomp-'), ...)
290
+ * ```
237
291
  *
238
292
  * In the webdriver test code:
239
- *
293
+ * ```ts
240
294
  * driver.find('.test-my-comp-some-name')
241
295
  * driver.find('.test-my-comp-another-name')
296
+ * ```
242
297
  *
243
298
  * When myComponent() is created with testId argument omitted, the testId() calls are no-ops. When
244
299
  * makeTestId('test-foo-') is passed in, testId() calls simply add a css class with that prefix.
package/lib/domevent.ts CHANGED
@@ -1,44 +1,3 @@
1
- /**
2
- * domevent provides a way to listen to DOM events, similar to JQuery's `on()` function. Its
3
- * methods are also exposed via the dom.js module, as `dom.on()`, etc.
4
- *
5
- * It is typically used as an argument to the dom() function:
6
- *
7
- * dom('div', dom.on('click', (event, elem) => { ... }));
8
- *
9
- * When the div is disposed, the listener is automatically removed.
10
- *
11
- * The underlying interface to listen to an event is this:
12
- *
13
- * let listener = dom.onElem(elem, 'click', (event, elem) => { ... });
14
- *
15
- * The callback is called with the event and the element to which it was attached. Unlike in
16
- * JQuery, the callback's return value is ignored. Use event.stopPropagation() and
17
- * event.preventDefault() explicitly if needed.
18
- *
19
- * To stop listening:
20
- *
21
- * listener.dispose();
22
- *
23
- * Disposing the listener returned by .onElem() is the only way to stop listening to an event. You
24
- * can use autoDispose to stop listening automatically when subscribing in a Disposable object:
25
- *
26
- * this.autoDispose(domevent.onElem(document, 'mouseup', callback));
27
- *
28
- * To listen to descendants of an element matching the given selector (what JQuery calls
29
- * "delegated events", see http://api.jquery.com/on/):
30
- *
31
- * dom('div', dom.onMatch('.selector', 'click', (event, elem) => { ... }));
32
- * or
33
- * let lis = domevent.onMatchElem(elem, '.selector', 'click', (event, el) => { ... });
34
- *
35
- * In this usage, the element passed to the callback will be a DOM element matching the given
36
- * selector. If there are multiple matches, the callback is only called for the innermost one.
37
- *
38
- * If you need to remove the callback on first call, here's a useful pattern:
39
- * let lis = domevent.onElem(elem, 'mouseup', e => { lis.dispose(); other_work(); });
40
- */
41
-
42
1
  import {IDisposable} from './dispose';
43
2
  import {DomElementMethod, DomMethod} from './domImpl';
44
3
 
@@ -87,20 +46,59 @@ class DomEventMatchListener<E extends Event> extends DomEventListener<E, EventTa
87
46
  }
88
47
 
89
48
  /**
90
- * Listen to a DOM event. The `on()` variant takes no `elem` argument, and may be used as an
91
- * argument to dom() function.
92
- * @param {DOMElement} elem: DOM Element to listen to.
93
- * @param {String} eventType: Event type to listen for (e.g. 'click').
94
- * @param {Function} callback: Callback to call as `callback(event, elem)`, where elem is `elem`.
95
- * @param [Boolean] options.useCapture: Add the listener in the capture phase. This should very
49
+ * Listen to a DOM event, returning the listener object.
50
+ * ```ts
51
+ * const listener = dom.onElem(elem, 'click', (event, elem) => { ... });
52
+ * ```
53
+ *
54
+ * To stop listening:
55
+ * ```ts
56
+ * listener.dispose();
57
+ * ```
58
+ *
59
+ * Disposing the listener returned by `onElem()` is the only way to stop listening to an event. You
60
+ * can use `autoDispose` to stop listening automatically when subscribing in a `Disposable` object:
61
+ * ```ts
62
+ * this.autoDispose(domevent.onElem(document, 'mouseup', callback));
63
+ * ```
64
+ *
65
+ * If you need "once" semantics, i.e. to remove the callback on first call, here's a useful pattern:
66
+ * ```ts
67
+ * const lis = domevent.onElem(elem, 'mouseup', e => { lis.dispose(); other_work(); });
68
+ * ```
69
+ *
70
+ * @param elem - DOM Element to listen to.
71
+ * @param eventType - Event type to listen for (e.g. `'click'`).
72
+ * @param callback - Callback to call as `callback(event, elem)`, where elem is `elem`.
73
+ * @param options - `useCapture: boolean`: Add the listener in the capture phase. This should very
96
74
  * rarely be useful (e.g. JQuery doesn't even offer it as an option).
97
- * @returns {Object} Listener object whose .dispose() method will remove the event listener.
75
+ * @returns Listener object whose `.dispose()` method will remove the event listener.
98
76
  */
99
77
  export function onElem<E extends EventName|string, T extends EventTarget>(
100
78
  elem: T, eventType: E, callback: EventCB<EventType<E>, T>, {useCapture = false} = {}): IDisposable {
101
79
  return new DomEventListener(elem, eventType, callback, useCapture);
102
80
  }
103
81
 
82
+ /**
83
+ * Listen to a DOM event. It is typically used as an argument to the `dom()` function:
84
+ * ```ts
85
+ * dom('div', dom.on('click', (event, elem) => { ... }));
86
+ * ```
87
+ *
88
+ * When the div is disposed, the listener is automatically removed.
89
+ *
90
+ * The callback is called with the event and the element to which it was attached. Unlike in, say,
91
+ * JQuery, the callback's return value is ignored. Use `event.stopPropagation()` and
92
+ * `event.preventDefault()` explicitly if needed.
93
+ *
94
+ * To listen to descendants of an element matching the given selector (what JQuery calls
95
+ * "delegated events", see http://api.jquery.com/on/), see [`onMatch`](#onMatch).
96
+ *
97
+ * @param eventType - Event type to listen for (e.g. `'click'`).
98
+ * @param callback - Callback to call as `callback(event, elem)`, where `elem` is the element this
99
+ * listener is attached to.
100
+ * @param options - `useCapture?: boolean`: Add the listener in the capture phase.
101
+ */
104
102
  export function on<E extends EventName|string, T extends EventTarget>(
105
103
  eventType: E, callback: EventCB<EventType<E>, T>, {useCapture = false} = {}): DomMethod<T> {
106
104
  // tslint:disable-next-line:no-unused-expression
@@ -108,24 +106,46 @@ export function on<E extends EventName|string, T extends EventTarget>(
108
106
  }
109
107
 
110
108
  /**
111
- * Listen to a DOM event on descendants of the given elem matching the given selector. The
112
- * `onMatch()` variant takes no `elem` argument, and may be used as an argument to dom().
113
- * @param {DOMElement} elem: DOM Element to whose descendants to listen.
114
- * @param {String} selector: CSS selector string to filter elements that trigger this event.
109
+ * Listen to a DOM event on descendants of the given elem matching the given selector.
110
+ *
111
+ * ```ts
112
+ * const let lis = domevent.onMatchElem(elem, '.selector', 'click', (event, el) => { ... });
113
+ * ```
114
+ *
115
+ * @param elem - DOM Element to whose descendants to listen.
116
+ * @param selector - CSS selector string to filter elements that trigger this event.
115
117
  * JQuery calls it "delegated events" (http://api.jquery.com/on/). The callback will only be
116
118
  * called when the event occurs for an element matching the given selector. If there are
117
119
  * multiple elements matching the selector, the callback is only called for the innermost one.
118
- * @param {String} eventType: Event type to listen for (e.g. 'click').
119
- * @param {Function} callback: Callback to call as `callback(event, elem)`, where elem is a
120
+ * @param eventType - Event type to listen for (e.g. 'click').
121
+ * @param callback - Callback to call as `callback(event, elem)`, where elem is a
120
122
  * descendent of `elem` which matches `selector`.
121
- * @param [Boolean] options.useCapture: Add the listener in the capture phase. This should very
122
- * rarely be useful (e.g. JQuery doesn't even offer it as an option).
123
- * @returns {Object} Listener object whose .dispose() method will remove the event listener.
123
+ * @param options - `useCapture?: boolean`: Add the listener in the capture phase.
124
+ * @returns Listener object whose `.dispose()` method will remove the event listener.
124
125
  */
125
126
  export function onMatchElem(elem: EventTarget, selector: string, eventType: string,
126
127
  callback: EventCB, {useCapture = false} = {}): IDisposable {
127
128
  return new DomEventMatchListener(elem, eventType, callback, useCapture, selector);
128
129
  }
130
+
131
+ /**
132
+ * Listen to a DOM event on descendants of the given element matching the given selector.
133
+ *
134
+ * This is similar to JQuery's [delegated events](https://api.jquery.com/on/#direct-and-delegated-events)
135
+ *
136
+ * ```ts
137
+ * dom('div', dom.onMatch('.selector', 'click', (event, elem) => { ... }));
138
+ * ```
139
+ *
140
+ * In this usage, the element passed to the callback will be a DOM element matching the given
141
+ * selector. If there are multiple matches, the callback is only called for the innermost one.
142
+ *
143
+ * @param selector - CSS selector string to filter elements that trigger this event.
144
+ * @param eventType - Event type to listen for (e.g. `'click'`).
145
+ * @param callback - Callback to call as `callback(event, elem)`, where `elem` is an element
146
+ * matching `selector`.
147
+ * @param options - `useCapture?: boolean`: Add the listener in the capture phase.
148
+ */
129
149
  export function onMatch(selector: string, eventType: string, callback: EventCB,
130
150
  {useCapture = false} = {}): DomElementMethod {
131
151
  // tslint:disable-next-line:no-unused-expression
@@ -142,8 +162,6 @@ export interface IKeyHandlers<T extends HTMLElement = HTMLElement> {
142
162
  * Listen to key events (typically 'keydown' or 'keypress'), with specified per-key callbacks.
143
163
  * Key names are listed at https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key/Key_Values
144
164
  *
145
- * Methods onKeyPress() and onKeyDown() are intended to be used as arguments to dom().
146
- *
147
165
  * By default, handled events are stopped from bubbling with stopPropagation() and
148
166
  * preventDefault(). If, however, you register a key with a "$" suffix (i.e. "Enter$" instead of
149
167
  * "Enter"), then the event is allowed to bubble normally.
@@ -152,7 +170,7 @@ export interface IKeyHandlers<T extends HTMLElement = HTMLElement> {
152
170
  * to allow this element to receive keyboard events.
153
171
  *
154
172
  * For example:
155
- *
173
+ * ```
156
174
  * dom('input', ...
157
175
  * dom.onKeyDown({
158
176
  * Enter: (e, elem) => console.log("Enter pressed"),
@@ -160,6 +178,7 @@ export interface IKeyHandlers<T extends HTMLElement = HTMLElement> {
160
178
  * Delete$: (e, elem) => console.log("Delete pressed, will bubble"),
161
179
  * })
162
180
  * )
181
+ * ```
163
182
  */
164
183
  export function onKeyElem<T extends HTMLElement>(
165
184
  elem: T, evType: KeyEventType, keyHandlers: IKeyHandlers<T>,
@@ -180,10 +199,16 @@ export function onKeyElem<T extends HTMLElement>(
180
199
  });
181
200
  }
182
201
 
202
+ /**
203
+ * Add listeners to `"keypress"` events. See [`onKeyElem`](#onKeyElem) for details.
204
+ */
183
205
  export function onKeyPress<T extends HTMLElement>(keyHandlers: IKeyHandlers<T>): DomMethod<T> {
184
206
  return (elem) => { onKeyElem(elem, 'keypress', keyHandlers); };
185
207
  }
186
208
 
209
+ /**
210
+ * Add listeners to `"keydown"` events. See [`onKeyElem`](#onKeyElem) for details.
211
+ */
187
212
  export function onKeyDown<T extends HTMLElement>(keyHandlers: IKeyHandlers<T>): DomMethod<T> {
188
213
  return (elem) => { onKeyElem(elem, 'keydown', keyHandlers); };
189
214
  }
package/lib/emit.ts CHANGED
@@ -1,48 +1,16 @@
1
- /**
2
- * emit.js implements an Emitter class which emits events to a list of listeners. Listeners are
3
- * simply functions to call, and "emitting an event" just calls those functions.
4
- *
5
- * This is similar to Backbone events, with more focus on efficiency. Both inserting and removing
6
- * listeners is constant time.
7
- *
8
- * To create an emitter:
9
- * let emitter = new Emitter();
10
- *
11
- * To add a listener:
12
- * let listener = fooEmitter.addListener(callback);
13
- * To remove a listener:
14
- * listener.dispose();
15
- *
16
- * The only way to remove a listener is to dispose the Listener object returned by addListener().
17
- * You can often use autoDispose to do this automatically when subscribing in a constructor:
18
- * this.autoDispose(fooEmitter.addListener(this.onFoo, this));
19
- *
20
- * To emit an event, call emit() with any number of arguments:
21
- * emitter.emit("hello", "world");
22
- */
23
-
24
- // Note about a possible alternative implementation.
25
- //
26
- // We could implement the same interface using an array of listeners. Certain issues apply, in
27
- // particular with removing listeners from inside emit(), and in ensuring that removals are
28
- // constant time on average. Such an implementation was attempted and timed. The result is that
29
- // compared to the linked-list implementation here, add/remove combination could be made nearly
30
- // twice faster (on average), while emit and add/remove/emit are consistently slightly slower.
31
- //
32
- // The implementation here was chosen based on those timings, and as the simpler one. For example,
33
- // on one setup (macbook, node4, 5-listener queue), add+remove take 0.1us, while add+remove+emit
34
- // take 3.82us. (In array-based implementation with same set up, add+remove is 0.06us, while
35
- // add+remove+emit is 4.80us.)
36
-
37
- // The private property name to hold next/prev pointers.
38
-
39
1
  function _noop() { /* noop */}
40
2
 
41
3
  export type ListenerCB<T> = (this: T, ...args: any[]) => void;
4
+
5
+ /**
6
+ * A callback that listens to _changes_ in the Emitter listeners. This is mainly used for
7
+ * internal purposes.
8
+ */
42
9
  export type ChangeCB = (hasListeners: boolean) => void;
43
10
 
44
11
  /**
45
12
  * This is an implementation of a doubly-linked list, with just the minimal functionality we need.
13
+ * @internal
46
14
  */
47
15
  export class LLink {
48
16
  protected _next: LLink|null = null;
@@ -76,7 +44,7 @@ export class LLink {
76
44
  }
77
45
 
78
46
  protected _disposeList(): void {
79
- let node: LLink = this;
47
+ let node: LLink = this; // eslint-disable-line @typescript-eslint/no-this-alias
80
48
  let next = node._next;
81
49
  while (next !== null) {
82
50
  node._next = node._prev = null;
@@ -86,20 +54,63 @@ export class LLink {
86
54
  }
87
55
  }
88
56
 
57
+ /**
58
+ * An `Emitter` emits events to a list of listeners. Listeners are
59
+ * simply functions to call, and "emitting an event" just calls those functions.
60
+ *
61
+ * This is similar to Backbone events, with more focus on efficiency. Both inserting and removing
62
+ * listeners is constant time.
63
+ *
64
+ * To create an emitter:
65
+ * ```ts
66
+ * const emitter = new Emitter();
67
+ * ```
68
+ *
69
+ * To add a listener:
70
+ * ```ts
71
+ * const listener = fooEmitter.addListener(callback);
72
+ * ```
73
+ *
74
+ * To remove a listener:
75
+ * ```ts
76
+ * listener.dispose();
77
+ * ```
78
+ *
79
+ * The only way to remove a listener is to dispose the `Listener` object returned by `addListener()`.
80
+ * You can often use autoDispose to do this automatically when subscribing in a constructor:
81
+ * ```ts
82
+ * this.autoDispose(fooEmitter.addListener(this.onFoo, this));
83
+ * ```
84
+ *
85
+ * To emit an event, call `emit()` with any number of arguments:
86
+ * ```ts
87
+ * emitter.emit("hello", "world");
88
+ * ```
89
+ *
90
+ * @privateRemarks
91
+ *
92
+ * Note about a possible alternative implementation.
93
+ *
94
+ * We could implement the same interface using an array of listeners. Certain issues apply, in
95
+ * particular with removing listeners from inside emit(), and in ensuring that removals are
96
+ * constant time on average. Such an implementation was attempted and timed. The result is that
97
+ * compared to the linked-list implementation here, add/remove combination could be made nearly
98
+ * twice faster (on average), while emit and add/remove/emit are consistently slightly slower.
99
+ *
100
+ * The implementation here was chosen based on those timings, and as the simpler one. For example,
101
+ * on one setup (macbook, node4, 5-listener queue), add+remove take 0.1us, while add+remove+emit
102
+ * take 3.82us. (In array-based implementation with same set up, add+remove is 0.06us, while
103
+ * add+remove+emit is 4.80us.)
104
+ */
89
105
  export class Emitter extends LLink {
90
106
  private _changeCB: ChangeCB = _noop;
91
107
  private _changeCBContext: any = undefined;
92
108
 
93
- /**
94
- * Constructs an Emitter object.
95
- */
96
- constructor() { super(); }
97
-
98
109
  /**
99
110
  * Adds a listening callback to the list of functions to call on emit().
100
- * @param {Function} callback: Function to call.
101
- * @param {Object} optContext: Context for the function.
102
- * @returns {Listener} Listener object. Its dispose() method removes the callback from the list.
111
+ * @param callback - Function to call.
112
+ * @param optContext - Context for the function.
113
+ * @returns Listener object. Its dispose() method removes the callback from the list.
103
114
  */
104
115
  public addListener<T>(callback: ListenerCB<T>, optContext?: T): Listener {
105
116
  return new Listener(this, callback, optContext);
@@ -114,7 +125,7 @@ export class Emitter extends LLink {
114
125
 
115
126
  /**
116
127
  * Sets the single callback that would get called when a listener is added or removed.
117
- * @param {Function} changeCB(hasListeners): Function to call after a listener is added or
128
+ * @param changeCB - Function to call after a listener is added or
118
129
  * removed. It's called with a boolean indicating whether this Emitter has any listeners.
119
130
  * Pass in `null` to unset the callback. Note that it can be called multiple times in a row
120
131
  * with hasListeners `true`.
@@ -126,6 +137,7 @@ export class Emitter extends LLink {
126
137
 
127
138
  /**
128
139
  * Helper used by Listener class, but not intended for public usage.
140
+ * @internal
129
141
  */
130
142
  public _triggerChangeCB(): void {
131
143
  this._changeCB.call(this._changeCBContext, this.hasListeners());
@@ -150,10 +162,11 @@ export class Emitter extends LLink {
150
162
  }
151
163
 
152
164
  /**
153
- * Listener object wraps a callback added to an Emitter, allowing for O(1) removal when the
154
- * listener is disposed.
165
+ * The `Listener` object wraps a callback added to an Emitter, allowing for O(1) removal when the
166
+ * listener is disposed. It implements `IDisposable`.
155
167
  */
156
168
  export class Listener extends LLink {
169
+ /** @internal */
157
170
  public static callAll(begin: LLink, end: LLink, args: any[]): void {
158
171
  while (begin !== end) {
159
172
  const lis = begin as Listener;
@@ -170,6 +183,7 @@ export class Listener extends LLink {
170
183
  emitter._triggerChangeCB();
171
184
  }
172
185
 
186
+ /** @internal */
173
187
  public dispose(): void {
174
188
  if (this.isDisposed()) { return; }
175
189
  this._removeNode(this);