UNPKG

20.7 kBJavaScriptView Raw
1// Types
2import { getClosestPropertyValue } from './text-base-common';
3// Requires
4import { Font } from '../styling/font';
5import { iosAccessibilityAdjustsFontSizeProperty, iosAccessibilityMaxFontScaleProperty, iosAccessibilityMinFontScaleProperty } from '../../accessibility/accessibility-properties';
6import { TextBaseCommon, textProperty, formattedTextProperty, textAlignmentProperty, textDecorationProperty, textTransformProperty, textShadowProperty, textStrokeProperty, letterSpacingProperty, lineHeightProperty, maxLinesProperty, resetSymbol } from './text-base-common';
7import { Color } from '../../color';
8import { Span } from './span';
9import { colorProperty, fontInternalProperty, fontScaleInternalProperty, Length } from '../styling/style-properties';
10import { isString, isNullOrUndefined } from '../../utils/types';
11import { iOSNativeHelper, layout } from '../../utils';
12import { CoreTypes } from '../../core-types';
13export * from './text-base-common';
14const majorVersion = iOSNativeHelper.MajorVersion;
15var UILabelClickHandlerImpl = /** @class */ (function (_super) {
16 __extends(UILabelClickHandlerImpl, _super);
17 function UILabelClickHandlerImpl() {
18 return _super !== null && _super.apply(this, arguments) || this;
19 }
20 UILabelClickHandlerImpl.initWithOwner = function (owner) {
21 var handler = UILabelClickHandlerImpl.new();
22 handler._owner = owner;
23 return handler;
24 };
25 UILabelClickHandlerImpl.prototype.linkTap = function (tapGesture) {
26 var _a;
27 var owner = (_a = this._owner) === null || _a === void 0 ? void 0 : _a.deref();
28 if (owner) {
29 var label = owner.nativeTextViewProtected;
30 // This offset along with setting paragraph style alignment will achieve perfect horizontal alignment for NSTextContainer
31 var offsetXMultiplier = void 0;
32 switch (owner.textAlignment) {
33 case 'center':
34 offsetXMultiplier = 0.5;
35 break;
36 case 'right':
37 offsetXMultiplier = 1.0;
38 break;
39 default:
40 offsetXMultiplier = 0.0;
41 break;
42 }
43 var offsetYMultiplier = 0.5; // Text is vertically aligned to center
44 var layoutManager = NSLayoutManager.alloc().init();
45 var textContainer = NSTextContainer.alloc().initWithSize(CGSizeZero);
46 var textStorage = NSTextStorage.alloc().initWithAttributedString(owner.nativeTextViewProtected['attributedText']);
47 layoutManager.addTextContainer(textContainer);
48 textStorage.addLayoutManager(layoutManager);
49 textContainer.lineFragmentPadding = 0;
50 textContainer.lineBreakMode = label.lineBreakMode;
51 textContainer.maximumNumberOfLines = label.numberOfLines;
52 var labelSize = label.bounds.size;
53 textContainer.size = labelSize;
54 var locationOfTouchInLabel = tapGesture.locationInView(label);
55 var textBoundingBox = layoutManager.usedRectForTextContainer(textContainer);
56 var textContainerOffset = CGPointMake((labelSize.width - textBoundingBox.size.width) * offsetXMultiplier - textBoundingBox.origin.x, (labelSize.height - textBoundingBox.size.height) * offsetYMultiplier - textBoundingBox.origin.y);
57 var locationOfTouchInTextContainer = CGPointMake(locationOfTouchInLabel.x - textContainerOffset.x, locationOfTouchInLabel.y - textContainerOffset.y);
58 // Check if tap was inside text bounding rect
59 if (CGRectContainsPoint(textBoundingBox, locationOfTouchInTextContainer)) {
60 // According to Apple docs, if no glyph is under point, the nearest glyph is returned
61 var glyphIndex = layoutManager.glyphIndexForPointInTextContainerFractionOfDistanceThroughGlyph(locationOfTouchInTextContainer, textContainer, null);
62 // In order to determine whether the tap point actually lies within the bounds
63 // of the glyph returned, we call the method below and test
64 // whether the point falls in the rectangle returned by that method
65 var glyphRect = layoutManager.boundingRectForGlyphRangeInTextContainer({
66 location: glyphIndex,
67 length: 1,
68 }, textContainer);
69 // Ensure that an actual glyph was tapped
70 if (CGRectContainsPoint(glyphRect, locationOfTouchInTextContainer)) {
71 var indexOfCharacter = layoutManager.characterIndexForGlyphAtIndex(glyphIndex);
72 var span = null;
73 // Try to find the corresponding span using the spanRanges
74 for (var i = 0; i < owner._spanRanges.length; i++) {
75 var range = owner._spanRanges[i];
76 if (range.location <= indexOfCharacter && range.location + range.length > indexOfCharacter) {
77 if (owner.formattedText && owner.formattedText.spans.length > i) {
78 span = owner.formattedText.spans.getItem(i);
79 }
80 break;
81 }
82 }
83 if (span && span.tappable) {
84 // if the span is found and tappable emit the linkTap event
85 span._emit(Span.linkTapEvent);
86 }
87 }
88 }
89 }
90 };
91 UILabelClickHandlerImpl.ObjCExposedMethods = {
92 linkTap: { returns: interop.types.void, params: [interop.types.id] },
93 };
94 return UILabelClickHandlerImpl;
95}(NSObject));
96export class TextBase extends TextBaseCommon {
97 constructor() {
98 super(...arguments);
99 this._tappable = false;
100 }
101 initNativeView() {
102 super.initNativeView();
103 this._setTappableState(false);
104 }
105 _setTappableState(tappable) {
106 if (this._tappable !== tappable) {
107 this._tappable = tappable;
108 if (this._tappable) {
109 const tapHandler = UILabelClickHandlerImpl.initWithOwner(new WeakRef(this));
110 // associate handler with menuItem or it will get collected by JSC.
111 this.handler = tapHandler;
112 this._tapGestureRecognizer = UITapGestureRecognizer.alloc().initWithTargetAction(tapHandler, 'linkTap');
113 this.nativeViewProtected.userInteractionEnabled = true;
114 this.nativeViewProtected.addGestureRecognizer(this._tapGestureRecognizer);
115 }
116 else {
117 this.nativeViewProtected.userInteractionEnabled = false;
118 this.nativeViewProtected.removeGestureRecognizer(this._tapGestureRecognizer);
119 }
120 }
121 }
122 [textProperty.getDefault]() {
123 return resetSymbol;
124 }
125 [textProperty.setNative](value) {
126 const reset = value === resetSymbol;
127 if (!reset && this.formattedText) {
128 return;
129 }
130 this._setNativeText(reset);
131 this._requestLayoutOnTextChanged();
132 }
133 [formattedTextProperty.setNative](value) {
134 this._setNativeText();
135 this._setTappableState(isStringTappable(value));
136 textProperty.nativeValueChange(this, !value ? '' : value.toString());
137 this._requestLayoutOnTextChanged();
138 }
139 [colorProperty.getDefault]() {
140 const nativeView = this.nativeTextViewProtected;
141 if (nativeView instanceof UIButton) {
142 return nativeView.titleColorForState(0 /* UIControlState.Normal */);
143 }
144 else {
145 return nativeView.textColor;
146 }
147 }
148 [colorProperty.setNative](value) {
149 const color = value instanceof Color ? value.ios : value;
150 this._setColor(color);
151 }
152 [fontInternalProperty.getDefault]() {
153 let nativeView = this.nativeTextViewProtected;
154 nativeView = nativeView instanceof UIButton ? nativeView.titleLabel : nativeView;
155 return nativeView.font;
156 }
157 [fontInternalProperty.setNative](value) {
158 if (!(value instanceof Font) || !this.formattedText) {
159 let nativeView = this.nativeTextViewProtected;
160 nativeView = nativeView instanceof UIButton ? nativeView.titleLabel : nativeView;
161 nativeView.font = value instanceof Font ? value.getUIFont(nativeView.font) : value;
162 }
163 }
164 [fontScaleInternalProperty.setNative](value) {
165 const nativeView = this.nativeTextViewProtected instanceof UIButton ? this.nativeTextViewProtected.titleLabel : this.nativeTextViewProtected;
166 const font = this.style.fontInternal || Font.default.withFontSize(nativeView.font.pointSize);
167 const finalValue = adjustMinMaxFontScale(value, this);
168 // Request layout on font scale as it's not done automatically
169 if (font.fontScale !== finalValue) {
170 this.style.fontInternal = font.withFontScale(finalValue);
171 this.requestLayout();
172 }
173 else {
174 if (!this.style.fontInternal) {
175 this.style.fontInternal = font;
176 }
177 }
178 }
179 [iosAccessibilityAdjustsFontSizeProperty.setNative](value) {
180 this[fontScaleInternalProperty.setNative](this.style.fontScaleInternal);
181 }
182 [iosAccessibilityMinFontScaleProperty.setNative](value) {
183 this[fontScaleInternalProperty.setNative](this.style.fontScaleInternal);
184 }
185 [iosAccessibilityMaxFontScaleProperty.setNative](value) {
186 this[fontScaleInternalProperty.setNative](this.style.fontScaleInternal);
187 }
188 [textAlignmentProperty.setNative](value) {
189 const nativeView = this.nativeTextViewProtected;
190 switch (value) {
191 case 'initial':
192 case 'left':
193 nativeView.textAlignment = 0 /* NSTextAlignment.Left */;
194 break;
195 case 'center':
196 nativeView.textAlignment = 1 /* NSTextAlignment.Center */;
197 break;
198 case 'right':
199 nativeView.textAlignment = 2 /* NSTextAlignment.Right */;
200 break;
201 case 'justify':
202 nativeView.textAlignment = 3 /* NSTextAlignment.Justified */;
203 break;
204 }
205 }
206 [textDecorationProperty.setNative](value) {
207 this._setNativeText();
208 }
209 [textTransformProperty.setNative](value) {
210 this._setNativeText();
211 }
212 [textStrokeProperty.setNative](value) {
213 this._setNativeText();
214 }
215 [letterSpacingProperty.setNative](value) {
216 this._setNativeText();
217 }
218 [lineHeightProperty.setNative](value) {
219 this._setNativeText();
220 }
221 [textShadowProperty.setNative](value) {
222 this._setShadow(value);
223 }
224 [maxLinesProperty.setNative](value) {
225 const nativeTextViewProtected = this.nativeTextViewProtected;
226 const numberOfLines = this.whiteSpace !== CoreTypes.WhiteSpace.nowrap ? value : 1;
227 if (nativeTextViewProtected instanceof UITextView) {
228 nativeTextViewProtected.textContainer.maximumNumberOfLines = numberOfLines;
229 if (value !== 0) {
230 nativeTextViewProtected.textContainer.lineBreakMode = 4 /* NSLineBreakMode.ByTruncatingTail */;
231 }
232 else {
233 nativeTextViewProtected.textContainer.lineBreakMode = 0 /* NSLineBreakMode.ByWordWrapping */;
234 }
235 }
236 else if (nativeTextViewProtected instanceof UILabel) {
237 nativeTextViewProtected.numberOfLines = numberOfLines;
238 nativeTextViewProtected.lineBreakMode = 4 /* NSLineBreakMode.ByTruncatingTail */;
239 }
240 else if (nativeTextViewProtected instanceof UIButton) {
241 nativeTextViewProtected.titleLabel.numberOfLines = numberOfLines;
242 }
243 }
244 _setColor(color) {
245 if (this.nativeTextViewProtected instanceof UIButton) {
246 this.nativeTextViewProtected.setTitleColorForState(color, 0 /* UIControlState.Normal */);
247 this.nativeTextViewProtected.titleLabel.textColor = color;
248 }
249 else {
250 this.nativeTextViewProtected.textColor = color;
251 }
252 }
253 _animationWrap(fn) {
254 const shouldAnimate = this.iosTextAnimation === 'inherit' ? TextBase.iosTextAnimationFallback : this.iosTextAnimation;
255 if (shouldAnimate) {
256 fn();
257 }
258 else {
259 UIView.performWithoutAnimation(fn);
260 }
261 }
262 _setNativeText(reset = false) {
263 this._animationWrap(() => {
264 if (reset) {
265 const nativeView = this.nativeTextViewProtected;
266 if (nativeView instanceof UIButton) {
267 // Clear attributedText or title won't be affected.
268 nativeView.setAttributedTitleForState(null, 0 /* UIControlState.Normal */);
269 nativeView.setTitleForState(null, 0 /* UIControlState.Normal */);
270 }
271 else {
272 // Clear attributedText or text won't be affected.
273 nativeView.attributedText = null;
274 nativeView.text = null;
275 }
276 return;
277 }
278 const letterSpacing = this.style.letterSpacing ? this.style.letterSpacing : 0;
279 const lineHeight = this.style.lineHeight ? this.style.lineHeight : 0;
280 if (this.formattedText) {
281 this.nativeTextViewProtected.nativeScriptSetFormattedTextDecorationAndTransformLetterSpacingLineHeight(this.getFormattedStringDetails(this.formattedText), letterSpacing, lineHeight);
282 }
283 else {
284 // console.log('setTextDecorationAndTransform...')
285 const text = getTransformedText(isNullOrUndefined(this.text) ? '' : `${this.text}`, this.textTransform);
286 this.nativeTextViewProtected.nativeScriptSetTextDecorationAndTransformTextDecorationLetterSpacingLineHeight(text, this.style.textDecoration || '', letterSpacing, lineHeight);
287 if (!this.style?.color && majorVersion >= 13 && UIColor.labelColor) {
288 this._setColor(UIColor.labelColor);
289 }
290 }
291 if (this.style?.textStroke) {
292 this.nativeTextViewProtected.nativeScriptSetFormattedTextStrokeColor(Length.toDevicePixels(this.style.textStroke.width, 0), this.style.textStroke.color.ios);
293 }
294 });
295 }
296 createFormattedTextNative(value) {
297 return NativeScriptUtils.createMutableStringWithDetails(this.getFormattedStringDetails(value));
298 }
299 getFormattedStringDetails(formattedString) {
300 const details = {
301 spans: [],
302 };
303 this._spanRanges = [];
304 if (formattedString && formattedString.parent) {
305 for (let i = 0, spanStart = 0, length = formattedString.spans.length; i < length; i++) {
306 const span = formattedString.spans.getItem(i);
307 const text = span.text;
308 const textTransform = formattedString.parent.textTransform;
309 let spanText = isNullOrUndefined(text) ? '' : `${text}`;
310 if (textTransform !== 'none' && textTransform !== 'initial') {
311 spanText = getTransformedText(spanText, textTransform);
312 }
313 details.spans.push(this.createMutableStringDetails(span, spanText, spanStart));
314 this._spanRanges.push({
315 location: spanStart,
316 length: spanText.length,
317 });
318 spanStart += spanText.length;
319 }
320 }
321 return details;
322 }
323 createMutableStringDetails(span, text, index) {
324 const fontScale = adjustMinMaxFontScale(span.style.fontScaleInternal, span);
325 const font = new Font(span.style.fontFamily, span.style.fontSize, span.style.fontStyle, span.style.fontWeight, fontScale, span.style.fontVariationSettings);
326 const iosFont = font.getUIFont(this.nativeTextViewProtected.font);
327 const backgroundColor = (span.style.backgroundColor || span.parent.backgroundColor || span.parent.parent.backgroundColor);
328 return {
329 text,
330 iosFont,
331 color: span.color ? span.color.ios : null,
332 backgroundColor: backgroundColor ? backgroundColor.ios : null,
333 textDecoration: getClosestPropertyValue(textDecorationProperty, span),
334 baselineOffset: this.getBaselineOffset(iosFont, span.style.verticalAlignment),
335 index,
336 };
337 }
338 createMutableStringForSpan(span, text) {
339 const details = this.createMutableStringDetails(span, text);
340 return NativeScriptUtils.createMutableStringForSpanFontColorBackgroundColorTextDecorationBaselineOffset(details.text, details.iosFont, details.color, details.backgroundColor, details.textDecoration, details.baselineOffset);
341 }
342 getBaselineOffset(font, align) {
343 if (!align || ['stretch', 'baseline'].includes(align)) {
344 return 0;
345 }
346 if (align === 'top') {
347 return -this.fontSize - font.descender - font.ascender - font.leading / 2;
348 }
349 if (align === 'bottom') {
350 return font.descender + font.leading / 2;
351 }
352 if (align === 'text-top') {
353 return -this.fontSize - font.descender - font.ascender;
354 }
355 if (align === 'text-bottom') {
356 return font.descender;
357 }
358 if (align === 'middle') {
359 return (font.descender - font.ascender) / 2 - font.descender;
360 }
361 if (align === 'sup') {
362 return -this.fontSize * 0.4;
363 }
364 if (align === 'sub') {
365 return (font.descender - font.ascender) * 0.4;
366 }
367 }
368 _setShadow(value) {
369 const layer = this.nativeTextViewProtected.layer;
370 if (isNullOrUndefined(value)) {
371 // clear the text shadow
372 layer.shadowOpacity = 0;
373 layer.shadowRadius = 0;
374 layer.shadowColor = UIColor.clearColor;
375 layer.shadowOffset = CGSizeMake(0, 0);
376 return;
377 }
378 // shadow opacity is handled on the shadow's color instance
379 layer.shadowOpacity = value.color?.a ? value.color.a / 255 : 1;
380 layer.shadowColor = value.color.ios.CGColor;
381 layer.shadowRadius = layout.toDeviceIndependentPixels(Length.toDevicePixels(value.blurRadius, 0));
382 // prettier-ignore
383 layer.shadowOffset = CGSizeMake(layout.toDeviceIndependentPixels(Length.toDevicePixels(value.offsetX, 0)), layout.toDeviceIndependentPixels(Length.toDevicePixels(value.offsetY, 0)));
384 layer.masksToBounds = false;
385 // NOTE: generally should not need shouldRasterize
386 // however for various detailed animation work which involves text-shadow applicable layers, we may want to give users the control of enabling this with text-shadow
387 // if (!(this.nativeTextViewProtected instanceof UITextView)) {
388 // layer.shouldRasterize = true;
389 // }
390 }
391}
392export function getTransformedText(text, textTransform) {
393 if (!text || !isString(text)) {
394 return '';
395 }
396 switch (textTransform) {
397 case 'uppercase':
398 return NSStringFromNSAttributedString(text).uppercaseString;
399 case 'lowercase':
400 return NSStringFromNSAttributedString(text).lowercaseString;
401 case 'capitalize':
402 return NSStringFromNSAttributedString(text).capitalizedString;
403 default:
404 return text;
405 }
406}
407function NSStringFromNSAttributedString(source) {
408 return NSString.stringWithString((source instanceof NSAttributedString && source.string) || source);
409}
410function isStringTappable(formattedString) {
411 if (!formattedString) {
412 return false;
413 }
414 for (let i = 0, length = formattedString.spans.length; i < length; i++) {
415 const span = formattedString.spans.getItem(i);
416 if (span.tappable) {
417 return true;
418 }
419 }
420 return false;
421}
422function adjustMinMaxFontScale(value, view) {
423 let finalValue;
424 if (view.iosAccessibilityAdjustsFontSize) {
425 finalValue = value;
426 if (view.iosAccessibilityMinFontScale && view.iosAccessibilityMinFontScale > value) {
427 finalValue = view.iosAccessibilityMinFontScale;
428 }
429 if (view.iosAccessibilityMaxFontScale && view.iosAccessibilityMaxFontScale < value) {
430 finalValue = view.iosAccessibilityMaxFontScale;
431 }
432 }
433 else {
434 finalValue = 1.0;
435 }
436 return finalValue;
437}
438//# sourceMappingURL=index.ios.js.map
\No newline at end of file