uicore-ts 1.1.101 → 1.1.105

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "uicore-ts",
3
- "version": "1.1.101",
3
+ "version": "1.1.105",
4
4
  "description": "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 that is used in IOS. In addition, UICore has tools to handle URL based routing, array sorting and filtering and adds a number of other utilities for convenience.",
5
5
  "main": "compiledScripts/index.js",
6
6
  "types": "compiledScripts/index.d.ts",
package/scripts/UICore.ts CHANGED
@@ -76,6 +76,11 @@ export class UICore extends UIObject {
76
76
 
77
77
  const windowDidResize = () => {
78
78
 
79
+ this.rootViewController.view.forEachViewInSubtree((view) => {
80
+
81
+ view._frameCache = undefined
82
+
83
+ })
79
84
  // Doing layout two times to prevent page scrollbars from confusing the layout
80
85
  this.rootViewController.view.setNeedsLayout()
81
86
  this.rootViewController._triggerLayoutViewSubviews()
@@ -60,6 +60,9 @@ export class UITableView extends UINativeScrollView {
60
60
 
61
61
  override animationDuration = 0.25
62
62
 
63
+ // Viewport scrolling properties
64
+ _intersectionObserver?: IntersectionObserver
65
+
63
66
 
64
67
  constructor(elementID?: string) {
65
68
 
@@ -72,6 +75,62 @@ export class UITableView extends UINativeScrollView {
72
75
 
73
76
  this.scrollsX = NO
74
77
 
78
+ this._setupViewportScrollAndResizeHandlersIfNeeded()
79
+
80
+ }
81
+
82
+
83
+ _windowScrollHandler = () => {
84
+ if (!this.isMemberOfViewTree) {
85
+ return
86
+ }
87
+ this._scheduleDrawVisibleRows()
88
+ }
89
+
90
+ _resizeHandler = () => {
91
+ if (!this.isMemberOfViewTree) {
92
+ return
93
+ }
94
+ // Invalidate all row positions on resize as widths may have changed
95
+ this._rowPositions.everyElement.isValid = NO
96
+ this._highestValidRowPositionIndex = -1
97
+ this._scheduleDrawVisibleRows()
98
+ }
99
+
100
+ _setupViewportScrollAndResizeHandlersIfNeeded() {
101
+ if (this._intersectionObserver) {
102
+ return
103
+ }
104
+
105
+ window.addEventListener("scroll", this._windowScrollHandler, { passive: true })
106
+ window.addEventListener("resize", this._resizeHandler, { passive: true })
107
+
108
+ // Use IntersectionObserver to detect when table enters/exits viewport
109
+ this._intersectionObserver = new IntersectionObserver(
110
+ (entries) => {
111
+ entries.forEach(entry => {
112
+ if (entry.isIntersecting && this.isMemberOfViewTree) {
113
+ this._scheduleDrawVisibleRows()
114
+ }
115
+ })
116
+ },
117
+ {
118
+ root: null,
119
+ rootMargin: "100% 0px", // Load rows 100% viewport height before/after
120
+ threshold: 0
121
+ }
122
+ )
123
+ this._intersectionObserver.observe(this.viewHTMLElement)
124
+ }
125
+
126
+
127
+ _cleanupViewportScrollListeners() {
128
+ window.removeEventListener("scroll", this._windowScrollHandler)
129
+ window.removeEventListener("resize", this._resizeHandler)
130
+ if (this._intersectionObserver) {
131
+ this._intersectionObserver.disconnect()
132
+ this._intersectionObserver = undefined
133
+ }
75
134
  }
76
135
 
77
136
 
@@ -128,8 +187,8 @@ export class UITableView extends UINativeScrollView {
128
187
 
129
188
 
130
189
  highlightRowAsNew(row: UIView) {
131
-
132
-
190
+
191
+
133
192
  }
134
193
 
135
194
 
@@ -141,14 +200,6 @@ export class UITableView extends UINativeScrollView {
141
200
 
142
201
  this._highestValidRowPositionIndex = Math.min(this._highestValidRowPositionIndex, index - 1)
143
202
 
144
- // if (index == 0) {
145
-
146
- // this._highestValidRowPositionIndex = 0;
147
-
148
- // this._rowPositions = [];
149
-
150
- // }
151
-
152
203
  this._needsDrawingOfVisibleRowsBeforeLayout = YES
153
204
 
154
205
  this._shouldAnimateNextLayout = animateChange
@@ -162,7 +213,7 @@ export class UITableView extends UINativeScrollView {
162
213
 
163
214
  _calculatePositionsUntilIndex(maxIndex: number) {
164
215
 
165
- var validPositionObject = this._rowPositions[this._highestValidRowPositionIndex]
216
+ let validPositionObject = this._rowPositions[this._highestValidRowPositionIndex]
166
217
  if (!IS(validPositionObject)) {
167
218
  validPositionObject = {
168
219
  bottomY: 0,
@@ -171,7 +222,7 @@ export class UITableView extends UINativeScrollView {
171
222
  }
172
223
  }
173
224
 
174
- var previousBottomY = validPositionObject.bottomY
225
+ let previousBottomY = validPositionObject.bottomY
175
226
 
176
227
  if (!this._rowPositions.length) {
177
228
 
@@ -179,9 +230,9 @@ export class UITableView extends UINativeScrollView {
179
230
 
180
231
  }
181
232
 
182
- for (var i = this._highestValidRowPositionIndex + 1; i <= maxIndex; i++) {
233
+ for (let i = this._highestValidRowPositionIndex + 1; i <= maxIndex; i++) {
183
234
 
184
- var height: number
235
+ let height: number
185
236
 
186
237
  const rowPositionObject = this._rowPositions[i]
187
238
 
@@ -219,47 +270,82 @@ export class UITableView extends UINativeScrollView {
219
270
 
220
271
  indexesForVisibleRows(paddingRatio = 0.5): number[] {
221
272
 
222
- const firstVisibleY = this.contentOffset.y - this.bounds.height * paddingRatio
223
- const lastVisibleY = firstVisibleY + this.bounds.height * (1 + paddingRatio)
273
+ // 1. Calculate the visible frame relative to the Table's bounds (0,0 is top-left of the table view)
274
+ // This accounts for the Window viewport clipping the table if it is partially off-screen.
275
+ const tableRect = this.viewHTMLElement.getBoundingClientRect()
276
+ const viewportHeight = window.innerHeight
277
+ const pageScale = UIView.pageScale
278
+
279
+ // The top of the visible window relative to the view's top edge.
280
+ // If tableRect.top is negative, the table is scrolled up and clipped by the window top.
281
+ const visibleFrameTop = Math.max(0, -tableRect.top / pageScale)
282
+
283
+ // The bottom of the visible window relative to the view's top edge.
284
+ // We clip it to the table's actual bounds height so we don't look past the table content.
285
+ const visibleFrameBottom = Math.min(
286
+ this.bounds.height,
287
+ (viewportHeight - tableRect.top) / pageScale
288
+ )
289
+
290
+ // If the table is completely off-screen, return empty
291
+ if (visibleFrameBottom <= visibleFrameTop) {
292
+ return []
293
+ }
294
+
295
+ // 2. Convert to Content Coordinates (Scroll Offset)
296
+ // contentOffset.y is the internal scroll position.
297
+ // If using viewport scrolling (full height), contentOffset.y is typically 0.
298
+ // If using internal scrolling, this shifts the visible frame to the correct content rows.
299
+ let firstVisibleY = this.contentOffset.y + visibleFrameTop
300
+ let lastVisibleY = this.contentOffset.y + visibleFrameBottom
301
+
302
+ // 3. Apply Padding
303
+ // We calculate padding based on the viewport height to ensure smooth scrolling
304
+ const paddingPx = (viewportHeight / pageScale) * paddingRatio
305
+ firstVisibleY = Math.max(0, firstVisibleY - paddingPx)
306
+ lastVisibleY = lastVisibleY + paddingPx
224
307
 
225
308
  const numberOfRows = this.numberOfRows()
226
309
 
310
+ // 4. Find Indexes
227
311
  if (this.allRowsHaveEqualHeight) {
228
312
 
229
313
  const rowHeight = this.heightForRowWithIndex(0)
230
314
 
231
- var firstIndex = firstVisibleY / rowHeight
232
- var lastIndex = lastVisibleY / rowHeight
233
-
234
- firstIndex = Math.trunc(firstIndex)
235
- lastIndex = Math.trunc(lastIndex) + 1
315
+ let firstIndex = Math.floor(firstVisibleY / rowHeight)
316
+ let lastIndex = Math.floor(lastVisibleY / rowHeight)
236
317
 
237
318
  firstIndex = Math.max(firstIndex, 0)
238
319
  lastIndex = Math.min(lastIndex, numberOfRows - 1)
239
320
 
240
- var result = []
241
- for (var i = firstIndex; i < lastIndex + 1; i++) {
321
+ const result = []
322
+ for (let i = firstIndex; i <= lastIndex; i++) {
242
323
  result.push(i)
243
324
  }
244
325
  return result
245
326
  }
246
327
 
247
- var accumulatedHeight = 0
248
- var result = []
249
-
328
+ // Variable Heights
250
329
  this._calculateAllPositions()
251
-
252
330
  const rowPositions = this._rowPositions
331
+ const result = []
253
332
 
254
- for (var i = 0; i < numberOfRows; i++) {
333
+ for (let i = 0; i < numberOfRows; i++) {
334
+
335
+ const position = rowPositions[i]
336
+ if (!position) {
337
+ break
338
+ }
255
339
 
256
- const height = rowPositions[i].bottomY - rowPositions[i].topY // this.heightForRowWithIndex(i)
340
+ const rowTop = position.topY
341
+ const rowBottom = position.bottomY
257
342
 
258
- accumulatedHeight = accumulatedHeight + height
259
- if (accumulatedHeight >= firstVisibleY) {
343
+ // Check intersection
344
+ if (rowBottom >= firstVisibleY && rowTop <= lastVisibleY) {
260
345
  result.push(i)
261
346
  }
262
- if (accumulatedHeight >= lastVisibleY) {
347
+
348
+ if (rowTop > lastVisibleY) {
263
349
  break
264
350
  }
265
351
 
@@ -312,75 +398,86 @@ export class UITableView extends UINativeScrollView {
312
398
  }
313
399
  }
314
400
 
401
+ _scheduleDrawVisibleRows() {
402
+ if (!this._isDrawVisibleRowsScheduled) {
403
+ this._isDrawVisibleRowsScheduled = YES
404
+
405
+ UIView.runFunctionBeforeNextFrame(() => {
406
+ this._calculateAllPositions()
407
+ this._drawVisibleRows()
408
+ this.setNeedsLayout()
409
+ this._isDrawVisibleRowsScheduled = NO
410
+ })
411
+ }
412
+ }
413
+
315
414
  _drawVisibleRows() {
316
415
 
317
416
  if (!this.isMemberOfViewTree) {
318
417
  return
319
418
  }
320
419
 
420
+ // Uses the unified method above
321
421
  const visibleIndexes = this.indexesForVisibleRows()
322
422
 
423
+ // If no rows are visible, remove all current rows
424
+ if (visibleIndexes.length === 0) {
425
+ this._removeVisibleRows()
426
+ return
427
+ }
428
+
323
429
  const minIndex = visibleIndexes[0]
324
430
  const maxIndex = visibleIndexes[visibleIndexes.length - 1]
325
431
 
326
432
  const removedViews: UITableViewRowView[] = []
327
-
328
433
  const visibleRows: UITableViewRowView[] = []
434
+
435
+ // 1. Identify rows that have moved off-screen
329
436
  this._visibleRows.forEach((row) => {
330
- if (IS_DEFINED(row._UITableViewRowIndex) && (row._UITableViewRowIndex < minIndex || row._UITableViewRowIndex > maxIndex)) {
331
-
332
- //row.removeFromSuperview();
437
+ if (IS_DEFINED(row._UITableViewRowIndex) &&
438
+ (row._UITableViewRowIndex < minIndex || row._UITableViewRowIndex > maxIndex)) {
333
439
 
440
+ // Persist state before removal
334
441
  this._persistedData[row._UITableViewRowIndex] = this.persistenceDataItemForRowWithIndex(
335
442
  row._UITableViewRowIndex,
336
443
  row
337
444
  )
338
445
 
339
446
  this._removedReusableViews[row._UITableViewReusabilityIdentifier].push(row)
340
-
341
447
  removedViews.push(row)
342
-
343
448
  }
344
449
  else {
345
450
  visibleRows.push(row)
346
451
  }
347
452
  })
453
+
348
454
  this._visibleRows = visibleRows
349
455
 
456
+ // 2. Add new rows that have moved on-screen
350
457
  visibleIndexes.forEach((rowIndex: number) => {
351
-
352
458
  if (this.isRowWithIndexVisible(rowIndex)) {
353
459
  return
354
460
  }
461
+
355
462
  const view: UITableViewRowView = this.viewForRowWithIndex(rowIndex)
356
- //view._UITableViewRowIndex = rowIndex;
357
463
  this._firstLayoutVisibleRows.push(view)
358
464
  this._visibleRows.push(view)
359
465
  this.addSubview(view)
360
-
361
466
  })
362
467
 
468
+ // 3. Clean up DOM
363
469
  for (let i = 0; i < removedViews.length; i++) {
364
-
365
470
  const view = removedViews[i]
366
471
  if (this._visibleRows.indexOf(view) == -1) {
367
-
368
- //this._persistedData[view._UITableViewRowIndex] = this.persistenceDataItemForRowWithIndex(view._UITableViewRowIndex, view);
369
472
  view.removeFromSuperview()
370
-
371
- //this._removedReusableViews[view._UITableViewReusabilityIdentifier].push(view);
372
-
373
473
  }
374
-
375
474
  }
376
475
 
377
- //this.setNeedsLayout();
378
-
379
476
  }
380
477
 
381
478
 
382
479
  visibleRowWithIndex(rowIndex: number | undefined): UIView {
383
- for (var i = 0; i < this._visibleRows.length; i++) {
480
+ for (let i = 0; i < this._visibleRows.length; i++) {
384
481
  const row = this._visibleRows[i]
385
482
  if (row._UITableViewRowIndex == rowIndex) {
386
483
  return row
@@ -449,13 +546,13 @@ export class UITableView extends UINativeScrollView {
449
546
  }
450
547
 
451
548
  defaultRowPersistenceDataItem(): any {
452
-
453
-
549
+
550
+
454
551
  }
455
552
 
456
553
  persistenceDataItemForRowWithIndex(rowIndex: number, row: UIView): any {
457
-
458
-
554
+
555
+
459
556
  }
460
557
 
461
558
  viewForRowWithIndex(rowIndex: number): UITableViewRowView {
@@ -473,34 +570,39 @@ export class UITableView extends UINativeScrollView {
473
570
 
474
571
  super.didScrollToPosition(offsetPosition)
475
572
 
476
- this.forEachViewInSubtree(function (view: UIView) {
477
-
573
+ this.forEachViewInSubtree((view: UIView) => {
478
574
  view._isPointerValid = NO
479
-
480
575
  })
481
576
 
482
- if (!this._isDrawVisibleRowsScheduled) {
483
-
484
- this._isDrawVisibleRowsScheduled = YES
485
-
486
- UIView.runFunctionBeforeNextFrame(function (this: UITableView) {
487
-
488
- this._calculateAllPositions()
489
-
490
- this._drawVisibleRows()
491
-
492
- this.setNeedsLayout()
493
-
494
- this._isDrawVisibleRowsScheduled = NO
495
-
496
- }.bind(this))
497
-
498
- }
577
+ this._scheduleDrawVisibleRows()
578
+
579
+ }
580
+
581
+ override willMoveToSuperview(superview: UIView) {
582
+ super.willMoveToSuperview(superview)
499
583
 
584
+ if (IS(superview)) {
585
+ // Set up viewport listeners when added to a superview
586
+ this._setupViewportScrollAndResizeHandlersIfNeeded()
587
+ }
588
+ else {
589
+ // Clean up when removed from superview
590
+ this._cleanupViewportScrollListeners()
591
+ }
500
592
  }
501
593
 
502
594
  override wasAddedToViewTree() {
595
+ super.wasAddedToViewTree()
503
596
  this.loadData()
597
+
598
+ // Ensure listeners are set up
599
+ this._setupViewportScrollAndResizeHandlersIfNeeded()
600
+
601
+ }
602
+
603
+ override wasRemovedFromViewTree() {
604
+ super.wasRemovedFromViewTree()
605
+ this._cleanupViewportScrollListeners()
504
606
  }
505
607
 
506
608
  override setFrame(rectangle: UIRectangle, zIndex?: number, performUncheckedLayout?: boolean) {
@@ -566,17 +668,15 @@ export class UITableView extends UINativeScrollView {
566
668
  this.animationDuration,
567
669
  0,
568
670
  undefined,
569
- function (this: UITableView) {
671
+ () => {
570
672
 
571
673
  this._layoutAllRows()
572
674
 
573
- }.bind(this),
574
- function (this: UITableView) {
575
-
576
- // this._calculateAllPositions()
577
- // this._layoutAllRows()
578
-
579
- }.bind(this)
675
+ },
676
+ () => {
677
+
678
+
679
+ }
580
680
  )
581
681
 
582
682
  }
@@ -584,14 +684,13 @@ export class UITableView extends UINativeScrollView {
584
684
 
585
685
  override layoutSubviews() {
586
686
 
587
- const previousPositions: UITableViewReusableViewPositionObject[] = JSON.parse(JSON.stringify(this._rowPositions))
687
+ const previousPositions: UITableViewReusableViewPositionObject[] = JSON.parse(
688
+ JSON.stringify(this._rowPositions))
588
689
 
589
690
  const previousVisibleRowsLength = this._visibleRows.length
590
691
 
591
692
  if (this._needsDrawingOfVisibleRowsBeforeLayout) {
592
693
 
593
- //this._calculateAllPositions()
594
-
595
694
  this._drawVisibleRows()
596
695
 
597
696
  this._needsDrawingOfVisibleRowsBeforeLayout = NO
@@ -620,11 +719,11 @@ export class UITableView extends UINativeScrollView {
620
719
  if (previousVisibleRowsLength < this._visibleRows.length) {
621
720
 
622
721
 
623
- UIView.runFunctionBeforeNextFrame(function (this: UITableView) {
722
+ UIView.runFunctionBeforeNextFrame(() => {
624
723
 
625
724
  this._animateLayoutAllRows()
626
725
 
627
- }.bind(this))
726
+ })
628
727
 
629
728
  }
630
729
  else {
@@ -639,14 +738,6 @@ export class UITableView extends UINativeScrollView {
639
738
  }
640
739
  else {
641
740
 
642
- // if (this._needsDrawingOfVisibleRowsBeforeLayout) {
643
-
644
- // this._drawVisibleRows();
645
-
646
- // this._needsDrawingOfVisibleRowsBeforeLayout = NO;
647
-
648
- // }
649
-
650
741
  this._calculateAllPositions()
651
742
 
652
743
  this._layoutAllRows()
@@ -661,7 +752,7 @@ export class UITableView extends UINativeScrollView {
661
752
  override intrinsicContentHeight(constrainingWidth = 0) {
662
753
 
663
754
 
664
- var result = 0
755
+ let result = 0
665
756
 
666
757
  this._calculateAllPositions()
667
758
 
@@ -677,35 +768,3 @@ export class UITableView extends UINativeScrollView {
677
768
 
678
769
 
679
770
  }
680
-
681
-
682
-
683
-
684
-
685
-
686
-
687
-
688
-
689
-
690
-
691
-
692
-
693
-
694
-
695
-
696
-
697
-
698
-
699
-
700
-
701
-
702
-
703
-
704
-
705
-
706
-
707
-
708
-
709
-
710
-
711
-