refactor(core): move render/dom from core
Currently, core depends on DomRenderer, which depends on the browser. This means that if you depend on angular2/core, you will always pull in the browser dom adapter and the browser render, regardless if you need them or not. This PR moves the browser dom adapter and the browser renderer out of core. BREAKING CHANGE If you import browser adapter or dom renderer directly (not via angular2/core), you will have to change the import path.
This commit is contained in:
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,19 @@
|
||||
import {Type, isPresent, isBlank} from 'angular2/src/facade/lang';
|
||||
import {Predicate} from 'angular2/src/facade/collection';
|
||||
import {DOM} from 'angular2/src/core/dom/dom_adapter';
|
||||
import {DebugElement} from 'angular2/core';
|
||||
|
||||
export class By {
|
||||
static all(): Function { return (debugElement) => true; }
|
||||
|
||||
static css(selector: string): Predicate<DebugElement> {
|
||||
return (debugElement) => {
|
||||
return isPresent(debugElement.nativeElement) ?
|
||||
DOM.elementMatches(debugElement.nativeElement, selector) :
|
||||
false;
|
||||
};
|
||||
}
|
||||
static directive(type: Type): Predicate<DebugElement> {
|
||||
return (debugElement) => { return debugElement.hasDirective(type); };
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
import {CONST_EXPR, isPresent, NumberWrapper, StringWrapper} from 'angular2/src/facade/lang';
|
||||
import {MapWrapper, Map, ListWrapper} from 'angular2/src/facade/collection';
|
||||
import {Injectable, provide, Provider} from 'angular2/src/core/di';
|
||||
import {AppViewListener} from 'angular2/src/core/linker/view_listener';
|
||||
import {AppView} from 'angular2/src/core/linker/view';
|
||||
import {DOM} from 'angular2/src/core/dom/dom_adapter';
|
||||
import {Renderer} from 'angular2/src/core/render/api';
|
||||
import {DebugElement, DebugElement_} from 'angular2/src/core/debug/debug_element';
|
||||
|
||||
const NG_ID_PROPERTY = 'ngid';
|
||||
const INSPECT_GLOBAL_NAME = 'ng.probe';
|
||||
|
||||
const NG_ID_SEPARATOR = '#';
|
||||
|
||||
// Need to keep the views in a global Map so that multiple angular apps are supported
|
||||
var _allIdsByView = new Map<AppView, number>();
|
||||
var _allViewsById = new Map<number, AppView>();
|
||||
|
||||
var _nextId = 0;
|
||||
|
||||
function _setElementId(element, indices: number[]) {
|
||||
if (isPresent(element)) {
|
||||
DOM.setData(element, NG_ID_PROPERTY, indices.join(NG_ID_SEPARATOR));
|
||||
}
|
||||
}
|
||||
|
||||
function _getElementId(element): number[] {
|
||||
var elId = DOM.getData(element, NG_ID_PROPERTY);
|
||||
if (isPresent(elId)) {
|
||||
return elId.split(NG_ID_SEPARATOR).map(partStr => NumberWrapper.parseInt(partStr, 10));
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export function inspectNativeElement(element): DebugElement {
|
||||
var elId = _getElementId(element);
|
||||
if (isPresent(elId)) {
|
||||
var view = _allViewsById.get(elId[0]);
|
||||
if (isPresent(view)) {
|
||||
return new DebugElement_(view, elId[1]);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class DebugElementViewListener implements AppViewListener {
|
||||
constructor(private _renderer: Renderer) {
|
||||
DOM.setGlobalVar(INSPECT_GLOBAL_NAME, inspectNativeElement);
|
||||
}
|
||||
|
||||
onViewCreated(view: AppView) {
|
||||
var viewId = _nextId++;
|
||||
_allViewsById.set(viewId, view);
|
||||
_allIdsByView.set(view, viewId);
|
||||
for (var i = 0; i < view.elementRefs.length; i++) {
|
||||
var el = view.elementRefs[i];
|
||||
_setElementId(this._renderer.getNativeElementSync(el), [viewId, i]);
|
||||
}
|
||||
}
|
||||
|
||||
onViewDestroyed(view: AppView) {
|
||||
var viewId = _allIdsByView.get(view);
|
||||
_allIdsByView.delete(view);
|
||||
_allViewsById.delete(viewId);
|
||||
}
|
||||
}
|
||||
|
||||
export const ELEMENT_PROBE_PROVIDERS: any[] = CONST_EXPR([
|
||||
DebugElementViewListener,
|
||||
CONST_EXPR(new Provider(AppViewListener, {useExisting: DebugElementViewListener})),
|
||||
]);
|
||||
|
||||
export const ELEMENT_PROBE_BINDINGS = ELEMENT_PROBE_PROVIDERS;
|
||||
@@ -0,0 +1,61 @@
|
||||
import {ListWrapper, StringMapWrapper} from 'angular2/src/facade/collection';
|
||||
import {isPresent, isFunction, Type} from 'angular2/src/facade/lang';
|
||||
import {DomAdapter} from 'angular2/src/core/dom/dom_adapter';
|
||||
import {XHRImpl} from 'angular2/src/platform/browser/xhr_impl';
|
||||
|
||||
|
||||
/**
|
||||
* Provides DOM operations in any browser environment.
|
||||
*/
|
||||
export abstract class GenericBrowserDomAdapter extends DomAdapter {
|
||||
private _animationPrefix: string = null;
|
||||
private _transitionEnd: string = null;
|
||||
constructor() {
|
||||
super();
|
||||
try {
|
||||
var element = this.createElement('div', this.defaultDoc());
|
||||
if (isPresent(this.getStyle(element, 'animationName'))) {
|
||||
this._animationPrefix = '';
|
||||
} else {
|
||||
var domPrefixes = ['Webkit', 'Moz', 'O', 'ms'];
|
||||
for (var i = 0; i < domPrefixes.length; i++) {
|
||||
if (isPresent(this.getStyle(element, domPrefixes[i] + 'AnimationName'))) {
|
||||
this._animationPrefix = '-' + domPrefixes[i].toLowerCase() + '-';
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
var transEndEventNames: {[key: string]: string} = {
|
||||
WebkitTransition: 'webkitTransitionEnd',
|
||||
MozTransition: 'transitionend',
|
||||
OTransition: 'oTransitionEnd otransitionend',
|
||||
transition: 'transitionend'
|
||||
};
|
||||
StringMapWrapper.forEach(transEndEventNames, (value, key) => {
|
||||
if (isPresent(this.getStyle(element, key))) {
|
||||
this._transitionEnd = value;
|
||||
}
|
||||
});
|
||||
} catch (e) {
|
||||
this._animationPrefix = null;
|
||||
this._transitionEnd = null;
|
||||
}
|
||||
}
|
||||
|
||||
getXHR(): Type { return XHRImpl; }
|
||||
getDistributedNodes(el: HTMLElement): Node[] { return (<any>el).getDistributedNodes(); }
|
||||
resolveAndSetHref(el: HTMLAnchorElement, baseUrl: string, href: string) {
|
||||
el.href = href == null ? baseUrl : baseUrl + '/../' + href;
|
||||
}
|
||||
supportsDOMEvents(): boolean { return true; }
|
||||
supportsNativeShadowDOM(): boolean {
|
||||
return isFunction((<any>this.defaultDoc().body).createShadowRoot);
|
||||
}
|
||||
getAnimationPrefix(): string {
|
||||
return isPresent(this._animationPrefix) ? this._animationPrefix : "";
|
||||
}
|
||||
getTransitionEnd(): string { return isPresent(this._transitionEnd) ? this._transitionEnd : ""; }
|
||||
supportsAnimation(): boolean {
|
||||
return isPresent(this._animationPrefix) && isPresent(this._transitionEnd);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
import {Promise, PromiseWrapper} from 'angular2/src/facade/async';
|
||||
import {DomAdapter} from 'angular2/src/core/dom/dom_adapter';
|
||||
import {ElementRef} from 'angular2/src/core/linker/element_ref';
|
||||
|
||||
export class Rectangle {
|
||||
left;
|
||||
right;
|
||||
top;
|
||||
bottom;
|
||||
height;
|
||||
width;
|
||||
constructor(left, top, width, height) {
|
||||
this.left = left;
|
||||
this.right = left + width;
|
||||
this.top = top;
|
||||
this.bottom = top + height;
|
||||
this.height = height;
|
||||
this.width = width;
|
||||
}
|
||||
}
|
||||
|
||||
export class Ruler {
|
||||
domAdapter: DomAdapter;
|
||||
constructor(domAdapter: DomAdapter) { this.domAdapter = domAdapter; }
|
||||
|
||||
measure(el: ElementRef): Promise<Rectangle> {
|
||||
var clntRect = <any>this.domAdapter.getBoundingClientRect(el.nativeElement);
|
||||
|
||||
// even if getBoundingClientRect is synchronous we use async API in preparation for further
|
||||
// changes
|
||||
return PromiseWrapper.resolve(
|
||||
new Rectangle(clntRect.left, clntRect.top, clntRect.width, clntRect.height));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,165 @@
|
||||
library testability.browser_testability;
|
||||
|
||||
import 'package:angular2/core.dart';
|
||||
|
||||
import 'dart:html';
|
||||
import 'dart:js' as js;
|
||||
|
||||
// Work around http://dartbug.com/17752, copied from
|
||||
// https://github.com/angular/angular.dart/blob/master/lib/introspection.dart
|
||||
// Proxies a Dart function that accepts up to 10 parameters.
|
||||
js.JsFunction _jsFunction(Function fn) {
|
||||
const Object X = __varargSentinel;
|
||||
return new js.JsFunction.withThis((thisArg,
|
||||
[o1 = X,
|
||||
o2 = X,
|
||||
o3 = X,
|
||||
o4 = X,
|
||||
o5 = X,
|
||||
o6 = X,
|
||||
o7 = X,
|
||||
o8 = X,
|
||||
o9 = X,
|
||||
o10 = X]) {
|
||||
return __invokeFn(fn, o1, o2, o3, o4, o5, o6, o7, o8, o9, o10);
|
||||
});
|
||||
}
|
||||
|
||||
const Object __varargSentinel = const Object();
|
||||
|
||||
__invokeFn(fn, o1, o2, o3, o4, o5, o6, o7, o8, o9, o10) {
|
||||
var args = [o1, o2, o3, o4, o5, o6, o7, o8, o9, o10];
|
||||
while (args.length > 0 && identical(args.last, __varargSentinel)) {
|
||||
args.removeLast();
|
||||
}
|
||||
return _jsify(Function.apply(fn, args));
|
||||
}
|
||||
|
||||
// Helper function to JSify a Dart object. While this is *required* to JSify
|
||||
// the result of a scope.eval(), other uses are not required and are used to
|
||||
// work around http://dartbug.com/17752 in a convenient way (that bug affects
|
||||
// dart2js in checked mode.)
|
||||
_jsify(var obj) {
|
||||
if (obj == null || obj is js.JsObject) {
|
||||
return obj;
|
||||
}
|
||||
if (obj is _JsObjectProxyable) {
|
||||
return obj._toJsObject();
|
||||
}
|
||||
if (obj is Function) {
|
||||
return _jsFunction(obj);
|
||||
}
|
||||
if ((obj is Map) || (obj is Iterable)) {
|
||||
var mappedObj = (obj is Map)
|
||||
? new Map.fromIterables(obj.keys, obj.values.map(_jsify))
|
||||
: obj.map(_jsify);
|
||||
if (obj is List) {
|
||||
return new js.JsArray.from(mappedObj);
|
||||
} else {
|
||||
return new js.JsObject.jsify(mappedObj);
|
||||
}
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
||||
abstract class _JsObjectProxyable {
|
||||
js.JsObject _toJsObject();
|
||||
}
|
||||
|
||||
class PublicTestability implements _JsObjectProxyable {
|
||||
Testability _testability;
|
||||
PublicTestability(Testability testability) {
|
||||
this._testability = testability;
|
||||
}
|
||||
|
||||
bool isStable() {
|
||||
return this._testability.isStable();
|
||||
}
|
||||
|
||||
whenStable(Function callback) {
|
||||
return this._testability.whenStable(callback);
|
||||
}
|
||||
|
||||
findBindings(Element elem, String binding, bool exactMatch) {
|
||||
return this._testability.findBindings(elem, binding, exactMatch);
|
||||
}
|
||||
|
||||
js.JsObject _toJsObject() {
|
||||
return _jsify({
|
||||
'findBindings': (bindingString, [exactMatch, allowNonElementNodes]) =>
|
||||
findBindings(bindingString, exactMatch, allowNonElementNodes),
|
||||
'isStable': () => isStable(),
|
||||
'whenStable': (callback) => whenStable(() => callback.apply([]))
|
||||
})..['_dart_'] = this;
|
||||
}
|
||||
}
|
||||
|
||||
class BrowserGetTestability implements GetTestability {
|
||||
const BrowserGetTestability();
|
||||
|
||||
static init() {
|
||||
setTestabilityGetter(const BrowserGetTestability());
|
||||
}
|
||||
|
||||
void addToWindow(TestabilityRegistry registry) {
|
||||
var jsRegistry = js.context['ngTestabilityRegistries'];
|
||||
if (jsRegistry == null) {
|
||||
js.context['ngTestabilityRegistries'] = jsRegistry = new js.JsArray();
|
||||
js.context['getAngularTestability'] =
|
||||
_jsify((Element elem, [bool findInAncestors = true]) {
|
||||
var registry = js.context['ngTestabilityRegistries'];
|
||||
for (int i = 0; i < registry.length; i++) {
|
||||
var result = registry[i]
|
||||
.callMethod('getAngularTestability', [elem, findInAncestors]);
|
||||
if (result != null) return result;
|
||||
}
|
||||
throw 'Could not find testability for element.';
|
||||
});
|
||||
js.context['getAllAngularTestabilities'] = _jsify(() {
|
||||
var registry = js.context['ngTestabilityRegistries'];
|
||||
var result = [];
|
||||
for (int i = 0; i < registry.length; i++) {
|
||||
var testabilities =
|
||||
registry[i].callMethod('getAllAngularTestabilities');
|
||||
if (testabilities != null) result.addAll(testabilities);
|
||||
}
|
||||
return _jsify(result);
|
||||
});
|
||||
}
|
||||
jsRegistry.add(this._createRegistry(registry));
|
||||
}
|
||||
|
||||
findTestabilityInTree(TestabilityRegistry registry, dynamic elem, bool findInAncestors) {
|
||||
if (elem == null) {
|
||||
return null;
|
||||
}
|
||||
var t = registry.getTestability(elem);
|
||||
if (t != null) {
|
||||
return t;
|
||||
} else if (!findInAncestors) {
|
||||
return null;
|
||||
}
|
||||
if (DOM.isShadowRoot(elem)) {
|
||||
return this.findTestabilityInTree(registry, DOM.getHost(elem), true);
|
||||
}
|
||||
return this.findTestabilityInTree(registry, DOM.parentElement(elem), true);
|
||||
}
|
||||
|
||||
js.JsObject _createRegistry(TestabilityRegistry registry) {
|
||||
var object = new js.JsObject(js.context['Object']);
|
||||
object['getAngularTestability'] =
|
||||
_jsify((Element elem, bool findInAncestors) {
|
||||
var testability = registry.findTestabilityInTree(elem, findInAncestors);
|
||||
return testability == null
|
||||
? null
|
||||
: _jsify(new PublicTestability(testability));
|
||||
});
|
||||
object['getAllAngularTestabilities'] = _jsify(() {
|
||||
var publicTestabilities = registry
|
||||
.getAllTestabilities()
|
||||
.map((testability) => new PublicTestability(testability));
|
||||
return _jsify(publicTestabilities);
|
||||
});
|
||||
return object;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
import {Map, MapWrapper, ListWrapper} from 'angular2/src/facade/collection';
|
||||
import {CONST, CONST_EXPR, global, isPresent} from 'angular2/src/facade/lang';
|
||||
import {BaseException, WrappedException} from 'angular2/src/facade/exceptions';
|
||||
import {PromiseWrapper, ObservableWrapper} from 'angular2/src/facade/async';
|
||||
|
||||
import {DOM} from 'angular2/src/core/dom/dom_adapter';
|
||||
|
||||
import {
|
||||
Injectable,
|
||||
TestabilityRegistry,
|
||||
Testability,
|
||||
GetTestability,
|
||||
setTestabilityGetter
|
||||
} from 'angular2/core';
|
||||
|
||||
class PublicTestability {
|
||||
/** @internal */
|
||||
_testability: Testability;
|
||||
|
||||
constructor(testability: Testability) { this._testability = testability; }
|
||||
|
||||
isStable(): boolean { return this._testability.isStable(); }
|
||||
|
||||
whenStable(callback: Function) { this._testability.whenStable(callback); }
|
||||
|
||||
findBindings(using: any, provider: string, exactMatch: boolean): any[] {
|
||||
return this.findProviders(using, provider, exactMatch);
|
||||
}
|
||||
|
||||
findProviders(using: any, provider: string, exactMatch: boolean): any[] {
|
||||
return this._testability.findBindings(using, provider, exactMatch);
|
||||
}
|
||||
}
|
||||
|
||||
export class BrowserGetTestability implements GetTestability {
|
||||
static init() { setTestabilityGetter(new BrowserGetTestability()); }
|
||||
|
||||
addToWindow(registry: TestabilityRegistry): void {
|
||||
global.getAngularTestability = (elem: any, findInAncestors: boolean = true) => {
|
||||
var testability = registry.findTestabilityInTree(elem, findInAncestors);
|
||||
if (testability == null) {
|
||||
throw new Error('Could not find testability for element.');
|
||||
}
|
||||
return new PublicTestability(testability);
|
||||
};
|
||||
|
||||
global.getAllAngularTestabilities = () => {
|
||||
var testabilities = registry.getAllTestabilities();
|
||||
return testabilities.map((testability) => { return new PublicTestability(testability); });
|
||||
};
|
||||
}
|
||||
|
||||
findTestabilityInTree(registry: TestabilityRegistry, elem: any,
|
||||
findInAncestors: boolean): Testability {
|
||||
if (elem == null) {
|
||||
return null;
|
||||
}
|
||||
var t = registry.getTestability(elem);
|
||||
if (isPresent(t)) {
|
||||
return t;
|
||||
} else if (!findInAncestors) {
|
||||
return null;
|
||||
}
|
||||
if (DOM.isShadowRoot(elem)) {
|
||||
return this.findTestabilityInTree(registry, DOM.getHost(elem), true);
|
||||
}
|
||||
return this.findTestabilityInTree(registry, DOM.parentElement(elem), true);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
import {DOM} from 'angular2/src/core/dom/dom_adapter';
|
||||
|
||||
/**
|
||||
* A service that can be used to get and set the title of a current HTML document.
|
||||
*
|
||||
* Since an Angular 2 application can't be bootstrapped on the entire HTML document (`<html>` tag)
|
||||
* it is not possible to bind to the `text` property of the `HTMLTitleElement` elements
|
||||
* (representing the `<title>` tag). Instead, this service can be used to set and get the current
|
||||
* title value.
|
||||
*/
|
||||
export class Title {
|
||||
/**
|
||||
* Get the title of the current HTML document.
|
||||
* @returns {string}
|
||||
*/
|
||||
getTitle(): string { return DOM.getTitle(); }
|
||||
|
||||
/**
|
||||
* Set the title of the current HTML document.
|
||||
* @param newTitle
|
||||
*/
|
||||
setTitle(newTitle: string) { DOM.setTitle(newTitle); }
|
||||
}
|
||||
Reference in New Issue
Block a user