Ok, so on the basis that when I've finally exhausted all my investigation and resorted to making a post asking for help, that's usually when I figure something out. :)
I've managed to implement this using the following component:
https://github.com/Ceylo/ListItemTracking
It's an iOS package, but I was able to use it in my Mac project directly. You basically attach the new onItemFrameChanged modifier to the items you want to track - in my case, the "Title" Text items, and as you scroll it returns either a frame of co-ordinates if the item is in view, or nil if it's not in view.
struct ContentView: View {
var body: some View {
GeometryReader { geometry in
List(0..<100) { i in
Text("Item \(i)")
.onItemFrameChanged(listGeometry: geometry) { (frame: CGRect?) in
print("rect of item \(i): \(String(describing: frame)))")
}
}
.trackListFrame()
}
}
}
By saving the visibility state back to my data as the List is scrolled, it just takes a bit of logic to inspect the visible items, and their position, and then decide on which item the prev/next cursor keys will scroll to from that point.
There's a few edge cases I've got to handle - it seems if you scroll the list very fast things may not update as you like, so I might need to implement a few extra things, but in any case, it's working pretty well!