refactor: move angular source to /packages rather than modules/@angular
This commit is contained in:
54
packages/platform-browser/src/dom/debug/by.ts
Normal file
54
packages/platform-browser/src/dom/debug/by.ts
Normal file
@ -0,0 +1,54 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {DebugElement, Predicate, Type} from '@angular/core';
|
||||
import {getDOM} from '../../dom/dom_adapter';
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Predicates for use with {@link DebugElement}'s query functions.
|
||||
*
|
||||
* @experimental All debugging apis are currently experimental.
|
||||
*/
|
||||
export class By {
|
||||
/**
|
||||
* Match all elements.
|
||||
*
|
||||
* ## Example
|
||||
*
|
||||
* {@example platform-browser/dom/debug/ts/by/by.ts region='by_all'}
|
||||
*/
|
||||
static all(): Predicate<DebugElement> { return (debugElement) => true; }
|
||||
|
||||
/**
|
||||
* Match elements by the given CSS selector.
|
||||
*
|
||||
* ## Example
|
||||
*
|
||||
* {@example platform-browser/dom/debug/ts/by/by.ts region='by_css'}
|
||||
*/
|
||||
static css(selector: string): Predicate<DebugElement> {
|
||||
return (debugElement) => {
|
||||
return debugElement.nativeElement != null ?
|
||||
getDOM().elementMatches(debugElement.nativeElement, selector) :
|
||||
false;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Match elements that have the given directive present.
|
||||
*
|
||||
* ## Example
|
||||
*
|
||||
* {@example platform-browser/dom/debug/ts/by/by.ts region='by_directive'}
|
||||
*/
|
||||
static directive(type: Type<any>): Predicate<DebugElement> {
|
||||
return (debugElement) => debugElement.providerTokens.indexOf(type) !== -1;
|
||||
}
|
||||
}
|
62
packages/platform-browser/src/dom/debug/ng_probe.ts
Normal file
62
packages/platform-browser/src/dom/debug/ng_probe.ts
Normal file
@ -0,0 +1,62 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import * as core from '@angular/core';
|
||||
import {getDOM} from '../dom_adapter';
|
||||
|
||||
const CORE_TOKENS = {
|
||||
'ApplicationRef': core.ApplicationRef,
|
||||
'NgZone': core.NgZone,
|
||||
};
|
||||
|
||||
const INSPECT_GLOBAL_NAME = 'ng.probe';
|
||||
const CORE_TOKENS_GLOBAL_NAME = 'ng.coreTokens';
|
||||
|
||||
/**
|
||||
* Returns a {@link DebugElement} for the given native DOM element, or
|
||||
* null if the given native element does not have an Angular view associated
|
||||
* with it.
|
||||
*/
|
||||
export function inspectNativeElement(element: any): core.DebugNode {
|
||||
return core.getDebugNode(element);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deprecated. Use the one from '@angular/core'.
|
||||
* @deprecated
|
||||
*/
|
||||
export class NgProbeToken {
|
||||
constructor(public name: string, public token: any) {}
|
||||
}
|
||||
|
||||
export function _createNgProbe(extraTokens: NgProbeToken[], coreTokens: core.NgProbeToken[]): any {
|
||||
const tokens = (extraTokens || []).concat(coreTokens || []);
|
||||
getDOM().setGlobalVar(INSPECT_GLOBAL_NAME, inspectNativeElement);
|
||||
getDOM().setGlobalVar(
|
||||
CORE_TOKENS_GLOBAL_NAME, core.ɵmerge(CORE_TOKENS, _ngProbeTokensToMap(tokens || [])));
|
||||
return () => inspectNativeElement;
|
||||
}
|
||||
|
||||
function _ngProbeTokensToMap(tokens: NgProbeToken[]): {[name: string]: any} {
|
||||
return tokens.reduce((prev: any, t: any) => (prev[t.name] = t.token, prev), {});
|
||||
}
|
||||
|
||||
/**
|
||||
* Providers which support debugging Angular applications (e.g. via `ng.probe`).
|
||||
*/
|
||||
export const ELEMENT_PROBE_PROVIDERS: core.Provider[] = [
|
||||
{
|
||||
provide: core.APP_INITIALIZER,
|
||||
useFactory: _createNgProbe,
|
||||
deps: [
|
||||
[NgProbeToken, new core.Optional()],
|
||||
[core.NgProbeToken, new core.Optional()],
|
||||
],
|
||||
multi: true,
|
||||
},
|
||||
];
|
163
packages/platform-browser/src/dom/dom_adapter.ts
Normal file
163
packages/platform-browser/src/dom/dom_adapter.ts
Normal file
@ -0,0 +1,163 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {Type} from '@angular/core';
|
||||
|
||||
let _DOM: DomAdapter = null;
|
||||
|
||||
export function getDOM() {
|
||||
return _DOM;
|
||||
}
|
||||
|
||||
export function setDOM(adapter: DomAdapter) {
|
||||
_DOM = adapter;
|
||||
}
|
||||
|
||||
export function setRootDomAdapter(adapter: DomAdapter) {
|
||||
if (!_DOM) {
|
||||
_DOM = adapter;
|
||||
}
|
||||
}
|
||||
|
||||
/* tslint:disable:requireParameterType */
|
||||
/**
|
||||
* Provides DOM operations in an environment-agnostic way.
|
||||
*
|
||||
* @security Tread carefully! Interacting with the DOM directly is dangerous and
|
||||
* can introduce XSS risks.
|
||||
*/
|
||||
export abstract class DomAdapter {
|
||||
public resourceLoaderType: Type<any> = null;
|
||||
abstract hasProperty(element: any, name: string): boolean;
|
||||
abstract setProperty(el: Element, name: string, value: any): any;
|
||||
abstract getProperty(el: Element, name: string): any;
|
||||
abstract invoke(el: Element, methodName: string, args: any[]): any;
|
||||
|
||||
abstract logError(error: any): any;
|
||||
abstract log(error: any): any;
|
||||
abstract logGroup(error: any): any;
|
||||
abstract logGroupEnd(): any;
|
||||
|
||||
/**
|
||||
* Maps attribute names to their corresponding property names for cases
|
||||
* where attribute name doesn't match property name.
|
||||
*/
|
||||
get attrToPropMap(): {[key: string]: string} { return this._attrToPropMap; };
|
||||
set attrToPropMap(value: {[key: string]: string}) { this._attrToPropMap = value; };
|
||||
/** @internal */
|
||||
_attrToPropMap: {[key: string]: string};
|
||||
|
||||
abstract parse(templateHtml: string): any;
|
||||
abstract querySelector(el: any, selector: string): any;
|
||||
abstract querySelectorAll(el: any, selector: string): any[];
|
||||
abstract on(el: any, evt: any, listener: any): any;
|
||||
abstract onAndCancel(el: any, evt: any, listener: any): Function;
|
||||
abstract dispatchEvent(el: any, evt: any): any;
|
||||
abstract createMouseEvent(eventType: any): any;
|
||||
abstract createEvent(eventType: string): any;
|
||||
abstract preventDefault(evt: any): any;
|
||||
abstract isPrevented(evt: any): boolean;
|
||||
abstract getInnerHTML(el: any): string;
|
||||
/** Returns content if el is a <template> element, null otherwise. */
|
||||
abstract getTemplateContent(el: any): any;
|
||||
abstract getOuterHTML(el: any): string;
|
||||
abstract nodeName(node: any): string;
|
||||
abstract nodeValue(node: any): string;
|
||||
abstract type(node: any): string;
|
||||
abstract content(node: any): any;
|
||||
abstract firstChild(el: any): Node;
|
||||
abstract nextSibling(el: any): Node;
|
||||
abstract parentElement(el: any): Node;
|
||||
abstract childNodes(el: any): Node[];
|
||||
abstract childNodesAsList(el: any): Node[];
|
||||
abstract clearNodes(el: any): any;
|
||||
abstract appendChild(el: any, node: any): any;
|
||||
abstract removeChild(el: any, node: any): any;
|
||||
abstract replaceChild(el: any, newNode: any, oldNode: any): any;
|
||||
abstract remove(el: any): Node;
|
||||
abstract insertBefore(parent: any, ref: any, node: any): any;
|
||||
abstract insertAllBefore(parent: any, ref: any, nodes: any): any;
|
||||
abstract insertAfter(parent: any, el: any, node: any): any;
|
||||
abstract setInnerHTML(el: any, value: any): any;
|
||||
abstract getText(el: any): string;
|
||||
abstract setText(el: any, value: string): any;
|
||||
abstract getValue(el: any): string;
|
||||
abstract setValue(el: any, value: string): any;
|
||||
abstract getChecked(el: any): boolean;
|
||||
abstract setChecked(el: any, value: boolean): any;
|
||||
abstract createComment(text: string): any;
|
||||
abstract createTemplate(html: any): HTMLElement;
|
||||
abstract createElement(tagName: any, doc?: any): HTMLElement;
|
||||
abstract createElementNS(ns: string, tagName: string, doc?: any): Element;
|
||||
abstract createTextNode(text: string, doc?: any): Text;
|
||||
abstract createScriptTag(attrName: string, attrValue: string, doc?: any): HTMLElement;
|
||||
abstract createStyleElement(css: string, doc?: any): HTMLStyleElement;
|
||||
abstract createShadowRoot(el: any): any;
|
||||
abstract getShadowRoot(el: any): any;
|
||||
abstract getHost(el: any): any;
|
||||
abstract getDistributedNodes(el: any): Node[];
|
||||
abstract clone /*<T extends Node>*/ (node: Node /*T*/): Node /*T*/;
|
||||
abstract getElementsByClassName(element: any, name: string): HTMLElement[];
|
||||
abstract getElementsByTagName(element: any, name: string): HTMLElement[];
|
||||
abstract classList(element: any): any[];
|
||||
abstract addClass(element: any, className: string): any;
|
||||
abstract removeClass(element: any, className: string): any;
|
||||
abstract hasClass(element: any, className: string): boolean;
|
||||
abstract setStyle(element: any, styleName: string, styleValue: string): any;
|
||||
abstract removeStyle(element: any, styleName: string): any;
|
||||
abstract getStyle(element: any, styleName: string): string;
|
||||
abstract hasStyle(element: any, styleName: string, styleValue?: string): boolean;
|
||||
abstract tagName(element: any): string;
|
||||
abstract attributeMap(element: any): Map<string, string>;
|
||||
abstract hasAttribute(element: any, attribute: string): boolean;
|
||||
abstract hasAttributeNS(element: any, ns: string, attribute: string): boolean;
|
||||
abstract getAttribute(element: any, attribute: string): string;
|
||||
abstract getAttributeNS(element: any, ns: string, attribute: string): string;
|
||||
abstract setAttribute(element: any, name: string, value: string): any;
|
||||
abstract setAttributeNS(element: any, ns: string, name: string, value: string): any;
|
||||
abstract removeAttribute(element: any, attribute: string): any;
|
||||
abstract removeAttributeNS(element: any, ns: string, attribute: string): any;
|
||||
abstract templateAwareRoot(el: any): any;
|
||||
abstract createHtmlDocument(): HTMLDocument;
|
||||
abstract getBoundingClientRect(el: any): any;
|
||||
abstract getTitle(doc: Document): string;
|
||||
abstract setTitle(doc: Document, newTitle: string): any;
|
||||
abstract elementMatches(n: any, selector: string): boolean;
|
||||
abstract isTemplateElement(el: any): boolean;
|
||||
abstract isTextNode(node: any): boolean;
|
||||
abstract isCommentNode(node: any): boolean;
|
||||
abstract isElementNode(node: any): boolean;
|
||||
abstract hasShadowRoot(node: any): boolean;
|
||||
abstract isShadowRoot(node: any): boolean;
|
||||
abstract importIntoDoc /*<T extends Node>*/ (node: Node /*T*/): Node /*T*/;
|
||||
abstract adoptNode /*<T extends Node>*/ (node: Node /*T*/): Node /*T*/;
|
||||
abstract getHref(element: any): string;
|
||||
abstract getEventKey(event: any): string;
|
||||
abstract resolveAndSetHref(element: any, baseUrl: string, href: string): any;
|
||||
abstract supportsDOMEvents(): boolean;
|
||||
abstract supportsNativeShadowDOM(): boolean;
|
||||
abstract getGlobalEventTarget(doc: Document, target: string): any;
|
||||
abstract getHistory(): History;
|
||||
abstract getLocation(): Location;
|
||||
abstract getBaseHref(doc: Document): string;
|
||||
abstract resetBaseElement(): void;
|
||||
abstract getUserAgent(): string;
|
||||
abstract setData(element: any, name: string, value: string): any;
|
||||
abstract getComputedStyle(element: any): any;
|
||||
abstract getData(element: any, name: string): string;
|
||||
abstract setGlobalVar(name: string, value: any): any;
|
||||
abstract supportsWebAnimation(): boolean;
|
||||
abstract performanceNow(): number;
|
||||
abstract getAnimationPrefix(): string;
|
||||
abstract getTransitionEnd(): string;
|
||||
abstract supportsAnimation(): boolean;
|
||||
|
||||
abstract supportsCookies(): boolean;
|
||||
abstract getCookie(name: string): string;
|
||||
abstract setCookie(name: string, value: string): any;
|
||||
}
|
270
packages/platform-browser/src/dom/dom_renderer.ts
Normal file
270
packages/platform-browser/src/dom/dom_renderer.ts
Normal file
@ -0,0 +1,270 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {APP_ID, Inject, Injectable, RenderComponentType, Renderer, Renderer2, RendererFactory2, RendererType2, RootRenderer, ViewEncapsulation} from '@angular/core';
|
||||
|
||||
import {EventManager} from './events/event_manager';
|
||||
import {DomSharedStylesHost} from './shared_styles_host';
|
||||
|
||||
export const NAMESPACE_URIS: {[ns: string]: string} = {
|
||||
'xlink': 'http://www.w3.org/1999/xlink',
|
||||
'svg': 'http://www.w3.org/2000/svg',
|
||||
'xhtml': 'http://www.w3.org/1999/xhtml',
|
||||
'xml': 'http://www.w3.org/XML/1998/namespace'
|
||||
};
|
||||
|
||||
const COMPONENT_REGEX = /%COMP%/g;
|
||||
export const COMPONENT_VARIABLE = '%COMP%';
|
||||
export const HOST_ATTR = `_nghost-${COMPONENT_VARIABLE}`;
|
||||
export const CONTENT_ATTR = `_ngcontent-${COMPONENT_VARIABLE}`;
|
||||
|
||||
export function shimContentAttribute(componentShortId: string): string {
|
||||
return CONTENT_ATTR.replace(COMPONENT_REGEX, componentShortId);
|
||||
}
|
||||
|
||||
export function shimHostAttribute(componentShortId: string): string {
|
||||
return HOST_ATTR.replace(COMPONENT_REGEX, componentShortId);
|
||||
}
|
||||
|
||||
export function flattenStyles(
|
||||
compId: string, styles: Array<any|any[]>, target: string[]): string[] {
|
||||
for (let i = 0; i < styles.length; i++) {
|
||||
let style = styles[i];
|
||||
|
||||
if (Array.isArray(style)) {
|
||||
flattenStyles(compId, style, target);
|
||||
} else {
|
||||
style = style.replace(COMPONENT_REGEX, compId);
|
||||
target.push(style);
|
||||
}
|
||||
}
|
||||
return target;
|
||||
}
|
||||
|
||||
function decoratePreventDefault(eventHandler: Function): Function {
|
||||
return (event: any) => {
|
||||
const allowDefaultBehavior = eventHandler(event);
|
||||
if (allowDefaultBehavior === false) {
|
||||
// TODO(tbosch): move preventDefault into event plugins...
|
||||
event.preventDefault();
|
||||
event.returnValue = false;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class DomRendererFactory2 implements RendererFactory2 {
|
||||
private rendererByCompId = new Map<string, Renderer2>();
|
||||
private defaultRenderer: Renderer2;
|
||||
|
||||
constructor(private eventManager: EventManager, private sharedStylesHost: DomSharedStylesHost) {
|
||||
this.defaultRenderer = new DefaultDomRenderer2(eventManager);
|
||||
};
|
||||
|
||||
createRenderer(element: any, type: RendererType2): Renderer2 {
|
||||
if (!element || !type) {
|
||||
return this.defaultRenderer;
|
||||
}
|
||||
switch (type.encapsulation) {
|
||||
case ViewEncapsulation.Emulated: {
|
||||
let renderer = this.rendererByCompId.get(type.id);
|
||||
if (!renderer) {
|
||||
renderer =
|
||||
new EmulatedEncapsulationDomRenderer2(this.eventManager, this.sharedStylesHost, type);
|
||||
this.rendererByCompId.set(type.id, renderer);
|
||||
}
|
||||
(<EmulatedEncapsulationDomRenderer2>renderer).applyToHost(element);
|
||||
return renderer;
|
||||
}
|
||||
case ViewEncapsulation.Native:
|
||||
return new ShadowDomRenderer(this.eventManager, this.sharedStylesHost, element, type);
|
||||
default: {
|
||||
if (!this.rendererByCompId.has(type.id)) {
|
||||
const styles = flattenStyles(type.id, type.styles, []);
|
||||
this.sharedStylesHost.addStyles(styles);
|
||||
this.rendererByCompId.set(type.id, this.defaultRenderer);
|
||||
}
|
||||
return this.defaultRenderer;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class DefaultDomRenderer2 implements Renderer2 {
|
||||
data: {[key: string]: any} = Object.create(null);
|
||||
|
||||
constructor(private eventManager: EventManager) {}
|
||||
|
||||
destroy(): void {}
|
||||
|
||||
destroyNode: null;
|
||||
|
||||
createElement(name: string, namespace?: string): any {
|
||||
if (namespace) {
|
||||
return document.createElementNS(NAMESPACE_URIS[namespace], name);
|
||||
}
|
||||
|
||||
return document.createElement(name);
|
||||
}
|
||||
|
||||
createComment(value: string): any { return document.createComment(value); }
|
||||
|
||||
createText(value: string): any { return document.createTextNode(value); }
|
||||
|
||||
appendChild(parent: any, newChild: any): void { parent.appendChild(newChild); }
|
||||
|
||||
insertBefore(parent: any, newChild: any, refChild: any): void {
|
||||
if (parent) {
|
||||
parent.insertBefore(newChild, refChild);
|
||||
}
|
||||
}
|
||||
|
||||
removeChild(parent: any, oldChild: any): void {
|
||||
if (parent) {
|
||||
parent.removeChild(oldChild);
|
||||
}
|
||||
}
|
||||
|
||||
selectRootElement(selectorOrNode: string|any): any {
|
||||
let el: any = typeof selectorOrNode === 'string' ? document.querySelector(selectorOrNode) :
|
||||
selectorOrNode;
|
||||
if (!el) {
|
||||
throw new Error(`The selector "${selectorOrNode}" did not match any elements`);
|
||||
}
|
||||
el.textContent = '';
|
||||
return el;
|
||||
}
|
||||
|
||||
parentNode(node: any): any { return node.parentNode; }
|
||||
|
||||
nextSibling(node: any): any { return node.nextSibling; }
|
||||
|
||||
setAttribute(el: any, name: string, value: string, namespace?: string): void {
|
||||
if (namespace) {
|
||||
el.setAttributeNS(NAMESPACE_URIS[namespace], namespace + ':' + name, value);
|
||||
} else {
|
||||
el.setAttribute(name, value);
|
||||
}
|
||||
}
|
||||
|
||||
removeAttribute(el: any, name: string, namespace?: string): void {
|
||||
if (namespace) {
|
||||
el.removeAttributeNS(NAMESPACE_URIS[namespace], name);
|
||||
} else {
|
||||
el.removeAttribute(name);
|
||||
}
|
||||
}
|
||||
|
||||
addClass(el: any, name: string): void { el.classList.add(name); }
|
||||
|
||||
removeClass(el: any, name: string): void { el.classList.remove(name); }
|
||||
|
||||
setStyle(el: any, style: string, value: any, hasVendorPrefix: boolean, hasImportant: boolean):
|
||||
void {
|
||||
if (hasVendorPrefix || hasImportant) {
|
||||
el.style.setProperty(style, value, hasImportant ? 'important' : '');
|
||||
} else {
|
||||
el.style[style] = value;
|
||||
}
|
||||
}
|
||||
|
||||
removeStyle(el: any, style: string, hasVendorPrefix: boolean): void {
|
||||
if (hasVendorPrefix) {
|
||||
el.style.removeProperty(style);
|
||||
} else {
|
||||
// IE requires '' instead of null
|
||||
// see https://github.com/angular/angular/issues/7916
|
||||
el.style[style] = '';
|
||||
}
|
||||
}
|
||||
|
||||
setProperty(el: any, name: string, value: any): void {
|
||||
checkNoSyntheticProp(name, 'property');
|
||||
el[name] = value;
|
||||
}
|
||||
|
||||
setValue(node: any, value: string): void { node.nodeValue = value; }
|
||||
|
||||
listen(target: 'window'|'document'|'body'|any, event: string, callback: (event: any) => boolean):
|
||||
() => void {
|
||||
checkNoSyntheticProp(event, 'listener');
|
||||
if (typeof target === 'string') {
|
||||
return <() => void>this.eventManager.addGlobalEventListener(
|
||||
target, event, decoratePreventDefault(callback));
|
||||
}
|
||||
return <() => void>this.eventManager.addEventListener(
|
||||
target, event, decoratePreventDefault(callback)) as() => void;
|
||||
}
|
||||
}
|
||||
|
||||
const AT_CHARCODE = '@'.charCodeAt(0);
|
||||
function checkNoSyntheticProp(name: string, nameKind: string) {
|
||||
if (name.charCodeAt(0) === AT_CHARCODE) {
|
||||
throw new Error(
|
||||
`Found the synthetic ${nameKind} ${name}. Please include either "BrowserAnimationsModule" or "NoopAnimationsModule" in your application.`);
|
||||
}
|
||||
}
|
||||
|
||||
class EmulatedEncapsulationDomRenderer2 extends DefaultDomRenderer2 {
|
||||
private contentAttr: string;
|
||||
private hostAttr: string;
|
||||
|
||||
constructor(
|
||||
eventManager: EventManager, sharedStylesHost: DomSharedStylesHost,
|
||||
private component: RendererType2) {
|
||||
super(eventManager);
|
||||
const styles = flattenStyles(component.id, component.styles, []);
|
||||
sharedStylesHost.addStyles(styles);
|
||||
|
||||
this.contentAttr = shimContentAttribute(component.id);
|
||||
this.hostAttr = shimHostAttribute(component.id);
|
||||
}
|
||||
|
||||
applyToHost(element: any) { super.setAttribute(element, this.hostAttr, ''); }
|
||||
|
||||
createElement(parent: any, name: string): Element {
|
||||
const el = super.createElement(parent, name);
|
||||
super.setAttribute(el, this.contentAttr, '');
|
||||
return el;
|
||||
}
|
||||
}
|
||||
|
||||
class ShadowDomRenderer extends DefaultDomRenderer2 {
|
||||
private shadowRoot: any;
|
||||
|
||||
constructor(
|
||||
eventManager: EventManager, private sharedStylesHost: DomSharedStylesHost,
|
||||
private hostEl: any, private component: RendererType2) {
|
||||
super(eventManager);
|
||||
this.shadowRoot = (hostEl as any).createShadowRoot();
|
||||
this.sharedStylesHost.addHost(this.shadowRoot);
|
||||
const styles = flattenStyles(component.id, component.styles, []);
|
||||
for (let i = 0; i < styles.length; i++) {
|
||||
const styleEl = document.createElement('style');
|
||||
styleEl.textContent = styles[i];
|
||||
this.shadowRoot.appendChild(styleEl);
|
||||
}
|
||||
}
|
||||
|
||||
private nodeOrShadowRoot(node: any): any { return node === this.hostEl ? this.shadowRoot : node; }
|
||||
|
||||
destroy() { this.sharedStylesHost.removeHost(this.shadowRoot); }
|
||||
|
||||
appendChild(parent: any, newChild: any): void {
|
||||
return super.appendChild(this.nodeOrShadowRoot(parent), newChild);
|
||||
}
|
||||
insertBefore(parent: any, newChild: any, refChild: any): void {
|
||||
return super.insertBefore(this.nodeOrShadowRoot(parent), newChild, refChild);
|
||||
}
|
||||
removeChild(parent: any, oldChild: any): void {
|
||||
return super.removeChild(this.nodeOrShadowRoot(parent), oldChild);
|
||||
}
|
||||
parentNode(node: any): any {
|
||||
return this.nodeOrShadowRoot(super.parentNode(this.nodeOrShadowRoot(node)));
|
||||
}
|
||||
}
|
19
packages/platform-browser/src/dom/dom_tokens.ts
Normal file
19
packages/platform-browser/src/dom/dom_tokens.ts
Normal file
@ -0,0 +1,19 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {InjectionToken} from '@angular/core';
|
||||
|
||||
/**
|
||||
* A DI Token representing the main rendering context. In a browser this is the DOM Document.
|
||||
*
|
||||
* Note: Document might not be available in the Application Context when Application and Rendering
|
||||
* Contexts are not the same (e.g. when running the application into a Web Worker).
|
||||
*
|
||||
* @stable
|
||||
*/
|
||||
export const DOCUMENT = new InjectionToken<Document>('DocumentToken');
|
27
packages/platform-browser/src/dom/events/dom_events.ts
Normal file
27
packages/platform-browser/src/dom/events/dom_events.ts
Normal file
@ -0,0 +1,27 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {Inject, Injectable} from '@angular/core';
|
||||
|
||||
import {DOCUMENT} from '../dom_tokens';
|
||||
|
||||
import {EventManagerPlugin} from './event_manager';
|
||||
|
||||
@Injectable()
|
||||
export class DomEventsPlugin extends EventManagerPlugin {
|
||||
constructor(@Inject(DOCUMENT) doc: any) { super(doc); }
|
||||
|
||||
// This plugin should come last in the list of plugins, because it accepts all
|
||||
// events.
|
||||
supports(eventName: string): boolean { return true; }
|
||||
|
||||
addEventListener(element: HTMLElement, eventName: string, handler: Function): Function {
|
||||
element.addEventListener(eventName, handler as any, false);
|
||||
return () => element.removeEventListener(eventName, handler as any, false);
|
||||
}
|
||||
}
|
79
packages/platform-browser/src/dom/events/event_manager.ts
Normal file
79
packages/platform-browser/src/dom/events/event_manager.ts
Normal file
@ -0,0 +1,79 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {Inject, Injectable, InjectionToken, NgZone} from '@angular/core';
|
||||
|
||||
import {getDOM} from '../dom_adapter';
|
||||
|
||||
/**
|
||||
* @stable
|
||||
*/
|
||||
export const EVENT_MANAGER_PLUGINS =
|
||||
new InjectionToken<EventManagerPlugin[]>('EventManagerPlugins');
|
||||
|
||||
/**
|
||||
* @stable
|
||||
*/
|
||||
@Injectable()
|
||||
export class EventManager {
|
||||
private _plugins: EventManagerPlugin[];
|
||||
private _eventNameToPlugin = new Map<string, EventManagerPlugin>();
|
||||
|
||||
constructor(@Inject(EVENT_MANAGER_PLUGINS) plugins: EventManagerPlugin[], private _zone: NgZone) {
|
||||
plugins.forEach(p => p.manager = this);
|
||||
this._plugins = plugins.slice().reverse();
|
||||
}
|
||||
|
||||
addEventListener(element: HTMLElement, eventName: string, handler: Function): Function {
|
||||
const plugin = this._findPluginFor(eventName);
|
||||
return plugin.addEventListener(element, eventName, handler);
|
||||
}
|
||||
|
||||
addGlobalEventListener(target: string, eventName: string, handler: Function): Function {
|
||||
const plugin = this._findPluginFor(eventName);
|
||||
return plugin.addGlobalEventListener(target, eventName, handler);
|
||||
}
|
||||
|
||||
getZone(): NgZone { return this._zone; }
|
||||
|
||||
/** @internal */
|
||||
_findPluginFor(eventName: string): EventManagerPlugin {
|
||||
const plugin = this._eventNameToPlugin.get(eventName);
|
||||
if (plugin) {
|
||||
return plugin;
|
||||
}
|
||||
|
||||
const plugins = this._plugins;
|
||||
for (let i = 0; i < plugins.length; i++) {
|
||||
const plugin = plugins[i];
|
||||
if (plugin.supports(eventName)) {
|
||||
this._eventNameToPlugin.set(eventName, plugin);
|
||||
return plugin;
|
||||
}
|
||||
}
|
||||
throw new Error(`No event manager plugin found for event ${eventName}`);
|
||||
}
|
||||
}
|
||||
|
||||
export abstract class EventManagerPlugin {
|
||||
constructor(private _doc: any) {}
|
||||
|
||||
manager: EventManager;
|
||||
|
||||
abstract supports(eventName: string): boolean;
|
||||
|
||||
abstract addEventListener(element: HTMLElement, eventName: string, handler: Function): Function;
|
||||
|
||||
addGlobalEventListener(element: string, eventName: string, handler: Function): Function {
|
||||
const target: HTMLElement = getDOM().getGlobalEventTarget(this._doc, element);
|
||||
if (!target) {
|
||||
throw new Error(`Unsupported event target ${target} for event ${eventName}`);
|
||||
}
|
||||
return this.addEventListener(target, eventName, handler);
|
||||
};
|
||||
}
|
125
packages/platform-browser/src/dom/events/hammer_gestures.ts
Normal file
125
packages/platform-browser/src/dom/events/hammer_gestures.ts
Normal file
@ -0,0 +1,125 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {Inject, Injectable, InjectionToken} from '@angular/core';
|
||||
|
||||
import {DOCUMENT} from '../dom_tokens';
|
||||
|
||||
import {EventManagerPlugin} from './event_manager';
|
||||
|
||||
const EVENT_NAMES = {
|
||||
// pan
|
||||
'pan': true,
|
||||
'panstart': true,
|
||||
'panmove': true,
|
||||
'panend': true,
|
||||
'pancancel': true,
|
||||
'panleft': true,
|
||||
'panright': true,
|
||||
'panup': true,
|
||||
'pandown': true,
|
||||
// pinch
|
||||
'pinch': true,
|
||||
'pinchstart': true,
|
||||
'pinchmove': true,
|
||||
'pinchend': true,
|
||||
'pinchcancel': true,
|
||||
'pinchin': true,
|
||||
'pinchout': true,
|
||||
// press
|
||||
'press': true,
|
||||
'pressup': true,
|
||||
// rotate
|
||||
'rotate': true,
|
||||
'rotatestart': true,
|
||||
'rotatemove': true,
|
||||
'rotateend': true,
|
||||
'rotatecancel': true,
|
||||
// swipe
|
||||
'swipe': true,
|
||||
'swipeleft': true,
|
||||
'swiperight': true,
|
||||
'swipeup': true,
|
||||
'swipedown': true,
|
||||
// tap
|
||||
'tap': true,
|
||||
};
|
||||
|
||||
/**
|
||||
* A DI token that you can use to provide{@link HammerGestureConfig} to Angular. Use it to configure
|
||||
* Hammer gestures.
|
||||
*
|
||||
* @experimental
|
||||
*/
|
||||
export const HAMMER_GESTURE_CONFIG = new InjectionToken<HammerGestureConfig>('HammerGestureConfig');
|
||||
|
||||
export interface HammerInstance {
|
||||
on(eventName: string, callback: Function): void;
|
||||
off(eventName: string, callback: Function): void;
|
||||
}
|
||||
|
||||
/**
|
||||
* @experimental
|
||||
*/
|
||||
@Injectable()
|
||||
export class HammerGestureConfig {
|
||||
events: string[] = [];
|
||||
|
||||
overrides: {[key: string]: Object} = {};
|
||||
|
||||
buildHammer(element: HTMLElement): HammerInstance {
|
||||
const mc = new Hammer(element);
|
||||
|
||||
mc.get('pinch').set({enable: true});
|
||||
mc.get('rotate').set({enable: true});
|
||||
|
||||
for (const eventName in this.overrides) {
|
||||
mc.get(eventName).set(this.overrides[eventName]);
|
||||
}
|
||||
|
||||
return mc;
|
||||
}
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class HammerGesturesPlugin extends EventManagerPlugin {
|
||||
constructor(
|
||||
@Inject(DOCUMENT) doc: any,
|
||||
@Inject(HAMMER_GESTURE_CONFIG) private _config: HammerGestureConfig) {
|
||||
super(doc);
|
||||
}
|
||||
|
||||
supports(eventName: string): boolean {
|
||||
if (!EVENT_NAMES.hasOwnProperty(eventName.toLowerCase()) && !this.isCustomEvent(eventName)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!(window as any).Hammer) {
|
||||
throw new Error(`Hammer.js is not loaded, can not bind ${eventName} event`);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
addEventListener(element: HTMLElement, eventName: string, handler: Function): Function {
|
||||
const zone = this.manager.getZone();
|
||||
eventName = eventName.toLowerCase();
|
||||
|
||||
return zone.runOutsideAngular(() => {
|
||||
// Creating the manager bind events, must be done outside of angular
|
||||
const mc = this._config.buildHammer(element);
|
||||
const callback = function(eventObj: HammerInput) {
|
||||
zone.runGuarded(function() { handler(eventObj); });
|
||||
};
|
||||
mc.on(eventName, callback);
|
||||
return () => mc.off(eventName, callback);
|
||||
});
|
||||
}
|
||||
|
||||
isCustomEvent(eventName: string): boolean { return this._config.events.indexOf(eventName) > -1; }
|
||||
}
|
114
packages/platform-browser/src/dom/events/key_events.ts
Normal file
114
packages/platform-browser/src/dom/events/key_events.ts
Normal file
@ -0,0 +1,114 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {Inject, Injectable, NgZone} from '@angular/core';
|
||||
|
||||
import {getDOM} from '../dom_adapter';
|
||||
import {DOCUMENT} from '../dom_tokens';
|
||||
|
||||
import {EventManagerPlugin} from './event_manager';
|
||||
|
||||
const MODIFIER_KEYS = ['alt', 'control', 'meta', 'shift'];
|
||||
const MODIFIER_KEY_GETTERS: {[key: string]: (event: KeyboardEvent) => boolean} = {
|
||||
'alt': (event: KeyboardEvent) => event.altKey,
|
||||
'control': (event: KeyboardEvent) => event.ctrlKey,
|
||||
'meta': (event: KeyboardEvent) => event.metaKey,
|
||||
'shift': (event: KeyboardEvent) => event.shiftKey
|
||||
};
|
||||
|
||||
/**
|
||||
* @experimental
|
||||
*/
|
||||
@Injectable()
|
||||
export class KeyEventsPlugin extends EventManagerPlugin {
|
||||
constructor(@Inject(DOCUMENT) doc: any) { super(doc); }
|
||||
|
||||
supports(eventName: string): boolean { return KeyEventsPlugin.parseEventName(eventName) != null; }
|
||||
|
||||
addEventListener(element: HTMLElement, eventName: string, handler: Function): Function {
|
||||
const parsedEvent = KeyEventsPlugin.parseEventName(eventName);
|
||||
|
||||
const outsideHandler =
|
||||
KeyEventsPlugin.eventCallback(parsedEvent['fullKey'], handler, this.manager.getZone());
|
||||
|
||||
return this.manager.getZone().runOutsideAngular(() => {
|
||||
return getDOM().onAndCancel(element, parsedEvent['domEventName'], outsideHandler);
|
||||
});
|
||||
}
|
||||
|
||||
static parseEventName(eventName: string): {[key: string]: string} {
|
||||
const parts: string[] = eventName.toLowerCase().split('.');
|
||||
|
||||
const domEventName = parts.shift();
|
||||
if ((parts.length === 0) || !(domEventName === 'keydown' || domEventName === 'keyup')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const key = KeyEventsPlugin._normalizeKey(parts.pop());
|
||||
|
||||
let fullKey = '';
|
||||
MODIFIER_KEYS.forEach(modifierName => {
|
||||
const index: number = parts.indexOf(modifierName);
|
||||
if (index > -1) {
|
||||
parts.splice(index, 1);
|
||||
fullKey += modifierName + '.';
|
||||
}
|
||||
});
|
||||
fullKey += key;
|
||||
|
||||
if (parts.length != 0 || key.length === 0) {
|
||||
// returning null instead of throwing to let another plugin process the event
|
||||
return null;
|
||||
}
|
||||
|
||||
const result: {[k: string]: string} = {};
|
||||
result['domEventName'] = domEventName;
|
||||
result['fullKey'] = fullKey;
|
||||
return result;
|
||||
}
|
||||
|
||||
static getEventFullKey(event: KeyboardEvent): string {
|
||||
let fullKey = '';
|
||||
let key = getDOM().getEventKey(event);
|
||||
key = key.toLowerCase();
|
||||
if (key === ' ') {
|
||||
key = 'space'; // for readability
|
||||
} else if (key === '.') {
|
||||
key = 'dot'; // because '.' is used as a separator in event names
|
||||
}
|
||||
MODIFIER_KEYS.forEach(modifierName => {
|
||||
if (modifierName != key) {
|
||||
const modifierGetter = MODIFIER_KEY_GETTERS[modifierName];
|
||||
if (modifierGetter(event)) {
|
||||
fullKey += modifierName + '.';
|
||||
}
|
||||
}
|
||||
});
|
||||
fullKey += key;
|
||||
return fullKey;
|
||||
}
|
||||
|
||||
static eventCallback(fullKey: any, handler: Function, zone: NgZone): Function {
|
||||
return (event: any /** TODO #9100 */) => {
|
||||
if (KeyEventsPlugin.getEventFullKey(event) === fullKey) {
|
||||
zone.runGuarded(() => handler(event));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
static _normalizeKey(keyName: string): string {
|
||||
// TODO: switch to a Map if the mapping grows too much
|
||||
switch (keyName) {
|
||||
case 'esc':
|
||||
return 'escape';
|
||||
default:
|
||||
return keyName;
|
||||
}
|
||||
}
|
||||
}
|
63
packages/platform-browser/src/dom/shared_styles_host.ts
Normal file
63
packages/platform-browser/src/dom/shared_styles_host.ts
Normal file
@ -0,0 +1,63 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {Inject, Injectable, OnDestroy} from '@angular/core';
|
||||
import {getDOM} from './dom_adapter';
|
||||
import {DOCUMENT} from './dom_tokens';
|
||||
|
||||
@Injectable()
|
||||
export class SharedStylesHost {
|
||||
/** @internal */
|
||||
protected _stylesSet = new Set<string>();
|
||||
|
||||
addStyles(styles: string[]): void {
|
||||
const additions = new Set<string>();
|
||||
styles.forEach(style => {
|
||||
if (!this._stylesSet.has(style)) {
|
||||
this._stylesSet.add(style);
|
||||
additions.add(style);
|
||||
}
|
||||
});
|
||||
this.onStylesAdded(additions);
|
||||
}
|
||||
|
||||
onStylesAdded(additions: Set<string>): void {}
|
||||
|
||||
getAllStyles(): string[] { return Array.from(this._stylesSet); }
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class DomSharedStylesHost extends SharedStylesHost implements OnDestroy {
|
||||
private _hostNodes = new Set<Node>();
|
||||
private _styleNodes = new Set<Node>();
|
||||
constructor(@Inject(DOCUMENT) private _doc: any) {
|
||||
super();
|
||||
this._hostNodes.add(_doc.head);
|
||||
}
|
||||
|
||||
private _addStylesToHost(styles: Set<string>, host: Node): void {
|
||||
styles.forEach((style: string) => {
|
||||
const styleEl = this._doc.createElement('style');
|
||||
styleEl.textContent = style;
|
||||
this._styleNodes.add(host.appendChild(styleEl));
|
||||
});
|
||||
}
|
||||
|
||||
addHost(hostNode: Node): void {
|
||||
this._addStylesToHost(this._stylesSet, hostNode);
|
||||
this._hostNodes.add(hostNode);
|
||||
}
|
||||
|
||||
removeHost(hostNode: Node): void { this._hostNodes.delete(hostNode); }
|
||||
|
||||
onStylesAdded(additions: Set<string>): void {
|
||||
this._hostNodes.forEach(hostNode => this._addStylesToHost(additions, hostNode));
|
||||
}
|
||||
|
||||
ngOnDestroy(): void { this._styleNodes.forEach(styleNode => getDOM().remove(styleNode)); }
|
||||
}
|
19
packages/platform-browser/src/dom/util.ts
Normal file
19
packages/platform-browser/src/dom/util.ts
Normal file
@ -0,0 +1,19 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
const CAMEL_CASE_REGEXP = /([A-Z])/g;
|
||||
const DASH_CASE_REGEXP = /-([a-z])/g;
|
||||
|
||||
|
||||
export function camelCaseToDashCase(input: string): string {
|
||||
return input.replace(CAMEL_CASE_REGEXP, (...m: string[]) => '-' + m[1].toLowerCase());
|
||||
}
|
||||
|
||||
export function dashCaseToCamelCase(input: string): string {
|
||||
return input.replace(DASH_CASE_REGEXP, (...m: string[]) => m[1].toUpperCase());
|
||||
}
|
Reference in New Issue
Block a user