uicore-ts 1.1.88 → 1.1.101

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.
package/scripts/UIView.ts CHANGED
@@ -117,7 +117,7 @@ export type UIViewAddControlEventTargetObject<T extends { controlEvent: Record<s
117
117
  sender: UIView,
118
118
  event: Event
119
119
  ) => void) & Partial<UIViewAddControlEventTargetObject<T>>
120
-
120
+
121
121
  }>
122
122
 
123
123
 
@@ -228,13 +228,35 @@ export class UIView extends UIObject {
228
228
  private _resizeObserverEntry?: ResizeObserverEntry
229
229
  protected _intrinsicSizesCache: Record<string, UIRectangle> = {}
230
230
 
231
- static isVirtualLayouting = NO
231
+ private static _virtualLayoutingDepth = 0
232
+
233
+ static get isVirtualLayouting(): boolean {
234
+ return this._virtualLayoutingDepth > 0
235
+ }
236
+
237
+ get isVirtualLayouting(): boolean {
238
+ return UIView._virtualLayoutingDepth > 0
239
+ }
240
+
241
+ startVirtualLayout() {
242
+ UIView._virtualLayoutingDepth = UIView._virtualLayoutingDepth + 1
243
+ }
244
+
245
+ finishVirtualLayout() {
246
+ if (UIView._virtualLayoutingDepth === 0) {
247
+ throw new Error("Unbalanced finishVirtualLayout()")
248
+ }
249
+ UIView._virtualLayoutingDepth = UIView._virtualLayoutingDepth - 1
250
+ }
251
+
232
252
  _frameForVirtualLayouting?: UIRectangle
233
253
 
234
254
  // Change this to no if the view contains pure HTML content that does not
235
255
  // use frame logic that can influence the intrinsic size
236
256
  usesVirtualLayoutingForIntrinsicSizing = YES
237
257
 
258
+ _contentInsets = { top: 0, left: 0, bottom: 0, right: 0 }
259
+
238
260
 
239
261
  constructor(
240
262
  elementID: string = ("UIView" + UIView.nextIndex),
@@ -890,7 +912,7 @@ export class UIView extends UIObject {
890
912
 
891
913
  public get frame() {
892
914
  let result: UIRectangle & { zIndex?: number }
893
- if (!UIView.isVirtualLayouting) {
915
+ if (!this.isVirtualLayouting) {
894
916
  result = this._frame?.copy() as any
895
917
  }
896
918
  else {
@@ -923,7 +945,7 @@ export class UIView extends UIObject {
923
945
  if (zIndex != undefined) {
924
946
  rectangle.zIndex = zIndex
925
947
  }
926
- if (!UIView.isVirtualLayouting) {
948
+ if (!this.isVirtualLayouting) {
927
949
  this._frame = rectangle
928
950
  }
929
951
  else {
@@ -939,7 +961,7 @@ export class UIView extends UIObject {
939
961
  rectangle.scale(1 / this.scale)
940
962
  }
941
963
 
942
- if (!UIView.isVirtualLayouting) {
964
+ if (!this.isVirtualLayouting) {
943
965
  UIView._setAbsoluteSizeAndPosition(
944
966
  this.viewHTMLElement,
945
967
  rectangle.topLeft.x,
@@ -961,7 +983,7 @@ export class UIView extends UIObject {
961
983
  get bounds() {
962
984
 
963
985
  let _frame: (UIRectangle & { zIndex?: number }) | undefined
964
- if (!UIView.isVirtualLayouting) {
986
+ if (!this.isVirtualLayouting) {
965
987
  _frame = this._frame
966
988
  }
967
989
  else {
@@ -979,7 +1001,7 @@ export class UIView extends UIObject {
979
1001
  }
980
1002
  else {
981
1003
  let frame: (UIRectangle & { zIndex?: number })
982
- if (!UIView.isVirtualLayouting) {
1004
+ if (!this.isVirtualLayouting) {
983
1005
  frame = this.frame
984
1006
  }
985
1007
  else {
@@ -989,6 +1011,10 @@ export class UIView extends UIObject {
989
1011
  result.x = 0
990
1012
  result.y = 0
991
1013
  }
1014
+
1015
+ result.minHeight = 0
1016
+ result.minWidth = 0
1017
+
992
1018
  return result
993
1019
  }
994
1020
 
@@ -1579,17 +1605,52 @@ export class UIView extends UIObject {
1579
1605
 
1580
1606
 
1581
1607
  static layoutViewsIfNeeded() {
1582
- for (var i = 0; i < UIView._viewsToLayout.length; i++) {
1583
- const view = UIView._viewsToLayout[i]
1584
- view.layoutIfNeeded()
1608
+ const maxIterations = 10
1609
+ let iteration = 0
1610
+ const layoutCounts = new Map<UIView, number>() // Track how many times each view has been laid out
1611
+
1612
+ while (UIView._viewsToLayout.length > 0 && iteration < maxIterations) {
1613
+ const viewsToProcess = UIView._viewsToLayout
1614
+ UIView._viewsToLayout = []
1615
+
1616
+ const viewDepthMap = new Map<UIView, number>()
1617
+
1618
+ for (let i = 0; i < viewsToProcess.length; i++) {
1619
+ const view = viewsToProcess[i]
1620
+ const layoutCount = layoutCounts.get(view) || 0
1621
+
1622
+ // Skip if this view has been laid out too many times (cycle detection)
1623
+ if (layoutCount >= 5) {
1624
+ console.warn('View layout cycle detected:', view)
1625
+ continue
1626
+ }
1627
+
1628
+ if (!viewDepthMap.has(view)) {
1629
+ viewDepthMap.set(view, view.withAllSuperviews.length)
1630
+ }
1631
+ }
1632
+
1633
+ const sortedViews = Array.from(viewDepthMap.keys()).sort((a, b) => {
1634
+ return viewDepthMap.get(a)! - viewDepthMap.get(b)!
1635
+ })
1636
+
1637
+ for (let i = 0; i < sortedViews.length; i++) {
1638
+ const view = sortedViews[i]
1639
+ view.layoutIfNeeded()
1640
+ layoutCounts.set(view, (layoutCounts.get(view) || 0) + 1)
1641
+ }
1642
+
1643
+ iteration++
1585
1644
  }
1586
- UIView._viewsToLayout = []
1645
+
1646
+ // console.log(iteration + " iterations to finish layout")
1647
+
1587
1648
  }
1588
1649
 
1589
1650
 
1590
1651
  setNeedsLayout() {
1591
1652
 
1592
- if (this._shouldLayout) {
1653
+ if (this._shouldLayout && UIView._viewsToLayout.contains(this)) {
1593
1654
  return
1594
1655
  }
1595
1656
 
@@ -1598,7 +1659,7 @@ export class UIView extends UIObject {
1598
1659
  // Register view for layout before next frame
1599
1660
  UIView._viewsToLayout.push(this)
1600
1661
 
1601
- this._intrinsicSizesCache = {}
1662
+ this.clearIntrinsicSizeCache()
1602
1663
 
1603
1664
  if (UIView._viewsToLayout.length == 1) {
1604
1665
  UIView.scheduleLayoutViewsIfNeeded()
@@ -1620,7 +1681,9 @@ export class UIView extends UIObject {
1620
1681
  return
1621
1682
  }
1622
1683
 
1623
- this._shouldLayout = NO
1684
+ if (!this.isVirtualLayouting) {
1685
+ this._shouldLayout = NO
1686
+ }
1624
1687
 
1625
1688
  try {
1626
1689
 
@@ -1639,7 +1702,9 @@ export class UIView extends UIObject {
1639
1702
 
1640
1703
  this.willLayoutSubviews()
1641
1704
 
1642
- this._shouldLayout = NO
1705
+ if (!this.isVirtualLayouting) {
1706
+ this._shouldLayout = NO
1707
+ }
1643
1708
 
1644
1709
  // Autolayout
1645
1710
  if (this.constraints.length) {
@@ -3350,10 +3415,72 @@ export class UIView extends UIObject {
3350
3415
  }
3351
3416
 
3352
3417
 
3418
+ get contentBounds(): UIRectangle {
3419
+
3420
+ const bounds = this.bounds
3421
+ const insets = this._contentInsets
3422
+
3423
+ return new UIRectangle(
3424
+ insets.left,
3425
+ insets.top,
3426
+ bounds.height - insets.top - insets.bottom,
3427
+ bounds.width - insets.left - insets.right
3428
+ )
3429
+
3430
+ }
3431
+
3432
+ contentBoundsWithInset(inset: number): UIRectangle {
3433
+ this._contentInsets = { top: inset, left: inset, bottom: inset, right: inset }
3434
+ const bounds = this.bounds
3435
+ return new UIRectangle(
3436
+ inset,
3437
+ inset,
3438
+ bounds.height - inset * 2,
3439
+ bounds.width - inset * 2
3440
+ )
3441
+ }
3442
+
3443
+ contentBoundsWithInsets(
3444
+ left: number,
3445
+ right: number,
3446
+ bottom: number,
3447
+ top: number
3448
+ ): UIRectangle {
3449
+ this._contentInsets = { left, right, bottom, top }
3450
+ const bounds = this.bounds
3451
+ return new UIRectangle(
3452
+ left,
3453
+ top,
3454
+ bounds.height - top - bottom,
3455
+ bounds.width - left - right
3456
+ )
3457
+ }
3458
+
3459
+ private _getIntrinsicSizeCacheKey(constrainingHeight: number, constrainingWidth: number): string {
3460
+ return `h_${constrainingHeight}__w_${constrainingWidth}`
3461
+ }
3462
+
3463
+ private _getCachedIntrinsicSize(cacheKey: string): UIRectangle | undefined {
3464
+ return this._intrinsicSizesCache[cacheKey]
3465
+ }
3466
+
3467
+ private _setCachedIntrinsicSize(cacheKey: string, size: UIRectangle): void {
3468
+ this._intrinsicSizesCache[cacheKey] = size.copy()
3469
+ }
3470
+
3471
+ clearIntrinsicSizeCache(): void {
3472
+ this._intrinsicSizesCache = {}
3473
+
3474
+ // Optionally clear parent cache if this view affects parent's intrinsic size
3475
+ if (this.superview?.usesVirtualLayoutingForIntrinsicSizing) {
3476
+ this.superview.clearIntrinsicSizeCache()
3477
+ }
3478
+ }
3479
+
3353
3480
  intrinsicContentSizeWithConstraints(constrainingHeight: number = 0, constrainingWidth: number = 0) {
3354
3481
 
3355
- const cacheKey = "h_" + constrainingHeight + "__w_" + constrainingWidth
3356
- const cachedResult = this._intrinsicSizesCache[cacheKey]
3482
+ const cacheKey = this._getIntrinsicSizeCacheKey(constrainingHeight, constrainingWidth)
3483
+ const cachedResult = this._getCachedIntrinsicSize(cacheKey)
3357
3484
  if (cachedResult) {
3358
3485
  return cachedResult
3359
3486
  }
@@ -3425,100 +3552,120 @@ export class UIView extends UIObject {
3425
3552
  result.height = resultHeight
3426
3553
  result.width = resultWidth
3427
3554
 
3428
-
3429
- this._intrinsicSizesCache[cacheKey] = result.copy()
3555
+ this._setCachedIntrinsicSize(cacheKey, result)
3430
3556
 
3431
3557
  return result
3432
3558
 
3433
3559
  }
3434
3560
 
3561
+
3562
+ private _intrinsicFrameFromSubviewFrames() {
3563
+
3564
+ const framePoints: UIPoint[] = []
3565
+ this.subviews.forEach(subview => {
3566
+ subview.layoutIfNeeded()
3567
+ framePoints.push(subview.frame.min)
3568
+ framePoints.push(subview.frame.max)
3569
+ })
3570
+ // Since this is always called inside a virtual layouting context,
3571
+ // we will add padding based on the _contentInsets
3572
+ const resultFrame = UIRectangle.boundingBoxForPoints(framePoints)
3573
+ .rectangleWithInsets(
3574
+ -this._contentInsets?.left ?? 0,
3575
+ -this._contentInsets?.right ?? 0,
3576
+ -this._contentInsets?.bottom ?? 0,
3577
+ -this._contentInsets?.top ?? 0
3578
+ )
3579
+ return resultFrame
3580
+ }
3581
+
3582
+
3435
3583
  intrinsicContentWidth(constrainingHeight: number = 0): number {
3436
3584
 
3585
+ const cacheKey = this._getIntrinsicSizeCacheKey(constrainingHeight, 0)
3586
+ const cached = this._getCachedIntrinsicSize(cacheKey)
3587
+ if (cached) {
3588
+ return cached.width
3589
+ }
3590
+
3437
3591
  if (this.usesVirtualLayoutingForIntrinsicSizing) {
3438
- UIView.isVirtualLayouting = YES
3439
- this.frame = this.frame.rectangleWithHeight(constrainingHeight)
3440
- this.layoutIfNeeded()
3441
- const framePoints: UIPoint[] = []
3442
- this.forEachViewInSubtree(view => {
3443
- if (view == this) {
3444
- return
3445
- }
3446
- let frame = view.frame
3447
- if (view.superview != this) {
3448
- frame = view.rectangleInView(
3449
- frame,
3450
- this,
3451
- YES
3452
- )
3453
- }
3454
- framePoints.push(frame.min)
3455
- framePoints.push(frame.max)
3456
- })
3457
- const resultFrame = UIRectangle.boundingBoxForPoints(framePoints)
3458
- UIView.isVirtualLayouting = NO
3592
+ this.startVirtualLayout()
3593
+ let resultFrame: UIRectangle
3594
+ try {
3595
+ this.frame = this.frame.rectangleWithHeight(constrainingHeight)
3596
+ this.layoutIfNeeded()
3597
+ resultFrame = this._intrinsicFrameFromSubviewFrames()
3598
+ }
3599
+ finally {
3600
+ this.finishVirtualLayout()
3601
+ }
3602
+
3603
+ this._setCachedIntrinsicSize(cacheKey, new UIRectangle(0, 0, resultFrame.height, resultFrame.width))
3459
3604
  return resultFrame.width
3460
3605
  }
3461
3606
 
3462
- return this.intrinsicContentSizeWithConstraints(constrainingHeight).width
3607
+ const size = this.intrinsicContentSizeWithConstraints(constrainingHeight, 0)
3608
+ return size.width
3463
3609
 
3464
3610
  }
3465
3611
 
3612
+
3466
3613
  intrinsicContentHeight(constrainingWidth: number = 0): number {
3467
3614
 
3615
+ const cacheKey = this._getIntrinsicSizeCacheKey(0, constrainingWidth)
3616
+ const cached = this._getCachedIntrinsicSize(cacheKey)
3617
+ if (cached) {
3618
+ return cached.height
3619
+ }
3620
+
3468
3621
  if (this.usesVirtualLayoutingForIntrinsicSizing) {
3469
- UIView.isVirtualLayouting = YES
3470
- this.frame = this.frame.rectangleWithWidth(constrainingWidth)
3471
- this.layoutIfNeeded()
3472
- const framePoints: UIPoint[] = []
3473
- this.forEachViewInSubtree(view => {
3474
- if (view == this) {
3475
- return
3476
- }
3477
- let frame = view.frame
3478
- if (view.superview != this) {
3479
- frame = view.rectangleInView(
3480
- frame,
3481
- this,
3482
- YES
3483
- )
3484
- }
3485
- framePoints.push(frame.min)
3486
- framePoints.push(frame.max)
3487
- })
3488
- const resultFrame = UIRectangle.boundingBoxForPoints(framePoints)
3489
- UIView.isVirtualLayouting = NO
3622
+ this.startVirtualLayout()
3623
+ let resultFrame: UIRectangle
3624
+ try {
3625
+ this.frame = this.frame.rectangleWithWidth(constrainingWidth)
3626
+ this.layoutIfNeeded()
3627
+ resultFrame = this._intrinsicFrameFromSubviewFrames()
3628
+ }
3629
+ finally {
3630
+ this.finishVirtualLayout()
3631
+ }
3632
+
3633
+ this._setCachedIntrinsicSize(cacheKey, new UIRectangle(0, 0, resultFrame.height, resultFrame.width))
3490
3634
  return resultFrame.height
3491
3635
  }
3492
3636
 
3493
- return this.intrinsicContentSizeWithConstraints(undefined, constrainingWidth).height
3637
+ const size = this.intrinsicContentSizeWithConstraints(0, constrainingWidth)
3494
3638
 
3639
+ if (isNaN(size.height)) {
3640
+ console.error("NaN in intrinsicContentHeight", this)
3641
+ var asd = 1
3642
+ }
3643
+
3644
+ return size.height
3495
3645
 
3496
3646
  }
3497
3647
 
3498
3648
 
3499
3649
  intrinsicContentSize(): UIRectangle {
3500
3650
 
3651
+ const cacheKey = this._getIntrinsicSizeCacheKey(0, 0)
3652
+ const cached = this._getCachedIntrinsicSize(cacheKey)
3653
+ if (cached) {
3654
+ return cached
3655
+ }
3656
+
3501
3657
  if (this.usesVirtualLayoutingForIntrinsicSizing) {
3502
- UIView.isVirtualLayouting = YES
3503
- this.layoutIfNeeded()
3504
- const framePoints: UIPoint[] = []
3505
- this.forEachViewInSubtree(view => {
3506
- if (view == this) {
3507
- return
3508
- }
3509
- let frame = view.frame
3510
- if (view.superview != this) {
3511
- frame = view.rectangleInView(
3512
- frame,
3513
- this,
3514
- YES
3515
- )
3516
- }
3517
- framePoints.push(frame.min)
3518
- framePoints.push(frame.max)
3519
- })
3520
- const resultFrame = UIRectangle.boundingBoxForPoints(framePoints)
3521
- UIView.isVirtualLayouting = NO
3658
+ this.startVirtualLayout()
3659
+ let resultFrame: UIRectangle
3660
+ try {
3661
+ this.layoutIfNeeded()
3662
+ resultFrame = this._intrinsicFrameFromSubviewFrames()
3663
+ }
3664
+ finally {
3665
+ this.finishVirtualLayout()
3666
+ }
3667
+
3668
+ this._setCachedIntrinsicSize(cacheKey, resultFrame)
3522
3669
  return resultFrame
3523
3670
  }
3524
3671