@nativescript/core 9.0.0-alpha.24 → 9.0.0-alpha.25

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.
Files changed (105) hide show
  1. package/application/application.android.d.ts +9 -2
  2. package/application/application.android.js +83 -6
  3. package/application/application.android.js.map +1 -1
  4. package/application/application.d.ts +8 -1
  5. package/application/application.ios.d.ts +13 -1
  6. package/application/application.ios.js +30 -3
  7. package/application/application.ios.js.map +1 -1
  8. package/application/helpers.android.d.ts +0 -9
  9. package/application/helpers.android.js +0 -54
  10. package/application/helpers.android.js.map +1 -1
  11. package/application/helpers.d.ts +0 -10
  12. package/application/helpers.ios.d.ts +0 -19
  13. package/application/helpers.ios.js +0 -38
  14. package/application/helpers.ios.js.map +1 -1
  15. package/connectivity/index.android.js +3 -3
  16. package/connectivity/index.android.js.map +1 -1
  17. package/core-types/index.d.ts +68 -63
  18. package/core-types/index.js.map +1 -1
  19. package/image-source/index.d.ts +2 -2
  20. package/index.js +0 -1
  21. package/index.js.map +1 -1
  22. package/package.json +1 -1
  23. package/platforms/android/widgets-release.aar +0 -0
  24. package/references.d.ts +1 -1
  25. package/ui/button/index.ios.js.map +1 -1
  26. package/ui/core/view/index.android.d.ts +4 -6
  27. package/ui/core/view/index.android.js +126 -271
  28. package/ui/core/view/index.android.js.map +1 -1
  29. package/ui/core/view/index.d.ts +1 -1
  30. package/ui/core/view/index.ios.d.ts +7 -0
  31. package/ui/core/view/index.ios.js +74 -35
  32. package/ui/core/view/index.ios.js.map +1 -1
  33. package/ui/core/view/view-common.d.ts +37 -5
  34. package/ui/core/view/view-common.js +21 -0
  35. package/ui/core/view/view-common.js.map +1 -1
  36. package/ui/core/view/view-interfaces.d.ts +3 -0
  37. package/ui/frame/index.android.js +55 -2
  38. package/ui/frame/index.android.js.map +1 -1
  39. package/ui/index.d.ts +1 -1
  40. package/ui/layouts/index.d.ts +2 -0
  41. package/ui/layouts/index.js +2 -0
  42. package/ui/layouts/index.js.map +1 -1
  43. package/ui/layouts/liquid-glass/index.android.d.ts +5 -0
  44. package/ui/layouts/liquid-glass/index.android.js +8 -0
  45. package/ui/layouts/liquid-glass/index.android.js.map +1 -0
  46. package/ui/layouts/liquid-glass/index.d.ts +10 -0
  47. package/ui/layouts/liquid-glass/index.ios.d.ts +10 -0
  48. package/ui/layouts/liquid-glass/index.ios.js +59 -0
  49. package/ui/layouts/liquid-glass/index.ios.js.map +1 -0
  50. package/ui/layouts/liquid-glass/liquid-glass-common.d.ts +3 -0
  51. package/ui/layouts/liquid-glass/liquid-glass-common.js +4 -0
  52. package/ui/layouts/liquid-glass/liquid-glass-common.js.map +1 -0
  53. package/ui/layouts/liquid-glass-container/index.android.d.ts +3 -0
  54. package/ui/layouts/liquid-glass-container/index.android.js +4 -0
  55. package/ui/layouts/liquid-glass-container/index.android.js.map +1 -0
  56. package/ui/layouts/liquid-glass-container/index.d.ts +3 -0
  57. package/ui/layouts/liquid-glass-container/index.ios.d.ts +14 -0
  58. package/ui/layouts/liquid-glass-container/index.ios.js +121 -0
  59. package/ui/layouts/liquid-glass-container/index.ios.js.map +1 -0
  60. package/ui/layouts/liquid-glass-container/liquid-glass-container-common.d.ts +4 -0
  61. package/ui/layouts/liquid-glass-container/liquid-glass-container-common.js +5 -0
  62. package/ui/layouts/liquid-glass-container/liquid-glass-container-common.js.map +1 -0
  63. package/ui/list-view/index.android.d.ts +26 -1
  64. package/ui/list-view/index.android.js +701 -23
  65. package/ui/list-view/index.android.js.map +1 -1
  66. package/ui/list-view/index.d.ts +122 -0
  67. package/ui/list-view/index.ios.d.ts +34 -2
  68. package/ui/list-view/index.ios.js +560 -9
  69. package/ui/list-view/index.ios.js.map +1 -1
  70. package/ui/list-view/list-view-common.d.ts +22 -1
  71. package/ui/list-view/list-view-common.js +85 -0
  72. package/ui/list-view/list-view-common.js.map +1 -1
  73. package/ui/styling/background-common.d.ts +4 -4
  74. package/ui/styling/background-common.js +8 -8
  75. package/ui/styling/background-common.js.map +1 -1
  76. package/ui/styling/background.d.ts +0 -3
  77. package/ui/styling/background.ios.d.ts +2 -1
  78. package/ui/styling/background.ios.js +47 -38
  79. package/ui/styling/background.ios.js.map +1 -1
  80. package/ui/styling/css-utils.d.ts +1 -0
  81. package/ui/styling/css-utils.js +15 -4
  82. package/ui/styling/css-utils.js.map +1 -1
  83. package/ui/styling/style/index.d.ts +2 -1
  84. package/ui/styling/style/index.js.map +1 -1
  85. package/ui/styling/style-properties.js +23 -11
  86. package/ui/styling/style-properties.js.map +1 -1
  87. package/ui/tab-view/index.android.js +22 -8
  88. package/ui/tab-view/index.android.js.map +1 -1
  89. package/ui/tab-view/index.ios.d.ts +1 -1
  90. package/ui/tab-view/index.ios.js +28 -9
  91. package/ui/tab-view/index.ios.js.map +1 -1
  92. package/ui/tab-view/tab-view-common.d.ts +2 -0
  93. package/ui/tab-view/tab-view-common.js +5 -0
  94. package/ui/tab-view/tab-view-common.js.map +1 -1
  95. package/utils/common.d.ts +3 -1
  96. package/utils/common.js +9 -3
  97. package/utils/common.js.map +1 -1
  98. package/utils/index.d.ts +6 -0
  99. package/utils/native-helper-for-android.d.ts +14 -3
  100. package/utils/native-helper-for-android.js +57 -54
  101. package/utils/native-helper-for-android.js.map +1 -1
  102. package/utils/native-helper.android.d.ts +3 -3
  103. package/utils/native-helper.android.js +2 -2
  104. package/utils/native-helper.android.js.map +1 -1
  105. package/utils/native-helper.d.ts +22 -5
@@ -1,13 +1,17 @@
1
- import { ListViewBase, separatorColorProperty, itemTemplatesProperty, iosEstimatedRowHeightProperty } from './list-view-common';
1
+ import { ListViewBase, separatorColorProperty, itemTemplatesProperty, iosEstimatedRowHeightProperty, stickyHeaderProperty, stickyHeaderTemplateProperty, stickyHeaderHeightProperty, sectionedProperty, showSearchProperty, searchAutoHideProperty } from './list-view-common';
2
2
  import { View } from '../core/view';
3
3
  import { Length } from '../styling/length-shared';
4
4
  import { Observable } from '../../data/observable';
5
5
  import { Color } from '../../color';
6
6
  import { layout } from '../../utils';
7
+ import { SDK_VERSION } from '../../utils/constants';
7
8
  import { StackLayout } from '../layouts/stack-layout';
8
9
  import { ProxyViewContainer } from '../proxy-view-container';
9
10
  import { profile } from '../../profiling';
10
11
  import { Trace } from '../../trace';
12
+ import { Builder } from '../builder';
13
+ import { Label } from '../label';
14
+ import { isFunction } from '../../utils/types';
11
15
  export * from './list-view-common';
12
16
  const ITEMLOADING = ListViewBase.itemLoadingEvent;
13
17
  const LOADMOREITEMS = ListViewBase.loadMoreItemsEvent;
@@ -48,6 +52,40 @@ var ListViewCell = /** @class */ (function (_super) {
48
52
  });
49
53
  return ListViewCell;
50
54
  }(UITableViewCell));
55
+ var ListViewHeaderCell = /** @class */ (function (_super) {
56
+ __extends(ListViewHeaderCell, _super);
57
+ function ListViewHeaderCell() {
58
+ return _super !== null && _super.apply(this, arguments) || this;
59
+ }
60
+ ListViewHeaderCell.initWithEmptyBackground = function () {
61
+ var cell = ListViewHeaderCell.new();
62
+ // Clear background by default - this will make headers transparent
63
+ cell.backgroundColor = UIColor.clearColor;
64
+ return cell;
65
+ };
66
+ ListViewHeaderCell.prototype.initWithReuseIdentifier = function (reuseIdentifier) {
67
+ var cell = _super.prototype.initWithReuseIdentifier.call(this, reuseIdentifier);
68
+ // Clear background by default - this will make headers transparent
69
+ cell.backgroundColor = UIColor.clearColor;
70
+ return cell;
71
+ };
72
+ ListViewHeaderCell.prototype.willMoveToSuperview = function (newSuperview) {
73
+ var parent = (this.view ? this.view.parent : null);
74
+ // When inside ListView and there is no newSuperview this header is
75
+ // removed from native visual tree so we remove it from our tree too.
76
+ if (parent && !newSuperview) {
77
+ parent._removeHeaderContainer(this);
78
+ }
79
+ };
80
+ Object.defineProperty(ListViewHeaderCell.prototype, "view", {
81
+ get: function () {
82
+ return this.owner ? this.owner.deref() : null;
83
+ },
84
+ enumerable: true,
85
+ configurable: true
86
+ });
87
+ return ListViewHeaderCell;
88
+ }(UITableViewHeaderFooterView));
51
89
  function notifyForItemAtIndex(listView, cell, view, eventName, indexPath) {
52
90
  const args = {
53
91
  eventName: eventName,
@@ -70,10 +108,30 @@ var DataSource = /** @class */ (function (_super) {
70
108
  dataSource._owner = owner;
71
109
  return dataSource;
72
110
  };
111
+ DataSource.prototype.numberOfSectionsInTableView = function (tableView) {
112
+ var _a;
113
+ var owner = (_a = this._owner) === null || _a === void 0 ? void 0 : _a.deref();
114
+ if (!owner) {
115
+ return 1;
116
+ }
117
+ var sections = owner._getSectionCount();
118
+ if (Trace.isEnabled()) {
119
+ Trace.write("ListView: numberOfSections = ".concat(sections, " (sectioned: ").concat(owner.sectioned, ")"), Trace.categories.Debug);
120
+ }
121
+ return sections;
122
+ };
73
123
  DataSource.prototype.tableViewNumberOfRowsInSection = function (tableView, section) {
74
124
  var _a;
75
125
  var owner = (_a = this._owner) === null || _a === void 0 ? void 0 : _a.deref();
76
- return owner && owner.items ? owner.items.length : 0;
126
+ if (!owner) {
127
+ return 0;
128
+ }
129
+ var sectionItems = owner._getItemsInSection(section);
130
+ var rowCount = sectionItems ? sectionItems.length : 0;
131
+ if (Trace.isEnabled()) {
132
+ Trace.write("ListView: numberOfRows in section ".concat(section, " = ").concat(rowCount), Trace.categories.Debug);
133
+ }
134
+ return rowCount;
77
135
  };
78
136
  DataSource.prototype.tableViewCellForRowAtIndexPath = function (tableView, indexPath) {
79
137
  var _a;
@@ -156,6 +214,46 @@ var UITableViewDelegateImpl = /** @class */ (function (_super) {
156
214
  }
157
215
  return layout.toDeviceIndependentPixels(height);
158
216
  };
217
+ UITableViewDelegateImpl.prototype.tableViewViewForHeaderInSection = function (tableView, section) {
218
+ var _a;
219
+ var owner = (_a = this._owner) === null || _a === void 0 ? void 0 : _a.deref();
220
+ if (!owner || !owner.stickyHeader || !owner.stickyHeaderTemplate) {
221
+ if (Trace.isEnabled()) {
222
+ Trace.write("ListView: No sticky header (stickyHeader: ".concat(owner === null || owner === void 0 ? void 0 : owner.stickyHeader, ", hasTemplate: ").concat(!!(owner === null || owner === void 0 ? void 0 : owner.stickyHeaderTemplate), ")"), Trace.categories.Debug);
223
+ }
224
+ return null;
225
+ }
226
+ if (Trace.isEnabled()) {
227
+ Trace.write("ListView: Creating sticky header", Trace.categories.Debug);
228
+ }
229
+ var headerReuseIdentifier = 'stickyHeader';
230
+ var headerCell = tableView.dequeueReusableHeaderFooterViewWithIdentifier(headerReuseIdentifier);
231
+ if (!headerCell) {
232
+ // Use proper iOS initialization for registered header cells
233
+ headerCell = ListViewHeaderCell.alloc().initWithReuseIdentifier(headerReuseIdentifier);
234
+ headerCell.backgroundColor = UIColor.clearColor;
235
+ }
236
+ owner._prepareHeader(headerCell, section);
237
+ return headerCell;
238
+ };
239
+ UITableViewDelegateImpl.prototype.tableViewHeightForHeaderInSection = function (tableView, section) {
240
+ var _a;
241
+ var owner = (_a = this._owner) === null || _a === void 0 ? void 0 : _a.deref();
242
+ if (!owner || !owner.stickyHeader) {
243
+ return 0;
244
+ }
245
+ var height;
246
+ if (owner.stickyHeaderHeight === 'auto') {
247
+ height = 44;
248
+ }
249
+ else {
250
+ height = layout.toDeviceIndependentPixels(Length.toDevicePixels(owner.stickyHeaderHeight, 44));
251
+ }
252
+ if (Trace.isEnabled()) {
253
+ Trace.write("ListView: Sticky header height: ".concat(height), Trace.categories.Debug);
254
+ }
255
+ return height;
256
+ };
159
257
  UITableViewDelegateImpl.ObjCProtocols = [UITableViewDelegate];
160
258
  return UITableViewDelegateImpl;
161
259
  }(NSObject));
@@ -200,14 +298,86 @@ var UITableViewRowHeightDelegateImpl = /** @class */ (function (_super) {
200
298
  }
201
299
  return layout.toDeviceIndependentPixels(owner._effectiveRowHeight);
202
300
  };
301
+ UITableViewRowHeightDelegateImpl.prototype.tableViewViewForHeaderInSection = function (tableView, section) {
302
+ var _a;
303
+ var owner = (_a = this._owner) === null || _a === void 0 ? void 0 : _a.deref();
304
+ if (!owner || !owner.stickyHeader || !owner.stickyHeaderTemplate) {
305
+ if (Trace.isEnabled()) {
306
+ Trace.write("ListView: No sticky header (stickyHeader: ".concat(owner === null || owner === void 0 ? void 0 : owner.stickyHeader, ", hasTemplate: ").concat(!!(owner === null || owner === void 0 ? void 0 : owner.stickyHeaderTemplate), ")"), Trace.categories.Debug);
307
+ }
308
+ return null;
309
+ }
310
+ if (Trace.isEnabled()) {
311
+ Trace.write("ListView: Creating sticky header", Trace.categories.Debug);
312
+ }
313
+ var headerReuseIdentifier = 'stickyHeader';
314
+ var headerCell = tableView.dequeueReusableHeaderFooterViewWithIdentifier(headerReuseIdentifier);
315
+ if (!headerCell) {
316
+ headerCell = ListViewHeaderCell.alloc().initWithReuseIdentifier(headerReuseIdentifier);
317
+ headerCell.backgroundColor = UIColor.clearColor;
318
+ }
319
+ owner._prepareHeader(headerCell, section);
320
+ return headerCell;
321
+ };
322
+ UITableViewRowHeightDelegateImpl.prototype.tableViewHeightForHeaderInSection = function (tableView, section) {
323
+ var _a;
324
+ var owner = (_a = this._owner) === null || _a === void 0 ? void 0 : _a.deref();
325
+ if (!owner || !owner.stickyHeader) {
326
+ return 0;
327
+ }
328
+ var height;
329
+ if (owner.stickyHeaderHeight === 'auto') {
330
+ height = 44;
331
+ }
332
+ else {
333
+ height = layout.toDeviceIndependentPixels(Length.toDevicePixels(owner.stickyHeaderHeight, 44));
334
+ }
335
+ if (Trace.isEnabled()) {
336
+ Trace.write("ListView: Sticky header height: ".concat(height), Trace.categories.Debug);
337
+ }
338
+ return height;
339
+ };
203
340
  UITableViewRowHeightDelegateImpl.ObjCProtocols = [UITableViewDelegate];
204
341
  return UITableViewRowHeightDelegateImpl;
205
342
  }(NSObject));
343
+ var UISearchResultsUpdatingImpl = /** @class */ (function (_super) {
344
+ __extends(UISearchResultsUpdatingImpl, _super);
345
+ function UISearchResultsUpdatingImpl() {
346
+ return _super !== null && _super.apply(this, arguments) || this;
347
+ }
348
+ UISearchResultsUpdatingImpl.initWithOwner = function (owner) {
349
+ var handler = UISearchResultsUpdatingImpl.new();
350
+ handler._owner = owner;
351
+ return handler;
352
+ };
353
+ UISearchResultsUpdatingImpl.prototype.updateSearchResultsForSearchController = function (searchController) {
354
+ var owner = this._owner ? this._owner.get() : null;
355
+ if (!owner) {
356
+ return;
357
+ }
358
+ var searchText = searchController.searchBar.text || '';
359
+ // Track search state
360
+ owner._isSearchActive = searchController.active;
361
+ // Create SearchEventData
362
+ var eventData = {
363
+ eventName: ListViewBase.searchChangeEvent,
364
+ object: owner,
365
+ text: searchText,
366
+ ios: searchController,
367
+ };
368
+ // Fire the searchChange event
369
+ owner.notify(eventData);
370
+ };
371
+ UISearchResultsUpdatingImpl.ObjCProtocols = [UISearchResultsUpdating];
372
+ return UISearchResultsUpdatingImpl;
373
+ }(NSObject));
206
374
  export class ListView extends ListViewBase {
207
375
  constructor() {
208
376
  super();
377
+ this._isSearchActive = false;
209
378
  this.widthMeasureSpec = 0;
210
379
  this._map = new Map();
380
+ this._headerMap = new Map();
211
381
  this._heights = new Array();
212
382
  }
213
383
  createNativeView() {
@@ -217,17 +387,125 @@ export class ListView extends ListViewBase {
217
387
  super.initNativeView();
218
388
  const nativeView = this.nativeViewProtected;
219
389
  nativeView.registerClassForCellReuseIdentifier(ListViewCell.class(), this._defaultTemplate.key);
390
+ nativeView.registerClassForHeaderFooterViewReuseIdentifier(ListViewHeaderCell.class(), 'stickyHeader');
220
391
  nativeView.estimatedRowHeight = DEFAULT_HEIGHT;
221
392
  nativeView.rowHeight = UITableViewAutomaticDimension;
222
393
  nativeView.dataSource = this._dataSource = DataSource.initWithOwner(new WeakRef(this));
223
394
  this._delegate = UITableViewDelegateImpl.initWithOwner(new WeakRef(this));
395
+ // Control section header top padding (iOS 15+)
396
+ if (nativeView.respondsToSelector('setSectionHeaderTopPadding:')) {
397
+ if (!this.stickyHeaderTopPadding) {
398
+ nativeView.sectionHeaderTopPadding = 0;
399
+ }
400
+ // When stickyHeaderTopPadding is true, don't set the property to use iOS default
401
+ }
224
402
  this._setNativeClipToBounds();
225
403
  }
226
404
  disposeNativeView() {
405
+ this._cleanupSearchController();
227
406
  this._delegate = null;
228
407
  this._dataSource = null;
229
408
  super.disposeNativeView();
230
409
  }
410
+ _setupSearchController() {
411
+ if (!this.showSearch || this._searchController) {
412
+ return; // Already setup or not needed
413
+ }
414
+ // 1. Create UISearchController with nil (show results in this table)
415
+ this._searchController = UISearchController.alloc().initWithSearchResultsController(null);
416
+ this._searchDelegate = UISearchResultsUpdatingImpl.initWithOwner(new WeakRef(this));
417
+ // 2. Tell it who will update results
418
+ this._searchController.searchResultsUpdater = this._searchDelegate;
419
+ // 3. Critical: Don't dim or obscure the table, and prevent extra content
420
+ this._searchController.obscuresBackgroundDuringPresentation = false;
421
+ this._searchController.dimsBackgroundDuringPresentation = false;
422
+ this._searchController.hidesNavigationBarDuringPresentation = false;
423
+ // 4. Placeholder text and styling
424
+ this._searchController.searchBar.placeholder = 'Search';
425
+ this._searchController.searchBar.searchBarStyle = 2 /* UISearchBarStyle.Minimal */;
426
+ // 5. CRITICAL: Proper presentation context setup
427
+ const viewController = this._getViewController();
428
+ if (viewController) {
429
+ viewController.definesPresentationContext = true;
430
+ viewController.providesPresentationContextTransitionStyle = true;
431
+ // 6a. If we're in a UINavigationController (iOS 11+)...
432
+ if (SDK_VERSION >= 11.0 && viewController.navigationItem) {
433
+ viewController.navigationItem.searchController = this._searchController;
434
+ // Set auto-hide behavior based on searchAutoHide property
435
+ viewController.navigationItem.hidesSearchBarWhenScrolling = this.searchAutoHide;
436
+ // Optional: Enable large titles for better auto-hide effect when searchAutoHide is true
437
+ // if (this.searchAutoHide && viewController.navigationController && viewController.navigationController.navigationBar) {
438
+ // // Only set large titles if not already configured
439
+ // if (!viewController.navigationController.navigationBar.prefersLargeTitles) {
440
+ // viewController.navigationController.navigationBar.prefersLargeTitles = true;
441
+ // }
442
+ // // Set large title display mode for this specific view controller
443
+ // if (viewController.navigationItem.largeTitleDisplayMode === UINavigationItemLargeTitleDisplayMode.Automatic) {
444
+ // viewController.navigationItem.largeTitleDisplayMode = UINavigationItemLargeTitleDisplayMode.Always;
445
+ // }
446
+ // }
447
+ }
448
+ else {
449
+ // 6b. Fallback: put it at the top of our table
450
+ this.nativeViewProtected.tableHeaderView = this._searchController.searchBar;
451
+ }
452
+ }
453
+ else {
454
+ // Fallback: no view controller found, use table header
455
+ this.nativeViewProtected.tableHeaderView = this._searchController.searchBar;
456
+ }
457
+ // 7. Ensure search bar is properly sized and prevent content inset issues
458
+ this._searchController.searchBar.sizeToFit();
459
+ // 8. Disable automatic content inset adjustment that can cause spacing issues
460
+ if (this.nativeViewProtected.respondsToSelector('setContentInsetAdjustmentBehavior:')) {
461
+ // iOS 11+ - prevent automatic content inset adjustments
462
+ this.nativeViewProtected.contentInsetAdjustmentBehavior = 2 /* UIScrollViewContentInsetAdjustmentBehavior.Never */;
463
+ }
464
+ else {
465
+ // iOS 10 and below - disable automatic content inset
466
+ this.nativeViewProtected.automaticallyAdjustsScrollIndicatorInsets = false;
467
+ }
468
+ if (Trace.isEnabled()) {
469
+ Trace.write(`ListView: UISearchController setup complete with searchAutoHide: ${this.searchAutoHide}`, Trace.categories.Debug);
470
+ }
471
+ }
472
+ _cleanupSearchController() {
473
+ if (!this._searchController) {
474
+ return;
475
+ }
476
+ // Remove search controller from navigation item or table header
477
+ const viewController = this._getViewController();
478
+ if (viewController && viewController.navigationItem && viewController.navigationItem.searchController === this._searchController) {
479
+ viewController.navigationItem.searchController = null;
480
+ }
481
+ else if (this.nativeViewProtected.tableHeaderView === this._searchController.searchBar) {
482
+ this.nativeViewProtected.tableHeaderView = null;
483
+ }
484
+ // Reset content inset adjustment behavior
485
+ if (this.nativeViewProtected.respondsToSelector('setContentInsetAdjustmentBehavior:')) {
486
+ // iOS 11+ - restore automatic content inset adjustments
487
+ this.nativeViewProtected.contentInsetAdjustmentBehavior = 0 /* UIScrollViewContentInsetAdjustmentBehavior.Automatic */;
488
+ }
489
+ else {
490
+ // iOS 10 and below - restore automatic content inset
491
+ this.nativeViewProtected.automaticallyAdjustsScrollIndicatorInsets = true;
492
+ }
493
+ // Cleanup references
494
+ this._searchController.searchResultsUpdater = null;
495
+ this._searchController = null;
496
+ this._searchDelegate = null;
497
+ }
498
+ _getViewController() {
499
+ // Helper to get the current view controller
500
+ let parent = this.parent;
501
+ while (parent) {
502
+ if (parent.viewController) {
503
+ return parent.viewController;
504
+ }
505
+ parent = parent.parent;
506
+ }
507
+ return null;
508
+ }
231
509
  _setNativeClipToBounds() {
232
510
  // Always set clipsToBounds for list-view
233
511
  const view = this.nativeViewProtected;
@@ -241,18 +519,25 @@ export class ListView extends ListViewBase {
241
519
  this.refresh();
242
520
  }
243
521
  this.nativeViewProtected.delegate = this._delegate;
522
+ // Setup search controller if enabled
523
+ if (this.showSearch) {
524
+ this._setupSearchController();
525
+ }
244
526
  }
245
527
  // @ts-ignore
246
528
  get ios() {
247
529
  return this.nativeViewProtected;
248
530
  }
249
531
  get _childrenCount() {
250
- return this._map.size;
532
+ return this._map.size + this._headerMap.size;
251
533
  }
252
534
  eachChildView(callback) {
253
535
  this._map.forEach((view, key) => {
254
536
  callback(view);
255
537
  });
538
+ this._headerMap.forEach((view, key) => {
539
+ callback(view);
540
+ });
256
541
  }
257
542
  scrollToIndex(index) {
258
543
  this._scrollToIndex(index, false);
@@ -286,6 +571,11 @@ export class ListView extends ListViewBase {
286
571
  view.bindingContext = null;
287
572
  }
288
573
  });
574
+ this._headerMap.forEach((view, nativeView, map) => {
575
+ if (!(view.bindingContext instanceof Observable)) {
576
+ view.bindingContext = null;
577
+ }
578
+ });
289
579
  if (this.isLoaded) {
290
580
  this.nativeViewProtected.reloadData();
291
581
  this.requestLayout();
@@ -324,8 +614,8 @@ export class ListView extends ListViewBase {
324
614
  super._onRowHeightPropertyChanged(oldValue, newValue);
325
615
  }
326
616
  requestLayout() {
327
- // When preparing cell don't call super - no need to invalidate our measure when cell desiredSize is changed.
328
- if (!this._preparingCell) {
617
+ // When preparing cell or header don't call super - no need to invalidate our measure when cell/header desiredSize is changed.
618
+ if (!this._preparingCell && !this._preparingHeader) {
329
619
  super.requestLayout();
330
620
  }
331
621
  }
@@ -343,6 +633,9 @@ export class ListView extends ListViewBase {
343
633
  this._map.forEach((childView, listViewCell) => {
344
634
  View.measureChild(this, childView, childView._currentWidthMeasureSpec, childView._currentHeightMeasureSpec);
345
635
  });
636
+ this._headerMap.forEach((childView, listViewHeaderCell) => {
637
+ View.measureChild(this, childView, childView._currentWidthMeasureSpec, childView._currentHeightMeasureSpec);
638
+ });
346
639
  }
347
640
  onLayout(left, top, right, bottom) {
348
641
  super.onLayout(left, top, right, bottom);
@@ -355,6 +648,12 @@ export class ListView extends ListViewBase {
355
648
  View.layoutChild(this, childView, 0, 0, width, cellHeight);
356
649
  }
357
650
  });
651
+ this._headerMap.forEach((childView, listViewHeaderCell) => {
652
+ const headerHeight = this.stickyHeaderHeight === 'auto' ? 44 : Length.toDevicePixels(this.stickyHeaderHeight, 44);
653
+ const width = layout.getMeasureSpecSize(this.widthMeasureSpec);
654
+ childView.iosOverflowSafeAreaEnabled = false;
655
+ View.layoutChild(this, childView, 0, 0, width, headerHeight);
656
+ });
358
657
  }
359
658
  _layoutCell(cellView, indexPath) {
360
659
  if (cellView) {
@@ -373,10 +672,21 @@ export class ListView extends ListViewBase {
373
672
  this._preparingCell = true;
374
673
  let view = cell.view;
375
674
  if (!view) {
376
- view = this._getItemTemplate(indexPath.row).createView();
675
+ if (this.sectioned) {
676
+ // For sectioned data, we need to calculate the absolute index for template selection
677
+ let absoluteIndex = 0;
678
+ for (let i = 0; i < indexPath.section; i++) {
679
+ absoluteIndex += this._getItemsInSection(i).length;
680
+ }
681
+ absoluteIndex += indexPath.row;
682
+ view = this._getItemTemplate(absoluteIndex).createView();
683
+ }
684
+ else {
685
+ view = this._getItemTemplate(indexPath.row).createView();
686
+ }
377
687
  }
378
688
  const args = notifyForItemAtIndex(this, cell, view, ITEMLOADING, indexPath);
379
- view = args.view || this._getDefaultItemContent(indexPath.row);
689
+ view = args.view || this._getDefaultItemContent(this.sectioned ? indexPath.row : indexPath.row);
380
690
  // Proxy containers should not get treated as layouts.
381
691
  // Wrap them in a real layout as well.
382
692
  if (view instanceof ProxyViewContainer) {
@@ -394,8 +704,15 @@ export class ListView extends ListViewBase {
394
704
  this._removeContainer(cell);
395
705
  cell.owner = new WeakRef(view);
396
706
  }
397
- this._prepareItem(view, indexPath.row);
398
- view._listViewItemIndex = indexPath.row;
707
+ if (this.sectioned) {
708
+ this._prepareItemInSection(view, indexPath.section, indexPath.row);
709
+ view._listViewItemIndex = indexPath.row; // Keep row index for compatibility
710
+ view._listViewSectionIndex = indexPath.section;
711
+ }
712
+ else {
713
+ this._prepareItem(view, indexPath.row);
714
+ view._listViewItemIndex = indexPath.row;
715
+ }
399
716
  this._map.set(cell, view);
400
717
  // We expect that views returned from itemLoading are new (e.g. not reused).
401
718
  if (view && !view.parent) {
@@ -423,6 +740,151 @@ export class ListView extends ListViewBase {
423
740
  this._preparingCell = preparing;
424
741
  this._map.delete(cell);
425
742
  }
743
+ _prepareHeader(headerCell, section) {
744
+ let headerHeight;
745
+ try {
746
+ this._preparingHeader = true;
747
+ let view = headerCell.view;
748
+ if (!view) {
749
+ view = this._getHeaderTemplate();
750
+ if (!view) {
751
+ if (Trace.isEnabled()) {
752
+ Trace.write(`ListView: Failed to create header view for section ${section}`, Trace.categories.Debug);
753
+ }
754
+ // Create a fallback view
755
+ const lbl = new Label();
756
+ lbl.text = `Section ${section}`;
757
+ view = lbl;
758
+ }
759
+ }
760
+ // Handle header cell reuse
761
+ if (!headerCell.view) {
762
+ headerCell.owner = new WeakRef(view);
763
+ }
764
+ else if (headerCell.view !== view) {
765
+ // Remove old view and set new one
766
+ headerCell.view.nativeViewProtected?.removeFromSuperview();
767
+ this._removeHeaderContainer(headerCell);
768
+ headerCell.owner = new WeakRef(view);
769
+ }
770
+ // Clear existing binding context and set new one
771
+ if (view.bindingContext) {
772
+ view.bindingContext = null;
773
+ }
774
+ if (this.sectioned) {
775
+ const sectionData = this._getSectionData(section);
776
+ if (sectionData) {
777
+ view.bindingContext = sectionData;
778
+ }
779
+ else {
780
+ // Fallback if section data is missing
781
+ view.bindingContext = { title: `Section ${section}`, section: section };
782
+ }
783
+ }
784
+ else {
785
+ view.bindingContext = this.bindingContext;
786
+ }
787
+ // Force immediate binding context evaluation
788
+ if (view && typeof view._onBindingContextChanged === 'function') {
789
+ view._onBindingContextChanged(null, view.bindingContext);
790
+ // Also trigger for child views
791
+ // @ts-ignore
792
+ if (view._childrenCount) {
793
+ view.eachChildView((child) => {
794
+ if (typeof child._onBindingContextChanged === 'function') {
795
+ child._onBindingContextChanged(null, view.bindingContext);
796
+ }
797
+ return true;
798
+ });
799
+ }
800
+ }
801
+ this._headerMap.set(headerCell, view);
802
+ // Add new header view to the cell
803
+ if (view && !view.parent) {
804
+ this._addView(view);
805
+ headerCell.contentView.addSubview(view.nativeViewProtected);
806
+ }
807
+ // Request layout and measure/layout the header
808
+ if (view && view.bindingContext) {
809
+ view.requestLayout();
810
+ }
811
+ headerHeight = this._layoutHeader(view);
812
+ }
813
+ finally {
814
+ this._preparingHeader = false;
815
+ }
816
+ return headerHeight;
817
+ }
818
+ _layoutHeader(headerView) {
819
+ if (headerView) {
820
+ const headerHeight = this.stickyHeaderHeight === 'auto' ? 44 : Length.toDevicePixels(this.stickyHeaderHeight, 44);
821
+ const heightMeasureSpec = layout.makeMeasureSpec(headerHeight, layout.EXACTLY);
822
+ const measuredSize = View.measureChild(this, headerView, this.widthMeasureSpec, heightMeasureSpec);
823
+ // Layout the header with the measured size
824
+ View.layoutChild(this, headerView, 0, 0, measuredSize.measuredWidth, measuredSize.measuredHeight);
825
+ return measuredSize.measuredHeight;
826
+ }
827
+ return 44;
828
+ }
829
+ _getHeaderTemplate() {
830
+ if (this.stickyHeaderTemplate) {
831
+ if (__UI_USE_EXTERNAL_RENDERER__) {
832
+ if (isFunction(this.stickyHeaderTemplate)) {
833
+ return this.stickyHeaderTemplate();
834
+ }
835
+ }
836
+ else {
837
+ if (typeof this.stickyHeaderTemplate === 'string') {
838
+ try {
839
+ const parsed = Builder.parse(this.stickyHeaderTemplate, this);
840
+ if (!parsed) {
841
+ // Create a simple fallback
842
+ const fallbackLabel = new Label();
843
+ fallbackLabel.text = 'Parse Failed';
844
+ return fallbackLabel;
845
+ }
846
+ return parsed;
847
+ }
848
+ catch (error) {
849
+ if (Trace.isEnabled()) {
850
+ Trace.write(`ListView: Template parsing error: ${error}`, Trace.categories.Debug);
851
+ }
852
+ // Create a simple fallback
853
+ const errorLabel = new Label();
854
+ errorLabel.text = 'Template Error';
855
+ return errorLabel;
856
+ }
857
+ }
858
+ else {
859
+ const view = this.stickyHeaderTemplate();
860
+ if (Trace.isEnabled()) {
861
+ Trace.write(`ListView: Created header view from template function: ${!!view} (type: ${view?.constructor?.name})`, Trace.categories.Debug);
862
+ }
863
+ return view;
864
+ }
865
+ }
866
+ }
867
+ if (Trace.isEnabled()) {
868
+ Trace.write(`ListView: No sticky header template, creating default`, Trace.categories.Debug);
869
+ }
870
+ // Return a default header if no template is provided
871
+ const lbl = new Label();
872
+ lbl.text = 'Default Header';
873
+ return lbl;
874
+ }
875
+ _removeHeaderContainer(headerCell) {
876
+ const view = headerCell.view;
877
+ // This is to clear the StackLayout that is used to wrap ProxyViewContainer instances.
878
+ if (!(view.parent instanceof ListView)) {
879
+ this._removeView(view.parent);
880
+ }
881
+ // No need to request layout when we are removing headers.
882
+ const preparing = this._preparingHeader;
883
+ this._preparingHeader = true;
884
+ view.parent._removeView(view);
885
+ this._preparingHeader = preparing;
886
+ this._headerMap.delete(headerCell);
887
+ }
426
888
  [separatorColorProperty.getDefault]() {
427
889
  return this.nativeViewProtected.separatorColor;
428
890
  }
@@ -450,6 +912,95 @@ export class ListView extends ListViewBase {
450
912
  const estimatedHeight = layout.toDeviceIndependentPixels(Length.toDevicePixels(value, 0));
451
913
  nativeView.estimatedRowHeight = estimatedHeight < 0 ? DEFAULT_HEIGHT : estimatedHeight;
452
914
  }
915
+ [stickyHeaderProperty.getDefault]() {
916
+ return false;
917
+ }
918
+ [stickyHeaderProperty.setNative](value) {
919
+ if (Trace.isEnabled()) {
920
+ Trace.write(`ListView: stickyHeader set to ${value}`, Trace.categories.Debug);
921
+ }
922
+ // Immediately refresh to apply changes
923
+ if (this.isLoaded) {
924
+ this.refresh();
925
+ }
926
+ }
927
+ [stickyHeaderTemplateProperty.getDefault]() {
928
+ return null;
929
+ }
930
+ [stickyHeaderTemplateProperty.setNative](value) {
931
+ if (Trace.isEnabled()) {
932
+ Trace.write(`ListView: stickyHeaderTemplate set: ${typeof value} ${value ? '(has value)' : '(null)'}`, Trace.categories.Debug);
933
+ }
934
+ // Clear any cached template
935
+ this._headerTemplateCache = null;
936
+ // Immediately refresh to apply changes
937
+ if (this.isLoaded) {
938
+ this.refresh();
939
+ }
940
+ }
941
+ [stickyHeaderHeightProperty.getDefault]() {
942
+ return 'auto';
943
+ }
944
+ [stickyHeaderHeightProperty.setNative](value) {
945
+ if (Trace.isEnabled()) {
946
+ Trace.write(`ListView: stickyHeaderHeight set to ${value}`, Trace.categories.Debug);
947
+ }
948
+ // Immediately refresh to apply changes
949
+ if (this.isLoaded) {
950
+ this.refresh();
951
+ }
952
+ }
953
+ [sectionedProperty.getDefault]() {
954
+ return false;
955
+ }
956
+ [sectionedProperty.setNative](value) {
957
+ if (Trace.isEnabled()) {
958
+ Trace.write(`ListView: sectioned set to ${value}`, Trace.categories.Debug);
959
+ }
960
+ // Immediately refresh to apply changes
961
+ if (this.isLoaded) {
962
+ this.refresh();
963
+ }
964
+ }
965
+ [showSearchProperty.getDefault]() {
966
+ return false;
967
+ }
968
+ [showSearchProperty.setNative](value) {
969
+ if (Trace.isEnabled()) {
970
+ Trace.write(`ListView: showSearch set to ${value}`, Trace.categories.Debug);
971
+ }
972
+ if (value) {
973
+ this._setupSearchController();
974
+ }
975
+ else {
976
+ this._cleanupSearchController();
977
+ }
978
+ }
979
+ [searchAutoHideProperty.getDefault]() {
980
+ return false;
981
+ }
982
+ [searchAutoHideProperty.setNative](value) {
983
+ if (Trace.isEnabled()) {
984
+ Trace.write(`ListView: searchAutoHide set to ${value}`, Trace.categories.Debug);
985
+ }
986
+ // If search is already enabled, update the existing search controller
987
+ if (this.showSearch && this._searchController) {
988
+ const viewController = this._getViewController();
989
+ if (viewController && viewController.navigationItem && SDK_VERSION >= 11.0) {
990
+ viewController.navigationItem.hidesSearchBarWhenScrolling = value;
991
+ // Enable large titles for better auto-hide effect when searchAutoHide is true
992
+ // if (value && viewController.navigationController && viewController.navigationController.navigationBar) {
993
+ // if (!viewController.navigationController.navigationBar.prefersLargeTitles) {
994
+ // viewController.navigationController.navigationBar.prefersLargeTitles = true;
995
+ // }
996
+ // if (viewController.navigationItem.largeTitleDisplayMode === UINavigationItemLargeTitleDisplayMode.Automatic) {
997
+ // viewController.navigationItem.largeTitleDisplayMode = UINavigationItemLargeTitleDisplayMode.Always;
998
+ // }
999
+ // }
1000
+ }
1001
+ }
1002
+ // If search is not enabled yet, the property will be used when _setupSearchController is called
1003
+ }
453
1004
  }
454
1005
  __decorate([
455
1006
  profile,