Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(863)

Unified Diff: pkg/custom_element/lib/custom_element.dart

Issue 20886002: add custom_element package (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Created 7 years, 5 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « no previous file | pkg/custom_element/lib/src/custom_tag_name.dart » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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);
+}
« no previous file with comments | « no previous file | pkg/custom_element/lib/src/custom_tag_name.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698