/
context-provider.ts
80 lines (73 loc) · 2.48 KB
/
context-provider.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
/**
* @license
* Copyright 2021 Google LLC
* SPDX-License-Identifier: BSD-3-Clause
*/
import {ContextRequestEvent} from '../context-request-event.js';
import {ContextKey, ContextType} from '../context-key.js';
import {ValueNotifier} from '../value-notifier.js';
import {ReactiveController, ReactiveElement} from 'lit';
declare global {
interface HTMLElementEventMap {
/**
* A 'context-provider' event can be emitted by any element which hosts
* a context provider to indicate it is available for use.
*/
'context-provider': ContextProviderEvent<ContextKey<unknown, unknown>>;
}
}
export class ContextProviderEvent<
Context extends ContextKey<unknown, unknown>
> extends Event {
/**
*
* @param context the context which this provider can provide
*/
public constructor(public readonly context: Context) {
super('context-provider', {bubbles: true, composed: true});
}
}
/**
* A ReactiveController which can add context provider behavior to a
* custom-element.
*
* This controller simply listens to the `context-request` event when
* the host is connected to the DOM and registers the received callbacks
* against its observable Context implementation.
*/
export class ContextProvider<T extends ContextKey<unknown, unknown>>
extends ValueNotifier<ContextType<T>>
implements ReactiveController
{
constructor(
protected host: ReactiveElement,
private context: T,
initialValue?: ContextType<T>
) {
super(initialValue);
this.host.addController(this);
this.attachListeners();
}
public onContextRequest = (
ev: ContextRequestEvent<ContextKey<unknown, unknown>>
): void => {
// Only call the callback if the context matches.
// Also, in case an element is a consumer AND a provider
// of the same context, we want to avoid the element to self-register.
// The check on composedPath (as opposed to ev.target) is to cover cases
// where the consumer is in the shadowDom of the provider (in which case,
// event.target === this.host because of event retargeting).
if (ev.context !== this.context || ev.composedPath()[0] === this.host) {
return;
}
ev.stopPropagation();
this.addCallback(ev.callback, ev.subscribe);
};
private attachListeners() {
this.host.addEventListener('context-request', this.onContextRequest);
}
hostConnected(): void {
// emit an event to signal a provider is available for this context
this.host.dispatchEvent(new ContextProviderEvent(this.context));
}
}