UNPKG

chrome-devtools-frontend

Version:
118 lines (106 loc) 3.81 kB
// Copyright 2026 The Chromium Authors // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. /* eslint-disable @devtools/no-lit-render-outside-of-view */ import * as Lit from '../../third_party/lit/lit.js'; export interface RenderOptions extends Lit.RenderOptions { container?: { attributes?: Record<string, string|null|boolean|undefined|{toString(): string}>, classes?: string[], listeners?: Record<string, EventListenerOrEventListenerObject>, }; } const renderOptions = new WeakMap<HTMLElement|DocumentFragment, RenderOptions|undefined>(); interface ListenerEntry { listener: EventListenerOrEventListenerObject; wrapper: EventListener; } const containerListeners = new WeakMap<HTMLElement|DocumentFragment, Map<string, ListenerEntry>>(); export function render(template: unknown, container: HTMLElement|DocumentFragment, options?: RenderOptions): ReturnType<typeof Lit.render> { const host = container instanceof ShadowRoot ? container.host : container; if (host instanceof Element) { const oldAttributes = renderOptions.get(container)?.container?.attributes; const newAttributes = options?.container?.attributes; if (newAttributes) { for (const [name, value] of Object.entries(newAttributes)) { if (oldAttributes?.[name] === value) { continue; } if (value === null || value === undefined) { host.removeAttribute(name); } else if (typeof value === 'boolean') { host.toggleAttribute(name, value); } else { host.setAttribute(name, value.toString()); } } } if (oldAttributes) { for (const name of Object.keys(oldAttributes)) { if (!newAttributes || !(name in newAttributes)) { host.removeAttribute(name); } } } const oldClasses = renderOptions.get(container)?.container?.classes; const newClasses = options?.container?.classes; if (oldClasses) { for (const cls of oldClasses) { if (!newClasses?.includes(cls)) { host.classList.remove(cls); } } } if (newClasses) { for (const cls of newClasses) { if (!oldClasses?.includes(cls)) { host.classList.add(cls); } } } } let listenersMap = containerListeners.get(container); if (!listenersMap) { listenersMap = new Map(); containerListeners.set(container, listenersMap); } const newListeners = options?.container?.listeners; if (newListeners) { for (const [name, listener] of Object.entries(newListeners)) { const entry = listenersMap.get(name); if (entry) { entry.listener = listener; } else { let currentListener = listener; const newEntry: ListenerEntry = { get listener() { return currentListener; }, set listener(val: EventListenerOrEventListenerObject) { currentListener = val; }, wrapper: (event: Event) => { if (typeof currentListener === 'function') { return currentListener.call(host, event); } if (currentListener && 'handleEvent' in currentListener) { return currentListener.handleEvent(event); } } }; listenersMap.set(name, newEntry); host.addEventListener(name, newEntry.wrapper); } } } // Remove old listeners that are no longer present for (const [name, entry] of listenersMap.entries()) { if (!newListeners || !(name in newListeners)) { host.removeEventListener(name, entry.wrapper); listenersMap.delete(name); } } renderOptions.set(container, options); return Lit.render(template, container, options); }