| // Copyright (c) 2016, 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. |
| |
| import 'dart:async'; |
| |
| import 'package:web/web.dart'; |
| |
| import 'package:observatory/src/elements/helpers/element_utils.dart'; |
| import 'package:observatory/utils.dart'; |
| |
| HTMLElement element(CustomElement e) => e.element; |
| |
| class CustomElement { |
| static Expando reverseElements = new Expando(); |
| static CustomElement reverse(HTMLElement element) => |
| reverseElements[element] as CustomElement; |
| |
| static List<CustomElement> toBeAttached = <CustomElement>[]; |
| static void drainAttached() { |
| // Send 'attached' to elements that have been attached to the document. |
| bool fired = false; |
| var connectedElements = toBeAttached |
| .where((CustomElement element) => element.element.isConnected) |
| .toList(); |
| for (CustomElement element in connectedElements) { |
| toBeAttached.remove(element); |
| element.attached(); |
| fired = true; |
| } |
| |
| if (toBeAttached.isEmpty) { |
| return; // Done. |
| } |
| |
| if (fired) { |
| // The 'attached' events above may have scheduled microtasks that will |
| // will add more CustomElements to be document, e.g. 'render'. |
| scheduleMicrotask(() => drainAttached()); |
| } |
| |
| while (!toBeAttached.isEmpty) { |
| // Either this element will never be attached or it will be attached |
| // after a turn of the outer event loop. Fire 'attached' in case it is |
| // the latter, since firing it out of order is preferable to not firing |
| // it at all. |
| CustomElement element = toBeAttached.removeLast(); |
| print("Warning: created but not in document: $element"); |
| element.attached(); |
| } |
| } |
| |
| final HTMLElement element; |
| CustomElement.created(String elementClass) |
| : element = document.createElement("shadow") as HTMLElement { |
| reverseElements[element] = this; |
| element.className = elementClass; |
| |
| if (toBeAttached.isEmpty) { |
| scheduleMicrotask(() => drainAttached()); |
| } |
| toBeAttached.add(this); |
| } |
| |
| void attached() {} |
| void detached() {} |
| |
| HTMLElement? get parent => element.parentElement as HTMLElement?; |
| |
| List<Node> get children { |
| final list = <Node>[]; |
| for (var i = 0; i < element.children.length; i++) { |
| final child = element.children.item(i); |
| if (child != null) { |
| list.add(child); |
| } |
| } |
| return list; |
| } |
| |
| set children(List<Node> nodes) { |
| element.removeChildren(); |
| for (var node in nodes) { |
| element.appendChild(node); |
| } |
| } |
| |
| CustomElement appendChild(Node node) { |
| element.appendChild(node); |
| return this; |
| } |
| |
| CustomElement removeChildren() { |
| element.removeChildren(); |
| return this; |
| } |
| |
| CustomElement appendChildren(Iterable<Node> nodes) { |
| element.appendChildren(nodes); |
| return this; |
| } |
| |
| CustomElement setChildren(Iterable<Node> nodes) { |
| element.setChildren(nodes); |
| return this; |
| } |
| |
| String get className => element.className; |
| set className(String c) => element.className = c; |
| |
| String get title => element.title; |
| set title(String t) => element.title = t; |
| |
| String get innerText => element.innerText; |
| set innerText(String t) => element.innerText = t; |
| |
| CSSStyleDeclaration get style => element.style; |
| |
| ElementStream<MouseEvent> get onClick => element.onClick; |
| |
| DOMRect getBoundingClientRect() => element.getBoundingClientRect(); |
| |
| HTMLCollection getElementsByClassName(String c) => |
| element.getElementsByClassName(c); |
| |
| void scrollIntoView() => element.scrollIntoView(); |
| } |