Skip to content

Commit 3c3a585

Browse files
authored
fix(datagrid): continue row numbers across pages instead of resetting per page (#1236)
* fix(datagrid): continue row numbers across pages instead of resetting per page * fix(datagrid): size # column to fit the widest row number on the current page * test(datagrid): cover row number column sizing and extract padding constants
1 parent 44e41b2 commit 3c3a585

8 files changed

Lines changed: 104 additions & 5 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1414

1515
### Changed
1616

17+
- Row numbers in the data grid now continue across pages instead of resetting to 1, matching the pagination footer (page 2 with a page size of 1000 shows 1001-2000). The `#` column auto-sizes to fit the widest row number expected on the current page so values like `14001` no longer clip.
1718
- The connection window shows the connecting state inline with a Cancel button instead of an empty sidebar.
1819
- Date, datetime, and timestamp cells use the same inline text editor as other columns; the popover date picker is removed.
1920
- The foreign key preview popover now follows the selected row when you arrow up or down, refreshing both the anchor and the displayed reference row. Arrow left or right (column change) and row mutations dismiss the popover.

TablePro/Views/Main/Child/MainEditorContentView.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -561,6 +561,9 @@ struct MainEditorContentView: View {
561561
return .none
562562
}
563563
},
564+
paginationOffsetProvider: { [coordinator] in
565+
coordinator.tabManager.tabs.first(where: { $0.id == tabId })?.pagination.currentOffset ?? 0
566+
},
564567
changeManager: currentChangeManager,
565568
isEditable: isEditable,
566569
configuration: DataGridConfiguration(

TablePro/Views/Results/Cells/DataGridCellRegistry.swift

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ final class DataGridCellRegistry {
6868
func makeRowNumberCell(
6969
in tableView: NSTableView,
7070
row: Int,
71+
pageOffset: Int,
7172
cachedRowCount: Int,
7273
visualState: RowVisualState
7374
) -> NSView {
@@ -111,9 +112,10 @@ final class DataGridCellRegistry {
111112
return cellView
112113
}
113114

114-
cell.stringValue = "\(row + 1)"
115+
let displayNumber = row + pageOffset + 1
116+
cell.stringValue = "\(displayNumber)"
115117
cell.textColor = visualState.isDeleted ? ThemeEngine.shared.colors.dataGrid.deletedText : .secondaryLabelColor
116-
cellView.setAccessibilityLabel(String(format: String(localized: "Row %d"), row + 1))
118+
cellView.setAccessibilityLabel(String(format: String(localized: "Row %d"), displayNumber))
117119
cellView.setAccessibilityRowIndexRange(NSRange(location: row, length: 1))
118120

119121
return cellView

TablePro/Views/Results/Cells/DataGridMetrics.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,6 @@ import CoreGraphics
77

88
enum DataGridMetrics {
99
static let cellHorizontalInset: CGFloat = 4
10+
static let rowNumberHeaderPadding: CGFloat = 8
11+
static let rowNumberColumnMinWidth: CGFloat = 40
1012
}

TablePro/Views/Results/DataGridCoordinator.swift

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ final class TableViewCoordinator: NSObject, NSTableViewDelegate, NSTableViewData
1111
{
1212
var tableRowsProvider: @MainActor () -> TableRows = { TableRows() }
1313
var tableRowsMutator: @MainActor (@MainActor (inout TableRows) -> Void) -> Void = { _ in }
14+
var paginationOffsetProvider: @MainActor () -> Int = { 0 }
1415
var changeManager: AnyChangeManager
1516
var isEditable: Bool
1617
var sortedIDs: [RowID]?
@@ -223,6 +224,17 @@ final class TableViewCoordinator: NSObject, NSTableViewDelegate, NSTableViewData
223224
let tableRows = tableRowsProvider()
224225
cachedRowCount = sortedIDs?.count ?? tableRows.count
225226
cachedColumnCount = tableRows.columns.count
227+
resizeRowNumberColumnForCurrentRange()
228+
}
229+
230+
func resizeRowNumberColumnForCurrentRange() {
231+
guard let tableView,
232+
let column = tableView.tableColumns.first(where: {
233+
$0.identifier == ColumnIdentitySchema.rowNumberIdentifier
234+
}),
235+
!column.isHidden else { return }
236+
let maxRowNumber = paginationOffsetProvider() + cachedRowCount
237+
DataGridView.sizeRowNumberColumn(column, forMaxRowNumber: maxRowNumber)
226238
}
227239

228240
func applyInsertedRows(_ indices: IndexSet) {

TablePro/Views/Results/DataGridView.swift

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ struct RowVisualState: Equatable {
3030
struct DataGridView: NSViewRepresentable {
3131
var tableRowsProvider: @MainActor () -> TableRows = { TableRows() }
3232
var tableRowsMutator: @MainActor (@MainActor (inout TableRows) -> Void) -> Void = { _ in }
33+
var paginationOffsetProvider: @MainActor () -> Int = { 0 }
3334
var changeManager: AnyChangeManager
3435
let isEditable: Bool
3536
var configuration: DataGridConfiguration = .init()
@@ -112,6 +113,7 @@ struct DataGridView: NSViewRepresentable {
112113
context.coordinator.tableRowsController.attach(tableView)
113114
context.coordinator.tableRowsProvider = tableRowsProvider
114115
context.coordinator.tableRowsMutator = tableRowsMutator
116+
context.coordinator.paginationOffsetProvider = paginationOffsetProvider
115117
context.coordinator.sortedIDs = sortedIDs
116118
// Intentionally do not prime cachedRowCount/cachedColumnCount here.
117119
// They represent what NSTableView has actually rendered. Leaving them
@@ -143,6 +145,7 @@ struct DataGridView: NSViewRepresentable {
143145

144146
coordinator.tableRowsProvider = tableRowsProvider
145147
coordinator.tableRowsMutator = tableRowsMutator
148+
coordinator.paginationOffsetProvider = paginationOffsetProvider
146149
coordinator.changeManager = changeManager
147150

148151
let latestRows = tableRowsProvider()
@@ -197,6 +200,9 @@ struct DataGridView: NSViewRepresentable {
197200
let shouldHide = !configuration.showRowNumbers
198201
if rowNumCol.isHidden != shouldHide {
199202
rowNumCol.isHidden = shouldHide
203+
if !shouldHide {
204+
coordinator.resizeRowNumberColumnForCurrentRange()
205+
}
200206
}
201207
}
202208

@@ -344,11 +350,9 @@ struct DataGridView: NSViewRepresentable {
344350
static func makeRowNumberColumn() -> NSTableColumn {
345351
let column = NSTableColumn(identifier: ColumnIdentitySchema.rowNumberIdentifier)
346352
column.title = "#"
347-
column.width = 40
348-
column.minWidth = 40
349-
column.maxWidth = 60
350353
column.isEditable = false
351354
column.resizingMask = []
355+
sizeRowNumberColumn(column, forMaxRowNumber: 1)
352356
let defaultHeaderFont = column.headerCell.font
353357
let headerCell = SortableHeaderCell(textCell: "#")
354358
headerCell.font = defaultHeaderFont
@@ -358,6 +362,20 @@ struct DataGridView: NSViewRepresentable {
358362
return column
359363
}
360364

365+
@MainActor
366+
static func sizeRowNumberColumn(_ column: NSTableColumn, forMaxRowNumber maxNumber: Int) {
367+
let display = "\(max(maxNumber, 1))"
368+
let font = ThemeEngine.shared.dataGridFonts.rowNumber
369+
let textWidth = (display as NSString).size(withAttributes: [.font: font]).width
370+
let measured = ceil(textWidth)
371+
+ 2 * DataGridMetrics.cellHorizontalInset
372+
+ DataGridMetrics.rowNumberHeaderPadding
373+
let columnWidth = max(DataGridMetrics.rowNumberColumnMinWidth, measured)
374+
column.minWidth = columnWidth
375+
column.maxWidth = columnWidth
376+
column.width = columnWidth
377+
}
378+
361379
static let firstDataTableColumnIndex: Int = 1
362380

363381
static func isDataTableColumn(_ tableColumnIndex: Int) -> Bool {

TablePro/Views/Results/Extensions/DataGridView+Columns.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ extension TableViewCoordinator {
2222
return cellRegistry.makeRowNumberCell(
2323
in: tableView,
2424
row: row,
25+
pageOffset: paginationOffsetProvider(),
2526
cachedRowCount: displayCount,
2627
visualState: visualState(for: row)
2728
)
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
//
2+
// RowNumberColumnSizingTests.swift
3+
// TableProTests
4+
//
5+
6+
import AppKit
7+
import Foundation
8+
@testable import TablePro
9+
import TableProPluginKit
10+
import Testing
11+
12+
@Suite("Row Number Column Sizing")
13+
@MainActor
14+
struct RowNumberColumnSizingTests {
15+
@Test("Single-digit max number sizes to the configured floor")
16+
func singleDigitHonoursFloor() {
17+
let column = NSTableColumn(identifier: ColumnIdentitySchema.rowNumberIdentifier)
18+
DataGridView.sizeRowNumberColumn(column, forMaxRowNumber: 1)
19+
#expect(column.width == DataGridMetrics.rowNumberColumnMinWidth)
20+
#expect(column.minWidth == column.width)
21+
#expect(column.maxWidth == column.width)
22+
}
23+
24+
@Test("Zero and negative inputs are clamped to the floor")
25+
func zeroAndNegativeClampToFloor() {
26+
let zero = NSTableColumn(identifier: ColumnIdentitySchema.rowNumberIdentifier)
27+
let negative = NSTableColumn(identifier: ColumnIdentitySchema.rowNumberIdentifier)
28+
DataGridView.sizeRowNumberColumn(zero, forMaxRowNumber: 0)
29+
DataGridView.sizeRowNumberColumn(negative, forMaxRowNumber: -42)
30+
#expect(zero.width == DataGridMetrics.rowNumberColumnMinWidth)
31+
#expect(negative.width == DataGridMetrics.rowNumberColumnMinWidth)
32+
}
33+
34+
@Test("Five-digit numbers grow past the floor")
35+
func fiveDigitGrowsPastFloor() {
36+
let column = NSTableColumn(identifier: ColumnIdentitySchema.rowNumberIdentifier)
37+
DataGridView.sizeRowNumberColumn(column, forMaxRowNumber: 14_001)
38+
#expect(column.width > DataGridMetrics.rowNumberColumnMinWidth)
39+
}
40+
41+
@Test("Width grows monotonically with digit count")
42+
func widthGrowsWithDigits() {
43+
let twoDigit = NSTableColumn(identifier: ColumnIdentitySchema.rowNumberIdentifier)
44+
let fourDigit = NSTableColumn(identifier: ColumnIdentitySchema.rowNumberIdentifier)
45+
let sixDigit = NSTableColumn(identifier: ColumnIdentitySchema.rowNumberIdentifier)
46+
DataGridView.sizeRowNumberColumn(twoDigit, forMaxRowNumber: 99)
47+
DataGridView.sizeRowNumberColumn(fourDigit, forMaxRowNumber: 9_999)
48+
DataGridView.sizeRowNumberColumn(sixDigit, forMaxRowNumber: 999_999)
49+
#expect(fourDigit.width >= twoDigit.width)
50+
#expect(sixDigit.width >= fourDigit.width)
51+
}
52+
53+
@Test("Min, max and width stay equal so the column is pinned")
54+
func widthIsPinned() {
55+
let column = NSTableColumn(identifier: ColumnIdentitySchema.rowNumberIdentifier)
56+
DataGridView.sizeRowNumberColumn(column, forMaxRowNumber: 1_234_567)
57+
#expect(column.minWidth == column.width)
58+
#expect(column.maxWidth == column.width)
59+
}
60+
}

0 commit comments

Comments
 (0)