grainjs 1.0.1 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +23 -26
- package/dist/cjs/index.js +28 -17
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/lib/PriorityQueue.d.ts +2 -2
- package/dist/cjs/lib/PriorityQueue.js +1 -0
- package/dist/cjs/lib/PriorityQueue.js.map +1 -1
- package/dist/cjs/lib/_computed_queue.js +4 -3
- package/dist/cjs/lib/_computed_queue.js.map +1 -1
- package/dist/cjs/lib/binding.d.ts +11 -4
- package/dist/cjs/lib/binding.js +6 -5
- package/dist/cjs/lib/binding.js.map +1 -1
- package/dist/cjs/lib/browserGlobals.d.ts +4 -1
- package/dist/cjs/lib/browserGlobals.js +2 -0
- package/dist/cjs/lib/browserGlobals.js.map +1 -1
- package/dist/cjs/lib/computed.d.ts +49 -28
- package/dist/cjs/lib/computed.js +38 -51
- package/dist/cjs/lib/computed.js.map +1 -1
- package/dist/cjs/lib/dispose.d.ts +109 -96
- package/dist/cjs/lib/dispose.js +106 -79
- package/dist/cjs/lib/dispose.js.map +1 -1
- package/dist/cjs/lib/dom.d.ts +40 -18
- package/dist/cjs/lib/dom.js +63 -29
- package/dist/cjs/lib/dom.js.map +1 -1
- package/dist/cjs/lib/domComponent.d.ts +56 -51
- package/dist/cjs/lib/domComponent.js +46 -44
- package/dist/cjs/lib/domComponent.js.map +1 -1
- package/dist/cjs/lib/domComputed.d.ts +50 -20
- package/dist/cjs/lib/domComputed.js +37 -7
- package/dist/cjs/lib/domComputed.js.map +1 -1
- package/dist/cjs/lib/domDispose.d.ts +27 -12
- package/dist/cjs/lib/domDispose.js +27 -11
- package/dist/cjs/lib/domDispose.js.map +1 -1
- package/dist/cjs/lib/domForEach.d.ts +5 -4
- package/dist/cjs/lib/domForEach.js +41 -41
- package/dist/cjs/lib/domForEach.js.map +1 -1
- package/dist/cjs/lib/domImpl.d.ts +33 -10
- package/dist/cjs/lib/domImpl.js +29 -9
- package/dist/cjs/lib/domImpl.js.map +1 -1
- package/dist/cjs/lib/domMethods.d.ts +93 -47
- package/dist/cjs/lib/domMethods.js +91 -47
- package/dist/cjs/lib/domMethods.js.map +1 -1
- package/dist/cjs/lib/domevent.d.ts +87 -62
- package/dist/cjs/lib/domevent.js +85 -59
- package/dist/cjs/lib/domevent.js.map +1 -1
- package/dist/cjs/lib/emit.d.ts +62 -32
- package/dist/cjs/lib/emit.js +68 -53
- package/dist/cjs/lib/emit.js.map +1 -1
- package/dist/cjs/lib/kowrap.d.ts +6 -3
- package/dist/cjs/lib/kowrap.js +7 -3
- package/dist/cjs/lib/kowrap.js.map +1 -1
- package/dist/cjs/lib/obsArray.d.ts +91 -53
- package/dist/cjs/lib/obsArray.js +87 -54
- package/dist/cjs/lib/obsArray.js.map +1 -1
- package/dist/cjs/lib/observable.d.ts +25 -15
- package/dist/cjs/lib/observable.js +31 -19
- package/dist/cjs/lib/observable.js.map +1 -1
- package/dist/cjs/lib/pureComputed.d.ts +12 -15
- package/dist/cjs/lib/pureComputed.js +16 -18
- package/dist/cjs/lib/pureComputed.js.map +1 -1
- package/dist/cjs/lib/styled.d.ts +78 -61
- package/dist/cjs/lib/styled.js +27 -79
- package/dist/cjs/lib/styled.js.map +1 -1
- package/dist/cjs/lib/subscribe.d.ts +41 -37
- package/dist/cjs/lib/subscribe.js +31 -39
- package/dist/cjs/lib/subscribe.js.map +1 -1
- package/dist/cjs/lib/util.js +2 -0
- package/dist/cjs/lib/util.js.map +1 -1
- package/dist/cjs/lib/widgets/input.d.ts +3 -1
- package/dist/cjs/lib/widgets/input.js +7 -4
- package/dist/cjs/lib/widgets/input.js.map +1 -1
- package/dist/cjs/lib/widgets/select.d.ts +4 -2
- package/dist/cjs/lib/widgets/select.js +8 -5
- package/dist/cjs/lib/widgets/select.js.map +1 -1
- package/dist/esm/lib/_computed_queue.js +3 -3
- package/dist/esm/lib/_computed_queue.js.map +1 -1
- package/dist/esm/lib/binding.js +2 -2
- package/dist/esm/lib/binding.js.map +1 -1
- package/dist/esm/lib/browserGlobals.js +1 -0
- package/dist/esm/lib/browserGlobals.js.map +1 -1
- package/dist/esm/lib/computed.js +36 -50
- package/dist/esm/lib/computed.js.map +1 -1
- package/dist/esm/lib/dispose.js +104 -78
- package/dist/esm/lib/dispose.js.map +1 -1
- package/dist/esm/lib/dom.js +40 -18
- package/dist/esm/lib/dom.js.map +1 -1
- package/dist/esm/lib/domComponent.js +45 -44
- package/dist/esm/lib/domComponent.js.map +1 -1
- package/dist/esm/lib/domComputed.js +32 -5
- package/dist/esm/lib/domComputed.js.map +1 -1
- package/dist/esm/lib/domDispose.js +26 -11
- package/dist/esm/lib/domDispose.js.map +1 -1
- package/dist/esm/lib/domForEach.js +40 -41
- package/dist/esm/lib/domForEach.js.map +1 -1
- package/dist/esm/lib/domImpl.js +26 -7
- package/dist/esm/lib/domImpl.js.map +1 -1
- package/dist/esm/lib/domMethods.js +77 -35
- package/dist/esm/lib/domMethods.js.map +1 -1
- package/dist/esm/lib/domevent.js +84 -59
- package/dist/esm/lib/domevent.js.map +1 -1
- package/dist/esm/lib/emit.js +67 -53
- package/dist/esm/lib/emit.js.map +1 -1
- package/dist/esm/lib/kowrap.js +5 -2
- package/dist/esm/lib/kowrap.js.map +1 -1
- package/dist/esm/lib/obsArray.js +82 -50
- package/dist/esm/lib/obsArray.js.map +1 -1
- package/dist/esm/lib/observable.js +26 -15
- package/dist/esm/lib/observable.js.map +1 -1
- package/dist/esm/lib/pureComputed.js +15 -18
- package/dist/esm/lib/pureComputed.js.map +1 -1
- package/dist/esm/lib/styled.js +24 -77
- package/dist/esm/lib/styled.js.map +1 -1
- package/dist/esm/lib/subscribe.js +27 -36
- package/dist/esm/lib/subscribe.js.map +1 -1
- package/dist/esm/lib/util.js +1 -0
- package/dist/esm/lib/util.js.map +1 -1
- package/dist/esm/lib/widgets/input.js +3 -1
- package/dist/esm/lib/widgets/input.js.map +1 -1
- package/dist/esm/lib/widgets/select.js +3 -1
- package/dist/esm/lib/widgets/select.js.map +1 -1
- package/dist/grain-full.debug.js +2138 -3052
- package/dist/grain-full.debug.js.map +7 -0
- package/dist/grain-full.min.js +6 -2
- package/dist/grain-full.min.js.map +7 -1
- package/lib/binding.ts +9 -2
- package/lib/browserGlobals.ts +3 -1
- package/lib/computed.ts +56 -56
- package/lib/dispose.ts +110 -85
- package/lib/dom.ts +41 -20
- package/lib/domComponent.ts +68 -70
- package/lib/domComputed.ts +66 -21
- package/lib/domDispose.ts +28 -11
- package/lib/domForEach.ts +13 -12
- package/lib/domImpl.ts +30 -7
- package/lib/domMethods.ts +101 -46
- package/lib/domevent.ts +86 -61
- package/lib/emit.ts +64 -50
- package/lib/kowrap.ts +5 -2
- package/lib/obsArray.ts +89 -54
- package/lib/observable.ts +26 -15
- package/lib/pureComputed.ts +16 -22
- package/lib/styled.ts +85 -71
- package/lib/subscribe.ts +41 -45
- package/lib/util.ts +1 -0
- package/lib/widgets/input.ts +3 -1
- package/lib/widgets/select.ts +3 -1
- package/package.json +48 -38
package/lib/domComponent.ts
CHANGED
|
@@ -1,65 +1,5 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* UI components that can be inserted into dom().
|
|
3
|
-
*
|
|
4
|
-
* Components are created and inserted using dom.create():
|
|
5
|
-
*
|
|
6
|
-
* dom('div',
|
|
7
|
-
* dom.create(MyWidget, ...myArgs), // Calls MyWidget.create(owner, ...myArgs)
|
|
8
|
-
* dom.create(createMyWidget, ...myArgs), // Calls createMyWidget(owner, ...myArgs)
|
|
9
|
-
* )
|
|
10
|
-
*
|
|
11
|
-
* The first argument may be a function, which is called directly, or a class with a .create()
|
|
12
|
-
* static method, in which case that's what gets called.
|
|
13
|
-
*
|
|
14
|
-
* In both cases, the call gets a first argument of `owner` followed by the rest of the arguments
|
|
15
|
-
* to dom.create(). The `owner` is a MultiHolder that will own this component. This works
|
|
16
|
-
* naturally with any class that derives from Disposable, since it then has a suitable static
|
|
17
|
-
* create() method.
|
|
18
|
-
*
|
|
19
|
-
* Function-based components may use owner to easily handle disposal. For example:
|
|
20
|
-
*
|
|
21
|
-
* dom.create(createMyWidget)
|
|
22
|
-
* function createMyWidget(owner) {
|
|
23
|
-
* const foo = Foo.create(owner);
|
|
24
|
-
* return dom('div', foo.getTitle());
|
|
25
|
-
* }
|
|
26
|
-
*
|
|
27
|
-
* The `owner` argument is the main benefit of dom.create(). Logically, the owner is the DOM where
|
|
28
|
-
* the component is attached. When the parent DOM element is disposed, so is the component.
|
|
29
|
-
*
|
|
30
|
-
* [Explanation] To understand why the syntax is such, consider a potential alternative such as:
|
|
31
|
-
*
|
|
32
|
-
* dom('div', _insert_(new Comp1()), _insert_(new Comp2(...args)))
|
|
33
|
-
*
|
|
34
|
-
* In both cases, the constructor for Comp1 runs before the constructor for Comp2. What happens
|
|
35
|
-
* when Comp2's constructor throws an exception? In the second case, nothing yet owns the
|
|
36
|
-
* created Comp1 component, and it will never get cleaned up. With dom.create(), the DOM
|
|
37
|
-
* gets ownership of Comp1 early enough and will dispose it.
|
|
38
|
-
*
|
|
39
|
-
* A function component may return DOM directly. A class component returns the class instance,
|
|
40
|
-
* which must have a .buildDom() method which will be called right after the constructor to get
|
|
41
|
-
* the DOM. Note that buildDom is only called once.
|
|
42
|
-
*
|
|
43
|
-
* A function component may also return an object with .buildDom(). So these are equivalent:
|
|
44
|
-
*
|
|
45
|
-
* dom.create(MyWidget)
|
|
46
|
-
* dom.create((owner) => MyWidget.create(owner))
|
|
47
|
-
*
|
|
48
|
-
* Note that ownership should be handled using the `owner` argument. Don't do this:
|
|
49
|
-
*
|
|
50
|
-
* // NON-EXAMPLE: Nothing will dispose the created object:
|
|
51
|
-
* // dom.create(() => new MyWidget());
|
|
52
|
-
*
|
|
53
|
-
* The returned DOM may includes Nodes, strings, and domComputed() values, as well as arrays of
|
|
54
|
-
* any of these. In other words, any DomArg goes except DomMethods. All the DOM returned will be
|
|
55
|
-
* disposed when the containing element is disposed, followed by the `owner` itself.
|
|
56
|
-
*/
|
|
57
1
|
import {MultiHolder} from './dispose';
|
|
58
|
-
import {
|
|
59
|
-
import {autoDisposeElem} from './domDispose';
|
|
60
|
-
|
|
61
|
-
export type DomContents = Node | string | DomComputed | void | null | undefined | IDomContentsArray;
|
|
62
|
-
export interface IDomContentsArray extends Array<DomContents> {}
|
|
2
|
+
import {domComputedOwned, DomContents} from './domComputed';
|
|
63
3
|
|
|
64
4
|
export interface IDomComponent {
|
|
65
5
|
buildDom(): DomContents;
|
|
@@ -78,23 +18,81 @@ export interface IDomCreateClass<Args extends any[]> {
|
|
|
78
18
|
}
|
|
79
19
|
export type IDomCreator<Args extends any[]> = IDomCreateFunc<Args> | IDomCreateClass<Args>;
|
|
80
20
|
|
|
81
|
-
type DomCreatorArgs<T> =
|
|
21
|
+
export type DomCreatorArgs<T> =
|
|
82
22
|
T extends (owner: MultiHolder, ...args: infer P) => any ? P :
|
|
83
23
|
(T extends new (...args: infer P) => any ? P : never);
|
|
84
24
|
|
|
25
|
+
/**
|
|
26
|
+
* UI components that can be inserted into `dom()`.
|
|
27
|
+
*
|
|
28
|
+
* Components are created and inserted using `dom.create()`:
|
|
29
|
+
* ```ts
|
|
30
|
+
* dom('div',
|
|
31
|
+
* dom.create(MyWidget, ...myArgs), // Calls MyWidget.create(owner, ...myArgs)
|
|
32
|
+
* dom.create(createMyWidget, ...myArgs), // Calls createMyWidget(owner, ...myArgs)
|
|
33
|
+
* )
|
|
34
|
+
* ```
|
|
35
|
+
*
|
|
36
|
+
* The first argument may be a function, which is called directly, or a class with a `.create()`
|
|
37
|
+
* static method, in which case that's what gets called.
|
|
38
|
+
*
|
|
39
|
+
* In both cases, the call gets a first argument of `owner` followed by the rest of the arguments
|
|
40
|
+
* to `dom.create()`. The `owner` is a `MultiHolder` that will own this component. This works
|
|
41
|
+
* naturally with any class that derives from Disposable, since it then has a suitable static
|
|
42
|
+
* `create()` method.
|
|
43
|
+
*
|
|
44
|
+
* Function-based components may use owner to easily handle disposal. For example:
|
|
45
|
+
* ```ts
|
|
46
|
+
* dom.create(createMyWidget)
|
|
47
|
+
* function createMyWidget(owner) {
|
|
48
|
+
* const foo = Foo.create(owner);
|
|
49
|
+
* return dom('div', foo.getTitle());
|
|
50
|
+
* }
|
|
51
|
+
* ```
|
|
52
|
+
*
|
|
53
|
+
* The `owner` argument is the main benefit of `dom.create()`. Logically, the owner is the DOM where
|
|
54
|
+
* the component is attached. When the parent DOM element is disposed, so is the component.
|
|
55
|
+
*
|
|
56
|
+
* :::info Explanation
|
|
57
|
+
*
|
|
58
|
+
* To understand why the syntax is such, consider a potential alternative such as:
|
|
59
|
+
* ```ts
|
|
60
|
+
* dom('div', _insert_(new Comp1()), _insert_(new Comp2(...args)))
|
|
61
|
+
* ```
|
|
62
|
+
*
|
|
63
|
+
* In both cases, the constructor for Comp1 runs before the constructor for Comp2. What happens
|
|
64
|
+
* when Comp2's constructor throws an exception? In the second case, nothing yet owns the
|
|
65
|
+
* created Comp1 component, and it will never get cleaned up. With `dom.create()`, the DOM
|
|
66
|
+
* gets ownership of Comp1 early enough and will dispose it.
|
|
67
|
+
*
|
|
68
|
+
* :::
|
|
69
|
+
*
|
|
70
|
+
* A function component may return DOM directly. A class component returns the class instance,
|
|
71
|
+
* which must have a `.buildDom()` method which will be called right after the constructor to get
|
|
72
|
+
* the DOM. Note that buildDom is only called once.
|
|
73
|
+
*
|
|
74
|
+
* A function component may also return an object with `.buildDom()`. So these are equivalent:
|
|
75
|
+
* ```ts
|
|
76
|
+
* dom.create(MyWidget)
|
|
77
|
+
* dom.create((owner) => MyWidget.create(owner))
|
|
78
|
+
* ```
|
|
79
|
+
*
|
|
80
|
+
* Note that ownership should be handled using the `owner` argument. Don't do this:
|
|
81
|
+
* ```ts
|
|
82
|
+
* // NON-EXAMPLE: Nothing will dispose the created object:
|
|
83
|
+
* // dom.create(() => new MyWidget());
|
|
84
|
+
* ```
|
|
85
|
+
*
|
|
86
|
+
* The returned DOM may includes Nodes, strings, and `domComputed()` values, as well as arrays of
|
|
87
|
+
* any of these. In other words, any `DomArg` goes except `DomMethods`. All the DOM returned will be
|
|
88
|
+
* disposed when the containing element is disposed, followed by the `owner` itself.
|
|
89
|
+
*/
|
|
85
90
|
export function create<Fn extends IDomCreator<any[]>>(fn: Fn, ...args: DomCreatorArgs<Fn>): DomContents {
|
|
86
|
-
|
|
87
|
-
// Note that the callback to domComputed() is not called until the markers have been attached
|
|
88
|
-
// to the parent element. We attach the MultiHolder's disposal to markerPost the way
|
|
89
|
-
// domComputed() normally attaches its own bindings.
|
|
90
|
-
const owner = MultiHolder.create(null);
|
|
91
|
-
autoDisposeElem(markerPost, owner);
|
|
92
|
-
|
|
91
|
+
return domComputedOwned(null, (owner) => {
|
|
93
92
|
const value: DomComponentReturn = ('create' in fn) ?
|
|
94
93
|
(fn as IDomCreateClass<any[]>).create(owner, ...args) :
|
|
95
94
|
(fn as IDomCreateFunc<any[]>)(owner, ...args);
|
|
96
95
|
return (value && typeof value === 'object' && 'buildDom' in value) ?
|
|
97
96
|
value.buildDom() : value;
|
|
98
97
|
});
|
|
99
|
-
return [markerPre, markerPost, func];
|
|
100
98
|
}
|
package/lib/domComputed.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import {BindableValue, subscribeElem} from './binding';
|
|
2
|
-
import {
|
|
2
|
+
import {Holder, MultiHolder} from './dispose';
|
|
3
|
+
import {autoDisposeElem, domDispose} from './domDispose';
|
|
3
4
|
import {DomArg, DomMethod, frag} from './domImpl';
|
|
4
5
|
|
|
5
6
|
// Use the browser globals in a way that allows replacing them with mocks in tests.
|
|
@@ -9,12 +10,16 @@ import {G} from './browserGlobals';
|
|
|
9
10
|
// name for use in places where a DomComputed is suitable but a general DomArg is not.
|
|
10
11
|
export type DomComputed = [Node, Node, DomMethod];
|
|
11
12
|
|
|
13
|
+
export type DomContents = Node | string | DomComputed | void | null | undefined | IDomContentsArray;
|
|
14
|
+
export interface IDomContentsArray extends Array<DomContents> {}
|
|
15
|
+
|
|
16
|
+
|
|
12
17
|
/**
|
|
13
18
|
* Replaces the content between nodeBefore and nodeAfter, which should be two siblings within the
|
|
14
19
|
* same parent node. New content may be anything allowed as an argument to dom(), including null
|
|
15
20
|
* to insert nothing. Runs disposers, if any, on all removed content.
|
|
16
21
|
*/
|
|
17
|
-
export function replaceContent(nodeBefore: Node, nodeAfter: Node, content:
|
|
22
|
+
export function replaceContent(nodeBefore: Node, nodeAfter: Node, content: DomContents): void {
|
|
18
23
|
const elem = nodeBefore.parentNode;
|
|
19
24
|
if (elem) {
|
|
20
25
|
let next;
|
|
@@ -39,37 +44,44 @@ export function replaceContent(nodeBefore: Node, nodeAfter: Node, content: DomAr
|
|
|
39
44
|
* changes, previous content is disposed and removed, and new content added in its place.
|
|
40
45
|
*
|
|
41
46
|
* The following are roughly equivalent:
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
47
|
+
* ```ts
|
|
48
|
+
* // (A)
|
|
49
|
+
* domComputed(nlinesObs, nlines => nlines > 1 ? dom('textarea') : dom('input'))
|
|
50
|
+
* // (B)
|
|
51
|
+
* domComputed(use => use(nlinesObs) > 1 ? dom('textarea') : dom('input'))
|
|
52
|
+
* // (C)
|
|
53
|
+
* domComputed(use => use(nlinesObs) > 1, isTall => isTall ? dom('textarea') : dom('input'))
|
|
54
|
+
* ```
|
|
45
55
|
*
|
|
46
56
|
* Here, (C) is best. Both (A) and (B) would rebuild DOM for any change in nlinesObs, but (C)
|
|
47
57
|
* encapsulates meaningful changes in the observable, and only recreates DOM when necessary.
|
|
48
58
|
*
|
|
49
59
|
* Syntax (B), without the second argument, may be useful in cases of DOM depending on several
|
|
50
60
|
* observables, e.g.
|
|
61
|
+
* ```ts
|
|
62
|
+
* domComputed(use => use(readonlyObs) ? dom('div') :
|
|
63
|
+
* (use(nlinesObs) > 1 ? dom('textarea') : dom('input')))
|
|
64
|
+
* ```
|
|
51
65
|
*
|
|
52
|
-
*
|
|
53
|
-
* (use(nlinesObs) > 1 ? dom('textarea') : dom('input')));
|
|
54
|
-
*
|
|
55
|
-
* If the argument is not an observable, domComputed() may but should not be used. The following
|
|
66
|
+
* If the argument is not an observable, `domComputed()` may but should not be used. The following
|
|
56
67
|
* are equivalent:
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
*
|
|
68
|
+
* ```ts
|
|
69
|
+
* dom(..., domComputed(listValue, list => `Have ${list.length} items`), ...);
|
|
70
|
+
* dom(..., `Have ${listValue.length} items`, ...);
|
|
71
|
+
* ```
|
|
60
72
|
*
|
|
61
73
|
* In this case, the latter is preferred as the clearly simpler one.
|
|
62
74
|
*
|
|
63
|
-
* @param valueObs
|
|
64
|
-
* @param contentFunc
|
|
75
|
+
* @param valueObs - Observable or function for a computed.
|
|
76
|
+
* @param contentFunc - Function called with the result of valueObs as the input, and
|
|
65
77
|
* returning DOM as output. If omitted, defaults to the identity function.
|
|
66
78
|
*/
|
|
67
79
|
// Note that DomMethod is excluded because it prevents typescript from inferring the type of
|
|
68
80
|
// the first argument when it's a function (and it's not useful).
|
|
69
81
|
export function domComputed(valueObs: BindableValue<Exclude<DomArg, DomMethod>>): DomComputed;
|
|
70
|
-
export function domComputed<T>(valueObs: BindableValue<T>, contentFunc: (val: T) =>
|
|
82
|
+
export function domComputed<T>(valueObs: BindableValue<T>, contentFunc: (val: T) => DomContents): DomComputed;
|
|
71
83
|
export function domComputed<T>(
|
|
72
|
-
valueObs: BindableValue<T>, contentFunc: (val: T) =>
|
|
84
|
+
valueObs: BindableValue<T>, contentFunc: (val: T) => DomContents = identity as any,
|
|
73
85
|
): DomComputed {
|
|
74
86
|
const markerPre = G.document.createComment('a');
|
|
75
87
|
const markerPost = G.document.createComment('b');
|
|
@@ -82,6 +94,23 @@ export function domComputed<T>(
|
|
|
82
94
|
}];
|
|
83
95
|
}
|
|
84
96
|
|
|
97
|
+
/**
|
|
98
|
+
* Like domComputed(), but the callback gets an additional first argument, owner, which may be
|
|
99
|
+
* used to take ownership of objects created by the callback. These will be disposed before each
|
|
100
|
+
* new call to the callback, and when the containing DOM is disposed.
|
|
101
|
+
*
|
|
102
|
+
* `domComputedOwned(valueObs, (owner, value) => Editor.create(owner, value).renderSomething())`
|
|
103
|
+
*/
|
|
104
|
+
export function domComputedOwned<T>(
|
|
105
|
+
valueObs: BindableValue<T>, contentFunc: (owner: MultiHolder, val: T) => DomContents
|
|
106
|
+
): DomComputed {
|
|
107
|
+
const holder = Holder.create(null);
|
|
108
|
+
const [markerPre, markerPost, func] = domComputed(valueObs,
|
|
109
|
+
(val: T) => contentFunc(MultiHolder.create(holder), val));
|
|
110
|
+
autoDisposeElem(markerPost, holder);
|
|
111
|
+
return [markerPre, markerPost, func];
|
|
112
|
+
}
|
|
113
|
+
|
|
85
114
|
function identity<T>(arg: T): T { return arg; }
|
|
86
115
|
|
|
87
116
|
/**
|
|
@@ -92,20 +121,36 @@ function identity<T>(arg: T): T { return arg; }
|
|
|
92
121
|
* Note that if the observable changes between different truthy values, contentFunc gets called
|
|
93
122
|
* for each value, and previous content gets destroyed. To consider all truthy values the same,
|
|
94
123
|
* use an observable that returns a proper boolean, e.g.
|
|
95
|
-
*
|
|
124
|
+
* ```ts
|
|
96
125
|
* dom.maybe(use => Boolean(use(fooObs)), () => dom(...));
|
|
126
|
+
* ```
|
|
97
127
|
*
|
|
98
128
|
* As with domComputed(), dom.maybe() may but should not be used when the argument is not an
|
|
99
129
|
* observable or function. The following are equivalent:
|
|
100
|
-
*
|
|
130
|
+
* ```ts
|
|
101
131
|
* dom(..., dom.maybe(myValue, () => dom(...)));
|
|
102
132
|
* dom(..., myValue ? dom(...) : null);
|
|
133
|
+
* ```
|
|
103
134
|
*
|
|
104
135
|
* The latter is preferred for being simpler.
|
|
105
136
|
*
|
|
106
|
-
* @param boolValueObs
|
|
107
|
-
* @param contentFunc
|
|
137
|
+
* @param boolValueObs - Observable or function for a computed.
|
|
138
|
+
* @param contentFunc - Called with the result of boolValueObs when it is truthy. Should return DOM.
|
|
108
139
|
*/
|
|
109
|
-
export function maybe<T>(boolValueObs: BindableValue<T>,
|
|
140
|
+
export function maybe<T>(boolValueObs: BindableValue<T>,
|
|
141
|
+
contentFunc: (val: NonNullable<T>) => DomContents): DomComputed {
|
|
110
142
|
return domComputed(boolValueObs, (value) => value ? contentFunc(value!) : null);
|
|
111
143
|
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Like maybe(), but the callback gets an additional first argument, owner, which may be used to
|
|
147
|
+
* take ownership of objects created by the callback. These will be disposed before each new call
|
|
148
|
+
* to the callback, and when the condition becomes false or the containing DOM gets disposed.
|
|
149
|
+
* ```ts
|
|
150
|
+
* maybeOwned(showEditor, (owner) => Editor.create(owner).renderSomething())
|
|
151
|
+
* ```
|
|
152
|
+
*/
|
|
153
|
+
export function maybeOwned<T>(boolValueObs: BindableValue<T>,
|
|
154
|
+
contentFunc: (owner: MultiHolder, val: NonNullable<T>) => DomContents): DomComputed {
|
|
155
|
+
return domComputedOwned(boolValueObs, (owner, value) => value ? contentFunc(owner, value!) : null);
|
|
156
|
+
}
|
package/lib/domDispose.ts
CHANGED
|
@@ -29,6 +29,7 @@ function _walkDom(elem: Node, visitFunc: INodeFunc): void {
|
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
// Internal helper to run all disposers for a single element.
|
|
32
|
+
/** @internal */
|
|
32
33
|
export function _disposeNode(node: Node): void {
|
|
33
34
|
let disposer = _disposeMap.get(node);
|
|
34
35
|
if (disposer) {
|
|
@@ -68,21 +69,22 @@ export const domDisposeHooks: IDomDisposeHooks = {
|
|
|
68
69
|
* It is automatically called if one of the function arguments to dom() throws an exception during
|
|
69
70
|
* element creation. This way any onDispose() handlers set on the unfinished element get called.
|
|
70
71
|
*
|
|
71
|
-
* @param
|
|
72
|
+
* @param node - The element to run disposers on.
|
|
72
73
|
*/
|
|
73
74
|
export function domDispose(node: Node): void {
|
|
74
75
|
domDisposeHooks.disposeRecursive(node);
|
|
75
76
|
}
|
|
76
77
|
|
|
77
78
|
/**
|
|
78
|
-
* Associate a
|
|
79
|
-
* using domDispose() on it or any of its parents. If
|
|
80
|
-
*
|
|
81
|
-
*
|
|
82
|
-
* @param {Function} disposerFunc(elem): Will be called when domDispose() is called on the
|
|
83
|
-
* element or its ancestor.
|
|
79
|
+
* Associate a disposer function with a DOM element. It will be called when the element is disposed
|
|
80
|
+
* using `domDispose()` on it or any of its parents. If called multiple times, all
|
|
81
|
+
* disposer functions will be called in reverse order.
|
|
82
|
+
*
|
|
84
83
|
* Note that it is not necessary usually to dispose event listeners attached to an element (e.g.
|
|
85
|
-
* with dom.on()) since their lifetime is naturally limited to the lifetime of the element.
|
|
84
|
+
* with `dom.on()`) since their lifetime is naturally limited to the lifetime of the element.
|
|
85
|
+
*
|
|
86
|
+
* @param elem - The element to associate the disposer with.
|
|
87
|
+
* @param disposerFunc - Will be called when `domDispose()` is called on the element or its ancestor.
|
|
86
88
|
*/
|
|
87
89
|
export function onDisposeElem(elem: Node, disposerFunc: INodeFunc): void {
|
|
88
90
|
const prevDisposer = _disposeMap.get(elem);
|
|
@@ -91,21 +93,36 @@ export function onDisposeElem(elem: Node, disposerFunc: INodeFunc): void {
|
|
|
91
93
|
_disposeMap.set(disposerFunc, prevDisposer);
|
|
92
94
|
}
|
|
93
95
|
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Associate a disposer function with a DOM element. It will be called when the element is disposed
|
|
99
|
+
* using `domDispose()` on it or any of its parents. If called multiple times, all
|
|
100
|
+
* disposer functions will be called in reverse order.
|
|
101
|
+
*
|
|
102
|
+
* @param disposerFunc - Will be called when `domDispose()` is called on the element or its ancestor.
|
|
103
|
+
*/
|
|
94
104
|
export function onDispose(disposerFunc: INodeFunc) {
|
|
95
105
|
return (elem: Node) => onDisposeElem(elem, disposerFunc);
|
|
96
106
|
}
|
|
97
107
|
|
|
98
108
|
/**
|
|
99
|
-
* Make the given element own the disposable, and call its dispose method when domDispose() is
|
|
109
|
+
* Make the given element own the disposable, and call its dispose method when `domDispose()` is
|
|
100
110
|
* called on the element or any of its parents.
|
|
101
|
-
* @param
|
|
102
|
-
* @param
|
|
111
|
+
* @param elem - The element to own the disposable.
|
|
112
|
+
* @param disposable - Anything with a `.dispose()` method.
|
|
103
113
|
*/
|
|
104
114
|
export function autoDisposeElem(elem: Node, disposable: IDisposable|null) {
|
|
105
115
|
if (disposable) {
|
|
106
116
|
onDisposeElem(elem, () => disposable.dispose());
|
|
107
117
|
}
|
|
108
118
|
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Make the given element own the disposable, and call its dispose method when `domDispose()` is
|
|
122
|
+
* called on the element or any of its parents.
|
|
123
|
+
*
|
|
124
|
+
* @param disposable - Anything with a `.dispose()` method.
|
|
125
|
+
*/
|
|
109
126
|
export function autoDispose(disposable: IDisposable|null) {
|
|
110
127
|
if (disposable) {
|
|
111
128
|
return (elem: Node) => autoDisposeElem(elem, disposable);
|
package/lib/domForEach.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {replaceContent} from './domComputed';
|
|
1
|
+
import {DomContents, replaceContent} from './domComputed';
|
|
2
2
|
import {autoDisposeElem, domDispose} from './domDispose';
|
|
3
|
-
import {
|
|
3
|
+
import {frag} from './domImpl';
|
|
4
4
|
import {computedArray, MaybeObsArray, ObsArray} from './obsArray';
|
|
5
5
|
|
|
6
6
|
// Use the browser globals in a way that allows replacing them with mocks in tests.
|
|
@@ -18,19 +18,20 @@ import {G} from './browserGlobals';
|
|
|
18
18
|
* If the created nodes are removed from their parent externally, forEach() will cope with it, but
|
|
19
19
|
* will consider these elements as no longer owned, and will not run domDispose() on them.
|
|
20
20
|
*
|
|
21
|
-
* Note that itemCreateFunc()
|
|
22
|
-
*
|
|
21
|
+
* Note that itemCreateFunc() is called with an index as the second argument, but that index is
|
|
22
|
+
* only accurate at the time of the call, and will stop reflecting the true index if more items
|
|
23
|
+
* are inserted or removed before it.
|
|
23
24
|
*
|
|
24
25
|
* If you'd like to map the DOM node back to its source item, use dom.data() and dom.getData() in
|
|
25
26
|
* itemCreateFunc().
|
|
26
27
|
*/
|
|
27
|
-
export function forEach<T>(
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
28
|
+
export function forEach<T>(
|
|
29
|
+
obsArray: MaybeObsArray<T>,
|
|
30
|
+
itemCreateFunc: (item: T, index: number) => Node|null
|
|
31
|
+
): DomContents {
|
|
32
|
+
const markerPre = G.document.createComment('a');
|
|
33
|
+
const markerPost = G.document.createComment('b');
|
|
34
|
+
return [markerPre, markerPost, (elem: Node) => {
|
|
34
35
|
if (Array.isArray(obsArray)) {
|
|
35
36
|
replaceContent(markerPre, markerPost, obsArray.map(itemCreateFunc));
|
|
36
37
|
return;
|
|
@@ -72,5 +73,5 @@ export function forEach<T>(obsArray: MaybeObsArray<T>, itemCreateFunc: (item: T)
|
|
|
72
73
|
}
|
|
73
74
|
});
|
|
74
75
|
replaceContent(markerPre, markerPost, nodes.get());
|
|
75
|
-
};
|
|
76
|
+
}];
|
|
76
77
|
}
|
package/lib/domImpl.ts
CHANGED
|
@@ -17,6 +17,10 @@ import {G} from './browserGlobals';
|
|
|
17
17
|
export type DomMethod<T = Node> = (elem: T) => DomArg<T>|void;
|
|
18
18
|
export type DomElementMethod = DomMethod<HTMLElement>;
|
|
19
19
|
|
|
20
|
+
/**
|
|
21
|
+
* Object mapping attribute names to attribute values. When applied to a DOM element, null and
|
|
22
|
+
* undefined values are omitted, and booleans are either omitted or set to empty string.
|
|
23
|
+
*/
|
|
20
24
|
export interface IAttrObj {
|
|
21
25
|
[attrName: string]: string|boolean|null|undefined;
|
|
22
26
|
}
|
|
@@ -51,14 +55,14 @@ export type DomElementArg = DomArg<HTMLElement>;
|
|
|
51
55
|
* to add the ID 'foo', and zero or more .bar suffixes to add a CSS class 'bar'.
|
|
52
56
|
*
|
|
53
57
|
* NOTE that better typings are available when a tag is used directly, e.g.
|
|
54
|
-
* dom('input', {id: 'foo'}, (elem) => ...)
|
|
55
|
-
* dom('input#foo', (elem) => ...)
|
|
58
|
+
* `dom('input', {id: 'foo'}, (elem) => ...)` -- elem has type HTMLInputElement
|
|
59
|
+
* `dom('input#foo', (elem) => ...)` -- elem has type HTMLElement
|
|
56
60
|
*
|
|
57
61
|
* The rest of the arguments are optional and may be:
|
|
58
62
|
*
|
|
59
63
|
* Nodes - which become children of the created element;
|
|
60
64
|
* strings - which become text node children;
|
|
61
|
-
* objects - of the form {attr: val} to set additional attributes on the element;
|
|
65
|
+
* objects - of the form `{attr: val}` to set additional attributes on the element;
|
|
62
66
|
* Arrays - which are flattened with each item processed recursively;
|
|
63
67
|
* functions - which are called with elem as the argument, for a chance to modify the
|
|
64
68
|
* element as it's being created. Return values are processed recursively.
|
|
@@ -93,11 +97,11 @@ function _createElementSvg(tag: string): SVGElement {
|
|
|
93
97
|
/**
|
|
94
98
|
* Internal helper to parse tagString, create an element using createFunc with the given tag, and
|
|
95
99
|
* set its id and classes from the tagString.
|
|
96
|
-
* @param
|
|
100
|
+
* @param createFunc(tag) - Function that should create an element given a tag name.
|
|
97
101
|
* It is passed in to allow creating elements in different namespaces (e.g. plain HTML vs SVG).
|
|
98
|
-
* @param
|
|
102
|
+
* @param tagString - String of the form "tag#id.class1.class2" where id and classes are
|
|
99
103
|
* optional.
|
|
100
|
-
* @
|
|
104
|
+
* @returns {Element} The result of createFunc(), possibly with id and class attributes also set.
|
|
101
105
|
*/
|
|
102
106
|
function _createFromTagString<E extends Element>(createFunc: (tag: string) => E, tagString: string): E {
|
|
103
107
|
// We do careful hand-written parsing rather than use a regexp for speed. Using a regexp is
|
|
@@ -179,7 +183,26 @@ function _updateWithArg<T extends Node>(elem: T, arg: DomArg<T>): void {
|
|
|
179
183
|
}
|
|
180
184
|
|
|
181
185
|
/**
|
|
182
|
-
* Creates a DocumentFragment processing arguments the same way as the dom() function.
|
|
186
|
+
* Creates a `DocumentFragment`, processing arguments in the same way as the `dom()` function.
|
|
187
|
+
*
|
|
188
|
+
* It's rarely needed since an array of `dom()` arguments is treated the same as a
|
|
189
|
+
* `DocumentFragment` in most cases.
|
|
190
|
+
*
|
|
191
|
+
* @example
|
|
192
|
+
* ```ts
|
|
193
|
+
* dom.frag(dom('span', 'Hello'), ' good ', dom('div', 'world'))
|
|
194
|
+
* ```
|
|
195
|
+
* creates document fragment with `<span>Hello</span> good <div>world</div>`.
|
|
196
|
+
*
|
|
197
|
+
* @example
|
|
198
|
+
* These two examples are equivalent:
|
|
199
|
+
* ```ts
|
|
200
|
+
* const world1 = () => dom.frag(' good ', dom('div', 'world'));
|
|
201
|
+
* dom('div', 'Hello', world1);
|
|
202
|
+
*
|
|
203
|
+
* const world2 = () => [' good ', dom('div', 'world')];
|
|
204
|
+
* dom('div', 'Hello', world2);
|
|
205
|
+
* ```
|
|
183
206
|
*/
|
|
184
207
|
export function frag(...args: IDomArgs<DocumentFragment>): DocumentFragment {
|
|
185
208
|
const elem = G.document.createDocumentFragment();
|