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
diff --git a/resources/android/ListRenderer.kt b/resources/android/ListRenderer.kt
index ef1bb81..0a9d200 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 endReachedBuffer = node.props.getInt("end_reached_buffer", 3).coerceAtLeast(1)
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 - endReachedBuffer
}.collect { nearEnd ->
if (nearEnd && !endReachedFired.value) {
endReachedFired.value = true
diff --git a/resources/ios/NativeUIListRenderer.swift b/resources/ios/NativeUIListRenderer.swift
index 3611017..647f034 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 endReachedBuffer = max(1, node.props.getInt("end_reached_buffer", 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, buffer: endReachedBuffer,
+ 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, buffer: endReachedBuffer,
+ 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,
+ buffer: Int, cb: Int, nodeId: Int) {
guard cb != 0, let gi = leafIndex[rowId] else { return }
- if gi >= leafCount - 3 {
+ if gi >= leafCount - buffer {
NativeElementBridge.sendPressEvent(cb, nodeId: nodeId)
}
}
diff --git a/src/Elements/NativeList.php b/src/Elements/NativeList.php
index ad3d5da..ecac078 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-buffer']) || isset($attrs['endReachedBuffer'])) {
+ $this->endReachedBuffer((int) ($attrs['end-reached-buffer'] ?? $attrs['endReachedBuffer']));
+ }
$this->applyA11yAttributes($attrs);
}
@@ -114,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;
diff --git a/tests/ListTest.php b/tests/ListTest.php
index ffaec08..37097d4 100644
--- a/tests/ListTest.php
+++ b/tests/ListTest.php
@@ -71,3 +71,21 @@ 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]);
+
+ expect($tree['props']['end_reached_buffer'])->toBe(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_buffer'])->toBe(5);
+});
+
+it('clamps a negative end reached buffer to one', function () {
+ $list = NativeList::make()->endReachedBuffer(-1);
+
+ expect($list->toArray(new CallbackRegistry)['props']['end_reached_buffer'])->toBe(1);
+});