ember-material-icons
Version:
Google Material icons for your ember-cli app
281 lines (228 loc) • 10 kB
text/typescript
import { ConcreteBounds, SingleNodeBounds, Bounds } from '../bounds';
import {
domChanges as domChangesTableElementFix,
treeConstruction as treeConstructionTableElementFix
} from '../compat/inner-html-fix';
import {
domChanges as domChangesSvgElementFix,
treeConstruction as treeConstructionSvgElementFix
} from '../compat/svg-inner-html-fix';
import {
domChanges as domChangesNodeMergingFix,
treeConstruction as treeConstructionNodeMergingFix
} from '../compat/text-node-merging-fix';
import * as Simple from './interfaces';
import { Option } from '@glimmer/util';
export const SVG_NAMESPACE = 'http://www.w3.org/2000/svg';
// http://www.w3.org/TR/html/syntax.html#html-integration-point
const SVG_INTEGRATION_POINTS = { foreignObject: 1, desc: 1, title: 1 };
// http://www.w3.org/TR/html/syntax.html#adjust-svg-attributes
// TODO: Adjust SVG attributes
// http://www.w3.org/TR/html/syntax.html#parsing-main-inforeign
// TODO: Adjust SVG elements
// http://www.w3.org/TR/html/syntax.html#parsing-main-inforeign
export const BLACKLIST_TABLE = Object.create(null);
([
"b", "big", "blockquote", "body", "br", "center", "code", "dd", "div", "dl", "dt", "em", "embed",
"h1", "h2", "h3", "h4", "h5", "h6", "head", "hr", "i", "img", "li", "listing", "main", "meta", "nobr",
"ol", "p", "pre", "ruby", "s", "small", "span", "strong", "strike", "sub", "sup", "table", "tt", "u",
"ul", "var"
]).forEach(tag => BLACKLIST_TABLE[tag] = 1);
const WHITESPACE = /[\t-\r \xA0\u1680\u180E\u2000-\u200A\u2028\u2029\u202F\u205F\u3000\uFEFF]/;
let doc: Option<Document> = typeof document === 'undefined' ? null : document;
export function isWhitespace(string: string) {
return WHITESPACE.test(string);
}
export function moveNodesBefore(source: Simple.Node, target: Simple.Element, nextSibling: Simple.Node) {
let first = source.firstChild;
let last = null;
let current = first;
while (current) {
last = current;
current = current.nextSibling;
target.insertBefore(last, nextSibling);
}
return [first, last];
}
export namespace DOM {
export type Node = Simple.Node;
export type Element = Simple.Element;
export type Document = Simple.Document;
export type Comment = Simple.Comment;
export type Text = Simple.Text;
export type Namespace = Simple.Namespace;
export type HTMLElement = Simple.HTMLElement;
export class TreeConstruction {
protected uselessElement: HTMLElement;
constructor(protected document: Document) {
this.setupUselessElement();
}
protected setupUselessElement() {
this.uselessElement = this.document.createElement('div');
}
createElement(tag: string, context?: Element): Element {
let isElementInSVGNamespace, isHTMLIntegrationPoint;
if (context) {
isElementInSVGNamespace = context.namespaceURI === SVG_NAMESPACE || tag === 'svg';
isHTMLIntegrationPoint = SVG_INTEGRATION_POINTS[context.tagName];
} else {
isElementInSVGNamespace = tag === 'svg';
isHTMLIntegrationPoint = false;
}
if (isElementInSVGNamespace && !isHTMLIntegrationPoint) {
// FIXME: This does not properly handle <font> with color, face, or
// size attributes, which is also disallowed by the spec. We should fix
// this.
if (BLACKLIST_TABLE[tag]) {
throw new Error(`Cannot create a ${tag} inside an SVG context`);
}
return this.document.createElementNS(SVG_NAMESPACE as Namespace, tag);
} else {
return this.document.createElement(tag);
}
}
createElementNS(namespace: Namespace, tag: string): Element {
return this.document.createElementNS(namespace, tag);
}
setAttribute(element: Element, name: string, value: string, namespace?: string) {
if (namespace) {
element.setAttributeNS(namespace, name, value);
} else {
element.setAttribute(name, value);
}
}
createTextNode(text: string): Text {
return this.document.createTextNode(text);
}
createComment(data: string): Comment {
return this.document.createComment(data);
}
insertBefore(parent: Element, node: Node, reference: Option<Node>) {
parent.insertBefore(node, reference);
}
insertHTMLBefore(parent: Element, html: string, reference: Option<Node>): Bounds {
return insertHTMLBefore(this.uselessElement, parent, reference, html);
};
}
let appliedTreeContruction = TreeConstruction;
appliedTreeContruction = treeConstructionNodeMergingFix(doc, appliedTreeContruction);
appliedTreeContruction = treeConstructionTableElementFix(doc, appliedTreeContruction);
appliedTreeContruction = treeConstructionSvgElementFix(doc, appliedTreeContruction, SVG_NAMESPACE);
export const DOMTreeConstruction = appliedTreeContruction;
export type DOMTreeConstruction = TreeConstruction;
}
export class DOMChanges {
protected namespace: Option<string>;
private uselessElement: HTMLElement;
constructor(protected document: HTMLDocument) {
this.namespace = null;
this.uselessElement = this.document.createElement('div');
}
setAttribute(element: Simple.Element, name: string, value: string) {
element.setAttribute(name, value);
}
setAttributeNS(element: Simple.Element, namespace: string, name: string, value: string) {
element.setAttributeNS(namespace, name, value);
}
removeAttribute(element: Simple.Element, name: string) {
element.removeAttribute(name);
}
removeAttributeNS(element: Simple.Element, namespace: string, name: string) {
element.removeAttributeNS(namespace, name);
}
createTextNode(text: string): Simple.Text {
return this.document.createTextNode(text);
}
createComment(data: string): Simple.Comment {
return this.document.createComment(data);
}
createElement(tag: string, context?: Simple.Element): Simple.Element {
let isElementInSVGNamespace, isHTMLIntegrationPoint;
if (context) {
isElementInSVGNamespace = context.namespaceURI === SVG_NAMESPACE || tag === 'svg';
isHTMLIntegrationPoint = SVG_INTEGRATION_POINTS[context.tagName];
} else {
isElementInSVGNamespace = tag === 'svg';
isHTMLIntegrationPoint = false;
}
if (isElementInSVGNamespace && !isHTMLIntegrationPoint) {
// FIXME: This does not properly handle <font> with color, face, or
// size attributes, which is also disallowed by the spec. We should fix
// this.
if (BLACKLIST_TABLE[tag]) {
throw new Error(`Cannot create a ${tag} inside an SVG context`);
}
return this.document.createElementNS(SVG_NAMESPACE as Simple.Namespace, tag);
} else {
return this.document.createElement(tag);
}
}
insertHTMLBefore(_parent: Element, nextSibling: Node, html: string): Bounds {
return insertHTMLBefore(this.uselessElement, _parent, nextSibling, html);
}
insertNodeBefore(parent: Simple.Element, node: Simple.Node, reference: Simple.Node): Bounds {
if (isDocumentFragment(node)) {
let { firstChild, lastChild } = node;
this.insertBefore(parent, node, reference);
return new ConcreteBounds(parent, firstChild, lastChild);
} else {
this.insertBefore(parent, node, reference);
return new SingleNodeBounds(parent, node);
}
}
insertTextBefore(parent: Simple.Element, nextSibling: Simple.Node, text: string): Simple.Text {
let textNode = this.createTextNode(text);
this.insertBefore(parent, textNode, nextSibling);
return textNode;
}
insertBefore(element: Simple.Element, node: Simple.Node, reference: Option<Simple.Node>) {
element.insertBefore(node, reference);
}
insertAfter(element: Simple.Element, node: Simple.Node, reference: Simple.Node) {
this.insertBefore(element, node, reference.nextSibling);
}
}
export function insertHTMLBefore(this: void, _useless: Simple.HTMLElement, _parent: Simple.Element, _nextSibling: Option<Simple.Node>, html: string): Bounds { // tslint:disable-line
// TypeScript vendored an old version of the DOM spec where `insertAdjacentHTML`
// only exists on `HTMLElement` but not on `Element`. We actually work with the
// newer version of the DOM API here (and monkey-patch this method in `./compat`
// when we detect older browsers). This is a hack to work around this limitation.
let parent = _parent as HTMLElement;
let useless = _useless as HTMLElement;
let nextSibling = _nextSibling as Node;
let prev = nextSibling ? nextSibling.previousSibling : parent.lastChild;
let last;
if (html === null || html === '') {
return new ConcreteBounds(parent, null, null);
}
if (nextSibling === null) {
parent.insertAdjacentHTML('beforeEnd', html);
last = parent.lastChild;
} else if (nextSibling instanceof HTMLElement) {
nextSibling.insertAdjacentHTML('beforeBegin', html);
last = nextSibling.previousSibling;
} else {
// Non-element nodes do not support insertAdjacentHTML, so add an
// element and call it on that element. Then remove the element.
//
// This also protects Edge, IE and Firefox w/o the inspector open
// from merging adjacent text nodes. See ./compat/text-node-merging-fix.ts
parent.insertBefore(useless, nextSibling);
useless.insertAdjacentHTML('beforeBegin', html);
last = useless.previousSibling;
parent.removeChild(useless);
}
let first = prev ? prev.nextSibling : parent.firstChild;
return new ConcreteBounds(parent, first, last);
}
function isDocumentFragment(node: Simple.Node): node is DocumentFragment {
return node.nodeType === Node.DOCUMENT_FRAGMENT_NODE;
}
let helper = DOMChanges;
helper = domChangesNodeMergingFix(doc, helper);
helper = domChangesTableElementFix(doc, helper);
helper = domChangesSvgElementFix(doc, helper, SVG_NAMESPACE);
export default helper;
export const DOMTreeConstruction = DOM.DOMTreeConstruction;
export type DOMTreeConstruction = DOM.DOMTreeConstruction;
export { Namespace as DOMNamespace } from './interfaces';