94 lines
3.9 KiB
JavaScript
94 lines
3.9 KiB
JavaScript
/**
|
|
* @license
|
|
* Copyright 2021 Google LLC
|
|
* SPDX-License-Identifier: BSD-3-Clause
|
|
*/
|
|
import { ContextRequestEvent } from './context-request-event.js';
|
|
/**
|
|
* A ContextRoot can be used to gather unsatisfied context requests and
|
|
* re-dispatch them when new providers which satisfy matching context keys are
|
|
* available.
|
|
*
|
|
* This allows providers to be added to a DOM tree, or upgraded, after the
|
|
* consumers.
|
|
*/
|
|
export class ContextRoot {
|
|
constructor() {
|
|
this.pendingContextRequests = new Map();
|
|
this.onContextProvider = (event) => {
|
|
const pendingRequestData = this.pendingContextRequests.get(event.context);
|
|
if (pendingRequestData === undefined) {
|
|
// No pending requests for this context at this time
|
|
return;
|
|
}
|
|
// Clear our list. Any still unsatisfied requests will re-add themselves
|
|
// when we dispatch the events below.
|
|
this.pendingContextRequests.delete(event.context);
|
|
// Loop over all pending requests and re-dispatch them from their source
|
|
const { requests } = pendingRequestData;
|
|
for (const { elementRef, callbackRef } of requests) {
|
|
const element = elementRef.deref();
|
|
const callback = callbackRef.deref();
|
|
if (element === undefined || callback === undefined) {
|
|
// The element was GC'ed. Do nothing.
|
|
}
|
|
else {
|
|
// Re-dispatch if we still have the element and callback
|
|
element.dispatchEvent(new ContextRequestEvent(event.context, element, callback, true));
|
|
}
|
|
}
|
|
};
|
|
this.onContextRequest = (event) => {
|
|
// Events that are not subscribing should not be buffered
|
|
if (event.subscribe !== true) {
|
|
return;
|
|
}
|
|
// Note, it's important to use the initial target
|
|
// since that's the requesting element and the event may be re-targeted
|
|
// to an outer host element.
|
|
const element = (event.contextTarget ??
|
|
event.composedPath()[0]);
|
|
const callback = event.callback;
|
|
let pendingContextRequests = this.pendingContextRequests.get(event.context);
|
|
if (pendingContextRequests === undefined) {
|
|
this.pendingContextRequests.set(event.context, (pendingContextRequests = {
|
|
callbacks: new WeakMap(),
|
|
requests: [],
|
|
}));
|
|
}
|
|
let callbacks = pendingContextRequests.callbacks.get(element);
|
|
if (callbacks === undefined) {
|
|
pendingContextRequests.callbacks.set(element, (callbacks = new WeakSet()));
|
|
}
|
|
if (callbacks.has(callback)) {
|
|
// We're already tracking this element/callback pair
|
|
return;
|
|
}
|
|
callbacks.add(callback);
|
|
pendingContextRequests.requests.push({
|
|
elementRef: new WeakRef(element),
|
|
callbackRef: new WeakRef(callback),
|
|
});
|
|
};
|
|
}
|
|
/**
|
|
* Attach the ContextRoot to a given element to intercept `context-request` and
|
|
* `context-provider` events.
|
|
*
|
|
* @param element an element to add event listeners to
|
|
*/
|
|
attach(element) {
|
|
element.addEventListener('context-request', this.onContextRequest);
|
|
element.addEventListener('context-provider', this.onContextProvider);
|
|
}
|
|
/**
|
|
* Removes the ContextRoot event listeners from a given element.
|
|
*
|
|
* @param element an element from which to remove event listeners
|
|
*/
|
|
detach(element) {
|
|
element.removeEventListener('context-request', this.onContextRequest);
|
|
element.removeEventListener('context-provider', this.onContextProvider);
|
|
}
|
|
}
|
|
//# sourceMappingURL=context-root.js.map
|