uicore-ts 1.1.310 → 1.1.315

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.
@@ -12,6 +12,22 @@ export class UITextView extends UIView {
12
12
 
13
13
  static defaultTextColor = UIColor.blackColor
14
14
  static notificationTextColor = UIColor.redColor
15
+ static attentionRequiredColor = new UIColor("#f59e0b")
16
+
17
+ /**
18
+ * Override this to customise the attention indicator HTML that is appended
19
+ * to a label's text when `attentionRequired` is `true`.
20
+ *
21
+ * The default renders an amber `●` dot:
22
+ * ```
23
+ * UITextView.renderAttentionIndicator = () =>
24
+ * `<span style="color: #f59e0b; margin-left: 4px;">●</span>`
25
+ * ```
26
+ * Call sites never need to change — only this one function needs to be
27
+ * replaced at app startup to restyle every attention indicator globally.
28
+ */
29
+ static renderAttentionIndicator: () => string = () =>
30
+ "<span style=\"color: " + UITextView.attentionRequiredColor.stringValue + "; margin-left: 4px;\">●</span>"
15
31
 
16
32
  // Global caches for all UILabels
17
33
  static _intrinsicHeightCache: { [x: string]: { [x: string]: number; }; } & UIObject = new UIObject() as any
@@ -283,6 +299,10 @@ export class UITextView extends UIView {
283
299
  return false
284
300
  }
285
301
 
302
+ if (this._attentionRequired) {
303
+ return false
304
+ }
305
+
286
306
  const hasComplexHTML = /<(?!\/?(b|i|em|strong|span)\b)[^>]+>/i.test(content)
287
307
 
288
308
  if (hasComplexHTML) {
@@ -401,6 +421,24 @@ export class UITextView extends UIView {
401
421
 
402
422
  //#endregion
403
423
 
424
+ //#region Getters & Setters - Attention Required
425
+
426
+ get attentionRequired() {
427
+ return this._attentionRequired
428
+ }
429
+
430
+ set attentionRequired(attentionRequired: boolean) {
431
+ if (this._attentionRequired == attentionRequired) {
432
+ return
433
+ }
434
+
435
+ this._attentionRequired = attentionRequired
436
+ this.text = this.text
437
+ this.setNeedsLayoutUpToRootView()
438
+ }
439
+
440
+ //#endregion
441
+
404
442
  //#region Getters & Setters - Text Content
405
443
 
406
444
  get text() {
@@ -415,11 +453,16 @@ export class UITextView extends UIView {
415
453
  (" (" + this.notificationAmount + ")").bold() + "</span>"
416
454
  }
417
455
 
456
+ var attentionDot = ""
457
+ if (this._attentionRequired) {
458
+ attentionDot = UITextView.renderAttentionIndicator()
459
+ }
460
+
418
461
  const displayText = this.thousandsSeparator !== null
419
462
  ? UITextView.applyThousandsSeparatorToNumericalString(text, this.thousandsSeparator)
420
463
  : text
421
464
 
422
- const newInnerHTML = this.textPrefix + FIRST(displayText, "") + this.textSuffix + notificationText
465
+ const newInnerHTML = this.textPrefix + FIRST(displayText, "") + this.textSuffix + notificationText + attentionDot
423
466
 
424
467
  if (this.textElementView.viewHTMLElement.innerHTML !== newInnerHTML) {
425
468
  this.textElementView.viewHTMLElement.innerHTML = newInnerHTML
@@ -626,6 +669,7 @@ export class UITextView extends UIView {
626
669
  textPrefix = ""
627
670
  textSuffix = ""
628
671
  _notificationAmount = 0
672
+ _attentionRequired = false
629
673
 
630
674
  _thousandsSeparator: string | null = null
631
675
 
package/scripts/UIView.ts CHANGED
@@ -6,6 +6,7 @@ import "./UICoreExtensions"
6
6
  import type { UIDialogView } from "./UIDialogView"
7
7
  import { UILocalizedTextObject } from "./UIInterfaces"
8
8
  import { UILayoutCycleTracer } from "./UILayoutCycleTracer"
9
+ import { UILayoutDebugger } from "./UILayoutDebugger"
9
10
  import {
10
11
  FIRST,
11
12
  FIRST_OR_NIL,
@@ -2033,6 +2034,7 @@ export class UIView extends UIObject {
2033
2034
  }
2034
2035
 
2035
2036
  window.UILayoutCycleTracer?.willBeginLayoutPass()
2037
+ window.UILayoutDebugger?.willBeginLayoutPass(UIView._viewsToLayout)
2036
2038
 
2037
2039
  const maxIterations = 10
2038
2040
  let iteration = 0
@@ -2041,6 +2043,7 @@ export class UIView extends UIObject {
2041
2043
  while (UIView._viewsToLayout.length > 0 && iteration < maxIterations) {
2042
2044
 
2043
2045
  window.UILayoutCycleTracer?.willBeginIteration(iteration)
2046
+ window.UILayoutDebugger?.willBeginIteration(iteration)
2044
2047
 
2045
2048
  const viewsToProcess = UIView._viewsToLayout
2046
2049
  UIView._viewsToLayout = []
@@ -2069,15 +2072,21 @@ export class UIView extends UIObject {
2069
2072
 
2070
2073
  for (let i = 0; i < sortedViews.length; i++) {
2071
2074
  const view = sortedViews[i]
2075
+ window.UILayoutDebugger?.willLayoutView(view)
2076
+ if (window.UILayoutDebugger?._shouldHitBreakpoint(view)) {
2077
+ const breakpointOnThisLine = "Add a breakpoint on this line to step through layout."
2078
+ }
2072
2079
  view.layoutIfNeeded()
2073
2080
  layoutCounts.set(view, (layoutCounts.get(view) || 0) + 1)
2074
2081
  window.UILayoutCycleTracer?.didLayoutView(view)
2082
+ window.UILayoutDebugger?.didLayoutView(view)
2075
2083
  }
2076
2084
 
2077
2085
  iteration++
2078
2086
  }
2079
2087
 
2080
2088
  window.UILayoutCycleTracer?.didFinishLayoutPass(iteration)
2089
+ window.UILayoutDebugger?.didFinishLayoutPass(iteration)
2081
2090
 
2082
2091
  // console.log(iteration + " iterations to finish layout")
2083
2092
 
@@ -2097,6 +2106,7 @@ export class UIView extends UIObject {
2097
2106
  this.clearIntrinsicSizeCache()
2098
2107
 
2099
2108
  window.UILayoutCycleTracer?.viewDidCallSetNeedsLayout(this)
2109
+ window.UILayoutDebugger?.viewDidCallSetNeedsLayout(this)
2100
2110
 
2101
2111
  // // Auto-propagate if intrinsic height changed
2102
2112
  // if (IS(this.superview) && this.superview.usesVirtualLayoutingForIntrinsicSizing) {
@@ -2133,7 +2143,7 @@ export class UIView extends UIObject {
2133
2143
 
2134
2144
  try {
2135
2145
 
2136
- if (this.bounds.width < 0) {
2146
+ if (this.bounds.width < 0 || (!this.isVirtualLayouting && !this.isMemberOfViewTree)) {
2137
2147
  return
2138
2148
  }
2139
2149
 
@@ -2167,12 +2177,14 @@ export class UIView extends UIObject {
2167
2177
 
2168
2178
  this.applyClassesAndStyles()
2169
2179
 
2180
+ window.UILayoutDebugger?.willSetSubviewFrames(this)
2170
2181
  for (let i = 0; i < this.subviews?.length; i++) {
2171
2182
 
2172
2183
  const subview = this.subviews[i]
2173
2184
  subview.calculateAndSetViewFrame()
2174
2185
 
2175
2186
  }
2187
+ window.UILayoutDebugger?.didSetSubviewFrames(this)
2176
2188
 
2177
2189
  // if (this._loadingView && this._loadingView.superview == this) {
2178
2190
  // this._loadingView.setFrame(this.bounds)
@@ -4111,9 +4123,11 @@ export class UIView extends UIObject {
4111
4123
  UIView._sharedIntrinsicSizeCaches.set(this.sharedIntrinsicSizeCacheIdentifier, bucket)
4112
4124
  }
4113
4125
  bucket[cacheKey] = size.copy()
4126
+ window.UILayoutDebugger?.didSetCachedIntrinsicSize(this, cacheKey, size)
4114
4127
  return
4115
4128
  }
4116
4129
  this._intrinsicSizesCache[cacheKey] = size.copy()
4130
+ window.UILayoutDebugger?.didSetCachedIntrinsicSize(this, cacheKey, size)
4117
4131
  }
4118
4132
 
4119
4133
  // clearIntrinsicSizeCache(): void {
@@ -4363,6 +4377,66 @@ export class UIView extends UIObject {
4363
4377
  }
4364
4378
 
4365
4379
 
4380
+ /**
4381
+ * ⚠️ NUCLEAR OPTION — DO NOT CALL IN NORMAL CODE ⚠️
4382
+ *
4383
+ * Performs a full-scorched-earth cache purge across the entire view subtree rooted
4384
+ * at `this`, then drives an immediate synchronous layout pass, as if the app had
4385
+ * just cold-started.
4386
+ *
4387
+ * What gets destroyed:
4388
+ * • Every per-instance intrinsic-size cache entry (`_intrinsicSizesCache`)
4389
+ * • Every shared intrinsic-size cache bucket (`UIView._sharedIntrinsicSizeCaches`)
4390
+ * • Every frame cache (`_frameCache`) and virtual-layout frame cache
4391
+ * (`_frameCacheForVirtualLayouting`) on every view in the subtree
4392
+ * • Every UITextMeasurement style cache (canvas glyph metrics,
4393
+ * computed-style snapshots, etc.)
4394
+ *
4395
+ * After the purge every view in the subtree is unconditionally marked dirty via
4396
+ * `setNeedsLayout()`, then `UIView.layoutViewsIfNeeded()` is called immediately
4397
+ * to flush the queue rather than waiting for the next rAF tick.
4398
+ *
4399
+ * Legitimate uses (exhaustive list):
4400
+ * - Recovery from a confirmed cache-corruption bug while a proper fix ships
4401
+ * - Test harness reset between isolated rendering assertions
4402
+ * - Post-hot-module-replacement refresh in development tooling
4403
+ *
4404
+ * Do NOT use this to fix a layout glitch you don't understand.
4405
+ * Fix the root cause instead. If you find yourself calling this in production
4406
+ * code for any reason other than the above, that is a bug — file it.
4407
+ *
4408
+ * Complexity: O(n) where n = total views in the subtree.
4409
+ * All shared caches are wiped globally, not just for the subtree.
4410
+ */
4411
+ performForcedSubtreeLayout(): void {
4412
+
4413
+ // 1. Nuke all text measurement caches globally — these are shared across
4414
+ // the entire document and cannot be scoped to a subtree.
4415
+ UITextMeasurement.clearCaches()
4416
+
4417
+ // 2. Wipe all shared intrinsic-size cache buckets globally. These are
4418
+ // keyed by sharedIntrinsicSizeCacheIdentifier and may be shared across
4419
+ // views outside this subtree, but stale global state is worse than a
4420
+ // cold remeasure of an unrelated view.
4421
+ UIView.invalidateAllSharedIntrinsicSizeCaches()
4422
+
4423
+ // 3. Walk every view in the subtree and obliterate all per-instance caches,
4424
+ // then unconditionally schedule it for layout.
4425
+ this.forEachViewInSubtree(view => {
4426
+ view._intrinsicSizesCache = {}
4427
+ view._frameCache = undefined
4428
+ view._frameCacheForVirtualLayouting = undefined
4429
+ view.setNeedsLayout()
4430
+ })
4431
+
4432
+ // 4. Drive the layout queue right now, synchronously — don't wait for the
4433
+ // scheduled rAF tick. Callers of this method expect the tree to be
4434
+ // fully laid out by the time the call returns.
4435
+ UIView.layoutViewsIfNeeded()
4436
+
4437
+ }
4438
+
4439
+
4366
4440
  }
4367
4441
 
4368
4442
 
@@ -187,6 +187,9 @@ export class UIViewController extends UIObject {
187
187
  if (IS(controller.view)) {
188
188
  controller.view.removeFromSuperview()
189
189
  }
190
+ else {
191
+ var asd = 1
192
+ }
190
193
  controller.viewDidDisappear()
191
194
 
192
195
  }
@@ -197,8 +200,12 @@ export class UIViewController extends UIObject {
197
200
  controller = FIRST_OR_NIL(controller)
198
201
  containerView = FIRST_OR_NIL(containerView)
199
202
  controller.viewWillAppear()
200
- this.addChildViewController(controller)
201
- containerView.addSubview(controller.view)
203
+ if (!this.hasChildViewController(controller)) {
204
+ controller.willMoveToParentViewController(this)
205
+ this.childViewControllers.push(controller)
206
+ containerView.addSubview(controller.view)
207
+ controller.didMoveToParentViewController(this)
208
+ }
202
209
 
203
210
  controller.handleRouteRecursively(UIRoute.currentRoute)
204
211