From fc540dd79c7094f2be34040c6776225263ff1787 Mon Sep 17 00:00:00 2001 From: Ngo Quoc Dat Date: Thu, 30 Apr 2026 02:48:02 +0700 Subject: [PATCH 1/3] fix(datagrid): render sort chevron via SF Symbol with palette tint --- TablePro/Views/Results/SortableHeaderCell.swift | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/TablePro/Views/Results/SortableHeaderCell.swift b/TablePro/Views/Results/SortableHeaderCell.swift index d6460b26a..6bf439576 100644 --- a/TablePro/Views/Results/SortableHeaderCell.swift +++ b/TablePro/Views/Results/SortableHeaderCell.swift @@ -52,7 +52,7 @@ final class SortableHeaderCell: NSTableHeaderCell { width: indicatorSize.width, height: indicatorSize.height ) - Self.drawTintedIndicator(image: indicatorImage, in: indicatorRect) + Self.drawIndicator(image: indicatorImage, in: indicatorRect) if let priorityText { let textOriginX = indicatorOriginX - Self.indicatorSpacing - priorityWidth @@ -79,15 +79,14 @@ final class SortableHeaderCell: NSTableHeaderCell { } private static func indicatorImage(for direction: SortDirection) -> NSImage? { - switch direction { - case .ascending: - return NSImage(named: NSImage.Name("NSAscendingSortIndicator")) - case .descending: - return NSImage(named: NSImage.Name("NSDescendingSortIndicator")) - } + let symbolName = direction == .ascending ? "chevron.up" : "chevron.down" + let configuration = NSImage.SymbolConfiguration(pointSize: priorityFontSize, weight: .semibold) + .applying(.init(paletteColors: [.secondaryLabelColor])) + return NSImage(systemSymbolName: symbolName, accessibilityDescription: nil)? + .withSymbolConfiguration(configuration) } - private static func drawTintedIndicator(image: NSImage?, in rect: NSRect) { + private static func drawIndicator(image: NSImage?, in rect: NSRect) { guard let image else { return } image.draw( in: rect, From 6f2b32b620db70846d307da1250ca25162567ed2 Mon Sep 17 00:00:00 2001 From: Ngo Quoc Dat Date: Thu, 30 Apr 2026 03:08:58 +0700 Subject: [PATCH 2/3] refactor(datagrid): use hierarchical SF Symbol tint for sort chevron --- TablePro/Views/Results/SortableHeaderCell.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TablePro/Views/Results/SortableHeaderCell.swift b/TablePro/Views/Results/SortableHeaderCell.swift index 205489d2d..33d6ee8e2 100644 --- a/TablePro/Views/Results/SortableHeaderCell.swift +++ b/TablePro/Views/Results/SortableHeaderCell.swift @@ -98,7 +98,7 @@ final class SortableHeaderCell: NSTableHeaderCell { private static func indicatorImage(for direction: SortDirection) -> NSImage? { let symbolName = direction == .ascending ? "chevron.up" : "chevron.down" let configuration = NSImage.SymbolConfiguration(pointSize: priorityFontSize, weight: .semibold) - .applying(.init(paletteColors: [.secondaryLabelColor])) + .applying(.init(hierarchicalColor: .secondaryLabelColor)) return NSImage(systemSymbolName: symbolName, accessibilityDescription: nil)? .withSymbolConfiguration(configuration) } From 708128e1e4e7b8c7dde8f2823c715e8795222462 Mon Sep 17 00:00:00 2001 From: Ngo Quoc Dat Date: Thu, 30 Apr 2026 03:09:01 +0700 Subject: [PATCH 3/3] docs(changelog): describe sort chevron via SF Symbol --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 73ad896b9..db65f71cd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,7 +27,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Data grid column identifiers are now the column name (with positional fallback for duplicate names), so saved widths follow the column across schema changes that shift its position. Identifier resolution moved from static `DataGridView` helpers to a `ColumnIdentitySchema` value type owned by the coordinator. - `ColumnLayoutStorage` singleton replaced by a `ColumnLayoutPersisting` protocol with an injectable `FileColumnLayoutPersister` default. The coordinator depends on the protocol, not the concrete class, so tests can substitute a fake. - Column layout save/restore on table-switch (`saveColumnLayoutForTable` / `restoreColumnLayoutForTable`) folded into the data grid coordinator's lifecycle (load on column build, persist on resize/move/dismantle). The standalone `MainContentCoordinator+ColumnLayout` extension is gone; only the visibility orchestration remains. Removes the redundant `hasUserResizedColumns` flag and the external save trigger from the binding setter. -- Data grid header sort indicators are drawn inside a custom `NSTableHeaderCell` instead of overlay `NSImageView` subviews, replacing Unicode arrows that were embedded in the column title string. Removing the overlay subviews lets `NSTableHeaderView`'s native cursor management run unimpeded, so the column resize cursor on hover works without any custom cursor handling. The primary sorted column gets the system header tint via `highlightedTableColumn`, and secondary sort columns show a small priority number to the left of the arrow. +- Data grid header sort indicators are drawn inside a custom `NSTableHeaderCell` instead of overlay `NSImageView` subviews, replacing Unicode arrows that were embedded in the column title string. The cell renders ascending/descending chevrons via SF Symbols (`chevron.up`/`chevron.down`) with a hierarchical tint, so they pick up the correct color in light and dark mode. Removing the overlay subviews lets `NSTableHeaderView`'s native cursor management run unimpeded, so the column resize cursor on hover works without any custom cursor handling. The primary sorted column gets the system header tint via `highlightedTableColumn`, and secondary sort columns show a small priority number to the left of the chevron. - Data grid header divider taps trigger a column resize instead of sorting the adjacent column. `SortableHeaderView` checks if the click landed within 4 pt of a column edge and forwards the event to `NSTableHeaderView`'s native resize handling. - Data grid column layout persistence routes through a coordinator callback fired from outside SwiftUI's update cycle, removing the `Task`-based `@Binding` mutation inside `updateNSView` and the `isWritingColumnLayout` re-entry guard. - Data grid cell reuse resets foreign-key arrow and dropdown chevron button context (target, action, row, column) when the button hides, preventing a stale handler from firing the wrong row if the column toggles between FK-eligible and not.