/** * @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