-
Notifications
You must be signed in to change notification settings - Fork 343
/
elements.ts
124 lines (112 loc) · 4.44 KB
/
elements.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
/**
* @license
* Copyright 2020 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Base class for LIT modules and other reactive elements.
*/
// tslint:disable:no-new-decorators
import {MobxLitElement} from '@adobe/lit-mobx';
import {property} from 'lit/decorators.js';
import {IReactionDisposer, IReactionOptions, IReactionPublic, observable, reaction} from 'mobx';
type ReactionInputFn<T> = (r: IReactionPublic) => T;
// tslint:disable:no-any
type PendingReactionMap =
Map<(arg: any, r: IReactionPublic) => void, {arg: any, r: IReactionPublic}>;
// tslint:enable:no-any
/**
* Extension of MobxLitElement, with automatic reaction disposal.
*
* Prefer using this to MobxLitElement if your element has a finite lifetime,
* since ReactiveElement will automatically clean up reactions when your element
* is disconnected.
* TODO(lit-dev): set reactions to be activated in connectedCallback() ?
*/
export abstract class ReactiveElement extends MobxLitElement {
// TODO(b/204677206): remove this once we clean up property declarations.
__allowInstanceProperties = true; // tslint:disable-line
/**
* Indicates if the element should perform reactions. Set to 0 if the
* element is not visible to the user and any other number if it is visible.
* Not using a boolean here as web components cannot have boolean attributes
* that default to true if not specified due to how boolean attributes are
* passed to elements, given that we want this attribute to default to true
* for backwards compabitibility with elements that don't have this value set
* through an attribute.
*/
@observable @property({type: Number}) shouldReact = 1;
private readonly reactionDisposers: IReactionDisposer[] = [];
// Map of pending reactions that are stored when an element is in a state
// where it shouldn't react which are performed when the element is set to
// react again.
private readonly pendingReactions: PendingReactionMap = new Map();
constructor() {
super();
reaction(() => this.shouldReact, shouldReact => {
if (shouldReact === 0) {
return;
}
this.performPendingReactions();
});
}
override disconnectedCallback() {
super.disconnectedCallback();
this.reactionDisposers.forEach(disposer => {
disposer();
});
}
/**
* Performs all pending reactions and clears them from the map.
*/
private performPendingReactions() {
this.pendingReactions.forEach((value, effect) => {
effect.apply(window, [value.arg, value.r]);
});
this.pendingReactions.clear();
}
/**
* A simple wrapper method around mobx `reaction`. Note that this reaction
* is not called immediately (for that, use this.reactImmediately).
* Automatically sets up the reaction for disposal upon the component being
* disconnected.
*/
protected react<T>(
fn: ReactionInputFn<T>, effect: (arg: T, r: IReactionPublic) => void,
opts: IReactionOptions = {}) {
// Wrapper function to pass to the mobx reaction call. If the element is
// set to react, then the effect happens immediately. If not, the arguments
// for the effect are stored in the map, keyed by the effect, so that
// the latest reaction of each type is stored for later use.
const reactWrapper = (arg: T, r: IReactionPublic) => {
if (this.shouldReact !== 0) {
effect.apply(window, [arg, r]);
} else {
this.pendingReactions.set(effect, {arg, r});
}
};
const disposer = reaction(fn, reactWrapper, opts);
this.reactionDisposers.push(disposer);
}
/**
* A simple wrapper method around mobx `reaction`, with the `fireImmediately`
* option set to true. Automatically sets up the reaction for disposal upon
* the component being disconnected.
*/
protected reactImmediately<T>(
fn: ReactionInputFn<T>, effect: (arg: T, r: IReactionPublic) => void,
opts: IReactionOptions = {}) {
this.react(fn, effect, {...opts, fireImmediately: true});
}
}