whiteSpace default value change to undefined (#3782)

TKUnit default message change to empty string
isSet method is now instance method of Property classes
fix detaching from parent bindingContext - were using oldParent.parent instead of parent
editable-text-base.android - onTextChanged implementation commented. Does nothing.
frame - onCreateView wrapped in try/catch and shows label with exception message if any
text-base.android - should support reset of nativeView. TransformationMethod won’t be set if TextField is secure
Change some types to their string couterparts
TextField.android won’t support multilines anymore in order to work as iOS
In android when page is removed from native backstack we won’t call tearDownUI again a second time
This commit is contained in:
Hristo Hristov
2017-03-14 10:26:45 +02:00
committed by GitHub
parent 8592c934ec
commit a64bba62aa
15 changed files with 314 additions and 303 deletions

View File

@ -214,7 +214,7 @@ export function assertNotEqual(actual: any, expected: any, message?: string) {
} }
} }
export function assertEqual<T extends { equals?(arg: T): boolean }>(actual: T, expected: T, message?: string) { export function assertEqual<T extends { equals?(arg: T): boolean }>(actual: T, expected: T, message: string = '') {
if (!types.isNullOrUndefined(actual) if (!types.isNullOrUndefined(actual)
&& !types.isNullOrUndefined(expected) && !types.isNullOrUndefined(expected)
&& types.getClass(actual) === types.getClass(expected) && types.getClass(actual) === types.getClass(expected)

View File

@ -508,18 +508,17 @@ export function test_TwoElementsBindingToSameBindingContext() {
export function test_BindingContext_NavigatingForwardAndBack() { export function test_BindingContext_NavigatingForwardAndBack() {
const expectedValue = "Tralala"; const expectedValue = "Tralala";
const moduleName = __dirname.substr(fs.knownFolders.currentApp().path.length);
const testFunc = function (page: Page) { const testFunc = function (page: Page) {
const innerTestFunc = function (childPage: Page) { const innerTestFunc = function (childPage: Page) {
const testTextField: TextField = <TextField>(childPage.getViewById("testTextField")); const testTextField: TextField = <TextField>(childPage.getViewById("testTextField"));
testTextField.text = expectedValue; testTextField.text = expectedValue;
}; };
const moduleName = __dirname.substr(fs.knownFolders.currentApp().path.length);
helper.navigateToModuleAndRunTest(("." + moduleName + "/bindingContext_testPage2"), page.bindingContext, innerTestFunc); helper.navigateToModuleAndRunTest(("." + moduleName + "/bindingContext_testPage2"), page.bindingContext, innerTestFunc);
const testLabel: Label = <Label>(page.getViewById("testLabel")); const testLabel: Label = <Label>(page.getViewById("testLabel"));
TKUnit.assertEqual(testLabel.text, expectedValue); TKUnit.assertEqual(testLabel.text, expectedValue);
}; };
const moduleName = __dirname.substr(fs.knownFolders.currentApp().path.length);
helper.navigateToModuleAndRunTest(("." + moduleName + "/bindingContext_testPage1"), null, testFunc); helper.navigateToModuleAndRunTest(("." + moduleName + "/bindingContext_testPage1"), null, testFunc);
}; };

View File

@ -5,37 +5,12 @@ import { Label } from "tns-core-modules/ui/label";
import { TextField } from "tns-core-modules/ui/text-field"; import { TextField } from "tns-core-modules/ui/text-field";
import { TextView } from "tns-core-modules/ui/text-view"; import { TextView } from "tns-core-modules/ui/text-view";
import { StackLayout } from "tns-core-modules/ui/layouts/stack-layout"; import { StackLayout } from "tns-core-modules/ui/layouts/stack-layout";
import { Page } from "tns-core-modules/ui/page";
import { Color } from "tns-core-modules/color"; import { Color } from "tns-core-modules/color";
import { isAndroid, isIOS } from "tns-core-modules/platform"; import { isAndroid, isIOS } from "tns-core-modules/platform";
import { View } from "tns-core-modules/ui/core/view"; import { View } from "tns-core-modules/ui/core/view";
import { Length, PercentLength } from "tns-core-modules/ui/core/view"; import { Length, PercentLength } from "tns-core-modules/ui/core/view";
import * as fontModule from "tns-core-modules/ui/styling/font"; import * as fontModule from "tns-core-modules/ui/styling/font";
let testBtn: Button;
let testPage: Page;
export function setUpModule() {
const pageFactory = function () {
testPage = new Page();
testBtn = new Button();
testBtn.text = "test";
testBtn.id = "testBtn";
testPage.content = testBtn;
return testPage;
};
helper.navigate(pageFactory);
}
export function tearDownModule() {
testBtn = null;
testPage = null;
}
export function tearDown() {
testPage.css = "";
}
export function test_setting_textDecoration_property_from_CSS_is_applied_to_Style() { export function test_setting_textDecoration_property_from_CSS_is_applied_to_Style() {
test_property_from_CSS_is_applied_to_style("textDecoration", "text-decoration", "underline"); test_property_from_CSS_is_applied_to_style("textDecoration", "text-decoration", "underline");
} }
@ -214,14 +189,18 @@ function test_property_from_CSS_is_applied_to_style(propName: string, cssName: s
cssValue = value + ""; cssValue = value + "";
} }
testPage.css = "#testBtn { " + cssName + ": " + cssValue + " }"; const btn = new Button();
btn.id = "testBtn";
const page = helper.getCurrentPage();
page.css = "#testBtn { " + cssName + ": " + cssValue + " }";
page.content = btn;
if (useDeepEquals) { if (useDeepEquals) {
TKUnit.assertDeepEqual(testBtn.style[propName], value); TKUnit.assertDeepEqual(btn.style[propName], value);
} else { } else {
TKUnit.assertEqual(testBtn.style[propName], value, "Setting property " + propName + " with CSS name " + cssName); TKUnit.assertEqual(btn.style[propName], value, "Setting property " + propName + " with CSS name " + cssName);
} }
testPage.css = "";
} }
export function test_width_property_is_synced_in_style_and_view() { export function test_width_property_is_synced_in_style_and_view() {

View File

@ -38,7 +38,6 @@ export class FormattedString extends ViewBase implements FormattedStringDefiniti
this.style.fontSize = value; this.style.fontSize = value;
} }
// Italic
get fontStyle(): FontStyle { get fontStyle(): FontStyle {
return this.style.fontStyle; return this.style.fontStyle;
} }
@ -46,7 +45,6 @@ export class FormattedString extends ViewBase implements FormattedStringDefiniti
this.style.fontStyle = value; this.style.fontStyle = value;
} }
// Bold
get fontWeight(): FontWeight { get fontWeight(): FontWeight {
return this.style.fontWeight; return this.style.fontWeight;
} }

View File

@ -1,6 +1,7 @@
import { import {
ButtonBase, PseudoClassHandler, ButtonBase, PseudoClassHandler,
paddingLeftProperty, paddingTopProperty, paddingRightProperty, paddingBottomProperty, Length, zIndexProperty paddingLeftProperty, paddingTopProperty, paddingRightProperty, paddingBottomProperty,
Length, zIndexProperty
} from "./button-common"; } from "./button-common";
import { TouchGestureEventData, GestureTypes, TouchAction } from "../gestures"; import { TouchGestureEventData, GestureTypes, TouchAction } from "../gestures";
@ -35,16 +36,13 @@ function initializeClickListener(): void {
export class Button extends ButtonBase { export class Button extends ButtonBase {
_button: android.widget.Button; _button: android.widget.Button;
private _highlightedHandler: (args: TouchGestureEventData) => void;
get android(): android.widget.Button { private _highlightedHandler: (args: TouchGestureEventData) => void;
return this._button;
}
public _createNativeView() { public _createNativeView() {
initializeClickListener(); initializeClickListener();
const button = this._button = new android.widget.Button(this._context); const button = this._button = new android.widget.Button(this._context);
this._button.setOnClickListener(new ClickListener(this)); button.setOnClickListener(new ClickListener(this));
return button; return button;
} }

View File

@ -3,10 +3,6 @@ import { Style } from "../../styling/style";
export { Style }; export { Style };
//@private
export function _isSet(cssProperty: CssProperty<any, any>, instance: Style): boolean;
//@endprivate
/** /**
* Value specifing that Property should be set to its initial value. * Value specifing that Property should be set to its initial value.
*/ */
@ -29,6 +25,13 @@ export interface CssPropertyOptions<T extends Style, U> extends PropertyOptions<
readonly cssName: string; readonly cssName: string;
} }
export interface ShorthandPropertyOptions<P> {
readonly name: string,
readonly cssName: string;
readonly converter: (value: string | P) => [CssProperty<any, any>, any][],
readonly getter: (this: Style) => string | P
}
export interface CssAnimationPropertyOptions<T, U> { export interface CssAnimationPropertyOptions<T, U> {
readonly name: string; readonly name: string;
readonly cssName?: string; readonly cssName?: string;
@ -38,21 +41,6 @@ export interface CssAnimationPropertyOptions<T, U> {
readonly valueConverter?: (value: string) => U; readonly valueConverter?: (value: string) => U;
} }
export class CssAnimationProperty<T extends Style, U> {
constructor(options: CssAnimationPropertyOptions<T, U>);
public readonly name: string;
public readonly cssName: string;
public readonly native: symbol;
readonly keyframe: string;
public register(cls: { prototype: T }): void;
public _valueConverter?: (value: string) => any;
public static _getByCssName(name: string): CssAnimationProperty<any, any>;
}
export class Property<T extends ViewBase, U> implements TypedPropertyDescriptor<U> { export class Property<T extends ViewBase, U> implements TypedPropertyDescriptor<U> {
constructor(options: PropertyOptions<T, U>); constructor(options: PropertyOptions<T, U>);
@ -60,6 +48,7 @@ export class Property<T extends ViewBase, U> implements TypedPropertyDescriptor<
public readonly defaultValue: U; public readonly defaultValue: U;
public register(cls: { prototype: T }): void; public register(cls: { prototype: T }): void;
public nativeValueChange(T, U): void; public nativeValueChange(T, U): void;
public isSet(instance: T): boolean;
} }
export class CoercibleProperty<T extends ViewBase, U> extends Property<T, U> implements TypedPropertyDescriptor<U> { export class CoercibleProperty<T extends ViewBase, U> extends Property<T, U> implements TypedPropertyDescriptor<U> {
@ -80,28 +69,39 @@ export class CssProperty<T extends Style, U> {
public readonly cssName: string; public readonly cssName: string;
public readonly defaultValue: U; public readonly defaultValue: U;
public register(cls: { prototype: T }): void; public register(cls: { prototype: T }): void;
public isSet(instance: T): boolean;
} }
export class InheritedCssProperty<T extends Style, U> extends CssProperty<T, U> { export class InheritedCssProperty<T extends Style, U> extends CssProperty<T, U> {
constructor(options: CssPropertyOptions<T, U>); constructor(options: CssPropertyOptions<T, U>);
} }
export interface ShorthandPropertyOptions<P> {
readonly name: string,
readonly cssName: string;
readonly converter: (value: string | P) => [CssProperty<any, any>, any][],
readonly getter: (this: Style) => string | P
}
export class ShorthandProperty<T extends Style, P> { export class ShorthandProperty<T extends Style, P> {
constructor(options: ShorthandPropertyOptions<P>); constructor(options: ShorthandPropertyOptions<P>);
public readonly native: symbol;
public readonly name: string; public readonly name: string;
public readonly cssName: string; public readonly cssName: string;
public readonly native: symbol;
public register(cls: { prototype: T }): void; public register(cls: { prototype: T }): void;
} }
export class CssAnimationProperty<T extends Style, U> {
constructor(options: CssAnimationPropertyOptions<T, U>);
public readonly name: string;
public readonly cssName: string;
public readonly native: symbol;
readonly keyframe: string;
public register(cls: { prototype: T }): void;
public isSet(instance: T): boolean;
public _valueConverter?: (value: string) => any;
public static _getByCssName(name: string): CssAnimationProperty<any, any>;
}
export function initNativeView(view: ViewBase): void; export function initNativeView(view: ViewBase): void;
export function resetNativeView(view: ViewBase): void; export function resetNativeView(view: ViewBase): void;
export function resetCSSProperties(style: Style): void; export function resetCSSProperties(style: Style): void;

View File

@ -26,10 +26,6 @@ function print(map) {
} }
} }
export function _isSet(cssProperty: CssProperty<any, any>, instance: Style): boolean {
return cssProperty.sourceKey in instance;
}
export function _printUnregisteredProperties(): void { export function _printUnregisteredProperties(): void {
print(symbolPropertyMap); print(symbolPropertyMap);
print(cssSymbolPropertyMap); print(cssSymbolPropertyMap);
@ -177,6 +173,10 @@ export class Property<T extends ViewBase, U> implements TypedPropertyDescriptor<
this.registered = true; this.registered = true;
Object.defineProperty(cls.prototype, this.name, this); Object.defineProperty(cls.prototype, this.name, this);
} }
public isSet(instance: T): boolean {
return this.key in instance;
}
} }
export class CoercibleProperty<T extends ViewBase, U> extends Property<T, U> implements definitions.CoercibleProperty<T, U> { export class CoercibleProperty<T extends ViewBase, U> extends Property<T, U> implements definitions.CoercibleProperty<T, U> {
@ -544,6 +544,10 @@ export class CssProperty<T extends Style, U> implements definitions.CssProperty<
Object.defineProperty(cls.prototype, this.cssLocalName, this.localValueDescriptor); Object.defineProperty(cls.prototype, this.cssLocalName, this.localValueDescriptor);
} }
} }
public isSet(instance: T): boolean {
return this.key in instance;
}
} }
export class CssAnimationProperty<T extends Style, U> { export class CssAnimationProperty<T extends Style, U> {
@ -555,6 +559,7 @@ export class CssAnimationProperty<T extends Style, U> {
public readonly keyframe: string; public readonly keyframe: string;
public readonly defaultValueKey: symbol; public readonly defaultValueKey: symbol;
public readonly computedValueKey: symbol;
private static properties: { [cssName: string]: CssAnimationProperty<any, any> } = {}; private static properties: { [cssName: string]: CssAnimationProperty<any, any> } = {};
@ -582,6 +587,7 @@ export class CssAnimationProperty<T extends Style, U> {
const styleValue = Symbol(propertyName); const styleValue = Symbol(propertyName);
const keyframeValue = Symbol(keyframeName); const keyframeValue = Symbol(keyframeName);
const computedValue = Symbol("computed-value:" + propertyName); const computedValue = Symbol("computed-value:" + propertyName);
this.computedValueKey = computedValue;
const computedSource = Symbol("computed-source:" + propertyName); const computedSource = Symbol("computed-source:" + propertyName);
const native = this.native = Symbol("native:" + propertyName); const native = this.native = Symbol("native:" + propertyName);
@ -664,6 +670,10 @@ export class CssAnimationProperty<T extends Style, U> {
public static _getByCssName(name: string): CssAnimationProperty<any, any> { public static _getByCssName(name: string): CssAnimationProperty<any, any> {
return this.properties[name]; return this.properties[name];
} }
public isSet(instance: T): boolean {
return instance[this.computedValueKey] !== unsetValue;
}
} }
export class InheritedCssProperty<T extends Style, U> extends CssProperty<T, U> implements definitions.InheritedCssProperty<T, U> { export class InheritedCssProperty<T extends Style, U> extends CssProperty<T, U> implements definitions.InheritedCssProperty<T, U> {

View File

@ -6,7 +6,7 @@ import { Order, FlexGrow, FlexShrink, FlexWrapBefore, AlignSelf } from "../../la
import { KeyframeAnimation } from "../../animation/keyframe-animation"; import { KeyframeAnimation } from "../../animation/keyframe-animation";
// Types. // Types.
import { Property, InheritedProperty, Style, clearInheritedProperties, propagateInheritableProperties, propagateInheritableCssProperties, resetCSSProperties, initNativeView, resetNativeView, _isSet } from "../properties"; import { Property, InheritedProperty, Style, clearInheritedProperties, propagateInheritableProperties, propagateInheritableCssProperties, resetCSSProperties, initNativeView, resetNativeView } from "../properties";
import { Binding, BindingOptions, Observable, WrappedValue, PropertyChangeData, traceEnabled, traceWrite, traceCategories, traceNotifyEvent } from "../bindable"; import { Binding, BindingOptions, Observable, WrappedValue, PropertyChangeData, traceEnabled, traceWrite, traceCategories, traceNotifyEvent } from "../bindable";
import { isIOS, isAndroid } from "../../../platform"; import { isIOS, isAndroid } from "../../../platform";
import { layout } from "../../../utils/utils"; import { layout } from "../../../utils/utils";
@ -97,6 +97,8 @@ export class ViewBase extends Observable implements ViewBaseDefinition {
public recycleNativeView: boolean; public recycleNativeView: boolean;
private _iosView: Object;
private _androidView: Object;
private _style: Style; private _style: Style;
private _isLoaded: boolean; private _isLoaded: boolean;
private _registeredAnimations: Array<KeyframeAnimation>; private _registeredAnimations: Array<KeyframeAnimation>;
@ -180,11 +182,11 @@ export class ViewBase extends Observable implements ViewBaseDefinition {
} }
get android(): any { get android(): any {
return undefined; return this._androidView;
} }
get ios(): any { get ios(): any {
return undefined; return this._iosView;
} }
get isLoaded(): boolean { get isLoaded(): boolean {
@ -581,8 +583,7 @@ export class ViewBase extends Observable implements ViewBaseDefinition {
traceNotifyEvent(this, "_onContextChanged"); traceNotifyEvent(this, "_onContextChanged");
if (isAndroid) { if (isAndroid) {
const native: any = this._createNativeView(); const nativeView = this._androidView = this.nativeView = <android.view.View>this._createNativeView();
const nativeView: android.view.View = this.nativeView = native;
if (nativeView) { if (nativeView) {
let result: android.graphics.Rect = (<any>nativeView).defaultPaddings; let result: android.graphics.Rect = (<any>nativeView).defaultPaddings;
if (result === undefined) { if (result === undefined) {
@ -596,23 +597,23 @@ export class ViewBase extends Observable implements ViewBaseDefinition {
this._defaultPaddingLeft = result.left; this._defaultPaddingLeft = result.left;
const style = this.style; const style = this.style;
if (!_isSet(paddingTopProperty, style)) { if (!paddingTopProperty.isSet(style)) {
this.effectivePaddingTop = this._defaultPaddingTop; this.effectivePaddingTop = this._defaultPaddingTop;
} }
if (!_isSet(paddingRightProperty, style)) { if (!paddingRightProperty.isSet(style)) {
this.effectivePaddingRight = this._defaultPaddingRight; this.effectivePaddingRight = this._defaultPaddingRight;
} }
if (!_isSet(paddingBottomProperty, style)) { if (!paddingBottomProperty.isSet(style)) {
this.effectivePaddingBottom = this._defaultPaddingBottom; this.effectivePaddingBottom = this._defaultPaddingBottom;
} }
if (!_isSet(paddingLeftProperty, style)) { if (!paddingLeftProperty.isSet(style)) {
this.effectivePaddingLeft = this._defaultPaddingLeft; this.effectivePaddingLeft = this._defaultPaddingLeft;
} }
} }
} else { } else {
// TODO: Implement _createNativeView for iOS // TODO: Implement _createNativeView for iOS
this._createNativeView(); this._createNativeView();
this.nativeView = (<any>this)._nativeView; this.nativeView = this._iosView = (<any>this)._nativeView;
} }
this._initNativeView(); this._initNativeView();
@ -637,6 +638,8 @@ export class ViewBase extends Observable implements ViewBaseDefinition {
traceWrite(`${this}._tearDownUI(${force})`, traceCategories.VisualTreeEvents); traceWrite(`${this}._tearDownUI(${force})`, traceCategories.VisualTreeEvents);
} }
this._resetNativeView();
this.eachChild((child) => { this.eachChild((child) => {
child._tearDownUI(force); child._tearDownUI(force);
return true; return true;
@ -646,8 +649,6 @@ export class ViewBase extends Observable implements ViewBaseDefinition {
this.parent._removeViewFromNativeVisualTree(this); this.parent._removeViewFromNativeVisualTree(this);
} }
this._resetNativeView();
this._disposeNativeView(); this._disposeNativeView();
this._context = null; this._context = null;
@ -713,7 +714,7 @@ export class ViewBase extends Observable implements ViewBaseDefinition {
if (oldParent) { if (oldParent) {
clearInheritedProperties(this); clearInheritedProperties(this);
if (this.bindingContextBoundToParentBindingContextChanged) { if (this.bindingContextBoundToParentBindingContextChanged) {
oldParent.parent.off("bindingContextChange", this.bindingContextChanged, this); oldParent.off("bindingContextChange", this.bindingContextChanged, this);
} }
} else if (this.shouldAddHandlerToParentBindingContextChanged) { } else if (this.shouldAddHandlerToParentBindingContextChanged) {
const parent = this.parent; const parent = this.parent;

View File

@ -39,11 +39,11 @@ function initializeEditTextListeners(): void {
} }
public onTextChanged(text: string, start: number, before: number, count: number) { public onTextChanged(text: string, start: number, before: number, count: number) {
const owner = this.owner; // const owner = this.owner;
let selectionStart = owner.android.getSelectionStart(); // let selectionStart = owner.android.getSelectionStart();
owner.android.removeTextChangedListener(owner._editTextListeners); // owner.android.removeTextChangedListener(owner._editTextListeners);
owner.android.addTextChangedListener(owner._editTextListeners); // owner.android.addTextChangedListener(owner._editTextListeners);
owner.android.setSelection(selectionStart); // owner.android.setSelection(selectionStart);
} }
public afterTextChanged(editable: android.text.IEditable) { public afterTextChanged(editable: android.text.IEditable) {

View File

@ -708,13 +708,18 @@ class FragmentCallbacksImplementation implements AndroidFragmentCallbacks {
} }
const entry = this.entry; const entry = this.entry;
const page = entry.resolvedPage; const page = entry.resolvedPage;
try {
if (savedInstanceState && savedInstanceState.getBoolean(HIDDEN, false)) { if (savedInstanceState && savedInstanceState.getBoolean(HIDDEN, false)) {
fragment.getFragmentManager().beginTransaction().hide(fragment).commit(); fragment.getFragmentManager().beginTransaction().hide(fragment).commit();
this.frame._addView(page); this.frame._addView(page);
} } else {
else {
onFragmentShown(fragment); onFragmentShown(fragment);
} }
} catch (ex) {
const label = new android.widget.TextView(container.getContext());
label.setText(ex.message + ", " + ex.stackTrace);
return label;
}
return page._nativeView; return page._nativeView;
} }

View File

@ -1,6 +1,10 @@
// Definitions.
import { TextBase as TextBaseDefinition } from "."; import { TextBase as TextBaseDefinition } from ".";
import { View, ViewBase, Property, CssProperty, InheritedCssProperty, Style, isIOS, Observable, makeValidator, makeParser, Length } from "../core/view"; import { FontWeight } from "../styling/font";
import { PropertyChangeData } from "../../data/observable"; import { PropertyChangeData } from "../../data/observable";
// Types.
import { View, ViewBase, Property, CssProperty, InheritedCssProperty, Style, isIOS, Observable, makeValidator, makeParser, Length } from "../core/view";
import { FormattedString, Span } from "../../text/formatted-string"; import { FormattedString, Span } from "../../text/formatted-string";
export { FormattedString, Span }; export { FormattedString, Span };
@ -12,8 +16,6 @@ const CHILD_FORMATTED_STRING = "FormattedString";
export abstract class TextBaseCommon extends View implements TextBaseDefinition { export abstract class TextBaseCommon extends View implements TextBaseDefinition {
// public abstract _setFormattedTextPropertyToNative(value: FormattedString): void;
public text: string; public text: string;
public formattedText: FormattedString; public formattedText: FormattedString;
@ -132,11 +134,13 @@ export abstract class TextBaseCommon extends View implements TextBaseDefinition
} }
} }
//Text export function isBold(fontWeight: FontWeight): boolean {
return fontWeight === "bold" || fontWeight === "700" || fontWeight === "800" || fontWeight === "900";
}
export const textProperty = new Property<TextBaseCommon, string>({ name: "text", defaultValue: "" }); export const textProperty = new Property<TextBaseCommon, string>({ name: "text", defaultValue: "" });
textProperty.register(TextBaseCommon); textProperty.register(TextBaseCommon);
//FormattedText
export const formattedTextProperty = new Property<TextBaseCommon, FormattedString>({ export const formattedTextProperty = new Property<TextBaseCommon, FormattedString>({
name: "formattedText", name: "formattedText",
affectsLayout: isIOS, affectsLayout: isIOS,
@ -156,7 +160,6 @@ function onFormattedTextPropertyChanged(textBase: TextBaseCommon, oldValue: Form
} }
} }
//TextAlignment
export type TextAlignment = "left" | "center" | "right"; export type TextAlignment = "left" | "center" | "right";
export namespace TextAlignment { export namespace TextAlignment {
export const LEFT: "left" = "left"; export const LEFT: "left" = "left";
@ -173,7 +176,6 @@ export const textAlignmentProperty = new InheritedCssProperty<Style, TextAlignme
}); });
textAlignmentProperty.register(Style); textAlignmentProperty.register(Style);
//TextDecoration
export type TextDecoration = "none" | "underline" | "line-through" | "underline line-through"; export type TextDecoration = "none" | "underline" | "line-through" | "underline line-through";
export namespace TextDecoration { export namespace TextDecoration {
export const NONE: "none" = "none"; export const NONE: "none" = "none";
@ -184,15 +186,15 @@ export namespace TextDecoration {
export const isValid = makeValidator<TextDecoration>(NONE, UNDERLINE, LINE_THROUGH, UNDERLINE_LINE_THROUGH); export const isValid = makeValidator<TextDecoration>(NONE, UNDERLINE, LINE_THROUGH, UNDERLINE_LINE_THROUGH);
export const parse = makeParser<TextDecoration>(isValid); export const parse = makeParser<TextDecoration>(isValid);
} }
export const textDecorationProperty = new CssProperty<Style, TextDecoration>({ export const textDecorationProperty = new CssProperty<Style, TextDecoration>({
name: "textDecoration", name: "textDecoration",
cssName: "text-decoration", cssName: "text-decoration",
defaultValue: TextDecoration.NONE, defaultValue: "none",
valueConverter: TextDecoration.parse valueConverter: TextDecoration.parse
}); });
textDecorationProperty.register(Style); textDecorationProperty.register(Style);
//TextTransform
export type TextTransform = "none" | "capitalize" | "uppercase" | "lowercase"; export type TextTransform = "none" | "capitalize" | "uppercase" | "lowercase";
export namespace TextTransform { export namespace TextTransform {
export const NONE: "none" = "none"; export const NONE: "none" = "none";
@ -202,15 +204,15 @@ export namespace TextTransform {
export const isValid = makeValidator<TextTransform>(NONE, CAPITALIZE, UPPERCASE, LOWERCASE); export const isValid = makeValidator<TextTransform>(NONE, CAPITALIZE, UPPERCASE, LOWERCASE);
export const parse = makeParser<TextTransform>(isValid); export const parse = makeParser<TextTransform>(isValid);
} }
export const textTransformProperty = new CssProperty<Style, TextTransform>({ export const textTransformProperty = new CssProperty<Style, TextTransform>({
name: "textTransform", name: "textTransform",
cssName: "text-transform", cssName: "text-transform",
defaultValue: TextTransform.NONE, defaultValue: "none",
valueConverter: TextTransform.parse valueConverter: TextTransform.parse
}); });
textTransformProperty.register(Style); textTransformProperty.register(Style);
//Whitespace
export type WhiteSpace = "normal" | "nowrap"; export type WhiteSpace = "normal" | "nowrap";
export namespace WhiteSpace { export namespace WhiteSpace {
export const NORMAL: "normal" = "normal"; export const NORMAL: "normal" = "normal";
@ -219,11 +221,9 @@ export namespace WhiteSpace {
export const parse = makeParser<WhiteSpace>(isValid); export const parse = makeParser<WhiteSpace>(isValid);
} }
//NB: Default value is deferent for Android and IOS
export const whiteSpaceProperty = new CssProperty<Style, WhiteSpace>({ export const whiteSpaceProperty = new CssProperty<Style, WhiteSpace>({
name: "whiteSpace", name: "whiteSpace",
cssName: "white-space", cssName: "white-space",
defaultValue: isIOS ? WhiteSpace.NO_WRAP : WhiteSpace.NORMAL,
valueConverter: WhiteSpace.parse valueConverter: WhiteSpace.parse
}); });
whiteSpaceProperty.register(Style); whiteSpaceProperty.register(Style);
@ -233,6 +233,6 @@ export const letterSpacingProperty = new CssProperty<Style, number>({
cssName: "letter-spacing", cssName: "letter-spacing",
defaultValue: 0, defaultValue: 0,
affectsLayout: isIOS, affectsLayout: isIOS,
valueConverter: (v: string) => parseFloat(v) valueConverter: v => parseFloat(v)
}); });
letterSpacingProperty.register(Style); letterSpacingProperty.register(Style);

View File

@ -1,15 +1,13 @@
import { Font, FontWeight, FontStyle } from "../styling/font"; import { Font } from "../styling/font";
import { backgroundColorProperty } from "../styling/style-properties";
import { import {
TextBaseCommon, formattedTextProperty, textAlignmentProperty, textDecorationProperty, fontSizeProperty, TextBaseCommon, formattedTextProperty, textAlignmentProperty, textDecorationProperty, fontSizeProperty,
textProperty, textTransformProperty, letterSpacingProperty, colorProperty, fontInternalProperty, textProperty, textTransformProperty, letterSpacingProperty, colorProperty, fontInternalProperty,
whiteSpaceProperty, FormattedString, TextDecoration, TextAlignment, TextTransform, WhiteSpace,
paddingLeftProperty, paddingTopProperty, paddingRightProperty, paddingBottomProperty, Length, paddingLeftProperty, paddingTopProperty, paddingRightProperty, paddingBottomProperty, Length,
layout, Span, Color whiteSpaceProperty, FormattedString, TextDecoration, TextAlignment, TextTransform, WhiteSpace,
layout, Span, Color, isBold
} from "./text-base-common"; } from "./text-base-common";
import { _isSet as isSet } from "../core/properties";
export * from "./text-base-common"; export * from "./text-base-common";
interface TextTransformation { interface TextTransformation {
@ -24,34 +22,14 @@ function initializeTextTransformation(): void {
} }
@Interfaces([android.text.method.TransformationMethod]) @Interfaces([android.text.method.TransformationMethod])
class TextTransformationImpl extends android.text.method.ReplacementTransformationMethod implements android.text.method.TransformationMethod { class TextTransformationImpl extends java.lang.Object implements android.text.method.TransformationMethod {
constructor(public textBase: TextBase) { constructor(public textBase: TextBase) {
super(); super();
return global.__native(this); return global.__native(this);
} }
protected getOriginal(): native.Array<string> {
return convertStringToNativeCharArray(this.textBase.formattedText ? this.textBase.formattedText.toString() : this.textBase.text);
}
protected getReplacement(): native.Array<string> {
let replacementString: string = "";
const formattedText = this.textBase.formattedText;
const textTransform = this.textBase.textTransform;
if (formattedText) {
for (let i = 0, length = formattedText.spans.length; i < length; i++) {
let span = formattedText.spans.getItem(i);
replacementString += getTransformedText(span.text, textTransform);
}
}
else {
replacementString = getTransformedText(this.textBase.text, textTransform);
}
return convertStringToNativeCharArray(replacementString);
}
public getTransformation(charSeq: any, view: android.view.View): any { public getTransformation(charSeq: any, view: android.view.View): any {
// NOTE: Do we need to transform the new text here?
const formattedText = this.textBase.formattedText; const formattedText = this.textBase.formattedText;
if (formattedText) { if (formattedText) {
return createSpannableStringBuilder(formattedText); return createSpannableStringBuilder(formattedText);
@ -60,173 +38,210 @@ function initializeTextTransformation(): void {
return getTransformedText(this.textBase.text, this.textBase.textTransform); return getTransformedText(this.textBase.text, this.textBase.textTransform);
} }
} }
public onFocusChanged(view: android.view.View, sourceText: string, focused: boolean, direction: number, previouslyFocusedRect: android.graphics.Rect): void {
// Do nothing for now.
}
} }
TextTransformation = TextTransformationImpl; TextTransformation = TextTransformationImpl;
} }
export class TextBase extends TextBaseCommon { export class TextBase extends TextBaseCommon {
_nativeView: android.widget.TextView; nativeView: android.widget.TextView;
_defaultTransformationMethod: android.text.method.TransformationMethod;
public _initNativeView(): void {
this._defaultTransformationMethod = this.nativeView.getTransformationMethod();
super._initNativeView();
}
public _resetNativeView(): void {
super._resetNativeView();
// We reset it here too because this could be changed by multiple properties - whiteSpace, secure, textTransform
this.nativeView.setTransformationMethod(this._defaultTransformationMethod);
}
//Text
get [textProperty.native](): string { get [textProperty.native](): string {
return this._nativeView.getText(); return '';
} }
set [textProperty.native](value: string) { set [textProperty.native](value: string) {
const text = (value === null || value === undefined) ? '' : value.toString(); if (this.formattedText) {
this._nativeView.setText(text); return;
}
this._setNativeText();
} }
//FormattedText
get [formattedTextProperty.native](): FormattedString { get [formattedTextProperty.native](): FormattedString {
return null; return null;
} }
set [formattedTextProperty.native](value: FormattedString) { set [formattedTextProperty.native](value: FormattedString) {
// Don't change the transformation method if this is secure TextField or we'll lose the hiding characters.
if ((<any>this).secure) {
return;
}
initializeTextTransformation(); initializeTextTransformation();
let spannableStringBuilder = createSpannableStringBuilder(value); const spannableStringBuilder = createSpannableStringBuilder(value);
this._nativeView.setText(<any>spannableStringBuilder); this.nativeView.setText(<any>spannableStringBuilder);
textProperty.nativeValueChange(this, (value === null || value === undefined) ? '' : value.toString()); textProperty.nativeValueChange(this, (value === null || value === undefined) ? '' : value.toString());
if (spannableStringBuilder && this._nativeView instanceof android.widget.Button && if (spannableStringBuilder && this.nativeView instanceof android.widget.Button &&
!(this._nativeView.getTransformationMethod() instanceof TextTransformation)) { !(this.nativeView.getTransformationMethod() instanceof TextTransformation)) {
// Replace Android Button's default transformation (in case the developer has not already specified a text-transform) method // Replace Android Button's default transformation (in case the developer has not already specified a text-transform) method
// with our transformation method which can handle formatted text. // with our transformation method which can handle formatted text.
// Otherwise, the default tranformation method of the Android Button will overwrite and ignore our spannableStringBuilder. // Otherwise, the default tranformation method of the Android Button will overwrite and ignore our spannableStringBuilder.
// We can't set it to NONE since it is the default value. Set it to something else first. this.nativeView.setTransformationMethod(new TextTransformation(this));
this.style[textTransformProperty.cssName] = TextTransform.UPPERCASE;
this.style[textTransformProperty.cssName] = TextTransform.NONE;
} }
} }
//TextTransform get [textTransformProperty.native](): string {
get [textTransformProperty.native](): android.text.method.TransformationMethod { return "default";
return this._nativeView.getTransformationMethod();
} }
set [textTransformProperty.native](value: TextTransform | android.text.method.TransformationMethod) { set [textTransformProperty.native](value: TextTransform | android.text.method.TransformationMethod) {
// In case of reset.
if (value === "default") {
this.nativeView.setTransformationMethod(this._defaultTransformationMethod);
return;
}
// Don't change the transformation method if this is secure TextField or we'll lose the hiding characters.
if ((<any>this).secure) {
return;
}
initializeTextTransformation(); initializeTextTransformation();
if (typeof value === "string") { if (typeof value === "string") {
this._nativeView.setTransformationMethod(new TextTransformation(this)); this.nativeView.setTransformationMethod(new TextTransformation(this));
} else { } else {
this._nativeView.setTransformationMethod(value); this.nativeView.setTransformationMethod(value);
} }
} }
//Color
get [colorProperty.native](): android.content.res.ColorStateList { get [colorProperty.native](): android.content.res.ColorStateList {
return this._nativeView.getTextColors(); return this.nativeView.getTextColors();
} }
set [colorProperty.native](value: Color | android.content.res.ColorStateList) { set [colorProperty.native](value: Color | android.content.res.ColorStateList) {
if (!this.formattedText || !(value instanceof Color)) {
if (value instanceof Color) { if (value instanceof Color) {
this._nativeView.setTextColor(value.android); this.nativeView.setTextColor(value.android);
} else { } else {
this._nativeView.setTextColor(value); this.nativeView.setTextColor(value);
}
} }
} }
//FontSize
get [fontSizeProperty.native](): { nativeSize: number } { get [fontSizeProperty.native](): { nativeSize: number } {
return { nativeSize: this._nativeView.getTextSize() }; return { nativeSize: this.nativeView.getTextSize() };
} }
set [fontSizeProperty.native](value: number | { nativeSize: number }) { set [fontSizeProperty.native](value: number | { nativeSize: number }) {
if (!this.formattedText || (typeof value !== "number")) {
if (typeof value === "number") { if (typeof value === "number") {
this._nativeView.setTextSize(value); this.nativeView.setTextSize(value);
} else { } else {
this._nativeView.setTextSize(android.util.TypedValue.COMPLEX_UNIT_PX, value.nativeSize); this.nativeView.setTextSize(android.util.TypedValue.COMPLEX_UNIT_PX, value.nativeSize);
}
} }
} }
//FontInternal
get [fontInternalProperty.native](): android.graphics.Typeface { get [fontInternalProperty.native](): android.graphics.Typeface {
return this._nativeView.getTypeface(); return this.nativeView.getTypeface();
} }
set [fontInternalProperty.native](value: Font | android.graphics.Typeface) { set [fontInternalProperty.native](value: Font | android.graphics.Typeface) {
this._nativeView.setTypeface(value instanceof Font ? value.getAndroidTypeface() : value); if (!this.formattedText || !(value instanceof Font)) {
this.nativeView.setTypeface(value instanceof Font ? value.getAndroidTypeface() : value);
}
} }
//TextAlignment
get [textAlignmentProperty.native](): TextAlignment { get [textAlignmentProperty.native](): TextAlignment {
let textAlignmentGravity = this._nativeView.getGravity() & android.view.Gravity.HORIZONTAL_GRAVITY_MASK; let textAlignmentGravity = this.nativeView.getGravity() & android.view.Gravity.HORIZONTAL_GRAVITY_MASK;
switch (textAlignmentGravity) { switch (textAlignmentGravity) {
case android.view.Gravity.LEFT:
return TextAlignment.LEFT;
case android.view.Gravity.CENTER_HORIZONTAL: case android.view.Gravity.CENTER_HORIZONTAL:
return TextAlignment.CENTER; return "center";
case android.view.Gravity.RIGHT: case android.view.Gravity.RIGHT:
return TextAlignment.RIGHT; return "right";
case android.view.Gravity.LEFT:
default: default:
return TextAlignment.LEFT; return "left";
} }
} }
set [textAlignmentProperty.native](value: TextAlignment) { set [textAlignmentProperty.native](value: TextAlignment) {
let verticalGravity = this._nativeView.getGravity() & android.view.Gravity.VERTICAL_GRAVITY_MASK; let verticalGravity = this.nativeView.getGravity() & android.view.Gravity.VERTICAL_GRAVITY_MASK;
switch (value) { switch (value) {
case TextAlignment.LEFT: case "left":
this._nativeView.setGravity(android.view.Gravity.LEFT | verticalGravity); this.nativeView.setGravity(android.view.Gravity.LEFT | verticalGravity);
break; break;
case TextAlignment.CENTER: case "center":
this._nativeView.setGravity(android.view.Gravity.CENTER_HORIZONTAL | verticalGravity); this.nativeView.setGravity(android.view.Gravity.CENTER_HORIZONTAL | verticalGravity);
break; break;
case TextAlignment.RIGHT: case "right":
this._nativeView.setGravity(android.view.Gravity.RIGHT | verticalGravity); this.nativeView.setGravity(android.view.Gravity.RIGHT | verticalGravity);
break; break;
default: default:
throw new Error(`Invalid text alignment value: ${value}. Valid values are: "${TextAlignment.LEFT}", "${TextAlignment.CENTER}", "${TextAlignment.RIGHT}".`); throw new Error(`Invalid text alignment value: ${value}. Valid values are: 'left', 'center', 'right'.`);
} }
} }
//TextDecoration get [textDecorationProperty.native](): number {
get [textDecorationProperty.native](): TextDecoration { return -1;
return TextDecoration.NONE;
} }
set [textDecorationProperty.native](value: TextDecoration) { set [textDecorationProperty.native](value: TextDecoration | number) {
const isReset = typeof value === "number";
if (!this.formattedText || isReset) {
value = isReset ? "none" : value;
let flags: number; let flags: number;
switch (value) { switch (value) {
case TextDecoration.NONE: case "none":
flags = 0; flags = 0;
break; break;
case TextDecoration.UNDERLINE: case "underline":
flags = android.graphics.Paint.UNDERLINE_TEXT_FLAG; flags = android.graphics.Paint.UNDERLINE_TEXT_FLAG;
break; break;
case TextDecoration.LINE_THROUGH: case "line-through":
flags = android.graphics.Paint.STRIKE_THRU_TEXT_FLAG; flags = android.graphics.Paint.STRIKE_THRU_TEXT_FLAG;
break; break;
case TextDecoration.UNDERLINE_LINE_THROUGH: case "underline line-through":
flags = android.graphics.Paint.UNDERLINE_TEXT_FLAG | android.graphics.Paint.STRIKE_THRU_TEXT_FLAG; flags = android.graphics.Paint.UNDERLINE_TEXT_FLAG | android.graphics.Paint.STRIKE_THRU_TEXT_FLAG;
break; break;
default: default:
throw new Error(`Invalid text decoration value: ${value}. Valid values are: "${TextDecoration.NONE}", "${TextDecoration.UNDERLINE}", "${TextDecoration.LINE_THROUGH}", "${TextDecoration.UNDERLINE_LINE_THROUGH}".`); throw new Error(`Invalid text decoration value: ${value}. Valid values are: 'none', 'underline', 'line-through', 'underline line-through'.`);
} }
this._nativeView.setPaintFlags(flags); this.nativeView.setPaintFlags(flags);
} else {
this._setNativeText();
}
} }
//WhiteSpace // Overriden in TextField becasue setSingleLine(false) will remove methodTransformation.
// and we don't want to allow TextField to be multiline
get [whiteSpaceProperty.native](): WhiteSpace { get [whiteSpaceProperty.native](): WhiteSpace {
return WhiteSpace.NORMAL; return "normal";
} }
set [whiteSpaceProperty.native](value: WhiteSpace) { set [whiteSpaceProperty.native](value: WhiteSpace) {
let nativeView = this._nativeView; const nativeView = this.nativeView;
switch (value) { switch (value) {
case WhiteSpace.NORMAL: case "normal":
nativeView.setSingleLine(false); nativeView.setSingleLine(false);
nativeView.setEllipsize(null); nativeView.setEllipsize(null);
break; break;
case WhiteSpace.NO_WRAP: case "nowrap":
nativeView.setSingleLine(true); nativeView.setSingleLine(true);
nativeView.setEllipsize(android.text.TextUtils.TruncateAt.END); nativeView.setEllipsize(android.text.TextUtils.TruncateAt.END);
break; break;
default: default:
throw new Error(`Invalid whitespace value: ${value}. Valid values are: "${WhiteSpace.NORMAL}", "${WhiteSpace.NO_WRAP}".`); throw new Error(`Invalid whitespace value: ${value}. Valid values are: 'normal', nowrap'.`);
} }
} }
get [letterSpacingProperty.native](): number { get [letterSpacingProperty.native](): number {
return org.nativescript.widgets.ViewHelper.getLetterspacing(this._nativeView); return org.nativescript.widgets.ViewHelper.getLetterspacing(this.nativeView);
} }
set [letterSpacingProperty.native](value: number) { set [letterSpacingProperty.native](value: number) {
org.nativescript.widgets.ViewHelper.setLetterspacing(this._nativeView, value); org.nativescript.widgets.ViewHelper.setLetterspacing(this.nativeView, value);
} }
get [paddingTopProperty.native](): Length { get [paddingTopProperty.native](): Length {
@ -256,6 +271,19 @@ export class TextBase extends TextBaseCommon {
set [paddingLeftProperty.native](value: Length) { set [paddingLeftProperty.native](value: Length) {
org.nativescript.widgets.ViewHelper.setPaddingLeft(this.nativeView, Length.toDevicePixels(value, 0) + Length.toDevicePixels(this.style.borderLeftWidth, 0)); org.nativescript.widgets.ViewHelper.setPaddingLeft(this.nativeView, Length.toDevicePixels(value, 0) + Length.toDevicePixels(this.style.borderLeftWidth, 0));
} }
_setNativeText() {
let transformedText: any;
if (this.formattedText) {
transformedText = createSpannableStringBuilder(this.formattedText);
} else {
const text = this.text;
const stringValue = (text === null || text === undefined) ? '' : text.toString();
transformedText = getTransformedText(stringValue, this.textTransform);
}
this.nativeView.setText(<any>transformedText);
}
} }
function getCapitalizedString(str: string): string { function getCapitalizedString(str: string): string {
@ -269,67 +297,51 @@ function getCapitalizedString(str: string): string {
return newWords.join(" "); return newWords.join(" ");
} }
function getTransformedText(text: string, textTransform: TextTransform): string { export function getTransformedText(text: string, textTransform: TextTransform): string {
switch (textTransform) { switch (textTransform) {
case TextTransform.NONE: case "none":
return text; return text;
case TextTransform.UPPERCASE: case "uppercase":
return text.toUpperCase(); return text.toUpperCase();
case TextTransform.LOWERCASE: case "lowercase":
return text.toLowerCase(); return text.toLowerCase();
case TextTransform.CAPITALIZE: case "capitalize":
return getCapitalizedString(text); return getCapitalizedString(text);
default: default:
throw new Error(`Invalid text transform value: ${textTransform}. Valid values are: "${TextTransform.NONE}", "${TextTransform.CAPITALIZE}", "${TextTransform.UPPERCASE}", "${TextTransform.LOWERCASE}".`); throw new Error(`Invalid text transform value: ${textTransform}. Valid values are: 'none', 'capitalize', 'uppercase', 'lowercase'.`);
} }
} }
function createSpannableStringBuilder(formattedString: FormattedString): android.text.SpannableStringBuilder { function createSpannableStringBuilder(formattedString: FormattedString): android.text.SpannableStringBuilder {
let ssb = new android.text.SpannableStringBuilder(); if (!formattedString) {
return null;
if (formattedString === null || formattedString === undefined) {
return ssb;
} }
const ssb = new android.text.SpannableStringBuilder();
for (let i = 0, spanStart = 0, spanLength = 0, length = formattedString.spans.length; i < length; i++) { for (let i = 0, spanStart = 0, spanLength = 0, length = formattedString.spans.length; i < length; i++) {
const span = formattedString.spans.getItem(i); const span = formattedString.spans.getItem(i);
const text = span.text; const text = span.text;
const textTransform = (<TextBase>formattedString.parent).textTransform; const textTransform = (<TextBase>formattedString.parent).textTransform;
let spanText = (text === null || text === undefined) ? '' : text.toString(); let spanText = (text === null || text === undefined) ? '' : text.toString();
if (textTransform) { if (textTransform !== "none") {
spanText = getTransformedText(spanText, textTransform); spanText = getTransformedText(spanText, textTransform);
} }
spanLength = spanText.length; spanLength = spanText.length;
if (spanLength !== 0) { if (spanLength > 0) {
ssb.insert(spanStart, spanText); ssb.insert(spanStart, spanText);
setSpanModifiers(ssb, span, spanStart, spanStart + spanLength); setSpanModifiers(ssb, span, spanStart, spanStart + spanLength);
spanStart += spanLength; spanStart += spanLength;
} }
} }
return ssb; return ssb;
} }
function convertStringToNativeCharArray(value: string): native.Array<string> {
let nativeCharArray: native.Array<string> = [];
for (let i = 0, length = value.length; i < length; i++) {
nativeCharArray[i] = value.charAt(i);
}
return nativeCharArray;
}
function isBold(fontWeight: FontWeight): boolean {
return fontWeight === FontWeight.BOLD
|| fontWeight === "700"
|| fontWeight === FontWeight.EXTRA_BOLD
|| fontWeight === FontWeight.BLACK;
}
function setSpanModifiers(ssb: android.text.SpannableStringBuilder, span: Span, start: number, end: number): void { function setSpanModifiers(ssb: android.text.SpannableStringBuilder, span: Span, start: number, end: number): void {
const style = span.style; const spanStyle = span.style;
const bold = isBold(style.fontWeight); const bold = isBold(spanStyle.fontWeight);
const italic = style.fontStyle === FontStyle.ITALIC; const italic = spanStyle.fontStyle === "italic";
if (bold && italic) { if (bold && italic) {
ssb.setSpan(new android.text.style.StyleSpan(android.graphics.Typeface.BOLD_ITALIC), start, end, android.text.Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); ssb.setSpan(new android.text.style.StyleSpan(android.graphics.Typeface.BOLD_ITALIC), start, end, android.text.Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
@ -358,33 +370,48 @@ function setSpanModifiers(ssb: android.text.SpannableStringBuilder, span: Span,
ssb.setSpan(new android.text.style.ForegroundColorSpan(color.android), start, end, android.text.Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); ssb.setSpan(new android.text.style.ForegroundColorSpan(color.android), start, end, android.text.Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
} }
// We don't use isSet function here because defaultValue for backgroundColor is null. let backgroundColor: Color;
const backgroundColor = style.backgroundColor || (<FormattedString>span.parent).backgroundColor || (<TextBase>(<FormattedString>span.parent).parent).backgroundColor; if (backgroundColorProperty.isSet(spanStyle)) {
backgroundColor = spanStyle.backgroundColor;
} else if (backgroundColorProperty.isSet(span.parent.style)) {
// parent is FormattedString
backgroundColor = span.parent.style.backgroundColor;
} else if (backgroundColorProperty.isSet(span.parent.parent.style)) {
// parent.parent is TextBase
backgroundColor = span.parent.parent.style.backgroundColor;
}
if (backgroundColor) { if (backgroundColor) {
ssb.setSpan(new android.text.style.BackgroundColorSpan(backgroundColor.android), start, end, android.text.Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); ssb.setSpan(new android.text.style.BackgroundColorSpan(backgroundColor.android), start, end, android.text.Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
} }
let valueSource: typeof style; let valueSource: typeof spanStyle;
if (isSet(textDecorationProperty, style)) { if (textDecorationProperty.isSet(spanStyle)) {
valueSource = style; valueSource = spanStyle;
} else if (isSet(textDecorationProperty, span.parent.style)) { } else if (textDecorationProperty.isSet(span.parent.style)) {
// span.parent is FormattedString // span.parent is FormattedString
valueSource = span.parent.style; valueSource = span.parent.style;
} else if (isSet(textDecorationProperty, span.parent.parent.style)) { } else if (textDecorationProperty.isSet(span.parent.parent.style)) {
// span.parent.parent is TextBase // span.parent.parent is TextBase
valueSource = span.parent.parent.style; valueSource = span.parent.parent.style;
} }
if (valueSource) { if (valueSource) {
const textDecorations = valueSource.textDecoration; const textDecorations = valueSource.textDecoration;
const underline = textDecorations.indexOf(TextDecoration.UNDERLINE) !== -1; const underline = textDecorations.indexOf('underline') !== -1;
if (underline) { if (underline) {
ssb.setSpan(new android.text.style.UnderlineSpan(), start, end, android.text.Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); ssb.setSpan(new android.text.style.UnderlineSpan(), start, end, android.text.Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
} }
const strikethrough = textDecorations.indexOf(TextDecoration.LINE_THROUGH) !== -1; const strikethrough = textDecorations.indexOf('line-through') !== -1;
if (strikethrough) { if (strikethrough) {
ssb.setSpan(new android.text.style.StrikethroughSpan(), start, end, android.text.Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); ssb.setSpan(new android.text.style.StrikethroughSpan(), start, end, android.text.Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
} }
} }
// TODO: Implement letterSpacing for Span here.
// const letterSpacing = formattedString.parent.style.letterSpacing;
// if (letterSpacing > 0) {
// ssb.setSpan(new android.text.style.ScaleXSpan((letterSpacing + 1) / 10), start, end, android.text.Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
// }
} }

View File

@ -1,19 +1,16 @@
import { Font, FontWeight, FontStyle } from "../styling/font"; import { Font } from "../styling/font";
import { import {
TextBaseCommon, textProperty, formattedTextProperty, textAlignmentProperty, textDecorationProperty, TextBaseCommon, textProperty, formattedTextProperty, textAlignmentProperty, textDecorationProperty,
textTransformProperty, letterSpacingProperty, colorProperty, fontInternalProperty, FormattedString, textTransformProperty, letterSpacingProperty, colorProperty, fontInternalProperty, FormattedString,
TextDecoration, TextAlignment, TextTransform, Span, Color TextDecoration, TextAlignment, TextTransform, Span, Color, isBold
} from "./text-base-common"; } from "./text-base-common";
import { _isSet as isSet } from "../core/properties";
export * from "./text-base-common"; export * from "./text-base-common";
export class TextBase extends TextBaseCommon { export class TextBase extends TextBaseCommon {
public nativeView: UITextField | UITextView | UILabel | UIButton; public nativeView: UITextField | UITextView | UILabel | UIButton;
//Text
get [textProperty.native](): string { get [textProperty.native](): string {
return ''; return '';
} }
@ -26,7 +23,6 @@ export class TextBase extends TextBaseCommon {
this._requestLayoutOnTextChanged(); this._requestLayoutOnTextChanged();
} }
//FormattedText
get [formattedTextProperty.native](): FormattedString { get [formattedTextProperty.native](): FormattedString {
return null; return null;
} }
@ -36,7 +32,6 @@ export class TextBase extends TextBaseCommon {
this._requestLayoutOnTextChanged(); this._requestLayoutOnTextChanged();
} }
//Color
get [colorProperty.native](): UIColor { get [colorProperty.native](): UIColor {
let nativeView = this.nativeView; let nativeView = this.nativeView;
if (nativeView instanceof UIButton) { if (nativeView instanceof UIButton) {
@ -57,7 +52,6 @@ export class TextBase extends TextBaseCommon {
} }
} }
//FontInternal
get [fontInternalProperty.native](): UIFont { get [fontInternalProperty.native](): UIFont {
let nativeView = this.nativeView; let nativeView = this.nativeView;
nativeView = nativeView instanceof UIButton ? nativeView.titleLabel : nativeView; nativeView = nativeView instanceof UIButton ? nativeView.titleLabel : nativeView;
@ -72,7 +66,6 @@ export class TextBase extends TextBaseCommon {
} }
} }
//TextAlignment
get [textAlignmentProperty.native](): TextAlignment { get [textAlignmentProperty.native](): TextAlignment {
let nativeView = this.nativeView; let nativeView = this.nativeView;
nativeView = nativeView instanceof UIButton ? nativeView.titleLabel : nativeView; nativeView = nativeView instanceof UIButton ? nativeView.titleLabel : nativeView;
@ -104,27 +97,24 @@ export class TextBase extends TextBaseCommon {
break; break;
default: default:
throw new Error(`Invalid text alignment value: ${value}. Valid values are: "${TextAlignment.LEFT}", "${TextAlignment.CENTER}", "${TextAlignment.RIGHT}".`); throw new Error(`Invalid text alignment value: ${value}. Valid values are: 'left', 'center', 'right'.`);
} }
} }
//TextDecoration
get [textDecorationProperty.native](): TextDecoration { get [textDecorationProperty.native](): TextDecoration {
return TextDecoration.NONE; return "none";
} }
set [textDecorationProperty.native](value: TextDecoration) { set [textDecorationProperty.native](value: TextDecoration) {
this._setNativeText(); this._setNativeText();
} }
//TextTransform
get [textTransformProperty.native](): TextTransform { get [textTransformProperty.native](): TextTransform {
return TextTransform.NONE; return "none";
} }
set [textTransformProperty.native](value: TextTransform) { set [textTransformProperty.native](value: TextTransform) {
this._setNativeText(); this._setNativeText();
} }
// LetterSpacing.
get [letterSpacingProperty.native](): number { get [letterSpacingProperty.native](): number {
return 0; return 0;
} }
@ -142,6 +132,7 @@ export class TextBase extends TextBaseCommon {
setFormattedTextDecorationAndTransform() { setFormattedTextDecorationAndTransform() {
const attrText = this.createNSMutableAttributedString(this.formattedText); const attrText = this.createNSMutableAttributedString(this.formattedText);
// TODO: letterSpacing should be applied per Span.
if (this.letterSpacing !== 0) { if (this.letterSpacing !== 0) {
attrText.addAttributeValueRange(NSKernAttributeName, this.letterSpacing * this.nativeView.font.pointSize, { location: 0, length: attrText.length }); attrText.addAttributeValueRange(NSKernAttributeName, this.letterSpacing * this.nativeView.font.pointSize, { location: 0, length: attrText.length });
} }
@ -159,20 +150,20 @@ export class TextBase extends TextBaseCommon {
let dict = new Map<string, any>(); let dict = new Map<string, any>();
switch (style.textDecoration) { switch (style.textDecoration) {
case TextDecoration.NONE: case "none":
break; break;
case TextDecoration.UNDERLINE: case "underline":
dict.set(NSUnderlineStyleAttributeName, NSUnderlineStyle.StyleSingle); dict.set(NSUnderlineStyleAttributeName, NSUnderlineStyle.StyleSingle);
break; break;
case TextDecoration.LINE_THROUGH: case "line-through":
dict.set(NSStrikethroughStyleAttributeName, NSUnderlineStyle.StyleSingle); dict.set(NSStrikethroughStyleAttributeName, NSUnderlineStyle.StyleSingle);
break; break;
case TextDecoration.UNDERLINE_LINE_THROUGH: case "underline line-through":
dict.set(NSUnderlineStyleAttributeName, NSUnderlineStyle.StyleSingle); dict.set(NSUnderlineStyleAttributeName, NSUnderlineStyle.StyleSingle);
dict.set(NSStrikethroughStyleAttributeName, NSUnderlineStyle.StyleSingle); dict.set(NSStrikethroughStyleAttributeName, NSUnderlineStyle.StyleSingle);
break; break;
default: default:
throw new Error(`Invalid text decoration value: ${style.textDecoration}. Valid values are: "${TextDecoration.NONE}", "${TextDecoration.UNDERLINE}", "${TextDecoration.LINE_THROUGH}", "${TextDecoration.UNDERLINE_LINE_THROUGH}".`); throw new Error(`Invalid text decoration value: ${style.textDecoration}. Valid values are: 'none', 'underline', 'line-through', 'underline line-through'.`);
} }
if (style.letterSpacing !== 0) { if (style.letterSpacing !== 0) {
@ -237,7 +228,7 @@ export class TextBase extends TextBaseCommon {
let attrDict = <{ key: string, value: any }>{}; let attrDict = <{ key: string, value: any }>{};
const style = span.style; const style = span.style;
const bold = isBold(style.fontWeight); const bold = isBold(style.fontWeight);
const italic = style.fontStyle === FontStyle.ITALIC; const italic = style.fontStyle === "italic";
let fontFamily = span.fontFamily; let fontFamily = span.fontFamily;
let fontSize = span.fontSize; let fontSize = span.fontSize;
@ -290,24 +281,24 @@ export class TextBase extends TextBaseCommon {
} }
let valueSource: typeof style; let valueSource: typeof style;
if (isSet(textDecorationProperty, style)) { if (textDecorationProperty.isSet(style)) {
valueSource = style; valueSource = style;
} else if (isSet(textDecorationProperty, span.parent.style)) { } else if (textDecorationProperty.isSet(span.parent.style)) {
// span.parent is FormattedString // span.parent is FormattedString
valueSource = span.parent.style; valueSource = span.parent.style;
} else if (isSet(textDecorationProperty, span.parent.parent.style)) { } else if (textDecorationProperty.isSet(span.parent.parent.style)) {
// span.parent.parent is TextBase // span.parent.parent is TextBase
valueSource = span.parent.parent.style; valueSource = span.parent.parent.style;
} }
if (valueSource) { if (valueSource) {
const textDecorations = valueSource.textDecoration; const textDecorations = valueSource.textDecoration;
const underline = textDecorations.indexOf(TextDecoration.UNDERLINE) !== -1; const underline = textDecorations.indexOf('underline') !== -1;
if (underline) { if (underline) {
attrDict[NSUnderlineStyleAttributeName] = underline; attrDict[NSUnderlineStyleAttributeName] = underline;
} }
const strikethrough = textDecorations.indexOf(TextDecoration.LINE_THROUGH) !== -1; const strikethrough = textDecorations.indexOf('line-through') !== -1;
if (strikethrough) { if (strikethrough) {
attrDict[NSStrikethroughStyleAttributeName] = strikethrough; attrDict[NSStrikethroughStyleAttributeName] = strikethrough;
} }
@ -319,26 +310,19 @@ export class TextBase extends TextBaseCommon {
export function getTransformedText(text: string, textTransform: TextTransform): string { export function getTransformedText(text: string, textTransform: TextTransform): string {
switch (textTransform) { switch (textTransform) {
case TextTransform.NONE: case "none":
return text; return text;
case TextTransform.UPPERCASE: case "uppercase":
return NSStringFromNSAttributedString(text).uppercaseString; return NSStringFromNSAttributedString(text).uppercaseString;
case TextTransform.LOWERCASE: case "lowercase":
return NSStringFromNSAttributedString(text).lowercaseString; return NSStringFromNSAttributedString(text).lowercaseString;
case TextTransform.CAPITALIZE: case "capitalize":
return NSStringFromNSAttributedString(text).capitalizedString; return NSStringFromNSAttributedString(text).capitalizedString;
default: default:
throw new Error(`Invalid text transform value: ${textTransform}. Valid values are: "${TextTransform.NONE}", "${TextTransform.CAPITALIZE}", "${TextTransform.UPPERCASE}, "${TextTransform.LOWERCASE}".`); throw new Error(`Invalid text transform value: ${textTransform}. Valid values are: 'none', 'capitalize', 'uppercase', 'lowercase'.`);
} }
} }
function NSStringFromNSAttributedString(source: NSAttributedString | string): NSString { function NSStringFromNSAttributedString(source: NSAttributedString | string): NSString {
return NSString.stringWithString(source instanceof NSAttributedString && source.string || <string>source); return NSString.stringWithString(source instanceof NSAttributedString && source.string || <string>source);
} }
function isBold(fontWeight: FontWeight): boolean {
return fontWeight === FontWeight.BOLD
|| fontWeight === "700"
|| fontWeight === FontWeight.EXTRA_BOLD
|| fontWeight === FontWeight.BLACK;
}

View File

@ -1,4 +1,4 @@
import { TextFieldBase, secureProperty } from "./text-field-common"; import { TextFieldBase, secureProperty, whiteSpaceProperty, WhiteSpace } from "./text-field-common";
export * from "./text-field-common"; export * from "./text-field-common";
@ -19,10 +19,10 @@ export class TextField extends TextFieldBase {
return false; return false;
} }
set [secureProperty.native](value: boolean) { set [secureProperty.native](value: boolean) {
let nativeView = this.nativeView; const nativeView = this.nativeView;
let currentInputType = nativeView.getInputType(); const currentInputType = nativeView.getInputType();
let currentClass = currentInputType & android.text.InputType.TYPE_MASK_CLASS; const currentClass = currentInputType & android.text.InputType.TYPE_MASK_CLASS;
let currentFlags = currentInputType & android.text.InputType.TYPE_MASK_FLAGS; const currentFlags = currentInputType & android.text.InputType.TYPE_MASK_FLAGS;
let newInputType = currentInputType; let newInputType = currentInputType;
// Password variations are supported only for Text and Number classes. // Password variations are supported only for Text and Number classes.
@ -48,4 +48,11 @@ export class TextField extends TextFieldBase {
nativeView.setInputType(newInputType); nativeView.setInputType(newInputType);
} }
get [whiteSpaceProperty.native](): WhiteSpace {
return "nowrap";
}
set [whiteSpaceProperty.native](value: WhiteSpace) {
// Don't change it otherwise TextField will go to multiline mode.
}
} }

View File

@ -428,7 +428,10 @@ export function _removePageNativeViewFromAndroidParent(page: Page): void {
if (traceEnabled()) { if (traceEnabled()) {
traceWrite(`REMOVED ${page}._nativeView from its Android parent`, traceCategories.Transition); traceWrite(`REMOVED ${page}._nativeView from its Android parent`, traceCategories.Transition);
} }
if (page._context) {
page._tearDownUI(true); page._tearDownUI(true);
}
androidParent.removeView(page._nativeView); androidParent.removeView(page._nativeView);
} }
} }