From 7eb7a7fd7b303d145567f490263d80079a170cf3 Mon Sep 17 00:00:00 2001
From: Punyapal Shah <53343069+MrPunyapal@users.noreply.github.com>
Date: Mon, 21 Sep 2026 21:30:51 +0530
Subject: [PATCH 01/14] Add configurable list end reached threshold
---
src/Elements/NativeList.php | 3 +++
1 file changed, 3 insertions(+)
diff --git a/src/Elements/NativeList.php b/src/Elements/NativeList.php
index ad3d5da..c3ef55f 100644
--- a/src/Elements/NativeList.php
+++ b/src/Elements/NativeList.php
@@ -46,6 +46,9 @@ public function applyAttributes(array $attrs): void
if (isset($attrs['on-end-reached']) || isset($attrs['onEndReached'])) {
$this->onEndReached($attrs['on-end-reached'] ?? $attrs['onEndReached']);
}
+ if (isset($attrs['end-reached-threshold']) || isset($attrs['endReachedThreshold'])) {
+ $this->endReachedThreshold((int) ($attrs['end-reached-threshold'] ?? $attrs['endReachedThreshold']));
+ }
$this->applyA11yAttributes($attrs);
}
From cb9ebc2b0f1700f5f80fa3a6ce61f606a9389c3e Mon Sep 17 00:00:00 2001
From: Punyapal Shah <53343069+MrPunyapal@users.noreply.github.com>
Date: Mon, 21 Sep 2026 21:30:57 +0530
Subject: [PATCH 02/14] Add configurable list end reached threshold
---
resources/android/ListRenderer.kt | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/resources/android/ListRenderer.kt b/resources/android/ListRenderer.kt
index ef1bb81..f033129 100644
--- a/resources/android/ListRenderer.kt
+++ b/resources/android/ListRenderer.kt
@@ -59,19 +59,20 @@ object ListRenderer {
val separator = node.props.getBool("separator")
val onRefreshCb = node.props.getCallbackId("on_refresh")
val onEndReachedCb = node.props.getCallbackId("on_end_reached")
+ val endReachedThreshold = node.props.getInt("end_reached_threshold", 3).coerceAtLeast(0)
val scrollState = rememberLazyListState()
val isRefreshing = remember { mutableStateOf(false) }
val endReachedFired = remember { mutableStateOf(false) }
- // Detect end reached — fire when within 3 items of the bottom
+ // Detect end reached — fire when within the configured number of items of the bottom
if (onEndReachedCb != 0) {
LaunchedEffect(scrollState) {
snapshotFlow {
val info = scrollState.layoutInfo
val lastVisible = info.visibleItemsInfo.lastOrNull()?.index ?: 0
val total = info.totalItemsCount
- total > 0 && lastVisible >= total - 3
+ total > 0 && lastVisible >= total - endReachedThreshold
}.collect { nearEnd ->
if (nearEnd && !endReachedFired.value) {
endReachedFired.value = true
From 2a8daecddc5df239276e7b09d6ae0e63d7a5f0e9 Mon Sep 17 00:00:00 2001
From: Punyapal Shah <53343069+MrPunyapal@users.noreply.github.com>
Date: Mon, 21 Sep 2026 21:31:01 +0530
Subject: [PATCH 03/14] Add configurable list end reached threshold
---
resources/ios/NativeUIListRenderer.swift | 16 ++++++++++------
1 file changed, 10 insertions(+), 6 deletions(-)
diff --git a/resources/ios/NativeUIListRenderer.swift b/resources/ios/NativeUIListRenderer.swift
index 3611017..662baac 100644
--- a/resources/ios/NativeUIListRenderer.swift
+++ b/resources/ios/NativeUIListRenderer.swift
@@ -34,6 +34,7 @@ struct NativeUIListRenderer: View {
let separator = node.props.getBool("separator")
let onRefreshCb = node.props.getCallbackId("on_refresh")
let onEndReachedCb = node.props.getCallbackId("on_end_reached")
+ let endReachedThreshold = max(0, node.props.getInt("end_reached_threshold", default: 3))
let nodeId = node.id
let children = node.children
@@ -68,7 +69,8 @@ struct NativeUIListRenderer: View {
rowView(row, separator: separator, isLastInSection: index == child.children.count - 1)
.onAppear {
fireEndReached(rowId: row.id, leafIndex: leafIndex,
- leafCount: leafCount, cb: onEndReachedCb, nodeId: nodeId)
+ leafCount: leafCount, threshold: endReachedThreshold,
+ cb: onEndReachedCb, nodeId: nodeId)
}
}
} header: {
@@ -80,7 +82,8 @@ struct NativeUIListRenderer: View {
rowView(child, separator: separator)
.onAppear {
fireEndReached(rowId: child.id, leafIndex: leafIndex,
- leafCount: leafCount, cb: onEndReachedCb, nodeId: nodeId)
+ leafCount: leafCount, threshold: endReachedThreshold,
+ cb: onEndReachedCb, nodeId: nodeId)
}
}
}
@@ -149,11 +152,12 @@ struct NativeUIListRenderer: View {
}
}
- /// Fire the end-reached callback when a row within the last 3 leaf rows
- /// appears. Works across sections via the precomputed leaf index.
- private func fireEndReached(rowId: Int, leafIndex: [Int: Int], leafCount: Int, cb: Int, nodeId: Int) {
+ /// Fire the end-reached callback when a row within the configured number
+ /// of leaf rows appears. Works across sections via the precomputed leaf index.
+ private func fireEndReached(rowId: Int, leafIndex: [Int: Int], leafCount: Int,
+ threshold: Int, cb: Int, nodeId: Int) {
guard cb != 0, let gi = leafIndex[rowId] else { return }
- if gi >= leafCount - 3 {
+ if gi >= leafCount - threshold {
NativeElementBridge.sendPressEvent(cb, nodeId: nodeId)
}
}
From 53290daf04523719ff24ddf515c8b0732ed9b138 Mon Sep 17 00:00:00 2001
From: Punyapal Shah <53343069+MrPunyapal@users.noreply.github.com>
Date: Mon, 21 Sep 2026 21:31:05 +0530
Subject: [PATCH 04/14] Test list end reached threshold
---
tests/ListTest.php | 19 +++++++++++++++++++
1 file changed, 19 insertions(+)
diff --git a/tests/ListTest.php b/tests/ListTest.php
index ffaec08..ecc1aaf 100644
--- a/tests/ListTest.php
+++ b/tests/ListTest.php
@@ -71,3 +71,22 @@ function collectList(array $attrs): array
expect($opaque->toArray(new CallbackRegistry)['props']['transparent'])->toBeFalse();
});
+
+
+it('serializes the end reached threshold from attributes', function () {
+ $tree = collectList(['end-reached-threshold' => 5]);
+
+ expect($tree['props']['end_reached_threshold'])->toBe(5);
+});
+
+it('accepts the end reached threshold on the fluent builder', function () {
+ $list = NativeList::make()->endReachedThreshold(5);
+
+ expect($list->toArray(new CallbackRegistry)['props']['end_reached_threshold'])->toBe(5);
+});
+
+it('clamps a negative end reached threshold to zero', function () {
+ $list = NativeList::make()->endReachedThreshold(-1);
+
+ expect($list->toArray(new CallbackRegistry)['props']['end_reached_threshold'])->toBe(0);
+});
From 5213ae3a9e34ae077fe318ee215e3e10cdd7bf1f Mon Sep 17 00:00:00 2001
From: Punyapal Shah <53343069+MrPunyapal@users.noreply.github.com>
Date: Mon, 21 Sep 2026 22:57:39 +0530
Subject: [PATCH 05/14] Add configurable list end reached buffer
---
src/Elements/NativeList.php | 11 +++++++++--
1 file changed, 9 insertions(+), 2 deletions(-)
diff --git a/src/Elements/NativeList.php b/src/Elements/NativeList.php
index c3ef55f..ecac078 100644
--- a/src/Elements/NativeList.php
+++ b/src/Elements/NativeList.php
@@ -46,8 +46,8 @@ public function applyAttributes(array $attrs): void
if (isset($attrs['on-end-reached']) || isset($attrs['onEndReached'])) {
$this->onEndReached($attrs['on-end-reached'] ?? $attrs['onEndReached']);
}
- if (isset($attrs['end-reached-threshold']) || isset($attrs['endReachedThreshold'])) {
- $this->endReachedThreshold((int) ($attrs['end-reached-threshold'] ?? $attrs['endReachedThreshold']));
+ if (isset($attrs['end-reached-buffer']) || isset($attrs['endReachedBuffer'])) {
+ $this->endReachedBuffer((int) ($attrs['end-reached-buffer'] ?? $attrs['endReachedBuffer']));
}
$this->applyA11yAttributes($attrs);
@@ -117,6 +117,13 @@ public function onEndReached(string $method): static
return $this;
}
+ public function endReachedBuffer(int $items): static
+ {
+ $this->listProps['end_reached_buffer'] = max(1, $items);
+
+ return $this;
+ }
+
protected function resolveProps(CallbackRegistry $registry): array
{
$props = $this->listProps;
From 4b850f0aedeaa586be40c32699b0b194d65c05bd Mon Sep 17 00:00:00 2001
From: Punyapal Shah <53343069+MrPunyapal@users.noreply.github.com>
Date: Mon, 21 Sep 2026 22:57:43 +0530
Subject: [PATCH 06/14] Rename list end reached threshold to buffer
---
resources/android/ListRenderer.kt | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/resources/android/ListRenderer.kt b/resources/android/ListRenderer.kt
index f033129..4ab702a 100644
--- a/resources/android/ListRenderer.kt
+++ b/resources/android/ListRenderer.kt
@@ -59,7 +59,7 @@ object ListRenderer {
val separator = node.props.getBool("separator")
val onRefreshCb = node.props.getCallbackId("on_refresh")
val onEndReachedCb = node.props.getCallbackId("on_end_reached")
- val endReachedThreshold = node.props.getInt("end_reached_threshold", 3).coerceAtLeast(0)
+ val endReachedBuffer = node.props.getInt("end_reached_buffer", 3).coerceAtLeast(1)
val scrollState = rememberLazyListState()
val isRefreshing = remember { mutableStateOf(false) }
From fdb8b40fecfec12792c24dc6f918648a687c5e07 Mon Sep 17 00:00:00 2001
From: Punyapal Shah <53343069+MrPunyapal@users.noreply.github.com>
Date: Mon, 21 Sep 2026 22:57:47 +0530
Subject: [PATCH 07/14] Rename list end reached threshold to buffer
---
resources/ios/NativeUIListRenderer.swift | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/resources/ios/NativeUIListRenderer.swift b/resources/ios/NativeUIListRenderer.swift
index 662baac..928a4d2 100644
--- a/resources/ios/NativeUIListRenderer.swift
+++ b/resources/ios/NativeUIListRenderer.swift
@@ -34,7 +34,7 @@ struct NativeUIListRenderer: View {
let separator = node.props.getBool("separator")
let onRefreshCb = node.props.getCallbackId("on_refresh")
let onEndReachedCb = node.props.getCallbackId("on_end_reached")
- let endReachedThreshold = max(0, node.props.getInt("end_reached_threshold", default: 3))
+ let endReachedBuffer = max(1, node.props.getInt("end_reached_buffer", default: 3))
let nodeId = node.id
let children = node.children
@@ -155,9 +155,9 @@ struct NativeUIListRenderer: View {
/// Fire the end-reached callback when a row within the configured number
/// of leaf rows appears. Works across sections via the precomputed leaf index.
private func fireEndReached(rowId: Int, leafIndex: [Int: Int], leafCount: Int,
- threshold: Int, cb: Int, nodeId: Int) {
+ buffer: Int, cb: Int, nodeId: Int) {
guard cb != 0, let gi = leafIndex[rowId] else { return }
- if gi >= leafCount - threshold {
+ if gi >= leafCount - buffer {
NativeElementBridge.sendPressEvent(cb, nodeId: nodeId)
}
}
From 5a3b154420009df2b5476ea13f6214e8405450e7 Mon Sep 17 00:00:00 2001
From: Punyapal Shah <53343069+MrPunyapal@users.noreply.github.com>
Date: Mon, 21 Sep 2026 22:57:52 +0530
Subject: [PATCH 08/14] Test list end reached buffer
---
tests/ListTest.php | 18 +++++++++---------
1 file changed, 9 insertions(+), 9 deletions(-)
diff --git a/tests/ListTest.php b/tests/ListTest.php
index ecc1aaf..0328d78 100644
--- a/tests/ListTest.php
+++ b/tests/ListTest.php
@@ -73,20 +73,20 @@ function collectList(array $attrs): array
});
-it('serializes the end reached threshold from attributes', function () {
- $tree = collectList(['end-reached-threshold' => 5]);
+it('serializes the end reached buffer from attributes', function () {
+ $tree = collectList(['end-reached-buffer' => 5]);
- expect($tree['props']['end_reached_threshold'])->toBe(5);
+ expect($tree['props']['end_reached_buffer'])->toBe(5);
});
-it('accepts the end reached threshold on the fluent builder', function () {
- $list = NativeList::make()->endReachedThreshold(5);
+it('accepts the end reached buffer on the fluent builder', function () {
+ $list = NativeList::make()->endReachedBuffer(5);
- expect($list->toArray(new CallbackRegistry)['props']['end_reached_threshold'])->toBe(5);
+ expect($list->toArray(new CallbackRegistry)['props']['end_reached_buffer'])->toBe(5);
});
-it('clamps a negative end reached threshold to zero', function () {
- $list = NativeList::make()->endReachedThreshold(-1);
+it('clamps a negative end reached buffer to one', function () {
+ $list = NativeList::make()->endReachedBuffer(-1);
- expect($list->toArray(new CallbackRegistry)['props']['end_reached_threshold'])->toBe(0);
+ expect($list->toArray(new CallbackRegistry)['props']['end_reached_threshold'})->toBe(1);
});
From 3fbc069b5fd3d05f90c79f53e934c992c4c48c79 Mon Sep 17 00:00:00 2001
From: Punyapal Shah <53343069+MrPunyapal@users.noreply.github.com>
Date: Mon, 21 Sep 2026 22:58:15 +0530
Subject: [PATCH 09/14] Fix Android list end reached buffer
---
resources/android/ListRenderer.kt | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/resources/android/ListRenderer.kt b/resources/android/ListRenderer.kt
index 4ab702a..0a9d200 100644
--- a/resources/android/ListRenderer.kt
+++ b/resources/android/ListRenderer.kt
@@ -72,7 +72,7 @@ object ListRenderer {
val info = scrollState.layoutInfo
val lastVisible = info.visibleItemsInfo.lastOrNull()?.index ?: 0
val total = info.totalItemsCount
- total > 0 && lastVisible >= total - endReachedThreshold
+ total > 0 && lastVisible >= total - endReachedBuffer
}.collect { nearEnd ->
if (nearEnd && !endReachedFired.value) {
endReachedFired.value = true
From 24224893f8014083b559e6952e0632dcb7f00662 Mon Sep 17 00:00:00 2001
From: Punyapal Shah <53343069+MrPunyapal@users.noreply.github.com>
Date: Mon, 21 Sep 2026 22:58:19 +0530
Subject: [PATCH 10/14] Fix iOS list end reached buffer
---
resources/ios/NativeUIListRenderer.swift | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/resources/ios/NativeUIListRenderer.swift b/resources/ios/NativeUIListRenderer.swift
index 928a4d2..e84fced 100644
--- a/resources/ios/NativeUIListRenderer.swift
+++ b/resources/ios/NativeUIListRenderer.swift
@@ -69,7 +69,7 @@ struct NativeUIListRenderer: View {
rowView(row, separator: separator, isLastInSection: index == child.children.count - 1)
.onAppear {
fireEndReached(rowId: row.id, leafIndex: leafIndex,
- leafCount: leafCount, threshold: endReachedThreshold,
+ leafCount: leafCount, buffer: endReachedBuffer,
cb: onEndReachedCb, nodeId: nodeId)
}
}
From 2dc94ab981323fea750a26fdbfcfd55130097fc2 Mon Sep 17 00:00:00 2001
From: Punyapal Shah <53343069+MrPunyapal@users.noreply.github.com>
Date: Mon, 21 Sep 2026 22:58:23 +0530
Subject: [PATCH 11/14] Fix list end reached buffer test
---
tests/ListTest.php | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/tests/ListTest.php b/tests/ListTest.php
index 0328d78..782857c 100644
--- a/tests/ListTest.php
+++ b/tests/ListTest.php
@@ -88,5 +88,5 @@ function collectList(array $attrs): array
it('clamps a negative end reached buffer to one', function () {
$list = NativeList::make()->endReachedBuffer(-1);
- expect($list->toArray(new CallbackRegistry)['props']['end_reached_threshold'})->toBe(1);
+ expect($list->toArray(new CallbackRegistry)['props']['end_reached_buffer'])->toBe(1);
});
From 8aef7d32758f423f646b4a3650f8982975fedd96 Mon Sep 17 00:00:00 2001
From: Punyapal Shah <53343069+MrPunyapal@users.noreply.github.com>
Date: Mon, 21 Sep 2026 23:06:17 +0530
Subject: [PATCH 12/14] Document list end reached buffer
---
README.md | 23 +++++++++++++++++++++++
1 file changed, 23 insertions(+)
diff --git a/README.md b/README.md
index aebb85b..07efa61 100644
--- a/README.md
+++ b/README.md
@@ -69,6 +69,29 @@ Icons accept platform enum overrides in Blade, matching the fluent API:
```
+## Lists
+
+The `native:list` element supports an optional end-reached buffer for loading more items before the user reaches the end of the list.
+
+```blade
+
+```
+
+A buffer of `5` starts the existing `on-end-reached` callback when the list reaches the configured buffer near the end. The default buffer is `3` when the attribute is omitted.
+
+The same option is available through the fluent API:
+
+```php
+use Native\Mobile\UI\Elements\NativeList;
+
+NativeList::make()
+ ->endReachedBuffer(5)
+ ->onEndReached('loadMore');
+```
+
## Accessibility
Every element accepts a screen-reader label and an optional hint, via Blade
From ae66314f7a90db8034cadb41b5217a6b136c73db Mon Sep 17 00:00:00 2001
From: Punyapal Shah <53343069+MrPunyapal@users.noreply.github.com>
Date: Mon, 21 Sep 2026 23:06:30 +0530
Subject: [PATCH 13/14] Fix iOS list end reached buffer
---
resources/ios/NativeUIListRenderer.swift | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/resources/ios/NativeUIListRenderer.swift b/resources/ios/NativeUIListRenderer.swift
index e84fced..647f034 100644
--- a/resources/ios/NativeUIListRenderer.swift
+++ b/resources/ios/NativeUIListRenderer.swift
@@ -82,7 +82,7 @@ struct NativeUIListRenderer: View {
rowView(child, separator: separator)
.onAppear {
fireEndReached(rowId: child.id, leafIndex: leafIndex,
- leafCount: leafCount, threshold: endReachedThreshold,
+ leafCount: leafCount, buffer: endReachedBuffer,
cb: onEndReachedCb, nodeId: nodeId)
}
}
From ee12c0e2ac3d6fcd228e3de5298bc7281bd7b0cb Mon Sep 17 00:00:00 2001
From: Punyapal Shah
Date: Tue, 22 Sep 2026 22:51:29 +0530
Subject: [PATCH 14/14] lint
---
tests/ListTest.php | 1 -
1 file changed, 1 deletion(-)
diff --git a/tests/ListTest.php b/tests/ListTest.php
index 782857c..37097d4 100644
--- a/tests/ListTest.php
+++ b/tests/ListTest.php
@@ -72,7 +72,6 @@ function collectList(array $attrs): array
expect($opaque->toArray(new CallbackRegistry)['props']['transparent'])->toBeFalse();
});
-
it('serializes the end reached buffer from attributes', function () {
$tree = collectList(['end-reached-buffer' => 5]);