UNPKG

5.32 kBPlain TextView Raw
1/**
2 * Copyright 2018 The Incremental DOM Authors. All Rights Reserved.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS-IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17import { AttrMutatorConfig } from "./types";
18import { assert } from "./assertions";
19import { createMap, has } from "./util";
20import { symbols } from "./symbols";
21
22/**
23 * @param name The name of the attribute. For example "tabindex" or
24 * "xlink:href".
25 * @returns The namespace to use for the attribute, or null if there is
26 * no namespace.
27 */
28function getNamespace(name: string): string | null {
29 if (name.lastIndexOf("xml:", 0) === 0) {
30 return "http://www.w3.org/XML/1998/namespace";
31 }
32
33 if (name.lastIndexOf("xlink:", 0) === 0) {
34 return "http://www.w3.org/1999/xlink";
35 }
36
37 return null;
38}
39
40/**
41 * Applies an attribute or property to a given Element. If the value is null
42 * or undefined, it is removed from the Element. Otherwise, the value is set
43 * as an attribute.
44 * @param el The element to apply the attribute to.
45 * @param name The attribute's name.
46 * @param value The attribute's value.
47 */
48function applyAttr(el: Element, name: string, value: unknown) {
49 if (value == null) {
50 el.removeAttribute(name);
51 } else {
52 const attrNS = getNamespace(name);
53 if (attrNS) {
54 el.setAttributeNS(attrNS, name, String(value));
55 } else {
56 el.setAttribute(name, String(value));
57 }
58 }
59}
60
61/**
62 * Applies a property to a given Element.
63 * @param el The element to apply the property to.
64 * @param name The property's name.
65 * @param value The property's value.
66 */
67function applyProp(el: Element, name: string, value: unknown) {
68 (el as any)[name] = value;
69}
70
71/**
72 * Applies a value to a style declaration. Supports CSS custom properties by
73 * setting properties containing a dash using CSSStyleDeclaration.setProperty.
74 * @param style A style declaration.
75 * @param prop The property to apply. This can be either camelcase or dash
76 * separated. For example: "backgroundColor" and "background-color" are both
77 * supported.
78 * @param value The value of the property.
79 */
80function setStyleValue(
81 style: CSSStyleDeclaration,
82 prop: string,
83 value: string
84) {
85 if (prop.indexOf("-") >= 0) {
86 style.setProperty(prop, value);
87 } else {
88 (style as any)[prop] = value;
89 }
90}
91
92/**
93 * Applies a style to an Element. No vendor prefix expansion is done for
94 * property names/values.
95 * @param el The Element to apply the style for.
96 * @param name The attribute's name.
97 * @param style The style to set. Either a string of css or an object
98 * containing property-value pairs.
99 */
100function applyStyle(
101 el: Element,
102 name: string,
103 style: string | { [k: string]: string }
104) {
105 // MathML elements inherit from Element, which does not have style. We cannot
106 // do `instanceof HTMLElement` / `instanceof SVGElement`, since el can belong
107 // to a different document, so just check that it has a style.
108 assert("style" in el);
109 const elStyle = (<HTMLElement | SVGElement>el).style;
110
111 if (typeof style === "string") {
112 elStyle.cssText = style;
113 } else {
114 elStyle.cssText = "";
115
116 for (const prop in style) {
117 if (has(style, prop)) {
118 setStyleValue(elStyle, prop, style[prop]);
119 }
120 }
121 }
122}
123
124/**
125 * Updates a single attribute on an Element.
126 * @param el The Element to apply the attribute to.
127 * @param name The attribute's name.
128 * @param value The attribute's value. If the value is an object or
129 * function it is set on the Element, otherwise, it is set as an HTML
130 * attribute.
131 */
132function applyAttributeTyped(el: Element, name: string, value: unknown) {
133 const type = typeof value;
134
135 if (type === "object" || type === "function") {
136 applyProp(el, name, value);
137 } else {
138 applyAttr(el, name, value);
139 }
140}
141
142/**
143 * A publicly mutable object to provide custom mutators for attributes.
144 * NB: The result of createMap() has to be recast since closure compiler
145 * will just assume attributes is "any" otherwise and throws away
146 * the type annotation set by tsickle.
147 */
148const attributes: AttrMutatorConfig = createMap() as AttrMutatorConfig;
149
150// Special generic mutator that's called for any attribute that does not
151// have a specific mutator.
152attributes[symbols.default] = applyAttributeTyped;
153
154attributes["style"] = applyStyle;
155
156/**
157 * Calls the appropriate attribute mutator for this attribute.
158 * @param el The Element to apply the attribute to.
159 * @param name The attribute's name.
160 * @param value The attribute's value. If the value is an object or
161 * function it is set on the Element, otherwise, it is set as an HTML
162 * attribute.
163 */
164function updateAttribute(el: Element, name: string, value: unknown) {
165 const mutator = attributes[name] || attributes[symbols.default];
166 mutator(el, name, value);
167}
168
169export { updateAttribute, applyProp, applyAttr, attributes };