import SwiftUI struct SheetGrid: View { @Bindable var model: SheetModel @State private var editing: Coordinate? @State private var resizing: SheetResizePreview? @State private var savingResize = false @GestureState private var draggingResize = false @FocusState private var focused: Bool var beginEditing: () -> Void = {} var body: some View { ScrollView([.horizontal, .vertical]) { LazyVStack(spacing: 0, pinnedViews: [.sectionHeaders]) { Section { ForEach(0.. KeyPress.Result { guard editing == nil else { return .ignored } let p = model.selection.start model.select( Coordinate(r: max(0, min(model.rows - 1, p.r + r)), c: max(0, min(model.cols - 1, p.c + c)))) return .handled } private func width(_ col: Int) -> Double { if let resizing, resizing.axis == "col", resizing.index == col { return resizing.size } return model.sizes.cols?[String(col)] ?? 110 } private func height(_ row: Int) -> Double { if let resizing, resizing.axis == "row", resizing.index == row { return resizing.size } return model.sizes.rows?[String(row)] ?? 32 } #if os(macOS) private func resizeHandle(axis: String, index: Int) -> some View { SheetResizeHandle(vertical: axis == "row", canBegin: { model.mayWrite && !model.busy && !model.hasUnconfirmedChanges && !savingResize }, change: { translation in if resizing == nil { resizing = SheetResizePreview(axis: axis, index: index, original: axis == "col" ? width(index) : height(index)) } resizing?.update(translation: translation) }, finish: { finishResize(axis: axis, index: index) }, cancel: { if !savingResize { resizing = nil } }, fit: axis == "col" ? { Task { await model.fitColumn(index) } } : nil) } #endif private func finishResize(axis: String, index: Int) { guard let preview = resizing, preview.axis == axis, preview.index == index else { return } savingResize = true Task { await model.resize(axis: axis, size: Int(preview.size), index: index) resizing = nil savingResize = false } } private func resizeGesture(axis: String, index: Int) -> some Gesture { DragGesture(minimumDistance: 2, coordinateSpace: .global) .updating($draggingResize) { _, active, _ in active = true } .onChanged { value in guard model.mayWrite, !model.busy, !model.hasUnconfirmedChanges else { return } if resizing == nil { resizing = SheetResizePreview(axis: axis, index: index, original: axis == "col" ? width(index) : height(index)) } guard resizing?.axis == axis, resizing?.index == index else { return } resizing?.update(translation: axis == "col" ? value.translation.width : value.translation.height) } .onEnded { _ in finishResize(axis: axis, index: index) } } } /// Observe selection in a leaf, so moving between cells does not invalidate the /// LazyVStack and every row's view construction. private struct SheetSelectionObserver: View { let model: SheetModel @Binding var editing: Coordinate? var body: some View { Color.clear.frame(width: 0, height: 0) .onChange(of: model.selection.start) { _, point in if editing != point { editing = nil } } } } struct SheetResizePreview { let axis: String let index: Int let original: Double var size: Double init(axis: String, index: Int, original: Double) { self.axis = axis; self.index = index; self.original = original; self.size = original } mutating func update(translation: Double) { size = min(2000, max(16, (original + translation).rounded())) } }