Skip to content

Commit cb5f3b9

Browse files
committed
refactor: unified self-registration for all child types
- Remove special case for EmptyView in add() - All children (items, sections, emptyView) self-register via onLifecyclePass - Add detailed comments explaining registration flow - Simplified add() to just forward to scrollBox
1 parent f6702a4 commit cb5f3b9

File tree

1 file changed

+32
-13
lines changed

1 file changed

+32
-13
lines changed

termcast/src/examples/internal/custom-renderable-list.tsx

Lines changed: 32 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -108,11 +108,20 @@ class CustomListSectionRenderable extends BoxRenderable {
108108
class CustomListEmptyViewRenderable extends BoxRenderable {
109109
public emptyTitle: string
110110
public emptyDescription?: string
111+
private parentList?: CustomListRenderable
111112

112113
constructor(ctx: RenderContext, options: CustomListEmptyViewOptions) {
113114
super(ctx, { ...options, height: 0, overflow: 'hidden' })
114-
this.emptyTitle = options.emptyTitle
115+
this.emptyTitle = options.emptyTitle || 'No items'
115116
this.emptyDescription = options.emptyDescription
117+
118+
// Self-register with parent list after being added to tree
119+
this.onLifecyclePass = () => {
120+
if (!this.parentList) {
121+
this.parentList = findParent(this, CustomListRenderable)
122+
this.parentList?.registerEmptyView(this)
123+
}
124+
}
116125
}
117126
}
118127

@@ -223,22 +232,28 @@ class CustomListRenderable extends BoxRenderable {
223232
super.add(this.statusText)
224233
}
225234

226-
// Override add() so React children go into scrollBox
235+
// ─────────────────────────────────────────────────────────────────────────
236+
// Child Management
237+
//
238+
// All React children are redirected to scrollBox for proper scrolling.
239+
// Children self-register with the list via onLifecyclePass callback:
240+
//
241+
// 1. React calls list.add(child) for sections/items/emptyView
242+
// 2. We forward to scrollBox.add(child), which sets child.parent
243+
// 3. opentui calls child.onLifecyclePass() after parent is set
244+
// 4. Child traverses up via findParent() to find this list
245+
// 5. Child calls registerItem/Section/EmptyView to add itself to sets
246+
// 6. scheduleUpdate() debounces and triggers refilter/selection
247+
//
248+
// This avoids the list needing to traverse the tree to find children.
249+
// Stale refs are cleaned lazily in getAllItems/Sections by checking
250+
// if items are still connected to the tree via parent chain.
251+
// ─────────────────────────────────────────────────────────────────────────
252+
227253
add(child: Renderable, index?: number): number {
228-
if (child instanceof CustomListEmptyViewRenderable) {
229-
// EmptyView is data-only, not rendered - just store reference
230-
this.emptyView = child
231-
return -1
232-
}
233-
// All other React children go into scrollBox
234-
// Items/sections register themselves via onLifecyclePass
235254
return this.scrollBox.add(child, index)
236255
}
237256

238-
// ─────────────────────────────────────────────────────────────────────────
239-
// Registration - items/sections register themselves via onLifecyclePass
240-
// ─────────────────────────────────────────────────────────────────────────
241-
242257
registerItem(item: CustomListItemRenderable) {
243258
this.registeredItems.add(item)
244259
this.scheduleUpdate()
@@ -249,6 +264,10 @@ class CustomListRenderable extends BoxRenderable {
249264
this.scheduleUpdate()
250265
}
251266

267+
registerEmptyView(emptyView: CustomListEmptyViewRenderable) {
268+
this.emptyView = emptyView
269+
}
270+
252271
private updateScheduled = false
253272
private scheduleUpdate() {
254273
if (this.updateScheduled) return

0 commit comments

Comments
 (0)