uicore-ts 1.1.57 → 1.1.59

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,9 +1,29 @@
1
+ export interface TextMeasurementStyle {
2
+ font: string;
3
+ fontSize: number;
4
+ lineHeight: number;
5
+ whiteSpace: string;
6
+ paddingLeft: number;
7
+ paddingRight: number;
8
+ paddingTop: number;
9
+ paddingBottom: number;
10
+ }
1
11
  export declare class UITextMeasurement {
2
12
  private static canvas;
3
13
  private static context;
4
- private static fontCache;
5
- private static lineHeightCache;
14
+ private static globalStyleCache;
15
+ private static elementToCacheKey;
6
16
  private static measurementElement;
17
+ /**
18
+ * Generate a cache key based only on styles that affect text measurement
19
+ * Ignores position, color, transform, etc.
20
+ */
21
+ private static generateStyleCacheKey;
22
+ /**
23
+ * Extract cache key from element's classList
24
+ * Elements with same classes likely have same text measurement styles
25
+ */
26
+ private static getSemanticCacheKey;
7
27
  /**
8
28
  * Get or create the canvas context for text measurement
9
29
  */
@@ -25,13 +45,14 @@ export declare class UITextMeasurement {
25
45
  */
26
46
  private static measureWithDOM;
27
47
  /**
28
- * Extract font properties from computed style and create font string
48
+ * Get or extract styles from element (with smart global caching)
49
+ * Returns cached styles if available, otherwise computes once and caches globally
29
50
  */
30
- private static getFontString;
51
+ private static getElementStyles;
31
52
  /**
32
- * Get line height in pixels
53
+ * Parse line height from computed style
33
54
  */
34
- private static getLineHeight;
55
+ private static parseLineHeight;
35
56
  /**
36
57
  * Measure text width using Canvas API
37
58
  */
@@ -46,7 +67,7 @@ export declare class UITextMeasurement {
46
67
  * Calculate intrinsic content size for text - SMART METHOD
47
68
  * Automatically chooses the most efficient measurement technique
48
69
  */
49
- static calculateTextSize(element: HTMLElement, content: string, constrainingWidth?: number, constrainingHeight?: number): {
70
+ static calculateTextSize(element: HTMLElement, content: string, constrainingWidth?: number, constrainingHeight?: number, providedStyles?: TextMeasurementStyle): {
50
71
  width: number;
51
72
  height: number;
52
73
  };
@@ -58,8 +79,28 @@ export declare class UITextMeasurement {
58
79
  * Clear all caches (call when fonts change or for cleanup)
59
80
  */
60
81
  static clearCaches(): void;
82
+ /**
83
+ * Invalidate cached styles for a specific element
84
+ */
85
+ static invalidateElement(element: HTMLElement): void;
86
+ /**
87
+ * Invalidate cache for elements with specific class
88
+ * Useful when you change a CSS class definition
89
+ */
90
+ static invalidateClass(className: string): void;
91
+ /**
92
+ * Pre-warm the cache by measuring a representative element
93
+ * Useful at app startup to avoid first-paint delays
94
+ */
95
+ static prewarmCache(elements: HTMLElement[]): void;
61
96
  /**
62
97
  * Clean up measurement element (call on app cleanup)
63
98
  */
64
99
  static cleanup(): void;
65
100
  }
101
+ export interface UITextViewMeasurementMethods {
102
+ intrinsicContentSizeEfficient(constrainingWidth?: number, constrainingHeight?: number): {
103
+ width: number;
104
+ height: number;
105
+ };
106
+ }
@@ -22,6 +22,49 @@ __export(UITextMeasurement_exports, {
22
22
  });
23
23
  module.exports = __toCommonJS(UITextMeasurement_exports);
24
24
  class UITextMeasurement {
25
+ static generateStyleCacheKey(computed) {
26
+ const relevantProps = [
27
+ computed.fontFamily,
28
+ computed.fontSize,
29
+ computed.fontWeight,
30
+ computed.fontStyle,
31
+ computed.fontVariant,
32
+ computed.lineHeight,
33
+ computed.letterSpacing,
34
+ computed.wordSpacing,
35
+ computed.textTransform,
36
+ computed.whiteSpace,
37
+ computed.wordBreak,
38
+ computed.wordWrap,
39
+ computed.paddingLeft,
40
+ computed.paddingRight,
41
+ computed.paddingTop,
42
+ computed.paddingBottom,
43
+ computed.borderLeftWidth,
44
+ computed.borderRightWidth,
45
+ computed.borderTopWidth,
46
+ computed.borderBottomWidth,
47
+ computed.boxSizing
48
+ ];
49
+ return relevantProps.join("|");
50
+ }
51
+ static getSemanticCacheKey(element) {
52
+ const existingKey = this.elementToCacheKey.get(element);
53
+ if (existingKey) {
54
+ return existingKey;
55
+ }
56
+ const classList = Array.from(element.classList).sort().join(" ");
57
+ const tagName = element.tagName.toLowerCase();
58
+ const semanticKey = `${tagName}::${classList}`;
59
+ if (this.globalStyleCache.has(semanticKey)) {
60
+ this.elementToCacheKey.set(element, semanticKey);
61
+ return semanticKey;
62
+ }
63
+ const computed = window.getComputedStyle(element);
64
+ const styleCacheKey = this.generateStyleCacheKey(computed);
65
+ this.elementToCacheKey.set(element, styleCacheKey);
66
+ return styleCacheKey;
67
+ }
25
68
  static getContext() {
26
69
  if (!this.context) {
27
70
  this.canvas = document.createElement("canvas");
@@ -52,17 +95,13 @@ class UITextMeasurement {
52
95
  }
53
96
  return this.measurementElement;
54
97
  }
55
- static measureWithDOM(element, content, constrainingWidth, constrainingHeight) {
98
+ static measureWithDOM(element, content, constrainingWidth, constrainingHeight, providedStyles) {
56
99
  const measureEl = this.getMeasurementElement();
57
- const style = window.getComputedStyle(element);
58
- measureEl.style.font = this.getFontString(element);
59
- measureEl.style.lineHeight = style.lineHeight;
60
- measureEl.style.whiteSpace = style.whiteSpace;
61
- measureEl.style.wordBreak = style.wordBreak;
62
- measureEl.style.wordWrap = style.wordWrap;
63
- measureEl.style.padding = style.padding;
64
- measureEl.style.border = style.border;
65
- measureEl.style.boxSizing = style.boxSizing;
100
+ const styles = this.getElementStyles(element, providedStyles);
101
+ measureEl.style.font = styles.font;
102
+ measureEl.style.lineHeight = styles.lineHeight + "px";
103
+ measureEl.style.whiteSpace = styles.whiteSpace;
104
+ measureEl.style.padding = `${styles.paddingTop}px ${styles.paddingRight}px ${styles.paddingBottom}px ${styles.paddingLeft}px`;
66
105
  if (constrainingWidth) {
67
106
  measureEl.style.width = constrainingWidth + "px";
68
107
  measureEl.style.maxWidth = constrainingWidth + "px";
@@ -88,25 +127,37 @@ class UITextMeasurement {
88
127
  };
89
128
  return result;
90
129
  }
91
- static getFontString(element) {
92
- const cacheKey = element.className + element.id;
93
- if (this.fontCache.has(cacheKey)) {
94
- return this.fontCache.get(cacheKey);
130
+ static getElementStyles(element, providedStyles) {
131
+ if (providedStyles) {
132
+ return providedStyles;
133
+ }
134
+ const cacheKey = this.getSemanticCacheKey(element);
135
+ const cached = this.globalStyleCache.get(cacheKey);
136
+ if (cached) {
137
+ return cached;
95
138
  }
96
- const style = window.getComputedStyle(element);
97
- const font = [
98
- style.fontStyle,
99
- style.fontVariant,
100
- style.fontWeight,
101
- style.fontSize,
102
- style.fontFamily
103
- ].join(" ");
104
- this.fontCache.set(cacheKey, font);
105
- return font;
139
+ const computed = window.getComputedStyle(element);
140
+ const fontSize = parseFloat(computed.fontSize);
141
+ const styles = {
142
+ font: [
143
+ computed.fontStyle,
144
+ computed.fontVariant,
145
+ computed.fontWeight,
146
+ computed.fontSize,
147
+ computed.fontFamily
148
+ ].join(" "),
149
+ fontSize,
150
+ lineHeight: this.parseLineHeight(computed.lineHeight, fontSize),
151
+ whiteSpace: computed.whiteSpace,
152
+ paddingLeft: parseFloat(computed.paddingLeft) || 0,
153
+ paddingRight: parseFloat(computed.paddingRight) || 0,
154
+ paddingTop: parseFloat(computed.paddingTop) || 0,
155
+ paddingBottom: parseFloat(computed.paddingBottom) || 0
156
+ };
157
+ this.globalStyleCache.set(cacheKey, styles);
158
+ return styles;
106
159
  }
107
- static getLineHeight(element, fontSize) {
108
- const style = window.getComputedStyle(element);
109
- const lineHeight = style.lineHeight;
160
+ static parseLineHeight(lineHeight, fontSize) {
110
161
  if (lineHeight === "normal") {
111
162
  return fontSize * 1.2;
112
163
  }
@@ -179,16 +230,12 @@ class UITextMeasurement {
179
230
  }
180
231
  return lines.length > 0 ? lines : [""];
181
232
  }
182
- static calculateTextSize(element, content, constrainingWidth, constrainingHeight) {
233
+ static calculateTextSize(element, content, constrainingWidth, constrainingHeight, providedStyles) {
183
234
  if (!content || content.length === 0) {
184
- const style = window.getComputedStyle(element);
185
- const paddingTop = parseFloat(style.paddingTop) || 0;
186
- const paddingBottom = parseFloat(style.paddingBottom) || 0;
187
- const paddingLeft = parseFloat(style.paddingLeft) || 0;
188
- const paddingRight = parseFloat(style.paddingRight) || 0;
235
+ const styles = this.getElementStyles(element, providedStyles);
189
236
  return {
190
- width: paddingLeft + paddingRight,
191
- height: paddingTop + paddingBottom
237
+ width: styles.paddingLeft + styles.paddingRight,
238
+ height: styles.paddingTop + styles.paddingBottom
192
239
  };
193
240
  }
194
241
  const isPlain = this.isPlainText(content);
@@ -198,7 +245,8 @@ class UITextMeasurement {
198
245
  element,
199
246
  content,
200
247
  constrainingWidth,
201
- constrainingHeight
248
+ constrainingHeight,
249
+ providedStyles
202
250
  );
203
251
  }
204
252
  if (hasSimple) {
@@ -207,44 +255,57 @@ class UITextMeasurement {
207
255
  element,
208
256
  plainText,
209
257
  constrainingWidth,
210
- constrainingHeight
258
+ constrainingHeight,
259
+ providedStyles
211
260
  );
212
261
  }
213
262
  return this.measureWithDOM(
214
263
  element,
215
264
  content,
216
265
  constrainingWidth,
217
- constrainingHeight
266
+ constrainingHeight,
267
+ providedStyles
218
268
  );
219
269
  }
220
- static calculatePlainTextSize(element, text, constrainingWidth, constrainingHeight) {
221
- const font = this.getFontString(element);
222
- const style = window.getComputedStyle(element);
223
- const whiteSpace = style.whiteSpace;
224
- const fontSize = parseFloat(style.fontSize);
225
- const lineHeight = this.getLineHeight(element, fontSize);
226
- const paddingLeft = parseFloat(style.paddingLeft) || 0;
227
- const paddingRight = parseFloat(style.paddingRight) || 0;
228
- const paddingTop = parseFloat(style.paddingTop) || 0;
229
- const paddingBottom = parseFloat(style.paddingBottom) || 0;
230
- const availableWidth = constrainingWidth ? constrainingWidth - paddingLeft - paddingRight : Infinity;
270
+ static calculatePlainTextSize(element, text, constrainingWidth, constrainingHeight, providedStyles) {
271
+ const styles = this.getElementStyles(element, providedStyles);
272
+ const availableWidth = constrainingWidth ? constrainingWidth - styles.paddingLeft - styles.paddingRight : Infinity;
231
273
  let width;
232
274
  let height;
233
- if (whiteSpace === "nowrap" || whiteSpace === "pre" || !constrainingWidth) {
234
- width = this.measureTextWidth(text, font) + paddingLeft + paddingRight;
235
- height = lineHeight + paddingTop + paddingBottom;
275
+ if (styles.whiteSpace === "nowrap" || styles.whiteSpace === "pre" || !constrainingWidth) {
276
+ width = this.measureTextWidth(text, styles.font) + styles.paddingLeft + styles.paddingRight;
277
+ height = styles.lineHeight + styles.paddingTop + styles.paddingBottom;
236
278
  } else {
237
- const lines = this.wrapText(text, availableWidth, font, whiteSpace);
279
+ const lines = this.wrapText(text, availableWidth, styles.font, styles.whiteSpace);
238
280
  width = Math.max(
239
- ...lines.map((line) => this.measureTextWidth(line, font))
240
- ) + paddingLeft + paddingRight;
241
- height = lines.length * lineHeight + paddingTop + paddingBottom;
281
+ ...lines.map((line) => this.measureTextWidth(line, styles.font))
282
+ ) + styles.paddingLeft + styles.paddingRight;
283
+ height = lines.length * styles.lineHeight + styles.paddingTop + styles.paddingBottom;
242
284
  }
243
285
  return { width, height };
244
286
  }
245
287
  static clearCaches() {
246
- this.fontCache.clear();
247
- this.lineHeightCache.clear();
288
+ this.globalStyleCache.clear();
289
+ this.elementToCacheKey = /* @__PURE__ */ new WeakMap();
290
+ }
291
+ static invalidateElement(element) {
292
+ const cacheKey = this.elementToCacheKey.get(element);
293
+ if (cacheKey) {
294
+ this.globalStyleCache.delete(cacheKey);
295
+ this.elementToCacheKey.delete(element);
296
+ }
297
+ }
298
+ static invalidateClass(className) {
299
+ for (const [key] of this.globalStyleCache.entries()) {
300
+ if (key.includes(className)) {
301
+ this.globalStyleCache.delete(key);
302
+ }
303
+ }
304
+ }
305
+ static prewarmCache(elements) {
306
+ elements.forEach((el) => {
307
+ this.getElementStyles(el);
308
+ });
248
309
  }
249
310
  static cleanup() {
250
311
  if (this.measurementElement && this.measurementElement.parentElement) {
@@ -258,8 +319,8 @@ class UITextMeasurement {
258
319
  }
259
320
  UITextMeasurement.canvas = null;
260
321
  UITextMeasurement.context = null;
261
- UITextMeasurement.fontCache = /* @__PURE__ */ new Map();
262
- UITextMeasurement.lineHeightCache = /* @__PURE__ */ new Map();
322
+ UITextMeasurement.globalStyleCache = /* @__PURE__ */ new Map();
323
+ UITextMeasurement.elementToCacheKey = /* @__PURE__ */ new WeakMap();
263
324
  UITextMeasurement.measurementElement = null;
264
325
  // Annotate the CommonJS export names for ESM import in node:
265
326
  0 && (module.exports = {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../scripts/UITextMeasurement.ts"],
4
- "sourcesContent": ["// UITextMeasurement.ts - Efficient text measurement without DOM reflows\n\nexport class UITextMeasurement {\n \n private static canvas: HTMLCanvasElement | null = null;\n private static context: CanvasRenderingContext2D | null = null;\n \n // Cache for computed font strings to avoid repeated style parsing\n private static fontCache = new Map<string, string>();\n \n // Cache for line height calculations\n private static lineHeightCache = new Map<string, number>();\n \n // Temporary element for complex HTML measurements (reused to avoid allocations)\n private static measurementElement: HTMLDivElement | null = null;\n \n /**\n * Get or create the canvas context for text measurement\n */\n private static getContext(): CanvasRenderingContext2D {\n if (!this.context) {\n this.canvas = document.createElement('canvas');\n this.context = this.canvas.getContext('2d')!;\n }\n return this.context;\n }\n \n /**\n * Detect if content is plain text or complex HTML\n */\n private static isPlainText(content: string): boolean {\n // Check for HTML tags (excluding simple formatting like <b>, <i>, <span>)\n const hasComplexHTML = /<(?!\\/?(b|i|em|strong|span|br)\\b)[^>]+>/i.test(content);\n return !hasComplexHTML;\n }\n \n /**\n * Check if content has only simple inline formatting\n */\n private static hasSimpleFormatting(content: string): boolean {\n // Only <b>, <i>, <strong>, <em>, <span> with inline styles\n const simpleTagPattern = /^[^<]*(?:<\\/?(?:b|i|em|strong|span)(?:\\s+style=\"[^\"]*\")?>[^<]*)*$/i;\n return simpleTagPattern.test(content);\n }\n \n /**\n * Get or create measurement element for complex HTML\n */\n private static getMeasurementElement(): HTMLDivElement {\n if (!this.measurementElement) {\n this.measurementElement = document.createElement('div');\n this.measurementElement.style.cssText = `\n position: absolute;\n visibility: hidden;\n pointer-events: none;\n top: -9999px;\n left: -9999px;\n width: auto;\n height: auto;\n `;\n }\n return this.measurementElement;\n }\n \n /**\n * Fast measurement using DOM (but optimized to minimize reflows)\n */\n private static measureWithDOM(\n element: HTMLElement,\n content: string,\n constrainingWidth?: number,\n constrainingHeight?: number\n ): { width: number; height: number } {\n const measureEl = this.getMeasurementElement();\n const style = window.getComputedStyle(element);\n \n // Copy relevant styles\n measureEl.style.font = this.getFontString(element);\n measureEl.style.lineHeight = style.lineHeight;\n measureEl.style.whiteSpace = style.whiteSpace;\n measureEl.style.wordBreak = style.wordBreak;\n measureEl.style.wordWrap = style.wordWrap;\n measureEl.style.padding = style.padding;\n measureEl.style.border = style.border;\n measureEl.style.boxSizing = style.boxSizing;\n \n // Set constraints\n if (constrainingWidth) {\n measureEl.style.width = constrainingWidth + 'px';\n measureEl.style.maxWidth = constrainingWidth + 'px';\n } else {\n measureEl.style.width = 'auto';\n measureEl.style.maxWidth = 'none';\n }\n \n if (constrainingHeight) {\n measureEl.style.height = constrainingHeight + 'px';\n measureEl.style.maxHeight = constrainingHeight + 'px';\n } else {\n measureEl.style.height = 'auto';\n measureEl.style.maxHeight = 'none';\n }\n \n // Set content\n measureEl.innerHTML = content;\n \n // Add to DOM only if not already there\n if (!measureEl.parentElement) {\n document.body.appendChild(measureEl);\n }\n \n // Single reflow for both measurements\n const rect = measureEl.getBoundingClientRect();\n const result = {\n width: rect.width || measureEl.scrollWidth,\n height: rect.height || measureEl.scrollHeight\n };\n \n return result;\n }\n \n /**\n * Extract font properties from computed style and create font string\n */\n private static getFontString(element: HTMLElement): string {\n const cacheKey = element.className + element.id;\n \n if (this.fontCache.has(cacheKey)) {\n return this.fontCache.get(cacheKey)!;\n }\n \n const style = window.getComputedStyle(element);\n const font = [\n style.fontStyle,\n style.fontVariant,\n style.fontWeight,\n style.fontSize,\n style.fontFamily\n ].join(' ');\n \n this.fontCache.set(cacheKey, font);\n return font;\n }\n \n /**\n * Get line height in pixels\n */\n private static getLineHeight(element: HTMLElement, fontSize: number): number {\n const style = window.getComputedStyle(element);\n const lineHeight = style.lineHeight;\n \n if (lineHeight === 'normal') {\n // Browsers typically use 1.2 as default line height\n return fontSize * 1.2;\n }\n \n if (lineHeight.endsWith('px')) {\n return parseFloat(lineHeight);\n }\n \n // If it's a unitless number, multiply by font size\n const numericLineHeight = parseFloat(lineHeight);\n if (!isNaN(numericLineHeight)) {\n return fontSize * numericLineHeight;\n }\n \n return fontSize * 1.2; // fallback\n }\n \n /**\n * Measure text width using Canvas API\n */\n static measureTextWidth(text: string, font: string): number {\n const ctx = this.getContext();\n ctx.font = font;\n return ctx.measureText(text).width;\n }\n \n /**\n * Split text into lines based on width constraint\n */\n private static wrapText(\n text: string,\n maxWidth: number,\n font: string,\n whiteSpace: string\n ): string[] {\n // No wrapping needed\n if (whiteSpace === 'nowrap' || whiteSpace === 'pre') {\n return [text];\n }\n \n const ctx = this.getContext();\n ctx.font = font;\n \n const lines: string[] = [];\n const paragraphs = text.split('\\n');\n \n for (const paragraph of paragraphs) {\n if (whiteSpace === 'pre-wrap') {\n // Preserve whitespace but wrap at maxWidth\n lines.push(...this.wrapPreservingWhitespace(paragraph, maxWidth, ctx));\n } else {\n // Normal wrapping (collapse whitespace)\n lines.push(...this.wrapNormal(paragraph, maxWidth, ctx));\n }\n }\n \n return lines;\n }\n \n private static wrapNormal(\n text: string,\n maxWidth: number,\n ctx: CanvasRenderingContext2D\n ): string[] {\n const words = text.split(/\\s+/).filter(w => w.length > 0);\n const lines: string[] = [];\n let currentLine = '';\n \n for (const word of words) {\n const testLine = currentLine ? `${currentLine} ${word}` : word;\n const metrics = ctx.measureText(testLine);\n \n if (metrics.width > maxWidth && currentLine) {\n lines.push(currentLine);\n currentLine = word;\n } else {\n currentLine = testLine;\n }\n }\n \n if (currentLine) {\n lines.push(currentLine);\n }\n \n return lines.length > 0 ? lines : [''];\n }\n \n private static wrapPreservingWhitespace(\n text: string,\n maxWidth: number,\n ctx: CanvasRenderingContext2D\n ): string[] {\n const lines: string[] = [];\n let currentLine = '';\n \n for (let i = 0; i < text.length; i++) {\n const char = text[i];\n const testLine = currentLine + char;\n const metrics = ctx.measureText(testLine);\n \n if (metrics.width > maxWidth && currentLine) {\n lines.push(currentLine);\n currentLine = char;\n } else {\n currentLine = testLine;\n }\n }\n \n if (currentLine) {\n lines.push(currentLine);\n }\n \n return lines.length > 0 ? lines : [''];\n }\n \n /**\n * Calculate intrinsic content size for text - SMART METHOD\n * Automatically chooses the most efficient measurement technique\n */\n static calculateTextSize(\n element: HTMLElement,\n content: string,\n constrainingWidth?: number,\n constrainingHeight?: number\n ): { width: number; height: number } {\n // Empty content\n if (!content || content.length === 0) {\n const style = window.getComputedStyle(element);\n const paddingTop = parseFloat(style.paddingTop) || 0;\n const paddingBottom = parseFloat(style.paddingBottom) || 0;\n const paddingLeft = parseFloat(style.paddingLeft) || 0;\n const paddingRight = parseFloat(style.paddingRight) || 0;\n \n return {\n width: paddingLeft + paddingRight,\n height: paddingTop + paddingBottom\n };\n }\n \n // Check complexity of content\n const isPlain = this.isPlainText(content);\n const hasSimple = this.hasSimpleFormatting(content);\n \n // Strategy 1: Pure canvas for plain text (fastest)\n if (isPlain) {\n return this.calculatePlainTextSize(\n element,\n content,\n constrainingWidth,\n constrainingHeight\n );\n }\n \n // Strategy 2: Optimized DOM for simple formatting (fast)\n if (hasSimple) {\n // For simple formatting, we can still use canvas but need to strip tags\n const plainText = content.replace(/<[^>]+>/g, '');\n return this.calculatePlainTextSize(\n element,\n plainText,\n constrainingWidth,\n constrainingHeight\n );\n }\n \n // Strategy 3: DOM measurement for complex HTML (slower but accurate)\n return this.measureWithDOM(\n element,\n content,\n constrainingWidth,\n constrainingHeight\n );\n }\n \n /**\n * Calculate size for plain text using canvas (no HTML)\n */\n private static calculatePlainTextSize(\n element: HTMLElement,\n text: string,\n constrainingWidth?: number,\n constrainingHeight?: number\n ): { width: number; height: number } {\n const font = this.getFontString(element);\n const style = window.getComputedStyle(element);\n const whiteSpace = style.whiteSpace;\n \n // Get font size in pixels\n const fontSize = parseFloat(style.fontSize);\n const lineHeight = this.getLineHeight(element, fontSize);\n \n // Get padding\n const paddingLeft = parseFloat(style.paddingLeft) || 0;\n const paddingRight = parseFloat(style.paddingRight) || 0;\n const paddingTop = parseFloat(style.paddingTop) || 0;\n const paddingBottom = parseFloat(style.paddingBottom) || 0;\n \n // Adjust constraining width for padding\n const availableWidth = constrainingWidth\n ? constrainingWidth - paddingLeft - paddingRight\n : Infinity;\n \n // Calculate dimensions\n let width: number;\n let height: number;\n \n if (whiteSpace === 'nowrap' || whiteSpace === 'pre' || !constrainingWidth) {\n // Single line or no width constraint\n width = this.measureTextWidth(text, font) + paddingLeft + paddingRight;\n height = lineHeight + paddingTop + paddingBottom;\n } else {\n // Multi-line text\n const lines = this.wrapText(text, availableWidth, font, whiteSpace);\n \n // Find the widest line\n width = Math.max(\n ...lines.map(line => this.measureTextWidth(line, font))\n ) + paddingLeft + paddingRight;\n \n height = (lines.length * lineHeight) + paddingTop + paddingBottom;\n }\n \n return { width, height };\n }\n \n /**\n * Clear all caches (call when fonts change or for cleanup)\n */\n static clearCaches(): void {\n this.fontCache.clear();\n this.lineHeightCache.clear();\n }\n \n /**\n * Clean up measurement element (call on app cleanup)\n */\n static cleanup(): void {\n if (this.measurementElement && this.measurementElement.parentElement) {\n document.body.removeChild(this.measurementElement);\n }\n this.measurementElement = null;\n this.canvas = null;\n this.context = null;\n this.clearCaches();\n }\n}\n\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["// UITextMeasurement.ts - Efficient text measurement without DOM reflows\n\nexport interface TextMeasurementStyle {\n font: string;\n fontSize: number;\n lineHeight: number;\n whiteSpace: string;\n paddingLeft: number;\n paddingRight: number;\n paddingTop: number;\n paddingBottom: number;\n}\n\nexport class UITextMeasurement {\n private static canvas: HTMLCanvasElement | null = null;\n private static context: CanvasRenderingContext2D | null = null;\n \n // Global cache for style objects using semantic cache key\n private static globalStyleCache = new Map<string, TextMeasurementStyle>();\n \n // Per-element cache to map element -> cache key\n private static elementToCacheKey = new WeakMap<HTMLElement, string>();\n \n // Temporary element for complex HTML measurements (reused to avoid allocations)\n private static measurementElement: HTMLDivElement | null = null;\n \n /**\n * Generate a cache key based only on styles that affect text measurement\n * Ignores position, color, transform, etc.\n */\n private static generateStyleCacheKey(computed: CSSStyleDeclaration): string {\n // Only include properties that affect text layout\n const relevantProps = [\n computed.fontFamily,\n computed.fontSize,\n computed.fontWeight,\n computed.fontStyle,\n computed.fontVariant,\n computed.lineHeight,\n computed.letterSpacing,\n computed.wordSpacing,\n computed.textTransform,\n computed.whiteSpace,\n computed.wordBreak,\n computed.wordWrap,\n computed.paddingLeft,\n computed.paddingRight,\n computed.paddingTop,\n computed.paddingBottom,\n computed.borderLeftWidth,\n computed.borderRightWidth,\n computed.borderTopWidth,\n computed.borderBottomWidth,\n computed.boxSizing\n ];\n \n // Create a hash-like key from relevant properties\n return relevantProps.join('|');\n }\n \n /**\n * Extract cache key from element's classList\n * Elements with same classes likely have same text measurement styles\n */\n private static getSemanticCacheKey(element: HTMLElement): string {\n // Check if we already computed a cache key for this element\n const existingKey = this.elementToCacheKey.get(element);\n if (existingKey) {\n return existingKey;\n }\n \n // Try to use class-based caching first (fastest)\n const classList = Array.from(element.classList).sort().join(' ');\n const tagName = element.tagName.toLowerCase();\n \n // Semantic key based on tag + classes\n const semanticKey = `${tagName}::${classList}`;\n \n // Check if we have styles for this semantic key\n if (this.globalStyleCache.has(semanticKey)) {\n this.elementToCacheKey.set(element, semanticKey);\n return semanticKey;\n }\n \n // If not cached, compute and use style-based key\n const computed = window.getComputedStyle(element);\n const styleCacheKey = this.generateStyleCacheKey(computed);\n \n // Use the style-based key\n this.elementToCacheKey.set(element, styleCacheKey);\n \n return styleCacheKey;\n }\n \n /**\n * Get or create the canvas context for text measurement\n */\n private static getContext(): CanvasRenderingContext2D {\n if (!this.context) {\n this.canvas = document.createElement('canvas');\n this.context = this.canvas.getContext('2d')!;\n }\n return this.context;\n }\n \n /**\n * Detect if content is plain text or complex HTML\n */\n private static isPlainText(content: string): boolean {\n // Check for HTML tags (excluding simple formatting like <b>, <i>, <span>)\n const hasComplexHTML = /<(?!\\/?(b|i|em|strong|span|br)\\b)[^>]+>/i.test(content);\n return !hasComplexHTML;\n }\n \n /**\n * Check if content has only simple inline formatting\n */\n private static hasSimpleFormatting(content: string): boolean {\n // Only <b>, <i>, <strong>, <em>, <span> with inline styles\n const simpleTagPattern = /^[^<]*(?:<\\/?(?:b|i|em|strong|span)(?:\\s+style=\"[^\"]*\")?>[^<]*)*$/i;\n return simpleTagPattern.test(content);\n }\n \n /**\n * Get or create measurement element for complex HTML\n */\n private static getMeasurementElement(): HTMLDivElement {\n if (!this.measurementElement) {\n this.measurementElement = document.createElement('div');\n this.measurementElement.style.cssText = `\n position: absolute;\n visibility: hidden;\n pointer-events: none;\n top: -9999px;\n left: -9999px;\n width: auto;\n height: auto;\n `;\n }\n return this.measurementElement;\n }\n \n /**\n * Fast measurement using DOM (but optimized to minimize reflows)\n */\n private static measureWithDOM(\n element: HTMLElement,\n content: string,\n constrainingWidth?: number,\n constrainingHeight?: number,\n providedStyles?: TextMeasurementStyle\n ): { width: number; height: number } {\n const measureEl = this.getMeasurementElement();\n const styles = this.getElementStyles(element, providedStyles);\n \n // Copy relevant styles\n measureEl.style.font = styles.font;\n measureEl.style.lineHeight = styles.lineHeight + 'px';\n measureEl.style.whiteSpace = styles.whiteSpace;\n measureEl.style.padding = `${styles.paddingTop}px ${styles.paddingRight}px ${styles.paddingBottom}px ${styles.paddingLeft}px`;\n \n // Set constraints\n if (constrainingWidth) {\n measureEl.style.width = constrainingWidth + 'px';\n measureEl.style.maxWidth = constrainingWidth + 'px';\n } else {\n measureEl.style.width = 'auto';\n measureEl.style.maxWidth = 'none';\n }\n \n if (constrainingHeight) {\n measureEl.style.height = constrainingHeight + 'px';\n measureEl.style.maxHeight = constrainingHeight + 'px';\n } else {\n measureEl.style.height = 'auto';\n measureEl.style.maxHeight = 'none';\n }\n \n // Set content\n measureEl.innerHTML = content;\n \n // Add to DOM only if not already there\n if (!measureEl.parentElement) {\n document.body.appendChild(measureEl);\n }\n \n // Single reflow for both measurements\n const rect = measureEl.getBoundingClientRect();\n const result = {\n width: rect.width || measureEl.scrollWidth,\n height: rect.height || measureEl.scrollHeight\n };\n \n return result;\n }\n \n /**\n * Get or extract styles from element (with smart global caching)\n * Returns cached styles if available, otherwise computes once and caches globally\n */\n private static getElementStyles(element: HTMLElement, providedStyles?: TextMeasurementStyle): TextMeasurementStyle {\n // Use provided styles if available (avoids getComputedStyle entirely)\n if (providedStyles) {\n return providedStyles;\n }\n \n // Get semantic cache key\n const cacheKey = this.getSemanticCacheKey(element);\n \n // Check global cache\n const cached = this.globalStyleCache.get(cacheKey);\n if (cached) {\n return cached;\n }\n \n // Compute once and cache globally (this is the only getComputedStyle call)\n const computed = window.getComputedStyle(element);\n const fontSize = parseFloat(computed.fontSize);\n \n const styles: TextMeasurementStyle = {\n font: [\n computed.fontStyle,\n computed.fontVariant,\n computed.fontWeight,\n computed.fontSize,\n computed.fontFamily\n ].join(' '),\n fontSize: fontSize,\n lineHeight: this.parseLineHeight(computed.lineHeight, fontSize),\n whiteSpace: computed.whiteSpace,\n paddingLeft: parseFloat(computed.paddingLeft) || 0,\n paddingRight: parseFloat(computed.paddingRight) || 0,\n paddingTop: parseFloat(computed.paddingTop) || 0,\n paddingBottom: parseFloat(computed.paddingBottom) || 0\n };\n \n this.globalStyleCache.set(cacheKey, styles);\n return styles;\n }\n \n /**\n * Parse line height from computed style\n */\n private static parseLineHeight(lineHeight: string, fontSize: number): number {\n if (lineHeight === 'normal') {\n return fontSize * 1.2;\n }\n \n if (lineHeight.endsWith('px')) {\n return parseFloat(lineHeight);\n }\n \n const numericLineHeight = parseFloat(lineHeight);\n if (!isNaN(numericLineHeight)) {\n return fontSize * numericLineHeight;\n }\n \n return fontSize * 1.2;\n }\n \n /**\n * Measure text width using Canvas API\n */\n static measureTextWidth(text: string, font: string): number {\n const ctx = this.getContext();\n ctx.font = font;\n return ctx.measureText(text).width;\n }\n \n /**\n * Split text into lines based on width constraint\n */\n private static wrapText(\n text: string,\n maxWidth: number,\n font: string,\n whiteSpace: string\n ): string[] {\n // No wrapping needed\n if (whiteSpace === 'nowrap' || whiteSpace === 'pre') {\n return [text];\n }\n \n const ctx = this.getContext();\n ctx.font = font;\n \n const lines: string[] = [];\n const paragraphs = text.split('\\n');\n \n for (const paragraph of paragraphs) {\n if (whiteSpace === 'pre-wrap') {\n // Preserve whitespace but wrap at maxWidth\n lines.push(...this.wrapPreservingWhitespace(paragraph, maxWidth, ctx));\n } else {\n // Normal wrapping (collapse whitespace)\n lines.push(...this.wrapNormal(paragraph, maxWidth, ctx));\n }\n }\n \n return lines;\n }\n \n private static wrapNormal(\n text: string,\n maxWidth: number,\n ctx: CanvasRenderingContext2D\n ): string[] {\n const words = text.split(/\\s+/).filter(w => w.length > 0);\n const lines: string[] = [];\n let currentLine = '';\n \n for (const word of words) {\n const testLine = currentLine ? `${currentLine} ${word}` : word;\n const metrics = ctx.measureText(testLine);\n \n if (metrics.width > maxWidth && currentLine) {\n lines.push(currentLine);\n currentLine = word;\n } else {\n currentLine = testLine;\n }\n }\n \n if (currentLine) {\n lines.push(currentLine);\n }\n \n return lines.length > 0 ? lines : [''];\n }\n \n private static wrapPreservingWhitespace(\n text: string,\n maxWidth: number,\n ctx: CanvasRenderingContext2D\n ): string[] {\n const lines: string[] = [];\n let currentLine = '';\n \n for (let i = 0; i < text.length; i++) {\n const char = text[i];\n const testLine = currentLine + char;\n const metrics = ctx.measureText(testLine);\n \n if (metrics.width > maxWidth && currentLine) {\n lines.push(currentLine);\n currentLine = char;\n } else {\n currentLine = testLine;\n }\n }\n \n if (currentLine) {\n lines.push(currentLine);\n }\n \n return lines.length > 0 ? lines : [''];\n }\n \n /**\n * Calculate intrinsic content size for text - SMART METHOD\n * Automatically chooses the most efficient measurement technique\n */\n static calculateTextSize(\n element: HTMLElement,\n content: string,\n constrainingWidth?: number,\n constrainingHeight?: number,\n providedStyles?: TextMeasurementStyle\n ): { width: number; height: number } {\n // Empty content\n if (!content || content.length === 0) {\n const styles = this.getElementStyles(element, providedStyles);\n return {\n width: styles.paddingLeft + styles.paddingRight,\n height: styles.paddingTop + styles.paddingBottom\n };\n }\n \n // Check complexity of content\n const isPlain = this.isPlainText(content);\n const hasSimple = this.hasSimpleFormatting(content);\n \n // Strategy 1: Pure canvas for plain text (fastest)\n if (isPlain) {\n return this.calculatePlainTextSize(\n element,\n content,\n constrainingWidth,\n constrainingHeight,\n providedStyles\n );\n }\n \n // Strategy 2: Optimized DOM for simple formatting (fast)\n if (hasSimple) {\n // For simple formatting, we can still use canvas but need to strip tags\n const plainText = content.replace(/<[^>]+>/g, '');\n return this.calculatePlainTextSize(\n element,\n plainText,\n constrainingWidth,\n constrainingHeight,\n providedStyles\n );\n }\n \n // Strategy 3: DOM measurement for complex HTML (slower but accurate)\n return this.measureWithDOM(\n element,\n content,\n constrainingWidth,\n constrainingHeight,\n providedStyles\n );\n }\n \n /**\n * Calculate size for plain text using canvas (no HTML)\n */\n private static calculatePlainTextSize(\n element: HTMLElement,\n text: string,\n constrainingWidth?: number,\n constrainingHeight?: number,\n providedStyles?: TextMeasurementStyle\n ): { width: number; height: number } {\n const styles = this.getElementStyles(element, providedStyles);\n \n // Adjust constraining width for padding\n const availableWidth = constrainingWidth\n ? constrainingWidth - styles.paddingLeft - styles.paddingRight\n : Infinity;\n \n // Calculate dimensions\n let width: number;\n let height: number;\n \n if (styles.whiteSpace === 'nowrap' || styles.whiteSpace === 'pre' || !constrainingWidth) {\n // Single line or no width constraint\n width = this.measureTextWidth(text, styles.font) + styles.paddingLeft + styles.paddingRight;\n height = styles.lineHeight + styles.paddingTop + styles.paddingBottom;\n } else {\n // Multi-line text\n const lines = this.wrapText(text, availableWidth, styles.font, styles.whiteSpace);\n \n // Find the widest line\n width = Math.max(\n ...lines.map(line => this.measureTextWidth(line, styles.font))\n ) + styles.paddingLeft + styles.paddingRight;\n \n height = (lines.length * styles.lineHeight) + styles.paddingTop + styles.paddingBottom;\n }\n \n return { width, height };\n }\n \n /**\n * Clear all caches (call when fonts change or for cleanup)\n */\n static clearCaches(): void {\n this.globalStyleCache.clear();\n this.elementToCacheKey = new WeakMap();\n }\n \n /**\n * Invalidate cached styles for a specific element\n */\n static invalidateElement(element: HTMLElement): void {\n const cacheKey = this.elementToCacheKey.get(element);\n if (cacheKey) {\n this.globalStyleCache.delete(cacheKey);\n this.elementToCacheKey.delete(element);\n }\n }\n \n /**\n * Invalidate cache for elements with specific class\n * Useful when you change a CSS class definition\n */\n static invalidateClass(className: string): void {\n // Clear all cache keys that contain this class\n for (const [key] of this.globalStyleCache.entries()) {\n if (key.includes(className)) {\n this.globalStyleCache.delete(key);\n }\n }\n }\n \n /**\n * Pre-warm the cache by measuring a representative element\n * Useful at app startup to avoid first-paint delays\n */\n static prewarmCache(elements: HTMLElement[]): void {\n elements.forEach(el => {\n this.getElementStyles(el);\n });\n }\n \n /**\n * Clean up measurement element (call on app cleanup)\n */\n static cleanup(): void {\n if (this.measurementElement && this.measurementElement.parentElement) {\n document.body.removeChild(this.measurementElement);\n }\n this.measurementElement = null;\n this.canvas = null;\n this.context = null;\n this.clearCaches();\n }\n}\n\n// Extension methods to add to UITextView\nexport interface UITextViewMeasurementMethods {\n intrinsicContentSizeEfficient(\n constrainingWidth?: number,\n constrainingHeight?: number\n ): { width: number; height: number };\n}\n\n// ==================== INTEGRATION CODE ====================\n// Add these methods to UITextView class:\n\n/*\n // In UITextView class:\n \n // Add this property to track content complexity and cached styles\n private _useFastMeasurement: boolean | undefined;\n private _cachedMeasurementStyles: TextMeasurementStyle | undefined;\n \n // Call this when styles change (fontSize, padding, etc.)\n private _invalidateMeasurementStyles(): void {\n this._cachedMeasurementStyles = undefined;\n UITextMeasurement.invalidateElement(this.viewHTMLElement);\n this._intrinsicSizesCache = {};\n }\n \n // Extract styles ONCE and cache them (avoids getComputedStyle)\n private _getMeasurementStyles(): TextMeasurementStyle {\n if (this._cachedMeasurementStyles) {\n return this._cachedMeasurementStyles;\n }\n \n // Only call getComputedStyle once and cache the result\n const computed = window.getComputedStyle(this.viewHTMLElement);\n const fontSize = parseFloat(computed.fontSize);\n \n this._cachedMeasurementStyles = {\n font: [\n computed.fontStyle,\n computed.fontVariant,\n computed.fontWeight,\n computed.fontSize,\n computed.fontFamily\n ].join(' '),\n fontSize: fontSize,\n lineHeight: this._parseLineHeight(computed.lineHeight, fontSize),\n whiteSpace: computed.whiteSpace,\n paddingLeft: parseFloat(computed.paddingLeft) || 0,\n paddingRight: parseFloat(computed.paddingRight) || 0,\n paddingTop: parseFloat(computed.paddingTop) || 0,\n paddingBottom: parseFloat(computed.paddingBottom) || 0\n };\n \n return this._cachedMeasurementStyles;\n }\n \n private _parseLineHeight(lineHeight: string, fontSize: number): number {\n if (lineHeight === 'normal') {\n return fontSize * 1.2;\n }\n if (lineHeight.endsWith('px')) {\n return parseFloat(lineHeight);\n }\n const numericLineHeight = parseFloat(lineHeight);\n if (!isNaN(numericLineHeight)) {\n return fontSize * numericLineHeight;\n }\n return fontSize * 1.2;\n }\n \n // Override the intrinsic size method\n override intrinsicContentSizeWithConstraints(\n constrainingHeight: number = 0,\n constrainingWidth: number = 0\n ): UIRectangle {\n const cacheKey = \"h_\" + constrainingHeight + \"__w_\" + constrainingWidth;\n const cachedResult = this._intrinsicSizesCache[cacheKey];\n if (cachedResult) {\n return cachedResult;\n }\n \n // Determine measurement strategy\n const shouldUseFastPath = this._useFastMeasurement ?? this._shouldUseFastMeasurement();\n \n let result: UIRectangle;\n \n if (shouldUseFastPath) {\n // Fast path: canvas-based measurement with pre-extracted styles\n const styles = this._getMeasurementStyles();\n const size = UITextMeasurement.calculateTextSize(\n this.viewHTMLElement,\n this.text || this.innerHTML,\n constrainingWidth || undefined,\n constrainingHeight || undefined,\n styles // Pass pre-computed styles to avoid getComputedStyle!\n );\n result = new UIRectangle(0, 0, size.height, size.width);\n } else {\n // Fallback: original DOM-based measurement for complex content\n result = super.intrinsicContentSizeWithConstraints(constrainingHeight, constrainingWidth);\n }\n \n this._intrinsicSizesCache[cacheKey] = result.copy();\n return result;\n }\n \n // Helper to determine if we can use fast measurement\n private _shouldUseFastMeasurement(): boolean {\n const content = this.text || this.innerHTML;\n \n // If using dynamic innerHTML with parameters, use DOM measurement\n if (this._innerHTMLKey || this._localizedTextObject) {\n return false;\n }\n \n // Check for notification badges\n if (this.notificationAmount > 0) {\n return false; // Has span with colored text\n }\n \n // Check content complexity\n const hasComplexHTML = /<(?!\\/?(b|i|em|strong|span|br)\\b)[^>]+>/i.test(content);\n \n return !hasComplexHTML;\n }\n \n // Optional: Allow manual override for specific instances\n setUseFastMeasurement(useFast: boolean): void {\n this._useFastMeasurement = useFast;\n this._intrinsicSizesCache = {};\n }\n \n // Optional: Force re-evaluation of measurement strategy\n invalidateMeasurementStrategy(): void {\n this._useFastMeasurement = undefined;\n this._invalidateMeasurementStyles();\n }\n \n // Update fontSize setter to invalidate cached styles\n override set fontSize(fontSize: number) {\n this.style.fontSize = \"\" + fontSize + \"pt\";\n this._intrinsicHeightCache = new UIObject() as any;\n this._intrinsicWidthCache = new UIObject() as any;\n this._invalidateMeasurementStyles(); // Invalidate when font changes\n }\n \n // Update the text setter to invalidate measurement strategy\n override set text(text: string) {\n this._text = text;\n \n var notificationText = \"\";\n \n if (this.notificationAmount) {\n notificationText = \"<span style=\\\"color: \" + UITextView.notificationTextColor.stringValue + \";\\\">\" +\n (\" (\" + this.notificationAmount + \")\").bold() + \"</span>\";\n }\n \n if (this.viewHTMLElement.innerHTML != this.textPrefix + text + this.textSuffix + notificationText) {\n this.viewHTMLElement.innerHTML = this.textPrefix + FIRST(text, \"\") + this.textSuffix + notificationText;\n }\n \n if (this.changesOften) {\n this._intrinsicHeightCache = new UIObject() as any;\n this._intrinsicWidthCache = new UIObject() as any;\n }\n \n // Invalidate measurement strategy when text changes significantly\n this._useFastMeasurement = undefined;\n this._intrinsicSizesCache = {};\n \n this.setNeedsLayout();\n }\n */\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -41,6 +41,7 @@ export declare class UITextView extends UIView {
41
41
  static _pxToPt: number;
42
42
  _text?: string;
43
43
  private _useFastMeasurement;
44
+ private _cachedMeasurementStyles;
44
45
  constructor(elementID?: string, textViewType?: string | ValueOf<typeof UITextView.type>, viewHTMLElement?: null);
45
46
  static _determinePXAndPTRatios(): void;
46
47
  static type: {
@@ -87,6 +88,9 @@ export declare class UITextView extends UIView {
87
88
  layoutSubviews(): void;
88
89
  intrinsicContentHeight(constrainingWidth?: number): any;
89
90
  intrinsicContentWidth(constrainingHeight?: number): any;
91
+ private _invalidateMeasurementStyles;
92
+ private _getMeasurementStyles;
93
+ private _parseLineHeight;
90
94
  intrinsicContentSizeWithConstraints(constrainingHeight?: number, constrainingWidth?: number): UIRectangle;
91
95
  private _shouldUseFastMeasurement;
92
96
  setUseFastMeasurement(useFast: boolean): void;
@@ -89,6 +89,7 @@ const _UITextView = class extends import_UIView.UIView {
89
89
  return;
90
90
  }
91
91
  this.style.whiteSpace = "pre-wrap";
92
+ this.invalidateMeasurementStrategy();
92
93
  }
93
94
  get notificationAmount() {
94
95
  return this._notificationAmount;
@@ -122,26 +123,33 @@ const _UITextView = class extends import_UIView.UIView {
122
123
  }
123
124
  this._useFastMeasurement = void 0;
124
125
  this._intrinsicSizesCache = {};
126
+ this.invalidateMeasurementStrategy();
127
+ this._invalidateMeasurementStyles();
125
128
  this.setNeedsLayout();
126
129
  }
127
130
  set innerHTML(innerHTML) {
128
131
  this.text = innerHTML;
132
+ this.invalidateMeasurementStrategy();
129
133
  }
130
134
  get innerHTML() {
131
135
  return this.viewHTMLElement.innerHTML;
132
136
  }
133
137
  setText(key, defaultString, parameters) {
134
138
  this.setInnerHTML(key, defaultString, parameters);
139
+ this.invalidateMeasurementStrategy();
135
140
  }
136
141
  get fontSize() {
137
- const style = window.getComputedStyle(this.viewHTMLElement, null).fontSize;
142
+ const style = this.style.fontSize || window.getComputedStyle(this.viewHTMLElement, null).fontSize;
138
143
  const result = parseFloat(style) * _UITextView._pxToPt;
139
144
  return result;
140
145
  }
141
146
  set fontSize(fontSize) {
142
- this.style.fontSize = "" + fontSize + "pt";
143
- this._intrinsicHeightCache = new import_UIObject.UIObject();
144
- this._intrinsicWidthCache = new import_UIObject.UIObject();
147
+ if (fontSize != this.fontSize) {
148
+ this.style.fontSize = "" + fontSize + "pt";
149
+ this._intrinsicHeightCache = new import_UIObject.UIObject();
150
+ this._intrinsicWidthCache = new import_UIObject.UIObject();
151
+ this._invalidateMeasurementStyles();
152
+ }
145
153
  }
146
154
  useAutomaticFontSize(minFontSize = import_UIObject.nil, maxFontSize = import_UIObject.nil) {
147
155
  this._automaticFontSizeSelection = import_UIObject.YES;
@@ -217,6 +225,48 @@ const _UITextView = class extends import_UIView.UIView {
217
225
  }
218
226
  return result;
219
227
  }
228
+ _invalidateMeasurementStyles() {
229
+ this._cachedMeasurementStyles = void 0;
230
+ import_UITextMeasurement.UITextMeasurement.invalidateElement(this.viewHTMLElement);
231
+ this._intrinsicSizesCache = {};
232
+ }
233
+ _getMeasurementStyles() {
234
+ if (this._cachedMeasurementStyles) {
235
+ return this._cachedMeasurementStyles;
236
+ }
237
+ const computed = window.getComputedStyle(this.viewHTMLElement);
238
+ const fontSize = parseFloat(computed.fontSize);
239
+ this._cachedMeasurementStyles = {
240
+ font: [
241
+ computed.fontStyle,
242
+ computed.fontVariant,
243
+ computed.fontWeight,
244
+ computed.fontSize,
245
+ computed.fontFamily
246
+ ].join(" "),
247
+ fontSize,
248
+ lineHeight: this._parseLineHeight(computed.lineHeight, fontSize),
249
+ whiteSpace: computed.whiteSpace,
250
+ paddingLeft: parseFloat(computed.paddingLeft) || 0,
251
+ paddingRight: parseFloat(computed.paddingRight) || 0,
252
+ paddingTop: parseFloat(computed.paddingTop) || 0,
253
+ paddingBottom: parseFloat(computed.paddingBottom) || 0
254
+ };
255
+ return this._cachedMeasurementStyles;
256
+ }
257
+ _parseLineHeight(lineHeight, fontSize) {
258
+ if (lineHeight === "normal") {
259
+ return fontSize * 1.2;
260
+ }
261
+ if (lineHeight.endsWith("px")) {
262
+ return parseFloat(lineHeight);
263
+ }
264
+ const numericLineHeight = parseFloat(lineHeight);
265
+ if (!isNaN(numericLineHeight)) {
266
+ return fontSize * numericLineHeight;
267
+ }
268
+ return fontSize * 1.2;
269
+ }
220
270
  intrinsicContentSizeWithConstraints(constrainingHeight = 0, constrainingWidth = 0) {
221
271
  var _a;
222
272
  const cacheKey = "h_" + constrainingHeight + "__w_" + constrainingWidth;
@@ -227,11 +277,13 @@ const _UITextView = class extends import_UIView.UIView {
227
277
  const shouldUseFastPath = (_a = this._useFastMeasurement) != null ? _a : this._shouldUseFastMeasurement();
228
278
  let result;
229
279
  if (shouldUseFastPath) {
280
+ const styles = this._getMeasurementStyles();
230
281
  const size = import_UITextMeasurement.UITextMeasurement.calculateTextSize(
231
282
  this.viewHTMLElement,
232
283
  this.text || this.innerHTML,
233
284
  constrainingWidth || void 0,
234
- constrainingHeight || void 0
285
+ constrainingHeight || void 0,
286
+ styles
235
287
  );
236
288
  result = new import_UIRectangle.UIRectangle(0, 0, size.height, size.width);
237
289
  } else {
@@ -242,7 +294,7 @@ const _UITextView = class extends import_UIView.UIView {
242
294
  }
243
295
  _shouldUseFastMeasurement() {
244
296
  const content = this.text || this.innerHTML;
245
- if ((0, import_UIObject.IS)(this._innerHTMLKey) || (0, import_UIObject.IS)(this._localizedTextObject)) {
297
+ if (this._innerHTMLKey || this._localizedTextObject) {
246
298
  return false;
247
299
  }
248
300
  if (this.notificationAmount > 0) {
@@ -257,7 +309,7 @@ const _UITextView = class extends import_UIView.UIView {
257
309
  }
258
310
  invalidateMeasurementStrategy() {
259
311
  this._useFastMeasurement = void 0;
260
- this._intrinsicSizesCache = {};
312
+ this._invalidateMeasurementStyles();
261
313
  }
262
314
  intrinsicContentSize() {
263
315
  const result = this.intrinsicContentSizeWithConstraints(import_UIObject.nil, import_UIObject.nil);