UNPKG

ember-material-icons

Version:

Google Material icons for your ember-cli app

281 lines (228 loc) 10 kB
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';