blob: 5c204f27968f26f06091753f13be87e90ba20f11 [file] [log] [blame]
// Copyright 2018 The Chromium Authors. 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 'dart:html' hide Point;
/// Finds the first descendant element of this document with the given id.
Element queryId(String id) => querySelector('#$id');
CoreElement a({String text, String c, String a, String href, String target}) =>
CoreElement('a', text: text, classes: c, attributes: a)
..setAttribute('href', href)
..setAttribute('target', target);
CoreElement br() => CoreElement('br');
CoreElement button({String text, String c, String a}) =>
CoreElement('button', text: text, classes: c, attributes: a);
CoreElement checkbox({String text, String c, String a}) =>
CoreElement('input', text: text, classes: c, attributes: a)
..setAttribute('type', 'checkbox');
CoreElement label({String text, String c, String a}) =>
CoreElement('label', text: text, classes: c, attributes: a);
CoreElement div({String text, String c, String a}) =>
CoreElement('div', text: text, classes: c, attributes: a);
CoreElement span({String text, String c, String a}) =>
CoreElement('span', text: text, classes: c, attributes: a);
CoreElement h2({String text, String c, String a}) =>
CoreElement('h2', text: text, classes: c, attributes: a);
CoreElement p({String text, String c, String a}) =>
CoreElement('p', text: text, classes: c, attributes: a);
CoreElement italic({String text, String c, String a}) =>
CoreElement('i', text: text, classes: c, attributes: a);
CoreElement em({String text, String c, String a}) =>
CoreElement('em', text: text, classes: c, attributes: a);
CoreElement img({String text, String c, String a, String src}) {
final CoreElement img =
CoreElement('img', text: text, classes: c, attributes: a);
// ignore: avoid_as
(img.element as ImageElement).src = src;
return img;
}
CoreElement ol({String text, String c, String a}) =>
CoreElement('ol', text: text, classes: c, attributes: a);
CoreElement ul({String text, String c, String a}) =>
CoreElement('ul', text: text, classes: c, attributes: a);
CoreElement li({String text, String html, String c, String a}) =>
CoreElement('li', text: text, html: html, classes: c, attributes: a);
CoreElement para({String text, String c, String a}) =>
CoreElement('p', text: text, classes: c, attributes: a);
CoreElement table() => CoreElement('table');
CoreElement tr() => CoreElement('tr');
CoreElement th({String text, String c}) =>
CoreElement('th', text: text, classes: c);
CoreElement td({String text, String c}) =>
CoreElement('td', text: text, classes: c);
CoreElement form() => CoreElement('form');
class CoreElement {
CoreElement(String tag,
{String text, String html, String classes, String attributes})
: element = Element.tag(tag) {
if (text != null) {
element.text = text;
}
if (html != null) {
element.innerHtml = html;
}
if (classes != null) {
element.classes.addAll(classes.split(' '));
}
if (attributes != null) {
attributes.split(' ').forEach(attribute);
}
}
CoreElement.from(this.element);
final Element element;
String get tag => element.tagName;
String get id => attributes['id'];
set id(String value) => setAttribute('id', value);
String get src => attributes['src'];
set src(String value) => setAttribute('src', value);
bool hasAttribute(String name) => element.attributes.containsKey(name);
void attribute(String name, [bool value]) {
value ??= !element.attributes.containsKey(name);
if (value) {
element.setAttribute(name, '');
} else {
element.attributes.remove(name);
}
}
void toggleAttribute(String name, [bool value]) => attribute(name, value);
Map<String, String> get attributes => element.attributes;
void setAttribute(String name, [String value = '']) =>
element.setAttribute(name, value);
String clearAttribute(String name) => element.attributes.remove(name);
void icon(String iconName) =>
element.classes.addAll(<String>['icon', 'icon-$iconName']);
bool hasClass(String name) => element.classes.contains(name);
void clazz(String _class, {bool removeOthers = false}) {
if (_class.contains(' ')) {
throw ArgumentError('spaces not allowed in class names');
}
if (removeOthers) {
element.classes.clear();
}
element.classes.add(_class);
}
void toggleClass(String name, [bool value]) {
element.classes.toggle(name, value);
}
String get text => element.text;
set text(String value) {
element.text = value;
}
bool get isVisible => element.style.visibility == 'visible';
/// Add the given child to this element's list of children. [child] must be
/// either a `CoreElement` or an `Element`.
dynamic add(dynamic child) {
if (child is Iterable) {
return child.map<dynamic>((dynamic c) => add(c)).toList();
} else if (child is CoreElement) {
element.children.add(child.element);
} else if (child is CoreElementView) {
element.children.add(child.element.element);
} else if (child is Element) {
element.children.add(child);
} else {
throw ArgumentError('argument type ${child.runtimeType} not supported');
}
return child;
}
/// Replace the given child/children to this element's list of children by the
/// childIndex. [child] must be either a `CoreElement` or an `Element`.
void replace(int childIndex, dynamic child) {
if (child is Iterable) {
// TODO(terry): Check begin index and end to ensure valid replace range.
int nextIndex = childIndex;
child.map<dynamic>((dynamic c) {
replace(nextIndex++, c);
});
} else if (child is CoreElement) {
element.children[childIndex] = child.element;
} else if (child is CoreElementView) {
element.children[childIndex] = child.element.element;
} else if (child is Element) {
element.children[childIndex] = child;
} else {
throw ArgumentError('argument type ${child.runtimeType} not supported');
}
}
bool get isHidden => hasAttribute('hidden');
void hidden([bool value]) => attribute('hidden', value);
String get label => attributes['label'];
set label(String value) => setAttribute('label', value);
bool get disabled => hasAttribute('disabled');
set disabled(bool value) => attribute('disabled', value);
bool get enabled => !disabled;
set enabled(bool value) => attribute('disabled', !value);
// Layout types.
void layout() => attribute('layout');
void horizontal() => attribute('horizontal');
void vertical() => attribute('vertical');
void layoutHorizontal() {
setAttribute('layout');
setAttribute('horizontal');
}
void layoutVertical() {
setAttribute('layout');
setAttribute('vertical');
}
// Layout params.
void fit() => attribute('fit');
void flex([int flexAmount]) {
attribute('flex', true);
if (flexAmount != null) {
if (flexAmount == 1) {
attribute('one', true);
} else if (flexAmount == 2) {
attribute('two', true);
} else if (flexAmount == 3) {
attribute('three', true);
} else if (flexAmount == 4) {
attribute('four', true);
} else if (flexAmount == 5) {
attribute('five', true);
}
}
}
String get tooltip => element.title;
set tooltip(String value) {
element.title = value;
}
String get display => element.style.display;
set display(String value) {
element.style.display = value;
}
int get scrollHeight => element.scrollHeight;
int get scrollTop => element.scrollTop;
set scrollTop(int value) => element.scrollTop = value;
int get offsetHeight => element.offsetHeight;
String get height => element.style.height;
set height(String value) {
element.style.height = value;
}
int get top => element.getBoundingClientRect().top.round();
int get left => element.getBoundingClientRect().left.round();
Stream<MouseEvent> get onClick => element.onClick.where((_) => !disabled);
Stream<TouchEvent> get onTouchStart =>
element.onTouchStart.where((_) => !disabled);
Stream<TouchEvent> get onTouchMove =>
element.onTouchMove.where((_) => !disabled);
Stream<TouchEvent> get onTouchEnd =>
element.onTouchEnd.where((_) => !disabled);
Stream<Event> get onFocus => element.onFocus.where((_) => !disabled);
Stream<Event> get onBlur => element.onBlur.where((_) => !disabled);
Stream<MouseEvent> get onMouseOver =>
element.onMouseOver.where((_) => !disabled);
Stream<MouseEvent> get onMouseLeave =>
element.onMouseLeave.where((_) => !disabled);
Stream<Event> get onScroll => element.onScroll;
Stream<KeyboardEvent> get onKeyDown => element.onKeyDown;
Stream<KeyboardEvent> get onKeyUp => element.onKeyUp;
Stream<ClipboardEvent> get onCut => element.onCut;
Stream<ClipboardEvent> get onPaste => element.onPaste;
/// Subscribe to the [onClick] event stream with a no-arg handler.
StreamSubscription<Event> click(void handle(), [void shiftHandle()]) {
return onClick.listen((MouseEvent e) {
e.stopImmediatePropagation();
if (shiftHandle != null && e.shiftKey) {
shiftHandle();
} else {
handle();
}
});
}
/// Subscribe to the [onDoubleClick] event stream with a no-arg handler.
StreamSubscription<Event> dblclick(void handle()) {
return element.onDoubleClick.listen((Event event) {
event.stopImmediatePropagation();
handle();
});
}
/// Subscribe to the [focus] event stream with a no-arg handler.
StreamSubscription<Event> focus(void handle()) {
return onFocus.listen((Event e) {
e.stopImmediatePropagation();
handle();
});
}
/// Subscribe to the [blur] event stream with a no-arg handler.
StreamSubscription<Event> blur(void handle()) {
return onBlur.listen((Event e) {
e.stopImmediatePropagation();
handle();
});
}
/// Subscribe to the [blur] event stream with a no-arg handler.
StreamSubscription<Event> over(void handle()) {
return onMouseOver.listen((Event e) {
e.stopImmediatePropagation();
handle();
});
}
/// Subscribe to the [blur] event stream with a no-arg handler.
StreamSubscription<Event> leave(void handle()) {
return onMouseLeave.listen((Event e) {
e.stopImmediatePropagation();
handle();
});
}
void clear() => element.children.clear();
void scrollIntoView({bool bottom = false, bool top = false}) {
if (bottom) {
element.scrollIntoView(ScrollAlignment.BOTTOM);
} else if (top) {
element.scrollIntoView(ScrollAlignment.TOP);
} else {
element.scrollIntoView();
}
}
void setInnerHtml(String str) {
element.setInnerHtml(str, treeSanitizer: const TrustedHtmlTreeSanitizer());
}
// /// Listen for a user copy event (ctrl-c / cmd-c) and copy the selected DOM
// /// bits into the user's paste buffer.
// void listenForUserCopy() {
// element.onKeyDown.listen(_handleCopyKeyPress);
// }
// void _handleCopyKeyPress(KeyboardEvent event) {
// // ctrl-c or cmd-c
// if (event.keyCode != 67) return;
// if ((isMac && event.metaKey) || (!isMac && event.ctrlKey)) {
// event.preventDefault();
// document.execCommand('copy', false, null);
// }
// }
void dispose() {
if (element.parent == null) {
return;
}
if (element.parent.children.contains(element)) {
try {
element.parent.children.remove(element);
} catch (e) {
// ignore
}
}
}
@override
String toString() => element.toString();
}
class CloseButton extends CoreElement {
CloseButton() : super('div', classes: 'close-button');
}
class TrustedHtmlTreeSanitizer implements NodeTreeSanitizer {
const TrustedHtmlTreeSanitizer();
@override
void sanitizeTree(Node node) {}
}
/// Base class for lightweight views that render to a CoreElement but are not
/// a CoreElement themselves.
abstract class CoreElementView {
CoreElement get element;
}