diff --git a/framework/components/input.dart b/framework/components/input.dart index 8ed2f0d37c6..111d582a330 100644 --- a/framework/components/input.dart +++ b/framework/components/input.dart @@ -6,6 +6,7 @@ import '../editing/editable_string.dart'; import '../editing/editable_text.dart'; import '../editing/keyboard.dart'; import '../fn.dart'; +import '../layout.dart'; import '../theme/colors.dart'; import '../theme/typography.dart' as typography; import 'dart:sky' as sky; @@ -14,7 +15,6 @@ typedef void ValueChanged(value); class Input extends Component { static final Style _style = new Style(''' - display: paragraph; transform: translateX(0); margin: 8px; padding: 8px; @@ -84,7 +84,8 @@ class Input extends Component { children.add(new EditableText(value: _editableValue, focused: focused)); return new EventListenerNode( - new Container( + new FlexContainer( + direction: FlexDirection.Column, style: _style, inlineStyle: focused ? _focusedInlineStyle : null, children: children diff --git a/framework/editing/editable_text.dart b/framework/editing/editable_text.dart index 8acff25b044..c8a7bf7ed91 100644 --- a/framework/editing/editable_text.dart +++ b/framework/editing/editable_text.dart @@ -8,12 +8,8 @@ import 'dart:async'; import 'editable_string.dart'; class EditableText extends Component { - static final Style _style = new Style(''' - display: inline;''' - ); static final Style _cursorStyle = new Style(''' - display: inline-flex; width: 2px; height: 1.2em; vertical-align: top; @@ -21,7 +17,6 @@ class EditableText extends Component { ); static final Style _composingStyle = new Style(''' - display: inline; text-decoration: underline;''' ); @@ -65,31 +60,30 @@ class EditableText extends Component { List children = new List(); if (!value.composing.isValid) { - children.add(new Text(value.text)); + children.add(new TextFragment(value.text)); } else { String beforeComposing = value.textBefore(value.composing); if (!beforeComposing.isEmpty) - children.add(new Text(beforeComposing)); + children.add(new TextFragment(beforeComposing)); String composing = value.textInside(value.composing); if (!composing.isEmpty) { - children.add(new Container( + children.add(new TextFragment( + composing, key: 'composing', - style: _composingStyle, - children: [new Text(composing)] + style: _composingStyle )); } String afterComposing = value.textAfter(value.composing); if (!afterComposing.isEmpty) - children.add(new Text(afterComposing)); + children.add(new TextFragment(afterComposing)); } if (_showCursor) children.add(new Container(key: 'cursor', style: _cursorStyle)); - return new Container( - style: _style, + return new Paragraph( children: children ); } diff --git a/framework/fn.dart b/framework/fn.dart index 3bc9dbbfcc5..751aa43afef 100644 --- a/framework/fn.dart +++ b/framework/fn.dart @@ -38,6 +38,8 @@ abstract class UINode { // if the |old| node has become stateful and should be retained. bool _willSync(UINode old) => false; + bool get interchangeable => false; // if true, then keys can be duplicated + void _sync(UINode old, RenderCSSContainer host, RenderCSS insertBefore); void _remove() { @@ -334,7 +336,7 @@ abstract class SkyElementWrapper extends SkyNodeWrapper { this.style, this.inlineStyle }) : this.children = children == null ? _emptyList : children, - super(key:key) { + super(key: key) { assert(!_debugHasDuplicateIds()); } @@ -352,12 +354,11 @@ abstract class SkyElementWrapper extends SkyNodeWrapper { var idSet = new HashSet(); for (var child in children) { assert(child != null); - if (child is Text) { - continue; // Text nodes all have the same key and are never reordered. - } + if (child.interchangeable) + continue; // when these nodes are reordered, we just reassign the data if (!idSet.add(child._key)) { - throw '''If multiple (non-Text) nodes of the same type exist as children + throw '''If multiple non-interchangeable nodes of the same type exist as children of another node, they must have unique keys. Duplicate: "${child._key}"'''; } @@ -456,22 +457,21 @@ abstract class SkyElementWrapper extends SkyNodeWrapper { oldNodeIdMap = new HashMap(); for (int i = oldStartIndex; i < oldEndIndex; i++) { var node = oldChildren[i]; - if (node is! Text) { + if (!node.interchangeable) oldNodeIdMap.putIfAbsent(node._key, () => node); - } } } bool searchForOldNode() { - if (currentNode is Text) - return false; // Never re-order Text nodes. + if (currentNode.interchangeable) + return false; // never re-order these nodes ensureOldIdMap(); oldNode = oldNodeIdMap[currentNode._key]; if (oldNode == null) return false; - oldNodeIdMap[currentNode._key] = null; // mark it reordered. + oldNodeIdMap[currentNode._key] = null; // mark it reordered assert(_root is RenderCSSContainer); assert(oldNode._root is RenderCSSContainer); oldSkyElementWrapper._root.remove(oldNode._root); @@ -520,7 +520,8 @@ abstract class SkyElementWrapper extends SkyNodeWrapper { class Container extends SkyElementWrapper { - RenderCSS _createNode() => new RenderCSSContainer(this); + RenderCSSContainer _root; + RenderCSSContainer _createNode() => new RenderCSSContainer(this); static final Container _emptyContainer = new Container(); @@ -539,6 +540,28 @@ class Container extends SkyElementWrapper { ); } +class Paragraph extends SkyElementWrapper { + + RenderCSSParagraph _root; + RenderCSSParagraph _createNode() => new RenderCSSParagraph(this); + + static final Paragraph _emptyContainer = new Paragraph(); + + SkyNodeWrapper get _emptyNode => _emptyContainer; + + Paragraph({ + Object key, + List children, + Style style, + String inlineStyle + }) : super( + key: key, + children: children, + style: style, + inlineStyle: inlineStyle + ); +} + class FlexContainer extends SkyElementWrapper { RenderCSSFlex _root; @@ -570,26 +593,23 @@ class FlexContainer extends SkyElementWrapper { } } -class Text extends SkyElementWrapper { +class TextFragment extends SkyElementWrapper { - RenderCSSText _root; - RenderCSSText _createNode() => new RenderCSSText(this, this.data); + RenderCSSInline _root; + RenderCSSInline _createNode() => new RenderCSSInline(this, this.data); - static final Text _emptyText = new Text(''); + static final TextFragment _emptyText = new TextFragment(''); SkyNodeWrapper get _emptyNode => _emptyText; final String data; - // Text nodes are special cases of having non-unique keys (which don't need - // to be assigned as part of the API). Since they are unique in not having - // children, there's little point to reordering, so we always just re-assign - // the data. - Text(this.data, { + TextFragment(this.data, { + Object key, Style style, String inlineStyle }) : super( - key: '*text*', + key: key, style: style, inlineStyle: inlineStyle ); @@ -615,7 +635,6 @@ class Image extends SkyElementWrapper { Image({ Object key, - List children, Style style, String inlineStyle, this.width, @@ -623,7 +642,6 @@ class Image extends SkyElementWrapper { this.src }) : super( key: key, - children: children, style: style, inlineStyle: inlineStyle ); @@ -737,7 +755,7 @@ abstract class Component extends UINode { Component({ Object key, bool stateful }) : _stateful = stateful != null ? stateful : false, _order = _currentOrder + 1, - super(key:key); + super(key: key); Component.fromArgs(Object key, bool stateful) : this(key: key, stateful: stateful); @@ -861,3 +879,10 @@ abstract class App extends Component { _sync(null, _host, _root); } } + +class Text extends Component { + Text(this.data) : super(key: '*text*'); + final String data; + bool get interchangeable => true; + UINode build() => new Paragraph(children: [new TextFragment(data)]); +} diff --git a/framework/layout.dart b/framework/layout.dart index 4e74c8947cd..7eb4aae4ae1 100644 --- a/framework/layout.dart +++ b/framework/layout.dart @@ -392,11 +392,9 @@ class RenderCSSFlex extends RenderCSSContainer { } -class RenderCSSText extends RenderCSS { +class RenderCSSParagraph extends RenderCSSContainer { - RenderCSSText(debug, String newData) : super(debug) { - data = newData; - } + RenderCSSParagraph(debug) : super(debug); static final Style _displayParagraph = new Style('display:paragraph'); @@ -404,6 +402,20 @@ class RenderCSSText extends RenderCSS { return super.stylesToClasses(styles) + ' ' + _displayParagraph._className; } +} + +class RenderCSSInline extends RenderCSS { + + RenderCSSInline(debug, String newData) : super(debug) { + data = newData; + } + + static final Style _displayInline = new Style('display:inline'); + + String stylesToClasses(List + + + + diff --git a/tests/framework/basic.sky b/tests/framework/basic.sky index efe8e0cff43..9dc207c01ae 100644 --- a/tests/framework/basic.sky +++ b/tests/framework/basic.sky @@ -8,7 +8,9 @@ import 'dart:sky.internals' as internals; void main() { new TestApp(); runAfterDisplay(() { - var result = serializeNode(document.lastChild.previousSibling) + serializeNode(document.lastChild); + document.firstChild.remove(); + document.firstChild.remove(); + var result = serializeNode(document); try { internals.notifyTestComplete(result); } catch (e) {