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.
Files changed (146) hide show
  1. package/README.md +23 -26
  2. package/dist/cjs/index.js +28 -17
  3. package/dist/cjs/index.js.map +1 -1
  4. package/dist/cjs/lib/PriorityQueue.d.ts +2 -2
  5. package/dist/cjs/lib/PriorityQueue.js +1 -0
  6. package/dist/cjs/lib/PriorityQueue.js.map +1 -1
  7. package/dist/cjs/lib/_computed_queue.js +4 -3
  8. package/dist/cjs/lib/_computed_queue.js.map +1 -1
  9. package/dist/cjs/lib/binding.d.ts +11 -4
  10. package/dist/cjs/lib/binding.js +6 -5
  11. package/dist/cjs/lib/binding.js.map +1 -1
  12. package/dist/cjs/lib/browserGlobals.d.ts +4 -1
  13. package/dist/cjs/lib/browserGlobals.js +2 -0
  14. package/dist/cjs/lib/browserGlobals.js.map +1 -1
  15. package/dist/cjs/lib/computed.d.ts +49 -28
  16. package/dist/cjs/lib/computed.js +38 -51
  17. package/dist/cjs/lib/computed.js.map +1 -1
  18. package/dist/cjs/lib/dispose.d.ts +109 -96
  19. package/dist/cjs/lib/dispose.js +106 -79
  20. package/dist/cjs/lib/dispose.js.map +1 -1
  21. package/dist/cjs/lib/dom.d.ts +40 -18
  22. package/dist/cjs/lib/dom.js +63 -29
  23. package/dist/cjs/lib/dom.js.map +1 -1
  24. package/dist/cjs/lib/domComponent.d.ts +56 -51
  25. package/dist/cjs/lib/domComponent.js +46 -44
  26. package/dist/cjs/lib/domComponent.js.map +1 -1
  27. package/dist/cjs/lib/domComputed.d.ts +50 -20
  28. package/dist/cjs/lib/domComputed.js +37 -7
  29. package/dist/cjs/lib/domComputed.js.map +1 -1
  30. package/dist/cjs/lib/domDispose.d.ts +27 -12
  31. package/dist/cjs/lib/domDispose.js +27 -11
  32. package/dist/cjs/lib/domDispose.js.map +1 -1
  33. package/dist/cjs/lib/domForEach.d.ts +5 -4
  34. package/dist/cjs/lib/domForEach.js +41 -41
  35. package/dist/cjs/lib/domForEach.js.map +1 -1
  36. package/dist/cjs/lib/domImpl.d.ts +33 -10
  37. package/dist/cjs/lib/domImpl.js +29 -9
  38. package/dist/cjs/lib/domImpl.js.map +1 -1
  39. package/dist/cjs/lib/domMethods.d.ts +93 -47
  40. package/dist/cjs/lib/domMethods.js +91 -47
  41. package/dist/cjs/lib/domMethods.js.map +1 -1
  42. package/dist/cjs/lib/domevent.d.ts +87 -62
  43. package/dist/cjs/lib/domevent.js +85 -59
  44. package/dist/cjs/lib/domevent.js.map +1 -1
  45. package/dist/cjs/lib/emit.d.ts +62 -32
  46. package/dist/cjs/lib/emit.js +68 -53
  47. package/dist/cjs/lib/emit.js.map +1 -1
  48. package/dist/cjs/lib/kowrap.d.ts +6 -3
  49. package/dist/cjs/lib/kowrap.js +7 -3
  50. package/dist/cjs/lib/kowrap.js.map +1 -1
  51. package/dist/cjs/lib/obsArray.d.ts +91 -53
  52. package/dist/cjs/lib/obsArray.js +87 -54
  53. package/dist/cjs/lib/obsArray.js.map +1 -1
  54. package/dist/cjs/lib/observable.d.ts +25 -15
  55. package/dist/cjs/lib/observable.js +31 -19
  56. package/dist/cjs/lib/observable.js.map +1 -1
  57. package/dist/cjs/lib/pureComputed.d.ts +12 -15
  58. package/dist/cjs/lib/pureComputed.js +16 -18
  59. package/dist/cjs/lib/pureComputed.js.map +1 -1
  60. package/dist/cjs/lib/styled.d.ts +78 -61
  61. package/dist/cjs/lib/styled.js +27 -79
  62. package/dist/cjs/lib/styled.js.map +1 -1
  63. package/dist/cjs/lib/subscribe.d.ts +41 -37
  64. package/dist/cjs/lib/subscribe.js +31 -39
  65. package/dist/cjs/lib/subscribe.js.map +1 -1
  66. package/dist/cjs/lib/util.js +2 -0
  67. package/dist/cjs/lib/util.js.map +1 -1
  68. package/dist/cjs/lib/widgets/input.d.ts +3 -1
  69. package/dist/cjs/lib/widgets/input.js +7 -4
  70. package/dist/cjs/lib/widgets/input.js.map +1 -1
  71. package/dist/cjs/lib/widgets/select.d.ts +4 -2
  72. package/dist/cjs/lib/widgets/select.js +8 -5
  73. package/dist/cjs/lib/widgets/select.js.map +1 -1
  74. package/dist/esm/lib/_computed_queue.js +3 -3
  75. package/dist/esm/lib/_computed_queue.js.map +1 -1
  76. package/dist/esm/lib/binding.js +2 -2
  77. package/dist/esm/lib/binding.js.map +1 -1
  78. package/dist/esm/lib/browserGlobals.js +1 -0
  79. package/dist/esm/lib/browserGlobals.js.map +1 -1
  80. package/dist/esm/lib/computed.js +36 -50
  81. package/dist/esm/lib/computed.js.map +1 -1
  82. package/dist/esm/lib/dispose.js +104 -78
  83. package/dist/esm/lib/dispose.js.map +1 -1
  84. package/dist/esm/lib/dom.js +40 -18
  85. package/dist/esm/lib/dom.js.map +1 -1
  86. package/dist/esm/lib/domComponent.js +45 -44
  87. package/dist/esm/lib/domComponent.js.map +1 -1
  88. package/dist/esm/lib/domComputed.js +32 -5
  89. package/dist/esm/lib/domComputed.js.map +1 -1
  90. package/dist/esm/lib/domDispose.js +26 -11
  91. package/dist/esm/lib/domDispose.js.map +1 -1
  92. package/dist/esm/lib/domForEach.js +40 -41
  93. package/dist/esm/lib/domForEach.js.map +1 -1
  94. package/dist/esm/lib/domImpl.js +26 -7
  95. package/dist/esm/lib/domImpl.js.map +1 -1
  96. package/dist/esm/lib/domMethods.js +77 -35
  97. package/dist/esm/lib/domMethods.js.map +1 -1
  98. package/dist/esm/lib/domevent.js +84 -59
  99. package/dist/esm/lib/domevent.js.map +1 -1
  100. package/dist/esm/lib/emit.js +67 -53
  101. package/dist/esm/lib/emit.js.map +1 -1
  102. package/dist/esm/lib/kowrap.js +5 -2
  103. package/dist/esm/lib/kowrap.js.map +1 -1
  104. package/dist/esm/lib/obsArray.js +82 -50
  105. package/dist/esm/lib/obsArray.js.map +1 -1
  106. package/dist/esm/lib/observable.js +26 -15
  107. package/dist/esm/lib/observable.js.map +1 -1
  108. package/dist/esm/lib/pureComputed.js +15 -18
  109. package/dist/esm/lib/pureComputed.js.map +1 -1
  110. package/dist/esm/lib/styled.js +24 -77
  111. package/dist/esm/lib/styled.js.map +1 -1
  112. package/dist/esm/lib/subscribe.js +27 -36
  113. package/dist/esm/lib/subscribe.js.map +1 -1
  114. package/dist/esm/lib/util.js +1 -0
  115. package/dist/esm/lib/util.js.map +1 -1
  116. package/dist/esm/lib/widgets/input.js +3 -1
  117. package/dist/esm/lib/widgets/input.js.map +1 -1
  118. package/dist/esm/lib/widgets/select.js +3 -1
  119. package/dist/esm/lib/widgets/select.js.map +1 -1
  120. package/dist/grain-full.debug.js +2138 -3052
  121. package/dist/grain-full.debug.js.map +7 -0
  122. package/dist/grain-full.min.js +6 -2
  123. package/dist/grain-full.min.js.map +7 -1
  124. package/lib/binding.ts +9 -2
  125. package/lib/browserGlobals.ts +3 -1
  126. package/lib/computed.ts +56 -56
  127. package/lib/dispose.ts +110 -85
  128. package/lib/dom.ts +41 -20
  129. package/lib/domComponent.ts +68 -70
  130. package/lib/domComputed.ts +66 -21
  131. package/lib/domDispose.ts +28 -11
  132. package/lib/domForEach.ts +13 -12
  133. package/lib/domImpl.ts +30 -7
  134. package/lib/domMethods.ts +101 -46
  135. package/lib/domevent.ts +86 -61
  136. package/lib/emit.ts +64 -50
  137. package/lib/kowrap.ts +5 -2
  138. package/lib/obsArray.ts +89 -54
  139. package/lib/observable.ts +26 -15
  140. package/lib/pureComputed.ts +16 -22
  141. package/lib/styled.ts +85 -71
  142. package/lib/subscribe.ts +41 -45
  143. package/lib/util.ts +1 -0
  144. package/lib/widgets/input.ts +3 -1
  145. package/lib/widgets/select.ts +3 -1
  146. package/package.json +48 -38
@@ -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 {domComputed, DomComputed} from './domComputed';
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
- const [markerPre, markerPost, func] = domComputed(null, () => {
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
  }
@@ -1,5 +1,6 @@
1
1
  import {BindableValue, subscribeElem} from './binding';
2
- import {domDispose} from './domDispose';
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: DomArg): void {
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
- * (A) domComputed(nlinesObs, nlines => nlines > 1 ? dom('textarea') : dom('input'));
43
- * (B) domComputed(use => use(nlinesObs) > 1 ? dom('textarea') : dom('input'));
44
- * (C) domComputed(use => use(nlinesObs) > 1, isTall => isTall ? dom('textarea') : dom('input'));
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
- * domComputed(use => use(readonlyObs) ? dom('div') :
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
- * dom(..., domComputed(listValue, list => `Have ${list.length} items`), ...)
59
- * dom(..., `Have ${listValue.length} items`, ...)
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: Observable or function for a computed.
64
- * @param contentFunc: Function called with the result of valueObs as the input, and
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) => DomArg): DomComputed;
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) => DomArg = identity as any,
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: Observable or function for a computed.
107
- * @param contentFunc: Called with the result of boolValueObs when it is truthy. Should return DOM.
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>, contentFunc: (val: NonNullable<T>) => DomArg): DomComputed {
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 {Node} node: The element to run disposers on.
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 disposerFunc with a DOM element. It will be called when the element is disposed
79
- * using domDispose() on it or any of its parents. If onDispose is called multiple times, all
80
- * disposerFuncs will be called in reverse order.
81
- * @param {Element} elem: The element to associate the disposer with.
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 {Element} elem: The element to own the disposable.
102
- * @param {Disposable} disposable: Anything with a .dispose() method.
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 {DomMethod, frag} from './domImpl';
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() does not receive an index: an index would only be correct at the
22
- * time the item is created, and would not reflect further changes to the array.
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>(obsArray: MaybeObsArray<T>, itemCreateFunc: (item: T) => Node|null): DomMethod {
28
- return (elem: Node) => {
29
- const markerPre = G.document.createComment('a');
30
- const markerPost = G.document.createComment('b');
31
- elem.appendChild(markerPre);
32
- elem.appendChild(markerPost);
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) => ...) --> elem has type HTMLInputElement
55
- * dom('input#foo', (elem) => ...) --> elem has type HTMLElement
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 {Funtion} createFunc(tag): Function that should create an element given a tag name.
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 {String} tagString: String of the form "tag#id.class1.class2" where id and classes are
102
+ * @param tagString - String of the form "tag#id.class1.class2" where id and classes are
99
103
  * optional.
100
- * @return {Element} The result of createFunc(), possibly with id and class attributes also set.
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();