incremental-dom
Version:
An in-place virtual DOM library
170 lines (152 loc) • 5.32 kB
text/typescript
/**
* Copyright 2018 The Incremental DOM Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS-IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { AttrMutatorConfig } from "./types";
import { assert } from "./assertions";
import { createMap, has } from "./util";
import { symbols } from "./symbols";
/**
* @param name The name of the attribute. For example "tabindex" or
* "xlink:href".
* @returns The namespace to use for the attribute, or null if there is
* no namespace.
*/
function getNamespace(name: string): string | null {
if (name.lastIndexOf("xml:", 0) === 0) {
return "http://www.w3.org/XML/1998/namespace";
}
if (name.lastIndexOf("xlink:", 0) === 0) {
return "http://www.w3.org/1999/xlink";
}
return null;
}
/**
* Applies an attribute or property to a given Element. If the value is null
* or undefined, it is removed from the Element. Otherwise, the value is set
* as an attribute.
* @param el The element to apply the attribute to.
* @param name The attribute's name.
* @param value The attribute's value.
*/
function applyAttr(el: Element, name: string, value: unknown) {
if (value == null) {
el.removeAttribute(name);
} else {
const attrNS = getNamespace(name);
if (attrNS) {
el.setAttributeNS(attrNS, name, String(value));
} else {
el.setAttribute(name, String(value));
}
}
}
/**
* Applies a property to a given Element.
* @param el The element to apply the property to.
* @param name The property's name.
* @param value The property's value.
*/
function applyProp(el: Element, name: string, value: unknown) {
(el as any)[name] = value;
}
/**
* Applies a value to a style declaration. Supports CSS custom properties by
* setting properties containing a dash using CSSStyleDeclaration.setProperty.
* @param style A style declaration.
* @param prop The property to apply. This can be either camelcase or dash
* separated. For example: "backgroundColor" and "background-color" are both
* supported.
* @param value The value of the property.
*/
function setStyleValue(
style: CSSStyleDeclaration,
prop: string,
value: string
) {
if (prop.indexOf("-") >= 0) {
style.setProperty(prop, value);
} else {
(style as any)[prop] = value;
}
}
/**
* Applies a style to an Element. No vendor prefix expansion is done for
* property names/values.
* @param el The Element to apply the style for.
* @param name The attribute's name.
* @param style The style to set. Either a string of css or an object
* containing property-value pairs.
*/
function applyStyle(
el: Element,
name: string,
style: string | { [k: string]: string }
) {
// MathML elements inherit from Element, which does not have style. We cannot
// do `instanceof HTMLElement` / `instanceof SVGElement`, since el can belong
// to a different document, so just check that it has a style.
assert("style" in el);
const elStyle = (<HTMLElement | SVGElement>el).style;
if (typeof style === "string") {
elStyle.cssText = style;
} else {
elStyle.cssText = "";
for (const prop in style) {
if (has(style, prop)) {
setStyleValue(elStyle, prop, style[prop]);
}
}
}
}
/**
* Updates a single attribute on an Element.
* @param el The Element to apply the attribute to.
* @param name The attribute's name.
* @param value The attribute's value. If the value is an object or
* function it is set on the Element, otherwise, it is set as an HTML
* attribute.
*/
function applyAttributeTyped(el: Element, name: string, value: unknown) {
const type = typeof value;
if (type === "object" || type === "function") {
applyProp(el, name, value);
} else {
applyAttr(el, name, value);
}
}
/**
* A publicly mutable object to provide custom mutators for attributes.
* NB: The result of createMap() has to be recast since closure compiler
* will just assume attributes is "any" otherwise and throws away
* the type annotation set by tsickle.
*/
const attributes: AttrMutatorConfig = createMap() as AttrMutatorConfig;
// Special generic mutator that's called for any attribute that does not
// have a specific mutator.
attributes[symbols.default] = applyAttributeTyped;
attributes["style"] = applyStyle;
/**
* Calls the appropriate attribute mutator for this attribute.
* @param el The Element to apply the attribute to.
* @param name The attribute's name.
* @param value The attribute's value. If the value is an object or
* function it is set on the Element, otherwise, it is set as an HTML
* attribute.
*/
function updateAttribute(el: Element, name: string, value: unknown) {
const mutator = attributes[name] || attributes[symbols.default];
mutator(el, name, value);
}
export { updateAttribute, applyProp, applyAttr, attributes };