element-vir
Version:
Heroic. Reactive. Declarative. Type safe. Web components without compromise.
114 lines (113 loc) • 4.26 kB
JavaScript
import { render as litRender } from 'lit-html';
import { AsyncDirective, clearPart, directive, getCommittedValue, insertPart, nothing, setCommittedValue, } from '../../lit-exports/all-lit-exports.js';
/**
* Implementation for {@link keyedCache}.
*
* @category Internal
*/
class KeyedCacheDirective extends AsyncDirective {
cache = new Map();
currentKey = undefined;
constructor(partInfo) {
super(partInfo);
}
/** Render the value. */
render(_key, value) {
/**
* Return an array of the value to induce lit-html to create a ChildPart for the value that
* we can move into the cache.
*/
return [value];
}
/** Update the template based on the cache key. */
update(containerPart, [key, value,]) {
if (key !== this.currentKey) {
/** Cache the current DOM if we have an active key. */
if (this.currentKey != undefined) {
const partValue = getCommittedValue(containerPart);
const childPart = partValue.pop();
const existingCachedPart = this.cache.get(this.currentKey);
if (existingCachedPart == undefined) {
const fragment = document.createDocumentFragment();
const cachedContainerPart = litRender(nothing, fragment);
cachedContainerPart.setConnected(false);
setCommittedValue(cachedContainerPart, [childPart]);
insertPart(cachedContainerPart, undefined, childPart);
this.cache.set(this.currentKey, cachedContainerPart);
}
else {
setCommittedValue(existingCachedPart, [childPart]);
insertPart(existingCachedPart, undefined, childPart);
}
}
/** Restore cached DOM for the new key if it exists. */
const cachedPart = this.cache.get(key);
if (cachedPart != undefined) {
const partValue = getCommittedValue(cachedPart);
const cachedChild = partValue.pop();
clearPart(containerPart);
insertPart(containerPart, undefined, cachedChild);
setCommittedValue(containerPart, [cachedChild]);
}
this.currentKey = key;
}
return this.render(key, value);
}
/** Handle disconnection. */
disconnected() {
this.clearCache();
}
/** Handle reconnection. */
reconnected() {
/**
* Nothing to restore — the active key's DOM is still live in the container part. Cached
* entries were cleared on disconnect, so they'll be rebuilt on future key switches.
*/
}
/** Clear the cache. */
clearCache() {
for (const cachedPart of this.cache.values()) {
cachedPart.setConnected(false);
}
this.cache.clear();
}
}
/**
* Caches rendered DOM by a user-provided key. When the key changes, the current DOM is stored and
* the previously cached DOM for the new key is restored (if any). This preserves DOM state (such as
* typed input values) across key switches.
*
* Unlike `cache`, which distinguishes templates by their structure, `keyedCache` distinguishes them
* by an arbitrary key, allowing multiple instances of the same template to maintain independent DOM
* state.
*
* Note: cached DOM is stored in a `Map` keyed by the provided key. Entries are not automatically
* garbage collected, so avoid using dynamically generated keys that are never reused.
*
* @category Directives
* @example
*
* ```ts
* import {html, defineElement, keyedCache} from 'element-vir';
*
* const MyElement = defineElement()({
* tagName: 'my-element',
* stateInit: {
* activeTab: 'tab-a',
* },
* render({state}) {
* return html`
* <div>
* ${keyedCache(
* state.activeTab,
* html`
* <input placeholder="Type here..." />
* `,
* )}
* </div>
* `;
* },
* });
* ```
*/
export const keyedCache = directive(KeyedCacheDirective);