diff --git a/ReactNative.Tests/ReactNative.Tests.csproj b/ReactNative.Tests/ReactNative.Tests.csproj
index f671724536d..6f686279b2e 100644
--- a/ReactNative.Tests/ReactNative.Tests.csproj
+++ b/ReactNative.Tests/ReactNative.Tests.csproj
@@ -144,7 +144,6 @@
UnitTestApp.xaml
-
diff --git a/ReactNative.Tests/Views/TextInput/ReactTextBoxPropertiesTests.cs b/ReactNative.Tests/Views/TextInput/ReactTextBoxPropertiesTests.cs
deleted file mode 100644
index 12fcce0af3a..00000000000
--- a/ReactNative.Tests/Views/TextInput/ReactTextBoxPropertiesTests.cs
+++ /dev/null
@@ -1,68 +0,0 @@
-using Microsoft.VisualStudio.TestPlatform.UnitTestFramework;
-using ReactNative.Views.TextInput;
-using ReactNative.UIManager;
-using Windows.UI.Text;
-using Windows.UI.Xaml;
-using Windows.UI.Xaml.Controls;
-
-namespace ReactNative.Tests.Views.TextInput
-{
- [TestClass]
- public class ReactTextBoxPropertiesTests
- {
- [Microsoft.VisualStudio.TestPlatform.UnitTestFramework.AppContainer.UITestMethod]
- public void ReactTextBoxPropertiesTests_SuccessfulSrcControlPropMerge()
- {
- var textBox = new TextBox();
- textBox.Text = "";
-
- var reactTextBox = new ReactTextBoxProperties() {
- LineHeight = 12,
- Padding = new Thickness(12, 23, 1, 23),
- FontSize = 12,
- FontStyle = FontStyle.Italic
- };
-
- textBox.SetReactTextBoxProperties(reactTextBox);
- Assert.AreEqual(textBox.FontStyle, reactTextBox.FontStyle);
- Assert.AreEqual(textBox.Padding, reactTextBox.Padding);
- }
-
- [Microsoft.VisualStudio.TestPlatform.UnitTestFramework.AppContainer.UITestMethod]
- public void ReactTextBoxPropertiesTests_NullSrcControlPropMerge()
- {
- var textBox = new TextBox();
- textBox.FontSize = 2;
-
- var reactTextBox = new ReactTextBoxProperties()
- {
- LineHeight = 12,
- Padding = new Thickness(12, 23, 1, 23),
- FontSize = 12,
- FontStyle = FontStyle.Italic
- };
-
- textBox.SetReactTextBoxProperties(reactTextBox);
- Assert.AreEqual(textBox.FontStyle, reactTextBox.FontStyle);
- Assert.AreEqual(textBox.Padding, reactTextBox.Padding);
- }
-
- [Microsoft.VisualStudio.TestPlatform.UnitTestFramework.AppContainer.UITestMethod]
- public void ReactTextBoxPropertiesTests_OverwriteControlPropsMerge()
- {
- var textBox = new TextBox();
- textBox.FontStyle = FontStyle.Normal;
-
- var reactTextBox = new ReactTextBoxProperties()
- {
- LineHeight = 12,
- Padding = new Thickness(12, 23, 1, 23),
- FontSize = 12,
- FontStyle = FontStyle.Italic
- };
-
- textBox.SetReactTextBoxProperties(reactTextBox);
- Assert.AreEqual(textBox.FontStyle, reactTextBox.FontStyle);
- }
- }
-}
diff --git a/ReactNative/ReactNative.csproj b/ReactNative/ReactNative.csproj
index 6eb7052fc77..66e05eba48b 100644
--- a/ReactNative/ReactNative.csproj
+++ b/ReactNative/ReactNative.csproj
@@ -250,6 +250,7 @@
+
@@ -287,8 +288,14 @@
-
-
+
+
+
+
+
+
+
+
@@ -320,19 +327,16 @@
-
+
-
-
-
diff --git a/ReactNative/Shell/MainReactPackage.cs b/ReactNative/Shell/MainReactPackage.cs
index 2edd0435c2e..e18a06b97aa 100644
--- a/ReactNative/Shell/MainReactPackage.cs
+++ b/ReactNative/Shell/MainReactPackage.cs
@@ -78,7 +78,6 @@ public IReadOnlyList CreateViewManagers(
new ReactScrollViewManager(),
new ReactSwitchManager(),
new ReactTextInputManager(),
- new ReactMultilineTextInputManager(),
new ReactTextViewManager(),
//new ReactToolbarManager(),
new ReactViewManager(),
diff --git a/ReactNative/UIManager/CSSNodeVisitor.cs b/ReactNative/UIManager/CSSNodeVisitor.cs
new file mode 100644
index 00000000000..480f071f3dc
--- /dev/null
+++ b/ReactNative/UIManager/CSSNodeVisitor.cs
@@ -0,0 +1,35 @@
+using Facebook.CSSLayout;
+using System;
+using System.Collections.Generic;
+
+namespace ReactNative.UIManager
+{
+ abstract class CSSNodeVisitor
+ {
+ public T Visit(CSSNode node)
+ {
+ if (node == null)
+ {
+ throw new ArgumentNullException(nameof(node));
+ }
+
+ var n = node.ChildCount;
+ if (n == 0)
+ {
+ return Make(node, Array.Empty());
+ }
+ else
+ {
+ var children = new List(n);
+ foreach (var child in node.Children)
+ {
+ children.Add(Visit(child));
+ }
+
+ return Make(node, children);
+ }
+ }
+
+ protected abstract T Make(CSSNode node, IList children);
+ }
+}
diff --git a/ReactNative/UIManager/ILayoutManager.cs b/ReactNative/UIManager/ILayoutManager.cs
new file mode 100644
index 00000000000..116914754ba
--- /dev/null
+++ b/ReactNative/UIManager/ILayoutManager.cs
@@ -0,0 +1,19 @@
+using Windows.UI.Xaml;
+
+namespace ReactNative.UIManager
+{
+ ///
+ /// Interface for overriding layout behavior for a view.
+ ///
+ public interface ILayoutManager
+ {
+ ///
+ /// Updates the layout of the current instance.
+ ///
+ /// The left coordinate.
+ /// The top coordinate.
+ /// The layout width.
+ /// The layout height.
+ void UpdateLayout(int x, int y, int width, int height);
+ }
+}
diff --git a/ReactNative/UIManager/LayoutAnimation/LayoutAnimationController.cs b/ReactNative/UIManager/LayoutAnimation/LayoutAnimationController.cs
index 590e6044097..32c191a04d7 100644
--- a/ReactNative/UIManager/LayoutAnimation/LayoutAnimationController.cs
+++ b/ReactNative/UIManager/LayoutAnimation/LayoutAnimationController.cs
@@ -70,7 +70,7 @@ public void InitializeFromConfig(JObject config)
///
public bool ShouldAnimateLayout(FrameworkElement view)
{
- return _shouldAnimateLayout && view.Parent != null;
+ return _shouldAnimateLayout && view.Parent != null && !(view is ILayoutManager);
}
///
diff --git a/ReactNative/UIManager/NativeViewHierarchyManager.cs b/ReactNative/UIManager/NativeViewHierarchyManager.cs
index 06ef6285315..b983f175a5a 100644
--- a/ReactNative/UIManager/NativeViewHierarchyManager.cs
+++ b/ReactNative/UIManager/NativeViewHierarchyManager.cs
@@ -109,7 +109,7 @@ public void UpdateViewExtraData(int tag, object extraData)
/// The parent view tag.
/// The view tag.
/// The left coordinate.
- /// The right coordinate.
+ /// The top coordinate.
/// The layout width.
/// The layout height.
public void UpdateLayout(int parentTag, int tag, int x, int y, int width, int height)
@@ -507,10 +507,15 @@ private void DropView(FrameworkElement view)
private void UpdateLayout(FrameworkElement viewToUpdate, int x, int y, int width, int height)
{
+ var layoutManager = default(ILayoutManager);
if (_layoutAnimator.ShouldAnimateLayout(viewToUpdate))
{
_layoutAnimator.ApplyLayoutUpdate(viewToUpdate, x, y, width, height);
}
+ else if ((layoutManager = viewToUpdate as ILayoutManager) != null)
+ {
+ layoutManager.UpdateLayout(x, y, width, height);
+ }
else
{
Canvas.SetLeft(viewToUpdate, x);
diff --git a/ReactNative/UIManager/ViewProperties.cs b/ReactNative/UIManager/ViewProperties.cs
index 05cb0292e74..be94c05d75e 100644
--- a/ReactNative/UIManager/ViewProperties.cs
+++ b/ReactNative/UIManager/ViewProperties.cs
@@ -60,6 +60,7 @@ public static class ViewProperties
public const string Value = "value";
public const string ResizeMode = "resizeMode";
public const string TextAlign = "textAlign";
+ public const string TextAlignVertical = "textAlignVertical";
public const string BorderWidth = "borderWidth";
public const string BorderLeftWidth = "borderLeftWidth";
diff --git a/ReactNative/Views/Text/ReactTextShadowNode.cs b/ReactNative/Views/Text/ReactTextShadowNode.cs
index 32f8fad39a9..8700b0648b3 100644
--- a/ReactNative/Views/Text/ReactTextShadowNode.cs
+++ b/ReactNative/Views/Text/ReactTextShadowNode.cs
@@ -2,6 +2,7 @@
using ReactNative.Bridge;
using ReactNative.UIManager;
using System;
+using System.Collections.Generic;
using Windows.Foundation;
using Windows.UI.Text;
using Windows.UI.Xaml;
@@ -16,25 +17,19 @@ namespace ReactNative.Views.Text
///
public class ReactTextShadowNode : LayoutShadowNode
{
- private const string INLINE_IMAGE_PLACEHOLDER = "I";
- private const int UNSET = -1;
+ private const int Unset = -1;
- private const string PROP_TEXT = "text";
-
- private int _lineHeight = UNSET;
private bool _isColorSet = false;
private uint _color;
private bool _isBackgroundColorSet = false;
private uint _backgroundColor;
- private int _numberOfLines = UNSET;
- private int _fontSize = UNSET;
+ private int _fontSize = Unset;
private FontStyle? _fontStyle;
private FontWeight? _fontWeight;
private string _fontFamily;
- private string _text;
private Inline _inline;
@@ -84,6 +79,15 @@ public override bool IsVirtualAnchor
}
}
+ ///
+ /// The text value.
+ ///
+ protected string Text
+ {
+ get;
+ private set;
+ }
+
///
/// Called once per batch of updates by the
/// if the text node is dirty.
@@ -95,7 +99,7 @@ public override void OnBeforeLayout()
return;
}
- _inline = DispatcherHelpers.CallOnDispatcher(() => FromTextCSSNode(this)).Result;
+ _inline = DispatcherHelpers.CallOnDispatcher(() => ReactTextShadowNodeInlineVisitor.Apply(this)).Result;
MarkUpdated();
}
@@ -125,29 +129,7 @@ public override void OnCollectExtraUpdates(UIViewOperationQueue uiViewOperationQ
[ReactProperty("text")]
public void SetText(string text)
{
- _text = text;
- MarkUpdated();
- }
-
- ///
- /// Sets the number of lines for the node.
- ///
- /// The number of lines.
- [ReactProperty(ViewProperties.NumberOfLines, DefaultInteger = UNSET)]
- public void SetNumberOfLines(int numberOfLines)
- {
- _numberOfLines = numberOfLines;
- MarkUpdated();
- }
-
- ///
- /// Sets the line height for the node.
- ///
- /// The line height.
- [ReactProperty(ViewProperties.LineHeight, DefaultInteger = UNSET)]
- public void SetLineHeight(int lineHeight)
- {
- _lineHeight = lineHeight;
+ Text = text;
MarkUpdated();
}
@@ -155,7 +137,7 @@ public void SetLineHeight(int lineHeight)
/// Sets the font size for the node.
///
/// The font size.
- [ReactProperty(ViewProperties.FontSize, DefaultDouble = UNSET)]
+ [ReactProperty(ViewProperties.FontSize, DefaultDouble = Unset)]
public void SetFontSize(double fontSize)
{
_fontSize = (int)fontSize;
@@ -253,6 +235,44 @@ protected override void MarkUpdated()
}
}
+ ///
+ /// Formats an inline instance with shadow properties..
+ ///
+ /// The text shadow node.
+ /// The inline.
+ /// Signals if the operation is used only for measurement.
+ protected static void FormatInline(ReactTextShadowNode textNode, Inline inline, bool measureOnly)
+ {
+ if (!measureOnly && textNode._isColorSet)
+ {
+ inline.Foreground = new SolidColorBrush(ColorHelpers.Parse(textNode._color));
+ }
+
+ if (textNode._fontSize != Unset)
+ {
+ var fontSize = textNode._fontSize;
+ inline.FontSize = fontSize;
+ }
+
+ if (textNode._fontStyle.HasValue)
+ {
+ var fontStyle = textNode._fontStyle.Value;
+ inline.FontStyle = fontStyle;
+ }
+
+ if (textNode._fontWeight.HasValue)
+ {
+ var fontWeight = textNode._fontWeight.Value;
+ inline.FontWeight = fontWeight;
+ }
+
+ if (textNode._fontFamily != null)
+ {
+ var fontFamily = new FontFamily(textNode._fontFamily);
+ inline.FontFamily = fontFamily;
+ }
+ }
+
private static MeasureOutput MeasureText(CSSNode node, float width, float height)
{
// This is not a terribly efficient way of projecting the height of
@@ -264,19 +284,18 @@ private static MeasureOutput MeasureText(CSSNode node, float width, float height
// TODO: determine another way to measure text elements.
var task = DispatcherHelpers.CallOnDispatcher(() =>
{
- var shadowNode = (ReactTextShadowNode)node;
var textBlock = new TextBlock
{
TextWrapping = TextWrapping.Wrap,
};
- textBlock.Inlines.Add(FromTextCSSNode(shadowNode));
+ textBlock.Inlines.Add(ReactTextShadowNodeInlineVisitor.Apply(node));
try
{
- var adjustedWidth = float.IsNaN(width) ? double.PositiveInfinity : width;
- var adjustedHeight = float.IsNaN(height) ? double.PositiveInfinity : height;
- textBlock.Measure(new Size(adjustedWidth, adjustedHeight));
+ var normalizedWidth = CSSConstants.IsUndefined(width) ? double.PositiveInfinity : width;
+ var normalizedHeight = CSSConstants.IsUndefined(height) ? double.PositiveInfinity : height;
+ textBlock.Measure(new Size(normalizedWidth, normalizedHeight));
return new MeasureOutput(
(float)textBlock.DesiredSize.Width,
(float)textBlock.DesiredSize.Height);
@@ -289,70 +308,48 @@ private static MeasureOutput MeasureText(CSSNode node, float width, float height
return task.Result;
}
-
- private static Inline FromTextCSSNode(ReactTextShadowNode textNode)
- {
- return BuildInlineFromTextCSSNode(textNode);
- }
- private static Inline BuildInlineFromTextCSSNode(ReactTextShadowNode textNode)
+ class ReactTextShadowNodeInlineVisitor : CSSNodeVisitor
{
- var length = textNode.ChildCount;
- var inline = default(Inline);
- if (length == 0)
+ private static ReactTextShadowNodeInlineVisitor s_instance = new ReactTextShadowNodeInlineVisitor();
+
+ public static Inline Apply(CSSNode node)
{
- inline = new Run { Text = textNode._text };
- }
- else
+ return s_instance.Visit(node);
+ }
+
+ protected sealed override Inline Make(CSSNode node, IList children)
{
- var span = new Span();
- for (var i = 0; i < length; ++i)
+ var textNode = (ReactTextShadowNode)node;
+ if (textNode._isVirtual)
+ {
+ textNode.MarkUpdateSeen();
+ }
+
+ var text = textNode.Text;
+ if (text != null && children.Count > 0)
{
- var child = textNode.GetChildAt(i);
- var textChild = child as ReactTextShadowNode;
- if (textChild == null)
+ throw new InvalidOperationException("Only leaf nodes can contain text.");
+ }
+ else if (text != null)
+ {
+ var inline = new Run();
+ inline.Text = text;
+ FormatInline(textNode, inline, false);
+ return inline;
+ }
+ else
+ {
+ var inline = new Span();
+ foreach (var child in children)
{
- throw new InvalidOperationException(
- $"Unexpected view type '{child.GetType()}' nested under text node.");
+ inline.Inlines.Add(child);
}
- var childInline = BuildInlineFromTextCSSNode(textChild);
- span.Inlines.Add(childInline);
+ FormatInline(textNode, inline, false);
+ return inline;
}
-
- inline = span;
}
-
- if (textNode._isColorSet)
- {
- inline.Foreground = new SolidColorBrush(ColorHelpers.Parse(textNode._color));
- }
-
- if (textNode._fontSize != UNSET)
- {
- var fontSize = textNode._fontSize;
- inline.FontSize = fontSize;
- }
-
- if (textNode._fontStyle.HasValue)
- {
- var fontStyle = textNode._fontStyle.Value;
- inline.FontStyle = fontStyle;
- }
-
- if (textNode._fontWeight.HasValue)
- {
- var fontWeight = textNode._fontWeight.Value;
- inline.FontWeight = fontWeight;
- }
-
- if (textNode._fontFamily != null)
- {
- var fontFamily = new FontFamily(textNode._fontFamily);
- inline.FontFamily = fontFamily;
- }
-
- return inline;
}
}
}
\ No newline at end of file
diff --git a/ReactNative/Views/TextInput/InputScopeHelpers.cs b/ReactNative/Views/TextInput/InputScopeHelpers.cs
new file mode 100644
index 00000000000..63606d788cf
--- /dev/null
+++ b/ReactNative/Views/TextInput/InputScopeHelpers.cs
@@ -0,0 +1,32 @@
+using Windows.UI.Xaml.Input;
+
+namespace ReactNative.Views.TextInput
+{
+ static class InputScopeHelpers
+ {
+ public static InputScopeNameValue FromString(string inputScope)
+ {
+ switch (inputScope)
+ {
+ case "url":
+ return InputScopeNameValue.Url;
+ case "number-pad":
+ return InputScopeNameValue.NumericPin;
+ case "phone-pad":
+ return InputScopeNameValue.TelephoneNumber;
+ case "name-phone-pad":
+ return InputScopeNameValue.NameOrPhoneNumber;
+ case "email-address":
+ return InputScopeNameValue.EmailNameOrAddress;
+ case "decimal-pad":
+ return InputScopeNameValue.Digits;
+ case "web-search":
+ return InputScopeNameValue.Search;
+ case "numeric":
+ return InputScopeNameValue.Number;
+ default:
+ return InputScopeNameValue.Default;
+ }
+ }
+ }
+}
diff --git a/ReactNative/Views/TextInput/ReactMultilineTextInputManager.cs b/ReactNative/Views/TextInput/ReactMultilineTextInputManager.cs
deleted file mode 100644
index 898810192b3..00000000000
--- a/ReactNative/Views/TextInput/ReactMultilineTextInputManager.cs
+++ /dev/null
@@ -1,38 +0,0 @@
-using ReactNative.UIManager;
-using Windows.UI.Xaml;
-using Windows.UI.Xaml.Controls;
-
-namespace ReactNative.Views.TextInput
-{
- ///
- /// Native component to support a multiline control.
- ///
- class ReactMultilineTextInputManager : ReactTextInputManager
- {
- private static readonly string ReactClass = "RCTTextView";
- private const string PROP_MULTILINE = "multiline";
-
- ///
- /// The name of the view manager.
- ///
- public override string Name
- {
- get
- {
- return ReactClass;
- }
- }
-
- ///
- /// Determines if there should be multiple lines allowed for the .
- ///
- /// The text input box control.
- /// To allow multiline.
- [ReactProperty(PROP_MULTILINE)]
- public void SetMultiline(TextBox view, bool multiline)
- {
- view.AcceptsReturn = multiline;
- view.TextWrapping = TextWrapping.Wrap;
- }
- }
-}
diff --git a/ReactNative/Views/TextInput/ReactTextBox.cs b/ReactNative/Views/TextInput/ReactTextBox.cs
new file mode 100644
index 00000000000..540fbd33e75
--- /dev/null
+++ b/ReactNative/Views/TextInput/ReactTextBox.cs
@@ -0,0 +1,87 @@
+using ReactNative.UIManager;
+using System.Threading;
+using Windows.UI.Xaml;
+using Windows.UI.Xaml.Controls;
+
+namespace ReactNative.Views.TextInput
+{
+ class ReactTextBox : TextBox, ILayoutManager
+ {
+ private int _eventCount;
+ private double _lastWidth;
+ private double _lastHeight;
+
+ public ReactTextBox()
+ {
+ LayoutUpdated += OnLayoutUpdated;
+ }
+
+ public int CurrentEventCount
+ {
+ get
+ {
+ return _eventCount;
+ }
+ }
+
+ public bool ClearTextOnFocus
+ {
+ get;
+ set;
+ }
+
+ public bool SelectTextOnFocus
+ {
+ get;
+ set;
+ }
+
+ public int IncrementEventCount()
+ {
+ return Interlocked.Increment(ref _eventCount);
+ }
+
+ public void UpdateLayout(int x, int y, int width, int height)
+ {
+ Canvas.SetLeft(this, x);
+ Canvas.SetTop(this, y);
+ Width = width;
+ }
+
+ protected override void OnGotFocus(RoutedEventArgs e)
+ {
+ if (ClearTextOnFocus)
+ {
+ Text = "";
+ }
+
+ if (SelectTextOnFocus)
+ {
+ SelectionStart = 0;
+ SelectionLength = Text.Length;
+ }
+ }
+
+ private void OnLayoutUpdated(object sender, object e)
+ {
+ var width = ActualWidth;
+ var height = ActualHeight;
+ if (width != _lastWidth || height != _lastHeight)
+ {
+ _lastWidth = width;
+ _lastHeight = height;
+
+ this.GetReactContext()
+ .GetNativeModule()
+ .EventDispatcher
+ .DispatchEvent(
+ new ReactTextChangedEvent(
+ this.GetTag(),
+ Text,
+ width,
+ height,
+ IncrementEventCount()));
+ }
+ }
+ }
+}
diff --git a/ReactNative/Views/TextInput/ReactTextBoxProperties.cs b/ReactNative/Views/TextInput/ReactTextBoxProperties.cs
deleted file mode 100644
index a63fe64fe9b..00000000000
--- a/ReactNative/Views/TextInput/ReactTextBoxProperties.cs
+++ /dev/null
@@ -1,56 +0,0 @@
-using Windows.UI;
-using Windows.UI.Text;
-using Windows.UI.Xaml;
-using Windows.UI.Xaml.Media;
-
-namespace ReactNative.Views.TextInput
-{
- ///
- /// A Data model which holds measurement related styling updates for
- /// .
- ///
- public class ReactTextBoxProperties
- {
- private const int UNSET = -1;
-
- ///
- /// The padding thickness.
- ///
- public Thickness? Padding { get; set; }
-
- ///
- /// The font style.
- ///
- public FontStyle? FontStyle { get; set; }
-
- ///
- /// The font weight.
- ///
- public FontWeight? FontWeight { get; set; }
-
- ///
- /// The border color.
- ///
- public Color? BorderColor { get; set; }
-
- ///
- /// The font family.
- ///
- public FontFamily FontFamily { get; set; }
-
- ///
- /// The font size.
- ///
- public int FontSize { get; set; } = UNSET;
-
- ///
- /// The text value.
- ///
- public string Text { get; set; }
-
- ///
- /// The line height.
- ///
- public int LineHeight { get; set; } = UNSET;
- }
-}
diff --git a/ReactNative/Views/TextInput/ReactTextChangedEvent.cs b/ReactNative/Views/TextInput/ReactTextChangedEvent.cs
index 894548d1c0e..f7f0f5aa455 100644
--- a/ReactNative/Views/TextInput/ReactTextChangedEvent.cs
+++ b/ReactNative/Views/TextInput/ReactTextChangedEvent.cs
@@ -13,13 +13,23 @@ class ReactTextChangedEvent : Event
private readonly string _text;
private readonly double _contextWidth;
private readonly double _contentHeight;
+ private readonly int _eventCount;
- public ReactTextChangedEvent(int viewId, string text, double contentWidth, double contentHeight)
- : base(viewId, TimeSpan.FromTicks(Environment.TickCount))
+ ///
+ /// Instantiates a .
+ ///
+ /// The view tag.
+ /// The text.
+ /// The content width.
+ /// The content height.
+ /// The event count.
+ public ReactTextChangedEvent(int viewTag, string text, double contentWidth, double contentHeight, int eventCount)
+ : base(viewTag, TimeSpan.FromTicks(Environment.TickCount))
{
_text = text;
_contextWidth = contentWidth;
_contentHeight = contentHeight;
+ _eventCount = eventCount;
}
///
@@ -53,21 +63,21 @@ public override bool CanCoalesce
/// The event emitter.
public override void Dispatch(RCTEventEmitter rctEventEmitter)
{
- rctEventEmitter.receiveEvent(this.ViewTag, this.EventName, this.GetEventJavascriptProperties);
- }
+ var contentSize = new JObject
+ {
+ { "width", _contextWidth },
+ { "height", _contentHeight },
+ };
- private JObject GetEventJavascriptProperties
- {
- get
+ var eventData = new JObject
{
- return new JObject()
- {
- { "width", _contextWidth },
- { "height", _contentHeight },
- { "text", _text },
- { "target", ViewTag }
- };
- }
+ { "text", _text },
+ { "contentSize", contentSize },
+ { "eventCount", _eventCount },
+ { "target", ViewTag },
+ };
+
+ rctEventEmitter.receiveEvent(ViewTag, EventName, eventData);
}
}
}
diff --git a/ReactNative/Views/TextInput/ReactTextInputBlurEvent.cs b/ReactNative/Views/TextInput/ReactTextInputBlurEvent.cs
index 0bde7d55de6..149ef624d13 100644
--- a/ReactNative/Views/TextInput/ReactTextInputBlurEvent.cs
+++ b/ReactNative/Views/TextInput/ReactTextInputBlurEvent.cs
@@ -10,13 +10,17 @@ namespace ReactNative.Views.TextInput
///
class ReactTextInputBlurEvent : Event
{
- public ReactTextInputBlurEvent(int viewId)
- : base(viewId, TimeSpan.FromTicks(Environment.TickCount))
+ ///
+ /// Instantiate a .
+ ///
+ /// The view tag.
+ public ReactTextInputBlurEvent(int viewTag)
+ : base(viewTag, TimeSpan.FromTicks(Environment.TickCount))
{
}
///
- /// Gets the name of the Event
+ /// The event name.
///
public override string EventName
{
diff --git a/ReactNative/Views/TextInput/ReactTextInputEndEditingEvent.cs b/ReactNative/Views/TextInput/ReactTextInputEndEditingEvent.cs
new file mode 100644
index 00000000000..4dc9996c7e8
--- /dev/null
+++ b/ReactNative/Views/TextInput/ReactTextInputEndEditingEvent.cs
@@ -0,0 +1,44 @@
+using Newtonsoft.Json.Linq;
+using ReactNative.UIManager.Events;
+using System;
+
+namespace ReactNative.Views.TextInput
+{
+ class ReactTextInputEndEditingEvent : Event
+ {
+ private readonly string _text;
+
+ public ReactTextInputEndEditingEvent(int viewTag, string text)
+ : base(viewTag, TimeSpan.FromTicks(Environment.TickCount))
+ {
+ _text = text;
+ }
+
+ public override string EventName
+ {
+ get
+ {
+ return "topEndEditing";
+ }
+ }
+
+ public override bool CanCoalesce
+ {
+ get
+ {
+ return false;
+ }
+ }
+
+ public override void Dispatch(RCTEventEmitter eventEmitter)
+ {
+ var eventData = new JObject
+ {
+ { "target", ViewTag },
+ { "text", _text },
+ };
+
+ eventEmitter.receiveEvent(ViewTag, EventName, eventData);
+ }
+ }
+}
\ No newline at end of file
diff --git a/ReactNative/Views/TextInput/ReactTextInputFocusEvent.cs b/ReactNative/Views/TextInput/ReactTextInputFocusEvent.cs
index 7568542c0da..5c1ae5aa854 100644
--- a/ReactNative/Views/TextInput/ReactTextInputFocusEvent.cs
+++ b/ReactNative/Views/TextInput/ReactTextInputFocusEvent.cs
@@ -10,11 +10,18 @@ namespace ReactNative.Views.TextInput
///
class ReactTextInputFocusEvent : Event
{
- public ReactTextInputFocusEvent(int viewId)
- : base(viewId, TimeSpan.FromTicks(Environment.TickCount))
+ ///
+ /// Instantiates a .
+ ///
+ /// The view tag.
+ public ReactTextInputFocusEvent(int viewTag)
+ : base(viewTag, TimeSpan.FromTicks(Environment.TickCount))
{
}
+ ///
+ /// The event name.
+ ///
public override string EventName
{
get
diff --git a/ReactNative/Views/TextInput/ReactTextInputManager.cs b/ReactNative/Views/TextInput/ReactTextInputManager.cs
index c03a17a9e58..9afb1ecf23a 100644
--- a/ReactNative/Views/TextInput/ReactTextInputManager.cs
+++ b/ReactNative/Views/TextInput/ReactTextInputManager.cs
@@ -1,36 +1,43 @@
using Newtonsoft.Json.Linq;
+using ReactNative.Reflection;
using ReactNative.UIManager;
-using ReactNative.UIManager.Events;
+using ReactNative.Views.Text;
using System;
using System.Collections.Generic;
+using Windows.System;
+using Windows.UI;
+using Windows.UI.Text;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
+using Windows.UI.Xaml.Input;
using Windows.UI.Xaml.Media;
namespace ReactNative.Views.TextInput
{
- class ReactTextInputManager : BaseViewManager
+ ///
+ /// View manager for .
+ ///
+ class ReactTextInputManager : BaseViewManager
{
- private static readonly int FOCUS_TEXT_INPUT = 1;
- private static readonly int BLUR_TEXT_INPUT = 2;
- private static readonly string REACT_CLASS = "RCTTextField";
-
- private const string PROP_ROTATION_X = "rotationX";
- private const string PROP_PLACEHOLDER = "placeholder";
- private const string PROP_TEXT_ALIGN = "textAlign";
- private const string PROP_VERTICAL_TEXT_ALIGN = "textAlignVertical";
- private const string PROP_MAX_LENGTH = "maxLength";
- private const string PROP_TEXT = "text";
- private const string PROP_IS_EDITABLE = "editable";
+ private static readonly int FocusTextInput = 1;
+ private static readonly int BlurTextInput = 2;
+ private bool _onSelectionChange;
+
+ ///
+ /// The name of the view manager.
+ ///
public override string Name
{
get
{
- return REACT_CLASS;
+ return "RCTTextBox";
}
}
+ ///
+ /// The exported custom bubbling event types.
+ ///
public override IReadOnlyDictionary ExportedCustomBubblingEventTypeConstants
{
get
@@ -38,15 +45,15 @@ public override IReadOnlyDictionary ExportedCustomBubblingEventT
return new Dictionary()
{
{
- "topFocus",
+ "topSubmitEditing",
new Dictionary()
{
{
"phasedRegistrationNames",
new Dictionary()
{
- { "bubbled" , "onFocus" },
- { "captured" , "onFocusCapture" }
+ { "bubbled" , "onSubmitEditing" },
+ { "captured" , "onSubmitEditingCapture" }
}
}
}
@@ -65,6 +72,20 @@ public override IReadOnlyDictionary ExportedCustomBubblingEventT
}
}
},
+ {
+ "topFocus",
+ new Dictionary()
+ {
+ {
+ "phasedRegistrationNames",
+ new Dictionary()
+ {
+ { "bubbled" , "onFocus" },
+ { "captured" , "onFocusCapture" }
+ }
+ }
+ }
+ },
{
"topBlur",
new Dictionary()
@@ -92,198 +113,456 @@ public override IReadOnlyDictionary CommandsMap
{
return new Dictionary()
{
- { "focusTextInput", FOCUS_TEXT_INPUT },
- { "blurTextInput", BLUR_TEXT_INPUT }
+ { "focusTextInput", FocusTextInput },
+ { "blurTextInput", BlurTextInput },
};
}
}
///
- /// Sets the text alignment property on the .
+ /// Sets the font size on the .
///
- /// The text input box control.
- /// The text alignment.
- [ReactProperty(PROP_TEXT_ALIGN)]
- public void SetTextAlign(TextBox view, string alignment)
+ /// The view instance.
+ /// The font size.
+ [ReactProperty(ViewProperties.FontSize)]
+ public void SetFontSize(ReactTextBox view, double fontSize)
+ {
+ view.FontSize = fontSize;
+ }
+
+ ///
+ /// Sets the font color for the node.
+ ///
+ /// The view instance.
+ /// The masked color value.
+ [ReactProperty(ViewProperties.Color, CustomType = "Color")]
+ public void SetColor(ReactTextBox view, uint? color)
{
- var textAlignment = default(TextAlignment);
- if (Enum.TryParse(alignment, out textAlignment))
+ if (color.HasValue)
{
- view.TextAlignment = textAlignment;
+ view.Foreground = new SolidColorBrush(ColorHelpers.Parse(color.Value));
+ }
+ else
+ {
+ view.Foreground = new SolidColorBrush(Colors.Black);
}
}
///
- /// Sets the text alignment property on the .
+ /// Sets the font family for the node.
///
- /// The text input box control.
- /// The text alignment.
- ///
- /// TODO: test this out.
- ///
- [ReactProperty(PROP_VERTICAL_TEXT_ALIGN)]
- public void SetTextVerticalAlign(TextBox view, string alignment)
- {
- var textAlignment = default(VerticalAlignment);
- if (Enum.TryParse(alignment, out textAlignment))
+ /// The view instance.
+ /// The font family.
+ [ReactProperty(ViewProperties.FontFamily)]
+ public void SetFontFamily(ReactTextBox view, string familyName)
+ {
+ if (familyName != null)
+ {
+ view.FontFamily = new FontFamily(familyName);
+ }
+ else
{
- view.VerticalContentAlignment = textAlignment;
+ view.FontFamily = new FontFamily("Segoe UI");
}
}
///
- /// Sets the editablity property on the .
+ /// Sets the font weight for the node.
///
- /// The text input box control.
- /// The text alignment.
- [ReactProperty(PROP_IS_EDITABLE)]
- public void SetEditable(TextBox view, bool editable)
+ /// The view instance.
+ /// The font weight string.
+ [ReactProperty(ViewProperties.FontWeight)]
+ public void SetFontWeight(ReactTextBox view, string fontWeightString)
{
- view.IsReadOnly = editable;
+ var fontWeight = FontStyleHelpers.ParseFontWeight(fontWeightString);
+ if (fontWeight.HasValue)
+ {
+ view.FontWeight = fontWeight.Value;
+ }
+ else
+ {
+ view.FontWeight = FontWeights.Normal;
+ }
}
///
- /// Sets the default text placeholder property on the .
+ /// Sets the font style for the node.
///
- /// The text input box control.
- /// placeholder text.
- [ReactProperty(PROP_PLACEHOLDER)]
- public void SetPlaceholder(TextBox view, string placeholder)
+ /// The view instance.
+ /// The font style string.
+ [ReactProperty(ViewProperties.FontStyle)]
+ public void SetFontStyle(ReactTextBox view, string fontStyleString)
{
- view.PlaceholderText = placeholder;
+ var fontStyle = FontStyleHelpers.ParseFontStyle(fontStyleString);
+ if (fontStyle.HasValue)
+ {
+ view.FontStyle = fontStyle.Value;
+ }
+ else
+ {
+ view.FontStyle = FontStyle.Normal;
+ }
}
///
- /// Sets the foreground color property on the .
+ /// Sets whether to track selection changes on the .
///
- /// The text input box control.
- /// The masked color value.
- [ReactProperty(ViewProperties.Color, CustomType = "Color")]
- public void SetColor(TextBox view, uint? color)
+ /// The view instance.
+ /// The indicator.
+ [ReactProperty("onSelectionChange", DefaultBoolean = false)]
+ public void SetSelectionChange(ReactTextBox view, bool? onSelectionChange)
{
- if (color.HasValue)
+ if (onSelectionChange.HasValue && onSelectionChange.Value)
{
- view.Foreground = new SolidColorBrush(ColorHelpers.Parse(color.Value));
+ _onSelectionChange = true;
+ view.SelectionChanged += OnSelectionChanged;
+ }
+ else
+ {
+ _onSelectionChange = false;
+ view.SelectionChanged -= OnSelectionChanged;
}
}
///
- /// Sets the max charcter length property on the .
+ /// Sets the default text placeholder property on the .
///
- /// The text input box control.
- /// The text alignment.
- [ReactProperty(PROP_MAX_LENGTH)]
- public void SetMaxLength(TextBox view, int maxCharLength)
+ /// The view instance.
+ /// The placeholder text.
+ [ReactProperty("placeholder")]
+ public void SetPlaceholder(ReactTextBox view, string placeholder)
{
- view.MaxLength = maxCharLength;
+ view.PlaceholderText = placeholder;
}
///
- /// The event interceptor for focus lost events for the native control.
+ /// Sets the selection color for the .
///
- /// The source sender view.
- /// The received event args
- public void OnInterceptLostFocusEvent(object sender, RoutedEventArgs @event)
+ /// The view instance.
+ /// The masked color value.
+ [ReactProperty("selectionColor", CustomType = "Color")]
+ public void SetSelectionColor(ReactTextBox view, uint color)
{
- var senderTextInput = (TextBox)sender;
- GetEventDispatcher(senderTextInput).DispatchEvent(new ReactTextInputBlurEvent(senderTextInput.GetTag()));
+ view.SelectionHighlightColor = new SolidColorBrush(ColorHelpers.Parse(color));
}
///
- /// The event interceptor for text change events for the native control.
+ /// Sets the text alignment property on the .
///
- /// The source sender view.
- /// The received event args
- public void OnInterceptTextChangeEvent(object sender, TextChangedEventArgs e)
+ /// The view instance.
+ /// The text alignment.
+ [ReactProperty(ViewProperties.TextAlign)]
+ public void SetTextAlign(ReactTextBox view, string alignment)
{
- var senderTextInput = (TextBox)sender;
- GetEventDispatcher(senderTextInput).DispatchEvent(new ReactTextChangedEvent(senderTextInput.GetTag(), senderTextInput.Text, senderTextInput.Width, senderTextInput.Height));
+ view.TextAlignment = EnumHelpers.Parse(alignment);
}
///
- /// Called when view is detached from view hierarchy and allows for
- /// additional cleanup by the .
- /// subclass. Unregister all event handlers for the .
+ /// Sets the text alignment property on the .
///
- /// The react context.
- /// The .
- public override void OnDropViewInstance(ThemedReactContext reactContext, TextBox view)
+ /// The view instance.
+ /// The text alignment.
+ [ReactProperty(ViewProperties.TextAlignVertical)]
+ public void SetTextVerticalAlign(ReactTextBox view, string alignment)
{
- view.TextChanged -= this.OnInterceptTextChangeEvent;
- // TODO: Figure out how to get intercept focus to work this to work.
- view.LostFocus -= this.OnInterceptLostFocusEvent;
+ view.VerticalContentAlignment = EnumHelpers.Parse(alignment);
}
///
- /// Returns the view instance for .
+ /// Sets the editablity property on the .
///
- ///
- ///
- protected override TextBox CreateViewInstance(ThemedReactContext reactContext)
+ /// The view instance.
+ /// The editable flag.
+ [ReactProperty("editable")]
+ public void SetEditable(ReactTextBox view, bool editable)
{
- return new TextBox();
+ view.IsEnabled = editable;
}
///
- /// Installing the textchanged event emitter on the Control.
+ /// Sets the max character length property on the .
///
- /// The react context.
- /// The view instance.
- protected override void AddEventEmitters(ThemedReactContext reactContext, TextBox view)
+ /// The view instance.
+ /// The max length.
+ [ReactProperty("maxLength")]
+ public void SetMaxLength(ReactTextBox view, int maxCharLength)
{
- view.TextChanged += this.OnInterceptTextChangeEvent;
- // TODO: Figure out how to get intercept focus to work this to work.
- view.LostFocus += this.OnInterceptLostFocusEvent;
+ view.MaxLength = maxCharLength;
}
///
- /// Sets the border width for a .
+ /// Sets whether to enable autocorrect on the .
///
- /// The text box instance.
- /// The border width.
- [ReactProperty(ViewProperties.BorderWidth)]
- public void SetBorderWidth(TextBox root, int width)
+ /// The view instance.
+ /// The autocorrect flag.
+ [ReactProperty("autoCorrect")]
+ public void SetAutoCorrect(ReactTextBox view, bool autoCorrect)
{
- root.BorderThickness = new Thickness(width);
+ view.IsSpellCheckEnabled = autoCorrect;
}
- public override void UpdateExtraData(TextBox root, object extraData)
+ ///
+ /// Sets whether to enable multiline input on the .
+ ///
+ /// The view instance.
+ /// The multiline flag.
+ [ReactProperty("multiline", DefaultBoolean = false)]
+ public void SetMultiline(ReactTextBox view, bool multiline)
{
- var reactTextBoxStyle = (ReactTextBoxProperties)extraData;
+ view.AcceptsReturn = multiline;
+ }
- if (reactTextBoxStyle == null)
+ ///
+ /// Sets the keyboard type on the .
+ ///
+ /// The view instance.
+ /// The keyboard type.
+ [ReactProperty("keyboardType")]
+ public void SetKeyboardType(ReactTextBox view, string keyboardType)
+ {
+ view.InputScope = null;
+ if (keyboardType != null)
{
- throw new InvalidOperationException("ReactTextBoxProperties is undefined exception. We were unable to measure the dimensions of the TextBox control.");
+ var inputScope = new InputScope();
+ inputScope.Names.Add(
+ new InputScopeName(
+ InputScopeHelpers.FromString(keyboardType)));
+
+ view.InputScope = inputScope;
}
+ }
- root.SetReactTextBoxProperties(reactTextBoxStyle);
+ ///
+ /// Sets the border width for a .
+ ///
+ /// The view instance.
+ /// The border width.
+ [ReactProperty(ViewProperties.BorderWidth)]
+ public void SetBorderWidth(ReactTextBox view, int width)
+ {
+ view.BorderThickness = new Thickness(width);
+ }
+
+ ///
+ /// Sets whether the text should be cleared on focus.
+ ///
+ /// The view instance.
+ /// The indicator.
+ [ReactProperty("clearTextOnFocus")]
+ public void SetClearTextOnFocus(ReactTextBox view, bool clearTextOnFocus)
+ {
+ view.ClearTextOnFocus = clearTextOnFocus;
}
+ ///
+ /// Sets whether the text should be selected on focus.
+ ///
+ /// The view instance.
+ /// The indicator.
+ [ReactProperty("selectTextOnFocus")]
+ public void SetSelectTextOnFocus(ReactTextBox view, bool selectTextOnFocus)
+ {
+ view.SelectTextOnFocus = selectTextOnFocus;
+ }
+
+ ///
+ /// Create the shadow node instance.
+ ///
+ /// The shadow node instance.
public override ReactTextInputShadowNode CreateShadowNodeInstance()
{
- return new ReactTextInputShadowNode(false);
+ return new ReactTextInputShadowNode();
}
///
/// Implement this method to receive events/commands directly from
- /// JavaScript through the .
+ /// JavaScript through the .
///
///
/// The view instance that should receive the command.
///
/// Identifer for the command.
/// Optional arguments for the command.
- public override void ReceiveCommand(TextBox view, int commandId, JArray args)
+ public override void ReceiveCommand(ReactTextBox view, int commandId, JArray args)
{
- if (commandId == FOCUS_TEXT_INPUT)
+ if (commandId == FocusTextInput)
{
+ // Sometimes, the focus command is received before the view
+ // is actually rendered on screen. In this case, we defer
+ // the focus command until after the view is ready.
+ // TODO: (#271) resolve issues with focus.
view.Focus(FocusState.Programmatic);
}
+ else if (commandId == BlurTextInput)
+ {
+ var frame = Window.Current?.Content as Frame;
+ frame?.Focus(FocusState.Programmatic);
+ }
+ }
+
+ ///
+ /// Update the view with extra data.
+ ///
+ /// The view instance.
+ /// The extra data.
+ public override void UpdateExtraData(ReactTextBox view, object extraData)
+ {
+ var paddings = extraData as float[];
+ var textUpdate = default(Tuple);
+ if (paddings != null)
+ {
+ view.Padding = new Thickness(
+ paddings[0],
+ paddings[1],
+ paddings[2],
+ paddings[3]);
+ }
+ else if ((textUpdate = extraData as Tuple) != null)
+ {
+ if (textUpdate.Item1 < view.CurrentEventCount)
+ {
+ return;
+ }
+
+ var text = textUpdate.Item2;
+
+ view.TextChanged -= OnTextChanged;
+ if (_onSelectionChange)
+ {
+ view.SelectionChanged -= OnSelectionChanged;
+ }
+
+ var selectionStart = view.SelectionStart;
+ var selectionLength = view.SelectionLength;
+ var textLength = text?.Length ?? 0;
+ var maxLength = textLength - selectionLength;
+
+ view.Text = text ?? "";
+ view.SelectionStart = Math.Min(selectionStart, textLength);
+ view.SelectionLength = Math.Min(selectionLength, maxLength < 0 ? 0 : maxLength);
+
+ view.TextChanged += OnTextChanged;
+ if (_onSelectionChange)
+ {
+ view.SelectionChanged += OnSelectionChanged;
+ }
+ }
+ }
+
+ ///
+ /// Called when view is detached from view hierarchy and allows for
+ /// additional cleanup by the .
+ /// subclass. Unregister all event handlers for the .
+ ///
+ /// The react context.
+ /// The .
+ public override void OnDropViewInstance(ThemedReactContext reactContext, ReactTextBox view)
+ {
+ view.KeyDown -= OnKeyDown;
+ view.LostFocus -= OnLostFocus;
+ view.GotFocus -= OnGotFocus;
+ view.TextChanged -= OnTextChanged;
+ }
+
+ ///
+ /// Returns the view instance for .
+ ///
+ ///
+ ///
+ protected override ReactTextBox CreateViewInstance(ThemedReactContext reactContext)
+ {
+ return new ReactTextBox
+ {
+ AcceptsReturn = false,
+ };
+ }
+
+ ///
+ /// Installing the textchanged event emitter on the Control.
+ ///
+ /// The react context.
+ /// The view instance.
+ protected override void AddEventEmitters(ThemedReactContext reactContext, ReactTextBox view)
+ {
+ view.TextChanged += OnTextChanged;
+ view.GotFocus += OnGotFocus;
+ view.LostFocus += OnLostFocus;
+ view.KeyDown += OnKeyDown;
+ }
+
+ private void OnTextChanged(object sender, TextChangedEventArgs e)
+ {
+ var textBox = (ReactTextBox)sender;
+ textBox.GetReactContext()
+ .GetNativeModule()
+ .EventDispatcher
+ .DispatchEvent(
+ new ReactTextChangedEvent(
+ textBox.GetTag(),
+ textBox.Text,
+ textBox.ActualWidth,
+ textBox.ActualHeight,
+ textBox.IncrementEventCount()));
+ }
+
+ private void OnGotFocus(object sender, RoutedEventArgs e)
+ {
+ var textBox = (ReactTextBox)sender;
+ textBox.GetReactContext()
+ .GetNativeModule()
+ .EventDispatcher
+ .DispatchEvent(
+ new ReactTextInputFocusEvent(textBox.GetTag()));
+ }
+
+ private void OnLostFocus(object sender, RoutedEventArgs e)
+ {
+ var textBox = (ReactTextBox)sender;
+ var eventDispatcher = textBox.GetReactContext()
+ .GetNativeModule()
+ .EventDispatcher;
+
+ eventDispatcher.DispatchEvent(
+ new ReactTextInputBlurEvent(textBox.GetTag()));
+
+ eventDispatcher.DispatchEvent(
+ new ReactTextInputEndEditingEvent(
+ textBox.GetTag(),
+ textBox.Text));
+ }
+
+ private void OnKeyDown(object sender, KeyRoutedEventArgs e)
+ {
+ if (e.Key == VirtualKey.Enter)
+ {
+ var textBox = (ReactTextBox)sender;
+ if (!textBox.AcceptsReturn)
+ {
+ e.Handled = true;
+ textBox.GetReactContext()
+ .GetNativeModule()
+ .EventDispatcher
+ .DispatchEvent(
+ new ReactTextInputSubmitEditingEvent(
+ textBox.GetTag(),
+ textBox.Text));
+ }
+ }
}
-
- private EventDispatcher GetEventDispatcher(TextBox textBox)
+
+ private void OnSelectionChanged(object sender, RoutedEventArgs e)
{
- return textBox?.GetReactContext().GetNativeModule().EventDispatcher;
+ var textBox = (ReactTextBox)sender;
+ var start = textBox.SelectionStart;
+ var length = textBox.SelectionLength;
+ textBox.GetReactContext()
+ .GetNativeModule()
+ .EventDispatcher
+ .DispatchEvent(
+ new ReactTextInputSelectionEvent(
+ textBox.GetTag(),
+ start,
+ start + length));
}
}
}
diff --git a/ReactNative/Views/TextInput/ReactTextInputSelectionEvent.cs b/ReactNative/Views/TextInput/ReactTextInputSelectionEvent.cs
new file mode 100644
index 00000000000..4c6869f46b8
--- /dev/null
+++ b/ReactNative/Views/TextInput/ReactTextInputSelectionEvent.cs
@@ -0,0 +1,43 @@
+using Newtonsoft.Json.Linq;
+using ReactNative.UIManager.Events;
+using System;
+
+namespace ReactNative.Views.TextInput
+{
+ class ReactTextInputSelectionEvent : Event
+ {
+ private readonly int _end;
+ private readonly int _start;
+
+ public ReactTextInputSelectionEvent(int viewTag, int start, int end)
+ : base(viewTag, TimeSpan.FromTicks(Environment.TickCount))
+ {
+ _start = start;
+ _end = end;
+ }
+
+ public override string EventName
+ {
+ get
+ {
+ return "topSelectionChange";
+ }
+ }
+
+ public override void Dispatch(RCTEventEmitter eventEmitter)
+ {
+ var selectionData = new JObject
+ {
+ { "start", _start },
+ { "end", _end },
+ };
+
+ var eventData = new JObject
+ {
+ { "selection", selectionData },
+ };
+
+ eventEmitter.receiveEvent(ViewTag, EventName, eventData);
+ }
+ }
+}
\ No newline at end of file
diff --git a/ReactNative/Views/TextInput/ReactTextInputShadowNode.cs b/ReactNative/Views/TextInput/ReactTextInputShadowNode.cs
index cdfb10dd648..0f3245cbe5e 100644
--- a/ReactNative/Views/TextInput/ReactTextInputShadowNode.cs
+++ b/ReactNative/Views/TextInput/ReactTextInputShadowNode.cs
@@ -2,11 +2,11 @@
using ReactNative.Bridge;
using ReactNative.UIManager;
using ReactNative.Views.Text;
+using System;
using Windows.Foundation;
-using Windows.UI.Text;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
-using Windows.UI.Xaml.Media;
+using Windows.UI.Xaml.Documents;
namespace ReactNative.Views.TextInput
{
@@ -14,188 +14,153 @@ namespace ReactNative.Views.TextInput
/// This extension of is responsible for
/// measuring the layout for Native .
///
- public class ReactTextInputShadowNode : LayoutShadowNode
+ public class ReactTextInputShadowNode : ReactTextShadowNode
{
- private const int UNSET = -1;
+ private const int Unset = -1;
- private readonly bool _isVirtual;
+ private float[] _computedPadding;
+
+ private int _numberOfLines = Unset;
+ private int _jsEventCount = Unset;
- private ReactTextBoxProperties _textBoxStyle;
-
///
/// Instantiates the .
///
- ///
- /// Indicates whether the shadow node is virtual or not.
- ///
- public ReactTextInputShadowNode(bool isVirtual)
+ public ReactTextInputShadowNode()
+ : base(false)
{
- _textBoxStyle = new ReactTextBoxProperties();
- _isVirtual = isVirtual;
-
- if (!isVirtual)
- {
- MeasureFunction = MeasureText;
- }
+ var computedPadding = GetDefaultPaddings();
+ SetPadding(CSSSpacingType.Left, computedPadding[0]);
+ SetPadding(CSSSpacingType.Top, computedPadding[1]);
+ SetPadding(CSSSpacingType.Right, computedPadding[2]);
+ SetPadding(CSSSpacingType.Bottom, computedPadding[3]);
+ MeasureFunction = MeasureText;
}
///
- /// Nodes that return true
will be treated as "virtual"
- /// nodes. That is, nodes that are not mapped into native views (e.g.,
- /// nested text node).
+ /// Set the most recent event count in JavaScript.
///
- public override bool IsVirtual
+ /// The event count.
+ [ReactProperty("mostRecentEventCount")]
+ public void SetMostRecentEventCount(int mostRecentEventCount)
{
- get
- {
- return _isVirtual;
- }
+ _jsEventCount = mostRecentEventCount;
}
///
- /// Queues up the view operations onto the .
+ /// Set the number of lines for the text input.
///
- ///
- public override void OnCollectExtraUpdates(UIViewOperationQueue uiViewOperationQueue)
+ /// The event count.
+ [ReactProperty("numberOfLines")]
+ public void SetNumberOfLines(int numberOfLines)
{
- if (_isVirtual)
- {
- return;
- }
-
- base.OnCollectExtraUpdates(uiViewOperationQueue);
- if (_textBoxStyle != null)
- {
- uiViewOperationQueue.EnqueueUpdateExtraData(ReactTag, _textBoxStyle);
- }
+ _numberOfLines = numberOfLines;
}
///
- /// This lifecycle method is called by to bind the CSS styling to the .
+ /// Called once per batch of updates by the
+ /// if the text node is dirty.
///
public override void OnBeforeLayout()
{
- DispatcherHelpers.AssertOnDispatcher();
-
- if (_isVirtual)
- {
- return;
- }
-
- MarkUpdated();
- }
-
- ///
- /// Sets the font size for the .
- ///
- /// The font size.
- [ReactProperty(ViewProperties.FontSize, DefaultDouble = UNSET)]
- public void SetFontSize(double fontSize)
- {
- _textBoxStyle.FontSize = (int)fontSize;
- MarkUpdated();
+ return;
}
///
- /// Sets the text for the .
+ /// Called to aggregate the current text and event counter.
///
- /// Font family string.
- [ReactProperty(ViewProperties.FontFamily)]
- public void SetFontFamily(string fontFamily)
+ /// The UI operation queue.
+ public override void OnCollectExtraUpdates(UIViewOperationQueue uiViewOperationQueue)
{
- _textBoxStyle.FontFamily = new FontFamily(fontFamily);
- MarkUpdated();
- }
+ base.OnCollectExtraUpdates(uiViewOperationQueue);
- ///
- /// Sets the text for the .
- ///
- /// Font weight string.
- [ReactProperty(ViewProperties.FontWeight)]
- public void SetFontWeight(string fontWeightString)
- {
- var fontWeight = FontStyleHelpers.ParseFontWeight(fontWeightString);
- if (_textBoxStyle.FontWeight.HasValue != fontWeight.HasValue ||
- (_textBoxStyle.FontWeight.HasValue && fontWeight.HasValue &&
- _textBoxStyle.FontWeight.Value.Weight != fontWeight.Value.Weight))
+ if (_computedPadding != null)
{
- _textBoxStyle.FontWeight = fontWeight;
- MarkUpdated();
+ uiViewOperationQueue.EnqueueUpdateExtraData(ReactTag, _computedPadding);
+ _computedPadding = null;
}
- }
- ///
- /// Sets the text for the .
- ///
- /// Font style string.
- [ReactProperty(ViewProperties.FontStyle)]
- public void SetFontStyle(string fontStyleString)
- {
- var fontStyle = FontStyleHelpers.ParseFontStyle(fontStyleString);
- if (_textBoxStyle.FontStyle != fontStyle)
+ if (_jsEventCount != Unset)
{
- _textBoxStyle.FontStyle = fontStyle;
- MarkUpdated();
+ uiViewOperationQueue.EnqueueUpdateExtraData(ReactTag, Tuple.Create(_jsEventCount, Text));
}
}
- ///
- /// Sets the text value for the .
- ///
- /// The text.
- [ReactProperty("text")]
- public void SetText(string text)
- {
- _textBoxStyle.Text = text;
- MarkUpdated();
- }
-
- ///
- /// Sets the the border color for a .
- ///
- /// The masked color value.
- [ReactProperty("borderColor")]
- public void SetBorderColor(uint? color)
+ private MeasureOutput MeasureText(CSSNode node, float width, float height)
{
- if (color.HasValue)
+ _computedPadding = GetComputedPadding();
+
+ var normalizedWidth = CSSConstants.IsUndefined(width) ? double.PositiveInfinity : width;
+ var normalizedHeight = CSSConstants.IsUndefined(height) ? double.PositiveInfinity : height;
+
+ var borderLeftWidth = GetBorder(CSSSpacingType.Left);
+ var borderRightWidth = GetBorder(CSSSpacingType.Right);
+
+ normalizedWidth -= _computedPadding[0];
+ normalizedWidth -= _computedPadding[2];
+ normalizedWidth -= CSSConstants.IsUndefined(borderLeftWidth) ? 0 : borderLeftWidth;
+ normalizedWidth -= CSSConstants.IsUndefined(borderRightWidth) ? 0 : borderRightWidth;
+
+ // This is not a terribly efficient way of projecting the height of
+ // the text elements. It requires that we have access to the
+ // dispatcher in order to do measurement, which, for obvious
+ // reasons, can cause perceived performance issues as it will block
+ // the UI thread from handling other work.
+ //
+ // TODO: determine another way to measure text elements.
+ var task = DispatcherHelpers.CallOnDispatcher(() =>
{
- _textBoxStyle.BorderColor = ColorHelpers.Parse(color.Value);
- MarkUpdated();
- }
- }
+ var textNode = (ReactTextInputShadowNode)node;
- ///
- /// Marks the node as updated/dirty. This occurs on any property
- /// changes affecting the measurement of the .
- ///
- protected override void MarkUpdated()
- {
- base.MarkUpdated();
+ var textBlock = new TextBlock
+ {
+ TextWrapping = TextWrapping.Wrap,
+ };
- if (!_isVirtual)
- {
- dirty();
- }
+ var normalizedText = string.IsNullOrEmpty(textNode.Text) ? " " : textNode.Text;
+ var inline = new Run { Text = normalizedText };
+ FormatInline(textNode, inline, true);
+
+ textBlock.Inlines.Add(inline);
+
+ textBlock.Measure(new Size(normalizedWidth, normalizedHeight));
+
+ var borderTopWidth = GetBorder(CSSSpacingType.Top);
+ var borderBottomWidth = GetBorder(CSSSpacingType.Bottom);
+
+ var finalizedHeight = (float)textBlock.DesiredSize.Height;
+ finalizedHeight += _computedPadding[1];
+ finalizedHeight += _computedPadding[3];
+ finalizedHeight += CSSConstants.IsUndefined(borderTopWidth) ? 0 : borderTopWidth;
+ finalizedHeight += CSSConstants.IsUndefined(borderBottomWidth) ? 0 : borderBottomWidth;
+
+ return new MeasureOutput(width, finalizedHeight);
+ });
+
+ return task.Result;
}
- private MeasureOutput MeasureText(CSSNode node, float width, float height)
+ private float[] GetDefaultPaddings()
{
- var shadowNode = (ReactTextInputShadowNode)node;
- var textBox = new TextBox();
-
- textBox.SetReactTextBoxProperties(shadowNode._textBoxStyle);
-
- if (!float.IsNaN(width))
+ // TODO: calculate dynamically
+ return new[]
{
- textBox.MaxWidth = width;
- }
+ 10f,
+ 3f,
+ 6f,
+ 5f,
+ };
+ }
- if (!float.IsNaN(height))
+ private float[] GetComputedPadding()
+ {
+ return new float[]
{
- textBox.MaxHeight = height;
- }
-
- return new MeasureOutput((float)textBox.DesiredSize.Width, (float)textBox.DesiredSize.Height);
+ GetPadding(CSSSpacingType.Left),
+ GetPadding(CSSSpacingType.Top),
+ GetPadding(CSSSpacingType.Right),
+ GetPadding(CSSSpacingType.Bottom),
+ };
}
}
}
diff --git a/ReactNative/Views/TextInput/ReactTextInputSubmitEditingEvent.cs b/ReactNative/Views/TextInput/ReactTextInputSubmitEditingEvent.cs
new file mode 100644
index 00000000000..2c633c18259
--- /dev/null
+++ b/ReactNative/Views/TextInput/ReactTextInputSubmitEditingEvent.cs
@@ -0,0 +1,44 @@
+using Newtonsoft.Json.Linq;
+using ReactNative.UIManager.Events;
+using System;
+
+namespace ReactNative.Views.TextInput
+{
+ class ReactTextInputSubmitEditingEvent : Event
+ {
+ private readonly string _text;
+
+ public ReactTextInputSubmitEditingEvent(int viewTag, string text)
+ : base(viewTag, TimeSpan.FromTicks(Environment.TickCount))
+ {
+ _text = text;
+ }
+
+ public override string EventName
+ {
+ get
+ {
+ return "topSubmitEditing";
+ }
+ }
+
+ public override bool CanCoalesce
+ {
+ get
+ {
+ return false;
+ }
+ }
+
+ public override void Dispatch(RCTEventEmitter eventEmitter)
+ {
+ var eventData = new JObject
+ {
+ { "target", ViewTag },
+ { "text", _text },
+ };
+
+ eventEmitter.receiveEvent(ViewTag, EventName, eventData);
+ }
+ }
+}
\ No newline at end of file
diff --git a/ReactNative/Views/TextInput/TextBoxExtensions.cs b/ReactNative/Views/TextInput/TextBoxExtensions.cs
deleted file mode 100644
index 906f3c482a5..00000000000
--- a/ReactNative/Views/TextInput/TextBoxExtensions.cs
+++ /dev/null
@@ -1,51 +0,0 @@
-using ReactNative.Views.TextInput;
-using Windows.UI.Xaml.Controls;
-using Windows.UI.Xaml.Media;
-
-namespace ReactNative.Views.TextInput
-{
- static class TextBoxExtensions
- {
- private const int UNSET = -1;
-
- ///
- /// Assigns any set property from the instance.
- ///
- /// The current context.
- /// The property instance.
- public static void SetReactTextBoxProperties(this TextBox textBox, ReactTextBoxProperties reactProperties)
- {
- textBox.Text = reactProperties.Text != null ? reactProperties.Text : "";
-
- if (reactProperties.FontWeight.HasValue)
- {
- textBox.FontWeight = reactProperties.FontWeight.Value;
- }
-
- if (reactProperties.FontStyle.HasValue)
- {
- textBox.FontStyle = reactProperties.FontStyle.Value;
- }
-
- if (reactProperties.FontSize != UNSET)
- {
- textBox.FontSize = reactProperties.FontSize;
- }
-
- if (reactProperties.FontFamily != null)
- {
- textBox.FontFamily = reactProperties.FontFamily;
- }
-
- if (reactProperties.BorderColor.HasValue)
- {
- textBox.BorderBrush = new SolidColorBrush(reactProperties.BorderColor.Value);
- }
-
- if (reactProperties.Padding.HasValue)
- {
- textBox.Padding = reactProperties.Padding.Value;
- }
- }
- }
-}