Index: pkg/custom_element/lib/custom_element.dart |
diff --git a/pkg/custom_element/lib/custom_element.dart b/pkg/custom_element/lib/custom_element.dart |
new file mode 100644 |
index 0000000000000000000000000000000000000000..e5c905824cf9f6ce69edb4085af14f36dc4a55b7 |
--- /dev/null |
+++ b/pkg/custom_element/lib/custom_element.dart |
@@ -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); |
+} |