diff --git a/Sources/TUIkit/Core/SelectableListRow.swift b/Sources/TUIkit/Core/SelectableListRow.swift index 613e7e9..7f2b8bf 100644 --- a/Sources/TUIkit/Core/SelectableListRow.swift +++ b/Sources/TUIkit/Core/SelectableListRow.swift @@ -46,14 +46,19 @@ public struct SelectableListRow: Sendable, /// The rendered content buffer. public let buffer: FrameBuffer - /// Creates a selectable list row with type and buffer. + /// The badge value for this row (from environment). + public let badge: BadgeValue? + + /// Creates a selectable list row with type, buffer, and optional badge. /// /// - Parameters: /// - type: The row type (header, content, or footer). /// - buffer: The rendered row content. - public init(type: ListRowType, buffer: FrameBuffer) { + /// - badge: The badge value for this row (default: nil). + public init(type: ListRowType, buffer: FrameBuffer, badge: BadgeValue? = nil) { self.type = type self.buffer = buffer + self.badge = badge } /// Indicates whether this row can be selected and focused. diff --git a/Sources/TUIkit/Modifiers/BadgeModifier.swift b/Sources/TUIkit/Modifiers/BadgeModifier.swift index 7e42458..fd965dc 100644 --- a/Sources/TUIkit/Modifiers/BadgeModifier.swift +++ b/Sources/TUIkit/Modifiers/BadgeModifier.swift @@ -78,6 +78,23 @@ extension BadgeValue: Equatable { } } +// MARK: - Badge Extraction + +/// Extracts the badge value from a view if it's wrapped in a BadgeModifier. +/// +/// This is used by List to extract badge values during row extraction. +@MainActor +public func extractBadgeValue(from view: V) -> BadgeValue? { + // Use Mirror to check if the view is a BadgeModifier + let mirror = Mirror(reflecting: view) + for child in mirror.children { + if child.label == "value", let badge = child.value as? BadgeValue { + return badge + } + } + return nil +} + // MARK: - Renderable extension BadgeModifier: Renderable { diff --git a/Sources/TUIkit/Rendering/Renderable.swift b/Sources/TUIkit/Rendering/Renderable.swift index f82e6dc..1e13bc7 100644 --- a/Sources/TUIkit/Rendering/Renderable.swift +++ b/Sources/TUIkit/Rendering/Renderable.swift @@ -221,11 +221,15 @@ public struct RenderContext { /// Used by layout containers (e.g., NavigationSplitView) to constrain /// child views to a specific column width. /// + /// This also sets `hasExplicitWidth` to true so that child views + /// (like List) know to expand to fill the available width. + /// /// - Parameter width: The new available width in characters. /// - Returns: A new RenderContext with the updated width. func withAvailableWidth(_ width: Int) -> Self { var copy = self copy.availableWidth = width + copy.hasExplicitWidth = true return copy } } diff --git a/Sources/TUIkit/Views/List.swift b/Sources/TUIkit/Views/List.swift index d110ee9..bbf4867 100644 --- a/Sources/TUIkit/Views/List.swift +++ b/Sources/TUIkit/Views/List.swift @@ -19,6 +19,9 @@ struct ListRow { /// The rendered content buffer for this row. let buffer: FrameBuffer + /// The badge value for this row (from environment). + let badge: BadgeValue? + /// The height of this row in lines. var height: Int { buffer.height } } @@ -615,7 +618,7 @@ private struct _ListCore] = extractor.extractListRows(context: context) - return rows.map { SelectableListRow(type: .content(id: $0.id), buffer: $0.buffer) } + return rows.map { SelectableListRow(type: .content(id: $0.id), buffer: $0.buffer, badge: $0.badge) } } // Check for ChildInfoProvider (handles TupleView with multiple children) @@ -670,7 +673,7 @@ private struct _ListCore] = extractor.extractListRows(context: context) for row in contentRows { - rows.append(SelectableListRow(type: .content(id: row.id), buffer: row.buffer)) + rows.append(SelectableListRow(type: .content(id: row.id), buffer: row.buffer, badge: row.badge)) } } else { // Fallback: render content as single row (if Section content is not ForEach) @@ -766,8 +769,8 @@ private struct _ListCore ListRow? in let elementID = element[keyPath: idKeyPath] let view = content(element) + + // Extract badge if the view is wrapped in a BadgeModifier + let badge = extractBadgeValue(from: view) + + // Render the view let buffer = TUIkit.renderToBuffer(view, context: context) guard let rowID = elementID as? RowID else { return nil } - return ListRow(id: rowID, buffer: buffer) + return ListRow(id: rowID, buffer: buffer, badge: badge) } } } diff --git a/Sources/TUIkit/Views/Section.swift b/Sources/TUIkit/Views/Section.swift index a8150e0..065e8b4 100644 --- a/Sources/TUIkit/Views/Section.swift +++ b/Sources/TUIkit/Views/Section.swift @@ -311,7 +311,7 @@ extension Section: ListRowExtractor { // Fallback: render content as a single row (rare case) let buffer = TUIkit.renderToBuffer(content, context: context) if let indexID = 0 as? RowID { - return [ListRow(id: indexID, buffer: buffer)] + return [ListRow(id: indexID, buffer: buffer, badge: nil)] } return [] }