mirror of
https://github.com/NativeScript/NativeScript.git
synced 2025-08-15 19:26:42 +08:00

* 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
336 lines
14 KiB
TypeScript
336 lines
14 KiB
TypeScript
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);
|
|
}
|