mirror of
https://github.com/NativeScript/NativeScript.git
synced 2025-11-05 13:26:48 +08:00
feat: Scoped Packages (#7911)
* chore: move tns-core-modules to nativescript-core * chore: preparing compat generate script * chore: add missing definitions * chore: no need for http-request to be private * chore: packages chore * test: generate tests for tns-core-modules * chore: add anroid module for consistency * chore: add .npmignore * chore: added privateModulesWhitelist * chore(webpack): added bundle-entry-points * chore: scripts * chore: tests changed to use @ns/core * test: add scoped-packages test project * test: fix types * test: update test project * chore: build scripts * chore: update build script * chore: npm scripts cleanup * chore: make the compat pgk work with old wp config * test: generate diff friendly tests * chore: create barrel exports * chore: move files after rebase * chore: typedoc config * chore: compat mode * chore: review of barrels * chore: remove tns-core-modules import after rebase * chore: dev workflow setup * chore: update developer-workflow * docs: experiment with API extractor * chore: api-extractor and barrel exports * chore: api-extractor configs * chore: generate d.ts rollup with api-extractor * refactor: move methods inside Frame * chore: fic tests to use Frame static methods * refactor: create Builder class * refactor: use Builder class in tests * refactor: include Style in ui barrel * chore: separate compat build script * chore: fix tslint errors * chore: update NATIVESCRIPT_CORE_ARGS * chore: fix compat pack * chore: fix ui-test-app build with linked modules * chore: Application, ApplicationSettings, Connectivity and Http * chore: export Trace, Profiling and Utils * refactor: Static create methods for ImageSource * chore: fix deprecated usages of ImageSource * chore: move Span and FormattedString to ui * chore: add events-args and ImageSource to index files * chore: check for CLI >= 6.2 when building for IOS * chore: update travis build * chore: copy Pod file to compat package * chore: update error msg ui-tests-app * refactor: Apply suggestions from code review Co-Authored-By: Martin Yankov <m.i.yankov@gmail.com> * chore: typings and refs * chore: add missing d.ts files for public API * chore: adress code review FB * chore: update api-report * chore: dev-workflow for other apps * chore: api update * chore: update api-report
This commit is contained in:
committed by
GitHub
parent
6c7139477e
commit
cc97a16800
64
nativescript-core/ui/text-base/formatted-string.d.ts
vendored
Normal file
64
nativescript-core/ui/text-base/formatted-string.d.ts
vendored
Normal file
@@ -0,0 +1,64 @@
|
||||
/**
|
||||
* Contains the FormattedString and Span classes, which are used to create a formatted (rich text) strings.
|
||||
* @module "ui/text-base/formatted-string"
|
||||
*/ /** */
|
||||
|
||||
import { Span } from "./span";
|
||||
import { ObservableArray } from "../../data/observable-array";
|
||||
import { ViewBase } from "../core/view";
|
||||
import { Color } from "../../color";
|
||||
import { FontStyle, FontWeight } from "../styling/font";
|
||||
import { TextDecoration } from "../text-base";
|
||||
|
||||
export { Span };
|
||||
|
||||
/**
|
||||
* A class used to create a formatted (rich text) string.
|
||||
*/
|
||||
export class FormattedString extends ViewBase {
|
||||
|
||||
/**
|
||||
* An observable collection of Span objects used to define common text properties.
|
||||
*/
|
||||
public spans: ObservableArray<Span>;
|
||||
|
||||
/**
|
||||
* A human readable representation of the formatted string.
|
||||
*/
|
||||
public toString(): string;
|
||||
|
||||
/**
|
||||
* Gets or sets the font family which will be used for all spans that doesn't have a specific value.
|
||||
*/
|
||||
public fontFamily: string;
|
||||
|
||||
/**
|
||||
* Gets or sets the font size which will be used for all spans that doesn't have a specific value.
|
||||
*/
|
||||
public fontSize: number;
|
||||
|
||||
/**
|
||||
* Gets or sets the font style which will be used for all spans that doesn't have a specific value.
|
||||
*/
|
||||
public fontStyle: FontStyle;
|
||||
|
||||
/**
|
||||
* Gets or sets the font weight which will be used for all spans that doesn't have a specific value.
|
||||
*/
|
||||
public fontWeight: FontWeight;
|
||||
|
||||
/**
|
||||
* Gets or sets text decorations which will be used for all spans that doesn't have a specific value.
|
||||
*/
|
||||
public textDecoration: TextDecoration;
|
||||
|
||||
/**
|
||||
* Gets or sets the font foreground color which will be used for all spans that doesn't have a specific value.
|
||||
*/
|
||||
public color: Color;
|
||||
|
||||
/**
|
||||
* Gets or sets the font background color which will be used for all spans that doesn't have a specific value.
|
||||
*/
|
||||
public backgroundColor: Color;
|
||||
}
|
||||
164
nativescript-core/ui/text-base/formatted-string.ts
Normal file
164
nativescript-core/ui/text-base/formatted-string.ts
Normal file
@@ -0,0 +1,164 @@
|
||||
import { FormattedString as FormattedStringDefinition } from "./formatted-string";
|
||||
import { Span } from "./span";
|
||||
import { Observable, PropertyChangeData } from "../../data/observable";
|
||||
import { ObservableArray, ChangedData } from "../../data/observable-array";
|
||||
import { ViewBase, AddArrayFromBuilder, AddChildFromBuilder } from "../core/view";
|
||||
import { Color } from "../../color";
|
||||
import { FontStyle, FontWeight } from "../styling/font";
|
||||
import { TextDecoration } from "../text-base";
|
||||
|
||||
export { Span };
|
||||
|
||||
export module knownCollections {
|
||||
export const spans = "spans";
|
||||
}
|
||||
|
||||
export class FormattedString extends ViewBase implements FormattedStringDefinition, AddArrayFromBuilder, AddChildFromBuilder {
|
||||
private _spans: ObservableArray<Span>;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this._spans = new ObservableArray<Span>();
|
||||
this._spans.addEventListener(ObservableArray.changeEvent, this.onSpansCollectionChanged, this);
|
||||
}
|
||||
|
||||
get fontFamily(): string {
|
||||
return this.style.fontFamily;
|
||||
}
|
||||
set fontFamily(value: string) {
|
||||
this.style.fontFamily = value;
|
||||
}
|
||||
|
||||
get fontSize(): number {
|
||||
return this.style.fontSize;
|
||||
}
|
||||
set fontSize(value: number) {
|
||||
this.style.fontSize = value;
|
||||
}
|
||||
|
||||
get fontStyle(): FontStyle {
|
||||
return this.style.fontStyle;
|
||||
}
|
||||
set fontStyle(value: FontStyle) {
|
||||
this.style.fontStyle = value;
|
||||
}
|
||||
|
||||
get fontWeight(): FontWeight {
|
||||
return this.style.fontWeight;
|
||||
}
|
||||
set fontWeight(value: FontWeight) {
|
||||
this.style.fontWeight = value;
|
||||
}
|
||||
|
||||
get textDecoration(): TextDecoration {
|
||||
return this.style.textDecoration;
|
||||
}
|
||||
set textDecoration(value: TextDecoration) {
|
||||
this.style.textDecoration = value;
|
||||
}
|
||||
|
||||
get color(): Color {
|
||||
return this.style.color;
|
||||
}
|
||||
set color(value: Color) {
|
||||
this.style.color = value;
|
||||
}
|
||||
|
||||
get backgroundColor(): Color {
|
||||
return this.style.backgroundColor;
|
||||
}
|
||||
set backgroundColor(value: Color) {
|
||||
this.style.backgroundColor = value;
|
||||
}
|
||||
|
||||
get spans(): ObservableArray<Span> {
|
||||
if (!this._spans) {
|
||||
this._spans = new ObservableArray<Span>();
|
||||
}
|
||||
|
||||
return this._spans;
|
||||
}
|
||||
|
||||
public toString(): string {
|
||||
let result = "";
|
||||
for (let i = 0, length = this._spans.length; i < length; i++) {
|
||||
result += this._spans.getItem(i).text;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public _addArrayFromBuilder(name: string, value: Array<any>) {
|
||||
if (name === knownCollections.spans) {
|
||||
this.spans.push(value);
|
||||
}
|
||||
}
|
||||
|
||||
public _addChildFromBuilder(name: string, value: any): void {
|
||||
if (value instanceof Span) {
|
||||
this.spans.push(value);
|
||||
}
|
||||
}
|
||||
|
||||
private onSpansCollectionChanged(eventData: ChangedData<Span>) {
|
||||
if (eventData.addedCount > 0) {
|
||||
for (let i = 0; i < eventData.addedCount; i++) {
|
||||
const span = (<ObservableArray<Span>>eventData.object).getItem(eventData.index + i);
|
||||
|
||||
// First add to logical tree so that inherited properties are set.
|
||||
this._addView(span);
|
||||
|
||||
// Then attach handlers - we skip the first nofitication because
|
||||
// we raise change for the whole instance.
|
||||
this.addPropertyChangeHandler(span);
|
||||
}
|
||||
}
|
||||
|
||||
if (eventData.removed && eventData.removed.length > 0) {
|
||||
for (let p = 0; p < eventData.removed.length; p++) {
|
||||
const span = eventData.removed[p];
|
||||
|
||||
// First remove handlers so that we don't listen for changes
|
||||
// on inherited properties.
|
||||
this.removePropertyChangeHandler(span);
|
||||
|
||||
// Then remove the element.
|
||||
this._removeView(span);
|
||||
}
|
||||
}
|
||||
|
||||
this.notifyPropertyChange(".", this);
|
||||
}
|
||||
|
||||
private addPropertyChangeHandler(span: Span) {
|
||||
const style = span.style;
|
||||
span.on(Observable.propertyChangeEvent, this.onPropertyChange, this);
|
||||
style.on("fontFamilyChange", this.onPropertyChange, this);
|
||||
style.on("fontSizeChange", this.onPropertyChange, this);
|
||||
style.on("fontStyleChange", this.onPropertyChange, this);
|
||||
style.on("fontWeightChange", this.onPropertyChange, this);
|
||||
style.on("textDecorationChange", this.onPropertyChange, this);
|
||||
style.on("colorChange", this.onPropertyChange, this);
|
||||
style.on("backgroundColorChange", this.onPropertyChange, this);
|
||||
}
|
||||
|
||||
private removePropertyChangeHandler(span: Span) {
|
||||
const style = span.style;
|
||||
span.off(Observable.propertyChangeEvent, this.onPropertyChange, this);
|
||||
style.off("fontFamilyChange", this.onPropertyChange, this);
|
||||
style.off("fontSizeChange", this.onPropertyChange, this);
|
||||
style.off("fontStyleChange", this.onPropertyChange, this);
|
||||
style.off("fontWeightChange", this.onPropertyChange, this);
|
||||
style.off("textDecorationChange", this.onPropertyChange, this);
|
||||
style.off("colorChange", this.onPropertyChange, this);
|
||||
style.off("backgroundColorChange", this.onPropertyChange, this);
|
||||
}
|
||||
|
||||
private onPropertyChange(data: PropertyChangeData) {
|
||||
this.notifyPropertyChange(data.propertyName, this);
|
||||
}
|
||||
|
||||
eachChild(callback: (child: ViewBase) => boolean): void {
|
||||
this.spans.forEach((v, i, arr) => callback(v));
|
||||
}
|
||||
}
|
||||
5
nativescript-core/ui/text-base/package.json
Normal file
5
nativescript-core/ui/text-base/package.json
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"name": "text-base",
|
||||
"main": "text-base",
|
||||
"types": "text-base.d.ts"
|
||||
}
|
||||
60
nativescript-core/ui/text-base/span.d.ts
vendored
Normal file
60
nativescript-core/ui/text-base/span.d.ts
vendored
Normal file
@@ -0,0 +1,60 @@
|
||||
/**
|
||||
* @module "ui/text-base/span"
|
||||
*/ /** */
|
||||
|
||||
import { Color } from "../../color";
|
||||
import { ViewBase } from "../core/view-base";
|
||||
import { FontStyle, FontWeight } from "../styling/font";
|
||||
import { TextDecoration } from "../text-base";
|
||||
|
||||
/**
|
||||
* A class used to create a single part of formatted string with a common text properties.
|
||||
*/
|
||||
export class Span extends ViewBase {
|
||||
/**
|
||||
* Gets or sets the font family of the span.
|
||||
*/
|
||||
public fontFamily: string;
|
||||
|
||||
/**
|
||||
* Gets or sets the font size of the span.
|
||||
*/
|
||||
public fontSize: number;
|
||||
|
||||
/**
|
||||
* Gets or sets the font style of the span.
|
||||
*/
|
||||
public fontStyle: FontStyle;
|
||||
|
||||
/**
|
||||
* Gets or sets the font weight of the span.
|
||||
*/
|
||||
public fontWeight: FontWeight;
|
||||
|
||||
/**
|
||||
* Gets or sets text decorations for the span.
|
||||
*/
|
||||
public textDecoration: TextDecoration;
|
||||
|
||||
/**
|
||||
* Gets or sets the font foreground color of the span.
|
||||
*/
|
||||
public color: Color;
|
||||
|
||||
/**
|
||||
* Gets or sets the font background color of the span.
|
||||
*/
|
||||
public backgroundColor: Color;
|
||||
|
||||
/**
|
||||
* Gets or sets the text for the span.
|
||||
*/
|
||||
public text: string;
|
||||
|
||||
//@private
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_setTextInternal(value: string): void;
|
||||
//@endprivate
|
||||
}
|
||||
74
nativescript-core/ui/text-base/span.ts
Normal file
74
nativescript-core/ui/text-base/span.ts
Normal file
@@ -0,0 +1,74 @@
|
||||
import { Color } from "../../color";
|
||||
import { Span as SpanDefinition } from "./span";
|
||||
import { ViewBase } from "../core/view";
|
||||
import { FontStyle, FontWeight, } from "../styling/font";
|
||||
import { TextDecoration } from "../text-base";
|
||||
|
||||
export class Span extends ViewBase implements SpanDefinition {
|
||||
private _text: string;
|
||||
|
||||
get fontFamily(): string {
|
||||
return this.style.fontFamily;
|
||||
}
|
||||
set fontFamily(value: string) {
|
||||
this.style.fontFamily = value;
|
||||
}
|
||||
|
||||
get fontSize(): number {
|
||||
return this.style.fontSize;
|
||||
}
|
||||
set fontSize(value: number) {
|
||||
this.style.fontSize = value;
|
||||
}
|
||||
|
||||
// Italic
|
||||
get fontStyle(): FontStyle {
|
||||
return this.style.fontStyle;
|
||||
}
|
||||
set fontStyle(value: FontStyle) {
|
||||
this.style.fontStyle = value;
|
||||
}
|
||||
|
||||
// Bold
|
||||
get fontWeight(): FontWeight {
|
||||
return this.style.fontWeight;
|
||||
}
|
||||
set fontWeight(value: FontWeight) {
|
||||
this.style.fontWeight = value;
|
||||
}
|
||||
|
||||
get textDecoration(): TextDecoration {
|
||||
return this.style.textDecoration;
|
||||
}
|
||||
set textDecoration(value: TextDecoration) {
|
||||
this.style.textDecoration = value;
|
||||
}
|
||||
|
||||
get color(): Color {
|
||||
return this.style.color;
|
||||
}
|
||||
set color(value: Color) {
|
||||
this.style.color = value;
|
||||
}
|
||||
|
||||
get backgroundColor(): Color {
|
||||
return this.style.backgroundColor;
|
||||
}
|
||||
set backgroundColor(value: Color) {
|
||||
this.style.backgroundColor = value;
|
||||
}
|
||||
|
||||
get text(): string {
|
||||
return this._text;
|
||||
}
|
||||
set text(value: string) {
|
||||
if (this._text !== value) {
|
||||
this._text = value;
|
||||
this.notifyPropertyChange("text", value);
|
||||
}
|
||||
}
|
||||
|
||||
_setTextInternal(value: string): void {
|
||||
this._text = value;
|
||||
}
|
||||
}
|
||||
220
nativescript-core/ui/text-base/text-base-common.ts
Normal file
220
nativescript-core/ui/text-base/text-base-common.ts
Normal file
@@ -0,0 +1,220 @@
|
||||
// Definitions.
|
||||
import { TextBase as TextBaseDefinition, TextAlignment, TextDecoration, TextTransform, WhiteSpace } from ".";
|
||||
import { FontStyle, FontWeight } from "../styling/font";
|
||||
import { PropertyChangeData } from "../../data/observable";
|
||||
|
||||
// Types.
|
||||
import { View, ViewBase, Property, CssProperty, InheritedCssProperty, Style, isAndroid, isIOS, Observable, makeValidator, makeParser, Length } from "../core/view";
|
||||
import { FormattedString, Span } from "./formatted-string";
|
||||
|
||||
export { FormattedString, Span };
|
||||
export * from "../core/view";
|
||||
|
||||
const CHILD_SPAN = "Span";
|
||||
const CHILD_FORMATTED_TEXT = "formattedText";
|
||||
const CHILD_FORMATTED_STRING = "FormattedString";
|
||||
|
||||
export abstract class TextBaseCommon extends View implements TextBaseDefinition {
|
||||
public _isSingleLine: boolean;
|
||||
public text: string;
|
||||
public formattedText: FormattedString;
|
||||
|
||||
get nativeTextViewProtected() {
|
||||
return this.nativeViewProtected;
|
||||
}
|
||||
|
||||
get fontFamily(): string {
|
||||
return this.style.fontFamily;
|
||||
}
|
||||
set fontFamily(value: string) {
|
||||
this.style.fontFamily = value;
|
||||
}
|
||||
|
||||
get fontSize(): number {
|
||||
return this.style.fontSize;
|
||||
}
|
||||
set fontSize(value: number) {
|
||||
this.style.fontSize = value;
|
||||
}
|
||||
|
||||
get fontStyle(): FontStyle {
|
||||
return this.style.fontStyle;
|
||||
}
|
||||
set fontStyle(value: FontStyle) {
|
||||
this.style.fontStyle = value;
|
||||
}
|
||||
|
||||
get fontWeight(): FontWeight {
|
||||
return this.style.fontWeight;
|
||||
}
|
||||
set fontWeight(value: FontWeight) {
|
||||
this.style.fontWeight = value;
|
||||
}
|
||||
|
||||
get letterSpacing(): number {
|
||||
return this.style.letterSpacing;
|
||||
}
|
||||
set letterSpacing(value: number) {
|
||||
this.style.letterSpacing = value;
|
||||
}
|
||||
|
||||
get lineHeight(): number {
|
||||
return this.style.lineHeight;
|
||||
}
|
||||
set lineHeight(value: number) {
|
||||
this.style.lineHeight = value;
|
||||
}
|
||||
|
||||
get textAlignment(): TextAlignment {
|
||||
return this.style.textAlignment;
|
||||
}
|
||||
set textAlignment(value: TextAlignment) {
|
||||
this.style.textAlignment = value;
|
||||
}
|
||||
|
||||
get textDecoration(): TextDecoration {
|
||||
return this.style.textDecoration;
|
||||
}
|
||||
set textDecoration(value: TextDecoration) {
|
||||
this.style.textDecoration = value;
|
||||
}
|
||||
|
||||
get textTransform(): TextTransform {
|
||||
return this.style.textTransform;
|
||||
}
|
||||
set textTransform(value: TextTransform) {
|
||||
this.style.textTransform = value;
|
||||
}
|
||||
|
||||
get whiteSpace(): WhiteSpace {
|
||||
return this.style.whiteSpace;
|
||||
}
|
||||
set whiteSpace(value: WhiteSpace) {
|
||||
this.style.whiteSpace = value;
|
||||
}
|
||||
|
||||
get padding(): string | Length {
|
||||
return this.style.padding;
|
||||
}
|
||||
set padding(value: string | Length) {
|
||||
this.style.padding = value;
|
||||
}
|
||||
|
||||
get paddingTop(): Length {
|
||||
return this.style.paddingTop;
|
||||
}
|
||||
set paddingTop(value: Length) {
|
||||
this.style.paddingTop = value;
|
||||
}
|
||||
|
||||
get paddingRight(): Length {
|
||||
return this.style.paddingRight;
|
||||
}
|
||||
set paddingRight(value: Length) {
|
||||
this.style.paddingRight = value;
|
||||
}
|
||||
|
||||
get paddingBottom(): Length {
|
||||
return this.style.paddingBottom;
|
||||
}
|
||||
set paddingBottom(value: Length) {
|
||||
this.style.paddingBottom = value;
|
||||
}
|
||||
|
||||
get paddingLeft(): Length {
|
||||
return this.style.paddingLeft;
|
||||
}
|
||||
set paddingLeft(value: Length) {
|
||||
this.style.paddingLeft = value;
|
||||
}
|
||||
|
||||
public _onFormattedTextContentsChanged(data: PropertyChangeData) {
|
||||
if (this.nativeViewProtected) {
|
||||
// Notifications from the FormattedString start arriving before the Android view is even created.
|
||||
this[formattedTextProperty.setNative](data.value);
|
||||
}
|
||||
}
|
||||
|
||||
public _addChildFromBuilder(name: string, value: any): void {
|
||||
if (name === CHILD_SPAN) {
|
||||
if (!this.formattedText) {
|
||||
const formattedText = new FormattedString();
|
||||
formattedText.spans.push(value);
|
||||
this.formattedText = formattedText;
|
||||
} else {
|
||||
this.formattedText.spans.push(value);
|
||||
}
|
||||
}
|
||||
else if (name === CHILD_FORMATTED_TEXT || name === CHILD_FORMATTED_STRING) {
|
||||
this.formattedText = value;
|
||||
}
|
||||
}
|
||||
|
||||
_requestLayoutOnTextChanged(): void {
|
||||
this.requestLayout();
|
||||
}
|
||||
|
||||
eachChild(callback: (child: ViewBase) => boolean): void {
|
||||
let text = this.formattedText;
|
||||
if (text) {
|
||||
callback(text);
|
||||
}
|
||||
}
|
||||
|
||||
_setNativeText(reset: boolean = false): void {
|
||||
//
|
||||
}
|
||||
}
|
||||
|
||||
TextBaseCommon.prototype._isSingleLine = false;
|
||||
|
||||
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: "", affectsLayout: isAndroid });
|
||||
textProperty.register(TextBaseCommon);
|
||||
|
||||
export const formattedTextProperty = new Property<TextBaseCommon, FormattedString>({ name: "formattedText", affectsLayout: true, valueChanged: onFormattedTextPropertyChanged });
|
||||
formattedTextProperty.register(TextBaseCommon);
|
||||
|
||||
function onFormattedTextPropertyChanged(textBase: TextBaseCommon, oldValue: FormattedString, newValue: FormattedString) {
|
||||
if (oldValue) {
|
||||
oldValue.off(Observable.propertyChangeEvent, textBase._onFormattedTextContentsChanged, textBase);
|
||||
textBase._removeView(oldValue);
|
||||
}
|
||||
|
||||
if (newValue) {
|
||||
const oldParent = newValue.parent;
|
||||
// In case formattedString is attached to new TextBase
|
||||
if (oldParent) {
|
||||
oldParent._removeView(newValue);
|
||||
}
|
||||
textBase._addView(newValue);
|
||||
newValue.on(Observable.propertyChangeEvent, textBase._onFormattedTextContentsChanged, textBase);
|
||||
}
|
||||
}
|
||||
|
||||
const textAlignmentConverter = makeParser<TextAlignment>(makeValidator<TextAlignment>("initial", "left", "center", "right"));
|
||||
export const textAlignmentProperty = new InheritedCssProperty<Style, TextAlignment>({ name: "textAlignment", cssName: "text-align", defaultValue: "initial", valueConverter: textAlignmentConverter });
|
||||
textAlignmentProperty.register(Style);
|
||||
|
||||
const textTransformConverter = makeParser<TextTransform>(makeValidator<TextTransform>("initial", "none", "capitalize", "uppercase", "lowercase"));
|
||||
export const textTransformProperty = new CssProperty<Style, TextTransform>({ name: "textTransform", cssName: "text-transform", defaultValue: "initial", valueConverter: textTransformConverter });
|
||||
textTransformProperty.register(Style);
|
||||
|
||||
const whiteSpaceConverter = makeParser<WhiteSpace>(makeValidator<WhiteSpace>("initial", "normal", "nowrap"));
|
||||
export const whiteSpaceProperty = new CssProperty<Style, WhiteSpace>({ name: "whiteSpace", cssName: "white-space", defaultValue: "initial", affectsLayout: isIOS, valueConverter: whiteSpaceConverter });
|
||||
whiteSpaceProperty.register(Style);
|
||||
|
||||
const textDecorationConverter = makeParser<TextDecoration>(makeValidator<TextDecoration>("none", "underline", "line-through", "underline line-through"));
|
||||
export const textDecorationProperty = new CssProperty<Style, TextDecoration>({ name: "textDecoration", cssName: "text-decoration", defaultValue: "none", valueConverter: textDecorationConverter });
|
||||
textDecorationProperty.register(Style);
|
||||
|
||||
export const letterSpacingProperty = new CssProperty<Style, number>({ name: "letterSpacing", cssName: "letter-spacing", defaultValue: 0, affectsLayout: isIOS, valueConverter: v => parseFloat(v) });
|
||||
letterSpacingProperty.register(Style);
|
||||
|
||||
export const lineHeightProperty = new CssProperty<Style, number>({ name: "lineHeight", cssName: "line-height", affectsLayout: isIOS, valueConverter: v => parseFloat(v) });
|
||||
lineHeightProperty.register(Style);
|
||||
|
||||
export const resetSymbol = Symbol("textPropertyDefault");
|
||||
453
nativescript-core/ui/text-base/text-base.android.ts
Normal file
453
nativescript-core/ui/text-base/text-base.android.ts
Normal file
@@ -0,0 +1,453 @@
|
||||
import { TextDecoration, TextAlignment, TextTransform, WhiteSpace } from "./text-base";
|
||||
import { Font } from "../styling/font";
|
||||
import { backgroundColorProperty } from "../styling/style-properties";
|
||||
import {
|
||||
TextBaseCommon, formattedTextProperty, textAlignmentProperty, textDecorationProperty, fontSizeProperty,
|
||||
textProperty, textTransformProperty, letterSpacingProperty, colorProperty, fontInternalProperty,
|
||||
paddingLeftProperty, paddingTopProperty, paddingRightProperty, paddingBottomProperty, Length,
|
||||
whiteSpaceProperty, lineHeightProperty, FormattedString, layout, Span, Color, isBold, resetSymbol
|
||||
} from "./text-base-common";
|
||||
import { isString } from "../../utils/types";
|
||||
|
||||
export * from "./text-base-common";
|
||||
|
||||
interface TextTransformation {
|
||||
new(owner: TextBase): android.text.method.TransformationMethod;
|
||||
}
|
||||
|
||||
let TextTransformation: TextTransformation;
|
||||
|
||||
function initializeTextTransformation(): void {
|
||||
if (TextTransformation) {
|
||||
return;
|
||||
}
|
||||
|
||||
@Interfaces([android.text.method.TransformationMethod])
|
||||
class TextTransformationImpl extends java.lang.Object implements android.text.method.TransformationMethod {
|
||||
constructor(public textBase: TextBase) {
|
||||
super();
|
||||
|
||||
return global.__native(this);
|
||||
}
|
||||
|
||||
public getTransformation(charSeq: any, view: android.view.View): any {
|
||||
// NOTE: Do we need to transform the new text here?
|
||||
const formattedText = this.textBase.formattedText;
|
||||
if (formattedText) {
|
||||
return createSpannableStringBuilder(formattedText);
|
||||
}
|
||||
else {
|
||||
const text = this.textBase.text;
|
||||
const stringValue = (text === null || text === undefined) ? "" : text.toString();
|
||||
|
||||
return getTransformedText(stringValue, 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;
|
||||
}
|
||||
|
||||
export class TextBase extends TextBaseCommon {
|
||||
nativeViewProtected: android.widget.TextView;
|
||||
nativeTextViewProtected: android.widget.TextView;
|
||||
private _defaultTransformationMethod: android.text.method.TransformationMethod;
|
||||
private _paintFlags: number;
|
||||
private _minHeight: number;
|
||||
private _maxHeight: number;
|
||||
private _minLines: number;
|
||||
private _maxLines: number;
|
||||
|
||||
public initNativeView(): void {
|
||||
super.initNativeView();
|
||||
initializeTextTransformation();
|
||||
const nativeView = this.nativeTextViewProtected;
|
||||
this._defaultTransformationMethod = nativeView.getTransformationMethod();
|
||||
this._minHeight = nativeView.getMinHeight();
|
||||
this._maxHeight = nativeView.getMaxHeight();
|
||||
this._minLines = nativeView.getMinLines();
|
||||
this._maxLines = nativeView.getMaxLines();
|
||||
}
|
||||
|
||||
public resetNativeView(): void {
|
||||
super.resetNativeView();
|
||||
const nativeView = this.nativeTextViewProtected;
|
||||
// We reset it here too because this could be changed by multiple properties - whiteSpace, secure, textTransform
|
||||
nativeView.setSingleLine(this._isSingleLine);
|
||||
nativeView.setTransformationMethod(this._defaultTransformationMethod);
|
||||
this._defaultTransformationMethod = null;
|
||||
|
||||
if (this._paintFlags !== undefined) {
|
||||
nativeView.setPaintFlags(this._paintFlags);
|
||||
this._paintFlags = undefined;
|
||||
}
|
||||
|
||||
if (this._minLines !== -1) {
|
||||
nativeView.setMinLines(this._minLines);
|
||||
} else {
|
||||
nativeView.setMinHeight(this._minHeight);
|
||||
}
|
||||
|
||||
this._minHeight = this._minLines = undefined;
|
||||
|
||||
if (this._maxLines !== -1) {
|
||||
nativeView.setMaxLines(this._maxLines);
|
||||
} else {
|
||||
nativeView.setMaxHeight(this._maxHeight);
|
||||
}
|
||||
|
||||
this._maxHeight = this._maxLines = undefined;
|
||||
}
|
||||
|
||||
[textProperty.getDefault](): symbol | number {
|
||||
return resetSymbol;
|
||||
}
|
||||
|
||||
[textProperty.setNative](value: string | number | symbol) {
|
||||
const reset = value === resetSymbol;
|
||||
if (!reset && this.formattedText) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._setNativeText(reset);
|
||||
}
|
||||
|
||||
[formattedTextProperty.setNative](value: FormattedString) {
|
||||
const nativeView = this.nativeTextViewProtected;
|
||||
if (!value) {
|
||||
if (nativeView instanceof android.widget.Button &&
|
||||
nativeView.getTransformationMethod() instanceof TextTransformation) {
|
||||
nativeView.setTransformationMethod(this._defaultTransformationMethod);
|
||||
}
|
||||
}
|
||||
|
||||
// Don't change the transformation method if this is secure TextField or we'll lose the hiding characters.
|
||||
if ((<any>this).secure) {
|
||||
return;
|
||||
}
|
||||
|
||||
const spannableStringBuilder = createSpannableStringBuilder(value);
|
||||
nativeView.setText(<any>spannableStringBuilder);
|
||||
|
||||
textProperty.nativeValueChange(this, (value === null || value === undefined) ? "" : value.toString());
|
||||
|
||||
if (spannableStringBuilder && nativeView instanceof android.widget.Button &&
|
||||
!(nativeView.getTransformationMethod() instanceof TextTransformation)) {
|
||||
// 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.
|
||||
// Otherwise, the default tranformation method of the Android Button will overwrite and ignore our spannableStringBuilder.
|
||||
nativeView.setTransformationMethod(new TextTransformation(this));
|
||||
}
|
||||
}
|
||||
|
||||
[textTransformProperty.setNative](value: TextTransform) {
|
||||
if (value === "initial") {
|
||||
this.nativeTextViewProtected.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;
|
||||
}
|
||||
|
||||
this.nativeTextViewProtected.setTransformationMethod(new TextTransformation(this));
|
||||
}
|
||||
|
||||
[textAlignmentProperty.getDefault](): TextAlignment {
|
||||
return "initial";
|
||||
}
|
||||
[textAlignmentProperty.setNative](value: TextAlignment) {
|
||||
let verticalGravity = this.nativeTextViewProtected.getGravity() & android.view.Gravity.VERTICAL_GRAVITY_MASK;
|
||||
switch (value) {
|
||||
case "initial":
|
||||
case "left":
|
||||
this.nativeTextViewProtected.setGravity(android.view.Gravity.START | verticalGravity);
|
||||
break;
|
||||
|
||||
case "center":
|
||||
this.nativeTextViewProtected.setGravity(android.view.Gravity.CENTER_HORIZONTAL | verticalGravity);
|
||||
break;
|
||||
|
||||
case "right":
|
||||
this.nativeTextViewProtected.setGravity(android.view.Gravity.END | verticalGravity);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Overridden in TextField because setSingleLine(false) will remove methodTransformation.
|
||||
// and we don't want to allow TextField to be multiline
|
||||
[whiteSpaceProperty.setNative](value: WhiteSpace) {
|
||||
const nativeView = this.nativeTextViewProtected;
|
||||
switch (value) {
|
||||
case "initial":
|
||||
case "normal":
|
||||
nativeView.setSingleLine(false);
|
||||
nativeView.setEllipsize(null);
|
||||
break;
|
||||
case "nowrap":
|
||||
nativeView.setSingleLine(true);
|
||||
nativeView.setEllipsize(android.text.TextUtils.TruncateAt.END);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
[colorProperty.getDefault](): android.content.res.ColorStateList {
|
||||
return this.nativeTextViewProtected.getTextColors();
|
||||
}
|
||||
[colorProperty.setNative](value: Color | android.content.res.ColorStateList) {
|
||||
if (!this.formattedText || !(value instanceof Color)) {
|
||||
if (value instanceof Color) {
|
||||
this.nativeTextViewProtected.setTextColor(value.android);
|
||||
} else {
|
||||
this.nativeTextViewProtected.setTextColor(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[fontSizeProperty.getDefault](): { nativeSize: number } {
|
||||
return { nativeSize: this.nativeTextViewProtected.getTextSize() };
|
||||
}
|
||||
[fontSizeProperty.setNative](value: number | { nativeSize: number }) {
|
||||
if (!this.formattedText || (typeof value !== "number")) {
|
||||
if (typeof value === "number") {
|
||||
this.nativeTextViewProtected.setTextSize(value);
|
||||
} else {
|
||||
this.nativeTextViewProtected.setTextSize(android.util.TypedValue.COMPLEX_UNIT_PX, value.nativeSize);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[lineHeightProperty.getDefault](): number {
|
||||
return this.nativeTextViewProtected.getLineSpacingExtra() / layout.getDisplayDensity();
|
||||
}
|
||||
[lineHeightProperty.setNative](value: number) {
|
||||
this.nativeTextViewProtected.setLineSpacing(value * layout.getDisplayDensity(), 1);
|
||||
}
|
||||
|
||||
[fontInternalProperty.getDefault](): android.graphics.Typeface {
|
||||
return this.nativeTextViewProtected.getTypeface();
|
||||
}
|
||||
[fontInternalProperty.setNative](value: Font | android.graphics.Typeface) {
|
||||
if (!this.formattedText || !(value instanceof Font)) {
|
||||
this.nativeTextViewProtected.setTypeface(value instanceof Font ? value.getAndroidTypeface() : value);
|
||||
}
|
||||
}
|
||||
|
||||
[textDecorationProperty.getDefault](value: number) {
|
||||
return this._paintFlags = this.nativeTextViewProtected.getPaintFlags();
|
||||
}
|
||||
|
||||
[textDecorationProperty.setNative](value: number | TextDecoration) {
|
||||
switch (value) {
|
||||
case "none":
|
||||
this.nativeTextViewProtected.setPaintFlags(0);
|
||||
break;
|
||||
case "underline":
|
||||
this.nativeTextViewProtected.setPaintFlags(android.graphics.Paint.UNDERLINE_TEXT_FLAG);
|
||||
break;
|
||||
case "line-through":
|
||||
this.nativeTextViewProtected.setPaintFlags(android.graphics.Paint.STRIKE_THRU_TEXT_FLAG);
|
||||
break;
|
||||
case "underline line-through":
|
||||
this.nativeTextViewProtected.setPaintFlags(android.graphics.Paint.UNDERLINE_TEXT_FLAG | android.graphics.Paint.STRIKE_THRU_TEXT_FLAG);
|
||||
break;
|
||||
default:
|
||||
this.nativeTextViewProtected.setPaintFlags(value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
[letterSpacingProperty.getDefault](): number {
|
||||
return org.nativescript.widgets.ViewHelper.getLetterspacing(this.nativeTextViewProtected);
|
||||
}
|
||||
[letterSpacingProperty.setNative](value: number) {
|
||||
org.nativescript.widgets.ViewHelper.setLetterspacing(this.nativeTextViewProtected, value);
|
||||
}
|
||||
|
||||
[paddingTopProperty.getDefault](): Length {
|
||||
return { value: this._defaultPaddingTop, unit: "px" };
|
||||
}
|
||||
[paddingTopProperty.setNative](value: Length) {
|
||||
org.nativescript.widgets.ViewHelper.setPaddingTop(this.nativeTextViewProtected, Length.toDevicePixels(value, 0) + Length.toDevicePixels(this.style.borderTopWidth, 0));
|
||||
}
|
||||
|
||||
[paddingRightProperty.getDefault](): Length {
|
||||
return { value: this._defaultPaddingRight, unit: "px" };
|
||||
}
|
||||
[paddingRightProperty.setNative](value: Length) {
|
||||
org.nativescript.widgets.ViewHelper.setPaddingRight(this.nativeTextViewProtected, Length.toDevicePixels(value, 0) + Length.toDevicePixels(this.style.borderRightWidth, 0));
|
||||
}
|
||||
|
||||
[paddingBottomProperty.getDefault](): Length {
|
||||
return { value: this._defaultPaddingBottom, unit: "px" };
|
||||
}
|
||||
[paddingBottomProperty.setNative](value: Length) {
|
||||
org.nativescript.widgets.ViewHelper.setPaddingBottom(this.nativeTextViewProtected, Length.toDevicePixels(value, 0) + Length.toDevicePixels(this.style.borderBottomWidth, 0));
|
||||
}
|
||||
|
||||
[paddingLeftProperty.getDefault](): Length {
|
||||
return { value: this._defaultPaddingLeft, unit: "px" };
|
||||
}
|
||||
[paddingLeftProperty.setNative](value: Length) {
|
||||
org.nativescript.widgets.ViewHelper.setPaddingLeft(this.nativeTextViewProtected, Length.toDevicePixels(value, 0) + Length.toDevicePixels(this.style.borderLeftWidth, 0));
|
||||
}
|
||||
|
||||
_setNativeText(reset: boolean = false): void {
|
||||
if (reset) {
|
||||
this.nativeTextViewProtected.setText(null);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
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.nativeTextViewProtected.setText(<any>transformedText);
|
||||
}
|
||||
}
|
||||
|
||||
function getCapitalizedString(str: string): string {
|
||||
let words = str.split(" ");
|
||||
let newWords = [];
|
||||
for (let i = 0, length = words.length; i < length; i++) {
|
||||
let word = words[i].toLowerCase();
|
||||
newWords.push(word.substr(0, 1).toUpperCase() + word.substring(1));
|
||||
}
|
||||
|
||||
return newWords.join(" ");
|
||||
}
|
||||
|
||||
export function getTransformedText(text: string, textTransform: TextTransform): string {
|
||||
if (!text || !isString(text)) {
|
||||
return "";
|
||||
}
|
||||
|
||||
switch (textTransform) {
|
||||
case "uppercase":
|
||||
return text.toUpperCase();
|
||||
case "lowercase":
|
||||
return text.toLowerCase();
|
||||
case "capitalize":
|
||||
return getCapitalizedString(text);
|
||||
case "none":
|
||||
default:
|
||||
return text;
|
||||
}
|
||||
}
|
||||
|
||||
function createSpannableStringBuilder(formattedString: FormattedString): android.text.SpannableStringBuilder {
|
||||
if (!formattedString || !formattedString.parent) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const ssb = new android.text.SpannableStringBuilder();
|
||||
for (let i = 0, spanStart = 0, spanLength = 0, length = formattedString.spans.length; i < length; i++) {
|
||||
const span = formattedString.spans.getItem(i);
|
||||
const text = span.text;
|
||||
const textTransform = (<TextBase>formattedString.parent).textTransform;
|
||||
let spanText = (text === null || text === undefined) ? "" : text.toString();
|
||||
if (textTransform && textTransform !== "none") {
|
||||
spanText = getTransformedText(spanText, textTransform);
|
||||
}
|
||||
|
||||
spanLength = spanText.length;
|
||||
if (spanLength > 0) {
|
||||
ssb.insert(spanStart, spanText);
|
||||
setSpanModifiers(ssb, span, spanStart, spanStart + spanLength);
|
||||
spanStart += spanLength;
|
||||
}
|
||||
}
|
||||
|
||||
return ssb;
|
||||
}
|
||||
|
||||
function setSpanModifiers(ssb: android.text.SpannableStringBuilder, span: Span, start: number, end: number): void {
|
||||
const spanStyle = span.style;
|
||||
const bold = isBold(spanStyle.fontWeight);
|
||||
const italic = spanStyle.fontStyle === "italic";
|
||||
|
||||
if (bold && italic) {
|
||||
ssb.setSpan(new android.text.style.StyleSpan(android.graphics.Typeface.BOLD_ITALIC), start, end, android.text.Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
}
|
||||
else if (bold) {
|
||||
ssb.setSpan(new android.text.style.StyleSpan(android.graphics.Typeface.BOLD), start, end, android.text.Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
}
|
||||
else if (italic) {
|
||||
ssb.setSpan(new android.text.style.StyleSpan(android.graphics.Typeface.ITALIC), start, end, android.text.Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
}
|
||||
|
||||
const fontFamily = span.fontFamily;
|
||||
if (fontFamily) {
|
||||
const font = new Font(fontFamily, 0, (italic) ? "italic" : "normal", (bold) ? "bold" : "normal");
|
||||
const typeface = font.getAndroidTypeface() || android.graphics.Typeface.create(fontFamily, 0);
|
||||
const typefaceSpan: android.text.style.TypefaceSpan = new org.nativescript.widgets.CustomTypefaceSpan(fontFamily, typeface);
|
||||
ssb.setSpan(typefaceSpan, start, end, android.text.Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
}
|
||||
|
||||
const realFontSize = span.fontSize;
|
||||
if (realFontSize) {
|
||||
ssb.setSpan(new android.text.style.AbsoluteSizeSpan(realFontSize * layout.getDisplayDensity()), start, end, android.text.Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
}
|
||||
|
||||
const color = span.color;
|
||||
if (color) {
|
||||
ssb.setSpan(new android.text.style.ForegroundColorSpan(color.android), start, end, android.text.Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
}
|
||||
|
||||
let backgroundColor: Color;
|
||||
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) {
|
||||
ssb.setSpan(new android.text.style.BackgroundColorSpan(backgroundColor.android), start, end, android.text.Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
}
|
||||
|
||||
let valueSource: typeof spanStyle;
|
||||
if (textDecorationProperty.isSet(spanStyle)) {
|
||||
valueSource = spanStyle;
|
||||
} else if (textDecorationProperty.isSet(span.parent.style)) {
|
||||
// span.parent is FormattedString
|
||||
valueSource = span.parent.style;
|
||||
} else if (textDecorationProperty.isSet(span.parent.parent.style)) {
|
||||
// span.parent.parent is TextBase
|
||||
valueSource = span.parent.parent.style;
|
||||
}
|
||||
|
||||
if (valueSource) {
|
||||
const textDecorations = valueSource.textDecoration;
|
||||
const underline = textDecorations.indexOf("underline") !== -1;
|
||||
if (underline) {
|
||||
ssb.setSpan(new android.text.style.UnderlineSpan(), start, end, android.text.Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
}
|
||||
|
||||
const strikethrough = textDecorations.indexOf("line-through") !== -1;
|
||||
if (strikethrough) {
|
||||
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);
|
||||
// }
|
||||
}
|
||||
134
nativescript-core/ui/text-base/text-base.d.ts
vendored
Normal file
134
nativescript-core/ui/text-base/text-base.d.ts
vendored
Normal file
@@ -0,0 +1,134 @@
|
||||
/**
|
||||
* @module "ui/text-base"
|
||||
*/ /** */
|
||||
|
||||
import { View, AddChildFromBuilder, Property, CssProperty, InheritedCssProperty, Style, Length } from "../core/view";
|
||||
import { FormattedString } from "./formatted-string";
|
||||
|
||||
export * from "../core/view";
|
||||
export { FormattedString };
|
||||
|
||||
export class TextBase extends View implements AddChildFromBuilder {
|
||||
|
||||
/**
|
||||
* Gets of the text widget. In some cases(android TextInputLayout) the TextView is made of 2 views: the layout and the text view
|
||||
* So we need a different getter for the layout and text functions
|
||||
*/
|
||||
public readonly nativeTextViewProtected: any;
|
||||
|
||||
/**
|
||||
* Gets or sets the text.
|
||||
*/
|
||||
text: string;
|
||||
|
||||
/**
|
||||
* Gets or sets a formatted string.
|
||||
*/
|
||||
formattedText: FormattedString;
|
||||
|
||||
/**
|
||||
* Gets or sets font-size style property.
|
||||
*/
|
||||
fontSize: number;
|
||||
|
||||
/**
|
||||
* Gets or sets letterSpace style property.
|
||||
*/
|
||||
letterSpacing: number;
|
||||
|
||||
/**
|
||||
* Gets or sets lineHeight style property.
|
||||
*/
|
||||
lineHeight: number;
|
||||
|
||||
/**
|
||||
* Gets or sets text-alignment style property.
|
||||
*/
|
||||
textAlignment: TextAlignment;
|
||||
|
||||
/**
|
||||
* Gets or sets text decorations style property.
|
||||
*/
|
||||
textDecoration: TextDecoration;
|
||||
|
||||
/**
|
||||
* Gets or sets text transform style property.
|
||||
*/
|
||||
textTransform: TextTransform;
|
||||
|
||||
/**
|
||||
* Gets or sets white space style property.
|
||||
*/
|
||||
whiteSpace: WhiteSpace;
|
||||
|
||||
/**
|
||||
* Gets or sets padding style property.
|
||||
*/
|
||||
padding: string | Length;
|
||||
|
||||
/**
|
||||
* Specify the bottom padding of this layout.
|
||||
*/
|
||||
paddingBottom: Length;
|
||||
|
||||
/**
|
||||
* Specify the left padding of this layout.
|
||||
*/
|
||||
paddingLeft: Length;
|
||||
|
||||
/**
|
||||
* Specify the right padding of this layout.
|
||||
*/
|
||||
paddingRight: Length;
|
||||
|
||||
/**
|
||||
* Specify the top padding of this layout.
|
||||
*/
|
||||
paddingTop: Length;
|
||||
|
||||
/**
|
||||
* Called for every child element declared in xml.
|
||||
* This method will add a child element (value) to current element.
|
||||
* @private
|
||||
* @param name - Name of the element.
|
||||
* @param value - Value of the element.
|
||||
*/
|
||||
_addChildFromBuilder(name: string, value: any): void;
|
||||
|
||||
//@private
|
||||
/**
|
||||
* Called when the text property is changed to request layout.
|
||||
* @private
|
||||
*/
|
||||
_requestLayoutOnTextChanged(): void;
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_setNativeText(reset?: boolean): void;
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_isSingleLine: boolean;
|
||||
//@endprivate
|
||||
}
|
||||
|
||||
export const textProperty: Property<TextBase, string>;
|
||||
export const formattedTextProperty: Property<TextBase, FormattedString>;
|
||||
|
||||
export type WhiteSpace = "initial" | "normal" | "nowrap";
|
||||
export type TextAlignment = "initial" | "left" | "center" | "right";
|
||||
export type TextTransform = "initial" | "none" | "capitalize" | "uppercase" | "lowercase";
|
||||
export type TextDecoration = "none" | "underline" | "line-through" | "underline line-through";
|
||||
|
||||
export const textAlignmentProperty: InheritedCssProperty<Style, TextAlignment>;
|
||||
export const textDecorationProperty: CssProperty<Style, TextDecoration>;
|
||||
export const textTransformProperty: CssProperty<Style, TextTransform>;
|
||||
export const whiteSpaceProperty: CssProperty<Style, WhiteSpace>;
|
||||
export const letterSpacingProperty: CssProperty<Style, number>;
|
||||
|
||||
//Used by tab view
|
||||
export function getTransformedText(text: string, textTransform: TextTransform): string;
|
||||
|
||||
export const resetSymbol: symbol;
|
||||
335
nativescript-core/ui/text-base/text-base.ios.ts
Normal file
335
nativescript-core/ui/text-base/text-base.ios.ts
Normal file
@@ -0,0 +1,335 @@
|
||||
import { TextDecoration, TextAlignment, TextTransform } from "./text-base";
|
||||
import { Font } from "../styling/font";
|
||||
import {
|
||||
TextBaseCommon, textProperty, formattedTextProperty, textAlignmentProperty, textDecorationProperty,
|
||||
textTransformProperty, letterSpacingProperty, colorProperty, fontInternalProperty, lineHeightProperty,
|
||||
FormattedString, Span, Color, isBold, resetSymbol
|
||||
} from "./text-base-common";
|
||||
|
||||
export * from "./text-base-common";
|
||||
|
||||
import { isString } from "../../utils/types";
|
||||
import { ios } from "../../utils/utils";
|
||||
|
||||
const majorVersion = ios.MajorVersion;
|
||||
|
||||
export class TextBase extends TextBaseCommon {
|
||||
|
||||
public nativeViewProtected: UITextField | UITextView | UILabel | UIButton;
|
||||
public nativeTextViewProtected: UITextField | UITextView | UILabel | UIButton;
|
||||
|
||||
[textProperty.getDefault](): number | symbol {
|
||||
return resetSymbol;
|
||||
}
|
||||
[textProperty.setNative](value: string | number | symbol) {
|
||||
const reset = value === resetSymbol;
|
||||
if (!reset && this.formattedText) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._setNativeText(reset);
|
||||
this._requestLayoutOnTextChanged();
|
||||
}
|
||||
|
||||
[formattedTextProperty.setNative](value: FormattedString) {
|
||||
this._setNativeText();
|
||||
textProperty.nativeValueChange(this, !value ? "" : value.toString());
|
||||
this._requestLayoutOnTextChanged();
|
||||
}
|
||||
|
||||
[colorProperty.getDefault](): UIColor {
|
||||
let nativeView = this.nativeTextViewProtected;
|
||||
if (nativeView instanceof UIButton) {
|
||||
return nativeView.titleColorForState(UIControlState.Normal);
|
||||
} else {
|
||||
return nativeView.textColor;
|
||||
}
|
||||
}
|
||||
[colorProperty.setNative](value: Color | UIColor) {
|
||||
const color = value instanceof Color ? value.ios : value;
|
||||
const nativeView = this.nativeTextViewProtected;
|
||||
if (nativeView instanceof UIButton) {
|
||||
nativeView.setTitleColorForState(color, UIControlState.Normal);
|
||||
nativeView.titleLabel.textColor = color;
|
||||
} else {
|
||||
nativeView.textColor = color;
|
||||
}
|
||||
}
|
||||
|
||||
[fontInternalProperty.getDefault](): UIFont {
|
||||
let nativeView = this.nativeTextViewProtected;
|
||||
nativeView = nativeView instanceof UIButton ? nativeView.titleLabel : nativeView;
|
||||
|
||||
return nativeView.font;
|
||||
}
|
||||
[fontInternalProperty.setNative](value: Font | UIFont) {
|
||||
if (!(value instanceof Font) || !this.formattedText) {
|
||||
let nativeView = this.nativeTextViewProtected;
|
||||
nativeView = nativeView instanceof UIButton ? nativeView.titleLabel : nativeView;
|
||||
const font = value instanceof Font ? value.getUIFont(nativeView.font) : value;
|
||||
nativeView.font = font;
|
||||
}
|
||||
}
|
||||
|
||||
[textAlignmentProperty.setNative](value: TextAlignment) {
|
||||
const nativeView = <UITextField | UITextView | UILabel>this.nativeTextViewProtected;
|
||||
switch (value) {
|
||||
case "initial":
|
||||
case "left":
|
||||
nativeView.textAlignment = NSTextAlignment.Left;
|
||||
break;
|
||||
case "center":
|
||||
nativeView.textAlignment = NSTextAlignment.Center;
|
||||
break;
|
||||
case "right":
|
||||
nativeView.textAlignment = NSTextAlignment.Right;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
[textDecorationProperty.setNative](value: TextDecoration) {
|
||||
this._setNativeText();
|
||||
}
|
||||
|
||||
[textTransformProperty.setNative](value: TextTransform) {
|
||||
this._setNativeText();
|
||||
}
|
||||
|
||||
[letterSpacingProperty.setNative](value: number) {
|
||||
this._setNativeText();
|
||||
}
|
||||
|
||||
[lineHeightProperty.setNative](value: number) {
|
||||
this._setNativeText();
|
||||
}
|
||||
|
||||
_setNativeText(reset: boolean = false): void {
|
||||
if (reset) {
|
||||
const nativeView = this.nativeTextViewProtected;
|
||||
if (nativeView instanceof UIButton) {
|
||||
// Clear attributedText or title won't be affected.
|
||||
nativeView.setAttributedTitleForState(null, UIControlState.Normal);
|
||||
nativeView.setTitleForState(null, UIControlState.Normal);
|
||||
} else {
|
||||
// Clear attributedText or text won't be affected.
|
||||
nativeView.attributedText = null;
|
||||
nativeView.text = null;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.formattedText) {
|
||||
this.setFormattedTextDecorationAndTransform();
|
||||
} else {
|
||||
this.setTextDecorationAndTransform();
|
||||
}
|
||||
}
|
||||
|
||||
setFormattedTextDecorationAndTransform() {
|
||||
const attrText = this.createNSMutableAttributedString(this.formattedText);
|
||||
// TODO: letterSpacing should be applied per Span.
|
||||
if (this.letterSpacing !== 0) {
|
||||
attrText.addAttributeValueRange(NSKernAttributeName, this.letterSpacing * this.nativeTextViewProtected.font.pointSize, { location: 0, length: attrText.length });
|
||||
}
|
||||
|
||||
if (this.style.lineHeight) {
|
||||
const paragraphStyle = NSMutableParagraphStyle.alloc().init();
|
||||
paragraphStyle.lineSpacing = this.lineHeight;
|
||||
// make sure a possible previously set text alignment setting is not lost when line height is specified
|
||||
paragraphStyle.alignment = (<UITextField | UITextView | UILabel>this.nativeTextViewProtected).textAlignment;
|
||||
if (this.nativeTextViewProtected instanceof UILabel) {
|
||||
// make sure a possible previously set line break mode is not lost when line height is specified
|
||||
paragraphStyle.lineBreakMode = this.nativeTextViewProtected.lineBreakMode;
|
||||
}
|
||||
attrText.addAttributeValueRange(NSParagraphStyleAttributeName, paragraphStyle, { location: 0, length: attrText.length });
|
||||
} else if (this.nativeTextViewProtected instanceof UITextView) {
|
||||
const paragraphStyle = NSMutableParagraphStyle.alloc().init();
|
||||
paragraphStyle.alignment = (<UITextView>this.nativeTextViewProtected).textAlignment;
|
||||
attrText.addAttributeValueRange(NSParagraphStyleAttributeName, paragraphStyle, { location: 0, length: attrText.length });
|
||||
}
|
||||
|
||||
if (this.nativeTextViewProtected instanceof UIButton) {
|
||||
this.nativeTextViewProtected.setAttributedTitleForState(attrText, UIControlState.Normal);
|
||||
}
|
||||
else {
|
||||
this.nativeTextViewProtected.attributedText = attrText;
|
||||
}
|
||||
}
|
||||
|
||||
setTextDecorationAndTransform() {
|
||||
const style = this.style;
|
||||
const dict = new Map<string, any>();
|
||||
switch (style.textDecoration) {
|
||||
case "none":
|
||||
break;
|
||||
case "underline":
|
||||
dict.set(NSUnderlineStyleAttributeName, NSUnderlineStyle.Single);
|
||||
break;
|
||||
case "line-through":
|
||||
dict.set(NSStrikethroughStyleAttributeName, NSUnderlineStyle.Single);
|
||||
break;
|
||||
case "underline line-through":
|
||||
dict.set(NSUnderlineStyleAttributeName, NSUnderlineStyle.Single);
|
||||
dict.set(NSStrikethroughStyleAttributeName, NSUnderlineStyle.Single);
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Invalid text decoration value: ${style.textDecoration}. Valid values are: 'none', 'underline', 'line-through', 'underline line-through'.`);
|
||||
}
|
||||
|
||||
if (style.letterSpacing !== 0) {
|
||||
dict.set(NSKernAttributeName, style.letterSpacing * this.nativeTextViewProtected.font.pointSize);
|
||||
}
|
||||
|
||||
const isTextView = this.nativeTextViewProtected instanceof UITextView;
|
||||
if (style.lineHeight) {
|
||||
const paragraphStyle = NSMutableParagraphStyle.alloc().init();
|
||||
paragraphStyle.lineSpacing = style.lineHeight;
|
||||
// make sure a possible previously set text alignment setting is not lost when line height is specified
|
||||
paragraphStyle.alignment = (<UITextField | UITextView | UILabel>this.nativeTextViewProtected).textAlignment;
|
||||
if (this.nativeTextViewProtected instanceof UILabel) {
|
||||
// make sure a possible previously set line break mode is not lost when line height is specified
|
||||
paragraphStyle.lineBreakMode = this.nativeTextViewProtected.lineBreakMode;
|
||||
}
|
||||
dict.set(NSParagraphStyleAttributeName, paragraphStyle);
|
||||
} else if (isTextView) {
|
||||
const paragraphStyle = NSMutableParagraphStyle.alloc().init();
|
||||
paragraphStyle.alignment = (<UITextView>this.nativeTextViewProtected).textAlignment;
|
||||
dict.set(NSParagraphStyleAttributeName, paragraphStyle);
|
||||
}
|
||||
|
||||
if (dict.size > 0 || isTextView) {
|
||||
if (style.color) {
|
||||
dict.set(NSForegroundColorAttributeName, style.color.ios);
|
||||
} else if (majorVersion >= 13) {
|
||||
dict.set(NSForegroundColorAttributeName, UIColor.labelColor);
|
||||
}
|
||||
}
|
||||
|
||||
const text = this.text;
|
||||
const string = (text === undefined || text === null) ? "" : text.toString();
|
||||
const source = getTransformedText(string, this.textTransform);
|
||||
if (dict.size > 0 || isTextView) {
|
||||
if (isTextView) {
|
||||
// UITextView's font seems to change inside.
|
||||
dict.set(NSFontAttributeName, this.nativeTextViewProtected.font);
|
||||
}
|
||||
|
||||
const result = NSMutableAttributedString.alloc().initWithString(source);
|
||||
result.setAttributesRange(<any>dict, { location: 0, length: source.length });
|
||||
if (this.nativeTextViewProtected instanceof UIButton) {
|
||||
this.nativeTextViewProtected.setAttributedTitleForState(result, UIControlState.Normal);
|
||||
} else {
|
||||
this.nativeTextViewProtected.attributedText = result;
|
||||
}
|
||||
} else {
|
||||
if (this.nativeTextViewProtected instanceof UIButton) {
|
||||
// Clear attributedText or title won't be affected.
|
||||
this.nativeTextViewProtected.setAttributedTitleForState(null, UIControlState.Normal);
|
||||
this.nativeTextViewProtected.setTitleForState(source, UIControlState.Normal);
|
||||
} else {
|
||||
// Clear attributedText or text won't be affected.
|
||||
this.nativeTextViewProtected.attributedText = undefined;
|
||||
this.nativeTextViewProtected.text = source;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
createNSMutableAttributedString(formattedString: FormattedString): NSMutableAttributedString {
|
||||
let mas = NSMutableAttributedString.alloc().init();
|
||||
if (formattedString && formattedString.parent) {
|
||||
for (let i = 0, spanStart = 0, length = formattedString.spans.length; i < length; i++) {
|
||||
const span = formattedString.spans.getItem(i);
|
||||
const text = span.text;
|
||||
const textTransform = (<TextBase>formattedString.parent).textTransform;
|
||||
let spanText = (text === null || text === undefined) ? "" : text.toString();
|
||||
if (textTransform !== "none" && textTransform !== "initial") {
|
||||
spanText = getTransformedText(spanText, textTransform);
|
||||
}
|
||||
|
||||
const nsAttributedString = this.createMutableStringForSpan(span, spanText);
|
||||
mas.insertAttributedStringAtIndex(nsAttributedString, spanStart);
|
||||
spanStart += spanText.length;
|
||||
}
|
||||
}
|
||||
|
||||
return mas;
|
||||
}
|
||||
|
||||
createMutableStringForSpan(span: Span, text: string): NSMutableAttributedString {
|
||||
const viewFont = this.nativeTextViewProtected.font;
|
||||
let attrDict = <{ key: string, value: any }>{};
|
||||
const style = span.style;
|
||||
const bold = isBold(style.fontWeight);
|
||||
const italic = style.fontStyle === "italic";
|
||||
|
||||
let fontFamily = span.fontFamily;
|
||||
let fontSize = span.fontSize;
|
||||
|
||||
if (bold || italic || fontFamily || fontSize) {
|
||||
let font = new Font(style.fontFamily, style.fontSize, style.fontStyle, style.fontWeight);
|
||||
let iosFont = font.getUIFont(viewFont);
|
||||
attrDict[NSFontAttributeName] = iosFont;
|
||||
}
|
||||
|
||||
const color = span.color;
|
||||
if (color) {
|
||||
attrDict[NSForegroundColorAttributeName] = color.ios;
|
||||
}
|
||||
|
||||
// We don't use isSet function here because defaultValue for backgroundColor is null.
|
||||
const backgroundColor = <Color>(style.backgroundColor
|
||||
|| (<FormattedString>span.parent).backgroundColor
|
||||
|| (<TextBase>(<FormattedString>span.parent).parent).backgroundColor);
|
||||
if (backgroundColor) {
|
||||
attrDict[NSBackgroundColorAttributeName] = backgroundColor.ios;
|
||||
}
|
||||
|
||||
let valueSource: typeof style;
|
||||
if (textDecorationProperty.isSet(style)) {
|
||||
valueSource = style;
|
||||
} else if (textDecorationProperty.isSet(span.parent.style)) {
|
||||
// span.parent is FormattedString
|
||||
valueSource = span.parent.style;
|
||||
} else if (textDecorationProperty.isSet(span.parent.parent.style)) {
|
||||
// span.parent.parent is TextBase
|
||||
valueSource = span.parent.parent.style;
|
||||
}
|
||||
|
||||
if (valueSource) {
|
||||
const textDecorations = valueSource.textDecoration;
|
||||
const underline = textDecorations.indexOf("underline") !== -1;
|
||||
if (underline) {
|
||||
attrDict[NSUnderlineStyleAttributeName] = underline;
|
||||
}
|
||||
|
||||
const strikethrough = textDecorations.indexOf("line-through") !== -1;
|
||||
if (strikethrough) {
|
||||
attrDict[NSStrikethroughStyleAttributeName] = strikethrough;
|
||||
}
|
||||
}
|
||||
|
||||
return NSMutableAttributedString.alloc().initWithStringAttributes(text, <any>attrDict);
|
||||
}
|
||||
}
|
||||
|
||||
export function getTransformedText(text: string, textTransform: TextTransform): string {
|
||||
if (!text || !isString(text)) {
|
||||
return "";
|
||||
}
|
||||
|
||||
switch (textTransform) {
|
||||
case "uppercase":
|
||||
return NSStringFromNSAttributedString(text).uppercaseString;
|
||||
case "lowercase":
|
||||
return NSStringFromNSAttributedString(text).lowercaseString;
|
||||
case "capitalize":
|
||||
return NSStringFromNSAttributedString(text).capitalizedString;
|
||||
default:
|
||||
return text;
|
||||
}
|
||||
}
|
||||
|
||||
function NSStringFromNSAttributedString(source: NSAttributedString | string): NSString {
|
||||
return NSString.stringWithString(source instanceof NSAttributedString && source.string || <string>source);
|
||||
}
|
||||
Reference in New Issue
Block a user