mirror of
https://github.com/dart-lang/sdk
synced 2024-11-02 12:24:24 +00:00
add custom_element package
code is from https://github.com/dart-lang/web-ui/blob/polymer/lib/custom_element.dart Includes a test for the polyfill. It also includes a static analysis test to catch the common errors of forgetting to add an Element API. Because we had analysis errors in "observe" and "mdv" packages too, I added tests for them too, and fixed the issues. R=sigmund@google.com Review URL: https://codereview.chromium.org//20886002 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart@25591 260f80e4-7a28-3924-810f-c04153c831b5
This commit is contained in:
parent
4aa77647cc
commit
9fa9329f90
11 changed files with 866 additions and 9 deletions
705
pkg/custom_element/lib/custom_element.dart
Normal file
705
pkg/custom_element/lib/custom_element.dart
Normal file
|
@ -0,0 +1,705 @@
|
|||
// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
|
||||
// for details. All rights reserved. Use of this source code is governed by a
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
/**
|
||||
* Custom Elements let authors define their own elements. Authors associate code
|
||||
* with custom tag names, and then use those custom tag names as they would any
|
||||
* standard tag. See <www.polymer-project.org/platform/custom-elements.html>
|
||||
* for more information.
|
||||
*/
|
||||
library custom_element;
|
||||
|
||||
import 'dart:async';
|
||||
import 'dart:html';
|
||||
import 'package:mdv/mdv.dart' as mdv;
|
||||
import 'package:meta/meta.dart';
|
||||
import 'src/custom_tag_name.dart';
|
||||
|
||||
// TODO(jmesserly): replace with a real custom element polyfill.
|
||||
// This is just something temporary.
|
||||
/**
|
||||
* *Warning*: this implementation is a work in progress. It only implements
|
||||
* the specification partially.
|
||||
*
|
||||
* Registers a custom HTML element with [localName] and the associated
|
||||
* constructor. This will ensure the element is detected and
|
||||
*
|
||||
* See the specification at:
|
||||
* <https://dvcs.w3.org/hg/webcomponents/raw-file/tip/spec/custom/index.html>
|
||||
*/
|
||||
void registerCustomElement(String localName, CustomElement create()) {
|
||||
if (_customElements == null) {
|
||||
_customElements = {};
|
||||
CustomElement.templateCreated.add(initCustomElements);
|
||||
// TODO(jmesserly): use MutationObserver to watch for inserts?
|
||||
}
|
||||
|
||||
if (!isCustomTag(localName)) {
|
||||
throw new ArgumentError('$localName is not a valid custom element name, '
|
||||
'it should have at least one dash and not be a reserved name.');
|
||||
}
|
||||
|
||||
if (_customElements.containsKey(localName)) {
|
||||
throw new ArgumentError('custom element $localName already registered.');
|
||||
}
|
||||
|
||||
// TODO(jmesserly): validate this is a valid tag name, not a selector.
|
||||
_customElements[localName] = create;
|
||||
|
||||
// Initialize elements already on the page.
|
||||
for (var query in [localName, '[is=$localName]']) {
|
||||
for (var element in document.queryAll(query)) {
|
||||
_initCustomElement(element, create);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new element and returns it. If the [localName] has been registered
|
||||
* with [registerCustomElement], it will create the custom element.
|
||||
*
|
||||
* This is similar to `new Element.tag` in Dart and `document.createElement`
|
||||
* in JavaScript.
|
||||
*
|
||||
* *Warning*: this API is temporary until [dart:html] supports custom elements.
|
||||
*/
|
||||
Element createElement(String localName) =>
|
||||
initCustomElements(new Element.tag(localName));
|
||||
|
||||
/**
|
||||
* Similar to `new Element.html`, but automatically creates registed custom
|
||||
* elements.
|
||||
* *Warning*: this API is temporary until [dart:html] supports custom elements.
|
||||
*/
|
||||
Element createElementFromHtml(String html) =>
|
||||
initCustomElements(new Element.html(html));
|
||||
|
||||
/**
|
||||
* Initialize any registered custom elements recursively in the [node] tree.
|
||||
* For convenience this returns the [node] instance.
|
||||
*
|
||||
* *Warning*: this API is temporary until [dart:html] supports custom elements.
|
||||
*/
|
||||
Node initCustomElements(Node node) {
|
||||
for (var c = node.firstChild; c != null; c = c.nextNode) {
|
||||
initCustomElements(c);
|
||||
}
|
||||
if (node is Element) {
|
||||
var ctor = _customElements[node.localName];
|
||||
if (ctor == null) {
|
||||
var attr = node.attributes['is'];
|
||||
if (attr != null) ctor = _customElements[attr];
|
||||
}
|
||||
if (ctor != null) _initCustomElement(node, ctor);
|
||||
}
|
||||
return node;
|
||||
}
|
||||
|
||||
/**
|
||||
* The base class for all Dart web components. In addition to the [Element]
|
||||
* interface, it also provides lifecycle methods:
|
||||
* - [created]
|
||||
* - [inserted]
|
||||
* - [attributeChanged]
|
||||
* - [removed]
|
||||
*/
|
||||
class CustomElement implements Element {
|
||||
/** The web component element wrapped by this class. */
|
||||
Element _host;
|
||||
List _shadowRoots;
|
||||
|
||||
/**
|
||||
* Shadow roots generated by dwc for each custom element, indexed by the
|
||||
* custom element tag name.
|
||||
*/
|
||||
Map<String, dynamic> _generatedRoots = {};
|
||||
|
||||
/**
|
||||
* Temporary property until components extend [Element]. An element can
|
||||
* only be associated with one host, and it is an error to use a web component
|
||||
* without an associated host element.
|
||||
*/
|
||||
Element get host {
|
||||
if (_host == null) throw new StateError('host element has not been set.');
|
||||
return _host;
|
||||
}
|
||||
|
||||
set host(Element value) {
|
||||
if (value == null) {
|
||||
throw new ArgumentError('host must not be null.');
|
||||
}
|
||||
// TODO(jmesserly): xtag used to return "null" if unset, now it checks for
|
||||
// "this". Temporarily allow both.
|
||||
var xtag = value.xtag;
|
||||
if (xtag != null && xtag != value) {
|
||||
throw new ArgumentError('host must not have its xtag property set.');
|
||||
}
|
||||
if (_host != null) {
|
||||
throw new StateError('host can only be set once.');
|
||||
}
|
||||
|
||||
value.xtag = this;
|
||||
_host = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* **Note**: This is an implementation helper and should not need to be called
|
||||
* from your code.
|
||||
*
|
||||
* Creates the [ShadowRoot] backing this component.
|
||||
*/
|
||||
createShadowRoot([String componentName]) {
|
||||
var root = host.createShadowRoot();
|
||||
if (componentName != null) {
|
||||
_generatedRoots[componentName] = root;
|
||||
}
|
||||
return root;
|
||||
}
|
||||
|
||||
getShadowRoot(String componentName) => _generatedRoots[componentName];
|
||||
|
||||
|
||||
/**
|
||||
* *Warning*: This is an implementation helper for Custom Elements and
|
||||
* should not be used in your code.
|
||||
*
|
||||
* Clones the template, instantiates custom elements and hooks events, then
|
||||
* returns it.
|
||||
*/
|
||||
DocumentFragment cloneTemplate(DocumentFragment shadowTemplate) {
|
||||
var result = shadowTemplate.clone(true);
|
||||
// TODO(jmesserly): should bindModel ensure this happens?
|
||||
TemplateElement.bootstrap(result);
|
||||
if (_templateCreated != null) {
|
||||
for (var callback in _templateCreated) callback(result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// TODO(jmesserly): ideally this would be a stream, but they don't allow
|
||||
// reentrancy.
|
||||
static Set<DocumentFragmentCreated> _templateCreated;
|
||||
|
||||
/**
|
||||
* *Warning*: This is an implementation helper for Custom Elements and
|
||||
* should not be used in your code.
|
||||
*
|
||||
* This event is fired whenever a template is instantiated via
|
||||
* [cloneTemplate] or via [Element.createInstance]
|
||||
*/
|
||||
// TODO(jmesserly): This is a hack, and is neccesary for the polyfill
|
||||
// because custom elements are not upgraded during clone()
|
||||
static Set<DocumentFragmentCreated> get templateCreated {
|
||||
if (_templateCreated == null) {
|
||||
_templateCreated = new Set<DocumentFragmentCreated>();
|
||||
mdv.instanceCreated.listen((value) {
|
||||
for (var callback in _templateCreated) callback(value);
|
||||
});
|
||||
}
|
||||
return _templateCreated;
|
||||
}
|
||||
/**
|
||||
* Invoked when this component gets created.
|
||||
* Note that [root] will be a [ShadowRoot] if the browser supports Shadow DOM.
|
||||
*/
|
||||
void created() {}
|
||||
|
||||
/** Invoked when this component gets inserted in the DOM tree. */
|
||||
void inserted() {}
|
||||
|
||||
/** Invoked when this component is removed from the DOM tree. */
|
||||
void removed() {}
|
||||
|
||||
// TODO(jmesserly): how do we implement this efficiently?
|
||||
// See https://github.com/dart-lang/web-ui/issues/37
|
||||
/** Invoked when any attribute of the component is modified. */
|
||||
void attributeChanged(String name, String oldValue, String newValue) {}
|
||||
|
||||
get model => host.model;
|
||||
|
||||
void set model(newModel) {
|
||||
host.model = newModel;
|
||||
}
|
||||
|
||||
get templateInstance => host.templateInstance;
|
||||
get isTemplate => host.isTemplate;
|
||||
get ref => host.ref;
|
||||
get content => host.content;
|
||||
DocumentFragment createInstance(model, BindingDelegate delegate) =>
|
||||
host.createInstance(model, delegate);
|
||||
createBinding(String name, model, String path) =>
|
||||
host.createBinding(name, model, path);
|
||||
void bind(String name, model, String path) => host.bind(name, model, path);
|
||||
void unbind(String name) => host.unbind(name);
|
||||
void unbindAll() => host.unbindAll();
|
||||
get bindings => host.bindings;
|
||||
BindingDelegate get bindingDelegate => host.bindingDelegate;
|
||||
set bindingDelegate(BindingDelegate value) { host.bindingDelegate = value; }
|
||||
|
||||
// TODO(jmesserly): this forwarding is temporary until Dart supports
|
||||
// subclassing Elements.
|
||||
// TODO(jmesserly): we were missing the setter for title, are other things
|
||||
// missing setters?
|
||||
|
||||
List<Node> get nodes => host.nodes;
|
||||
|
||||
set nodes(Iterable<Node> value) { host.nodes = value; }
|
||||
|
||||
/**
|
||||
* Replaces this node with another node.
|
||||
*/
|
||||
Node replaceWith(Node otherNode) { host.replaceWith(otherNode); }
|
||||
|
||||
/**
|
||||
* Removes this node from the DOM.
|
||||
*/
|
||||
void remove() => host.remove();
|
||||
|
||||
Node get nextNode => host.nextNode;
|
||||
|
||||
String get nodeName => host.nodeName;
|
||||
|
||||
Document get document => host.document;
|
||||
|
||||
Node get previousNode => host.previousNode;
|
||||
|
||||
String get text => host.text;
|
||||
|
||||
set text(String v) { host.text = v; }
|
||||
|
||||
bool contains(Node other) => host.contains(other);
|
||||
|
||||
bool hasChildNodes() => host.hasChildNodes();
|
||||
|
||||
Node insertBefore(Node newChild, Node refChild) =>
|
||||
host.insertBefore(newChild, refChild);
|
||||
|
||||
Node insertAllBefore(Iterable<Node> newChild, Node refChild) =>
|
||||
host.insertAllBefore(newChild, refChild);
|
||||
|
||||
Map<String, String> get attributes => host.attributes;
|
||||
set attributes(Map<String, String> value) {
|
||||
host.attributes = value;
|
||||
}
|
||||
|
||||
List<Element> get elements => host.children;
|
||||
|
||||
set elements(List<Element> value) {
|
||||
host.children = value;
|
||||
}
|
||||
|
||||
List<Element> get children => host.children;
|
||||
|
||||
set children(List<Element> value) {
|
||||
host.children = value;
|
||||
}
|
||||
|
||||
Set<String> get classes => host.classes;
|
||||
|
||||
set classes(Iterable<String> value) {
|
||||
host.classes = value;
|
||||
}
|
||||
|
||||
CssRect get contentEdge => host.contentEdge;
|
||||
CssRect get paddingEdge => host.paddingEdge;
|
||||
CssRect get borderEdge => host.borderEdge;
|
||||
CssRect get marginEdge => host.marginEdge;
|
||||
Point get documentOffset => host.documentOffset;
|
||||
Point offsetTo(Element parent) => host.offsetTo(parent);
|
||||
|
||||
Map<String, String> getNamespacedAttributes(String namespace) =>
|
||||
host.getNamespacedAttributes(namespace);
|
||||
|
||||
CssStyleDeclaration getComputedStyle([String pseudoElement])
|
||||
=> host.getComputedStyle(pseudoElement);
|
||||
|
||||
Element clone(bool deep) => host.clone(deep);
|
||||
|
||||
Element get parent => host.parent;
|
||||
|
||||
Node get parentNode => host.parentNode;
|
||||
|
||||
String get nodeValue => host.nodeValue;
|
||||
|
||||
@deprecated
|
||||
// TODO(sigmund): restore the old return type and call host.on when
|
||||
// dartbug.com/8131 is fixed.
|
||||
dynamic get on { throw new UnsupportedError('on is deprecated'); }
|
||||
|
||||
String get contentEditable => host.contentEditable;
|
||||
set contentEditable(String v) { host.contentEditable = v; }
|
||||
|
||||
String get dir => host.dir;
|
||||
set dir(String v) { host.dir = v; }
|
||||
|
||||
bool get draggable => host.draggable;
|
||||
set draggable(bool v) { host.draggable = v; }
|
||||
|
||||
bool get hidden => host.hidden;
|
||||
set hidden(bool v) { host.hidden = v; }
|
||||
|
||||
String get id => host.id;
|
||||
set id(String v) { host.id = v; }
|
||||
|
||||
String get innerHTML => host.innerHtml;
|
||||
|
||||
void set innerHTML(String v) {
|
||||
host.innerHtml = v;
|
||||
}
|
||||
|
||||
String get innerHtml => host.innerHtml;
|
||||
void set innerHtml(String v) {
|
||||
host.innerHtml = v;
|
||||
}
|
||||
|
||||
bool get isContentEditable => host.isContentEditable;
|
||||
|
||||
String get lang => host.lang;
|
||||
set lang(String v) { host.lang = v; }
|
||||
|
||||
String get outerHtml => host.outerHtml;
|
||||
|
||||
bool get spellcheck => host.spellcheck;
|
||||
set spellcheck(bool v) { host.spellcheck = v; }
|
||||
|
||||
int get tabIndex => host.tabIndex;
|
||||
set tabIndex(int i) { host.tabIndex = i; }
|
||||
|
||||
String get title => host.title;
|
||||
|
||||
set title(String value) { host.title = value; }
|
||||
|
||||
bool get translate => host.translate;
|
||||
set translate(bool v) { host.translate = v; }
|
||||
|
||||
String get dropzone => host.dropzone;
|
||||
set dropzone(String v) { host.dropzone = v; }
|
||||
|
||||
void click() { host.click(); }
|
||||
|
||||
InputMethodContext getInputContext() => host.getInputContext();
|
||||
|
||||
Element insertAdjacentElement(String where, Element element) =>
|
||||
host.insertAdjacentElement(where, element);
|
||||
|
||||
void insertAdjacentHtml(String where, String html) {
|
||||
host.insertAdjacentHtml(where, html);
|
||||
}
|
||||
|
||||
void insertAdjacentText(String where, String text) {
|
||||
host.insertAdjacentText(where, text);
|
||||
}
|
||||
|
||||
Map<String, String> get dataset => host.dataset;
|
||||
|
||||
set dataset(Map<String, String> value) {
|
||||
host.dataset = value;
|
||||
}
|
||||
|
||||
Element get nextElementSibling => host.nextElementSibling;
|
||||
|
||||
Element get offsetParent => host.offsetParent;
|
||||
|
||||
Element get previousElementSibling => host.previousElementSibling;
|
||||
|
||||
CssStyleDeclaration get style => host.style;
|
||||
|
||||
String get tagName => host.tagName;
|
||||
|
||||
String get pseudo => host.pseudo;
|
||||
|
||||
void set pseudo(String value) {
|
||||
host.pseudo = value;
|
||||
}
|
||||
|
||||
// Note: we are not polyfilling the shadow root here. This will be fixed when
|
||||
// we migrate to the JS Shadow DOM polyfills. You can still use getShadowRoot
|
||||
// to retrieve a node that behaves as the shadow root when Shadow DOM is not
|
||||
// enabled.
|
||||
ShadowRoot get shadowRoot => host.shadowRoot;
|
||||
|
||||
void blur() { host.blur(); }
|
||||
|
||||
void focus() { host.focus(); }
|
||||
|
||||
void scrollByLines(int lines) {
|
||||
host.scrollByLines(lines);
|
||||
}
|
||||
|
||||
void scrollByPages(int pages) {
|
||||
host.scrollByPages(pages);
|
||||
}
|
||||
|
||||
void scrollIntoView([ScrollAlignment alignment]) {
|
||||
host.scrollIntoView(alignment);
|
||||
}
|
||||
|
||||
bool matches(String selectors) => host.matches(selectors);
|
||||
|
||||
@deprecated
|
||||
void requestFullScreen(int flags) { requestFullscreen(); }
|
||||
|
||||
void requestFullscreen() { host.requestFullscreen(); }
|
||||
|
||||
void requestPointerLock() { host.requestPointerLock(); }
|
||||
|
||||
Element query(String selectors) => host.query(selectors);
|
||||
|
||||
ElementList queryAll(String selectors) => host.queryAll(selectors);
|
||||
|
||||
HtmlCollection get $dom_children => host.$dom_children;
|
||||
|
||||
int get $dom_childElementCount => host.$dom_childElementCount;
|
||||
|
||||
String get className => host.className;
|
||||
set className(String value) { host.className = value; }
|
||||
|
||||
@deprecated
|
||||
int get clientHeight => client.height;
|
||||
|
||||
@deprecated
|
||||
int get clientLeft => client.left;
|
||||
|
||||
@deprecated
|
||||
int get clientTop => client.top;
|
||||
|
||||
@deprecated
|
||||
int get clientWidth => client.width;
|
||||
|
||||
Rect get client => host.client;
|
||||
|
||||
Element get $dom_firstElementChild => host.$dom_firstElementChild;
|
||||
|
||||
Element get $dom_lastElementChild => host.$dom_lastElementChild;
|
||||
|
||||
@deprecated
|
||||
int get offsetHeight => offset.height;
|
||||
|
||||
@deprecated
|
||||
int get offsetLeft => offset.left;
|
||||
|
||||
@deprecated
|
||||
int get offsetTop => offset.top;
|
||||
|
||||
@deprecated
|
||||
int get offsetWidth => offset.width;
|
||||
|
||||
Rect get offset => host.offset;
|
||||
|
||||
int get scrollHeight => host.scrollHeight;
|
||||
|
||||
int get scrollLeft => host.scrollLeft;
|
||||
|
||||
int get scrollTop => host.scrollTop;
|
||||
|
||||
set scrollLeft(int value) { host.scrollLeft = value; }
|
||||
|
||||
set scrollTop(int value) { host.scrollTop = value; }
|
||||
|
||||
int get scrollWidth => host.scrollWidth;
|
||||
|
||||
String $dom_getAttribute(String name) =>
|
||||
host.$dom_getAttribute(name);
|
||||
|
||||
String $dom_getAttributeNS(String namespaceUri, String localName) =>
|
||||
host.$dom_getAttributeNS(namespaceUri, localName);
|
||||
|
||||
String $dom_setAttributeNS(
|
||||
String namespaceUri, String localName, String value) {
|
||||
host.$dom_setAttributeNS(namespaceUri, localName, value);
|
||||
}
|
||||
|
||||
bool $dom_hasAttributeNS(String namespaceUri, String localName) =>
|
||||
host.$dom_hasAttributeNS(namespaceUri, localName);
|
||||
|
||||
void $dom_removeAttributeNS(String namespaceUri, String localName) =>
|
||||
host.$dom_removeAttributeNS(namespaceUri, localName);
|
||||
|
||||
Rect getBoundingClientRect() => host.getBoundingClientRect();
|
||||
|
||||
List<Rect> getClientRects() => host.getClientRects();
|
||||
|
||||
List<Node> getElementsByClassName(String name) =>
|
||||
host.getElementsByClassName(name);
|
||||
|
||||
List<Node> $dom_getElementsByTagName(String name) =>
|
||||
host.$dom_getElementsByTagName(name);
|
||||
|
||||
bool $dom_hasAttribute(String name) =>
|
||||
host.$dom_hasAttribute(name);
|
||||
|
||||
List<Node> $dom_querySelectorAll(String selectors) =>
|
||||
host.$dom_querySelectorAll(selectors);
|
||||
|
||||
void $dom_removeAttribute(String name) =>
|
||||
host.$dom_removeAttribute(name);
|
||||
|
||||
void $dom_setAttribute(String name, String value) =>
|
||||
host.$dom_setAttribute(name, value);
|
||||
|
||||
get $dom_attributes => host.$dom_attributes;
|
||||
|
||||
List<Node> get $dom_childNodes => host.$dom_childNodes;
|
||||
|
||||
Node get firstChild => host.firstChild;
|
||||
|
||||
Node get lastChild => host.lastChild;
|
||||
|
||||
String get localName => host.localName;
|
||||
String get $dom_localName => host.$dom_localName;
|
||||
|
||||
String get namespaceUri => host.namespaceUri;
|
||||
String get $dom_namespaceUri => host.$dom_namespaceUri;
|
||||
|
||||
int get nodeType => host.nodeType;
|
||||
|
||||
void $dom_addEventListener(String type, EventListener listener,
|
||||
[bool useCapture]) {
|
||||
host.$dom_addEventListener(type, listener, useCapture);
|
||||
}
|
||||
|
||||
bool dispatchEvent(Event event) => host.dispatchEvent(event);
|
||||
|
||||
Node $dom_removeChild(Node oldChild) => host.$dom_removeChild(oldChild);
|
||||
|
||||
void $dom_removeEventListener(String type, EventListener listener,
|
||||
[bool useCapture]) {
|
||||
host.$dom_removeEventListener(type, listener, useCapture);
|
||||
}
|
||||
|
||||
Node $dom_replaceChild(Node newChild, Node oldChild) =>
|
||||
host.$dom_replaceChild(newChild, oldChild);
|
||||
|
||||
get xtag => host.xtag;
|
||||
|
||||
set xtag(value) { host.xtag = value; }
|
||||
|
||||
Node append(Node e) => host.append(e);
|
||||
|
||||
void appendText(String text) => host.appendText(text);
|
||||
|
||||
void appendHtml(String html) => host.appendHtml(html);
|
||||
|
||||
void $dom_scrollIntoView([bool alignWithTop]) {
|
||||
if (alignWithTop == null) {
|
||||
host.$dom_scrollIntoView();
|
||||
} else {
|
||||
host.$dom_scrollIntoView(alignWithTop);
|
||||
}
|
||||
}
|
||||
|
||||
void $dom_scrollIntoViewIfNeeded([bool centerIfNeeded]) {
|
||||
if (centerIfNeeded == null) {
|
||||
host.$dom_scrollIntoViewIfNeeded();
|
||||
} else {
|
||||
host.$dom_scrollIntoViewIfNeeded(centerIfNeeded);
|
||||
}
|
||||
}
|
||||
|
||||
String get regionOverset => host.regionOverset;
|
||||
|
||||
List<Range> getRegionFlowRanges() => host.getRegionFlowRanges();
|
||||
|
||||
// TODO(jmesserly): rename "created" to "onCreated".
|
||||
void onCreated() => created();
|
||||
|
||||
Stream<Event> get onAbort => host.onAbort;
|
||||
Stream<Event> get onBeforeCopy => host.onBeforeCopy;
|
||||
Stream<Event> get onBeforeCut => host.onBeforeCut;
|
||||
Stream<Event> get onBeforePaste => host.onBeforePaste;
|
||||
Stream<Event> get onBlur => host.onBlur;
|
||||
Stream<Event> get onChange => host.onChange;
|
||||
Stream<MouseEvent> get onClick => host.onClick;
|
||||
Stream<MouseEvent> get onContextMenu => host.onContextMenu;
|
||||
Stream<Event> get onCopy => host.onCopy;
|
||||
Stream<Event> get onCut => host.onCut;
|
||||
Stream<Event> get onDoubleClick => host.onDoubleClick;
|
||||
Stream<MouseEvent> get onDrag => host.onDrag;
|
||||
Stream<MouseEvent> get onDragEnd => host.onDragEnd;
|
||||
Stream<MouseEvent> get onDragEnter => host.onDragEnter;
|
||||
Stream<MouseEvent> get onDragLeave => host.onDragLeave;
|
||||
Stream<MouseEvent> get onDragOver => host.onDragOver;
|
||||
Stream<MouseEvent> get onDragStart => host.onDragStart;
|
||||
Stream<MouseEvent> get onDrop => host.onDrop;
|
||||
Stream<Event> get onError => host.onError;
|
||||
Stream<Event> get onFocus => host.onFocus;
|
||||
Stream<Event> get onInput => host.onInput;
|
||||
Stream<Event> get onInvalid => host.onInvalid;
|
||||
Stream<KeyboardEvent> get onKeyDown => host.onKeyDown;
|
||||
Stream<KeyboardEvent> get onKeyPress => host.onKeyPress;
|
||||
Stream<KeyboardEvent> get onKeyUp => host.onKeyUp;
|
||||
Stream<Event> get onLoad => host.onLoad;
|
||||
Stream<MouseEvent> get onMouseDown => host.onMouseDown;
|
||||
Stream<MouseEvent> get onMouseMove => host.onMouseMove;
|
||||
Stream<Event> get onFullscreenChange => host.onFullscreenChange;
|
||||
Stream<Event> get onFullscreenError => host.onFullscreenError;
|
||||
Stream<Event> get onPaste => host.onPaste;
|
||||
Stream<Event> get onReset => host.onReset;
|
||||
Stream<Event> get onScroll => host.onScroll;
|
||||
Stream<Event> get onSearch => host.onSearch;
|
||||
Stream<Event> get onSelect => host.onSelect;
|
||||
Stream<Event> get onSelectStart => host.onSelectStart;
|
||||
Stream<Event> get onSubmit => host.onSubmit;
|
||||
Stream<MouseEvent> get onMouseOut => host.onMouseOut;
|
||||
Stream<MouseEvent> get onMouseOver => host.onMouseOver;
|
||||
Stream<MouseEvent> get onMouseUp => host.onMouseUp;
|
||||
Stream<TouchEvent> get onTouchCancel => host.onTouchCancel;
|
||||
Stream<TouchEvent> get onTouchEnd => host.onTouchEnd;
|
||||
Stream<TouchEvent> get onTouchEnter => host.onTouchEnter;
|
||||
Stream<TouchEvent> get onTouchLeave => host.onTouchLeave;
|
||||
Stream<TouchEvent> get onTouchMove => host.onTouchMove;
|
||||
Stream<TouchEvent> get onTouchStart => host.onTouchStart;
|
||||
Stream<TransitionEvent> get onTransitionEnd => host.onTransitionEnd;
|
||||
|
||||
// TODO(sigmund): do the normal forwarding when dartbug.com/7919 is fixed.
|
||||
Stream<WheelEvent> get onMouseWheel {
|
||||
throw new UnsupportedError('onMouseWheel is not supported');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
typedef DocumentFragmentCreated(DocumentFragment fragment);
|
||||
|
||||
Map<String, Function> _customElements;
|
||||
|
||||
void _initCustomElement(Element node, CustomElement ctor()) {
|
||||
CustomElement element = ctor();
|
||||
element.host = node;
|
||||
|
||||
// TODO(jmesserly): replace lifecycle stuff with a proper polyfill.
|
||||
element.created();
|
||||
|
||||
_registerLifecycleInsert(element);
|
||||
}
|
||||
|
||||
void _registerLifecycleInsert(CustomElement element) {
|
||||
runAsync(() {
|
||||
// TODO(jmesserly): bottom up or top down insert?
|
||||
var node = element.host;
|
||||
|
||||
// TODO(jmesserly): need a better check to see if the node has been removed.
|
||||
if (node.parentNode == null) return;
|
||||
|
||||
_registerLifecycleRemove(element);
|
||||
element.inserted();
|
||||
});
|
||||
}
|
||||
|
||||
void _registerLifecycleRemove(CustomElement element) {
|
||||
// TODO(jmesserly): need fallback or polyfill for MutationObserver.
|
||||
if (!MutationObserver.supported) return;
|
||||
|
||||
new MutationObserver((records, observer) {
|
||||
var node = element.host;
|
||||
for (var record in records) {
|
||||
for (var removed in record.removedNodes) {
|
||||
if (identical(node, removed)) {
|
||||
observer.disconnect();
|
||||
element.removed();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}).observe(element.parentNode, childList: true);
|
||||
}
|
27
pkg/custom_element/lib/src/custom_tag_name.dart
Normal file
27
pkg/custom_element/lib/src/custom_tag_name.dart
Normal file
|
@ -0,0 +1,27 @@
|
|||
// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
|
||||
// for details. All rights reserved. Use of this source code is governed by a
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
library custom_element.src.custom_tag_name;
|
||||
|
||||
/**
|
||||
* Returns true if this is a valid custom element name. See:
|
||||
* <https://dvcs.w3.org/hg/webcomponents/raw-file/tip/spec/custom/index.html#dfn-custom-element-name>
|
||||
*/
|
||||
bool isCustomTag(String name) {
|
||||
if (!name.contains('-')) return false;
|
||||
|
||||
// These names have meaning in SVG or MathML, so they aren't allowed as custom
|
||||
// tags.
|
||||
var invalidNames = const {
|
||||
'annotation-xml': '',
|
||||
'color-profile': '',
|
||||
'font-face': '',
|
||||
'font-face-src': '',
|
||||
'font-face-uri': '',
|
||||
'font-face-format': '',
|
||||
'font-face-name': '',
|
||||
'missing-glyph': '',
|
||||
};
|
||||
return !invalidNames.containsKey(name);
|
||||
}
|
12
pkg/custom_element/pubspec.yaml
Normal file
12
pkg/custom_element/pubspec.yaml
Normal file
|
@ -0,0 +1,12 @@
|
|||
name: custom_element
|
||||
author: "Web UI Team <web-ui-dev@dartlang.org>"
|
||||
homepage: http://www.dartlang.org/
|
||||
description: >
|
||||
Custom Elements let authors define their own elements. Authors associate code
|
||||
with custom tag names, and then use those custom tag names as they would any
|
||||
standard tag.
|
||||
dependencies:
|
||||
# TODO(jmesserly): fix this, we should not depend on MDV.
|
||||
mdv: any
|
||||
dev_dependencies:
|
||||
unittest: any
|
13
pkg/custom_element/test/analyzer_test.dart
Normal file
13
pkg/custom_element/test/analyzer_test.dart
Normal file
|
@ -0,0 +1,13 @@
|
|||
// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
|
||||
// for details. All rights reserved. Use of this source code is governed by a
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
library custom_element.test.analyzer_test;
|
||||
|
||||
import 'package:custom_element/custom_element.dart';
|
||||
|
||||
// @static-clean
|
||||
|
||||
// This test ensures CustomElement compiles without errors.
|
||||
void main() {
|
||||
}
|
80
pkg/custom_element/test/custom_element_test.dart
Normal file
80
pkg/custom_element/test/custom_element_test.dart
Normal file
|
@ -0,0 +1,80 @@
|
|||
// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
|
||||
// for details. All rights reserved. Use of this source code is governed by a
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
library custom_element.test.custom_element_test;
|
||||
|
||||
import 'dart:async';
|
||||
import 'dart:html';
|
||||
import 'package:custom_element/custom_element.dart';
|
||||
import 'package:unittest/html_config.dart';
|
||||
import 'package:unittest/unittest.dart';
|
||||
|
||||
main() {
|
||||
useHtmlConfiguration();
|
||||
|
||||
test('register creates the element and calls lifecycle methods', () {
|
||||
// Add element to the page.
|
||||
var element = new Element.html('<fancy-button>foo bar</fancy-button>');
|
||||
document.body.nodes.add(element);
|
||||
|
||||
var xtag = null;
|
||||
registerCustomElement('fancy-button', () => xtag = new FancyButton());
|
||||
expect(xtag, isNotNull, reason: 'FancyButton was created');
|
||||
expect(element.xtag, xtag, reason: 'xtag pointer should be set');
|
||||
expect(xtag.host, element, reason: 'host pointer should be set');
|
||||
expect(xtag.lifecycle, ['created']);
|
||||
return new Future(() {
|
||||
expect(xtag.lifecycle, ['created', 'inserted']);
|
||||
element.remove();
|
||||
return new Future(() {
|
||||
expect(xtag.lifecycle, ['created', 'inserted', 'removed']);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test('create a component in code', () {
|
||||
var element = createElement('super-button');
|
||||
expect(element.xtag, element, reason: 'element not registered');
|
||||
|
||||
var xtag = null;
|
||||
registerCustomElement('super-button', () => xtag = new FancyButton());
|
||||
|
||||
element = createElement('super-button');
|
||||
expect(xtag, isNotNull, reason: 'FancyButton was created');
|
||||
expect(element.xtag, xtag, reason: 'xtag pointer should be set');
|
||||
expect(xtag.host, element, reason: 'host pointer should be set');
|
||||
expect(xtag.lifecycle, ['created']);
|
||||
return new Future(() {
|
||||
expect(xtag.lifecycle, ['created'], reason: 'not inserted into document');
|
||||
|
||||
document.body.nodes.add(element);
|
||||
return new Future(() {
|
||||
expect(xtag.lifecycle, ['created'],
|
||||
reason: 'mutation observer not implemented yet');
|
||||
|
||||
element.remove();
|
||||
return new Future(() {
|
||||
expect(xtag.lifecycle, ['created'],
|
||||
reason: 'mutation observer not implemented yet');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
class FancyButton extends CustomElement {
|
||||
final lifecycle = [];
|
||||
created() {
|
||||
super.created();
|
||||
lifecycle.add('created');
|
||||
}
|
||||
inserted() {
|
||||
super.inserted();
|
||||
lifecycle.add('inserted');
|
||||
}
|
||||
removed() {
|
||||
super.removed();
|
||||
lifecycle.add('removed');
|
||||
}
|
||||
}
|
|
@ -159,7 +159,7 @@ bool _isSimpleBinding(List<String> tokens) =>
|
|||
* [TEXT, (PATH, TEXT)+] if there is at least one mustache.
|
||||
*/
|
||||
List<String> _parseMustacheTokens(String s) {
|
||||
if (s.isEmpty) return;
|
||||
if (s.isEmpty) return null;
|
||||
|
||||
var tokens = null;
|
||||
var length = s.length;
|
||||
|
|
12
pkg/mdv/test/analyzer_test.dart
Normal file
12
pkg/mdv/test/analyzer_test.dart
Normal file
|
@ -0,0 +1,12 @@
|
|||
// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
|
||||
// for details. All rights reserved. Use of this source code is governed by a
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
library mdv.test.analyzer_test;
|
||||
|
||||
import 'package:mdv/mdv.dart';
|
||||
|
||||
// @static-clean
|
||||
|
||||
// This test ensures MDV compiles without errors.
|
||||
void main() {}
|
|
@ -62,6 +62,12 @@ abstract class Observable {
|
|||
*/
|
||||
void notifyChange(ChangeRecord record);
|
||||
|
||||
/**
|
||||
* True if this object has any observers, and should call
|
||||
* [notifyChange] for changes.
|
||||
*/
|
||||
bool get hasObservers;
|
||||
|
||||
/**
|
||||
* Performs dirty checking of objects that inherit from [ObservableMixin].
|
||||
* This scans all observed objects using mirrors and determines if any fields
|
||||
|
@ -99,10 +105,6 @@ abstract class ObservableMixin implements Observable {
|
|||
return _changes.stream;
|
||||
}
|
||||
|
||||
/**
|
||||
* True if this object has any observers, and should call
|
||||
* [notifyPropertyChange] for changes.
|
||||
*/
|
||||
bool get hasObservers => _changes != null && _changes.hasListener;
|
||||
|
||||
void _observed() {
|
||||
|
|
|
@ -67,9 +67,7 @@ class ObservableMap<K, V> extends ChangeNotifierBase implements Map<K, V> {
|
|||
* you should use [toObservable].
|
||||
*/
|
||||
factory ObservableMap.from(Map<K, V> other) {
|
||||
var result = new ObservableMap<K, V>._createFromType(other);
|
||||
other.forEach((key, value) { result[key] = value; });
|
||||
return result;
|
||||
return new ObservableMap<K, V>._createFromType(other)..addAll(other);
|
||||
}
|
||||
|
||||
factory ObservableMap._createFromType(Map<K, V> other) {
|
||||
|
@ -114,6 +112,10 @@ class ObservableMap<K, V> extends ChangeNotifierBase implements Map<K, V> {
|
|||
}
|
||||
}
|
||||
|
||||
void addAll(Map<K, V> other) {
|
||||
other.forEach((K key, V value) { this[key] = value; });
|
||||
}
|
||||
|
||||
V putIfAbsent(K key, V ifAbsent()) {
|
||||
int len = _map.length;
|
||||
V result = _map.putIfAbsent(key, ifAbsent);
|
||||
|
|
|
@ -9,6 +9,9 @@ import 'package:observe/src/dirty_check.dart' as dirty_check;
|
|||
import 'package:unittest/unittest.dart';
|
||||
import 'observe_test_utils.dart';
|
||||
|
||||
// Note: this ensures we run the dartanalyzer on the @observe package.
|
||||
// @static-clean
|
||||
|
||||
const _VALUE = const Symbol('value');
|
||||
|
||||
main() {
|
||||
|
|
|
@ -171,8 +171,9 @@ dartdoc/test/dartdoc_test: Skip # See dartbug.com/4541.
|
|||
[ $runtime == dartium || $runtime == drt ]
|
||||
serialization/test/no_library_test: Skip # Expected Failure
|
||||
|
||||
# Skip mdv tests on command line VM, they only run in the browser.
|
||||
# Skip tests on the VM if the package depends on dart:html
|
||||
[ $runtime == vm ]
|
||||
custom_element: Skip
|
||||
mdv: Skip
|
||||
|
||||
[ $runtime == safari || $runtime == chrome || $runtime == ie9 || $runtime == ff || $runtime == dartium || $runtime == drt ]
|
||||
|
|
Loading…
Reference in a new issue