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/dist/grain-full.debug.js
CHANGED
|
@@ -1,25 +1,32 @@
|
|
|
1
1
|
(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.grainjs = f()}})(function(){var define,module,exports;return (function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i<t.length;i++)o(t[i]);return o}return r})()({1:[function(require,module,exports){
|
|
2
2
|
"use strict";
|
|
3
|
-
function
|
|
4
|
-
|
|
5
|
-
}
|
|
3
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
4
|
+
if (k2 === undefined) k2 = k;
|
|
5
|
+
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
|
|
6
|
+
}) : (function(o, m, k, k2) {
|
|
7
|
+
if (k2 === undefined) k2 = k;
|
|
8
|
+
o[k2] = m[k];
|
|
9
|
+
}));
|
|
10
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
11
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
12
|
+
};
|
|
6
13
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
-
|
|
8
|
-
exports
|
|
9
|
-
exports
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
exports
|
|
19
|
-
exports
|
|
20
|
-
|
|
14
|
+
__exportStar(require("./lib/binding"), exports);
|
|
15
|
+
__exportStar(require("./lib/computed"), exports);
|
|
16
|
+
__exportStar(require("./lib/dispose"), exports);
|
|
17
|
+
__exportStar(require("./lib/dom"), exports);
|
|
18
|
+
__exportStar(require("./lib/emit"), exports);
|
|
19
|
+
__exportStar(require("./lib/kowrap"), exports);
|
|
20
|
+
__exportStar(require("./lib/obsArray"), exports);
|
|
21
|
+
__exportStar(require("./lib/observable"), exports);
|
|
22
|
+
__exportStar(require("./lib/pureComputed"), exports);
|
|
23
|
+
__exportStar(require("./lib/styled"), exports);
|
|
24
|
+
__exportStar(require("./lib/subscribe"), exports);
|
|
25
|
+
__exportStar(require("./lib/util"), exports);
|
|
26
|
+
__exportStar(require("./lib/widgets/input"), exports);
|
|
27
|
+
__exportStar(require("./lib/widgets/select"), exports);
|
|
21
28
|
|
|
22
|
-
},{"./lib/computed":
|
|
29
|
+
},{"./lib/binding":4,"./lib/computed":6,"./lib/dispose":7,"./lib/dom":8,"./lib/emit":16,"./lib/kowrap":17,"./lib/obsArray":18,"./lib/observable":19,"./lib/pureComputed":20,"./lib/styled":21,"./lib/subscribe":22,"./lib/util":23,"./lib/widgets/input":24,"./lib/widgets/select":25}],2:[function(require,module,exports){
|
|
23
30
|
"use strict";
|
|
24
31
|
/**
|
|
25
32
|
* A simple and fast priority queue with a limited interface to push, pop, peek, and get size. It
|
|
@@ -30,6 +37,7 @@ __export(require("./lib/util"));
|
|
|
30
37
|
* returns the most-prior element.
|
|
31
38
|
*/
|
|
32
39
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
40
|
+
exports.PriorityQueue = void 0;
|
|
33
41
|
class PriorityQueue {
|
|
34
42
|
constructor(_isPrior) {
|
|
35
43
|
this._isPrior = _isPrior;
|
|
@@ -107,6 +115,7 @@ exports.PriorityQueue = PriorityQueue;
|
|
|
107
115
|
* call, or of bundleChanges() call, the queue gets processed in order of _priority.
|
|
108
116
|
*/
|
|
109
117
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
118
|
+
exports.bundleChanges = exports.compute = exports._getPriority = exports.DepItem = void 0;
|
|
110
119
|
const PriorityQueue_1 = require("./PriorityQueue");
|
|
111
120
|
/**
|
|
112
121
|
* DepItem is an item in a dependency relationship. It may depend on other DepItems. It is used
|
|
@@ -119,11 +128,13 @@ class DepItem {
|
|
|
119
128
|
constructor(callback, optContext) {
|
|
120
129
|
this._priority = 0;
|
|
121
130
|
this._enqueued = false;
|
|
131
|
+
// Order of creation, used for ordering items at same priority.
|
|
132
|
+
this._creation = ++_nextCreationNum;
|
|
122
133
|
this._callback = callback;
|
|
123
134
|
this._context = optContext;
|
|
124
135
|
}
|
|
125
136
|
static isPrioritySmaller(a, b) {
|
|
126
|
-
return a._priority < b._priority;
|
|
137
|
+
return a._priority < b._priority || (a._priority === b._priority && a._creation < b._creation);
|
|
127
138
|
}
|
|
128
139
|
/**
|
|
129
140
|
* Mark depItem as a dependency of this DepItem. The argument may be null to indicate a leaf (an
|
|
@@ -155,6 +166,8 @@ class DepItem {
|
|
|
155
166
|
exports.DepItem = DepItem;
|
|
156
167
|
// The main compute queue.
|
|
157
168
|
const queue = new PriorityQueue_1.PriorityQueue(DepItem.isPrioritySmaller);
|
|
169
|
+
// Counter for creation order, used to create a stable ordering of DepItems at same priority.
|
|
170
|
+
let _nextCreationNum = 0;
|
|
158
171
|
// Array to keep track of items recomputed during this call to compute(). It could be a local
|
|
159
172
|
// variable in compute(), but is made global to minimize allocations.
|
|
160
173
|
const _seen = [];
|
|
@@ -220,707 +233,704 @@ exports.bundleChanges = bundleChanges;
|
|
|
220
233
|
},{"./PriorityQueue":2}],4:[function(require,module,exports){
|
|
221
234
|
"use strict";
|
|
222
235
|
/**
|
|
223
|
-
*
|
|
224
|
-
*
|
|
236
|
+
* binding.ts offers a convenient subscribe() function that creates a binding to an observable, a
|
|
237
|
+
* a plain value, or a function from which it builds a computed.
|
|
225
238
|
*/
|
|
226
239
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
227
|
-
|
|
228
|
-
const
|
|
229
|
-
const
|
|
230
|
-
const
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
}
|
|
242
|
-
/**
|
|
243
|
-
* A UI component should extend this base class and implement a constructor that creates some DOM
|
|
244
|
-
* and calls this.setContent() with it. Compared to a simple function returning DOM (a
|
|
245
|
-
* "functional" component), a "class" component makes it easier to organize code into methods.
|
|
246
|
-
*
|
|
247
|
-
* In addition, a "class" component may be disposed to remove it from the DOM, although this is
|
|
248
|
-
* uncommon since a UI component is normally owned by its containing DOM.
|
|
249
|
-
*/
|
|
250
|
-
class Component extends dispose_1.Disposable {
|
|
251
|
-
constructor() {
|
|
252
|
-
super();
|
|
253
|
-
this._markerPre = browserGlobals_1.G.document.createComment('A');
|
|
254
|
-
this._markerPost = browserGlobals_1.G.document.createComment('B');
|
|
255
|
-
this._contentToMount = null;
|
|
256
|
-
// If the containing DOM is disposed, it will dispose all of our DOM (included among children
|
|
257
|
-
// of the containing DOM). Let it also dispose this Component when it gets to _markerPost.
|
|
258
|
-
// Since _unmount() is unnecessary here, we skip its work by unseting _markerPre/_markerPost.
|
|
259
|
-
_domDispose_1.onDisposeElem(this._markerPost, () => {
|
|
260
|
-
this._markerPre = this._markerPost = undefined;
|
|
261
|
-
this.dispose();
|
|
262
|
-
});
|
|
263
|
-
// When the component is disposed, unmount the DOM we created (i.e. dispose and remove).
|
|
264
|
-
// Except that we skip this as unnecessary when the disposal is triggered by containing DOM.
|
|
265
|
-
this.onDispose(this._unmount, this);
|
|
266
|
-
}
|
|
267
|
-
/**
|
|
268
|
-
* Create a component using Foo.create(owner, ...args) similarly to creating any other
|
|
269
|
-
* Disposable object. The difference is that `owner` may be a DOM Element, and the content set
|
|
270
|
-
* by the constructor's setContent() call will be appended to and owned by that owner element.
|
|
271
|
-
*
|
|
272
|
-
* If the owner is not an Element, works like a regular Disposable. To add such a component to
|
|
273
|
-
* DOM, use the mount() method.
|
|
274
|
-
*/
|
|
275
|
-
// TODO add typescript overloads for strict argument checks.
|
|
276
|
-
static create(owner, ...args) {
|
|
277
|
-
const _owner = owner instanceof browserGlobals_1.G.Element ? new DomOwner(owner) : owner;
|
|
278
|
-
return dispose_1.Disposable.create.call(this, _owner, ...args);
|
|
279
|
-
}
|
|
280
|
-
/**
|
|
281
|
-
* Inserts the content of this component into a parent DOM element.
|
|
282
|
-
*/
|
|
283
|
-
mount(elem) {
|
|
284
|
-
// Insert the result of setContent() into the given parent element. Note that mount() must
|
|
285
|
-
// only ever be called once. It is normally called as part of .create().
|
|
286
|
-
if (!this._markerPost) {
|
|
287
|
-
throw new Error('Component mount() called when already disposed');
|
|
288
|
-
}
|
|
289
|
-
if (this._markerPost.parentNode) {
|
|
290
|
-
throw new Error('Component mount() called twice');
|
|
291
|
-
}
|
|
292
|
-
_domImpl_1.update(elem, this._markerPre, this._contentToMount, this._markerPost);
|
|
293
|
-
this._contentToMount = null;
|
|
294
|
-
}
|
|
295
|
-
/**
|
|
296
|
-
* Components should call setContent() with their DOM content, typically in the constructor. If
|
|
297
|
-
* called outside the constructor, setContent() will replace previously set DOM. It accepts any
|
|
298
|
-
* DOM Node; use dom.frag() to insert multiple nodes together.
|
|
299
|
-
*/
|
|
300
|
-
setContent(content) {
|
|
301
|
-
if (this._markerPost) {
|
|
302
|
-
if (this._markerPost.parentNode) {
|
|
303
|
-
// Component is already mounted. Replace previous content.
|
|
304
|
-
_domMethods_1.replaceContent(this._markerPre, this._markerPost, content);
|
|
305
|
-
}
|
|
306
|
-
else {
|
|
307
|
-
// Component is created but not yet mounted. Save the content for the mount() call.
|
|
308
|
-
this._contentToMount = content;
|
|
309
|
-
}
|
|
240
|
+
exports.subscribeElem = exports.subscribeBindable = void 0;
|
|
241
|
+
const computed_1 = require("./computed");
|
|
242
|
+
const domDispose_1 = require("./domDispose");
|
|
243
|
+
const observable_1 = require("./observable");
|
|
244
|
+
const subscribe_1 = require("./subscribe");
|
|
245
|
+
function subscribeBindable(valueObs, callback) {
|
|
246
|
+
// A plain function (to make a computed from), or a knockout observable.
|
|
247
|
+
if (typeof valueObs === 'function') {
|
|
248
|
+
// Knockout observable.
|
|
249
|
+
const koValue = valueObs;
|
|
250
|
+
if (typeof koValue.peek === 'function') {
|
|
251
|
+
const sub = koValue.subscribe((val) => callback(val));
|
|
252
|
+
callback(koValue.peek());
|
|
253
|
+
return sub;
|
|
310
254
|
}
|
|
255
|
+
// Function from which to make a computed. Note that this is also reasonable:
|
|
256
|
+
// let sub = subscribe(use => callback(valueObs(use)));
|
|
257
|
+
// The difference is that when valueObs() evaluates to unchanged value, callback would be
|
|
258
|
+
// called in the version above, but not in the version below.
|
|
259
|
+
const comp = computed_1.computed(valueObs);
|
|
260
|
+
comp.addListener((val) => callback(val));
|
|
261
|
+
callback(comp.get());
|
|
262
|
+
return comp; // Disposing this will dispose its one listener.
|
|
311
263
|
}
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
// the unmounting is triggered by the disposal of the containing DOM.
|
|
318
|
-
if (this._markerPost && this._markerPost.parentNode) {
|
|
319
|
-
const elem = this._markerPost.parentNode;
|
|
320
|
-
_domMethods_1.replaceContent(this._markerPre, this._markerPost, null);
|
|
321
|
-
elem.removeChild(this._markerPre);
|
|
322
|
-
elem.removeChild(this._markerPost);
|
|
323
|
-
}
|
|
324
|
-
this._markerPre = this._markerPost = undefined;
|
|
264
|
+
// An observable.
|
|
265
|
+
if (valueObs instanceof observable_1.BaseObservable) {
|
|
266
|
+
// Use subscribe() rather than addListener(), so that bundling of changes (implicit and with
|
|
267
|
+
// bundleChanges()) is respected. This matters when callback also uses observables.
|
|
268
|
+
return subscribe_1.subscribe(valueObs, (use, val) => callback(val));
|
|
325
269
|
}
|
|
270
|
+
callback(valueObs);
|
|
271
|
+
return null;
|
|
326
272
|
}
|
|
327
|
-
exports.
|
|
328
|
-
/**
|
|
329
|
-
* Construct and insert a UI component into the given DOM element. The component must extend
|
|
330
|
-
* dom.Component, and should build DOM and call setContent(DOM) in the constructor. DOM may be any
|
|
331
|
-
* Node. Use dom.frag() to insert multiple nodes together.
|
|
332
|
-
*
|
|
333
|
-
* Logically, the parent `elem` owns the created component, and the component owns the DOM set by
|
|
334
|
-
* setContent(). If the parent is disposed, so is the component and its DOM. If the component is
|
|
335
|
-
* somehow disposed directly, then its DOM is disposed and removed from `elem`.
|
|
336
|
-
*
|
|
337
|
-
* Note the correct usage:
|
|
338
|
-
*
|
|
339
|
-
* dom('div', dom.create(Comp1), dom.create(Comp2, ...args))
|
|
340
|
-
*
|
|
341
|
-
* To understand why the syntax is such, consider a potential alterntive such as:
|
|
342
|
-
*
|
|
343
|
-
* dom('div', _insert_(new Comp1()), _insert_(new Comp2(...args))
|
|
344
|
-
*
|
|
345
|
-
* In both cases, the constructor for Comp1 runs before the constructor for Comp2. What happens
|
|
346
|
-
* when Comp2's constructor throws an exception? In the second case, nothing yet owns the
|
|
347
|
-
* created Comp1 component, and it will never get cleaned up. In the first, correct case,
|
|
348
|
-
* dom('div') element gets ownership of it early enough and will dispose it.
|
|
349
|
-
*
|
|
350
|
-
* @param {Element} elem: The element to which to append the newly constructed component.
|
|
351
|
-
* @param {Class} ComponentClass: The component class to instantiate. It must extend
|
|
352
|
-
* dom.Component(...) and implement the render() method.
|
|
353
|
-
* @param {Objects} ...args: Arguments to the Component's constructor.
|
|
354
|
-
*/
|
|
355
|
-
// TODO add typescript overloads for strict argument checks.
|
|
356
|
-
function create(cls, ...args) {
|
|
357
|
-
return (elem) => { cls.create(elem, ...args); };
|
|
358
|
-
}
|
|
359
|
-
exports.create = create;
|
|
273
|
+
exports.subscribeBindable = subscribeBindable;
|
|
360
274
|
/**
|
|
361
|
-
*
|
|
362
|
-
*
|
|
363
|
-
* constructed instance of the component:
|
|
364
|
-
* dom.createInit(MyComponent, ...args, c => {
|
|
365
|
-
* c.addChild(...);
|
|
366
|
-
* c.setOption(...);
|
|
367
|
-
* });
|
|
368
|
-
* The benefit of such inline construction is that the component is owned by the dom element as
|
|
369
|
-
* soon as it's created, so an exception in the init function or later among dom()'s arguments
|
|
370
|
-
* will trigger a cleanup.
|
|
275
|
+
* Subscribes a callback to valueObs (which may be a value, observable, or function) using
|
|
276
|
+
* subscribe(), and disposes the subscription with the passed-in element.
|
|
371
277
|
*/
|
|
372
|
-
function
|
|
373
|
-
|
|
374
|
-
const initFunc = args.pop();
|
|
375
|
-
const c = cls.create(elem, ...args);
|
|
376
|
-
initFunc(c);
|
|
377
|
-
};
|
|
278
|
+
function subscribeElem(elem, valueObs, callback) {
|
|
279
|
+
domDispose_1.autoDisposeElem(elem, subscribeBindable(valueObs, callback));
|
|
378
280
|
}
|
|
379
|
-
exports.
|
|
281
|
+
exports.subscribeElem = subscribeElem;
|
|
380
282
|
|
|
381
|
-
},{"./
|
|
283
|
+
},{"./computed":6,"./domDispose":11,"./observable":19,"./subscribe":22}],5:[function(require,module,exports){
|
|
382
284
|
"use strict";
|
|
383
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
384
285
|
/**
|
|
385
|
-
*
|
|
386
|
-
*
|
|
387
|
-
* WeakMap-based linked list:
|
|
286
|
+
* Module that allows client-side code to use browser globals (such as `document` or `Node`) in a
|
|
287
|
+
* way that allows those globals to be replaced by mocks in browser-less tests.
|
|
388
288
|
*
|
|
389
|
-
*
|
|
390
|
-
*
|
|
391
|
-
*
|
|
289
|
+
* import {G} from 'browserGlobals';
|
|
290
|
+
* ... use G.document
|
|
291
|
+
* ... use G.Node
|
|
392
292
|
*
|
|
393
|
-
*
|
|
394
|
-
*/
|
|
395
|
-
const _disposeMap = new WeakMap();
|
|
396
|
-
// Internal helper to walk the DOM tree, calling visitFunc(elem) on all descendants of elem.
|
|
397
|
-
// Descendants are processed first.
|
|
398
|
-
function _walkDom(elem, visitFunc) {
|
|
399
|
-
let c = elem.firstChild;
|
|
400
|
-
while (c) {
|
|
401
|
-
// Note: this might be better done using an explicit stack, but in practice DOM trees aren't
|
|
402
|
-
// so deep as to cause problems.
|
|
403
|
-
_walkDom(c, visitFunc);
|
|
404
|
-
c = c.nextSibling;
|
|
405
|
-
}
|
|
406
|
-
visitFunc(elem);
|
|
407
|
-
}
|
|
408
|
-
// Internal helper to run all disposers for a single element.
|
|
409
|
-
function _disposeElem(elem) {
|
|
410
|
-
let disposer = _disposeMap.get(elem);
|
|
411
|
-
if (disposer) {
|
|
412
|
-
let key = elem;
|
|
413
|
-
do {
|
|
414
|
-
_disposeMap.delete(key);
|
|
415
|
-
disposer(elem);
|
|
416
|
-
// Find the next disposer; these are chained when there are multiple.
|
|
417
|
-
key = disposer;
|
|
418
|
-
disposer = _disposeMap.get(key);
|
|
419
|
-
} while (disposer);
|
|
420
|
-
}
|
|
421
|
-
}
|
|
422
|
-
/**
|
|
423
|
-
* Run disposers associated with any descendant of elem or with elem itself. Disposers get
|
|
424
|
-
* associated with elements using dom.onDispose(). Descendants are processed first.
|
|
293
|
+
* Initially, the global `window` object, is the source of the global values.
|
|
425
294
|
*
|
|
426
|
-
*
|
|
427
|
-
* element creation. This way any onDispose() handlers set on the unfinished element get called.
|
|
295
|
+
* To use a mock of globals in a test, use:
|
|
428
296
|
*
|
|
429
|
-
*
|
|
297
|
+
* import {pushGlobals, popGlobals} as G from 'browserGlobals';
|
|
298
|
+
* before(function() {
|
|
299
|
+
* pushGlobals(mockWindow); // e.g. jsdom.jsdom(...).defaultView
|
|
300
|
+
* });
|
|
301
|
+
* after(function() {
|
|
302
|
+
* popGlobals();
|
|
303
|
+
* });
|
|
430
304
|
*/
|
|
431
|
-
|
|
432
|
-
|
|
305
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
306
|
+
exports.popGlobals = exports.pushGlobals = exports.G = void 0;
|
|
307
|
+
;
|
|
308
|
+
function _updateGlobals(dest, source) {
|
|
309
|
+
dest.DocumentFragment = source.DocumentFragment;
|
|
310
|
+
dest.Element = source.Element;
|
|
311
|
+
dest.Node = source.Node;
|
|
312
|
+
dest.document = source.document;
|
|
313
|
+
dest.window = source.window;
|
|
433
314
|
}
|
|
434
|
-
|
|
315
|
+
// The initial IBrowserGlobals object.
|
|
316
|
+
const initial = {};
|
|
317
|
+
_updateGlobals(initial, (typeof window !== 'undefined' ? window : {}));
|
|
318
|
+
// The globals G object strats out with a copy of `initial`.
|
|
319
|
+
exports.G = Object.assign({}, initial);
|
|
320
|
+
// The stack of globals that always has the intial object, but which may be overridden.
|
|
321
|
+
const _globalsStack = [initial];
|
|
435
322
|
/**
|
|
436
|
-
*
|
|
437
|
-
* using domDispose() on it or any of its parents. If onDispose is called multiple times, all
|
|
438
|
-
* disposerFuncs will be called in reverse order.
|
|
439
|
-
* @param {Element} elem: The element to associate the disposer with.
|
|
440
|
-
* @param {Function} disposerFunc(elem): Will be called when domDispose() is called on the
|
|
441
|
-
* element or its ancestor.
|
|
442
|
-
* Note that it is not necessary usually to dispose event listeners attached to an element (e.g.
|
|
443
|
-
* with dom.on()) since their lifetime is naturally limited to the lifetime of the element.
|
|
323
|
+
* Replace globals with those from the given object. Use popGlobals() to restore previous values.
|
|
444
324
|
*/
|
|
445
|
-
function
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
if (prevDisposer) {
|
|
449
|
-
_disposeMap.set(disposerFunc, prevDisposer);
|
|
450
|
-
}
|
|
451
|
-
}
|
|
452
|
-
exports.onDisposeElem = onDisposeElem;
|
|
453
|
-
function onDispose(disposerFunc) {
|
|
454
|
-
return (elem) => onDisposeElem(elem, disposerFunc);
|
|
325
|
+
function pushGlobals(globals) {
|
|
326
|
+
_globalsStack.push(globals);
|
|
327
|
+
_updateGlobals(exports.G, globals);
|
|
455
328
|
}
|
|
456
|
-
exports.
|
|
329
|
+
exports.pushGlobals = pushGlobals;
|
|
457
330
|
/**
|
|
458
|
-
*
|
|
459
|
-
* called on the element or any of its parents.
|
|
460
|
-
* @param {Element} elem: The element to own the disposable.
|
|
461
|
-
* @param {Disposable} disposable: Anything with a .dispose() method.
|
|
331
|
+
* Restore the values of globals to undo the preceding pushGlobals() call.
|
|
462
332
|
*/
|
|
463
|
-
function
|
|
464
|
-
if (
|
|
465
|
-
|
|
466
|
-
}
|
|
467
|
-
}
|
|
468
|
-
exports.autoDisposeElem = autoDisposeElem;
|
|
469
|
-
function autoDispose(disposable) {
|
|
470
|
-
if (disposable) {
|
|
471
|
-
return (elem) => autoDisposeElem(elem, disposable);
|
|
333
|
+
function popGlobals() {
|
|
334
|
+
if (_globalsStack.length > 1) {
|
|
335
|
+
_globalsStack.pop();
|
|
472
336
|
}
|
|
337
|
+
_updateGlobals(exports.G, _globalsStack[_globalsStack.length - 1]);
|
|
473
338
|
}
|
|
474
|
-
exports.
|
|
339
|
+
exports.popGlobals = popGlobals;
|
|
475
340
|
|
|
476
341
|
},{}],6:[function(require,module,exports){
|
|
477
342
|
"use strict";
|
|
478
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
479
|
-
const _domDispose_1 = require("./_domDispose");
|
|
480
|
-
const _domImpl_1 = require("./_domImpl");
|
|
481
|
-
const _domMethods_1 = require("./_domMethods");
|
|
482
|
-
const obsArray_1 = require("./obsArray");
|
|
483
|
-
// Use the browser globals in a way that allows replacing them with mocks in tests.
|
|
484
|
-
const browserGlobals_1 = require("./browserGlobals");
|
|
485
343
|
/**
|
|
486
|
-
*
|
|
487
|
-
*
|
|
488
|
-
* computedArray() it works more efficiently for simple changes.
|
|
344
|
+
* computed.js implements a computed observable, whose value depends on other observables and gets
|
|
345
|
+
* recalculated automatically when they change.
|
|
489
346
|
*
|
|
490
|
-
*
|
|
491
|
-
*
|
|
492
|
-
*
|
|
347
|
+
* E.g. if we have some existing observables (which may themselves be instances of `computed`),
|
|
348
|
+
* we can create a computed that subscribes to them explicitly:
|
|
349
|
+
* let obs1 = observable(5), obs2 = observable(12);
|
|
350
|
+
* let computed1 = computed(obs1, obs2, (use, v1, v2) => v1 + v2);
|
|
493
351
|
*
|
|
494
|
-
*
|
|
495
|
-
*
|
|
352
|
+
* or implicitly by using `use(obs)` function:
|
|
353
|
+
* let computed2 = computed(use => use(obs1) + use(obs2));
|
|
496
354
|
*
|
|
497
|
-
*
|
|
498
|
-
*
|
|
355
|
+
* In either case, computed1.get() and computed2.get() will have the value 17. If obs1 or obs2 is
|
|
356
|
+
* changed, computed1 and computed2 will get recomputed automatically.
|
|
499
357
|
*
|
|
500
|
-
*
|
|
501
|
-
*
|
|
502
|
-
|
|
503
|
-
function forEach(obsArray, itemCreateFunc) {
|
|
504
|
-
return (elem) => {
|
|
505
|
-
const markerPre = browserGlobals_1.G.document.createComment('a');
|
|
506
|
-
const markerPost = browserGlobals_1.G.document.createComment('b');
|
|
507
|
-
elem.appendChild(markerPre);
|
|
508
|
-
elem.appendChild(markerPost);
|
|
509
|
-
if (Array.isArray(obsArray)) {
|
|
510
|
-
_domMethods_1.replaceContent(markerPre, markerPost, obsArray.map(itemCreateFunc));
|
|
511
|
-
return;
|
|
512
|
-
}
|
|
513
|
-
const nodes = obsArray_1.computedArray(obsArray, itemCreateFunc);
|
|
514
|
-
nodes.addListener((newArr, oldArr, splice) => {
|
|
515
|
-
if (splice) {
|
|
516
|
-
// Remove the elements that are gone.
|
|
517
|
-
for (const node of splice.deleted) {
|
|
518
|
-
if (node && node.parentNode === elem) {
|
|
519
|
-
_domDispose_1.domDispose(node);
|
|
520
|
-
elem.removeChild(node);
|
|
521
|
-
}
|
|
522
|
-
}
|
|
523
|
-
if (splice.numAdded > 0) {
|
|
524
|
-
// Find a valid child immediately following the spliced out portion, for DOM insertion.
|
|
525
|
-
const endIndex = splice.start + splice.numAdded;
|
|
526
|
-
let nextElem = markerPost;
|
|
527
|
-
for (let i = endIndex; i < newArr.length; i++) {
|
|
528
|
-
const node = newArr[i];
|
|
529
|
-
if (node && node.parentNode === elem) {
|
|
530
|
-
nextElem = node;
|
|
531
|
-
break;
|
|
532
|
-
}
|
|
533
|
-
}
|
|
534
|
-
// Insert the new elements.
|
|
535
|
-
const content = _domImpl_1.frag(newArr.slice(splice.start, endIndex));
|
|
536
|
-
elem.insertBefore(content, nextElem);
|
|
537
|
-
}
|
|
538
|
-
}
|
|
539
|
-
else {
|
|
540
|
-
_domMethods_1.replaceContent(markerPre, markerPost, newArr);
|
|
541
|
-
}
|
|
542
|
-
});
|
|
543
|
-
_domMethods_1.replaceContent(markerPre, markerPost, nodes.get());
|
|
544
|
-
};
|
|
545
|
-
}
|
|
546
|
-
exports.forEach = forEach;
|
|
547
|
-
|
|
548
|
-
},{"./_domDispose":5,"./_domImpl":7,"./_domMethods":8,"./browserGlobals":10,"./obsArray":17}],7:[function(require,module,exports){
|
|
549
|
-
"use strict";
|
|
550
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
551
|
-
const _domDispose_1 = require("./_domDispose");
|
|
552
|
-
const _domMethods_1 = require("./_domMethods");
|
|
553
|
-
// Use the browser globals in a way that allows replacing them with mocks in tests.
|
|
554
|
-
const browserGlobals_1 = require("./browserGlobals");
|
|
555
|
-
// The goal of the above declarations is to get help from TypeScript in detecting incorrect usage:
|
|
556
|
-
// import {text, hide} from './_domMethods';
|
|
557
|
-
// dom('div', text('hello')); // OK
|
|
558
|
-
// dom('div', hide(true)); // OK
|
|
559
|
-
// dom('div', {title: 'hello'}); // OK
|
|
560
|
-
// frag(text('hello')); // OK
|
|
561
|
-
// frag(hide(true)); // Bad: DocumentFragment is not an Element
|
|
562
|
-
// frag({title: 'hello'}); // Bad: DocumentFragment is not an Element
|
|
563
|
-
/**
|
|
564
|
-
* dom('tag#id.class1.class2', ...args)
|
|
565
|
-
* The first argument is a string consisting of a tag name, with optional #foo suffix
|
|
566
|
-
* to add the ID 'foo', and zero or more .bar suffixes to add a CSS class 'bar'.
|
|
358
|
+
* Creating a computed allows any number of dependencies to be specified explicitly, and their
|
|
359
|
+
* values will be passed to the read() callback. These may be combined with automatic dependencies
|
|
360
|
+
* detected using use(). Note that constructor dependencies have less overhead.
|
|
567
361
|
*
|
|
568
|
-
*
|
|
362
|
+
* let val = computed(...deps, ((use, ...depValues) => READ_CALLBACK));
|
|
569
363
|
*
|
|
570
|
-
*
|
|
571
|
-
*
|
|
572
|
-
*
|
|
573
|
-
*
|
|
574
|
-
*
|
|
575
|
-
*
|
|
576
|
-
*
|
|
577
|
-
*
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
/**
|
|
584
|
-
* svg('tag#id.class1.class2', ...args)
|
|
585
|
-
* Same as dom(...), but creates an SVG element.
|
|
586
|
-
*/
|
|
587
|
-
function svg(tagString, ...args) {
|
|
588
|
-
return _updateWithArgsOrDispose(_createFromTagString(_createElementSvg, tagString), args);
|
|
589
|
-
}
|
|
590
|
-
exports.svg = svg;
|
|
591
|
-
// Internal helper used to create HTML elements.
|
|
592
|
-
function _createElementHtml(tag) {
|
|
593
|
-
return browserGlobals_1.G.document.createElement(tag);
|
|
594
|
-
}
|
|
595
|
-
// Internal helper used to create SVG elements.
|
|
596
|
-
function _createElementSvg(tag) {
|
|
597
|
-
return browserGlobals_1.G.document.createElementNS("http://www.w3.org/2000/svg", tag);
|
|
598
|
-
}
|
|
599
|
-
/**
|
|
600
|
-
* Internal helper to parse tagString, create an element using createFunc with the given tag, and
|
|
601
|
-
* set its id and classes from the tagString.
|
|
602
|
-
* @param {Funtion} createFunc(tag): Function that should create an element given a tag name.
|
|
603
|
-
* It is passed in to allow creating elements in different namespaces (e.g. plain HTML vs SVG).
|
|
604
|
-
* @param {String} tagString: String of the form "tag#id.class1.class2" where id and classes are
|
|
605
|
-
* optional.
|
|
606
|
-
* @return {Element} The result of createFunc(), possibly with id and class attributes also set.
|
|
364
|
+
* You may specify a `write` callback by calling `onWrite(WRITE_CALLBACK)`, which will be called
|
|
365
|
+
* whenever set() is called on the computed by its user. If a `write` bacllback is not specified,
|
|
366
|
+
* calling `set` on a computed observable will throw an exception.
|
|
367
|
+
*
|
|
368
|
+
* Note that pureComputed.js offers a variation of computed() with the same interface, but which
|
|
369
|
+
* stays unsubscribed from dependencies while it itself has no subscribers.
|
|
370
|
+
*
|
|
371
|
+
* A computed may be used with a disposable value using `use.owner` as the value's owner. E.g.
|
|
372
|
+
* let val = computed((use) => Foo.create(use.owner, use(a), use(b)));
|
|
373
|
+
*
|
|
374
|
+
* When the computed() is re-evaluated, and when it itself is disposed, it disposes the previously
|
|
375
|
+
* owned value. Note that only the pattern above works, i.e. use.owner may only be used to take
|
|
376
|
+
* ownership of the same disposable that the callback returns.
|
|
607
377
|
*/
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
const hashPos = tagString.indexOf('#');
|
|
616
|
-
if (dotPos === -1) {
|
|
617
|
-
dotPos = tagString.length;
|
|
618
|
-
}
|
|
619
|
-
else {
|
|
620
|
-
classes = tagString.substring(dotPos + 1).replace(/\./g, ' ');
|
|
621
|
-
}
|
|
622
|
-
if (hashPos === -1) {
|
|
623
|
-
tag = tagString.substring(0, dotPos);
|
|
624
|
-
}
|
|
625
|
-
else if (hashPos > dotPos) {
|
|
626
|
-
throw new Error(`ID must come before classes in dom("${tagString}")`);
|
|
627
|
-
}
|
|
628
|
-
else {
|
|
629
|
-
tag = tagString.substring(0, hashPos);
|
|
630
|
-
id = tagString.substring(hashPos + 1, dotPos);
|
|
631
|
-
}
|
|
632
|
-
const elem = createFunc(tag);
|
|
633
|
-
if (id) {
|
|
634
|
-
elem.setAttribute('id', id);
|
|
635
|
-
}
|
|
636
|
-
if (classes) {
|
|
637
|
-
elem.setAttribute('class', classes);
|
|
638
|
-
}
|
|
639
|
-
return elem;
|
|
640
|
-
}
|
|
641
|
-
function update(elem, ...args) {
|
|
642
|
-
return _updateWithArgs(elem, args);
|
|
643
|
-
}
|
|
644
|
-
exports.update = update;
|
|
645
|
-
function _updateWithArgs(elem, args) {
|
|
646
|
-
for (const arg of args) {
|
|
647
|
-
_updateWithArg(elem, arg);
|
|
648
|
-
}
|
|
649
|
-
return elem;
|
|
650
|
-
}
|
|
651
|
-
function _updateWithArgsOrDispose(elem, args) {
|
|
652
|
-
try {
|
|
653
|
-
return _updateWithArgs(elem, args);
|
|
654
|
-
}
|
|
655
|
-
catch (e) {
|
|
656
|
-
_domDispose_1.domDispose(elem);
|
|
657
|
-
throw e;
|
|
658
|
-
}
|
|
378
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
379
|
+
exports.computed = exports.Computed = void 0;
|
|
380
|
+
const dispose_1 = require("./dispose");
|
|
381
|
+
const observable_1 = require("./observable");
|
|
382
|
+
const subscribe_1 = require("./subscribe");
|
|
383
|
+
function _noWrite() {
|
|
384
|
+
throw new Error("Can't write to non-writable computed");
|
|
659
385
|
}
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
386
|
+
class Computed extends observable_1.Observable {
|
|
387
|
+
/**
|
|
388
|
+
* Internal constructor for a Computed observable. You should use computed() function instead.
|
|
389
|
+
*/
|
|
390
|
+
constructor(callback, dependencies) {
|
|
391
|
+
// At initialization we force an undefined value even though it's not of type T: it gets set
|
|
392
|
+
// to a proper value during the creation of new Subscription, which calls this._read.
|
|
393
|
+
super(undefined);
|
|
394
|
+
this._callback = callback;
|
|
395
|
+
this._write = _noWrite;
|
|
396
|
+
this._sub = new subscribe_1.Subscription(this._read.bind(this), dependencies, this);
|
|
667
397
|
}
|
|
668
|
-
|
|
669
|
-
|
|
398
|
+
/**
|
|
399
|
+
* Creates a new Computed, owned by the given owner.
|
|
400
|
+
* @param owner: Object to own this Computed, or null to handle disposal manually.
|
|
401
|
+
* @param ...observables: Zero or more observables on which this computes depends. The callback
|
|
402
|
+
* will get called when any of these changes.
|
|
403
|
+
* @param callback: Read callback that will be called with (use, ...values),
|
|
404
|
+
* i.e. the `use` function and values for all of the ...observables. The callback is called
|
|
405
|
+
* immediately and whenever any dependency changes.
|
|
406
|
+
* @returns {Computed} The newly created computed observable.
|
|
407
|
+
*/
|
|
408
|
+
static create(owner, ...args) {
|
|
409
|
+
const readCb = args.pop();
|
|
410
|
+
return dispose_1.setDisposeOwner(owner, new Computed(readCb, args));
|
|
670
411
|
}
|
|
671
|
-
|
|
672
|
-
|
|
412
|
+
/**
|
|
413
|
+
* Used by subscriptions to keep track of dependencies.
|
|
414
|
+
*/
|
|
415
|
+
_getDepItem() {
|
|
416
|
+
return this._sub._getDepItem();
|
|
673
417
|
}
|
|
674
|
-
|
|
675
|
-
|
|
418
|
+
/**
|
|
419
|
+
* "Sets" the value of the computed by calling the write() callback if one was provided in the
|
|
420
|
+
* constructor. Throws an error if there was no such callback (not a "writable" computed).
|
|
421
|
+
* @param {Object} value: The value to pass to the write() callback.
|
|
422
|
+
*/
|
|
423
|
+
set(value) { this._write(value); }
|
|
424
|
+
/**
|
|
425
|
+
* Set callback to call when this.set(value) is called, to make it a writable computed. If not
|
|
426
|
+
* set, attempting to write to this computed will throw an exception.
|
|
427
|
+
*/
|
|
428
|
+
onWrite(writeFunc) {
|
|
429
|
+
this._write = writeFunc;
|
|
430
|
+
return this;
|
|
676
431
|
}
|
|
677
|
-
|
|
678
|
-
|
|
432
|
+
/**
|
|
433
|
+
* Disposes the computed, unsubscribing it from all observables it depends on.
|
|
434
|
+
*/
|
|
435
|
+
dispose() {
|
|
436
|
+
this._sub.dispose();
|
|
437
|
+
super.dispose();
|
|
679
438
|
}
|
|
680
|
-
|
|
681
|
-
|
|
439
|
+
_read(use, ...args) {
|
|
440
|
+
super.set(this._callback(use, ...args));
|
|
682
441
|
}
|
|
683
442
|
}
|
|
443
|
+
exports.Computed = Computed;
|
|
684
444
|
/**
|
|
685
|
-
* Creates a
|
|
445
|
+
* Creates a new Computed.
|
|
446
|
+
* @param {Observable} ...observables: The initial params, of which there may be zero or more, are
|
|
447
|
+
* observables on which this computed depends. When any of them change, the read() callback
|
|
448
|
+
* will be called with the values of these observables as arguments.
|
|
449
|
+
* @param {Function} readCallback: Read callback that will be called with (use, ...values),
|
|
450
|
+
* i.e. the `use` function and values for all of the ...observables. The callback is called
|
|
451
|
+
* immediately and whenever any dependency changes.
|
|
452
|
+
* @returns {Computed} The newly created computed observable.
|
|
686
453
|
*/
|
|
687
|
-
function
|
|
688
|
-
const
|
|
689
|
-
return
|
|
454
|
+
function computed(...args) {
|
|
455
|
+
const readCb = args.pop();
|
|
456
|
+
return new Computed(readCb, args);
|
|
690
457
|
}
|
|
691
|
-
exports.
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
458
|
+
exports.computed = computed;
|
|
459
|
+
// TODO Consider implementing .singleUse() method.
|
|
460
|
+
// An open question is in how to pass e.g. kd.hide(computed(x, x => !x)) in such a way that
|
|
461
|
+
// the temporary computed can be disposed when temporary, but not otherwise. A function-only
|
|
462
|
+
// syntax is kd.hide(use => !use(x)), but prevents use of static subscriptions.
|
|
463
|
+
//
|
|
464
|
+
// (a) function-only use of computeds is fine and useful.
|
|
465
|
+
// (b) pureComputed is another option, and doesn't technically require getting disposed.
|
|
466
|
+
// (c) kd.hide(compObs), kd.autoDispose(compObs) is more general and
|
|
467
|
+
// can be replaced more concisely by kd.hide(compObs.singleUse())
|
|
468
|
+
// .singleUse() automatically disposes a computed (or an observable?) once there are no
|
|
469
|
+
// subscriptions to it. If there are no subscriptions at the time of this call, waits for the next
|
|
470
|
+
// tick, and possibly disposes then.
|
|
702
471
|
|
|
703
|
-
},{"./
|
|
472
|
+
},{"./dispose":7,"./observable":19,"./subscribe":22}],7:[function(require,module,exports){
|
|
704
473
|
"use strict";
|
|
705
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
706
|
-
const _domDispose_1 = require("./_domDispose");
|
|
707
|
-
const _domImpl_1 = require("./_domImpl");
|
|
708
|
-
const binding_1 = require("./binding");
|
|
709
|
-
// Use the browser globals in a way that allows replacing them with mocks in tests.
|
|
710
|
-
const browserGlobals_1 = require("./browserGlobals");
|
|
711
474
|
/**
|
|
712
|
-
*
|
|
713
|
-
*
|
|
475
|
+
* dispose.js provides tools to objects that needs to dispose resources, such as destroy DOM, and
|
|
476
|
+
* unsubscribe from events. The motivation with examples is presented here:
|
|
477
|
+
*
|
|
478
|
+
* https://phab.getgrist.com/w/disposal/
|
|
479
|
+
*
|
|
480
|
+
* Disposable is a class for components that need cleanup (e.g. maintain DOM, listen to events,
|
|
481
|
+
* subscribe to anything). It provides a .dispose() method that should be called to destroy the
|
|
482
|
+
* component, and .onDispose()/.autoDispose() methods that the component should use to take
|
|
483
|
+
* responsibility for other pieces that require cleanup.
|
|
484
|
+
*
|
|
485
|
+
* To define a disposable class:
|
|
486
|
+
* class Foo extends Disposable { ... }
|
|
487
|
+
*
|
|
488
|
+
* To create Foo:
|
|
489
|
+
* const foo = Foo.create(owner, ...args);
|
|
490
|
+
* This is better than `new Foo` for two reasons:
|
|
491
|
+
* 1. If Foo's constructor throws an exception, any disposals registered in that constructor
|
|
492
|
+
* before the exception are honored.
|
|
493
|
+
* 2. It ensures you specify the owner of the new instance (but you can use null to skip it).
|
|
494
|
+
*
|
|
495
|
+
* In Foo's constructor (or rarely methods), take ownership of other Disposable objects:
|
|
496
|
+
* this.bar = Bar.create(this, ...);
|
|
497
|
+
*
|
|
498
|
+
* For objects that are not instances of Disposable but have a .dispose() methods, use:
|
|
499
|
+
* this.bar = this.autoDispose(createSomethingDisposable());
|
|
500
|
+
*
|
|
501
|
+
* To call a function on disposal (e.g. to add custom disposal logic):
|
|
502
|
+
* this.onDispose(() => this.myUnsubscribeAllMethod());
|
|
503
|
+
* this.onDispose(this.myUnsubscribeAllMethod, this); // slightly more efficient
|
|
504
|
+
*
|
|
505
|
+
* To mark this object to be wiped out on disposal (i.e. set all properties to null):
|
|
506
|
+
* this.wipeOnDispose();
|
|
507
|
+
* See the documentation of that method for more info.
|
|
508
|
+
*
|
|
509
|
+
* To dispose Foo directly:
|
|
510
|
+
* foo.dispose();
|
|
511
|
+
* To determine if an object has already been disposed:
|
|
512
|
+
* foo.isDisposed()
|
|
513
|
+
*
|
|
514
|
+
* If you need to replace an owned object, or release, or dispose it early, use a Holder:
|
|
515
|
+
* this._holder = Holder.create(this);
|
|
516
|
+
* Bar.create(this._holder, 1); // creates new Bar(1)
|
|
517
|
+
* Bar.create(this._holder, 2); // creates new Bar(2) and disposes previous object
|
|
518
|
+
* this._holder.clear(); // disposes contained object
|
|
519
|
+
* this._holder.release(); // releases contained object
|
|
520
|
+
*
|
|
521
|
+
* If you need a container for multiple objects and dispose them all together, use a MultiHolder:
|
|
522
|
+
* this._mholder = MultiHolder.create(null);
|
|
523
|
+
* Bar.create(this._mholder, 1); // create new Bar(1)
|
|
524
|
+
* Bar.create(this._mholder, 2); // create new Bar(2)
|
|
525
|
+
* this._mholder.dispose(); // disposes both objects
|
|
526
|
+
*
|
|
527
|
+
* If creating your own class with a dispose() method, do NOT throw exceptions from dispose().
|
|
528
|
+
* These cannot be handled properly in all cases. Read here about the same issue in C++:
|
|
529
|
+
* http://bin-login.name/ftp/pub/docs/programming_languages/cpp/cffective_cpp/MAGAZINE/SU_FRAME.HTM#destruct
|
|
530
|
+
*
|
|
531
|
+
* Using a parametrized (generic) class as a Disposable is tricky. E.g.
|
|
532
|
+
* class Bar<T> extends Disposable { ... }
|
|
533
|
+
* // Bar<T>.create(...) <-- doesn't work
|
|
534
|
+
* // Bar.create<T>(...) <-- doesn't work
|
|
535
|
+
* // Bar.create(...) <-- works, but with {} for Bar's type parameters
|
|
536
|
+
*
|
|
537
|
+
* The solution is to expose the constructor type using a helper method:
|
|
538
|
+
* class Bar<T> extends Disposable {
|
|
539
|
+
* // Note the tuple below which must match the constructor parameters of Bar<U>.
|
|
540
|
+
* public static ctor<U>(): IDisposableCtor<Bar<U>, [U, boolean]> { return this; }
|
|
541
|
+
* constructor(a: T, b: boolean) { ... }
|
|
542
|
+
* }
|
|
543
|
+
* Bar.ctor<T>().create(...) // <-- works, creates Bar<T>, and does type-checking!
|
|
714
544
|
*/
|
|
715
|
-
|
|
545
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
546
|
+
exports.setDisposeOwner = exports.MultiHolder = exports.Holder = exports.Disposable = void 0;
|
|
547
|
+
const emit_1 = require("./emit");
|
|
548
|
+
// Internal "owner" of disposable objects which doesn't actually dispose or keep track of them. It
|
|
549
|
+
// is the effective owner when creating a Disposable with `new Foo()` rather than `Foo.create()`.
|
|
550
|
+
const _noopOwner = {
|
|
551
|
+
autoDispose(obj) { },
|
|
552
|
+
};
|
|
553
|
+
// Newly-created Disposable instances will have this as their owner. This is not a constant, it
|
|
554
|
+
// is used by create() for the safe creation of Disposables.
|
|
555
|
+
let _defaultDisposableOwner = _noopOwner;
|
|
716
556
|
/**
|
|
717
|
-
*
|
|
718
|
-
* function, and attaches a disposal callback to the passed-in element.
|
|
557
|
+
* Base class for disposable objects that can own other objects. See the module documentation.
|
|
719
558
|
*/
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
559
|
+
class Disposable {
|
|
560
|
+
constructor() {
|
|
561
|
+
this._disposalList = new DisposalList();
|
|
562
|
+
// This registers with a temp Holder when using create(), and is a no-op when using `new Foo`.
|
|
563
|
+
_defaultDisposableOwner.autoDispose(this);
|
|
564
|
+
// Be sure to reset to no-op, so that a (non-recommended) direct call like 'new Bar()', from
|
|
565
|
+
// inside Foo's constructor doesn't use the same Holder that's temporarily holding Foo.
|
|
566
|
+
_defaultDisposableOwner = _noopOwner;
|
|
567
|
+
}
|
|
568
|
+
/**
|
|
569
|
+
* Create Disposable instances using `Class.create(owner, ...)` rather than `new Class(...)`.
|
|
570
|
+
*
|
|
571
|
+
* This reminds you to provide an owner, and ensures that if the constructor throws an
|
|
572
|
+
* exception, dispose() gets called to clean up the partially-constructed object.
|
|
573
|
+
*
|
|
574
|
+
* Owner may be null if intend to ensure disposal some other way.
|
|
575
|
+
*/
|
|
576
|
+
static create(owner, ...args) {
|
|
577
|
+
const origDefaultOwner = _defaultDisposableOwner;
|
|
578
|
+
const holder = new Holder();
|
|
579
|
+
try {
|
|
580
|
+
// The newly-created object will have holder as its owner.
|
|
581
|
+
_defaultDisposableOwner = holder;
|
|
582
|
+
return setDisposeOwner(owner, new this(...args));
|
|
583
|
+
}
|
|
584
|
+
catch (e) {
|
|
585
|
+
try {
|
|
586
|
+
// This calls dispose on the partially-constructed object
|
|
587
|
+
holder.clear();
|
|
588
|
+
}
|
|
589
|
+
catch (e2) {
|
|
590
|
+
// tslint:disable-next-line:no-console
|
|
591
|
+
console.error("Error disposing partially constructed %s:", this.name, e2);
|
|
592
|
+
}
|
|
593
|
+
throw e;
|
|
594
|
+
}
|
|
595
|
+
finally {
|
|
596
|
+
// On success, the new object has a new owner, and we release it from holder.
|
|
597
|
+
// On error, the holder has been cleared, and the release() is a no-op.
|
|
598
|
+
holder.release();
|
|
599
|
+
_defaultDisposableOwner = origDefaultOwner;
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
/** Take ownership of obj, and dispose it when this.dispose() is called. */
|
|
603
|
+
autoDispose(obj) {
|
|
604
|
+
this.onDispose(obj.dispose, obj);
|
|
605
|
+
return obj;
|
|
606
|
+
}
|
|
607
|
+
/** Call the given callback when this.dispose() is called. */
|
|
608
|
+
onDispose(callback, context) {
|
|
609
|
+
return this._disposalList.addListener(callback, context);
|
|
610
|
+
}
|
|
611
|
+
/**
|
|
612
|
+
* Wipe out this object when it is disposed, i.e. set all its properties to null. It is
|
|
613
|
+
* recommended to call this early in the constructor.
|
|
614
|
+
*
|
|
615
|
+
* This makes disposal more costly, but has certain benefits:
|
|
616
|
+
* - If anything still refers to the object and uses it, we'll get an early error, rather than
|
|
617
|
+
* silently keep going, potentially doing useless work (or worse) and wasting resources.
|
|
618
|
+
* - If anything still refers to the object (even without using it), the fields of the object
|
|
619
|
+
* can still be garbage-collected.
|
|
620
|
+
* - If there are circular references involving this object, they get broken, making the job
|
|
621
|
+
* easier for the garbage collector.
|
|
622
|
+
*
|
|
623
|
+
* The recommendation is to use it for complex, longer-lived objects, but to skip for objects
|
|
624
|
+
* which are numerous and short-lived (and less likely to be referenced from unexpected places).
|
|
625
|
+
*/
|
|
626
|
+
wipeOnDispose() {
|
|
627
|
+
this.onDispose(this._wipeOutObject, this);
|
|
628
|
+
}
|
|
629
|
+
/**
|
|
630
|
+
* Returns whether this object has already been disposed.
|
|
631
|
+
*/
|
|
632
|
+
isDisposed() {
|
|
633
|
+
return this._disposalList === null;
|
|
634
|
+
}
|
|
635
|
+
/**
|
|
636
|
+
* Clean up `this` by disposing all owned objects, and calling onDispose() callbacks, in reverse
|
|
637
|
+
* order to that in which they were added.
|
|
638
|
+
*/
|
|
639
|
+
dispose() {
|
|
640
|
+
const disposalList = this._disposalList;
|
|
641
|
+
if (!disposalList) {
|
|
642
|
+
// tslint:disable-next-line:no-console
|
|
643
|
+
console.error("Error disposing %s which is already disposed", _describe(this));
|
|
644
|
+
}
|
|
645
|
+
else {
|
|
646
|
+
this._disposalList = null;
|
|
647
|
+
disposalList.callAndDispose(this);
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
/**
|
|
651
|
+
* Wipe out this object by setting each property to null. This is helpful for objects that are
|
|
652
|
+
* disposed and should be ready to be garbage-collected.
|
|
653
|
+
*/
|
|
654
|
+
_wipeOutObject() {
|
|
655
|
+
// The sentinel value doesn't have to be null, but some values cause more helpful errors than
|
|
656
|
+
// others. E.g. if a.x = "disposed", then a.x.foo() throws "undefined is not a function", but
|
|
657
|
+
// when a.x = null, a.x.foo() throws a more helpful "Cannot read property 'foo' of null".
|
|
658
|
+
for (const k of Object.keys(this)) {
|
|
659
|
+
this[k] = null;
|
|
733
660
|
}
|
|
734
661
|
}
|
|
735
662
|
}
|
|
736
|
-
exports.
|
|
737
|
-
function attrs(attrsObj) {
|
|
738
|
-
return (elem) => attrsElem(elem, attrsObj);
|
|
739
|
-
}
|
|
740
|
-
exports.attrs = attrs;
|
|
663
|
+
exports.Disposable = Disposable;
|
|
741
664
|
/**
|
|
742
|
-
*
|
|
743
|
-
*
|
|
744
|
-
*
|
|
745
|
-
*
|
|
746
|
-
*
|
|
747
|
-
*
|
|
665
|
+
* Holder keeps a single disposable object. If given responsibility for another object using
|
|
666
|
+
* holder.autoDispose() or Foo.create(holder, ...), it automatically disposes the currently held
|
|
667
|
+
* object. It also disposes it when the holder itself is disposed.
|
|
668
|
+
*
|
|
669
|
+
* If the object is an instance of Disposable, the holder will also notice when the object gets
|
|
670
|
+
* disposed from outside of it, in which case the holder will become empty again.
|
|
748
671
|
*/
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
672
|
+
class Holder {
|
|
673
|
+
constructor() {
|
|
674
|
+
this._owned = null;
|
|
675
|
+
this._disposalListener = undefined;
|
|
752
676
|
}
|
|
753
|
-
|
|
754
|
-
|
|
677
|
+
static create(owner) {
|
|
678
|
+
return setDisposeOwner(owner, new Holder());
|
|
679
|
+
}
|
|
680
|
+
/** Take ownership of a new object, disposing the previously held one. */
|
|
681
|
+
autoDispose(obj) {
|
|
682
|
+
this.clear();
|
|
683
|
+
this._owned = obj;
|
|
684
|
+
if (obj instanceof Disposable) {
|
|
685
|
+
this._disposalListener = obj.onDispose(this._onOutsideDispose, this);
|
|
686
|
+
}
|
|
687
|
+
return obj;
|
|
688
|
+
}
|
|
689
|
+
/** Releases the held object without disposing it, emptying the holder. */
|
|
690
|
+
release() {
|
|
691
|
+
this._unlisten();
|
|
692
|
+
const ret = this._owned;
|
|
693
|
+
this._owned = null;
|
|
694
|
+
return ret;
|
|
695
|
+
}
|
|
696
|
+
/** Disposes the held object and empties the holder. */
|
|
697
|
+
clear() {
|
|
698
|
+
this._unlisten();
|
|
699
|
+
const owned = this._owned;
|
|
700
|
+
if (owned) {
|
|
701
|
+
this._owned = null;
|
|
702
|
+
owned.dispose();
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
/** Returns the held object, or null if the Holder is empty. */
|
|
706
|
+
get() { return this._owned; }
|
|
707
|
+
/** Returns whether the Holder is empty. */
|
|
708
|
+
isEmpty() { return !this._owned; }
|
|
709
|
+
/** When the holder is disposed, it disposes the held object if any. */
|
|
710
|
+
dispose() { this.clear(); }
|
|
711
|
+
/** Stop listening for the disposal of this._owned. */
|
|
712
|
+
_unlisten() {
|
|
713
|
+
const disposalListener = this._disposalListener;
|
|
714
|
+
if (disposalListener) {
|
|
715
|
+
this._disposalListener = undefined;
|
|
716
|
+
disposalListener.dispose();
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
_onOutsideDispose() {
|
|
720
|
+
this._disposalListener = undefined;
|
|
721
|
+
this._owned = null;
|
|
755
722
|
}
|
|
756
723
|
}
|
|
757
|
-
exports.
|
|
758
|
-
function attr(attrName, attrValueObs) {
|
|
759
|
-
return (elem) => _subscribe(elem, attrValueObs, (val) => attrElem(elem, attrName, val));
|
|
760
|
-
}
|
|
761
|
-
exports.attr = attr;
|
|
724
|
+
exports.Holder = Holder;
|
|
762
725
|
/**
|
|
763
|
-
*
|
|
764
|
-
*
|
|
765
|
-
*
|
|
766
|
-
* @param {Element} elem: The element to update.
|
|
767
|
-
* @param {String} attrName: The name of the attribute to bind, e.g. 'checked'.
|
|
768
|
-
* @param {Boolean} boolValue: Boolean value whether to set or unset the attribute.
|
|
726
|
+
* MultiHolder keeps multiple disposable object. It disposes all held object when the holder
|
|
727
|
+
* itself is disposed. It's actually nothing more than the Disposable base class itself, just
|
|
728
|
+
* exposed with a clearer name that describes its purpose.
|
|
769
729
|
*/
|
|
770
|
-
|
|
771
|
-
attrElem(elem, attrName, boolValue ? '' : null);
|
|
730
|
+
class MultiHolder extends Disposable {
|
|
772
731
|
}
|
|
773
|
-
exports.
|
|
774
|
-
function boolAttr(attrName, boolValueObs) {
|
|
775
|
-
return (elem) => _subscribe(elem, boolValueObs, (val) => boolAttrElem(elem, attrName, val));
|
|
776
|
-
}
|
|
777
|
-
exports.boolAttr = boolAttr;
|
|
732
|
+
exports.MultiHolder = MultiHolder;
|
|
778
733
|
/**
|
|
779
|
-
*
|
|
780
|
-
* observable or function.
|
|
781
|
-
* @param {Element} elem: The element to update.
|
|
782
|
-
* @param {String} value: The text value to add.
|
|
734
|
+
* Sets owner of obj (i.e. calls owner.autoDispose(obj)) unless owner is null. Returns obj.
|
|
783
735
|
*/
|
|
784
|
-
function
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
return (elem) => {
|
|
790
|
-
const textNode = browserGlobals_1.G.document.createTextNode('');
|
|
791
|
-
_subscribe(elem, valueObs, (val) => { textNode.nodeValue = val; });
|
|
792
|
-
elem.appendChild(textNode);
|
|
793
|
-
};
|
|
736
|
+
function setDisposeOwner(owner, obj) {
|
|
737
|
+
if (owner) {
|
|
738
|
+
owner.autoDispose(obj);
|
|
739
|
+
}
|
|
740
|
+
return obj;
|
|
794
741
|
}
|
|
795
|
-
exports.
|
|
742
|
+
exports.setDisposeOwner = setDisposeOwner;
|
|
796
743
|
/**
|
|
797
|
-
*
|
|
798
|
-
* `elem`, and `value` may be an observable or function.
|
|
799
|
-
* @param {Element} elem: The element to update.
|
|
800
|
-
* @param {String} property: The name of the style property to update, e.g. 'fontWeight'.
|
|
801
|
-
* @param {String} value: The value for the property.
|
|
744
|
+
* Helper for reporting errors during disposal. Try to report the type of the object.
|
|
802
745
|
*/
|
|
803
|
-
function
|
|
804
|
-
|
|
805
|
-
}
|
|
806
|
-
exports.styleElem = styleElem;
|
|
807
|
-
function style(property, valueObs) {
|
|
808
|
-
return (elem) => _subscribe(elem, valueObs, (val) => styleElem(elem, property, val));
|
|
746
|
+
function _describe(obj) {
|
|
747
|
+
return (obj && obj.constructor && obj.constructor.name ? obj.constructor.name : '' + obj);
|
|
809
748
|
}
|
|
810
|
-
exports.style = style;
|
|
811
749
|
/**
|
|
812
|
-
*
|
|
813
|
-
*
|
|
814
|
-
* @param {Element} elem: The element to update.
|
|
815
|
-
* @param {String} property: The name of the property to update, e.g. 'disabled'.
|
|
816
|
-
* @param {Object} value: The value for the property.
|
|
750
|
+
* DisposalList is an internal class mimicking emit.Emitter. The difference is that callbacks are
|
|
751
|
+
* called in reverse order, and exceptions in callbacks are reported and swallowed.
|
|
817
752
|
*/
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
753
|
+
class DisposalList extends emit_1.LLink {
|
|
754
|
+
constructor() { super(); }
|
|
755
|
+
addListener(callback, optContext) {
|
|
756
|
+
const lis = new DisposeListener(callback, optContext);
|
|
757
|
+
this._insertBefore(this._next, lis);
|
|
758
|
+
return lis;
|
|
759
|
+
}
|
|
760
|
+
/**
|
|
761
|
+
* Call all callbacks and dispose this object. The owner is required for better reporting of
|
|
762
|
+
* errors if any callback throws.
|
|
763
|
+
*/
|
|
764
|
+
callAndDispose(owner) {
|
|
765
|
+
try {
|
|
766
|
+
DisposeListener.callAll(this._next, this, owner);
|
|
767
|
+
}
|
|
768
|
+
finally {
|
|
769
|
+
this._disposeList();
|
|
770
|
+
}
|
|
771
|
+
}
|
|
824
772
|
}
|
|
825
|
-
exports.prop = prop;
|
|
826
773
|
/**
|
|
827
|
-
*
|
|
828
|
-
*
|
|
829
|
-
* The `show()` variant takes no `elem`, and `boolValue` may be an observable or function.
|
|
830
|
-
* @param {Element} elem: The element to update.
|
|
831
|
-
* @param {Boolean} boolValue: True to show the element, false to hide it.
|
|
774
|
+
* Internal class that keeps track of one item of the DisposalList. It mimicks emit.Listener, but
|
|
775
|
+
* reports and swallows erros when it calls the callbacks in the list.
|
|
832
776
|
*/
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
return (elem) => _subscribe(elem, boolValueObs, (val) => showElem(elem, val));
|
|
839
|
-
}
|
|
840
|
-
exports.show = show;
|
|
841
|
-
/**
|
|
842
|
-
* The opposite of show, hiding the element when boolValue is true.
|
|
843
|
-
* The `hide()` variant takes no `elem`, and `boolValue` may be an observable or function.
|
|
844
|
-
* @param {Element} elem: The element to update.
|
|
845
|
-
* @param {Boolean} boolValue: True to hide the element, false to show it.
|
|
846
|
-
*/
|
|
847
|
-
function hideElem(elem, boolValue) {
|
|
848
|
-
elem.style.display = boolValue ? 'none' : '';
|
|
849
|
-
}
|
|
850
|
-
exports.hideElem = hideElem;
|
|
851
|
-
function hide(boolValueObs) {
|
|
852
|
-
return (elem) => _subscribe(elem, boolValueObs, (val) => hideElem(elem, val));
|
|
853
|
-
}
|
|
854
|
-
exports.hide = hide;
|
|
855
|
-
/**
|
|
856
|
-
* Sets or toggles the given css class className.
|
|
857
|
-
*/
|
|
858
|
-
function clsElem(elem, className, boolValue = true) {
|
|
859
|
-
elem.classList.toggle(className, Boolean(boolValue));
|
|
860
|
-
}
|
|
861
|
-
exports.clsElem = clsElem;
|
|
862
|
-
function cls(className, boolValue) {
|
|
863
|
-
if (typeof className !== 'string') {
|
|
864
|
-
return _clsDynamicPrefix('', className);
|
|
865
|
-
}
|
|
866
|
-
else if (!boolValue || typeof boolValue === 'boolean') {
|
|
867
|
-
return (elem) => clsElem(elem, className, boolValue);
|
|
868
|
-
}
|
|
869
|
-
else {
|
|
870
|
-
return (elem) => _subscribe(elem, boolValue, (val) => clsElem(elem, className, val));
|
|
871
|
-
}
|
|
872
|
-
}
|
|
873
|
-
exports.cls = cls;
|
|
874
|
-
function clsPrefix(prefix, className, boolValue) {
|
|
875
|
-
if (typeof className !== 'string') {
|
|
876
|
-
return _clsDynamicPrefix(prefix, className);
|
|
877
|
-
}
|
|
878
|
-
else {
|
|
879
|
-
return cls(prefix + className, boolValue);
|
|
777
|
+
class DisposeListener extends emit_1.LLink {
|
|
778
|
+
constructor(callback, context) {
|
|
779
|
+
super();
|
|
780
|
+
this.callback = callback;
|
|
781
|
+
this.context = context;
|
|
880
782
|
}
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
_subscribe(elem, className, (name) => {
|
|
887
|
-
if (prevClass) {
|
|
888
|
-
elem.classList.remove(prevClass);
|
|
783
|
+
static callAll(begin, end, owner) {
|
|
784
|
+
while (begin !== end) {
|
|
785
|
+
const lis = begin;
|
|
786
|
+
try {
|
|
787
|
+
lis.callback.call(lis.context);
|
|
889
788
|
}
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
789
|
+
catch (e) {
|
|
790
|
+
// tslint:disable-next-line:no-console
|
|
791
|
+
console.error("While disposing %s, error disposing %s: %s", _describe(owner), _describe(this), e);
|
|
893
792
|
}
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
}
|
|
897
|
-
/**
|
|
898
|
-
* Associate arbitrary data with a DOM element. The `data()` variant takes no `elem`, and `value`
|
|
899
|
-
* may be an observable or function.
|
|
900
|
-
* @param {Element} elem: The element with which to associate data.
|
|
901
|
-
* @param {String} key: Key to identify this piece of data among others attached to elem.
|
|
902
|
-
* @param {Object} value: Arbitrary value to associate with elem.
|
|
903
|
-
*/
|
|
904
|
-
function dataElem(elem, key, value) {
|
|
905
|
-
const obj = _dataMap.get(elem);
|
|
906
|
-
if (obj) {
|
|
907
|
-
obj[key] = value;
|
|
793
|
+
begin = lis._next;
|
|
794
|
+
}
|
|
908
795
|
}
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
796
|
+
dispose() {
|
|
797
|
+
if (this.isDisposed()) {
|
|
798
|
+
return;
|
|
799
|
+
}
|
|
800
|
+
this._removeNode(this);
|
|
912
801
|
}
|
|
913
802
|
}
|
|
914
|
-
|
|
915
|
-
function
|
|
916
|
-
|
|
803
|
+
|
|
804
|
+
},{"./emit":16}],8:[function(require,module,exports){
|
|
805
|
+
"use strict";
|
|
806
|
+
/**
|
|
807
|
+
* dom.js provides a way to build a DOM tree easily.
|
|
808
|
+
*
|
|
809
|
+
* E.g.
|
|
810
|
+
* import {dom} from 'grainjs';
|
|
811
|
+
* dom('a#link.c1.c2', {'href': url}, 'Hello ', dom('span', 'world'));
|
|
812
|
+
* creates Node <a id="link" class="c1 c2" href={{url}}Hello <span>world</span></a>.
|
|
813
|
+
*
|
|
814
|
+
* dom.frag(dom('span', 'Hello'), ['blah', dom('div', 'world')])
|
|
815
|
+
* creates document fragment with <span>Hello</span>blah<div>world</div>.
|
|
816
|
+
*
|
|
817
|
+
* DOM can also be created and modified inline during creation:
|
|
818
|
+
* dom('a#id.c1',
|
|
819
|
+
* dom.cls('c2'), dom.attr('href', url),
|
|
820
|
+
* dom.text('Hello '), dom('span', dom.text('world')))
|
|
821
|
+
* creates Node <a id="link" class="c1 c2" href={{url}}Hello <span>world</span></a>,
|
|
822
|
+
* identical to the first example above.
|
|
823
|
+
*/
|
|
824
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
825
|
+
if (k2 === undefined) k2 = k;
|
|
826
|
+
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
|
|
827
|
+
}) : (function(o, m, k, k2) {
|
|
828
|
+
if (k2 === undefined) k2 = k;
|
|
829
|
+
o[k2] = m[k];
|
|
830
|
+
}));
|
|
831
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
832
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
833
|
+
};
|
|
834
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
835
|
+
exports.dom = void 0;
|
|
836
|
+
// We keep various dom-related functions organized in private modules, but they are exposed here.
|
|
837
|
+
__exportStar(require("./domImpl"), exports);
|
|
838
|
+
__exportStar(require("./domComponent"), exports);
|
|
839
|
+
__exportStar(require("./domComputed"), exports);
|
|
840
|
+
__exportStar(require("./domDispose"), exports);
|
|
841
|
+
__exportStar(require("./domForEach"), exports);
|
|
842
|
+
__exportStar(require("./domMethods"), exports);
|
|
843
|
+
__exportStar(require("./domevent"), exports);
|
|
844
|
+
const _domComponent = require("./domComponent");
|
|
845
|
+
const _domComputed = require("./domComputed");
|
|
846
|
+
const _domDispose = require("./domDispose");
|
|
847
|
+
const _domForEach = require("./domForEach");
|
|
848
|
+
const _domImpl = require("./domImpl");
|
|
849
|
+
const _domMethods = require("./domMethods");
|
|
850
|
+
const domevent = require("./domevent");
|
|
851
|
+
const domImpl_1 = require("./domImpl");
|
|
852
|
+
// We just want to re-export _domImpl.dom, but to allow adding methods to it in a typesafe way,
|
|
853
|
+
// TypeScript wants us to declare a real function in the same file.
|
|
854
|
+
function dom(tagString, ...args) {
|
|
855
|
+
return domImpl_1.dom(tagString, ...args);
|
|
917
856
|
}
|
|
918
|
-
exports.
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
857
|
+
exports.dom = dom;
|
|
858
|
+
// Additionally export all methods as properties of dom() function.
|
|
859
|
+
(function (dom) {
|
|
860
|
+
dom.svg = _domImpl.svg;
|
|
861
|
+
dom.frag = _domImpl.frag;
|
|
862
|
+
dom.update = _domImpl.update;
|
|
863
|
+
dom.find = _domImpl.find;
|
|
864
|
+
dom.findAll = _domImpl.findAll;
|
|
865
|
+
dom.domDispose = _domDispose.domDispose;
|
|
866
|
+
dom.onDisposeElem = _domDispose.onDisposeElem;
|
|
867
|
+
dom.onDispose = _domDispose.onDispose;
|
|
868
|
+
dom.autoDisposeElem = _domDispose.autoDisposeElem;
|
|
869
|
+
dom.autoDispose = _domDispose.autoDispose;
|
|
870
|
+
dom.attrsElem = _domMethods.attrsElem;
|
|
871
|
+
dom.attrs = _domMethods.attrs;
|
|
872
|
+
dom.attrElem = _domMethods.attrElem;
|
|
873
|
+
dom.attr = _domMethods.attr;
|
|
874
|
+
dom.boolAttrElem = _domMethods.boolAttrElem;
|
|
875
|
+
dom.boolAttr = _domMethods.boolAttr;
|
|
876
|
+
dom.textElem = _domMethods.textElem;
|
|
877
|
+
dom.text = _domMethods.text;
|
|
878
|
+
dom.styleElem = _domMethods.styleElem;
|
|
879
|
+
dom.style = _domMethods.style;
|
|
880
|
+
dom.propElem = _domMethods.propElem;
|
|
881
|
+
dom.prop = _domMethods.prop;
|
|
882
|
+
dom.showElem = _domMethods.showElem;
|
|
883
|
+
dom.show = _domMethods.show;
|
|
884
|
+
dom.hideElem = _domMethods.hideElem;
|
|
885
|
+
dom.hide = _domMethods.hide;
|
|
886
|
+
dom.clsElem = _domMethods.clsElem;
|
|
887
|
+
dom.cls = _domMethods.cls;
|
|
888
|
+
dom.clsPrefix = _domMethods.clsPrefix;
|
|
889
|
+
dom.dataElem = _domMethods.dataElem;
|
|
890
|
+
dom.data = _domMethods.data;
|
|
891
|
+
dom.getData = _domMethods.getData;
|
|
892
|
+
dom.replaceContent = _domComputed.replaceContent;
|
|
893
|
+
dom.domComputed = _domComputed.domComputed;
|
|
894
|
+
dom.domComputedOwned = _domComputed.domComputedOwned;
|
|
895
|
+
dom.maybe = _domComputed.maybe;
|
|
896
|
+
dom.maybeOwned = _domComputed.maybeOwned;
|
|
897
|
+
dom.forEach = _domForEach.forEach;
|
|
898
|
+
dom.create = _domComponent.create;
|
|
899
|
+
dom.onElem = domevent.onElem;
|
|
900
|
+
dom.on = domevent.on;
|
|
901
|
+
dom.onMatchElem = domevent.onMatchElem;
|
|
902
|
+
dom.onMatch = domevent.onMatch;
|
|
903
|
+
dom.onKeyElem = domevent.onKeyElem;
|
|
904
|
+
dom.onKeyPress = domevent.onKeyPress;
|
|
905
|
+
dom.onKeyDown = domevent.onKeyDown;
|
|
906
|
+
})(dom = exports.dom || (exports.dom = {}));
|
|
907
|
+
|
|
908
|
+
},{"./domComponent":9,"./domComputed":10,"./domDispose":11,"./domForEach":12,"./domImpl":13,"./domMethods":14,"./domevent":15}],9:[function(require,module,exports){
|
|
909
|
+
"use strict";
|
|
910
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
911
|
+
exports.create = void 0;
|
|
912
|
+
const domComputed_1 = require("./domComputed");
|
|
913
|
+
function create(fn, ...args) {
|
|
914
|
+
return domComputed_1.domComputedOwned(null, (owner) => {
|
|
915
|
+
const value = ('create' in fn) ?
|
|
916
|
+
fn.create(owner, ...args) :
|
|
917
|
+
fn(owner, ...args);
|
|
918
|
+
return (value && typeof value === 'object' && 'buildDom' in value) ?
|
|
919
|
+
value.buildDom() : value;
|
|
920
|
+
});
|
|
922
921
|
}
|
|
923
|
-
exports.
|
|
922
|
+
exports.create = create;
|
|
923
|
+
|
|
924
|
+
},{"./domComputed":10}],10:[function(require,module,exports){
|
|
925
|
+
"use strict";
|
|
926
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
927
|
+
exports.maybeOwned = exports.maybe = exports.domComputedOwned = exports.domComputed = exports.replaceContent = void 0;
|
|
928
|
+
const binding_1 = require("./binding");
|
|
929
|
+
const dispose_1 = require("./dispose");
|
|
930
|
+
const domDispose_1 = require("./domDispose");
|
|
931
|
+
const domImpl_1 = require("./domImpl");
|
|
932
|
+
// Use the browser globals in a way that allows replacing them with mocks in tests.
|
|
933
|
+
const browserGlobals_1 = require("./browserGlobals");
|
|
924
934
|
/**
|
|
925
935
|
* Replaces the content between nodeBefore and nodeAfter, which should be two siblings within the
|
|
926
936
|
* same parent node. New content may be anything allowed as an argument to dom(), including null
|
|
@@ -932,26 +942,39 @@ function replaceContent(nodeBefore, nodeAfter, content) {
|
|
|
932
942
|
let next;
|
|
933
943
|
for (let n = nodeBefore.nextSibling; n && n !== nodeAfter; n = next) {
|
|
934
944
|
next = n.nextSibling;
|
|
935
|
-
|
|
945
|
+
domDispose_1.domDispose(n);
|
|
936
946
|
elem.removeChild(n);
|
|
937
947
|
}
|
|
938
948
|
if (content) {
|
|
939
|
-
elem.insertBefore(content instanceof browserGlobals_1.G.Node ? content :
|
|
949
|
+
elem.insertBefore(content instanceof browserGlobals_1.G.Node ? content : domImpl_1.frag(content), nodeAfter);
|
|
940
950
|
}
|
|
941
951
|
}
|
|
942
952
|
}
|
|
943
953
|
exports.replaceContent = replaceContent;
|
|
944
|
-
function domComputed(valueObs, contentFunc) {
|
|
945
|
-
const
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
};
|
|
954
|
+
function domComputed(valueObs, contentFunc = identity) {
|
|
955
|
+
const markerPre = browserGlobals_1.G.document.createComment('a');
|
|
956
|
+
const markerPost = browserGlobals_1.G.document.createComment('b');
|
|
957
|
+
// Function is added after markerPre and markerPost, so that it runs once they have already been
|
|
958
|
+
// attached to elem (the parent element).
|
|
959
|
+
return [markerPre, markerPost, (elem) => {
|
|
960
|
+
binding_1.subscribeElem(markerPost, valueObs, (value) => replaceContent(markerPre, markerPost, contentFunc(value)));
|
|
961
|
+
}];
|
|
953
962
|
}
|
|
954
963
|
exports.domComputed = domComputed;
|
|
964
|
+
/**
|
|
965
|
+
* Like domComputed(), but the callback gets an additional first argument, owner, which may be
|
|
966
|
+
* used to take ownership of objects created by the callback. These will be disposed before each
|
|
967
|
+
* new call to the callback, and when the containing DOM is disposed.
|
|
968
|
+
*
|
|
969
|
+
* domComputedOwned(valueObs, (owner, value) => Editor.create(owner, value).renderSomething());
|
|
970
|
+
*/
|
|
971
|
+
function domComputedOwned(valueObs, contentFunc) {
|
|
972
|
+
const holder = dispose_1.Holder.create(null);
|
|
973
|
+
const [markerPre, markerPost, func] = domComputed(valueObs, (val) => contentFunc(dispose_1.MultiHolder.create(holder), val));
|
|
974
|
+
domDispose_1.autoDisposeElem(markerPost, holder);
|
|
975
|
+
return [markerPre, markerPost, func];
|
|
976
|
+
}
|
|
977
|
+
exports.domComputedOwned = domComputedOwned;
|
|
955
978
|
function identity(arg) { return arg; }
|
|
956
979
|
/**
|
|
957
980
|
* Conditionally appends DOM to an element. The value may be an observable or function (from which
|
|
@@ -972,607 +995,606 @@ function identity(arg) { return arg; }
|
|
|
972
995
|
*
|
|
973
996
|
* The latter is preferred for being simpler.
|
|
974
997
|
*
|
|
975
|
-
* @param
|
|
976
|
-
* @param
|
|
977
|
-
* @param [Function] contentFunc: Function called with the result of boolValueObs when it is
|
|
978
|
-
* truthy. Should returning DOM as output.
|
|
998
|
+
* @param boolValueObs: Observable or function for a computed.
|
|
999
|
+
* @param contentFunc: Called with the result of boolValueObs when it is truthy. Should return DOM.
|
|
979
1000
|
*/
|
|
980
1001
|
function maybe(boolValueObs, contentFunc) {
|
|
981
1002
|
return domComputed(boolValueObs, (value) => value ? contentFunc(value) : null);
|
|
982
1003
|
}
|
|
983
1004
|
exports.maybe = maybe;
|
|
984
|
-
|
|
985
|
-
},{"./_domDispose":5,"./_domImpl":7,"./binding":9,"./browserGlobals":10}],9:[function(require,module,exports){
|
|
986
|
-
"use strict";
|
|
987
1005
|
/**
|
|
988
|
-
*
|
|
989
|
-
*
|
|
1006
|
+
* Like maybe(), but the callback gets an additional first argument, owner, which may be used to
|
|
1007
|
+
* take ownership of objects created by the callback. These will be disposed before each new call
|
|
1008
|
+
* to the callback, and when the condition becomes false or the containing DOM gets disposed.
|
|
1009
|
+
*
|
|
1010
|
+
* maybeOwned(showEditor, (owner) => Editor.create(owner).renderSomething());
|
|
990
1011
|
*/
|
|
1012
|
+
function maybeOwned(boolValueObs, contentFunc) {
|
|
1013
|
+
return domComputedOwned(boolValueObs, (owner, value) => value ? contentFunc(owner, value) : null);
|
|
1014
|
+
}
|
|
1015
|
+
exports.maybeOwned = maybeOwned;
|
|
1016
|
+
|
|
1017
|
+
},{"./binding":4,"./browserGlobals":5,"./dispose":7,"./domDispose":11,"./domImpl":13}],11:[function(require,module,exports){
|
|
1018
|
+
"use strict";
|
|
991
1019
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
992
|
-
|
|
993
|
-
const observable_1 = require("./observable");
|
|
1020
|
+
exports.autoDispose = exports.autoDisposeElem = exports.onDispose = exports.onDisposeElem = exports.domDispose = exports.domDisposeHooks = exports._disposeNode = void 0;
|
|
994
1021
|
/**
|
|
995
|
-
*
|
|
996
|
-
*
|
|
997
|
-
*
|
|
998
|
-
* documentation for `computed`).
|
|
1022
|
+
* Private global disposal map. It maintains the association between DOM nodes and cleanup
|
|
1023
|
+
* functions added with dom.onDispose(). To support multiple disposers on one element, we use a
|
|
1024
|
+
* WeakMap-based linked list:
|
|
999
1025
|
*
|
|
1000
|
-
*
|
|
1001
|
-
*
|
|
1026
|
+
* _disposeMap[elem] = disposer2;
|
|
1027
|
+
* _disposeMap[disposer2] = disposer1;
|
|
1028
|
+
* etc.
|
|
1002
1029
|
*
|
|
1003
|
-
*
|
|
1030
|
+
* This avoids allocating arrays or using undeclared properties for a different linked list.
|
|
1004
1031
|
*/
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
callback(val, old);
|
|
1016
|
-
});
|
|
1017
|
-
callback(savedValue, undefined);
|
|
1018
|
-
return sub;
|
|
1019
|
-
}
|
|
1020
|
-
// Function from which to make a computed. Note that this is also reasonable:
|
|
1021
|
-
// let sub = subscribe(use => callback(valueObs(use)));
|
|
1022
|
-
// The difference is that when valueObs() evaluates to unchanged value, callback would be
|
|
1023
|
-
// called in the version above, but not in the version below.
|
|
1024
|
-
const comp = computed_1.computed(valueObs);
|
|
1025
|
-
comp.addListener(callback);
|
|
1026
|
-
callback(comp.get(), undefined);
|
|
1027
|
-
return comp; // Disposing this will dispose its one listener.
|
|
1032
|
+
const _disposeMap = new WeakMap();
|
|
1033
|
+
// Internal helper to walk the DOM tree, calling visitFunc(elem) on all descendants of elem.
|
|
1034
|
+
// Descendants are processed first.
|
|
1035
|
+
function _walkDom(elem, visitFunc) {
|
|
1036
|
+
let c = elem.firstChild;
|
|
1037
|
+
while (c) {
|
|
1038
|
+
// Note: this might be better done using an explicit stack, but in practice DOM trees aren't
|
|
1039
|
+
// so deep as to cause problems.
|
|
1040
|
+
_walkDom(c, visitFunc);
|
|
1041
|
+
c = c.nextSibling;
|
|
1028
1042
|
}
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1043
|
+
visitFunc(elem);
|
|
1044
|
+
}
|
|
1045
|
+
// Internal helper to run all disposers for a single element.
|
|
1046
|
+
function _disposeNode(node) {
|
|
1047
|
+
let disposer = _disposeMap.get(node);
|
|
1048
|
+
if (disposer) {
|
|
1049
|
+
let key = node;
|
|
1050
|
+
do {
|
|
1051
|
+
_disposeMap.delete(key);
|
|
1052
|
+
disposer(node);
|
|
1053
|
+
// Find the next disposer; these are chained when there are multiple.
|
|
1054
|
+
key = disposer;
|
|
1055
|
+
disposer = _disposeMap.get(key);
|
|
1056
|
+
} while (disposer);
|
|
1034
1057
|
}
|
|
1035
|
-
callback(valueObs, undefined);
|
|
1036
|
-
return null;
|
|
1037
1058
|
}
|
|
1038
|
-
exports.
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1059
|
+
exports._disposeNode = _disposeNode;
|
|
1060
|
+
function _disposeNodeRecursive(node) {
|
|
1061
|
+
_walkDom(node, exports.domDisposeHooks.disposeNode);
|
|
1062
|
+
}
|
|
1042
1063
|
/**
|
|
1043
|
-
*
|
|
1044
|
-
*
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1064
|
+
* Support for extending dom disposal. This is very low-level, and needs utmost care. Any
|
|
1065
|
+
* disposers set should take care of calling the original versions of the disposers.
|
|
1066
|
+
*/
|
|
1067
|
+
exports.domDisposeHooks = {
|
|
1068
|
+
disposeNode: _disposeNode,
|
|
1069
|
+
disposeRecursive: _disposeNodeRecursive,
|
|
1070
|
+
};
|
|
1071
|
+
/**
|
|
1072
|
+
* Run disposers associated with any descendant of elem or with elem itself. Disposers get
|
|
1073
|
+
* associated with elements using dom.onDispose(). Descendants are processed first.
|
|
1051
1074
|
*
|
|
1052
|
-
*
|
|
1075
|
+
* It is automatically called if one of the function arguments to dom() throws an exception during
|
|
1076
|
+
* element creation. This way any onDispose() handlers set on the unfinished element get called.
|
|
1053
1077
|
*
|
|
1054
|
-
*
|
|
1055
|
-
* before(function() {
|
|
1056
|
-
* pushGlobals(mockWindow); // e.g. jsdom.jsdom(...).defaultView
|
|
1057
|
-
* });
|
|
1058
|
-
* after(function() {
|
|
1059
|
-
* popGlobals();
|
|
1060
|
-
* });
|
|
1078
|
+
* @param {Node} node: The element to run disposers on.
|
|
1061
1079
|
*/
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
dest.DocumentFragment = source.DocumentFragment;
|
|
1065
|
-
dest.Element = source.Element;
|
|
1066
|
-
dest.Node = source.Node;
|
|
1067
|
-
dest.document = source.document;
|
|
1068
|
-
dest.window = source.window;
|
|
1080
|
+
function domDispose(node) {
|
|
1081
|
+
exports.domDisposeHooks.disposeRecursive(node);
|
|
1069
1082
|
}
|
|
1070
|
-
|
|
1071
|
-
const initial = {};
|
|
1072
|
-
_updateGlobals(initial, (typeof window !== 'undefined' ? window : {}));
|
|
1073
|
-
// The globals G object strats out with a copy of `initial`.
|
|
1074
|
-
exports.G = Object.assign({}, initial);
|
|
1075
|
-
// The stack of globals that always has the intial object, but which may be overridden.
|
|
1076
|
-
const _globalsStack = [initial];
|
|
1083
|
+
exports.domDispose = domDispose;
|
|
1077
1084
|
/**
|
|
1078
|
-
*
|
|
1085
|
+
* Associate a disposerFunc with a DOM element. It will be called when the element is disposed
|
|
1086
|
+
* using domDispose() on it or any of its parents. If onDispose is called multiple times, all
|
|
1087
|
+
* disposerFuncs will be called in reverse order.
|
|
1088
|
+
* @param {Element} elem: The element to associate the disposer with.
|
|
1089
|
+
* @param {Function} disposerFunc(elem): Will be called when domDispose() is called on the
|
|
1090
|
+
* element or its ancestor.
|
|
1091
|
+
* Note that it is not necessary usually to dispose event listeners attached to an element (e.g.
|
|
1092
|
+
* with dom.on()) since their lifetime is naturally limited to the lifetime of the element.
|
|
1079
1093
|
*/
|
|
1080
|
-
function
|
|
1081
|
-
|
|
1082
|
-
|
|
1094
|
+
function onDisposeElem(elem, disposerFunc) {
|
|
1095
|
+
const prevDisposer = _disposeMap.get(elem);
|
|
1096
|
+
_disposeMap.set(elem, disposerFunc);
|
|
1097
|
+
if (prevDisposer) {
|
|
1098
|
+
_disposeMap.set(disposerFunc, prevDisposer);
|
|
1099
|
+
}
|
|
1083
1100
|
}
|
|
1084
|
-
exports.
|
|
1101
|
+
exports.onDisposeElem = onDisposeElem;
|
|
1102
|
+
function onDispose(disposerFunc) {
|
|
1103
|
+
return (elem) => onDisposeElem(elem, disposerFunc);
|
|
1104
|
+
}
|
|
1105
|
+
exports.onDispose = onDispose;
|
|
1085
1106
|
/**
|
|
1086
|
-
*
|
|
1107
|
+
* Make the given element own the disposable, and call its dispose method when domDispose() is
|
|
1108
|
+
* called on the element or any of its parents.
|
|
1109
|
+
* @param {Element} elem: The element to own the disposable.
|
|
1110
|
+
* @param {Disposable} disposable: Anything with a .dispose() method.
|
|
1087
1111
|
*/
|
|
1088
|
-
function
|
|
1089
|
-
if (
|
|
1090
|
-
|
|
1112
|
+
function autoDisposeElem(elem, disposable) {
|
|
1113
|
+
if (disposable) {
|
|
1114
|
+
onDisposeElem(elem, () => disposable.dispose());
|
|
1091
1115
|
}
|
|
1092
|
-
_updateGlobals(exports.G, _globalsStack[_globalsStack.length - 1]);
|
|
1093
1116
|
}
|
|
1094
|
-
exports.
|
|
1117
|
+
exports.autoDisposeElem = autoDisposeElem;
|
|
1118
|
+
function autoDispose(disposable) {
|
|
1119
|
+
if (disposable) {
|
|
1120
|
+
return (elem) => autoDisposeElem(elem, disposable);
|
|
1121
|
+
}
|
|
1122
|
+
}
|
|
1123
|
+
exports.autoDispose = autoDispose;
|
|
1095
1124
|
|
|
1096
|
-
},{}],
|
|
1125
|
+
},{}],12:[function(require,module,exports){
|
|
1097
1126
|
"use strict";
|
|
1127
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
1128
|
+
exports.forEach = void 0;
|
|
1129
|
+
const domComputed_1 = require("./domComputed");
|
|
1130
|
+
const domDispose_1 = require("./domDispose");
|
|
1131
|
+
const domImpl_1 = require("./domImpl");
|
|
1132
|
+
const obsArray_1 = require("./obsArray");
|
|
1133
|
+
// Use the browser globals in a way that allows replacing them with mocks in tests.
|
|
1134
|
+
const browserGlobals_1 = require("./browserGlobals");
|
|
1098
1135
|
/**
|
|
1099
|
-
*
|
|
1100
|
-
*
|
|
1101
|
-
*
|
|
1102
|
-
* E.g. if we have some existing observables (which may themselves be instances of `computed`),
|
|
1103
|
-
* we can create a computed that subscribes to them explicitly:
|
|
1104
|
-
* let obs1 = observable(5), obs2 = observable(12);
|
|
1105
|
-
* let computed1 = computed(obs1, obs2, (use, v1, v2) => v1 + v2);
|
|
1106
|
-
*
|
|
1107
|
-
* or implicitly by using `use(obs)` function:
|
|
1108
|
-
* let computed2 = computed(use => use(obs1) + use(obs2));
|
|
1136
|
+
* Creates DOM elements for each element of an observable array. As the array is changed, children
|
|
1137
|
+
* are added or removed. This works for any array-valued observable, and for obsArray() and
|
|
1138
|
+
* computedArray() it works more efficiently for simple changes.
|
|
1109
1139
|
*
|
|
1110
|
-
*
|
|
1111
|
-
*
|
|
1140
|
+
* The given itemCreateFunc() should return a single DOM node for each item, or null to skip that
|
|
1141
|
+
* item. It is called for new items whenever they are spliced in, or the array replaced. The
|
|
1142
|
+
* forEach() owns the created nodes, and runs domDispose() on them when they are spliced out.
|
|
1112
1143
|
*
|
|
1113
|
-
*
|
|
1114
|
-
*
|
|
1115
|
-
* detected using use(). Note that constructor dependencies have less overhead.
|
|
1144
|
+
* If the created nodes are removed from their parent externally, forEach() will cope with it, but
|
|
1145
|
+
* will consider these elements as no longer owned, and will not run domDispose() on them.
|
|
1116
1146
|
*
|
|
1117
|
-
*
|
|
1147
|
+
* Note that itemCreateFunc() does not receive an index: an index would only be correct at the
|
|
1148
|
+
* time the item is created, and would not reflect further changes to the array.
|
|
1118
1149
|
*
|
|
1119
|
-
*
|
|
1120
|
-
*
|
|
1121
|
-
|
|
1150
|
+
* If you'd like to map the DOM node back to its source item, use dom.data() and dom.getData() in
|
|
1151
|
+
* itemCreateFunc().
|
|
1152
|
+
*/
|
|
1153
|
+
function forEach(obsArray, itemCreateFunc) {
|
|
1154
|
+
const markerPre = browserGlobals_1.G.document.createComment('a');
|
|
1155
|
+
const markerPost = browserGlobals_1.G.document.createComment('b');
|
|
1156
|
+
return [markerPre, markerPost, (elem) => {
|
|
1157
|
+
if (Array.isArray(obsArray)) {
|
|
1158
|
+
domComputed_1.replaceContent(markerPre, markerPost, obsArray.map(itemCreateFunc));
|
|
1159
|
+
return;
|
|
1160
|
+
}
|
|
1161
|
+
const nodes = obsArray_1.computedArray(obsArray, itemCreateFunc);
|
|
1162
|
+
// Be sure to dispose the newly-created array when the DOM it's associated with is gone.
|
|
1163
|
+
domDispose_1.autoDisposeElem(markerPost, nodes);
|
|
1164
|
+
nodes.addListener((newArr, oldArr, splice) => {
|
|
1165
|
+
if (splice) {
|
|
1166
|
+
// Remove the elements that are gone.
|
|
1167
|
+
for (const node of splice.deleted) {
|
|
1168
|
+
if (node && node.parentNode === elem) {
|
|
1169
|
+
domDispose_1.domDispose(node);
|
|
1170
|
+
elem.removeChild(node);
|
|
1171
|
+
}
|
|
1172
|
+
}
|
|
1173
|
+
if (splice.numAdded > 0) {
|
|
1174
|
+
// Find a valid child immediately following the spliced out portion, for DOM insertion.
|
|
1175
|
+
const endIndex = splice.start + splice.numAdded;
|
|
1176
|
+
let nextElem = markerPost;
|
|
1177
|
+
for (let i = endIndex; i < newArr.length; i++) {
|
|
1178
|
+
const node = newArr[i];
|
|
1179
|
+
if (node && node.parentNode === elem) {
|
|
1180
|
+
nextElem = node;
|
|
1181
|
+
break;
|
|
1182
|
+
}
|
|
1183
|
+
}
|
|
1184
|
+
// Insert the new elements.
|
|
1185
|
+
const content = domImpl_1.frag(newArr.slice(splice.start, endIndex));
|
|
1186
|
+
elem.insertBefore(content, nextElem);
|
|
1187
|
+
}
|
|
1188
|
+
}
|
|
1189
|
+
else {
|
|
1190
|
+
domComputed_1.replaceContent(markerPre, markerPost, newArr);
|
|
1191
|
+
}
|
|
1192
|
+
});
|
|
1193
|
+
domComputed_1.replaceContent(markerPre, markerPost, nodes.get());
|
|
1194
|
+
}];
|
|
1195
|
+
}
|
|
1196
|
+
exports.forEach = forEach;
|
|
1197
|
+
|
|
1198
|
+
},{"./browserGlobals":5,"./domComputed":10,"./domDispose":11,"./domImpl":13,"./obsArray":18}],13:[function(require,module,exports){
|
|
1199
|
+
"use strict";
|
|
1200
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
1201
|
+
exports.findAll = exports.find = exports.frag = exports.update = exports.svg = exports.dom = void 0;
|
|
1202
|
+
const domDispose_1 = require("./domDispose");
|
|
1203
|
+
const domMethods_1 = require("./domMethods");
|
|
1204
|
+
// Use the browser globals in a way that allows replacing them with mocks in tests.
|
|
1205
|
+
const browserGlobals_1 = require("./browserGlobals");
|
|
1206
|
+
// The goal of the above declarations is to get help from TypeScript in detecting incorrect usage:
|
|
1207
|
+
// (See test/types/dom.ts for a test of this.)
|
|
1208
|
+
// import {text, hide} from './domMethods';
|
|
1209
|
+
// dom('div', text('hello')); // OK
|
|
1210
|
+
// dom('div', hide(true)); // OK
|
|
1211
|
+
// dom('div', {title: 'hello'}); // OK
|
|
1212
|
+
// frag(text('hello')); // OK
|
|
1213
|
+
// frag(hide(true)); // Bad: DocumentFragment is not an Element
|
|
1214
|
+
// frag({title: 'hello'}); // Bad: DocumentFragment is not an Element
|
|
1215
|
+
/**
|
|
1216
|
+
* dom('tag#id.class1.class2', ...args)
|
|
1217
|
+
* The first argument is a string consisting of a tag name, with optional #foo suffix
|
|
1218
|
+
* to add the ID 'foo', and zero or more .bar suffixes to add a CSS class 'bar'.
|
|
1122
1219
|
*
|
|
1123
|
-
*
|
|
1124
|
-
*
|
|
1220
|
+
* NOTE that better typings are available when a tag is used directly, e.g.
|
|
1221
|
+
* dom('input', {id: 'foo'}, (elem) => ...) --> elem has type HTMLInputElement
|
|
1222
|
+
* dom('input#foo', (elem) => ...) --> elem has type HTMLElement
|
|
1125
1223
|
*
|
|
1126
|
-
*
|
|
1127
|
-
* let val = computed((use) => Foo.create(use.owner, use(a), use(b)));
|
|
1224
|
+
* The rest of the arguments are optional and may be:
|
|
1128
1225
|
*
|
|
1129
|
-
*
|
|
1130
|
-
*
|
|
1131
|
-
*
|
|
1226
|
+
* Nodes - which become children of the created element;
|
|
1227
|
+
* strings - which become text node children;
|
|
1228
|
+
* objects - of the form {attr: val} to set additional attributes on the element;
|
|
1229
|
+
* Arrays - which are flattened with each item processed recursively;
|
|
1230
|
+
* functions - which are called with elem as the argument, for a chance to modify the
|
|
1231
|
+
* element as it's being created. Return values are processed recursively.
|
|
1232
|
+
* "dom methods" - expressions such as `dom.attr('href', url)` or `dom.hide(obs)`, which
|
|
1233
|
+
* are actually special cases of the "functions" category.
|
|
1132
1234
|
*/
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
const subscribe_1 = require("./subscribe");
|
|
1136
|
-
function _noWrite() {
|
|
1137
|
-
throw new Error("Can't write to non-writable computed");
|
|
1235
|
+
function dom(tagString, ...args) {
|
|
1236
|
+
return _updateWithArgsOrDispose(_createFromTagString(_createElementHtml, tagString), args);
|
|
1138
1237
|
}
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1238
|
+
exports.dom = dom;
|
|
1239
|
+
/**
|
|
1240
|
+
* svg('tag#id.class1.class2', ...args)
|
|
1241
|
+
* Same as dom(...), but creates an SVG element.
|
|
1242
|
+
*/
|
|
1243
|
+
function svg(tagString, ...args) {
|
|
1244
|
+
return _updateWithArgsOrDispose(_createFromTagString(_createElementSvg, tagString), args);
|
|
1245
|
+
}
|
|
1246
|
+
exports.svg = svg;
|
|
1247
|
+
// Internal helper used to create HTML elements.
|
|
1248
|
+
function _createElementHtml(tag) {
|
|
1249
|
+
return browserGlobals_1.G.document.createElement(tag);
|
|
1250
|
+
}
|
|
1251
|
+
// Internal helper used to create SVG elements.
|
|
1252
|
+
function _createElementSvg(tag) {
|
|
1253
|
+
return browserGlobals_1.G.document.createElementNS("http://www.w3.org/2000/svg", tag);
|
|
1254
|
+
}
|
|
1255
|
+
/**
|
|
1256
|
+
* Internal helper to parse tagString, create an element using createFunc with the given tag, and
|
|
1257
|
+
* set its id and classes from the tagString.
|
|
1258
|
+
* @param {Funtion} createFunc(tag): Function that should create an element given a tag name.
|
|
1259
|
+
* It is passed in to allow creating elements in different namespaces (e.g. plain HTML vs SVG).
|
|
1260
|
+
* @param {String} tagString: String of the form "tag#id.class1.class2" where id and classes are
|
|
1261
|
+
* optional.
|
|
1262
|
+
* @return {Element} The result of createFunc(), possibly with id and class attributes also set.
|
|
1263
|
+
*/
|
|
1264
|
+
function _createFromTagString(createFunc, tagString) {
|
|
1265
|
+
// We do careful hand-written parsing rather than use a regexp for speed. Using a regexp is
|
|
1266
|
+
// significantly more expensive.
|
|
1267
|
+
let tag;
|
|
1268
|
+
let id;
|
|
1269
|
+
let classes;
|
|
1270
|
+
let dotPos = tagString.indexOf(".");
|
|
1271
|
+
const hashPos = tagString.indexOf('#');
|
|
1272
|
+
if (dotPos === -1) {
|
|
1273
|
+
dotPos = tagString.length;
|
|
1150
1274
|
}
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
*/
|
|
1154
|
-
_getDepItem() {
|
|
1155
|
-
return this._sub._getDepItem();
|
|
1275
|
+
else {
|
|
1276
|
+
classes = tagString.substring(dotPos + 1).replace(/\./g, ' ');
|
|
1156
1277
|
}
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
* constructor. Throws an error if there was no such callback (not a "writable" computed).
|
|
1160
|
-
* @param {Object} value: The value to pass to the write() callback.
|
|
1161
|
-
*/
|
|
1162
|
-
set(value) { this._write(value); }
|
|
1163
|
-
/**
|
|
1164
|
-
* Set callback to call when this.set(value) is called, to make it a writable computed. If not
|
|
1165
|
-
* set, attempting to write to this computed will throw an exception.
|
|
1166
|
-
*/
|
|
1167
|
-
onWrite(writeFunc) {
|
|
1168
|
-
this._write = writeFunc;
|
|
1169
|
-
return this;
|
|
1278
|
+
if (hashPos === -1) {
|
|
1279
|
+
tag = tagString.substring(0, dotPos);
|
|
1170
1280
|
}
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
*/
|
|
1174
|
-
dispose() {
|
|
1175
|
-
this._sub.dispose();
|
|
1176
|
-
super.dispose();
|
|
1281
|
+
else if (hashPos > dotPos) {
|
|
1282
|
+
throw new Error(`ID must come before classes in dom("${tagString}")`);
|
|
1177
1283
|
}
|
|
1178
|
-
|
|
1179
|
-
|
|
1284
|
+
else {
|
|
1285
|
+
tag = tagString.substring(0, hashPos);
|
|
1286
|
+
id = tagString.substring(hashPos + 1, dotPos);
|
|
1287
|
+
}
|
|
1288
|
+
const elem = createFunc(tag);
|
|
1289
|
+
if (id) {
|
|
1290
|
+
elem.setAttribute('id', id);
|
|
1291
|
+
}
|
|
1292
|
+
if (classes) {
|
|
1293
|
+
elem.setAttribute('class', classes);
|
|
1180
1294
|
}
|
|
1295
|
+
return elem;
|
|
1181
1296
|
}
|
|
1182
|
-
exports.Computed = Computed;
|
|
1183
1297
|
/**
|
|
1184
|
-
*
|
|
1185
|
-
* @param {Observable} ...observables: The initial params, of which there may be zero or more, are
|
|
1186
|
-
* observables on which this computed depends. When any of them change, the read() callback
|
|
1187
|
-
* will be called with the values of these observables as arguments.
|
|
1188
|
-
* @param {Function} readCallback: Read callback that will be called with (use, ...values),
|
|
1189
|
-
* i.e. the `use` function and values for all of the ...observables. The callback is called
|
|
1190
|
-
* immediately and whenever any dependency changes.
|
|
1191
|
-
* @returns {Computed} The newly created computed observable.
|
|
1298
|
+
* Update an element with any number of arguments, as documented in dom().
|
|
1192
1299
|
*/
|
|
1193
|
-
function
|
|
1194
|
-
|
|
1195
|
-
return new Computed(readCb, args);
|
|
1300
|
+
function update(elem, ...args) {
|
|
1301
|
+
return _updateWithArgs(elem, args);
|
|
1196
1302
|
}
|
|
1197
|
-
exports.
|
|
1198
|
-
// TODO Consider implementing .singleUse() method.
|
|
1199
|
-
// An open question is in how to pass e.g. kd.hide(computed(x, x => !x)) in such a way that
|
|
1200
|
-
// the temporary computed can be disposed when temporary, but not otherwise. A function-only
|
|
1201
|
-
// syntax is kd.hide(use => !use(x)), but prevents use of static subscriptions.
|
|
1202
|
-
//
|
|
1203
|
-
// (a) function-only use of computeds is fine and useful.
|
|
1204
|
-
// (b) pureComputed is another option, and doesn't technically require getting disposed.
|
|
1205
|
-
// (c) kd.hide(compObs), kd.autoDispose(compObs) is more general and
|
|
1206
|
-
// can be replaced more concisely by kd.hide(compObs.singleUse())
|
|
1207
|
-
// .singleUse() automatically disposes a computed (or an observable?) once there are no
|
|
1208
|
-
// subscriptions to it. If there are no subscriptions at the time of this call, waits for the next
|
|
1209
|
-
// tick, and possibly disposes then.
|
|
1210
|
-
|
|
1211
|
-
},{"./observable":18,"./subscribe":20}],12:[function(require,module,exports){
|
|
1212
|
-
"use strict";
|
|
1303
|
+
exports.update = update;
|
|
1213
1304
|
/**
|
|
1214
|
-
*
|
|
1215
|
-
* unsubscribe from events. The motivation with examples is presented here:
|
|
1216
|
-
*
|
|
1217
|
-
* https://phab.getgrist.com/w/disposal/
|
|
1218
|
-
*
|
|
1219
|
-
* Disposable is a class for components that need cleanup (e.g. maintain DOM, listen to events,
|
|
1220
|
-
* subscribe to anything). It provides a .dispose() method that should be called to destroy the
|
|
1221
|
-
* component, and .onDispose()/.autoDispose() methods that the component should use to take
|
|
1222
|
-
* responsibility for other pieces that require cleanup.
|
|
1223
|
-
*
|
|
1224
|
-
* To define a disposable class:
|
|
1225
|
-
* class Foo extends Disposable { ... }
|
|
1226
|
-
*
|
|
1227
|
-
* To create Foo:
|
|
1228
|
-
* const foo = Foo.create(owner, ...args);
|
|
1229
|
-
* This is better than `new Foo` for two reasons:
|
|
1230
|
-
* 1. If Foo's constructor throws an exception, any disposals registered in that constructor
|
|
1231
|
-
* before the exception are honored.
|
|
1232
|
-
* 2. It ensures you specify the owner of the new instance (but you can use null to skip it).
|
|
1233
|
-
*
|
|
1234
|
-
* In Foo's constructor (or rarely methods), take ownership of other Disposable objects:
|
|
1235
|
-
* this.bar = Bar.create(this, ...);
|
|
1236
|
-
*
|
|
1237
|
-
* For objects that are not instances of Disposable but have a .dispose() methods, use:
|
|
1238
|
-
* this.bar = this.autoDispose(createSomethingDisposable());
|
|
1239
|
-
*
|
|
1240
|
-
* To call a function on disposal (e.g. to add custom disposal logic):
|
|
1241
|
-
* this.onDispose(() => this.myUnsubscribeAllMethod());
|
|
1242
|
-
* this.onDispose(this.myUnsubscribeAllMethod, this); // slightly more efficient
|
|
1243
|
-
*
|
|
1244
|
-
* To mark this object to be wiped out on disposal (i.e. set all properties to null):
|
|
1245
|
-
* this.wipeOnDispose();
|
|
1246
|
-
* See the documentation of that method for more info.
|
|
1247
|
-
*
|
|
1248
|
-
* To dispose Foo directly:
|
|
1249
|
-
* foo.dispose();
|
|
1250
|
-
* To determine if an object has already been disposed:
|
|
1251
|
-
* foo.isDisposed()
|
|
1252
|
-
*
|
|
1253
|
-
* If you need to replace an owned object, or release, or dispose it early, use a Holder:
|
|
1254
|
-
* this._holder = Holder.create(this);
|
|
1255
|
-
* Bar.create(this._holder, 1); // creates new Bar(1)
|
|
1256
|
-
* Bar.create(this._holder, 2); // creates new Bar(2) and disposes previous object
|
|
1257
|
-
* this._holder.clear(); // disposes contained object
|
|
1258
|
-
* this._holder.release(); // releases contained object
|
|
1259
|
-
*
|
|
1260
|
-
* If creating your own class with a dispose() method, do NOT throw exceptions from dispose().
|
|
1261
|
-
* These cannot be handled properly in all cases. Read here about the same issue in C++:
|
|
1262
|
-
* http://bin-login.name/ftp/pub/docs/programming_languages/cpp/cffective_cpp/MAGAZINE/SU_FRAME.HTM#destruct
|
|
1305
|
+
* Update an element with an array of arguments.
|
|
1263
1306
|
*/
|
|
1264
|
-
|
|
1265
|
-
const
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
};
|
|
1271
|
-
// Newly-created Disposable instances will have this as their owner. This is not a constant, it
|
|
1272
|
-
// is used by create() for the safe creation of Disposables.
|
|
1273
|
-
let _defaultDisposableOwner = _noopOwner;
|
|
1307
|
+
function _updateWithArgs(elem, args) {
|
|
1308
|
+
for (const arg of args) {
|
|
1309
|
+
_updateWithArg(elem, arg);
|
|
1310
|
+
}
|
|
1311
|
+
return elem;
|
|
1312
|
+
}
|
|
1274
1313
|
/**
|
|
1275
|
-
*
|
|
1314
|
+
* Update an element with an array of arguments, calling disposers in case of an exception. It is
|
|
1315
|
+
* an internal helper to be used whenever elem is a newly-created element. If elem is an existing
|
|
1316
|
+
* element which the user already knows about, then _updateWithArgs should be called.
|
|
1276
1317
|
*/
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
// This registers with a temp Holder when using create(), and is a no-op when using `new Foo`.
|
|
1281
|
-
_defaultDisposableOwner.autoDispose(this);
|
|
1318
|
+
function _updateWithArgsOrDispose(elem, args) {
|
|
1319
|
+
try {
|
|
1320
|
+
return _updateWithArgs(elem, args);
|
|
1282
1321
|
}
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
try {
|
|
1287
|
-
// The newly-created object will have holder as its owner.
|
|
1288
|
-
_defaultDisposableOwner = holder;
|
|
1289
|
-
return setDisposeOwner(owner, new this(...args));
|
|
1290
|
-
}
|
|
1291
|
-
catch (e) {
|
|
1292
|
-
try {
|
|
1293
|
-
// This calls dispose on the partially-constructed object
|
|
1294
|
-
holder.clear();
|
|
1295
|
-
}
|
|
1296
|
-
catch (e2) {
|
|
1297
|
-
// tslint:disable-next-line:no-console
|
|
1298
|
-
console.error("Error disposing partially constructed %s:", this.name, e2);
|
|
1299
|
-
}
|
|
1300
|
-
throw e;
|
|
1301
|
-
}
|
|
1302
|
-
finally {
|
|
1303
|
-
// On success, the new object has a new owner, and we release it from holder.
|
|
1304
|
-
// On error, the holder has been cleared, and the release() is a no-op.
|
|
1305
|
-
holder.release();
|
|
1306
|
-
_defaultDisposableOwner = origDefaultOwner;
|
|
1307
|
-
}
|
|
1322
|
+
catch (e) {
|
|
1323
|
+
domDispose_1.domDispose(elem);
|
|
1324
|
+
throw e;
|
|
1308
1325
|
}
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1326
|
+
}
|
|
1327
|
+
function _updateWithArg(elem, arg) {
|
|
1328
|
+
if (typeof arg === 'function') {
|
|
1329
|
+
const value = arg(elem);
|
|
1330
|
+
// Skip the recursive call in the common case when the function returns nothing.
|
|
1331
|
+
if (value !== undefined && value !== null) {
|
|
1332
|
+
_updateWithArg(elem, value);
|
|
1333
|
+
}
|
|
1313
1334
|
}
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
this._disposalList.addListener(callback, context);
|
|
1335
|
+
else if (Array.isArray(arg)) {
|
|
1336
|
+
_updateWithArgs(elem, arg);
|
|
1317
1337
|
}
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
* recommended to call this early in the constructor.
|
|
1321
|
-
*
|
|
1322
|
-
* This makes disposal more costly, but has certain benefits:
|
|
1323
|
-
* - If anything still refers to the object and uses it, we'll get an early error, rather than
|
|
1324
|
-
* silently keep going, potentially doing useless work (or worse) and wasting resources.
|
|
1325
|
-
* - If anything still refers to the object (even without using it), the fields of the object
|
|
1326
|
-
* can still be garbage-collected.
|
|
1327
|
-
* - If there are circular references involving this object, they get broken, making the job
|
|
1328
|
-
* easier for the garbage collector.
|
|
1329
|
-
*
|
|
1330
|
-
* The recommendation is to use it for complex, longer-lived objects, but to skip for objects
|
|
1331
|
-
* which are numerous and short-lived (and less likely to be referenced from unexpected places).
|
|
1332
|
-
*/
|
|
1333
|
-
wipeOnDispose() {
|
|
1334
|
-
this.onDispose(this._wipeOutObject, this);
|
|
1338
|
+
else if (arg === undefined || arg === null) {
|
|
1339
|
+
// Nothing to do.
|
|
1335
1340
|
}
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
*/
|
|
1339
|
-
isDisposed() {
|
|
1340
|
-
return this._disposalList === null;
|
|
1341
|
+
else if (arg instanceof browserGlobals_1.G.Node) {
|
|
1342
|
+
elem.appendChild(arg);
|
|
1341
1343
|
}
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
* order to that in which they were added.
|
|
1345
|
-
*/
|
|
1346
|
-
dispose() {
|
|
1347
|
-
const disposalList = this._disposalList;
|
|
1348
|
-
this._disposalList = null;
|
|
1349
|
-
disposalList.callAndDispose(this);
|
|
1344
|
+
else if (typeof arg === 'object') {
|
|
1345
|
+
domMethods_1.attrsElem(elem, arg);
|
|
1350
1346
|
}
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
* disposed and should be ready to be garbage-collected.
|
|
1354
|
-
*/
|
|
1355
|
-
_wipeOutObject() {
|
|
1356
|
-
// The sentinel value doesn't have to be null, but some values cause more helpful errors than
|
|
1357
|
-
// others. E.g. if a.x = "disposed", then a.x.foo() throws "undefined is not a function", but
|
|
1358
|
-
// when a.x = null, a.x.foo() throws a more helpful "Cannot read property 'foo' of null".
|
|
1359
|
-
for (const k of Object.keys(this)) {
|
|
1360
|
-
this[k] = null;
|
|
1361
|
-
}
|
|
1347
|
+
else {
|
|
1348
|
+
elem.appendChild(browserGlobals_1.G.document.createTextNode(arg));
|
|
1362
1349
|
}
|
|
1363
1350
|
}
|
|
1364
|
-
exports.Disposable = Disposable;
|
|
1365
1351
|
/**
|
|
1366
|
-
*
|
|
1367
|
-
* holder.autoDispose() or Foo.create(holder, ...), it automatically disposes the currently held
|
|
1368
|
-
* object. It also disposes it when the holder itself is disposed.
|
|
1369
|
-
*
|
|
1370
|
-
* If the object is an instance of Disposable, the holder will also notice when the object gets
|
|
1371
|
-
* disposed from outside of it, in which case the holder will become empty again.
|
|
1372
|
-
*
|
|
1373
|
-
* TODO Holder needs unittests.
|
|
1352
|
+
* Creates a DocumentFragment processing arguments the same way as the dom() function.
|
|
1374
1353
|
*/
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1354
|
+
function frag(...args) {
|
|
1355
|
+
const elem = browserGlobals_1.G.document.createDocumentFragment();
|
|
1356
|
+
return _updateWithArgsOrDispose(elem, args);
|
|
1357
|
+
}
|
|
1358
|
+
exports.frag = frag;
|
|
1359
|
+
/**
|
|
1360
|
+
* Find the first element matching a selector; just an abbreviation for document.querySelector().
|
|
1361
|
+
*/
|
|
1362
|
+
function find(selector) { return browserGlobals_1.G.document.querySelector(selector); }
|
|
1363
|
+
exports.find = find;
|
|
1364
|
+
/**
|
|
1365
|
+
* Find all elements matching a selector; just an abbreviation for document.querySelectorAll().
|
|
1366
|
+
*/
|
|
1367
|
+
function findAll(selector) { return browserGlobals_1.G.document.querySelectorAll(selector); }
|
|
1368
|
+
exports.findAll = findAll;
|
|
1369
|
+
|
|
1370
|
+
},{"./browserGlobals":5,"./domDispose":11,"./domMethods":14}],14:[function(require,module,exports){
|
|
1371
|
+
"use strict";
|
|
1372
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
1373
|
+
exports.noTestId = exports.makeTestId = exports.getData = exports.data = exports.dataElem = exports.clsPrefix = exports.cls = exports.clsElem = exports.hide = exports.hideElem = exports.show = exports.showElem = exports.prop = exports.propElem = exports.style = exports.styleElem = exports.text = exports.textElem = exports.boolAttr = exports.boolAttrElem = exports.attr = exports.attrElem = exports.attrs = exports.attrsElem = void 0;
|
|
1374
|
+
const binding_1 = require("./binding");
|
|
1375
|
+
const domDispose_1 = require("./domDispose");
|
|
1376
|
+
// Use the browser globals in a way that allows replacing them with mocks in tests.
|
|
1377
|
+
const browserGlobals_1 = require("./browserGlobals");
|
|
1378
|
+
/**
|
|
1379
|
+
* Private global map for associating arbitrary data with DOM. It's a WeakMap, so does not prevent
|
|
1380
|
+
* values from being garbage collected when the owning DOM elements are no longer used.
|
|
1381
|
+
*/
|
|
1382
|
+
const _dataMap = new WeakMap();
|
|
1383
|
+
/**
|
|
1384
|
+
* Sets multiple attributes of a DOM element. The `attrs()` variant takes no `elem` argument.
|
|
1385
|
+
* Null and undefined values are omitted, and booleans are either omitted or set to empty string.
|
|
1386
|
+
* @param {Object} attrsObj: Object mapping attribute names to attribute values.
|
|
1387
|
+
*/
|
|
1388
|
+
function attrsElem(elem, attrsObj) {
|
|
1389
|
+
for (const key of Object.keys(attrsObj)) {
|
|
1390
|
+
const val = attrsObj[key];
|
|
1391
|
+
if (val != null && val !== false) {
|
|
1392
|
+
elem.setAttribute(key, val === true ? '' : val);
|
|
1390
1393
|
}
|
|
1391
|
-
return obj;
|
|
1392
1394
|
}
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1395
|
+
}
|
|
1396
|
+
exports.attrsElem = attrsElem;
|
|
1397
|
+
function attrs(attrsObj) {
|
|
1398
|
+
return (elem) => attrsElem(elem, attrsObj);
|
|
1399
|
+
}
|
|
1400
|
+
exports.attrs = attrs;
|
|
1401
|
+
/**
|
|
1402
|
+
* Sets an attribute of a DOM element to the given value. Removes the attribute when the value is
|
|
1403
|
+
* null or undefined. The `attr()` variant takes no `elem` argument, and `attrValue` may be an
|
|
1404
|
+
* observable or function.
|
|
1405
|
+
* @param {Element} elem: The element to update.
|
|
1406
|
+
* @param {String} attrName: The name of the attribute to bind, e.g. 'href'.
|
|
1407
|
+
* @param {String|null} attrValue: The string value or null to remove the attribute.
|
|
1408
|
+
*/
|
|
1409
|
+
function attrElem(elem, attrName, attrValue) {
|
|
1410
|
+
if (attrValue === null || attrValue === undefined) {
|
|
1411
|
+
elem.removeAttribute(attrName);
|
|
1398
1412
|
}
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
if (this._owned) {
|
|
1402
|
-
this._owned.dispose();
|
|
1403
|
-
this._owned = null;
|
|
1404
|
-
}
|
|
1413
|
+
else {
|
|
1414
|
+
elem.setAttribute(attrName, attrValue);
|
|
1405
1415
|
}
|
|
1406
|
-
/** Returns the held object, or null if the Holder is empty. */
|
|
1407
|
-
get() { return this._owned; }
|
|
1408
|
-
/** Returns whether the Holder is empty. */
|
|
1409
|
-
isEmpty() { return !this._owned; }
|
|
1410
|
-
/** When the holder is disposed, it disposes the held object if any. */
|
|
1411
|
-
dispose() { this.clear(); }
|
|
1412
1416
|
}
|
|
1413
|
-
exports.
|
|
1417
|
+
exports.attrElem = attrElem;
|
|
1418
|
+
function attr(attrName, attrValueObs) {
|
|
1419
|
+
return (elem) => binding_1.subscribeElem(elem, attrValueObs, (val) => attrElem(elem, attrName, val));
|
|
1420
|
+
}
|
|
1421
|
+
exports.attr = attr;
|
|
1414
1422
|
/**
|
|
1415
|
-
* Sets
|
|
1423
|
+
* Sets or removes a boolean attribute of a DOM element. According to the spec, empty string is a
|
|
1424
|
+
* valid true value for the attribute, and the false value is indicated by the attribute's absence.
|
|
1425
|
+
* The `boolAttr()` variant takes no `elem`, and `boolValue` may be an observable or function.
|
|
1426
|
+
* @param {Element} elem: The element to update.
|
|
1427
|
+
* @param {String} attrName: The name of the attribute to bind, e.g. 'checked'.
|
|
1428
|
+
* @param {Boolean} boolValue: Boolean value whether to set or unset the attribute.
|
|
1416
1429
|
*/
|
|
1417
|
-
function
|
|
1418
|
-
|
|
1419
|
-
owner.autoDispose(obj);
|
|
1420
|
-
}
|
|
1421
|
-
return obj;
|
|
1430
|
+
function boolAttrElem(elem, attrName, boolValue) {
|
|
1431
|
+
attrElem(elem, attrName, boolValue ? '' : null);
|
|
1422
1432
|
}
|
|
1423
|
-
exports.
|
|
1433
|
+
exports.boolAttrElem = boolAttrElem;
|
|
1434
|
+
function boolAttr(attrName, boolValueObs) {
|
|
1435
|
+
return (elem) => binding_1.subscribeElem(elem, boolValueObs, (val) => boolAttrElem(elem, attrName, val));
|
|
1436
|
+
}
|
|
1437
|
+
exports.boolAttr = boolAttr;
|
|
1424
1438
|
/**
|
|
1425
|
-
*
|
|
1439
|
+
* Adds a text node to the element. The `text()` variant takes no `elem`, and `value` may be an
|
|
1440
|
+
* observable or function.
|
|
1441
|
+
* @param {Element} elem: The element to update.
|
|
1442
|
+
* @param {String} value: The text value to add.
|
|
1426
1443
|
*/
|
|
1427
|
-
function
|
|
1428
|
-
|
|
1444
|
+
function textElem(elem, value) {
|
|
1445
|
+
elem.appendChild(browserGlobals_1.G.document.createTextNode(value));
|
|
1446
|
+
}
|
|
1447
|
+
exports.textElem = textElem;
|
|
1448
|
+
function text(valueObs) {
|
|
1449
|
+
return (elem) => {
|
|
1450
|
+
const textNode = browserGlobals_1.G.document.createTextNode('');
|
|
1451
|
+
binding_1.subscribeElem(elem, valueObs, (val) => { textNode.nodeValue = val; });
|
|
1452
|
+
elem.appendChild(textNode);
|
|
1453
|
+
};
|
|
1454
|
+
}
|
|
1455
|
+
exports.text = text;
|
|
1456
|
+
/**
|
|
1457
|
+
* Sets a style property of a DOM element to the given value. The `style()` variant takes no
|
|
1458
|
+
* `elem`, and `value` may be an observable or function.
|
|
1459
|
+
* @param {Element} elem: The element to update.
|
|
1460
|
+
* @param {String} property: The name of the style property to update, e.g. 'fontWeight'.
|
|
1461
|
+
* @param {String} value: The value for the property.
|
|
1462
|
+
*/
|
|
1463
|
+
function styleElem(elem, property, value) {
|
|
1464
|
+
elem.style[property] = value;
|
|
1465
|
+
}
|
|
1466
|
+
exports.styleElem = styleElem;
|
|
1467
|
+
function style(property, valueObs) {
|
|
1468
|
+
return (elem) => binding_1.subscribeElem(elem, valueObs, (val) => styleElem(elem, property, val));
|
|
1469
|
+
}
|
|
1470
|
+
exports.style = style;
|
|
1471
|
+
/**
|
|
1472
|
+
* Sets the property of a DOM element to the given value.
|
|
1473
|
+
* The `prop()` variant takes no `elem`, and `value` may be an observable or function.
|
|
1474
|
+
* @param {Element} elem: The element to update.
|
|
1475
|
+
* @param {String} property: The name of the property to update, e.g. 'disabled'.
|
|
1476
|
+
* @param {Object} value: The value for the property.
|
|
1477
|
+
*/
|
|
1478
|
+
function propElem(elem, property, value) {
|
|
1479
|
+
elem[property] = value;
|
|
1480
|
+
}
|
|
1481
|
+
exports.propElem = propElem;
|
|
1482
|
+
function prop(property, valueObs) {
|
|
1483
|
+
return (elem) => binding_1.subscribeElem(elem, valueObs, (val) => propElem(elem, property, val));
|
|
1484
|
+
}
|
|
1485
|
+
exports.prop = prop;
|
|
1486
|
+
/**
|
|
1487
|
+
* Shows or hides the element depending on a boolean value. Note that the element must be visible
|
|
1488
|
+
* initially (i.e. unsetting style.display should show it).
|
|
1489
|
+
* The `show()` variant takes no `elem`, and `boolValue` may be an observable or function.
|
|
1490
|
+
* @param {Element} elem: The element to update.
|
|
1491
|
+
* @param {Boolean} boolValue: True to show the element, false to hide it.
|
|
1492
|
+
*/
|
|
1493
|
+
function showElem(elem, boolValue) {
|
|
1494
|
+
elem.style.display = boolValue ? '' : 'none';
|
|
1495
|
+
}
|
|
1496
|
+
exports.showElem = showElem;
|
|
1497
|
+
function show(boolValueObs) {
|
|
1498
|
+
return (elem) => binding_1.subscribeElem(elem, boolValueObs, (val) => showElem(elem, val));
|
|
1499
|
+
}
|
|
1500
|
+
exports.show = show;
|
|
1501
|
+
/**
|
|
1502
|
+
* The opposite of show, hiding the element when boolValue is true.
|
|
1503
|
+
* The `hide()` variant takes no `elem`, and `boolValue` may be an observable or function.
|
|
1504
|
+
* @param {Element} elem: The element to update.
|
|
1505
|
+
* @param {Boolean} boolValue: True to hide the element, false to show it.
|
|
1506
|
+
*/
|
|
1507
|
+
function hideElem(elem, boolValue) {
|
|
1508
|
+
elem.style.display = boolValue ? 'none' : '';
|
|
1509
|
+
}
|
|
1510
|
+
exports.hideElem = hideElem;
|
|
1511
|
+
function hide(boolValueObs) {
|
|
1512
|
+
return (elem) => binding_1.subscribeElem(elem, boolValueObs, (val) => hideElem(elem, val));
|
|
1513
|
+
}
|
|
1514
|
+
exports.hide = hide;
|
|
1515
|
+
/**
|
|
1516
|
+
* Sets or toggles the given css class className.
|
|
1517
|
+
*/
|
|
1518
|
+
function clsElem(elem, className, boolValue = true) {
|
|
1519
|
+
elem.classList.toggle(className, Boolean(boolValue));
|
|
1520
|
+
}
|
|
1521
|
+
exports.clsElem = clsElem;
|
|
1522
|
+
function cls(className, boolValue) {
|
|
1523
|
+
if (typeof className !== 'string') {
|
|
1524
|
+
return _clsDynamicPrefix('', className);
|
|
1525
|
+
}
|
|
1526
|
+
else if (!boolValue || typeof boolValue === 'boolean') {
|
|
1527
|
+
return (elem) => clsElem(elem, className, boolValue);
|
|
1528
|
+
}
|
|
1529
|
+
else {
|
|
1530
|
+
return (elem) => binding_1.subscribeElem(elem, boolValue, (val) => clsElem(elem, className, val));
|
|
1531
|
+
}
|
|
1532
|
+
}
|
|
1533
|
+
exports.cls = cls;
|
|
1534
|
+
function clsPrefix(prefix, className, boolValue) {
|
|
1535
|
+
if (typeof className !== 'string') {
|
|
1536
|
+
return _clsDynamicPrefix(prefix, className);
|
|
1537
|
+
}
|
|
1538
|
+
else {
|
|
1539
|
+
return cls(prefix + className, boolValue);
|
|
1540
|
+
}
|
|
1541
|
+
}
|
|
1542
|
+
exports.clsPrefix = clsPrefix;
|
|
1543
|
+
function _clsDynamicPrefix(prefix, className) {
|
|
1544
|
+
return (elem) => {
|
|
1545
|
+
let prevClass = null;
|
|
1546
|
+
binding_1.subscribeElem(elem, className, (name) => {
|
|
1547
|
+
if (prevClass) {
|
|
1548
|
+
elem.classList.remove(prevClass);
|
|
1549
|
+
}
|
|
1550
|
+
prevClass = name ? prefix + name : null;
|
|
1551
|
+
if (prevClass) {
|
|
1552
|
+
elem.classList.add(prevClass);
|
|
1553
|
+
}
|
|
1554
|
+
});
|
|
1555
|
+
};
|
|
1429
1556
|
}
|
|
1430
1557
|
/**
|
|
1431
|
-
*
|
|
1432
|
-
*
|
|
1558
|
+
* Associate arbitrary data with a DOM element. The `data()` variant takes no `elem`, and `value`
|
|
1559
|
+
* may be an observable or function.
|
|
1560
|
+
* @param {Element} elem: The element with which to associate data.
|
|
1561
|
+
* @param {String} key: Key to identify this piece of data among others attached to elem.
|
|
1562
|
+
* @param {Object} value: Arbitrary value to associate with elem.
|
|
1433
1563
|
*/
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
this._insertBefore(this._next, lis);
|
|
1564
|
+
function dataElem(elem, key, value) {
|
|
1565
|
+
const obj = _dataMap.get(elem);
|
|
1566
|
+
if (obj) {
|
|
1567
|
+
obj[key] = value;
|
|
1439
1568
|
}
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
*/
|
|
1444
|
-
callAndDispose(owner) {
|
|
1445
|
-
try {
|
|
1446
|
-
DisposeListener.callAll(this._next, this, owner);
|
|
1447
|
-
}
|
|
1448
|
-
finally {
|
|
1449
|
-
this._disposeList();
|
|
1450
|
-
}
|
|
1569
|
+
else {
|
|
1570
|
+
domDispose_1.onDisposeElem(elem, () => _dataMap.delete(elem));
|
|
1571
|
+
_dataMap.set(elem, { [key]: value });
|
|
1451
1572
|
}
|
|
1452
1573
|
}
|
|
1574
|
+
exports.dataElem = dataElem;
|
|
1575
|
+
function data(key, valueObs) {
|
|
1576
|
+
return (elem) => binding_1.subscribeElem(elem, valueObs, (val) => dataElem(elem, key, val));
|
|
1577
|
+
}
|
|
1578
|
+
exports.data = data;
|
|
1579
|
+
function getData(elem, key) {
|
|
1580
|
+
const obj = _dataMap.get(elem);
|
|
1581
|
+
return obj && obj[key];
|
|
1582
|
+
}
|
|
1583
|
+
exports.getData = getData;
|
|
1453
1584
|
/**
|
|
1454
|
-
*
|
|
1455
|
-
* reports and swallows erros when it calls the callbacks in the list.
|
|
1585
|
+
* See documentation for TestId above.
|
|
1456
1586
|
*/
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
super();
|
|
1460
|
-
this.callback = callback;
|
|
1461
|
-
this.context = context;
|
|
1462
|
-
}
|
|
1463
|
-
static callAll(begin, end, owner) {
|
|
1464
|
-
while (begin !== end) {
|
|
1465
|
-
const lis = begin;
|
|
1466
|
-
try {
|
|
1467
|
-
lis.callback.call(lis.context);
|
|
1468
|
-
}
|
|
1469
|
-
catch (e) {
|
|
1470
|
-
// tslint:disable-next-line:no-console
|
|
1471
|
-
console.error("While disposing %s, error disposing %s: %s", _describe(owner), _describe(this), e);
|
|
1472
|
-
}
|
|
1473
|
-
begin = lis._next;
|
|
1474
|
-
}
|
|
1475
|
-
}
|
|
1587
|
+
function makeTestId(prefix) {
|
|
1588
|
+
return clsPrefix.bind(null, prefix);
|
|
1476
1589
|
}
|
|
1477
|
-
|
|
1478
|
-
},{"./emit":15}],13:[function(require,module,exports){
|
|
1479
|
-
"use strict";
|
|
1590
|
+
exports.makeTestId = makeTestId;
|
|
1480
1591
|
/**
|
|
1481
|
-
*
|
|
1482
|
-
*
|
|
1483
|
-
* E.g.
|
|
1484
|
-
* import {dom} from 'grainjs';
|
|
1485
|
-
* dom('a#link.c1.c2', {'href': url}, 'Hello ', dom('span', 'world'));
|
|
1486
|
-
* creates Node <a id="link" class="c1 c2" href={{url}}Hello <span>world</span></a>.
|
|
1487
|
-
*
|
|
1488
|
-
* dom.frag(dom('span', 'Hello'), ['blah', dom('div', 'world')])
|
|
1489
|
-
* creates document fragment with <span>Hello</span>blah<div>world</div>.
|
|
1490
|
-
*
|
|
1491
|
-
* DOM can also be created and modified inline during creation:
|
|
1492
|
-
* dom('a#id.c1',
|
|
1493
|
-
* dom.cls('c2'), dom.attr('href', url),
|
|
1494
|
-
* dom.text('Hello '), dom('span', dom.text('world')))
|
|
1495
|
-
* creates Node <a id="link" class="c1 c2" href={{url}}Hello <span>world</span></a>,
|
|
1496
|
-
* identical to the first example above.
|
|
1592
|
+
* See documentation for TestId above.
|
|
1497
1593
|
*/
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
}
|
|
1501
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
1502
|
-
// We keep various dom-related functions organized in private modules, but they are exposed here.
|
|
1503
|
-
var _domImpl_1 = require("./_domImpl");
|
|
1504
|
-
exports.svg = _domImpl_1.svg;
|
|
1505
|
-
exports.update = _domImpl_1.update;
|
|
1506
|
-
exports.frag = _domImpl_1.frag;
|
|
1507
|
-
exports.find = _domImpl_1.find;
|
|
1508
|
-
exports.findAll = _domImpl_1.findAll;
|
|
1509
|
-
__export(require("./_domComponent"));
|
|
1510
|
-
__export(require("./_domDispose"));
|
|
1511
|
-
__export(require("./_domForEach"));
|
|
1512
|
-
__export(require("./_domMethods"));
|
|
1513
|
-
__export(require("./domevent"));
|
|
1514
|
-
const _domComponent = require("./_domComponent");
|
|
1515
|
-
const _domDispose = require("./_domDispose");
|
|
1516
|
-
const _domForEach = require("./_domForEach");
|
|
1517
|
-
const _domImpl = require("./_domImpl");
|
|
1518
|
-
const _domMethods = require("./_domMethods");
|
|
1519
|
-
const domevent = require("./domevent");
|
|
1520
|
-
// We just want to re-export _domImpl.dom, but to allow adding methods to it in a typesafe way,
|
|
1521
|
-
// TypeScript wants us to declare a real function in the same file.
|
|
1522
|
-
function dom(tagString, ...args) {
|
|
1523
|
-
return _domImpl.dom(tagString, ...args);
|
|
1524
|
-
}
|
|
1525
|
-
exports.dom = dom;
|
|
1526
|
-
// Additionally export all methods as properties of dom() function.
|
|
1527
|
-
(function (dom) {
|
|
1528
|
-
dom.svg = _domImpl.svg;
|
|
1529
|
-
dom.frag = _domImpl.frag;
|
|
1530
|
-
dom.update = _domImpl.update;
|
|
1531
|
-
dom.find = _domImpl.find;
|
|
1532
|
-
dom.findAll = _domImpl.findAll;
|
|
1533
|
-
dom.domDispose = _domDispose.domDispose;
|
|
1534
|
-
dom.onDisposeElem = _domDispose.onDisposeElem;
|
|
1535
|
-
dom.onDispose = _domDispose.onDispose;
|
|
1536
|
-
dom.autoDisposeElem = _domDispose.autoDisposeElem;
|
|
1537
|
-
dom.autoDispose = _domDispose.autoDispose;
|
|
1538
|
-
dom.attrsElem = _domMethods.attrsElem;
|
|
1539
|
-
dom.attrs = _domMethods.attrs;
|
|
1540
|
-
dom.attrElem = _domMethods.attrElem;
|
|
1541
|
-
dom.attr = _domMethods.attr;
|
|
1542
|
-
dom.boolAttrElem = _domMethods.boolAttrElem;
|
|
1543
|
-
dom.boolAttr = _domMethods.boolAttr;
|
|
1544
|
-
dom.textElem = _domMethods.textElem;
|
|
1545
|
-
dom.text = _domMethods.text;
|
|
1546
|
-
dom.styleElem = _domMethods.styleElem;
|
|
1547
|
-
dom.style = _domMethods.style;
|
|
1548
|
-
dom.propElem = _domMethods.propElem;
|
|
1549
|
-
dom.prop = _domMethods.prop;
|
|
1550
|
-
dom.showElem = _domMethods.showElem;
|
|
1551
|
-
dom.show = _domMethods.show;
|
|
1552
|
-
dom.hideElem = _domMethods.hideElem;
|
|
1553
|
-
dom.hide = _domMethods.hide;
|
|
1554
|
-
dom.clsElem = _domMethods.clsElem;
|
|
1555
|
-
dom.cls = _domMethods.cls;
|
|
1556
|
-
dom.clsPrefix = _domMethods.clsPrefix;
|
|
1557
|
-
dom.dataElem = _domMethods.dataElem;
|
|
1558
|
-
dom.data = _domMethods.data;
|
|
1559
|
-
dom.getData = _domMethods.getData;
|
|
1560
|
-
dom.replaceContent = _domMethods.replaceContent;
|
|
1561
|
-
dom.domComputed = _domMethods.domComputed;
|
|
1562
|
-
dom.maybe = _domMethods.maybe;
|
|
1563
|
-
dom.forEach = _domForEach.forEach;
|
|
1564
|
-
dom.Component = _domComponent.Component;
|
|
1565
|
-
dom.create = _domComponent.create;
|
|
1566
|
-
dom.createInit = _domComponent.createInit;
|
|
1567
|
-
dom.onElem = domevent.onElem;
|
|
1568
|
-
dom.on = domevent.on;
|
|
1569
|
-
dom.onMatchElem = domevent.onMatchElem;
|
|
1570
|
-
dom.onMatch = domevent.onMatch;
|
|
1571
|
-
dom.onKeyPressElem = domevent.onKeyPressElem;
|
|
1572
|
-
dom.onKeyPress = domevent.onKeyPress;
|
|
1573
|
-
})(dom = exports.dom || (exports.dom = {}));
|
|
1594
|
+
const noTestId = (name) => null;
|
|
1595
|
+
exports.noTestId = noTestId;
|
|
1574
1596
|
|
|
1575
|
-
},{"./
|
|
1597
|
+
},{"./binding":4,"./browserGlobals":5,"./domDispose":11}],15:[function(require,module,exports){
|
|
1576
1598
|
"use strict";
|
|
1577
1599
|
/**
|
|
1578
1600
|
* domevent provides a way to listen to DOM events, similar to JQuery's `on()` function. Its
|
|
@@ -1615,6 +1637,7 @@ exports.dom = dom;
|
|
|
1615
1637
|
* let lis = domevent.onElem(elem, 'mouseup', e => { lis.dispose(); other_work(); });
|
|
1616
1638
|
*/
|
|
1617
1639
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
1640
|
+
exports.onKeyDown = exports.onKeyPress = exports.onKeyElem = exports.onMatch = exports.onMatchElem = exports.on = exports.onElem = void 0;
|
|
1618
1641
|
function _findMatch(inner, outer, selector) {
|
|
1619
1642
|
for (let el = inner; el && el !== outer; el = el.parentElement) {
|
|
1620
1643
|
if (el.matches(selector)) {
|
|
@@ -1693,35 +1716,55 @@ function onMatch(selector, eventType, callback, { useCapture = false } = {}) {
|
|
|
1693
1716
|
}
|
|
1694
1717
|
exports.onMatch = onMatch;
|
|
1695
1718
|
/**
|
|
1696
|
-
* Listen to key
|
|
1697
|
-
* `elem` argument, and may be used as an argument to dom().
|
|
1698
|
-
*
|
|
1719
|
+
* Listen to key events (typically 'keydown' or 'keypress'), with specified per-key callbacks.
|
|
1699
1720
|
* Key names are listed at https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key/Key_Values
|
|
1700
1721
|
*
|
|
1722
|
+
* Methods onKeyPress() and onKeyDown() are intended to be used as arguments to dom().
|
|
1723
|
+
*
|
|
1724
|
+
* By default, handled events are stopped from bubbling with stopPropagation() and
|
|
1725
|
+
* preventDefault(). If, however, you register a key with a "$" suffix (i.e. "Enter$" instead of
|
|
1726
|
+
* "Enter"), then the event is allowed to bubble normally.
|
|
1727
|
+
*
|
|
1728
|
+
* When this handler is set on an element, we automatically ensure that tabindex attribute is set,
|
|
1729
|
+
* to allow this element to receive keyboard events.
|
|
1730
|
+
*
|
|
1701
1731
|
* For example:
|
|
1702
1732
|
*
|
|
1703
1733
|
* dom('input', ...
|
|
1704
|
-
* dom.
|
|
1734
|
+
* dom.onKeyDown({
|
|
1705
1735
|
* Enter: (e, elem) => console.log("Enter pressed"),
|
|
1706
1736
|
* Escape: (e, elem) => console.log("Escape pressed"),
|
|
1737
|
+
* Delete$: (e, elem) => console.log("Delete pressed, will bubble"),
|
|
1707
1738
|
* })
|
|
1708
1739
|
* )
|
|
1709
1740
|
*/
|
|
1710
|
-
function
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1741
|
+
function onKeyElem(elem, evType, keyHandlers) {
|
|
1742
|
+
if (!(elem.tabIndex >= 0)) { // If tabIndex property is undefined or -1,
|
|
1743
|
+
elem.setAttribute('tabindex', '-1'); // Set tabIndex attribute to make the element focusable.
|
|
1744
|
+
}
|
|
1745
|
+
return onElem(elem, evType, (ev, _elem) => {
|
|
1746
|
+
const plainHandler = keyHandlers[ev.key];
|
|
1747
|
+
const handler = plainHandler || keyHandlers[ev.key + '$'];
|
|
1748
|
+
if (handler) {
|
|
1749
|
+
if (plainHandler) {
|
|
1750
|
+
ev.stopPropagation();
|
|
1751
|
+
ev.preventDefault();
|
|
1752
|
+
}
|
|
1753
|
+
handler(ev, _elem);
|
|
1715
1754
|
}
|
|
1716
1755
|
});
|
|
1717
1756
|
}
|
|
1718
|
-
exports.
|
|
1719
|
-
function onKeyPress(
|
|
1720
|
-
return (elem) => {
|
|
1757
|
+
exports.onKeyElem = onKeyElem;
|
|
1758
|
+
function onKeyPress(keyHandlers) {
|
|
1759
|
+
return (elem) => { onKeyElem(elem, 'keypress', keyHandlers); };
|
|
1721
1760
|
}
|
|
1722
1761
|
exports.onKeyPress = onKeyPress;
|
|
1762
|
+
function onKeyDown(keyHandlers) {
|
|
1763
|
+
return (elem) => { onKeyElem(elem, 'keydown', keyHandlers); };
|
|
1764
|
+
}
|
|
1765
|
+
exports.onKeyDown = onKeyDown;
|
|
1723
1766
|
|
|
1724
|
-
},{}],
|
|
1767
|
+
},{}],16:[function(require,module,exports){
|
|
1725
1768
|
"use strict";
|
|
1726
1769
|
/**
|
|
1727
1770
|
* emit.js implements an Emitter class which emits events to a list of listeners. Listeners are
|
|
@@ -1746,6 +1789,7 @@ exports.onKeyPress = onKeyPress;
|
|
|
1746
1789
|
* emitter.emit("hello", "world");
|
|
1747
1790
|
*/
|
|
1748
1791
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
1792
|
+
exports.Listener = exports.Emitter = exports.LLink = void 0;
|
|
1749
1793
|
// Note about a possible alternative implementation.
|
|
1750
1794
|
//
|
|
1751
1795
|
// We could implement the same interface using an array of listeners. Certain issues apply, in
|
|
@@ -1828,7 +1872,8 @@ class Emitter extends LLink {
|
|
|
1828
1872
|
* Sets the single callback that would get called when a listener is added or removed.
|
|
1829
1873
|
* @param {Function} changeCB(hasListeners): Function to call after a listener is added or
|
|
1830
1874
|
* removed. It's called with a boolean indicating whether this Emitter has any listeners.
|
|
1831
|
-
* Pass in `null` to unset the callback.
|
|
1875
|
+
* Pass in `null` to unset the callback. Note that it can be called multiple times in a row
|
|
1876
|
+
* with hasListeners `true`.
|
|
1832
1877
|
*/
|
|
1833
1878
|
setChangeCB(changeCB, optContext) {
|
|
1834
1879
|
this._changeCB = changeCB || _noop;
|
|
@@ -1887,7 +1932,7 @@ class Listener extends LLink {
|
|
|
1887
1932
|
}
|
|
1888
1933
|
exports.Listener = Listener;
|
|
1889
1934
|
|
|
1890
|
-
},{}],
|
|
1935
|
+
},{}],17:[function(require,module,exports){
|
|
1891
1936
|
"use strict";
|
|
1892
1937
|
/**
|
|
1893
1938
|
* Grain.js observables and computeds are similar to (and mostly inspired by) those in
|
|
@@ -1910,26 +1955,56 @@ exports.Listener = Listener;
|
|
|
1910
1955
|
* knockout as a dependency of grainjs.
|
|
1911
1956
|
*
|
|
1912
1957
|
* In both cases, calling fromKo/toKo twice on the same observable will return the same wrapper,
|
|
1913
|
-
* and subscriptions and disposal are appropriately set up to make usage seamless.
|
|
1958
|
+
* and subscriptions and disposal are appropriately set up to make usage seamless. In particular,
|
|
1959
|
+
* the returned wrapper should not be disposed; it's tied to the lifetime of the wrapped object.
|
|
1914
1960
|
*/
|
|
1915
1961
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
1962
|
+
exports.setupKoDisposal = exports.toKo = exports.KoWrapObs = exports.fromKo = void 0;
|
|
1963
|
+
const domDispose_1 = require("./domDispose");
|
|
1916
1964
|
const observable_1 = require("./observable");
|
|
1917
1965
|
const fromKoWrappers = new WeakMap();
|
|
1918
1966
|
const toKoWrappers = new WeakMap();
|
|
1919
1967
|
/**
|
|
1920
1968
|
* Returns a Grain.js observable which mirrors a Knockout observable.
|
|
1969
|
+
*
|
|
1970
|
+
* Do not dispose this wrapper, as it is shared by all code using koObs, and its lifetime is tied
|
|
1971
|
+
* to the lifetime of koObs. If unused, it consumes minimal resources, and should get garbage
|
|
1972
|
+
* collected along with koObs.
|
|
1921
1973
|
*/
|
|
1922
|
-
function fromKo(
|
|
1923
|
-
|
|
1924
|
-
if (prevObs) {
|
|
1925
|
-
return prevObs;
|
|
1926
|
-
}
|
|
1927
|
-
const newObs = observable_1.observable(koObservable.peek());
|
|
1928
|
-
fromKoWrappers.set(koObservable, newObs);
|
|
1929
|
-
koObservable.subscribe((val) => newObs.set(val));
|
|
1930
|
-
return newObs;
|
|
1974
|
+
function fromKo(koObs) {
|
|
1975
|
+
return fromKoWrappers.get(koObs) || fromKoWrappers.set(koObs, new KoWrapObs(koObs)).get(koObs);
|
|
1931
1976
|
}
|
|
1932
1977
|
exports.fromKo = fromKo;
|
|
1978
|
+
/**
|
|
1979
|
+
* An Observable that wraps a Knockout observable, created via fromKo(). It keeps minimal overhead
|
|
1980
|
+
* when unused by only subscribing to the wrapped observable while it itself has subscriptions.
|
|
1981
|
+
*
|
|
1982
|
+
* This way, when unused, the only reference is from the wrapper to the wrapped object. KoWrapObs
|
|
1983
|
+
* should not be disposed; its lifetime is tied to that of the wrapped object.
|
|
1984
|
+
*/
|
|
1985
|
+
class KoWrapObs extends observable_1.Observable {
|
|
1986
|
+
constructor(_koObs) {
|
|
1987
|
+
super(_koObs.peek());
|
|
1988
|
+
this._koObs = _koObs;
|
|
1989
|
+
this._koSub = null;
|
|
1990
|
+
this.setListenerChangeCB((hasListeners) => {
|
|
1991
|
+
if (!hasListeners) {
|
|
1992
|
+
this._koSub.dispose();
|
|
1993
|
+
this._koSub = null;
|
|
1994
|
+
}
|
|
1995
|
+
else if (!this._koSub) {
|
|
1996
|
+
// TODO this is a little hack, really, BaseObservable should expose a way to set the value
|
|
1997
|
+
// directly by derived classes, i.e. a protected setter.
|
|
1998
|
+
this._value = this._koObs.peek();
|
|
1999
|
+
this._koSub = this._koObs.subscribe((val) => this.setAndTrigger(val));
|
|
2000
|
+
}
|
|
2001
|
+
});
|
|
2002
|
+
}
|
|
2003
|
+
get() { return this._koObs.peek(); }
|
|
2004
|
+
set(value) { observable_1.bundleChanges(() => this._koObs(value)); }
|
|
2005
|
+
dispose() { throw new Error("KoWrapObs should not be disposed"); }
|
|
2006
|
+
}
|
|
2007
|
+
exports.KoWrapObs = KoWrapObs;
|
|
1933
2008
|
/**
|
|
1934
2009
|
* Returns a Knockout observable which mirrors a Grain.js observable.
|
|
1935
2010
|
*/
|
|
@@ -1944,8 +2019,61 @@ function toKo(knockout, grainObs) {
|
|
|
1944
2019
|
return newKoObs;
|
|
1945
2020
|
}
|
|
1946
2021
|
exports.toKo = toKo;
|
|
2022
|
+
// Marker for when knockout-disposal integration has already been setup.
|
|
2023
|
+
let koDisposalIsSetup = false;
|
|
2024
|
+
/**
|
|
2025
|
+
* Set up integration between grainjs and knockout disposal. Knockout does cleanup using
|
|
2026
|
+
* ko.removeNode / ko.cleanNode (it also takes care of JQuery cleanup if needed). GrainJS does
|
|
2027
|
+
* cleanup using dom.domDispose(). By default these don't know about each other.
|
|
2028
|
+
*
|
|
2029
|
+
* If you mix the two libraries, however, disposing an element may need to trigger disposers
|
|
2030
|
+
* registered by either library.
|
|
2031
|
+
*
|
|
2032
|
+
* This method ensures that this happens.
|
|
2033
|
+
*
|
|
2034
|
+
* Note: grainjs disposes text nodes too, but nothing relies on it. When disposal is triggered via
|
|
2035
|
+
* knockout, we are forced to rely on knockout's node traversal which ignores text nodes.
|
|
2036
|
+
*/
|
|
2037
|
+
function setupKoDisposal(ko) {
|
|
2038
|
+
// Ensure we don't do the setup more than once, or things will get called multiple times.
|
|
2039
|
+
if (koDisposalIsSetup) {
|
|
2040
|
+
return;
|
|
2041
|
+
}
|
|
2042
|
+
koDisposalIsSetup = true;
|
|
2043
|
+
const koDomNodeDisposal = ko.utils.domNodeDisposal;
|
|
2044
|
+
// Knockout by default has an external-data-cleanup func set to cleanup JQuery. Whatever it is
|
|
2045
|
+
// set to, we will continue calling it, and also will call grainjs domDisposeNode.
|
|
2046
|
+
const origKoCleanExternalData = koDomNodeDisposal.cleanExternalData;
|
|
2047
|
+
// The original function called by grainjs to clean nodes recursively. We'll override it.
|
|
2048
|
+
const origGrainDisposeRecursive = domDispose_1.domDisposeHooks.disposeRecursive;
|
|
2049
|
+
// New function called by knockout to do extra cleanup. Now calls grainjs single-node cleanup.
|
|
2050
|
+
// (In knockout, we can only override single-node cleanup.)
|
|
2051
|
+
function newKoCleanExternalData(node) {
|
|
2052
|
+
origKoCleanExternalData(node);
|
|
2053
|
+
domDispose_1.domDisposeHooks.disposeNode(node);
|
|
2054
|
+
}
|
|
2055
|
+
// Function called by grainjs to clean nodes recursively. We override the recursive cleanup
|
|
2056
|
+
// function to call the recursive knockout cleanup (letting knockout do the dom traversal it
|
|
2057
|
+
// normally does).
|
|
2058
|
+
function newGrainDisposeRecursive(node) {
|
|
2059
|
+
origGrainDisposeRecursive(node);
|
|
2060
|
+
// While doing knockout cleanup, do NOT have it call grainjs cleanup too, as that would cause
|
|
2061
|
+
// multiple unnecessary traversals of DOM.
|
|
2062
|
+
koDomNodeDisposal.cleanExternalData = origKoCleanExternalData;
|
|
2063
|
+
try {
|
|
2064
|
+
ko.cleanNode(node);
|
|
2065
|
+
}
|
|
2066
|
+
finally {
|
|
2067
|
+
koDomNodeDisposal.cleanExternalData = newKoCleanExternalData;
|
|
2068
|
+
}
|
|
2069
|
+
}
|
|
2070
|
+
// Use knockout and grainjs hooks to actually set the new cleanup functions.
|
|
2071
|
+
koDomNodeDisposal.cleanExternalData = newKoCleanExternalData;
|
|
2072
|
+
domDispose_1.domDisposeHooks.disposeRecursive = newGrainDisposeRecursive;
|
|
2073
|
+
}
|
|
2074
|
+
exports.setupKoDisposal = setupKoDisposal;
|
|
1947
2075
|
|
|
1948
|
-
},{"./observable":
|
|
2076
|
+
},{"./domDispose":11,"./observable":19}],18:[function(require,module,exports){
|
|
1949
2077
|
"use strict";
|
|
1950
2078
|
/**
|
|
1951
2079
|
* ObsArray extends a plain Observable to allow for more efficient observation of array changes.
|
|
@@ -1980,6 +2108,7 @@ exports.toKo = toKo;
|
|
|
1980
2108
|
* ownership of those disposables that are added to it as array elements.
|
|
1981
2109
|
*/
|
|
1982
2110
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
2111
|
+
exports.LiveIndex = exports.makeLiveIndex = exports.computedArray = exports.ComputedArray = exports.obsArray = exports.MutableObsArray = exports.ObsArray = void 0;
|
|
1983
2112
|
const dispose_1 = require("./dispose");
|
|
1984
2113
|
const observable_1 = require("./observable");
|
|
1985
2114
|
const subscribe_1 = require("./subscribe");
|
|
@@ -2243,7 +2372,7 @@ class LiveIndex extends observable_1.Observable {
|
|
|
2243
2372
|
}
|
|
2244
2373
|
exports.LiveIndex = LiveIndex;
|
|
2245
2374
|
|
|
2246
|
-
},{"./dispose":
|
|
2375
|
+
},{"./dispose":7,"./observable":19,"./subscribe":22}],19:[function(require,module,exports){
|
|
2247
2376
|
"use strict";
|
|
2248
2377
|
/**
|
|
2249
2378
|
* observable.js implements an observable value, which lets other code subscribe to changes.
|
|
@@ -2268,10 +2397,12 @@ exports.LiveIndex = LiveIndex;
|
|
|
2268
2397
|
* dependency is created, and which observables the dependency connects.
|
|
2269
2398
|
*/
|
|
2270
2399
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
2400
|
+
exports.obsHolder = exports.observable = exports.Observable = exports.BaseObservable = exports.bundleChanges = void 0;
|
|
2271
2401
|
const _computed_queue_1 = require("./_computed_queue");
|
|
2402
|
+
const dispose_1 = require("./dispose");
|
|
2272
2403
|
const emit_1 = require("./emit");
|
|
2273
2404
|
var _computed_queue_2 = require("./_computed_queue");
|
|
2274
|
-
exports
|
|
2405
|
+
Object.defineProperty(exports, "bundleChanges", { enumerable: true, get: function () { return _computed_queue_2.bundleChanges; } });
|
|
2275
2406
|
class BaseObservable {
|
|
2276
2407
|
/**
|
|
2277
2408
|
* Internal constructor for an Observable. You should use observable() function instead.
|
|
@@ -2326,7 +2457,8 @@ class BaseObservable {
|
|
|
2326
2457
|
* previously-set such callback.
|
|
2327
2458
|
* @param {Function} changeCB(hasListeners): Function to call after a listener is added or
|
|
2328
2459
|
* removed. It's called with a boolean indicating whether this observable has any listeners.
|
|
2329
|
-
* Pass in `null` to unset the callback.
|
|
2460
|
+
* Pass in `null` to unset the callback. Note that it can be called multiple times in a row
|
|
2461
|
+
* with hasListeners `true`.
|
|
2330
2462
|
*/
|
|
2331
2463
|
setListenerChangeCB(changeCB, optContext) {
|
|
2332
2464
|
this._onChange.setChangeCB(changeCB, optContext);
|
|
@@ -2377,6 +2509,12 @@ class Observable extends BaseObservable {
|
|
|
2377
2509
|
obs._owned = value;
|
|
2378
2510
|
return obs;
|
|
2379
2511
|
}
|
|
2512
|
+
/**
|
|
2513
|
+
* Creates a new Observable with the given initial value, and owned by owner.
|
|
2514
|
+
*/
|
|
2515
|
+
static create(owner, value) {
|
|
2516
|
+
return dispose_1.setDisposeOwner(owner, new Observable(value));
|
|
2517
|
+
}
|
|
2380
2518
|
/**
|
|
2381
2519
|
* The use an observable for a disposable object, use it a DisposableOwner:
|
|
2382
2520
|
*
|
|
@@ -2426,7 +2564,124 @@ function obsHolder(value) {
|
|
|
2426
2564
|
}
|
|
2427
2565
|
exports.obsHolder = obsHolder;
|
|
2428
2566
|
|
|
2429
|
-
},{"./_computed_queue":3,"./emit":
|
|
2567
|
+
},{"./_computed_queue":3,"./dispose":7,"./emit":16}],20:[function(require,module,exports){
|
|
2568
|
+
"use strict";
|
|
2569
|
+
/**
|
|
2570
|
+
* pureComputed.js implements a variant of computed() suitable for use with a pure read function
|
|
2571
|
+
* (free of side-effects). A pureComputed is only subscribed to its dependencies when something is
|
|
2572
|
+
* subscribed to it. At other times, it is not subscribed to anything, and calls to `get()` will
|
|
2573
|
+
* recompute its value each time by calling its read() function.
|
|
2574
|
+
*
|
|
2575
|
+
* Its syntax and usage are otherwise exactly as for a computed.
|
|
2576
|
+
*
|
|
2577
|
+
* In addition to being cheaper when unused, a pureComputed() also avoids leaking memory when
|
|
2578
|
+
* unused (since it's not registered with dependencies), so it is not necessary to dispose it.
|
|
2579
|
+
*/
|
|
2580
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
2581
|
+
exports.pureComputed = exports.PureComputed = void 0;
|
|
2582
|
+
const observable_1 = require("./observable");
|
|
2583
|
+
const subscribe_1 = require("./subscribe");
|
|
2584
|
+
function _noWrite() {
|
|
2585
|
+
throw new Error("Can't write to non-writable pureComputed");
|
|
2586
|
+
}
|
|
2587
|
+
function _useFunc(obs) {
|
|
2588
|
+
return ('get' in obs) ? obs.get() : obs.peek();
|
|
2589
|
+
}
|
|
2590
|
+
// Constant empty array, which we use to avoid allocating new read-only empty arrays.
|
|
2591
|
+
const emptyArray = [];
|
|
2592
|
+
class PureComputed extends observable_1.Observable {
|
|
2593
|
+
/**
|
|
2594
|
+
* Internal constructor for a PureComputed. You should use pureComputed() function instead.
|
|
2595
|
+
*/
|
|
2596
|
+
constructor(callback, dependencies) {
|
|
2597
|
+
// At initialization we force an undefined value even though it's not of type T: it's not
|
|
2598
|
+
// actually used as get() is overridden.
|
|
2599
|
+
super(undefined);
|
|
2600
|
+
this._callback = callback;
|
|
2601
|
+
this._write = _noWrite;
|
|
2602
|
+
this._dependencies = dependencies.length > 0 ? dependencies : emptyArray;
|
|
2603
|
+
this._sub = null;
|
|
2604
|
+
this._inCall = false;
|
|
2605
|
+
this.setListenerChangeCB(this._onListenerChange, this);
|
|
2606
|
+
}
|
|
2607
|
+
_getDepItem() {
|
|
2608
|
+
this._activate();
|
|
2609
|
+
return this._sub._getDepItem();
|
|
2610
|
+
}
|
|
2611
|
+
get() {
|
|
2612
|
+
if (!this._sub && !this._inCall) {
|
|
2613
|
+
// _inCall member prevents infinite recursion.
|
|
2614
|
+
this._inCall = true;
|
|
2615
|
+
try {
|
|
2616
|
+
const readArgs = [_useFunc];
|
|
2617
|
+
// Note that this attempts to optimize for speed.
|
|
2618
|
+
for (let i = 0, len = this._dependencies.length; i < len; i++) {
|
|
2619
|
+
readArgs[i + 1] = this._dependencies[i].get();
|
|
2620
|
+
}
|
|
2621
|
+
super.set(this._callback.apply(undefined, readArgs));
|
|
2622
|
+
}
|
|
2623
|
+
finally {
|
|
2624
|
+
this._inCall = false;
|
|
2625
|
+
}
|
|
2626
|
+
}
|
|
2627
|
+
return super.get();
|
|
2628
|
+
}
|
|
2629
|
+
/**
|
|
2630
|
+
* "Sets" the value of the pure computed by calling the write() callback if one was provided in
|
|
2631
|
+
* the constructor. Throws an error if there was no such callback (not a "writable" computed).
|
|
2632
|
+
* @param {Object} value: The value to pass to the write() callback.
|
|
2633
|
+
*/
|
|
2634
|
+
set(value) { this._write(value); }
|
|
2635
|
+
/**
|
|
2636
|
+
* Set callback to call when this.set(value) is called, to make it a writable computed. If not
|
|
2637
|
+
* set, attempting to write to this computed will throw an exception.
|
|
2638
|
+
*/
|
|
2639
|
+
onWrite(writeFunc) {
|
|
2640
|
+
this._write = writeFunc;
|
|
2641
|
+
return this;
|
|
2642
|
+
}
|
|
2643
|
+
/**
|
|
2644
|
+
* Disposes the pureComputed, unsubscribing it from all observables it depends on.
|
|
2645
|
+
*/
|
|
2646
|
+
dispose() {
|
|
2647
|
+
if (this._sub) {
|
|
2648
|
+
this._sub.dispose();
|
|
2649
|
+
}
|
|
2650
|
+
// Truthy value for _sub prevents some errors after disposal, by avoiding activation or
|
|
2651
|
+
// _directRead calls.
|
|
2652
|
+
this._sub = true;
|
|
2653
|
+
super.dispose();
|
|
2654
|
+
}
|
|
2655
|
+
_activate() {
|
|
2656
|
+
if (!this._sub) {
|
|
2657
|
+
this._sub = new subscribe_1.Subscription(this._read.bind(this), this._dependencies);
|
|
2658
|
+
}
|
|
2659
|
+
}
|
|
2660
|
+
_onListenerChange(hasListeners) {
|
|
2661
|
+
if (hasListeners) {
|
|
2662
|
+
this._activate();
|
|
2663
|
+
}
|
|
2664
|
+
else if (this._sub) {
|
|
2665
|
+
this._sub.dispose();
|
|
2666
|
+
this._sub = null;
|
|
2667
|
+
}
|
|
2668
|
+
}
|
|
2669
|
+
_read(use, ...args) {
|
|
2670
|
+
super.set(this._callback(use, ...args));
|
|
2671
|
+
}
|
|
2672
|
+
}
|
|
2673
|
+
exports.PureComputed = PureComputed;
|
|
2674
|
+
/**
|
|
2675
|
+
* Creates and returns a new PureComputed. The interface is identical to that of a Computed.
|
|
2676
|
+
*/
|
|
2677
|
+
function pureComputed(...args) {
|
|
2678
|
+
const readCb = args.pop();
|
|
2679
|
+
// The cast helps ensure that Observable is compatible with ISubscribable abstraction that we use.
|
|
2680
|
+
return new PureComputed(readCb, args);
|
|
2681
|
+
}
|
|
2682
|
+
exports.pureComputed = pureComputed;
|
|
2683
|
+
|
|
2684
|
+
},{"./observable":19,"./subscribe":22}],21:[function(require,module,exports){
|
|
2430
2685
|
"use strict";
|
|
2431
2686
|
/**
|
|
2432
2687
|
* In-code styling for DOM components, inspired by Reacts Styled Components.
|
|
@@ -2484,11 +2739,25 @@ exports.obsHolder = obsHolder;
|
|
|
2484
2739
|
* myButton(myButton.cls('-small'), 'Test')
|
|
2485
2740
|
*
|
|
2486
2741
|
* creates a button with both the myButton style above, and the style specified under "&-small".
|
|
2742
|
+
*
|
|
2743
|
+
* Animations with @keyframes may be created with a unique name by using the keyframes() helper:
|
|
2744
|
+
*
|
|
2745
|
+
* const rotate360 = keyframes(`
|
|
2746
|
+
* from { transform: rotate(0deg); }
|
|
2747
|
+
* to { transform: rotate(360deg); }
|
|
2748
|
+
* `);
|
|
2749
|
+
*
|
|
2750
|
+
* const Rotate = styled('div', `
|
|
2751
|
+
* display: inline-block;
|
|
2752
|
+
* animation: ${rotate360} 2s linear infinite;
|
|
2753
|
+
* `);
|
|
2487
2754
|
*/
|
|
2488
2755
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
2756
|
+
exports.keyframes = exports.styled = void 0;
|
|
2489
2757
|
// Use the browser globals in a way that allows replacing them with mocks in tests.
|
|
2490
2758
|
const browserGlobals_1 = require("./browserGlobals");
|
|
2491
|
-
const
|
|
2759
|
+
const domImpl_1 = require("./domImpl");
|
|
2760
|
+
const domMethods_1 = require("./domMethods");
|
|
2492
2761
|
function styled(creator, styles) {
|
|
2493
2762
|
// Note that we intentionally minimize the work done when styled() is called; it's better to do
|
|
2494
2763
|
// any needed work on first use. That's when we will actually build the css rules.
|
|
@@ -2496,25 +2765,37 @@ function styled(creator, styles) {
|
|
|
2496
2765
|
// Creator function reflects the input, with only the addition of style.use() at the end. Note
|
|
2497
2766
|
// that it needs to be at the end because creator() might take special initial arguments.
|
|
2498
2767
|
const newCreator = (typeof creator === 'string') ?
|
|
2499
|
-
(...args) =>
|
|
2500
|
-
(...args) => creator(...args
|
|
2768
|
+
(...args) => style.addToElem(domImpl_1.dom(creator, ...args)) :
|
|
2769
|
+
(...args) => style.addToElem(creator(...args));
|
|
2501
2770
|
return Object.assign(newCreator, {
|
|
2502
2771
|
className: style.className,
|
|
2503
|
-
cls:
|
|
2772
|
+
cls: domMethods_1.clsPrefix.bind(null, style.className),
|
|
2504
2773
|
});
|
|
2505
2774
|
}
|
|
2506
2775
|
exports.styled = styled;
|
|
2776
|
+
// Keyframes produces simply a string with the generated name. Note that these does not support
|
|
2777
|
+
// nesting or ampersand (&) handling, since these would be difficult and are entirely unneeded.
|
|
2778
|
+
function keyframes(styles) {
|
|
2779
|
+
return (new KeyframePiece(styles)).className;
|
|
2780
|
+
}
|
|
2781
|
+
exports.keyframes = keyframes;
|
|
2507
2782
|
function createCssRules(className, styles) {
|
|
2508
|
-
|
|
2509
|
-
//
|
|
2510
|
-
|
|
2511
|
-
const mainRules = styles.
|
|
2512
|
-
|
|
2513
|
-
|
|
2514
|
-
|
|
2515
|
-
|
|
2516
|
-
|
|
2517
|
-
|
|
2783
|
+
// The first time we encounter a nested section, we know which are the "main" rules, and can
|
|
2784
|
+
// wrap them appropriately.
|
|
2785
|
+
const nestedStart = styles.search(/[^;]*\{/);
|
|
2786
|
+
const mainRules = nestedStart < 0 ? styles : styles.slice(0, nestedStart);
|
|
2787
|
+
const nestedRules = nestedStart < 0 ? "" : styles.slice(nestedStart);
|
|
2788
|
+
// At the end, replace all occurrences of & with ".className".
|
|
2789
|
+
return `& {${mainRules}\n}\n${nestedRules}`.replace(/&/g, className);
|
|
2790
|
+
}
|
|
2791
|
+
// Used by getNextStyleNum when running without a global window object (e.g. in tests).
|
|
2792
|
+
const _global = {};
|
|
2793
|
+
// Keep the counter for next class attached to the global window object rather than be a library
|
|
2794
|
+
// global. This way if by some chance multiple instance of grainjs are loaded into the page, it
|
|
2795
|
+
// still works without overwriting class names (which would be extremely confusing).
|
|
2796
|
+
function getNextStyleNum() {
|
|
2797
|
+
const g = browserGlobals_1.G.window || _global;
|
|
2798
|
+
return g._grainNextStyleNum = (g._grainNextStyleNum || 0) + 1;
|
|
2518
2799
|
}
|
|
2519
2800
|
class StylePiece {
|
|
2520
2801
|
constructor(_styles) {
|
|
@@ -2523,31 +2804,37 @@ class StylePiece {
|
|
|
2523
2804
|
this.className = StylePiece._nextClassName();
|
|
2524
2805
|
StylePiece._unmounted.add(this);
|
|
2525
2806
|
}
|
|
2526
|
-
// Generate a new css class name.
|
|
2527
|
-
static _nextClassName() { return `_grain${
|
|
2807
|
+
// Generate a new css class name. The suffix ensures that names like "&2" can't cause a conflict.
|
|
2808
|
+
static _nextClassName() { return `_grain${getNextStyleNum()}_`; }
|
|
2528
2809
|
// Mount all unmounted StylePieces, and clear the _unmounted map.
|
|
2529
2810
|
static _mountAll() {
|
|
2530
|
-
const sheet = Array.from(this._unmounted, (p) =>
|
|
2531
|
-
|
|
2532
|
-
browserGlobals_1.G.document.head.appendChild(dom_1.dom('style', sheet));
|
|
2811
|
+
const sheet = Array.from(this._unmounted, (p) => p._createRules()).join("\n\n");
|
|
2812
|
+
browserGlobals_1.G.document.head.appendChild(domImpl_1.dom('style', sheet));
|
|
2533
2813
|
for (const piece of this._unmounted) {
|
|
2534
2814
|
piece._mounted = true;
|
|
2535
2815
|
}
|
|
2536
2816
|
this._unmounted.clear();
|
|
2537
2817
|
}
|
|
2538
|
-
|
|
2818
|
+
addToElem(elem) {
|
|
2539
2819
|
if (!this._mounted) {
|
|
2540
2820
|
StylePiece._mountAll();
|
|
2541
2821
|
}
|
|
2542
|
-
|
|
2822
|
+
elem.classList.add(this.className);
|
|
2823
|
+
return elem;
|
|
2824
|
+
}
|
|
2825
|
+
_createRules() {
|
|
2826
|
+
return createCssRules('.' + this.className, this._styles);
|
|
2543
2827
|
}
|
|
2544
2828
|
}
|
|
2545
|
-
// Index of next auto-generated css class name.
|
|
2546
|
-
StylePiece._next = 1;
|
|
2547
2829
|
// Set of all StylePieces created but not yet mounted.
|
|
2548
2830
|
StylePiece._unmounted = new Set();
|
|
2831
|
+
class KeyframePiece extends StylePiece {
|
|
2832
|
+
_createRules() {
|
|
2833
|
+
return `@keyframes ${this.className} {${this._styles}}`;
|
|
2834
|
+
}
|
|
2835
|
+
}
|
|
2549
2836
|
|
|
2550
|
-
},{"./browserGlobals":
|
|
2837
|
+
},{"./browserGlobals":5,"./domImpl":13,"./domMethods":14}],22:[function(require,module,exports){
|
|
2551
2838
|
"use strict";
|
|
2552
2839
|
/**
|
|
2553
2840
|
* subscribe.js implements subscriptions to several observables at once.
|
|
@@ -2569,7 +2856,9 @@ StylePiece._unmounted = new Set();
|
|
|
2569
2856
|
* subscribe(...deps, ((use, ...depValues) => READ_CALLBACK));
|
|
2570
2857
|
*/
|
|
2571
2858
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
2859
|
+
exports.subscribe = exports.Subscription = void 0;
|
|
2572
2860
|
const _computed_queue_1 = require("./_computed_queue");
|
|
2861
|
+
const kowrap_1 = require("./kowrap");
|
|
2573
2862
|
// Constant empty array, which we use to avoid allocating new read-only empty arrays.
|
|
2574
2863
|
const emptyArray = [];
|
|
2575
2864
|
class Subscription {
|
|
@@ -2612,7 +2901,8 @@ class Subscription {
|
|
|
2612
2901
|
* subscription to `obs` if one doesn't yet exist.
|
|
2613
2902
|
* @param {Observable} obs: The observable being used as a dependency.
|
|
2614
2903
|
*/
|
|
2615
|
-
_useDependency(
|
|
2904
|
+
_useDependency(_obs) {
|
|
2905
|
+
const obs = ('_getDepItem' in _obs) ? _obs : kowrap_1.fromKo(_obs);
|
|
2616
2906
|
let listener = this._dynDeps.get(obs);
|
|
2617
2907
|
if (!listener) {
|
|
2618
2908
|
listener = this._subscribeTo(obs);
|
|
@@ -2658,7 +2948,8 @@ class Subscription {
|
|
|
2658
2948
|
* @param {Observable} obs: The observable to subscribe to.
|
|
2659
2949
|
* @returns {Listener} Listener object.
|
|
2660
2950
|
*/
|
|
2661
|
-
_subscribeTo(
|
|
2951
|
+
_subscribeTo(_obs) {
|
|
2952
|
+
const obs = ('_getDepItem' in _obs) ? _obs : kowrap_1.fromKo(_obs);
|
|
2662
2953
|
return obs.addListener(this._enqueue, this);
|
|
2663
2954
|
}
|
|
2664
2955
|
/**
|
|
@@ -2687,9 +2978,10 @@ function subscribe(...args) {
|
|
|
2687
2978
|
}
|
|
2688
2979
|
exports.subscribe = subscribe;
|
|
2689
2980
|
|
|
2690
|
-
},{"./_computed_queue":3}],
|
|
2981
|
+
},{"./_computed_queue":3,"./kowrap":17}],23:[function(require,module,exports){
|
|
2691
2982
|
"use strict";
|
|
2692
2983
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
2984
|
+
exports.bindBU = exports.bindUB = exports.bindB = void 0;
|
|
2693
2985
|
/**
|
|
2694
2986
|
* Returns f such that f() calls func(...boundArgs), i.e. optimizes `() => func(...boundArgs)`.
|
|
2695
2987
|
* It is faster on node6 by 57-92%.
|
|
@@ -2750,5 +3042,118 @@ function bindBU(func, b) {
|
|
|
2750
3042
|
}
|
|
2751
3043
|
exports.bindBU = bindBU;
|
|
2752
3044
|
|
|
2753
|
-
},{}]
|
|
3045
|
+
},{}],24:[function(require,module,exports){
|
|
3046
|
+
"use strict";
|
|
3047
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3048
|
+
exports.input = void 0;
|
|
3049
|
+
/**
|
|
3050
|
+
* General INPUT widget.
|
|
3051
|
+
*/
|
|
3052
|
+
const index_1 = require("../../index");
|
|
3053
|
+
/**
|
|
3054
|
+
* Creates a input element tied to the given observable. The required options argument allows
|
|
3055
|
+
* controlling the behavior, see IInputOptions for details.
|
|
3056
|
+
*
|
|
3057
|
+
* This is intended for string input elements, with "type" such as text, email, url, password,
|
|
3058
|
+
* number, tel.
|
|
3059
|
+
*
|
|
3060
|
+
* Note that every change to the observable will affect the input element, but not every change to
|
|
3061
|
+
* the input element will affect the observable. Specifically, unless {onInput: true} is set, the
|
|
3062
|
+
* visible content may differ from the observable until the element loses focus or Enter is hit.
|
|
3063
|
+
*
|
|
3064
|
+
* Example usage:
|
|
3065
|
+
* input(obs, {}, {type: 'text', placeholder: 'Your name...'});
|
|
3066
|
+
* input(obs, {isValid: isValidObs}, {type: 'email', placeholder: 'Your email...'});
|
|
3067
|
+
* input(obs, {onInput: true}, {type: 'text'});
|
|
3068
|
+
*/
|
|
3069
|
+
function input(obs, options, ...args) {
|
|
3070
|
+
const isValid = options.isValid;
|
|
3071
|
+
function setValue(elem) {
|
|
3072
|
+
index_1.bundleChanges(() => {
|
|
3073
|
+
obs.set(elem.value);
|
|
3074
|
+
if (isValid) {
|
|
3075
|
+
isValid.set(elem.validity.valid);
|
|
3076
|
+
}
|
|
3077
|
+
});
|
|
3078
|
+
}
|
|
3079
|
+
return index_1.dom('input', ...args, index_1.dom.prop('value', obs), (isValid ?
|
|
3080
|
+
(elem) => index_1.dom.autoDisposeElem(elem, index_1.subscribe(obs, (use) => isValid.set(elem.checkValidity()))) :
|
|
3081
|
+
null), options.onInput ? index_1.dom.on('input', (e, elem) => setValue(elem)) : null, index_1.dom.on('change', (e, elem) => setValue(elem)), index_1.dom.onKeyPress({ Enter: (e, elem) => setValue(elem) }));
|
|
3082
|
+
}
|
|
3083
|
+
exports.input = input;
|
|
3084
|
+
|
|
3085
|
+
},{"../../index":1}],25:[function(require,module,exports){
|
|
3086
|
+
"use strict";
|
|
3087
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3088
|
+
exports.select = void 0;
|
|
3089
|
+
/**
|
|
3090
|
+
* Select dropdown widget.
|
|
3091
|
+
*/
|
|
3092
|
+
const index_1 = require("../../index");
|
|
3093
|
+
function unwrapMaybeObsArray(array) {
|
|
3094
|
+
return Array.isArray(array) ? array : array.get();
|
|
3095
|
+
}
|
|
3096
|
+
function getOptionValue(option) {
|
|
3097
|
+
return (typeof option === "string") ?
|
|
3098
|
+
option : option.value;
|
|
3099
|
+
}
|
|
3100
|
+
/**
|
|
3101
|
+
* Creates a select dropdown widget. The observable `obs` reflects the value of the selected
|
|
3102
|
+
* option, and `optionArray` is an array (regular or observable) of option values and labels.
|
|
3103
|
+
* These may be either strings, or {label, value, disabled} objects.
|
|
3104
|
+
*
|
|
3105
|
+
* The type of value may be any type at all; it is opaque to this widget.
|
|
3106
|
+
*
|
|
3107
|
+
* If obs is set to an invalid or disabled value, then defLabel option is used to determine the
|
|
3108
|
+
* label that the select box will show, blank by default.
|
|
3109
|
+
*
|
|
3110
|
+
* Usage:
|
|
3111
|
+
* const fruit = observable("apple");
|
|
3112
|
+
* select(fruit, ["apple", "banana", "mango"]);
|
|
3113
|
+
*
|
|
3114
|
+
* const employee = observable(17);
|
|
3115
|
+
* const employees = obsArray<IOption<number>>([
|
|
3116
|
+
* {value: 12, label: "Bob", disabled: true},
|
|
3117
|
+
* {value: 17, label: "Alice"},
|
|
3118
|
+
* {value: 21, label: "Eve"},
|
|
3119
|
+
* ]);
|
|
3120
|
+
* select(employee, employees, {defLabel: "Select employee:"});
|
|
3121
|
+
*/
|
|
3122
|
+
function select(obs, optionArray, options = {}) {
|
|
3123
|
+
const { defLabel = "" } = options;
|
|
3124
|
+
return index_1.dom('select',
|
|
3125
|
+
// Include a hidden option to represent a default value. This one gets shown when none of the
|
|
3126
|
+
// options are selected. This is more consistent when showing the first valid option.
|
|
3127
|
+
index_1.dom('option', index_1.dom.hide(true), defLabel),
|
|
3128
|
+
// Create all the option elements.
|
|
3129
|
+
index_1.dom.forEach(optionArray, (option) => {
|
|
3130
|
+
const obj = (typeof option === "string") ?
|
|
3131
|
+
{ value: option, label: option } : option;
|
|
3132
|
+
// Note we only set 'selected' when an <option> is created; we are not subscribing to obs.
|
|
3133
|
+
// This is to reduce the amount of subscriptions, esp. when number of options is large.
|
|
3134
|
+
return index_1.dom('option', {
|
|
3135
|
+
disabled: obj.disabled,
|
|
3136
|
+
selected: obj.value === obs.get(),
|
|
3137
|
+
}, obj.label);
|
|
3138
|
+
}),
|
|
3139
|
+
// When obs changes, update select's value; we do it after <options> have been created.
|
|
3140
|
+
// Note that autoDisposeElem ensures the subscription is disposed with the 'select' element.
|
|
3141
|
+
(elem) => index_1.dom.autoDisposeElem(elem, index_1.subscribe(obs, (use, obsValue) => {
|
|
3142
|
+
const arr = unwrapMaybeObsArray(optionArray);
|
|
3143
|
+
const index = arr.findIndex((item) => getOptionValue(item) === obsValue);
|
|
3144
|
+
elem.selectedIndex = index + 1; // +1 for default option
|
|
3145
|
+
})),
|
|
3146
|
+
// When user picks a new item, use its value to update the observable.
|
|
3147
|
+
index_1.dom.on('change', (e, elem) => {
|
|
3148
|
+
const index = elem.selectedIndex;
|
|
3149
|
+
const item = unwrapMaybeObsArray(optionArray)[index - 1]; // -1 for default option
|
|
3150
|
+
// It should be impossible for the user to select an invalid option, but check just in case.
|
|
3151
|
+
if (item !== undefined) {
|
|
3152
|
+
obs.set(getOptionValue(item));
|
|
3153
|
+
}
|
|
3154
|
+
}));
|
|
3155
|
+
}
|
|
3156
|
+
exports.select = select;
|
|
3157
|
+
|
|
3158
|
+
},{"../../index":1}]},{},[1])(1)
|
|
2754
3159
|
});
|