diff --git a/Sources/TUIkit/Views/List.swift b/Sources/TUIkit/Views/List.swift index a93cdfdc..77c123ea 100644 --- a/Sources/TUIkit/Views/List.swift +++ b/Sources/TUIkit/Views/List.swift @@ -101,9 +101,6 @@ public struct List, focusID: String? = nil, - maxVisibleRows: Int? = nil, + emptyPlaceholder: String = "No items", showFooterSeparator: Bool = true, @ViewBuilder content: () -> Content, @@ -158,7 +154,7 @@ extension List { self.multiSelection = nil self.focusID = focusID self.isDisabled = false - self.maxVisibleRows = maxVisibleRows + self.emptyPlaceholder = emptyPlaceholder self.showFooterSeparator = showFooterSeparator } @@ -168,7 +164,7 @@ extension List { /// - Parameters: /// - selection: A binding to the selected item's ID (nil = no selection). /// - focusID: The unique focus identifier (default: auto-generated). - /// - maxVisibleRows: Maximum visible rows (default: nil = available height). + /// - emptyPlaceholder: Placeholder text when empty (default: "No items"). /// - showFooterSeparator: Whether to show separator before footer (default: true). /// - content: A ViewBuilder that defines the list content. @@ -176,7 +172,7 @@ extension List { public init( selection: Binding, focusID: String? = nil, - maxVisibleRows: Int? = nil, + emptyPlaceholder: String = "No items", showFooterSeparator: Bool = true, @ViewBuilder content: () -> Content, @@ -189,7 +185,7 @@ extension List { self.multiSelection = nil self.focusID = focusID self.isDisabled = false - self.maxVisibleRows = maxVisibleRows + self.emptyPlaceholder = emptyPlaceholder self.showFooterSeparator = showFooterSeparator } @@ -204,14 +200,14 @@ extension List where Footer == EmptyView { /// - title: The title displayed in the border. /// - selection: A binding to the selected item's ID (nil = no selection). /// - focusID: The unique focus identifier (default: auto-generated). - /// - maxVisibleRows: Maximum visible rows (default: nil = available height). + /// - emptyPlaceholder: Placeholder text when empty (default: "No items"). /// - content: A ViewBuilder that defines the list content. public init( _ title: String, selection: Binding, focusID: String? = nil, - maxVisibleRows: Int? = nil, + emptyPlaceholder: String = "No items", @ViewBuilder content: () -> Content ) { @@ -222,7 +218,7 @@ extension List where Footer == EmptyView { self.multiSelection = nil self.focusID = focusID self.isDisabled = false - self.maxVisibleRows = maxVisibleRows + self.emptyPlaceholder = emptyPlaceholder self.showFooterSeparator = false } @@ -232,13 +228,13 @@ extension List where Footer == EmptyView { /// - Parameters: /// - selection: A binding to the selected item's ID (nil = no selection). /// - focusID: The unique focus identifier (default: auto-generated). - /// - maxVisibleRows: Maximum visible rows (default: nil = available height). + /// - emptyPlaceholder: Placeholder text when empty (default: "No items"). /// - content: A ViewBuilder that defines the list content. public init( selection: Binding, focusID: String? = nil, - maxVisibleRows: Int? = nil, + emptyPlaceholder: String = "No items", @ViewBuilder content: () -> Content ) { @@ -249,7 +245,7 @@ extension List where Footer == EmptyView { self.multiSelection = nil self.focusID = focusID self.isDisabled = false - self.maxVisibleRows = maxVisibleRows + self.emptyPlaceholder = emptyPlaceholder self.showFooterSeparator = false } @@ -264,7 +260,7 @@ extension List { /// - title: The title displayed in the border. /// - selection: A binding to the set of selected item IDs. /// - focusID: The unique focus identifier (default: auto-generated). - /// - maxVisibleRows: Maximum visible rows (default: nil = available height). + /// - emptyPlaceholder: Placeholder text when empty (default: "No items"). /// - showFooterSeparator: Whether to show separator before footer (default: true). /// - content: A ViewBuilder that defines the list content. @@ -273,7 +269,7 @@ extension List { _ title: String, selection: Binding>, focusID: String? = nil, - maxVisibleRows: Int? = nil, + emptyPlaceholder: String = "No items", showFooterSeparator: Bool = true, @ViewBuilder content: () -> Content, @@ -286,7 +282,7 @@ extension List { self.multiSelection = selection self.focusID = focusID self.isDisabled = false - self.maxVisibleRows = maxVisibleRows + self.emptyPlaceholder = emptyPlaceholder self.showFooterSeparator = showFooterSeparator } @@ -296,7 +292,7 @@ extension List { /// - Parameters: /// - selection: A binding to the set of selected item IDs. /// - focusID: The unique focus identifier (default: auto-generated). - /// - maxVisibleRows: Maximum visible rows (default: nil = available height). + /// - emptyPlaceholder: Placeholder text when empty (default: "No items"). /// - showFooterSeparator: Whether to show separator before footer (default: true). /// - content: A ViewBuilder that defines the list content. @@ -304,7 +300,7 @@ extension List { public init( selection: Binding>, focusID: String? = nil, - maxVisibleRows: Int? = nil, + emptyPlaceholder: String = "No items", showFooterSeparator: Bool = true, @ViewBuilder content: () -> Content, @@ -317,7 +313,7 @@ extension List { self.multiSelection = selection self.focusID = focusID self.isDisabled = false - self.maxVisibleRows = maxVisibleRows + self.emptyPlaceholder = emptyPlaceholder self.showFooterSeparator = showFooterSeparator } @@ -332,14 +328,14 @@ extension List where Footer == EmptyView { /// - title: The title displayed in the border. /// - selection: A binding to the set of selected item IDs. /// - focusID: The unique focus identifier (default: auto-generated). - /// - maxVisibleRows: Maximum visible rows (default: nil = available height). + /// - emptyPlaceholder: Placeholder text when empty (default: "No items"). /// - content: A ViewBuilder that defines the list content. public init( _ title: String, selection: Binding>, focusID: String? = nil, - maxVisibleRows: Int? = nil, + emptyPlaceholder: String = "No items", @ViewBuilder content: () -> Content ) { @@ -350,7 +346,7 @@ extension List where Footer == EmptyView { self.multiSelection = selection self.focusID = focusID self.isDisabled = false - self.maxVisibleRows = maxVisibleRows + self.emptyPlaceholder = emptyPlaceholder self.showFooterSeparator = false } @@ -360,13 +356,13 @@ extension List where Footer == EmptyView { /// - Parameters: /// - selection: A binding to the set of selected item IDs. /// - focusID: The unique focus identifier (default: auto-generated). - /// - maxVisibleRows: Maximum visible rows (default: nil = available height). + /// - emptyPlaceholder: Placeholder text when empty (default: "No items"). /// - content: A ViewBuilder that defines the list content. public init( selection: Binding>, focusID: String? = nil, - maxVisibleRows: Int? = nil, + emptyPlaceholder: String = "No items", @ViewBuilder content: () -> Content ) { @@ -377,7 +373,7 @@ extension List where Footer == EmptyView { self.multiSelection = selection self.focusID = focusID self.isDisabled = false - self.maxVisibleRows = maxVisibleRows + self.emptyPlaceholder = emptyPlaceholder self.showFooterSeparator = false } @@ -409,7 +405,6 @@ private struct _ListCore: View where Value.ID: Hashab /// Whether the table is disabled. var isDisabled: Bool - /// The maximum number of visible rows (nil = use available height). - let maxVisibleRows: Int? - /// The placeholder text shown when the table is empty. let emptyPlaceholder: String @@ -87,7 +84,6 @@ public struct Table: View where Value.ID: Hashab selectionMode: selectionMode, focusID: focusID, isDisabled: isDisabled, - maxVisibleRows: maxVisibleRows, emptyPlaceholder: emptyPlaceholder, columnSpacing: columnSpacing ) @@ -103,7 +99,7 @@ extension Table { /// - data: The data items to display. /// - selection: A binding to the selected item's ID (nil = no selection). /// - focusID: The unique focus identifier (default: auto-generated). - /// - maxVisibleRows: Maximum visible rows (default: nil = available height). + /// - columnSpacing: Spacing between columns (default: 2). /// - emptyPlaceholder: Placeholder text when empty (default: "No items"). /// - columns: A builder that defines the table columns. @@ -111,7 +107,7 @@ extension Table { _ data: [Value], selection: Binding, focusID: String? = nil, - maxVisibleRows: Int? = nil, + columnSpacing: Int = 2, emptyPlaceholder: String = "No items", @TableColumnBuilder columns: () -> [TableColumn] @@ -122,7 +118,7 @@ extension Table { self.multiSelection = nil self.focusID = focusID self.isDisabled = false - self.maxVisibleRows = maxVisibleRows + self.columnSpacing = columnSpacing self.emptyPlaceholder = emptyPlaceholder } @@ -137,7 +133,7 @@ extension Table { /// - data: The data items to display. /// - selection: A binding to the set of selected item IDs. /// - focusID: The unique focus identifier (default: auto-generated). - /// - maxVisibleRows: Maximum visible rows (default: nil = available height). + /// - columnSpacing: Spacing between columns (default: 2). /// - emptyPlaceholder: Placeholder text when empty (default: "No items"). /// - columns: A builder that defines the table columns. @@ -145,7 +141,7 @@ extension Table { _ data: [Value], selection: Binding>, focusID: String? = nil, - maxVisibleRows: Int? = nil, + columnSpacing: Int = 2, emptyPlaceholder: String = "No items", @TableColumnBuilder columns: () -> [TableColumn] @@ -156,7 +152,7 @@ extension Table { self.multiSelection = selection self.focusID = focusID self.isDisabled = false - self.maxVisibleRows = maxVisibleRows + self.columnSpacing = columnSpacing self.emptyPlaceholder = emptyPlaceholder } @@ -187,7 +183,6 @@ private struct _TableCore: View, Renderable wher let selectionMode: SelectionMode let focusID: String? let isDisabled: Bool - let maxVisibleRows: Int? let emptyPlaceholder: String let columnSpacing: Int @@ -220,7 +215,7 @@ private struct _TableCore: View, Renderable wher } else { // Calculate viewport height let availableHeight = context.availableHeight - let viewportHeight = maxVisibleRows ?? max(1, availableHeight - 6) // Reserve for border + header + indicators + let viewportHeight = max(1, availableHeight - 6) // Reserve for border + header + indicators // Get or create persistent focusID let focusIDKey = StateStorage.StateKey(identity: context.identity, propertyIndex: 1) diff --git a/Sources/TUIkitExample/Pages/ListPage.swift b/Sources/TUIkitExample/Pages/ListPage.swift index 209fd369..05af3bd9 100644 --- a/Sources/TUIkitExample/Pages/ListPage.swift +++ b/Sources/TUIkitExample/Pages/ListPage.swift @@ -51,8 +51,7 @@ struct ListPage: View { HStack(spacing: 2) { List( "Single Selection", - selection: $singleSelection, - maxVisibleRows: 6 + selection: $singleSelection ) { ForEach(FileItem.sampleFiles) { file in HStack(spacing: 1) { @@ -64,8 +63,7 @@ struct ListPage: View { List( "Multi Selection", - selection: $multiSelection, - maxVisibleRows: 6 + selection: $multiSelection ) { ForEach(FileItem.sampleFiles) { file in HStack(spacing: 1) { diff --git a/Sources/TUIkitExample/Pages/TablePage.swift b/Sources/TUIkitExample/Pages/TablePage.swift index 1db727c7..b333ed97 100644 --- a/Sources/TUIkitExample/Pages/TablePage.swift +++ b/Sources/TUIkitExample/Pages/TablePage.swift @@ -54,8 +54,7 @@ struct TablePage: View { .foregroundStyle(.palette.foregroundSecondary) Table( FileEntry.sampleFiles, - selection: $singleSelection, - maxVisibleRows: 6 + selection: $singleSelection ) { TableColumn("Name", value: \FileEntry.name) TableColumn("Size", value: \FileEntry.size) @@ -71,8 +70,7 @@ struct TablePage: View { .foregroundStyle(.palette.foregroundSecondary) Table( FileEntry.sampleFiles, - selection: $multiSelection, - maxVisibleRows: 4 + selection: $multiSelection ) { TableColumn("Name", value: \FileEntry.name) TableColumn("Type", value: \FileEntry.type) diff --git a/Tests/TUIkitTests/ListTests.swift b/Tests/TUIkitTests/ListTests.swift index a4836e57..ee28d0f6 100644 --- a/Tests/TUIkitTests/ListTests.swift +++ b/Tests/TUIkitTests/ListTests.swift @@ -132,12 +132,11 @@ struct ListRenderingTests { @Test("Scroll indicators appear when needed") func scrollIndicatorsAppear() { - let context = createTestContext(height: 5) - struct Item: Identifiable { let id: Int let name: String } + // Create list with more items than will fit in available height let items = (0..<20).map { Item(id: $0, name: "Item \($0)") } var selection: Int? @@ -145,18 +144,19 @@ struct ListRenderingTests { selection: Binding( get: { selection }, set: { selection = $0 } - ), - maxVisibleRows: 3 + ) ) { ForEach(items) { item in Text(item.name) } } + // Use a small height context so scrolling is triggered + let context = createTestContext(width: 40, height: 8) let buffer = renderToBuffer(list, context: context) let content = buffer.lines.joined() - // Should have "more below" indicator + // Should have "more below" indicator since we have 20 items in height 8 #expect(content.contains("▼") || content.contains("more below")) }