uicore-ts
Version:
UICore is a library to build native-like user interfaces using pure Typescript. No HTML is needed at all. Components are described as TS classes and all user interactions are handled explicitly. This library is strongly inspired by the UIKit framework tha
951 lines (739 loc) • 32.4 kB
text/typescript
import { UIColor } from "./UIColor"
import { UILocalizedTextObject } from "./UIInterfaces"
import { EXTEND, FIRST, IS_LIKE_NULL, nil, NO, UIObject, ValueOf, YES } from "./UIObject"
import { UIRectangle } from "./UIRectangle"
import { TextMeasurementStyle, UITextMeasurement } from "./UITextMeasurement"
import { UIView, UIViewBroadcastEvent } from "./UIView"
export class UITextView extends UIView {
//#region Static Properties
static defaultTextColor = UIColor.blackColor
static notificationTextColor = UIColor.redColor
static attentionRequiredColor = new UIColor("#f59e0b")
/**
* Override this to customise the attention indicator HTML that is appended
* to a label's text when `attentionRequired` is `YES`.
*
* The default renders an amber `●` dot:
* ```
* UITextView.renderAttentionIndicator = () =>
* `<span style="color: #f59e0b; margin-left: 4px;">●</span>`
* ```
* Call sites never need to change — only this one function needs to be
* replaced at app startup to restyle every attention indicator globally.
*/
static attentionIndicatorHTMLString: () => string = () =>
"<span style=\"color: " + UITextView.attentionRequiredColor.stringValue + "; margin-left: 4px;\">●</span>"
attentionIndicatorHTMLString: () => string = UITextView.attentionIndicatorHTMLString
// Global caches for all UILabels
static _intrinsicHeightCache: { [x: string]: { [x: string]: number; }; } & UIObject = new UIObject() as any
static _intrinsicWidthCache: { [x: string]: { [x: string]: number; }; } & UIObject = new UIObject() as any
static _ptToPx: number
static _pxToPt: number
static type = {
"paragraph": "p",
"header1": "h1",
"header2": "h2",
"header3": "h3",
"header4": "h4",
"header5": "h5",
"header6": "h6",
"textArea": "textarea",
"textField": "input",
"span": "span",
"label": "label"
} as const
static textAlignment = {
"left": "left",
"center": "center",
"right": "right",
"justify": "justify"
} as const
//#endregion
//#region Constructor
constructor(
elementID?: string,
textViewType: string | ValueOf<typeof UITextView.type> = UITextView.type.paragraph,
viewHTMLElement = null
) {
// Create inner text element as a UIView
const innerElementID = elementID ? `${elementID}_textElement` : undefined
const _textElementView = new UIView(innerElementID, null, textViewType)
// Create outer container (wrapper) - this is the main viewHTMLElement
super(elementID, viewHTMLElement, "span", { _textElementView })
// Configure outer container for vertical centering using direct property access
this.configureWithObject({
// @ts-ignore
viewHTMLElement: {
style: {
display: "flex",
alignItems: "center", // Vertical centering
overflow: "hidden"
}
}
})
this.text = ""
this._textElementView = _textElementView
// Configure inner text element for ellipsis and positioning
this._textElementView.configureWithObject({
style: {
position: "relative",
overflow: "hidden",
textOverflow: "ellipsis",
width: "100%",
margin: "0",
padding: "0"
},
// Forward control events from text element to the container
sendControlEventForKey: EXTEND(this.sendControlEventForKey.bind(this))
})
// Add text element as a subview
this.addSubview(this._textElementView)
this.isSingleLine = YES
this.textColor = this.textColor
this.userInteractionEnabled = YES
if (textViewType == UITextView.type.textArea) {
this.pausesPointerEvents = YES
this.addTargetForControlEvent(
UIView.controlEvent.PointerUpInside,
(sender, event) => sender.focus()
)
}
}
//#endregion
//#region Text Element View Property
private _textElementView: UIView
/**
* The inner text element that holds the actual text content
*/
get textElementView(): UIView {
return this._textElementView
}
/**
* Override style to apply to the text element instead of the container
*/
// override get style() {
// return this._textElementView.style
// }
//
// /**
// * Override computedStyle to get computed styles from the text element
// */
// override get computedStyle() {
// return this._textElementView.computedStyle
// }
/**
* Access the outer container's style (for positioning, layout, etc.)
*/
get containerStyle() {
return this.viewHTMLElement.style
}
/**
* Override styleClasses to apply to the text element
*/
override get styleClasses() {
return this._textElementView.styleClasses
}
override set styleClasses(styleClasses: string[]) {
this._textElementView.styleClasses = styleClasses
}
//#endregion
//#region Lifecycle Methods
override didReceiveBroadcastEvent(event: UIViewBroadcastEvent) {
super.didReceiveBroadcastEvent(event)
}
override willMoveToSuperview(superview: UIView) {
super.willMoveToSuperview(superview)
}
override documentFontsDidLoad() {
super.documentFontsDidLoad()
this._invalidateFontCache()
this.invalidateMeasurementStrategy()
this._intrinsicHeightCache = new UIObject() as any
this._intrinsicWidthCache = new UIObject() as any
UITextView._intrinsicHeightCache = new UIObject() as any
UITextView._intrinsicWidthCache = new UIObject() as any
}
override layoutSubviews() {
super.layoutSubviews()
if (this._automaticFontSizeSelection) {
this.fontSize = UITextView.automaticallyCalculatedFontSize(
new UIRectangle(
0,
0,
this.textElementView.viewHTMLElement.offsetHeight,
this.textElementView.viewHTMLElement.offsetWidth
),
this.intrinsicContentSize(),
this.fontSize,
this._minFontSize,
this._maxFontSize
)
}
}
//#endregion
//#region Measurement & Sizing - Private Methods
private _invalidateMeasurementStyles(): void {
this._cachedMeasurementStyles = undefined
UITextMeasurement.invalidateElement(this.textElementView.viewHTMLElement)
this._intrinsicSizesCache = {}
}
private _getMeasurementStyles(): TextMeasurementStyle | null {
if (this._cachedMeasurementStyles) {
return this._cachedMeasurementStyles
}
// Ensure element is in document
if (!this.textElementView.viewHTMLElement.isConnected) {
return null
}
// Force a layout flush ONCE to ensure computed styles are available
// This is only paid once per style change, then we use cached values
this.textElementView.viewHTMLElement.offsetHeight
const computed = window.getComputedStyle(this.textElementView.viewHTMLElement)
const fontSizeStr = computed.fontSize
const fontSize = parseFloat(fontSizeStr)
if (!fontSize || isNaN(fontSize)) {
return null
}
const lineHeight = this._parseLineHeight(computed.lineHeight, fontSize)
if (isNaN(lineHeight)) {
return null
}
const font = [
computed.fontStyle || "normal",
computed.fontVariant || "normal",
computed.fontWeight || "normal",
fontSize + "px",
computed.fontFamily || "sans-serif"
].join(" ")
this._cachedMeasurementStyles = {
font: font,
fontSize: fontSize,
lineHeight: lineHeight,
whiteSpace: computed.whiteSpace || "normal",
paddingLeft: parseFloat(computed.paddingLeft) || 0,
paddingRight: parseFloat(computed.paddingRight) || 0,
paddingTop: parseFloat(computed.paddingTop) || 0,
paddingBottom: parseFloat(computed.paddingBottom) || 0,
letterSpacing: parseFloat(computed.letterSpacing) || 0,
textTransform: computed.textTransform || "none"
}
return this._cachedMeasurementStyles
}
private _parseLineHeight(lineHeight: string, fontSize: number): number {
if (lineHeight === "normal") {
return fontSize * 1.2
}
if (lineHeight.endsWith("px")) {
return parseFloat(lineHeight)
}
const numericLineHeight = parseFloat(lineHeight)
if (!isNaN(numericLineHeight)) {
return fontSize * numericLineHeight
}
return fontSize * 1.2
}
private _shouldUseFastMeasurement(): boolean {
try {
const content = this.text || this.textElementView.innerHTML
if (this._innerHTMLKey || this._localizedTextObject) {
return NO
}
if (this.notificationAmount > 0) {
return NO
}
if (this._attentionRequired) {
return NO
}
const hasComplexHTML = /<(?!\/?(b|i|em|strong|span)\b)[^>]+>/i.test(content)
if (hasComplexHTML) {
return NO
}
// Canvas measureText silently falls back to the system font when the
// custom font hasn't been loaded into the canvas font system yet, even
// if getComputedStyle already reports the correct font family. Guard
// against this by checking the font is confirmed available before
// trusting canvas-based measurement.
const styles = this._getMeasurementStyles()
if (!styles?.font) {
return YES
}
const documentHasFont = document.fonts.check(styles.font)
// noinspection RedundantIfStatementJS
if (!documentHasFont) {
return NO
}
return YES
}
catch (exception) {
console.error(exception)
return NO
}
}
//#endregion
//#region Measurement & Sizing - Public Methods
setUseFastMeasurement(useFast: boolean): void {
this._useFastMeasurement = useFast
this._intrinsicSizesCache = {}
}
invalidateMeasurementStrategy(): void {
this._useFastMeasurement = undefined
this._invalidateMeasurementStyles()
}
//#endregion
//#region Getters & Setters - Text Alignment
get textAlignment() {
return this._textElementView.style.textAlign as ValueOf<typeof UITextView.textAlignment>
}
set textAlignment(textAlignment: ValueOf<typeof UITextView.textAlignment>) {
this._textAlignment = textAlignment
this._textElementView.style.textAlign = textAlignment
}
//#endregion
//#region Getters & Setters - Text Color
get textColor() {
return this._textColor
}
set textColor(color: UIColor) {
this._textColor = color || UITextView.defaultTextColor
this._textElementView.style.color = this._textColor.stringValue
}
//#endregion
//#region Getters & Setters - Single Line
get isSingleLine() {
return this._isSingleLine
}
set isSingleLine(isSingleLine: boolean) {
this._isSingleLine = isSingleLine
this._intrinsicHeightCache = new UIObject() as any
this._intrinsicWidthCache = new UIObject() as any
if (isSingleLine) {
// Single line: use nowrap with ellipsis
this._textElementView.style.whiteSpace = "nowrap"
this._textElementView.style.textOverflow = "ellipsis"
this._textElementView.style.display = ""
this._textElementView.style.webkitLineClamp = ""
this._textElementView.style.webkitBoxOrient = ""
return
}
// Multiline: allow wrapping, but still show ellipsis if content overflows the container
// This uses the -webkit-line-clamp approach which works for multiline ellipsis
this._textElementView.style.whiteSpace = "normal"
this._textElementView.style.textOverflow = "ellipsis"
this._textElementView.style.display = "-webkit-box"
this._textElementView.style.webkitBoxOrient = "vertical"
// Don't set line-clamp to a specific number - let it fill available space
// The overflow: hidden from the constructor will clip content that exceeds the height
this.invalidateMeasurementStrategy()
}
//#endregion
//#region Getters & Setters - Notification Amount
get notificationAmount() {
return this._notificationAmount
}
set notificationAmount(notificationAmount: number) {
if (this._notificationAmount == notificationAmount) {
return
}
this._notificationAmount = notificationAmount
this.text = this.text
this.setNeedsLayoutUpToRootView()
this.notificationAmountDidChange(notificationAmount)
}
notificationAmountDidChange(notificationAmount: number) {
}
//#endregion
//#region Getters & Setters - Attention Required
get attentionRequired() {
return this._attentionRequired
}
set attentionRequired(attentionRequired: boolean) {
if (this._attentionRequired == attentionRequired) {
return
}
this._attentionRequired = attentionRequired
this.text = this.text
this.setNeedsLayoutUpToRootView()
}
//#endregion
//#region Getters & Setters - Text Content
get text() {
return (this._text || this.textElementView.viewHTMLElement.innerHTML)
}
set text(text) {
this._text = text
var notificationText = ""
if (this.notificationAmount) {
notificationText = "<span style=\"color: " + UITextView.notificationTextColor.stringValue + ";\">" +
(" (" + this.notificationAmount + ")").bold() + "</span>"
}
var attentionDot = ""
if (this._attentionRequired) {
attentionDot = this.attentionIndicatorHTMLString()
}
const displayText = this.thousandsSeparator !== null
? UITextView.applyThousandsSeparatorToNumericalString(text, this.thousandsSeparator)
: text
const newInnerHTML = this.textPrefix + FIRST(displayText, "") + this.textSuffix + notificationText + attentionDot
if (this.textElementView.viewHTMLElement.innerHTML !== newInnerHTML) {
this.textElementView.viewHTMLElement.innerHTML = newInnerHTML
if (this.changesOften) {
this._intrinsicHeightCache = new UIObject() as any
this._intrinsicWidthCache = new UIObject() as any
}
this._useFastMeasurement = undefined
this._intrinsicSizesCache = {}
this.invalidateMeasurementStrategy()
this._invalidateMeasurementStyles()
this.clearIntrinsicSizeCache()
this.setNeedsLayout()
}
}
/**
* Formats a raw number string by inserting `separator` every three digits
* in the integer part. Handles negative numbers and decimals (machine locale
* "." as decimal point). Non-numeric strings are returned unchanged.
*/
static applyThousandsSeparatorToNumericalString(value: string, separator: string): string {
const trimmed = (value || "").trim()
if (trimmed === "") {
return value
}
// Split on the decimal point (machine locale uses ".")
const parts = trimmed.split(".")
const integerPart = parts[0]
const decimalPart = parts.length > 1 ? parts[1] : null
// Only format if the integer part consists solely of digits (optionally
// prefixed with a minus sign). Non-numeric strings pass through as-is.
if (!/^-?\d+$/.test(integerPart)) {
return value
}
const isNegative = integerPart.startsWith("-")
const digits = isNegative ? integerPart.slice(1) : integerPart
let formatted = ""
const offset = digits.length % 3
for (let index = 0; index < digits.length; index++) {
if (index > 0 && (index - offset) % 3 === 0) {
formatted += separator
}
formatted += digits[index]
}
const result = (isNegative ? "-" : "") + formatted
return decimalPart !== null ? result + "." + decimalPart : result
}
override set innerHTML(innerHTML: string) {
this.text = innerHTML
this.invalidateMeasurementStrategy()
}
override get innerHTML() {
return this.viewHTMLElement.innerHTML
}
setText(key: string, defaultString: string, parameters?: { [x: string]: string | UILocalizedTextObject }) {
this.textElementView.setInnerHTML(key, defaultString, parameters)
this.invalidateMeasurementStrategy()
}
//#endregion
//#region Getters & Setters - Font Size
get fontSize() {
const style = this._textElementView.style.fontSize || window.getComputedStyle(this._textElementView.viewHTMLElement, null).fontSize
const result = (parseFloat(style) * UITextView._pxToPt)
return result
}
set fontSize(fontSize: number) {
if (fontSize != this.fontSize) {
this._textElementView.style.fontSize = "" + fontSize + "pt"
this._intrinsicHeightCache = new UIObject() as any
this._intrinsicWidthCache = new UIObject() as any
this._invalidateFontCache()
this._invalidateMeasurementStyles()
this.clearIntrinsicSizeCache()
}
}
useAutomaticFontSize(minFontSize: number = nil, maxFontSize: number = nil) {
this._automaticFontSizeSelection = YES
this._minFontSize = minFontSize
this._maxFontSize = maxFontSize
this.setNeedsLayout()
}
//#endregion
//#region Font Caching - Private Methods
/**
* Get a stable cache key for the font without triggering reflow.
* Only computes font on first access or when font properties change.
*/
private _getFontCacheKey(): string {
// Check if font-related properties have changed
const currentTriggers = {
fontSize: this._textElementView.style.fontSize || "",
fontFamily: this._textElementView.style.fontFamily || "",
fontWeight: this._textElementView.style.fontWeight || "",
fontStyle: this._textElementView.style.fontStyle || "",
styleClasses: this.styleClasses.join(",")
}
const hasChanged =
currentTriggers.fontSize !== this._fontInvalidationTriggers.fontSize ||
currentTriggers.fontFamily !== this._fontInvalidationTriggers.fontFamily ||
currentTriggers.fontWeight !== this._fontInvalidationTriggers.fontWeight ||
currentTriggers.fontStyle !== this._fontInvalidationTriggers.fontStyle ||
currentTriggers.styleClasses !== this._fontInvalidationTriggers.styleClasses
if (!this._cachedFontKey || hasChanged) {
// Only access computedStyle when we know something changed
const computed = this._textElementView.computedStyle
this._cachedFontKey = [
computed.fontStyle,
computed.fontVariant,
computed.fontWeight,
computed.fontSize,
computed.fontFamily
].join("_").replace(/[.\s]/g, "_")
this._fontInvalidationTriggers = currentTriggers
}
return this._cachedFontKey
}
/**
* Invalidate font cache when font properties change
*/
private _invalidateFontCache(): void {
this._cachedFontKey = undefined
}
//#endregion
//#region Static Methods
static _determinePXAndPTRatios() {
if (UITextView._ptToPx) {
return
}
const o = document.createElement("div")
o.style.width = "1000pt"
document.body.appendChild(o)
UITextView._ptToPx = o.clientWidth / 1000
document.body.removeChild(o)
UITextView._pxToPt = 1 / UITextView._ptToPx
}
static automaticallyCalculatedFontSize(
bounds: UIRectangle,
currentSize: UIRectangle,
currentFontSize: number,
minFontSize?: number,
maxFontSize?: number
) {
minFontSize = FIRST(minFontSize, 1)
maxFontSize = FIRST(maxFontSize, 100000000000)
const heightMultiplier = bounds.height / (currentSize.height + 1)
const widthMultiplier = bounds.width / (currentSize.width + 1)
var multiplier = heightMultiplier
if (heightMultiplier > widthMultiplier) {
multiplier = widthMultiplier
}
const maxFittingFontSize = currentFontSize * multiplier
if (maxFittingFontSize > maxFontSize) {
return maxFontSize
}
if (minFontSize > maxFittingFontSize) {
return minFontSize
}
return maxFittingFontSize
}
//#endregion
//#region Instance Properties - Text Content
_text?: string
textPrefix = ""
textSuffix = ""
_notificationAmount = 0
_attentionRequired = NO
_thousandsSeparator: string | null = null
get thousandsSeparator(): string | null {
return this._thousandsSeparator
}
set thousandsSeparator(value: string | null) {
this._thousandsSeparator = value
}
//#endregion
//#region Instance Properties - Styling
_textColor: UIColor = UITextView.defaultTextColor
_textAlignment?: ValueOf<typeof UITextView.textAlignment>
_isSingleLine = YES
//#endregion
//#region Instance Properties - Font & Sizing
_minFontSize?: number
_maxFontSize?: number
_automaticFontSizeSelection = NO
// Cache for the computed font string
private _cachedFontKey?: string
private _fontInvalidationTriggers = {
fontSize: "",
fontFamily: "",
fontWeight: "",
fontStyle: "",
styleClasses: ""
}
//#endregion
//#region Instance Properties - Caching & Performance
changesOften = NO
// Local cache for this instance if the label changes often
_intrinsicHeightCache: { [x: string]: { [x: string]: number; }; } & UIObject = new UIObject() as any
_intrinsicWidthCache: { [x: string]: { [x: string]: number; }; } & UIObject = new UIObject() as any
private _useFastMeasurement: boolean | undefined
private _cachedMeasurementStyles: TextMeasurementStyle | undefined | null
override usesVirtualLayoutingForIntrinsicSizing = NO
//#endregion
// Override addStyleClass to invalidate font cache
override addStyleClass(styleClass: string) {
super.addStyleClass(styleClass)
this._invalidateFontCache()
}
// Override removeStyleClass to invalidate font cache
override removeStyleClass(styleClass: string) {
super.removeStyleClass(styleClass)
this._invalidateFontCache()
}
// Override focus to focus the text element
override focus() {
this._textElementView.focus()
}
// Override blur to blur the text element
override blur() {
this._textElementView.blur()
}
override intrinsicContentHeight(constrainingWidth = 0) {
constrainingWidth = Math.max(constrainingWidth.integerValue, 0)
const keyPath = ((this.textElementView.viewHTMLElement.innerHTML || this.text) + "_csf_" + this._getFontCacheKey()) + "." +
("" + constrainingWidth).replace(new RegExp("\\.", "g"), "_")
let cacheObject = UITextView._intrinsicHeightCache
if (this.changesOften) {
cacheObject = this._intrinsicHeightCache
}
var result = cacheObject.valueForKeyPath(keyPath)
if (IS_LIKE_NULL(result)) {
// Determine if we should use fast measurement
const shouldUseFastPath = this._useFastMeasurement ?? this._shouldUseFastMeasurement()
if (shouldUseFastPath) {
// Fast path: use UITextMeasurement with pre-extracted styles
const styles = this._getMeasurementStyles()
// If styles are invalid (element not properly initialized), fall back to DOM
if (styles) {
const size = UITextMeasurement.calculateTextSize(
this.textElementView.viewHTMLElement,
((this.text || this.textElementView.innerHTML || "") + ""),
constrainingWidth || undefined,
undefined,
styles
)
result = size.height
}
else {
// Styles not ready, use DOM measurement
result = super.intrinsicContentHeight(constrainingWidth)
}
}
else {
// Fallback: DOM-based measurement for complex content
result = super.intrinsicContentHeight(constrainingWidth)
}
cacheObject.setValueForKeyPath(keyPath, result)
}
if (isNaN(result) || (!result && !this.text)) {
result = super.intrinsicContentHeight(constrainingWidth)
cacheObject.setValueForKeyPath(keyPath, result)
}
return result
}
override intrinsicContentWidth(constrainingHeight = 0) {
constrainingHeight = Math.max(constrainingHeight.integerValue, 0)
const keyPath = ((this.textElementView.viewHTMLElement.innerHTML || this.text) + "_csf_" + this._getFontCacheKey()) + "." +
("" + constrainingHeight).replace(new RegExp("\\.", "g"), "_")
let cacheObject = UITextView._intrinsicWidthCache
if (this.changesOften) {
cacheObject = this._intrinsicWidthCache
}
var result = cacheObject.valueForKeyPath(keyPath)
if (IS_LIKE_NULL(result)) {
// Determine if we should use fast measurement
const shouldUseFastPath = this._useFastMeasurement ?? this._shouldUseFastMeasurement()
if (shouldUseFastPath) {
// Fast path: use UITextMeasurement with pre-extracted styles
const styles = this._getMeasurementStyles()
// If styles are invalid (element not properly initialized), fall back to DOM
if (styles) {
const size = UITextMeasurement.calculateTextSize(
this.textElementView.viewHTMLElement,
((this.text || this.textElementView.innerHTML || "") + "").replace(/<br\s*\/?>/gi, "\n"),
undefined,
constrainingHeight || undefined,
styles
)
result = size.width
}
else {
// Styles not ready, use DOM measurement
result = super.intrinsicContentWidth(constrainingHeight)
}
}
else {
// Fallback: DOM-based measurement for complex content
result = super.intrinsicContentWidth(constrainingHeight)
}
cacheObject.setValueForKeyPath(keyPath, result)
}
return result
}
override intrinsicContentSizeWithConstraints(constrainingHeight: number = 0, constrainingWidth: number = 0) {
const cacheKey = this._getIntrinsicSizeCacheKey(constrainingHeight, constrainingWidth)
const cachedResult = this._getCachedIntrinsicSize(cacheKey)
if (cachedResult) {
return cachedResult
}
// UITextView needs to measure the text element, not the outer container
const result = new UIRectangle(0, 0, 0, 0)
if (this.rootView.forceIntrinsicSizeZero) {
return result
}
let temporarilyInViewTree = NO
let nodeAboveThisView: Node | null = null
if (!this.isMemberOfViewTree) {
document.body.appendChild(this.viewHTMLElement)
temporarilyInViewTree = YES
nodeAboveThisView = this.viewHTMLElement.nextSibling
}
// Save and clear styles on the TEXT ELEMENT (not the container)
const height = this._textElementView.style.height
const width = this._textElementView.style.width
this._textElementView.style.height = "" + constrainingHeight + "px"
this._textElementView.style.width = "" + constrainingWidth + "px"
const left = this._textElementView.style.left
const right = this._textElementView.style.right
const bottom = this._textElementView.style.bottom
const top = this._textElementView.style.top
this._textElementView.style.left = ""
this._textElementView.style.right = ""
this._textElementView.style.bottom = ""
this._textElementView.style.top = ""
// Measure height with the text element
const resultHeight = this._textElementView.viewHTMLElement.scrollHeight
// Measure width by temporarily setting nowrap
const whiteSpace = this._textElementView.style.whiteSpace
this._textElementView.style.whiteSpace = "nowrap"
const resultWidth = this._textElementView.viewHTMLElement.scrollWidth
this._textElementView.style.whiteSpace = whiteSpace
// Restore styles on the TEXT ELEMENT
this._textElementView.style.height = height
this._textElementView.style.width = width
this._textElementView.style.left = left
this._textElementView.style.right = right
this._textElementView.style.bottom = bottom
this._textElementView.style.top = top
if (temporarilyInViewTree) {
document.body.removeChild(this.viewHTMLElement)
if (this.superview) {
if (nodeAboveThisView) {
this.superview.viewHTMLElement.insertBefore(this.viewHTMLElement, nodeAboveThisView)
}
else {
this.superview.viewHTMLElement.appendChild(this.viewHTMLElement)
}
}
}
result.height = resultHeight
result.width = resultWidth
this._setCachedIntrinsicSize(cacheKey, result)
return result
}
}