uicore-ts 1.1.101 → 1.1.102

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.102",
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,12 @@ export class UITableView extends UINativeScrollView {
60
60
 
61
61
  override animationDuration = 0.25
62
62
 
63
+ // Viewport scrolling properties
64
+ private _useViewportScrolling = NO
65
+ private _windowScrollHandler?: () => void
66
+ private _resizeHandler?: () => void
67
+ private _intersectionObserver?: IntersectionObserver
68
+
63
69
 
64
70
  constructor(elementID?: string) {
65
71
 
@@ -72,6 +78,260 @@ export class UITableView extends UINativeScrollView {
72
78
 
73
79
  this.scrollsX = NO
74
80
 
81
+ // Automatically detect if we should use viewport scrolling
82
+ this._autoDetectScrollMode()
83
+
84
+ }
85
+
86
+
87
+ /**
88
+ * Automatically detect if this table should use viewport scrolling
89
+ * If the table's bounds height is >= the total content height,
90
+ * then it doesn't need internal scrolling and should use viewport scrolling
91
+ */
92
+ private _autoDetectScrollMode() {
93
+ // Run detection after the view is added to the tree and layout has occurred
94
+ const checkScrollMode = () => {
95
+ if (!this.isMemberOfViewTree) {
96
+ return
97
+ }
98
+
99
+ this._calculateAllPositions()
100
+
101
+ const totalContentHeight = this._rowPositions.length > 0
102
+ ? this._rowPositions[this._rowPositions.length - 1].bottomY
103
+ : 0
104
+
105
+ const tableBoundsHeight = this.bounds.height
106
+
107
+ // If the table's height can contain all content, use viewport scrolling
108
+ if (tableBoundsHeight >= totalContentHeight) {
109
+ this.enableViewportBasedVirtualScrolling()
110
+ }
111
+ }
112
+
113
+ // Check after first layout
114
+ UIView.runFunctionBeforeNextFrame(checkScrollMode)
115
+ }
116
+
117
+
118
+ /**
119
+ * Enable viewport-based virtual scrolling for full-height tables
120
+ * This allows the table to be part of the page flow while still
121
+ * benefiting from virtual scrolling performance
122
+ */
123
+ enableViewportBasedVirtualScrolling() {
124
+ if (this._useViewportScrolling) {
125
+ return // Already enabled
126
+ }
127
+
128
+ this._useViewportScrolling = YES
129
+ this.scrollsX = NO
130
+ this.scrollsY = NO
131
+
132
+ // No style changes - respect the absolute positioning system
133
+
134
+ this._setupViewportScrollListeners()
135
+ }
136
+
137
+
138
+ /**
139
+ * Disable viewport scrolling and return to normal scroll behavior
140
+ */
141
+ disableViewportBasedVirtualScrolling() {
142
+ if (!this._useViewportScrolling) {
143
+ return
144
+ }
145
+
146
+ this._useViewportScrolling = NO
147
+ this.scrollsY = YES
148
+
149
+ this._cleanupViewportScrollListeners()
150
+ }
151
+
152
+
153
+ private _setupViewportScrollListeners() {
154
+ this._windowScrollHandler = () => {
155
+ this._scheduleDrawVisibleRowsInViewport()
156
+ }
157
+
158
+ this._resizeHandler = () => {
159
+ // Invalidate all row positions on resize as widths may have changed
160
+ this._rowPositions.forEach(pos => pos.isValid = NO)
161
+ this._highestValidRowPositionIndex = -1
162
+ this._scheduleDrawVisibleRowsInViewport()
163
+ }
164
+
165
+ window.addEventListener('scroll', this._windowScrollHandler, { passive: true })
166
+ window.addEventListener('resize', this._resizeHandler, { passive: true })
167
+
168
+ // Use IntersectionObserver to detect when table enters/exits viewport
169
+ this._intersectionObserver = new IntersectionObserver(
170
+ (entries) => {
171
+ entries.forEach(entry => {
172
+ if (entry.isIntersecting) {
173
+ this._scheduleDrawVisibleRowsInViewport()
174
+ }
175
+ })
176
+ },
177
+ {
178
+ root: null,
179
+ rootMargin: '100% 0px', // Load rows 100% viewport height before/after
180
+ threshold: 0
181
+ }
182
+ )
183
+
184
+ this._intersectionObserver.observe(this.viewHTMLElement)
185
+ }
186
+
187
+
188
+ private _cleanupViewportScrollListeners() {
189
+ if (this._windowScrollHandler) {
190
+ window.removeEventListener('scroll', this._windowScrollHandler)
191
+ this._windowScrollHandler = undefined
192
+ }
193
+
194
+ if (this._resizeHandler) {
195
+ window.removeEventListener('resize', this._resizeHandler)
196
+ this._resizeHandler = undefined
197
+ }
198
+
199
+ if (this._intersectionObserver) {
200
+ this._intersectionObserver.disconnect()
201
+ this._intersectionObserver = undefined
202
+ }
203
+ }
204
+
205
+
206
+ private _scheduleDrawVisibleRowsInViewport() {
207
+ if (!this._isDrawVisibleRowsScheduled) {
208
+ this._isDrawVisibleRowsScheduled = YES
209
+
210
+ UIView.runFunctionBeforeNextFrame(() => {
211
+ this._calculateAllPositions()
212
+ this._drawVisibleRowsInViewport()
213
+ this.setNeedsLayout()
214
+ this._isDrawVisibleRowsScheduled = NO
215
+ })
216
+ }
217
+ }
218
+
219
+
220
+ /**
221
+ * Calculate which rows are visible in the browser viewport
222
+ * rather than in the scrollview's content area
223
+ */
224
+ indexesForVisibleRowsInViewport(paddingRatio = 0.5): number[] {
225
+ const tableRect = this.viewHTMLElement.getBoundingClientRect()
226
+ const viewportHeight = window.innerHeight
227
+ const pageScale = UIView.pageScale
228
+
229
+ // Calculate which part of the table is visible in viewport
230
+ // Account for page scale when converting from screen to content coordinates
231
+ const visibleTop = Math.max(0, -tableRect.top / pageScale)
232
+ const visibleBottom = Math.min(tableRect.height / pageScale, (viewportHeight - tableRect.top) / pageScale)
233
+
234
+ // Add padding to render rows slightly before they enter viewport
235
+ const paddingPx = (viewportHeight / pageScale) * paddingRatio
236
+ const firstVisibleY = Math.max(0, visibleTop - paddingPx)
237
+ const lastVisibleY = Math.min(tableRect.height / pageScale, visibleBottom + paddingPx)
238
+
239
+ const numberOfRows = this.numberOfRows()
240
+
241
+ if (this.allRowsHaveEqualHeight) {
242
+ const rowHeight = this.heightForRowWithIndex(0)
243
+
244
+ let firstIndex = Math.floor(firstVisibleY / rowHeight)
245
+ let lastIndex = Math.floor(lastVisibleY / rowHeight)
246
+
247
+ firstIndex = Math.max(firstIndex, 0)
248
+ lastIndex = Math.min(lastIndex, numberOfRows - 1)
249
+
250
+ const result = []
251
+ for (let i = firstIndex; i <= lastIndex; i++) {
252
+ result.push(i)
253
+ }
254
+ return result
255
+ }
256
+
257
+ // Variable height rows
258
+ this._calculateAllPositions()
259
+
260
+ const rowPositions = this._rowPositions
261
+ const result = []
262
+
263
+ for (let i = 0; i < numberOfRows; i++) {
264
+ const position = rowPositions[i]
265
+ if (!position) break
266
+
267
+ const rowTop = position.topY
268
+ const rowBottom = position.bottomY
269
+
270
+ // Check if row intersects with visible area
271
+ if (rowBottom >= firstVisibleY && rowTop <= lastVisibleY) {
272
+ result.push(i)
273
+ }
274
+
275
+ // Early exit if we've passed the visible area
276
+ if (rowTop > lastVisibleY) {
277
+ break
278
+ }
279
+ }
280
+
281
+ return result
282
+ }
283
+
284
+
285
+ /**
286
+ * Draw visible rows based on viewport position
287
+ */
288
+ private _drawVisibleRowsInViewport() {
289
+ if (!this.isMemberOfViewTree) {
290
+ return
291
+ }
292
+
293
+ const visibleIndexes = this.indexesForVisibleRowsInViewport()
294
+
295
+ const minIndex = visibleIndexes[0]
296
+ const maxIndex = visibleIndexes[visibleIndexes.length - 1]
297
+
298
+ const removedViews: UITableViewRowView[] = []
299
+
300
+ const visibleRows: UITableViewRowView[] = []
301
+ this._visibleRows.forEach((row) => {
302
+ if (IS_DEFINED(row._UITableViewRowIndex) &&
303
+ (row._UITableViewRowIndex < minIndex || row._UITableViewRowIndex > maxIndex)) {
304
+
305
+ this._persistedData[row._UITableViewRowIndex] = this.persistenceDataItemForRowWithIndex(
306
+ row._UITableViewRowIndex,
307
+ row
308
+ )
309
+
310
+ this._removedReusableViews[row._UITableViewReusabilityIdentifier].push(row)
311
+ removedViews.push(row)
312
+ }
313
+ else {
314
+ visibleRows.push(row)
315
+ }
316
+ })
317
+ this._visibleRows = visibleRows
318
+
319
+ visibleIndexes.forEach((rowIndex: number) => {
320
+ if (this.isRowWithIndexVisible(rowIndex)) {
321
+ return
322
+ }
323
+ const view: UITableViewRowView = this.viewForRowWithIndex(rowIndex)
324
+ this._firstLayoutVisibleRows.push(view)
325
+ this._visibleRows.push(view)
326
+ this.addSubview(view)
327
+ })
328
+
329
+ for (let i = 0; i < removedViews.length; i++) {
330
+ const view = removedViews[i]
331
+ if (this._visibleRows.indexOf(view) == -1) {
332
+ view.removeFromSuperview()
333
+ }
334
+ }
75
335
  }
76
336
 
77
337
 
@@ -128,8 +388,8 @@ export class UITableView extends UINativeScrollView {
128
388
 
129
389
 
130
390
  highlightRowAsNew(row: UIView) {
131
-
132
-
391
+
392
+
133
393
  }
134
394
 
135
395
 
@@ -141,14 +401,6 @@ export class UITableView extends UINativeScrollView {
141
401
 
142
402
  this._highestValidRowPositionIndex = Math.min(this._highestValidRowPositionIndex, index - 1)
143
403
 
144
- // if (index == 0) {
145
-
146
- // this._highestValidRowPositionIndex = 0;
147
-
148
- // this._rowPositions = [];
149
-
150
- // }
151
-
152
404
  this._needsDrawingOfVisibleRowsBeforeLayout = YES
153
405
 
154
406
  this._shouldAnimateNextLayout = animateChange
@@ -162,7 +414,7 @@ export class UITableView extends UINativeScrollView {
162
414
 
163
415
  _calculatePositionsUntilIndex(maxIndex: number) {
164
416
 
165
- var validPositionObject = this._rowPositions[this._highestValidRowPositionIndex]
417
+ let validPositionObject = this._rowPositions[this._highestValidRowPositionIndex]
166
418
  if (!IS(validPositionObject)) {
167
419
  validPositionObject = {
168
420
  bottomY: 0,
@@ -171,7 +423,7 @@ export class UITableView extends UINativeScrollView {
171
423
  }
172
424
  }
173
425
 
174
- var previousBottomY = validPositionObject.bottomY
426
+ let previousBottomY = validPositionObject.bottomY
175
427
 
176
428
  if (!this._rowPositions.length) {
177
429
 
@@ -179,9 +431,9 @@ export class UITableView extends UINativeScrollView {
179
431
 
180
432
  }
181
433
 
182
- for (var i = this._highestValidRowPositionIndex + 1; i <= maxIndex; i++) {
434
+ for (let i = this._highestValidRowPositionIndex + 1; i <= maxIndex; i++) {
183
435
 
184
- var height: number
436
+ let height: number
185
437
 
186
438
  const rowPositionObject = this._rowPositions[i]
187
439
 
@@ -219,6 +471,12 @@ export class UITableView extends UINativeScrollView {
219
471
 
220
472
  indexesForVisibleRows(paddingRatio = 0.5): number[] {
221
473
 
474
+ // If using viewport scrolling, delegate to viewport method
475
+ if (this._useViewportScrolling) {
476
+ return this.indexesForVisibleRowsInViewport(paddingRatio)
477
+ }
478
+
479
+ // Original scroll-based logic
222
480
  const firstVisibleY = this.contentOffset.y - this.bounds.height * paddingRatio
223
481
  const lastVisibleY = firstVisibleY + this.bounds.height * (1 + paddingRatio)
224
482
 
@@ -228,8 +486,8 @@ export class UITableView extends UINativeScrollView {
228
486
 
229
487
  const rowHeight = this.heightForRowWithIndex(0)
230
488
 
231
- var firstIndex = firstVisibleY / rowHeight
232
- var lastIndex = lastVisibleY / rowHeight
489
+ let firstIndex = firstVisibleY / rowHeight
490
+ let lastIndex = lastVisibleY / rowHeight
233
491
 
234
492
  firstIndex = Math.trunc(firstIndex)
235
493
  lastIndex = Math.trunc(lastIndex) + 1
@@ -237,23 +495,23 @@ export class UITableView extends UINativeScrollView {
237
495
  firstIndex = Math.max(firstIndex, 0)
238
496
  lastIndex = Math.min(lastIndex, numberOfRows - 1)
239
497
 
240
- var result = []
241
- for (var i = firstIndex; i < lastIndex + 1; i++) {
498
+ const result = []
499
+ for (let i = firstIndex; i < lastIndex + 1; i++) {
242
500
  result.push(i)
243
501
  }
244
502
  return result
245
503
  }
246
504
 
247
- var accumulatedHeight = 0
248
- var result = []
505
+ let accumulatedHeight = 0
506
+ const result = []
249
507
 
250
508
  this._calculateAllPositions()
251
509
 
252
510
  const rowPositions = this._rowPositions
253
511
 
254
- for (var i = 0; i < numberOfRows; i++) {
512
+ for (let i = 0; i < numberOfRows; i++) {
255
513
 
256
- const height = rowPositions[i].bottomY - rowPositions[i].topY // this.heightForRowWithIndex(i)
514
+ const height = rowPositions[i].bottomY - rowPositions[i].topY
257
515
 
258
516
  accumulatedHeight = accumulatedHeight + height
259
517
  if (accumulatedHeight >= firstVisibleY) {
@@ -329,8 +587,6 @@ export class UITableView extends UINativeScrollView {
329
587
  this._visibleRows.forEach((row) => {
330
588
  if (IS_DEFINED(row._UITableViewRowIndex) && (row._UITableViewRowIndex < minIndex || row._UITableViewRowIndex > maxIndex)) {
331
589
 
332
- //row.removeFromSuperview();
333
-
334
590
  this._persistedData[row._UITableViewRowIndex] = this.persistenceDataItemForRowWithIndex(
335
591
  row._UITableViewRowIndex,
336
592
  row
@@ -353,7 +609,6 @@ export class UITableView extends UINativeScrollView {
353
609
  return
354
610
  }
355
611
  const view: UITableViewRowView = this.viewForRowWithIndex(rowIndex)
356
- //view._UITableViewRowIndex = rowIndex;
357
612
  this._firstLayoutVisibleRows.push(view)
358
613
  this._visibleRows.push(view)
359
614
  this.addSubview(view)
@@ -365,22 +620,17 @@ export class UITableView extends UINativeScrollView {
365
620
  const view = removedViews[i]
366
621
  if (this._visibleRows.indexOf(view) == -1) {
367
622
 
368
- //this._persistedData[view._UITableViewRowIndex] = this.persistenceDataItemForRowWithIndex(view._UITableViewRowIndex, view);
369
623
  view.removeFromSuperview()
370
624
 
371
- //this._removedReusableViews[view._UITableViewReusabilityIdentifier].push(view);
372
-
373
625
  }
374
626
 
375
627
  }
376
628
 
377
- //this.setNeedsLayout();
378
-
379
629
  }
380
630
 
381
631
 
382
632
  visibleRowWithIndex(rowIndex: number | undefined): UIView {
383
- for (var i = 0; i < this._visibleRows.length; i++) {
633
+ for (let i = 0; i < this._visibleRows.length; i++) {
384
634
  const row = this._visibleRows[i]
385
635
  if (row._UITableViewRowIndex == rowIndex) {
386
636
  return row
@@ -449,13 +699,13 @@ export class UITableView extends UINativeScrollView {
449
699
  }
450
700
 
451
701
  defaultRowPersistenceDataItem(): any {
452
-
453
-
702
+
703
+
454
704
  }
455
705
 
456
706
  persistenceDataItemForRowWithIndex(rowIndex: number, row: UIView): any {
457
-
458
-
707
+
708
+
459
709
  }
460
710
 
461
711
  viewForRowWithIndex(rowIndex: number): UITableViewRowView {
@@ -473,6 +723,11 @@ export class UITableView extends UINativeScrollView {
473
723
 
474
724
  super.didScrollToPosition(offsetPosition)
475
725
 
726
+ // Skip if using viewport scrolling
727
+ if (this._useViewportScrolling) {
728
+ return
729
+ }
730
+
476
731
  this.forEachViewInSubtree(function (view: UIView) {
477
732
 
478
733
  view._isPointerValid = NO
@@ -483,7 +738,7 @@ export class UITableView extends UINativeScrollView {
483
738
 
484
739
  this._isDrawVisibleRowsScheduled = YES
485
740
 
486
- UIView.runFunctionBeforeNextFrame(function (this: UITableView) {
741
+ UIView.runFunctionBeforeNextFrame(() => {
487
742
 
488
743
  this._calculateAllPositions()
489
744
 
@@ -493,14 +748,25 @@ export class UITableView extends UINativeScrollView {
493
748
 
494
749
  this._isDrawVisibleRowsScheduled = NO
495
750
 
496
- }.bind(this))
751
+ })
497
752
 
498
753
  }
499
754
 
500
755
  }
501
756
 
502
757
  override wasAddedToViewTree() {
758
+ super.wasAddedToViewTree()
503
759
  this.loadData()
760
+
761
+ // Re-check scroll mode in case CSS was applied after construction
762
+ if (!this._useViewportScrolling) {
763
+ this._autoDetectScrollMode()
764
+ }
765
+ }
766
+
767
+ override wasRemovedFromViewTree() {
768
+ super.wasRemovedFromViewTree()
769
+ this._cleanupViewportScrollListeners()
504
770
  }
505
771
 
506
772
  override setFrame(rectangle: UIRectangle, zIndex?: number, performUncheckedLayout?: boolean) {
@@ -552,8 +818,17 @@ export class UITableView extends UINativeScrollView {
552
818
 
553
819
  })
554
820
 
555
- this._fullHeightView.frame = bounds.rectangleWithHeight((positions.lastElement ||
556
- nil).bottomY).rectangleWithWidth(bounds.width * 0.5)
821
+ // For viewport scrolling, the full height view needs to establish the total height
822
+ if (this._useViewportScrolling) {
823
+ this._fullHeightView.hidden = NO
824
+ this._fullHeightView.style.position = 'absolute'
825
+ this._fullHeightView.style.pointerEvents = 'none'
826
+ this._fullHeightView.frame = bounds.rectangleWithHeight((positions.lastElement || nil).bottomY)
827
+ .rectangleWithWidth(1) // Minimal width
828
+ } else {
829
+ this._fullHeightView.frame = bounds.rectangleWithHeight((positions.lastElement || nil).bottomY)
830
+ .rectangleWithWidth(bounds.width * 0.5)
831
+ }
557
832
 
558
833
  this._firstLayoutVisibleRows = []
559
834
 
@@ -566,17 +841,15 @@ export class UITableView extends UINativeScrollView {
566
841
  this.animationDuration,
567
842
  0,
568
843
  undefined,
569
- function (this: UITableView) {
844
+ () => {
570
845
 
571
846
  this._layoutAllRows()
572
847
 
573
- }.bind(this),
574
- function (this: UITableView) {
575
-
576
- // this._calculateAllPositions()
577
- // this._layoutAllRows()
578
-
579
- }.bind(this)
848
+ },
849
+ () => {
850
+
851
+
852
+ }
580
853
  )
581
854
 
582
855
  }
@@ -590,9 +863,11 @@ export class UITableView extends UINativeScrollView {
590
863
 
591
864
  if (this._needsDrawingOfVisibleRowsBeforeLayout) {
592
865
 
593
- //this._calculateAllPositions()
594
-
595
- this._drawVisibleRows()
866
+ if (this._useViewportScrolling) {
867
+ this._drawVisibleRowsInViewport()
868
+ } else {
869
+ this._drawVisibleRows()
870
+ }
596
871
 
597
872
  this._needsDrawingOfVisibleRowsBeforeLayout = NO
598
873
 
@@ -620,11 +895,11 @@ export class UITableView extends UINativeScrollView {
620
895
  if (previousVisibleRowsLength < this._visibleRows.length) {
621
896
 
622
897
 
623
- UIView.runFunctionBeforeNextFrame(function (this: UITableView) {
898
+ UIView.runFunctionBeforeNextFrame(() => {
624
899
 
625
900
  this._animateLayoutAllRows()
626
901
 
627
- }.bind(this))
902
+ })
628
903
 
629
904
  }
630
905
  else {
@@ -639,14 +914,6 @@ export class UITableView extends UINativeScrollView {
639
914
  }
640
915
  else {
641
916
 
642
- // if (this._needsDrawingOfVisibleRowsBeforeLayout) {
643
-
644
- // this._drawVisibleRows();
645
-
646
- // this._needsDrawingOfVisibleRowsBeforeLayout = NO;
647
-
648
- // }
649
-
650
917
  this._calculateAllPositions()
651
918
 
652
919
  this._layoutAllRows()
@@ -661,7 +928,7 @@ export class UITableView extends UINativeScrollView {
661
928
  override intrinsicContentHeight(constrainingWidth = 0) {
662
929
 
663
930
 
664
- var result = 0
931
+ let result = 0
665
932
 
666
933
  this._calculateAllPositions()
667
934
 
@@ -677,35 +944,3 @@ export class UITableView extends UINativeScrollView {
677
944
 
678
945
 
679
946
  }
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
-