media-show
Version:
Showcase your media player in style!
360 lines (323 loc) • 10.5 kB
JavaScript
import {
TemplateInstance,
NodeTemplatePart,
createProcessor,
AttributeTemplatePart,
} from '../node_modules/@github/template-parts/lib/index.js';
export function css(strings, ...values) {
return values.reduce((finalString, value, index) => {
return `${finalString}${value}${strings[index + 1]}`;
}, strings[0]);
}
// NOTE: These are either direct ports or significantly based off of github's jtml template part processing logic. For more, see: https://github.com/github/jtml
const eventListeners = new WeakMap();
class EventHandler {
constructor(element, type) {
this.element = element;
this.type = type;
this.element.addEventListener(this.type, this);
const elementMap = eventListeners.get(this.element);
if (elementMap) {
elementMap.set(this.type, this);
}
}
set(listener) {
if (typeof listener == 'function') {
this.handleEvent = listener.bind(this.element);
} else if (
typeof listener === 'object' &&
typeof listener.handleEvent === 'function'
) {
this.handleEvent = listener.handleEvent.bind(listener);
} else {
this.element.removeEventListener(this.type, this);
const elementMap = eventListeners.get(this.element);
if (elementMap) {
elementMap.delete(this.type);
}
}
}
static for(part) {
if (!eventListeners.has(part.element))
eventListeners.set(part.element, new Map());
const type = part.attributeName.slice(2);
const elementListeners = eventListeners.get(part.element);
if (elementListeners && elementListeners.has(type))
return elementListeners.get(type);
return new EventHandler(part.element, type);
}
}
export function processEvent(part, value) {
if (
part instanceof AttributeTemplatePart &&
part.attributeName.startsWith('on')
) {
EventHandler.for(part).set(value);
part.element.removeAttributeNS(part.attributeNamespace, part.attributeName);
return true;
}
return false;
}
function processSubTemplate(part, value) {
if (value instanceof TemplateResult && part instanceof NodeTemplatePart) {
value.renderInto(part);
return true;
}
return false;
}
function processDocumentFragment(part, value) {
if (value instanceof DocumentFragment && part instanceof NodeTemplatePart) {
if (value.childNodes.length) part.replace(...value.childNodes);
return true;
}
return false;
}
export function processPropertyIdentity(part, value) {
if (part instanceof AttributeTemplatePart) {
const ns = part.attributeNamespace;
const oldValue = part.element.getAttributeNS(ns, part.attributeName);
if (String(value) !== oldValue) {
part.value = String(value);
}
return true;
}
part.value = String(value);
return true;
}
export function processBooleanAttribute(part, value) {
if (
typeof value === 'boolean' &&
part instanceof AttributeTemplatePart
// can't use this because on custom elements the props are always undefined
// typeof part.element[part.attributeName as keyof Element] === 'boolean'
) {
const ns = part.attributeNamespace;
const oldValue = part.element.hasAttributeNS(ns, part.attributeName);
if (value !== oldValue) {
part.booleanValue = value;
}
return true;
}
return false;
}
export function processBooleanNode(part, value) {
if (value === false && part instanceof NodeTemplatePart) {
part.replace('');
return true;
}
return false;
}
function isIterable(value) {
return typeof value === 'object' && Symbol.iterator in value;
}
function processIterable(part, value) {
if (!isIterable(value)) return false;
if (part instanceof NodeTemplatePart) {
const nodes = [];
for (const item of value) {
if (item instanceof TemplateResult) {
const fragment = document.createDocumentFragment();
item.renderInto(fragment);
nodes.push(...fragment.childNodes);
} else if (item instanceof DocumentFragment) {
nodes.push(...item.childNodes);
} else {
nodes.push(String(item));
}
}
if (nodes.length) part.replace(...nodes);
return true;
} else {
part.value = Array.from(value).join(' ');
return true;
}
}
export function processFunction(part, value) {
if (typeof value === 'function') {
value(part);
return true;
}
return false;
}
export function processPart(part, value) {
processFunction(part, value) ||
processBooleanAttribute(part, value) ||
processEvent(part, value) ||
processBooleanNode(part, value) ||
processSubTemplate(part, value) ||
processDocumentFragment(part, value) ||
processIterable(part, value) ||
processPropertyIdentity(part, value);
}
const templates = new WeakMap();
const renderedTemplates = new WeakMap();
const renderedTemplateInstances = new WeakMap();
export class TemplateResult {
#template;
constructor(strings, values, processor) {
this.strings = strings;
this.values = values;
this.processor = processor;
}
set template(value) {
this.#template = value;
}
get template() {
if (this.#template) return this.#template;
if (templates.has(this.strings)) {
return templates.get(this.strings);
} else {
const template = document.createElement('template');
const end = this.strings.length - 1;
template.innerHTML = this.strings.reduce(
(str, cur, i) => str + cur + (i < end ? `{{ ${i} }}` : ''),
''
);
templates.set(this.strings, template);
return template;
}
}
renderInto(element) {
const template = this.template;
if (renderedTemplates.get(element) !== template) {
renderedTemplates.set(element, template);
const instance = new TemplateInstance(
template,
this.values,
this.processor
);
renderedTemplateInstances.set(element, instance);
if (element instanceof NodeTemplatePart) {
element.replace(...instance.children);
} else {
element.appendChild(instance);
}
return;
}
const templateInstance = renderedTemplateInstances.get(element);
if (templateInstance) {
templateInstance.update(this.values);
}
}
}
const defaultProcessor = createProcessor(processPart);
export function coreHtml(strings, ...values) {
return new TemplateResult(strings, values, defaultProcessor);
}
export function render(result, element) {
result.renderInto(element);
}
const templateResults = new Map();
export function templateInstance(templateNode, values, { key = '' }) {
key += templateNode.outerHTML;
if (templateResults.has(key)) {
const result = templateResults.get(key);
result.values = values;
return result;
}
const result = new TemplateResult([], values, defaultProcessor);
result.template = templateNode;
templateResults.set(key, result);
return result;
}
export const unsafeHTML = (value) => (part) => {
if (!(part instanceof NodeTemplatePart)) return;
const template = document.createElement('template');
template.innerHTML = value;
const fragment = document.importNode(template.content, true);
part.replace(...fragment.childNodes);
};
/**
* @license
* Copyright 2020 Google LLC
* SPDX-License-Identifier: BSD-3-Clause
*/
/**
* Prevents JSON injection attacks.
*
* The goals of this brand:
* 1) fast to check
* 2) code is small on the wire
* 3) multiple versions of Lit in a single page will all produce mutually
* interoperable StaticValues
* 4) normal JSON.parse (without an unusual reviver) can not produce a
* StaticValue
*
* Symbols satisfy (1), (2), and (4). We use Symbol.for to satisfy (3), but
* we don't care about the key, so we break ties via (2) and use the empty
* string.
*/
const brand = Symbol.for('');
/** Safely extracts the string part of a StaticValue. */
const unwrapStaticValue = (value) => {
if (value?.r !== brand) {
return undefined;
}
return value?.['_$litStatic$'];
};
/**
* Wraps a string so that it behaves like part of the static template
* strings instead of a dynamic value.
*
* Users must take care to ensure that adding the static string to the template
* results in well-formed HTML, or else templates may break unexpectedly.
*
* Note that this function is unsafe to use on untrusted content, as it will be
* directly parsed into HTML. Do not pass user input to this function
* without sanitizing it.
*
* Static values can be changed, but they will cause a complete re-render
* since they effectively create a new template.
*/
// edit Wesley Luyten, 05/02/2022, add support for joining template results.
export const unsafeStatic = (...values) => ({
['_$litStatic$']: values.map((value) => {
return value instanceof TemplateResult ? value : { strings: [value] };
}),
r: brand,
});
const stringsCache = new Map();
/**
* Wraps a lit-html template tag (`html` or `svg`) to add static value support.
*/
// edit Wesley Luyten, 05/02/2022, add support for joining template results.
export const withStatic =
(coreTag) =>
(strings, ...values) => {
const staticStrings = [''];
let dynamicValues = [];
let staticValues;
let hasStatics = false;
const join = (strs, vals = []) => {
staticStrings[staticStrings.length - 1] =
staticStrings[staticStrings.length - 1] + strs[0];
vals.forEach((dynamicValue, i) => {
if ((staticValues = unwrapStaticValue(dynamicValue)) !== undefined) {
staticValues.forEach((staticValue) => {
join(staticValue.strings, staticValue.values);
});
staticStrings[staticStrings.length - 1] =
staticStrings[staticStrings.length - 1] + strs[i + 1];
hasStatics = true;
} else {
dynamicValues.push(dynamicValue);
staticStrings.push(strs[i + 1]);
}
});
};
join(strings, values);
if (hasStatics) {
const key = staticStrings.join('$$lit$$');
strings = stringsCache.get(key);
if (strings === undefined) {
// Beware: in general this pattern is unsafe, and doing so may bypass
// lit's security checks and allow an attacker to execute arbitrary
// code and inject arbitrary content.
staticStrings.raw = staticStrings;
stringsCache.set(key, (strings = staticStrings));
}
values = dynamicValues;
}
return coreTag(strings, ...values);
};
export const html = withStatic(coreHtml);