├── .gitignore ├── .sourcery.yml ├── Images ├── iOSa.png ├── iOSb.png ├── iOSc.png ├── macOSa.png ├── macOSb.png └── macOSc.png ├── LICENSE.txt ├── Package.swift ├── README.md ├── Sources ├── Generated │ ├── TablerGrid+AutoInit.generated.swift │ ├── TablerGrid1+AutoInit.generated.swift │ ├── TablerGrid1B+AutoInit.generated.swift │ ├── TablerGrid1C+AutoInit.generated.swift │ ├── TablerGridB+AutoInit.generated.swift │ ├── TablerGridC+AutoInit.generated.swift │ ├── TablerGridM+AutoInit.generated.swift │ ├── TablerGridMB+AutoInit.generated.swift │ ├── TablerGridMC+AutoInit.generated.swift │ ├── TablerList+AutoInit.generated.swift │ ├── TablerList1+AutoInit.generated.swift │ ├── TablerList1B+AutoInit.generated.swift │ ├── TablerList1C+AutoInit.generated.swift │ ├── TablerListB+AutoInit.generated.swift │ ├── TablerListC+AutoInit.generated.swift │ ├── TablerListM+AutoInit.generated.swift │ ├── TablerListMB+AutoInit.generated.swift │ ├── TablerListMC+AutoInit.generated.swift │ ├── TablerStack+AutoInit.generated.swift │ ├── TablerStack1+AutoInit.generated.swift │ ├── TablerStack1B+AutoInit.generated.swift │ ├── TablerStack1C+AutoInit.generated.swift │ ├── TablerStackB+AutoInit.generated.swift │ ├── TablerStackC+AutoInit.generated.swift │ ├── TablerStackM+AutoInit.generated.swift │ ├── TablerStackMB+AutoInit.generated.swift │ └── TablerStackMC+AutoInit.generated.swift ├── Grid │ ├── Internal │ │ ├── BaseGrid.swift │ │ ├── GridItemMod.swift │ │ ├── GridItemMod1.swift │ │ └── GridItemModM.swift │ ├── TablerGrid.swift │ ├── TablerGrid1.swift │ ├── TablerGrid1B.swift │ ├── TablerGrid1C.swift │ ├── TablerGridB.swift │ ├── TablerGridC.swift │ ├── TablerGridConfig.swift │ ├── TablerGridM.swift │ ├── TablerGridMB.swift │ └── TablerGridMC.swift ├── Internal │ ├── BaseTable.swift │ ├── ObservableHolder.swift │ └── TablerSpacedConfig.swift ├── List │ ├── Internal │ │ ├── BaseList.swift │ │ ├── BaseList1.swift │ │ ├── BaseListM.swift │ │ └── ListRowMod.swift │ ├── TablerList.swift │ ├── TablerList1.swift │ ├── TablerList1B.swift │ ├── TablerList1C.swift │ ├── TablerListB.swift │ ├── TablerListC.swift │ ├── TablerListConfig.swift │ ├── TablerListM.swift │ ├── TablerListMB.swift │ └── TablerListMC.swift ├── Stack │ ├── Internal │ │ ├── BaseStack.swift │ │ ├── StackRowMod.swift │ │ ├── StackRowMod1.swift │ │ └── StackRowModM.swift │ ├── TablerStack.swift │ ├── TablerStack1.swift │ ├── TablerStack1B.swift │ ├── TablerStack1C.swift │ ├── TablerStackB.swift │ ├── TablerStackC.swift │ ├── TablerStackConfig.swift │ ├── TablerStackM.swift │ ├── TablerStackMB.swift │ └── TablerStackMC.swift ├── TablerConfig.swift ├── TablerContext.swift └── TablerSort.swift └── Templates └── AutoInit.stencil /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /.build 3 | /Packages 4 | /*.xcodeproj 5 | xcuserdata/ 6 | .swiftpm/ 7 | Package.resolved 8 | -------------------------------------------------------------------------------- /.sourcery.yml: -------------------------------------------------------------------------------- 1 | sources: 2 | - Sources 3 | templates: 4 | - Templates 5 | output: Sources/Generated 6 | -------------------------------------------------------------------------------- /Images/iOSa.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openalloc/SwiftTabler/b5503059b10f88b6c94a49dff28e44617bbfa035/Images/iOSa.png -------------------------------------------------------------------------------- /Images/iOSb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openalloc/SwiftTabler/b5503059b10f88b6c94a49dff28e44617bbfa035/Images/iOSb.png -------------------------------------------------------------------------------- /Images/iOSc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openalloc/SwiftTabler/b5503059b10f88b6c94a49dff28e44617bbfa035/Images/iOSc.png -------------------------------------------------------------------------------- /Images/macOSa.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openalloc/SwiftTabler/b5503059b10f88b6c94a49dff28e44617bbfa035/Images/macOSa.png -------------------------------------------------------------------------------- /Images/macOSb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openalloc/SwiftTabler/b5503059b10f88b6c94a49dff28e44617bbfa035/Images/macOSb.png -------------------------------------------------------------------------------- /Images/macOSc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openalloc/SwiftTabler/b5503059b10f88b6c94a49dff28e44617bbfa035/Images/macOSc.png -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version: 5.7 2 | 3 | // Copyright 2021, 2022 OpenAlloc LLC 4 | // 5 | // Licensed under the Apache License, Version 2.0 (the "License"); 6 | // you may not use this file except in compliance with the License. 7 | // You may obtain a copy of the License at 8 | // 9 | // http://www.apache.org/licenses/LICENSE-2.0 10 | // 11 | // Unless required by applicable law or agreed to in writing, software 12 | // distributed under the License is distributed on an "AS IS" BASIS, 13 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | // See the License for the specific language governing permissions and 15 | // limitations under the License. 16 | // 17 | 18 | import PackageDescription 19 | 20 | let package = Package( 21 | name: "SwiftTabler", 22 | platforms: [.macOS("11.0"), .iOS("14.0")], 23 | products: [ 24 | .library(name: "Tabler", targets: ["Tabler"]), 25 | ], 26 | dependencies: [], 27 | targets: [ 28 | .target( 29 | name: "Tabler", 30 | dependencies: [], 31 | path: "Sources" 32 | ), 33 | // .testTarget( 34 | // name: "TablerTests", 35 | // dependencies: [ 36 | // "Tabler", 37 | // ], 38 | // path: "Tests" 39 | // ), 40 | ] 41 | ) 42 | -------------------------------------------------------------------------------- /Sources/Generated/TablerGrid+AutoInit.generated.swift: -------------------------------------------------------------------------------- 1 | // Generated using Sourcery 1.8.1 — https://github.com/krzysztofzablocki/Sourcery 2 | // DO NOT EDIT 3 | 4 | import SwiftUI 5 | 6 | public extension TablerGrid { 7 | // omitting Header 8 | init(_ config: Config = .init(), 9 | @ViewBuilder footer: @escaping FooterContent, 10 | @ViewBuilder row: @escaping RowContent, 11 | @ViewBuilder rowBackground: @escaping RowBackground, 12 | @ViewBuilder rowOverlay: @escaping RowOverlay, 13 | results: Results) 14 | where Header == EmptyView 15 | { 16 | self.init(config, 17 | header: { _ in EmptyView() }, 18 | footer: footer, 19 | row: row, 20 | rowBackground: rowBackground, 21 | rowOverlay: rowOverlay, 22 | results: results) 23 | } 24 | 25 | // omitting Overlay 26 | init(_ config: Config = .init(), 27 | @ViewBuilder header: @escaping HeaderContent, 28 | @ViewBuilder footer: @escaping FooterContent, 29 | @ViewBuilder row: @escaping RowContent, 30 | @ViewBuilder rowBackground: @escaping RowBackground, 31 | results: Results) 32 | where RowOver == EmptyView 33 | { 34 | self.init(config, 35 | header: header, 36 | footer: footer, 37 | row: row, 38 | rowBackground: rowBackground, 39 | rowOverlay: { _ in EmptyView() }, 40 | results: results) 41 | } 42 | 43 | // omitting Background 44 | init(_ config: Config = .init(), 45 | @ViewBuilder header: @escaping HeaderContent, 46 | @ViewBuilder footer: @escaping FooterContent, 47 | @ViewBuilder row: @escaping RowContent, 48 | @ViewBuilder rowOverlay: @escaping RowOverlay, 49 | results: Results) 50 | where RowBack == EmptyView 51 | { 52 | self.init(config, 53 | header: header, 54 | footer: footer, 55 | row: row, 56 | rowBackground: { _ in EmptyView() }, 57 | rowOverlay: rowOverlay, 58 | results: results) 59 | } 60 | 61 | // omitting Header AND Overlay 62 | init(_ config: Config = .init(), 63 | @ViewBuilder footer: @escaping FooterContent, 64 | @ViewBuilder row: @escaping RowContent, 65 | @ViewBuilder rowBackground: @escaping RowBackground, 66 | results: Results) 67 | where Header == EmptyView, RowOver == EmptyView 68 | { 69 | self.init(config, 70 | header: { _ in EmptyView() }, 71 | footer: footer, 72 | row: row, 73 | rowBackground: rowBackground, 74 | rowOverlay: { _ in EmptyView() }, 75 | results: results) 76 | } 77 | 78 | // omitting Header AND Background 79 | init(_ config: Config = .init(), 80 | @ViewBuilder footer: @escaping FooterContent, 81 | @ViewBuilder row: @escaping RowContent, 82 | @ViewBuilder rowOverlay: @escaping RowOverlay, 83 | results: Results) 84 | where Header == EmptyView, RowBack == EmptyView 85 | { 86 | self.init(config, 87 | header: { _ in EmptyView() }, 88 | footer: footer, 89 | row: row, 90 | rowBackground: { _ in EmptyView() }, 91 | rowOverlay: rowOverlay, 92 | results: results) 93 | } 94 | 95 | // omitting Background AND Overlay 96 | init(_ config: Config = .init(), 97 | @ViewBuilder header: @escaping HeaderContent, 98 | @ViewBuilder footer: @escaping FooterContent, 99 | @ViewBuilder row: @escaping RowContent, 100 | results: Results) 101 | where RowBack == EmptyView, RowOver == EmptyView 102 | { 103 | self.init(config, 104 | header: header, 105 | footer: footer, 106 | row: row, 107 | rowBackground: { _ in EmptyView() }, 108 | rowOverlay: { _ in EmptyView() }, 109 | results: results) 110 | } 111 | 112 | // omitting Header, Background, AND Overlay 113 | init(_ config: Config = .init(), 114 | @ViewBuilder footer: @escaping FooterContent, 115 | @ViewBuilder row: @escaping RowContent, 116 | results: Results) 117 | 118 | where Header == EmptyView, RowBack == EmptyView, RowOver == EmptyView 119 | { 120 | self.init(config, 121 | header: { _ in EmptyView() }, 122 | footer: footer, 123 | row: row, 124 | rowBackground: { _ in EmptyView() }, 125 | rowOverlay: { _ in EmptyView() }, 126 | results: results) 127 | } 128 | 129 | // omitting Footer 130 | init(_ config: Config = .init(), 131 | @ViewBuilder header: @escaping HeaderContent, 132 | @ViewBuilder row: @escaping RowContent, 133 | @ViewBuilder rowBackground: @escaping RowBackground, 134 | @ViewBuilder rowOverlay: @escaping RowOverlay, 135 | results: Results) 136 | where Footer == EmptyView 137 | { 138 | self.init(config, 139 | header: header, 140 | footer: { _ in EmptyView() }, 141 | row: row, 142 | rowBackground: rowBackground, 143 | rowOverlay: rowOverlay, 144 | results: results) 145 | } 146 | 147 | // omitting Header, Footer 148 | init(_ config: Config = .init(), 149 | @ViewBuilder row: @escaping RowContent, 150 | @ViewBuilder rowBackground: @escaping RowBackground, 151 | @ViewBuilder rowOverlay: @escaping RowOverlay, 152 | results: Results) 153 | where Header == EmptyView, Footer == EmptyView 154 | { 155 | self.init(config, 156 | header: { _ in EmptyView() }, 157 | footer: { _ in EmptyView() }, 158 | row: row, 159 | rowBackground: rowBackground, 160 | rowOverlay: rowOverlay, 161 | results: results) 162 | } 163 | 164 | // omitting Footer, Overlay 165 | init(_ config: Config = .init(), 166 | @ViewBuilder header: @escaping HeaderContent, 167 | @ViewBuilder row: @escaping RowContent, 168 | @ViewBuilder rowBackground: @escaping RowBackground, 169 | results: Results) 170 | where Footer == EmptyView, RowOver == EmptyView 171 | { 172 | self.init(config, 173 | header: header, 174 | footer: { _ in EmptyView() }, 175 | row: row, 176 | rowBackground: rowBackground, 177 | rowOverlay: { _ in EmptyView() }, 178 | results: results) 179 | } 180 | 181 | // omitting Footer, Background 182 | init(_ config: Config = .init(), 183 | @ViewBuilder header: @escaping HeaderContent, 184 | @ViewBuilder row: @escaping RowContent, 185 | @ViewBuilder rowOverlay: @escaping RowOverlay, 186 | results: Results) 187 | where Footer == EmptyView, RowBack == EmptyView 188 | { 189 | self.init(config, 190 | header: header, 191 | footer: { _ in EmptyView() }, 192 | row: row, 193 | rowBackground: { _ in EmptyView() }, 194 | rowOverlay: rowOverlay, 195 | results: results) 196 | } 197 | 198 | // omitting Header, Footer AND Overlay 199 | init(_ config: Config = .init(), 200 | @ViewBuilder row: @escaping RowContent, 201 | @ViewBuilder rowBackground: @escaping RowBackground, 202 | results: Results) 203 | where Header == EmptyView, Footer == EmptyView, RowOver == EmptyView 204 | { 205 | self.init(config, 206 | header: { _ in EmptyView() }, 207 | footer: { _ in EmptyView() }, 208 | row: row, 209 | rowBackground: rowBackground, 210 | rowOverlay: { _ in EmptyView() }, 211 | results: results) 212 | } 213 | 214 | // omitting Header, Footer AND Background 215 | init(_ config: Config = .init(), 216 | @ViewBuilder row: @escaping RowContent, 217 | @ViewBuilder rowOverlay: @escaping RowOverlay, 218 | results: Results) 219 | where Header == EmptyView, Footer == EmptyView, RowBack == EmptyView 220 | { 221 | self.init(config, 222 | header: { _ in EmptyView() }, 223 | footer: { _ in EmptyView() }, 224 | row: row, 225 | rowBackground: { _ in EmptyView() }, 226 | rowOverlay: rowOverlay, 227 | results: results) 228 | } 229 | 230 | // omitting Footer, Background AND Overlay 231 | init(_ config: Config = .init(), 232 | @ViewBuilder header: @escaping HeaderContent, 233 | @ViewBuilder row: @escaping RowContent, 234 | results: Results) 235 | where Footer == EmptyView, RowBack == EmptyView, RowOver == EmptyView 236 | { 237 | self.init(config, 238 | header: header, 239 | footer: { _ in EmptyView() }, 240 | row: row, 241 | rowBackground: { _ in EmptyView() }, 242 | rowOverlay: { _ in EmptyView() }, 243 | results: results) 244 | } 245 | 246 | // omitting Header, Footer, Background, AND Overlay 247 | init(_ config: Config = .init(), 248 | @ViewBuilder row: @escaping RowContent, 249 | results: Results) 250 | 251 | where Header == EmptyView, Footer == EmptyView, RowBack == EmptyView, RowOver == EmptyView 252 | { 253 | self.init(config, 254 | header: { _ in EmptyView() }, 255 | footer: { _ in EmptyView() }, 256 | row: row, 257 | rowBackground: { _ in EmptyView() }, 258 | rowOverlay: { _ in EmptyView() }, 259 | results: results) 260 | } 261 | } 262 | -------------------------------------------------------------------------------- /Sources/Generated/TablerGridB+AutoInit.generated.swift: -------------------------------------------------------------------------------- 1 | // Generated using Sourcery 1.8.1 — https://github.com/krzysztofzablocki/Sourcery 2 | // DO NOT EDIT 3 | 4 | import SwiftUI 5 | 6 | public extension TablerGridB { 7 | // omitting Header 8 | init(_ config: Config = .init(), 9 | @ViewBuilder footer: @escaping FooterContent, 10 | @ViewBuilder row: @escaping RowContent, 11 | @ViewBuilder rowBackground: @escaping RowBackground, 12 | @ViewBuilder rowOverlay: @escaping RowOverlay, 13 | results: Binding) 14 | where Header == EmptyView 15 | { 16 | self.init(config, 17 | header: { _ in EmptyView() }, 18 | footer: footer, 19 | row: row, 20 | rowBackground: rowBackground, 21 | rowOverlay: rowOverlay, 22 | results: results) 23 | } 24 | 25 | // omitting Overlay 26 | init(_ config: Config = .init(), 27 | @ViewBuilder header: @escaping HeaderContent, 28 | @ViewBuilder footer: @escaping FooterContent, 29 | @ViewBuilder row: @escaping RowContent, 30 | @ViewBuilder rowBackground: @escaping RowBackground, 31 | results: Binding) 32 | where RowOver == EmptyView 33 | { 34 | self.init(config, 35 | header: header, 36 | footer: footer, 37 | row: row, 38 | rowBackground: rowBackground, 39 | rowOverlay: { _ in EmptyView() }, 40 | results: results) 41 | } 42 | 43 | // omitting Background 44 | init(_ config: Config = .init(), 45 | @ViewBuilder header: @escaping HeaderContent, 46 | @ViewBuilder footer: @escaping FooterContent, 47 | @ViewBuilder row: @escaping RowContent, 48 | @ViewBuilder rowOverlay: @escaping RowOverlay, 49 | results: Binding) 50 | where RowBack == EmptyView 51 | { 52 | self.init(config, 53 | header: header, 54 | footer: footer, 55 | row: row, 56 | rowBackground: { _ in EmptyView() }, 57 | rowOverlay: rowOverlay, 58 | results: results) 59 | } 60 | 61 | // omitting Header AND Overlay 62 | init(_ config: Config = .init(), 63 | @ViewBuilder footer: @escaping FooterContent, 64 | @ViewBuilder row: @escaping RowContent, 65 | @ViewBuilder rowBackground: @escaping RowBackground, 66 | results: Binding) 67 | where Header == EmptyView, RowOver == EmptyView 68 | { 69 | self.init(config, 70 | header: { _ in EmptyView() }, 71 | footer: footer, 72 | row: row, 73 | rowBackground: rowBackground, 74 | rowOverlay: { _ in EmptyView() }, 75 | results: results) 76 | } 77 | 78 | // omitting Header AND Background 79 | init(_ config: Config = .init(), 80 | @ViewBuilder footer: @escaping FooterContent, 81 | @ViewBuilder row: @escaping RowContent, 82 | @ViewBuilder rowOverlay: @escaping RowOverlay, 83 | results: Binding) 84 | where Header == EmptyView, RowBack == EmptyView 85 | { 86 | self.init(config, 87 | header: { _ in EmptyView() }, 88 | footer: footer, 89 | row: row, 90 | rowBackground: { _ in EmptyView() }, 91 | rowOverlay: rowOverlay, 92 | results: results) 93 | } 94 | 95 | // omitting Background AND Overlay 96 | init(_ config: Config = .init(), 97 | @ViewBuilder header: @escaping HeaderContent, 98 | @ViewBuilder footer: @escaping FooterContent, 99 | @ViewBuilder row: @escaping RowContent, 100 | results: Binding) 101 | where RowBack == EmptyView, RowOver == EmptyView 102 | { 103 | self.init(config, 104 | header: header, 105 | footer: footer, 106 | row: row, 107 | rowBackground: { _ in EmptyView() }, 108 | rowOverlay: { _ in EmptyView() }, 109 | results: results) 110 | } 111 | 112 | // omitting Header, Background, AND Overlay 113 | init(_ config: Config = .init(), 114 | @ViewBuilder footer: @escaping FooterContent, 115 | @ViewBuilder row: @escaping RowContent, 116 | results: Binding) 117 | 118 | where Header == EmptyView, RowBack == EmptyView, RowOver == EmptyView 119 | { 120 | self.init(config, 121 | header: { _ in EmptyView() }, 122 | footer: footer, 123 | row: row, 124 | rowBackground: { _ in EmptyView() }, 125 | rowOverlay: { _ in EmptyView() }, 126 | results: results) 127 | } 128 | 129 | // omitting Footer 130 | init(_ config: Config = .init(), 131 | @ViewBuilder header: @escaping HeaderContent, 132 | @ViewBuilder row: @escaping RowContent, 133 | @ViewBuilder rowBackground: @escaping RowBackground, 134 | @ViewBuilder rowOverlay: @escaping RowOverlay, 135 | results: Binding) 136 | where Footer == EmptyView 137 | { 138 | self.init(config, 139 | header: header, 140 | footer: { _ in EmptyView() }, 141 | row: row, 142 | rowBackground: rowBackground, 143 | rowOverlay: rowOverlay, 144 | results: results) 145 | } 146 | 147 | // omitting Header, Footer 148 | init(_ config: Config = .init(), 149 | @ViewBuilder row: @escaping RowContent, 150 | @ViewBuilder rowBackground: @escaping RowBackground, 151 | @ViewBuilder rowOverlay: @escaping RowOverlay, 152 | results: Binding) 153 | where Header == EmptyView, Footer == EmptyView 154 | { 155 | self.init(config, 156 | header: { _ in EmptyView() }, 157 | footer: { _ in EmptyView() }, 158 | row: row, 159 | rowBackground: rowBackground, 160 | rowOverlay: rowOverlay, 161 | results: results) 162 | } 163 | 164 | // omitting Footer, Overlay 165 | init(_ config: Config = .init(), 166 | @ViewBuilder header: @escaping HeaderContent, 167 | @ViewBuilder row: @escaping RowContent, 168 | @ViewBuilder rowBackground: @escaping RowBackground, 169 | results: Binding) 170 | where Footer == EmptyView, RowOver == EmptyView 171 | { 172 | self.init(config, 173 | header: header, 174 | footer: { _ in EmptyView() }, 175 | row: row, 176 | rowBackground: rowBackground, 177 | rowOverlay: { _ in EmptyView() }, 178 | results: results) 179 | } 180 | 181 | // omitting Footer, Background 182 | init(_ config: Config = .init(), 183 | @ViewBuilder header: @escaping HeaderContent, 184 | @ViewBuilder row: @escaping RowContent, 185 | @ViewBuilder rowOverlay: @escaping RowOverlay, 186 | results: Binding) 187 | where Footer == EmptyView, RowBack == EmptyView 188 | { 189 | self.init(config, 190 | header: header, 191 | footer: { _ in EmptyView() }, 192 | row: row, 193 | rowBackground: { _ in EmptyView() }, 194 | rowOverlay: rowOverlay, 195 | results: results) 196 | } 197 | 198 | // omitting Header, Footer AND Overlay 199 | init(_ config: Config = .init(), 200 | @ViewBuilder row: @escaping RowContent, 201 | @ViewBuilder rowBackground: @escaping RowBackground, 202 | results: Binding) 203 | where Header == EmptyView, Footer == EmptyView, RowOver == EmptyView 204 | { 205 | self.init(config, 206 | header: { _ in EmptyView() }, 207 | footer: { _ in EmptyView() }, 208 | row: row, 209 | rowBackground: rowBackground, 210 | rowOverlay: { _ in EmptyView() }, 211 | results: results) 212 | } 213 | 214 | // omitting Header, Footer AND Background 215 | init(_ config: Config = .init(), 216 | @ViewBuilder row: @escaping RowContent, 217 | @ViewBuilder rowOverlay: @escaping RowOverlay, 218 | results: Binding) 219 | where Header == EmptyView, Footer == EmptyView, RowBack == EmptyView 220 | { 221 | self.init(config, 222 | header: { _ in EmptyView() }, 223 | footer: { _ in EmptyView() }, 224 | row: row, 225 | rowBackground: { _ in EmptyView() }, 226 | rowOverlay: rowOverlay, 227 | results: results) 228 | } 229 | 230 | // omitting Footer, Background AND Overlay 231 | init(_ config: Config = .init(), 232 | @ViewBuilder header: @escaping HeaderContent, 233 | @ViewBuilder row: @escaping RowContent, 234 | results: Binding) 235 | where Footer == EmptyView, RowBack == EmptyView, RowOver == EmptyView 236 | { 237 | self.init(config, 238 | header: header, 239 | footer: { _ in EmptyView() }, 240 | row: row, 241 | rowBackground: { _ in EmptyView() }, 242 | rowOverlay: { _ in EmptyView() }, 243 | results: results) 244 | } 245 | 246 | // omitting Header, Footer, Background, AND Overlay 247 | init(_ config: Config = .init(), 248 | @ViewBuilder row: @escaping RowContent, 249 | results: Binding) 250 | 251 | where Header == EmptyView, Footer == EmptyView, RowBack == EmptyView, RowOver == EmptyView 252 | { 253 | self.init(config, 254 | header: { _ in EmptyView() }, 255 | footer: { _ in EmptyView() }, 256 | row: row, 257 | rowBackground: { _ in EmptyView() }, 258 | rowOverlay: { _ in EmptyView() }, 259 | results: results) 260 | } 261 | } 262 | -------------------------------------------------------------------------------- /Sources/Generated/TablerGridC+AutoInit.generated.swift: -------------------------------------------------------------------------------- 1 | // Generated using Sourcery 1.8.1 — https://github.com/krzysztofzablocki/Sourcery 2 | // DO NOT EDIT 3 | 4 | import SwiftUI 5 | 6 | public extension TablerGridC { 7 | // omitting Header 8 | init(_ config: Config = .init(), 9 | @ViewBuilder footer: @escaping FooterContent, 10 | @ViewBuilder row: @escaping RowContent, 11 | @ViewBuilder rowBackground: @escaping RowBackground, 12 | @ViewBuilder rowOverlay: @escaping RowOverlay, 13 | results: Results) 14 | where Header == EmptyView 15 | { 16 | self.init(config, 17 | header: { _ in EmptyView() }, 18 | footer: footer, 19 | row: row, 20 | rowBackground: rowBackground, 21 | rowOverlay: rowOverlay, 22 | results: results) 23 | } 24 | 25 | // omitting Overlay 26 | init(_ config: Config = .init(), 27 | @ViewBuilder header: @escaping HeaderContent, 28 | @ViewBuilder footer: @escaping FooterContent, 29 | @ViewBuilder row: @escaping RowContent, 30 | @ViewBuilder rowBackground: @escaping RowBackground, 31 | results: Results) 32 | where RowOver == EmptyView 33 | { 34 | self.init(config, 35 | header: header, 36 | footer: footer, 37 | row: row, 38 | rowBackground: rowBackground, 39 | rowOverlay: { _ in EmptyView() }, 40 | results: results) 41 | } 42 | 43 | // omitting Background 44 | init(_ config: Config = .init(), 45 | @ViewBuilder header: @escaping HeaderContent, 46 | @ViewBuilder footer: @escaping FooterContent, 47 | @ViewBuilder row: @escaping RowContent, 48 | @ViewBuilder rowOverlay: @escaping RowOverlay, 49 | results: Results) 50 | where RowBack == EmptyView 51 | { 52 | self.init(config, 53 | header: header, 54 | footer: footer, 55 | row: row, 56 | rowBackground: { _ in EmptyView() }, 57 | rowOverlay: rowOverlay, 58 | results: results) 59 | } 60 | 61 | // omitting Header AND Overlay 62 | init(_ config: Config = .init(), 63 | @ViewBuilder footer: @escaping FooterContent, 64 | @ViewBuilder row: @escaping RowContent, 65 | @ViewBuilder rowBackground: @escaping RowBackground, 66 | results: Results) 67 | where Header == EmptyView, RowOver == EmptyView 68 | { 69 | self.init(config, 70 | header: { _ in EmptyView() }, 71 | footer: footer, 72 | row: row, 73 | rowBackground: rowBackground, 74 | rowOverlay: { _ in EmptyView() }, 75 | results: results) 76 | } 77 | 78 | // omitting Header AND Background 79 | init(_ config: Config = .init(), 80 | @ViewBuilder footer: @escaping FooterContent, 81 | @ViewBuilder row: @escaping RowContent, 82 | @ViewBuilder rowOverlay: @escaping RowOverlay, 83 | results: Results) 84 | where Header == EmptyView, RowBack == EmptyView 85 | { 86 | self.init(config, 87 | header: { _ in EmptyView() }, 88 | footer: footer, 89 | row: row, 90 | rowBackground: { _ in EmptyView() }, 91 | rowOverlay: rowOverlay, 92 | results: results) 93 | } 94 | 95 | // omitting Background AND Overlay 96 | init(_ config: Config = .init(), 97 | @ViewBuilder header: @escaping HeaderContent, 98 | @ViewBuilder footer: @escaping FooterContent, 99 | @ViewBuilder row: @escaping RowContent, 100 | results: Results) 101 | where RowBack == EmptyView, RowOver == EmptyView 102 | { 103 | self.init(config, 104 | header: header, 105 | footer: footer, 106 | row: row, 107 | rowBackground: { _ in EmptyView() }, 108 | rowOverlay: { _ in EmptyView() }, 109 | results: results) 110 | } 111 | 112 | // omitting Header, Background, AND Overlay 113 | init(_ config: Config = .init(), 114 | @ViewBuilder footer: @escaping FooterContent, 115 | @ViewBuilder row: @escaping RowContent, 116 | results: Results) 117 | 118 | where Header == EmptyView, RowBack == EmptyView, RowOver == EmptyView 119 | { 120 | self.init(config, 121 | header: { _ in EmptyView() }, 122 | footer: footer, 123 | row: row, 124 | rowBackground: { _ in EmptyView() }, 125 | rowOverlay: { _ in EmptyView() }, 126 | results: results) 127 | } 128 | 129 | // omitting Footer 130 | init(_ config: Config = .init(), 131 | @ViewBuilder header: @escaping HeaderContent, 132 | @ViewBuilder row: @escaping RowContent, 133 | @ViewBuilder rowBackground: @escaping RowBackground, 134 | @ViewBuilder rowOverlay: @escaping RowOverlay, 135 | results: Results) 136 | where Footer == EmptyView 137 | { 138 | self.init(config, 139 | header: header, 140 | footer: { _ in EmptyView() }, 141 | row: row, 142 | rowBackground: rowBackground, 143 | rowOverlay: rowOverlay, 144 | results: results) 145 | } 146 | 147 | // omitting Header, Footer 148 | init(_ config: Config = .init(), 149 | @ViewBuilder row: @escaping RowContent, 150 | @ViewBuilder rowBackground: @escaping RowBackground, 151 | @ViewBuilder rowOverlay: @escaping RowOverlay, 152 | results: Results) 153 | where Header == EmptyView, Footer == EmptyView 154 | { 155 | self.init(config, 156 | header: { _ in EmptyView() }, 157 | footer: { _ in EmptyView() }, 158 | row: row, 159 | rowBackground: rowBackground, 160 | rowOverlay: rowOverlay, 161 | results: results) 162 | } 163 | 164 | // omitting Footer, Overlay 165 | init(_ config: Config = .init(), 166 | @ViewBuilder header: @escaping HeaderContent, 167 | @ViewBuilder row: @escaping RowContent, 168 | @ViewBuilder rowBackground: @escaping RowBackground, 169 | results: Results) 170 | where Footer == EmptyView, RowOver == EmptyView 171 | { 172 | self.init(config, 173 | header: header, 174 | footer: { _ in EmptyView() }, 175 | row: row, 176 | rowBackground: rowBackground, 177 | rowOverlay: { _ in EmptyView() }, 178 | results: results) 179 | } 180 | 181 | // omitting Footer, Background 182 | init(_ config: Config = .init(), 183 | @ViewBuilder header: @escaping HeaderContent, 184 | @ViewBuilder row: @escaping RowContent, 185 | @ViewBuilder rowOverlay: @escaping RowOverlay, 186 | results: Results) 187 | where Footer == EmptyView, RowBack == EmptyView 188 | { 189 | self.init(config, 190 | header: header, 191 | footer: { _ in EmptyView() }, 192 | row: row, 193 | rowBackground: { _ in EmptyView() }, 194 | rowOverlay: rowOverlay, 195 | results: results) 196 | } 197 | 198 | // omitting Header, Footer AND Overlay 199 | init(_ config: Config = .init(), 200 | @ViewBuilder row: @escaping RowContent, 201 | @ViewBuilder rowBackground: @escaping RowBackground, 202 | results: Results) 203 | where Header == EmptyView, Footer == EmptyView, RowOver == EmptyView 204 | { 205 | self.init(config, 206 | header: { _ in EmptyView() }, 207 | footer: { _ in EmptyView() }, 208 | row: row, 209 | rowBackground: rowBackground, 210 | rowOverlay: { _ in EmptyView() }, 211 | results: results) 212 | } 213 | 214 | // omitting Header, Footer AND Background 215 | init(_ config: Config = .init(), 216 | @ViewBuilder row: @escaping RowContent, 217 | @ViewBuilder rowOverlay: @escaping RowOverlay, 218 | results: Results) 219 | where Header == EmptyView, Footer == EmptyView, RowBack == EmptyView 220 | { 221 | self.init(config, 222 | header: { _ in EmptyView() }, 223 | footer: { _ in EmptyView() }, 224 | row: row, 225 | rowBackground: { _ in EmptyView() }, 226 | rowOverlay: rowOverlay, 227 | results: results) 228 | } 229 | 230 | // omitting Footer, Background AND Overlay 231 | init(_ config: Config = .init(), 232 | @ViewBuilder header: @escaping HeaderContent, 233 | @ViewBuilder row: @escaping RowContent, 234 | results: Results) 235 | where Footer == EmptyView, RowBack == EmptyView, RowOver == EmptyView 236 | { 237 | self.init(config, 238 | header: header, 239 | footer: { _ in EmptyView() }, 240 | row: row, 241 | rowBackground: { _ in EmptyView() }, 242 | rowOverlay: { _ in EmptyView() }, 243 | results: results) 244 | } 245 | 246 | // omitting Header, Footer, Background, AND Overlay 247 | init(_ config: Config = .init(), 248 | @ViewBuilder row: @escaping RowContent, 249 | results: Results) 250 | 251 | where Header == EmptyView, Footer == EmptyView, RowBack == EmptyView, RowOver == EmptyView 252 | { 253 | self.init(config, 254 | header: { _ in EmptyView() }, 255 | footer: { _ in EmptyView() }, 256 | row: row, 257 | rowBackground: { _ in EmptyView() }, 258 | rowOverlay: { _ in EmptyView() }, 259 | results: results) 260 | } 261 | } 262 | -------------------------------------------------------------------------------- /Sources/Generated/TablerList+AutoInit.generated.swift: -------------------------------------------------------------------------------- 1 | // Generated using Sourcery 1.8.1 — https://github.com/krzysztofzablocki/Sourcery 2 | // DO NOT EDIT 3 | 4 | import SwiftUI 5 | 6 | public extension TablerList { 7 | // omitting Header 8 | init(_ config: Config = .init(), 9 | @ViewBuilder footer: @escaping FooterContent, 10 | @ViewBuilder row: @escaping RowContent, 11 | @ViewBuilder rowBackground: @escaping RowBackground, 12 | @ViewBuilder rowOverlay: @escaping RowOverlay, 13 | results: Results) 14 | where Header == EmptyView 15 | { 16 | self.init(config, 17 | header: { _ in EmptyView() }, 18 | footer: footer, 19 | row: row, 20 | rowBackground: rowBackground, 21 | rowOverlay: rowOverlay, 22 | results: results) 23 | } 24 | 25 | // omitting Overlay 26 | init(_ config: Config = .init(), 27 | @ViewBuilder header: @escaping HeaderContent, 28 | @ViewBuilder footer: @escaping FooterContent, 29 | @ViewBuilder row: @escaping RowContent, 30 | @ViewBuilder rowBackground: @escaping RowBackground, 31 | results: Results) 32 | where RowOver == EmptyView 33 | { 34 | self.init(config, 35 | header: header, 36 | footer: footer, 37 | row: row, 38 | rowBackground: rowBackground, 39 | rowOverlay: { _ in EmptyView() }, 40 | results: results) 41 | } 42 | 43 | // omitting Background 44 | init(_ config: Config = .init(), 45 | @ViewBuilder header: @escaping HeaderContent, 46 | @ViewBuilder footer: @escaping FooterContent, 47 | @ViewBuilder row: @escaping RowContent, 48 | @ViewBuilder rowOverlay: @escaping RowOverlay, 49 | results: Results) 50 | where RowBack == EmptyView 51 | { 52 | self.init(config, 53 | header: header, 54 | footer: footer, 55 | row: row, 56 | rowBackground: { _ in EmptyView() }, 57 | rowOverlay: rowOverlay, 58 | results: results) 59 | } 60 | 61 | // omitting Header AND Overlay 62 | init(_ config: Config = .init(), 63 | @ViewBuilder footer: @escaping FooterContent, 64 | @ViewBuilder row: @escaping RowContent, 65 | @ViewBuilder rowBackground: @escaping RowBackground, 66 | results: Results) 67 | where Header == EmptyView, RowOver == EmptyView 68 | { 69 | self.init(config, 70 | header: { _ in EmptyView() }, 71 | footer: footer, 72 | row: row, 73 | rowBackground: rowBackground, 74 | rowOverlay: { _ in EmptyView() }, 75 | results: results) 76 | } 77 | 78 | // omitting Header AND Background 79 | init(_ config: Config = .init(), 80 | @ViewBuilder footer: @escaping FooterContent, 81 | @ViewBuilder row: @escaping RowContent, 82 | @ViewBuilder rowOverlay: @escaping RowOverlay, 83 | results: Results) 84 | where Header == EmptyView, RowBack == EmptyView 85 | { 86 | self.init(config, 87 | header: { _ in EmptyView() }, 88 | footer: footer, 89 | row: row, 90 | rowBackground: { _ in EmptyView() }, 91 | rowOverlay: rowOverlay, 92 | results: results) 93 | } 94 | 95 | // omitting Background AND Overlay 96 | init(_ config: Config = .init(), 97 | @ViewBuilder header: @escaping HeaderContent, 98 | @ViewBuilder footer: @escaping FooterContent, 99 | @ViewBuilder row: @escaping RowContent, 100 | results: Results) 101 | where RowBack == EmptyView, RowOver == EmptyView 102 | { 103 | self.init(config, 104 | header: header, 105 | footer: footer, 106 | row: row, 107 | rowBackground: { _ in EmptyView() }, 108 | rowOverlay: { _ in EmptyView() }, 109 | results: results) 110 | } 111 | 112 | // omitting Header, Background, AND Overlay 113 | init(_ config: Config = .init(), 114 | @ViewBuilder footer: @escaping FooterContent, 115 | @ViewBuilder row: @escaping RowContent, 116 | results: Results) 117 | 118 | where Header == EmptyView, RowBack == EmptyView, RowOver == EmptyView 119 | { 120 | self.init(config, 121 | header: { _ in EmptyView() }, 122 | footer: footer, 123 | row: row, 124 | rowBackground: { _ in EmptyView() }, 125 | rowOverlay: { _ in EmptyView() }, 126 | results: results) 127 | } 128 | 129 | // omitting Footer 130 | init(_ config: Config = .init(), 131 | @ViewBuilder header: @escaping HeaderContent, 132 | @ViewBuilder row: @escaping RowContent, 133 | @ViewBuilder rowBackground: @escaping RowBackground, 134 | @ViewBuilder rowOverlay: @escaping RowOverlay, 135 | results: Results) 136 | where Footer == EmptyView 137 | { 138 | self.init(config, 139 | header: header, 140 | footer: { _ in EmptyView() }, 141 | row: row, 142 | rowBackground: rowBackground, 143 | rowOverlay: rowOverlay, 144 | results: results) 145 | } 146 | 147 | // omitting Header, Footer 148 | init(_ config: Config = .init(), 149 | @ViewBuilder row: @escaping RowContent, 150 | @ViewBuilder rowBackground: @escaping RowBackground, 151 | @ViewBuilder rowOverlay: @escaping RowOverlay, 152 | results: Results) 153 | where Header == EmptyView, Footer == EmptyView 154 | { 155 | self.init(config, 156 | header: { _ in EmptyView() }, 157 | footer: { _ in EmptyView() }, 158 | row: row, 159 | rowBackground: rowBackground, 160 | rowOverlay: rowOverlay, 161 | results: results) 162 | } 163 | 164 | // omitting Footer, Overlay 165 | init(_ config: Config = .init(), 166 | @ViewBuilder header: @escaping HeaderContent, 167 | @ViewBuilder row: @escaping RowContent, 168 | @ViewBuilder rowBackground: @escaping RowBackground, 169 | results: Results) 170 | where Footer == EmptyView, RowOver == EmptyView 171 | { 172 | self.init(config, 173 | header: header, 174 | footer: { _ in EmptyView() }, 175 | row: row, 176 | rowBackground: rowBackground, 177 | rowOverlay: { _ in EmptyView() }, 178 | results: results) 179 | } 180 | 181 | // omitting Footer, Background 182 | init(_ config: Config = .init(), 183 | @ViewBuilder header: @escaping HeaderContent, 184 | @ViewBuilder row: @escaping RowContent, 185 | @ViewBuilder rowOverlay: @escaping RowOverlay, 186 | results: Results) 187 | where Footer == EmptyView, RowBack == EmptyView 188 | { 189 | self.init(config, 190 | header: header, 191 | footer: { _ in EmptyView() }, 192 | row: row, 193 | rowBackground: { _ in EmptyView() }, 194 | rowOverlay: rowOverlay, 195 | results: results) 196 | } 197 | 198 | // omitting Header, Footer AND Overlay 199 | init(_ config: Config = .init(), 200 | @ViewBuilder row: @escaping RowContent, 201 | @ViewBuilder rowBackground: @escaping RowBackground, 202 | results: Results) 203 | where Header == EmptyView, Footer == EmptyView, RowOver == EmptyView 204 | { 205 | self.init(config, 206 | header: { _ in EmptyView() }, 207 | footer: { _ in EmptyView() }, 208 | row: row, 209 | rowBackground: rowBackground, 210 | rowOverlay: { _ in EmptyView() }, 211 | results: results) 212 | } 213 | 214 | // omitting Header, Footer AND Background 215 | init(_ config: Config = .init(), 216 | @ViewBuilder row: @escaping RowContent, 217 | @ViewBuilder rowOverlay: @escaping RowOverlay, 218 | results: Results) 219 | where Header == EmptyView, Footer == EmptyView, RowBack == EmptyView 220 | { 221 | self.init(config, 222 | header: { _ in EmptyView() }, 223 | footer: { _ in EmptyView() }, 224 | row: row, 225 | rowBackground: { _ in EmptyView() }, 226 | rowOverlay: rowOverlay, 227 | results: results) 228 | } 229 | 230 | // omitting Footer, Background AND Overlay 231 | init(_ config: Config = .init(), 232 | @ViewBuilder header: @escaping HeaderContent, 233 | @ViewBuilder row: @escaping RowContent, 234 | results: Results) 235 | where Footer == EmptyView, RowBack == EmptyView, RowOver == EmptyView 236 | { 237 | self.init(config, 238 | header: header, 239 | footer: { _ in EmptyView() }, 240 | row: row, 241 | rowBackground: { _ in EmptyView() }, 242 | rowOverlay: { _ in EmptyView() }, 243 | results: results) 244 | } 245 | 246 | // omitting Header, Footer, Background, AND Overlay 247 | init(_ config: Config = .init(), 248 | @ViewBuilder row: @escaping RowContent, 249 | results: Results) 250 | 251 | where Header == EmptyView, Footer == EmptyView, RowBack == EmptyView, RowOver == EmptyView 252 | { 253 | self.init(config, 254 | header: { _ in EmptyView() }, 255 | footer: { _ in EmptyView() }, 256 | row: row, 257 | rowBackground: { _ in EmptyView() }, 258 | rowOverlay: { _ in EmptyView() }, 259 | results: results) 260 | } 261 | } 262 | -------------------------------------------------------------------------------- /Sources/Generated/TablerListC+AutoInit.generated.swift: -------------------------------------------------------------------------------- 1 | // Generated using Sourcery 1.8.1 — https://github.com/krzysztofzablocki/Sourcery 2 | // DO NOT EDIT 3 | 4 | import SwiftUI 5 | 6 | public extension TablerListC { 7 | // omitting Header 8 | init(_ config: Config = .init(), 9 | @ViewBuilder footer: @escaping FooterContent, 10 | @ViewBuilder row: @escaping RowContent, 11 | @ViewBuilder rowBackground: @escaping RowBackground, 12 | @ViewBuilder rowOverlay: @escaping RowOverlay, 13 | results: Results) 14 | where Header == EmptyView 15 | { 16 | self.init(config, 17 | header: { _ in EmptyView() }, 18 | footer: footer, 19 | row: row, 20 | rowBackground: rowBackground, 21 | rowOverlay: rowOverlay, 22 | results: results) 23 | } 24 | 25 | // omitting Overlay 26 | init(_ config: Config = .init(), 27 | @ViewBuilder header: @escaping HeaderContent, 28 | @ViewBuilder footer: @escaping FooterContent, 29 | @ViewBuilder row: @escaping RowContent, 30 | @ViewBuilder rowBackground: @escaping RowBackground, 31 | results: Results) 32 | where RowOver == EmptyView 33 | { 34 | self.init(config, 35 | header: header, 36 | footer: footer, 37 | row: row, 38 | rowBackground: rowBackground, 39 | rowOverlay: { _ in EmptyView() }, 40 | results: results) 41 | } 42 | 43 | // omitting Background 44 | init(_ config: Config = .init(), 45 | @ViewBuilder header: @escaping HeaderContent, 46 | @ViewBuilder footer: @escaping FooterContent, 47 | @ViewBuilder row: @escaping RowContent, 48 | @ViewBuilder rowOverlay: @escaping RowOverlay, 49 | results: Results) 50 | where RowBack == EmptyView 51 | { 52 | self.init(config, 53 | header: header, 54 | footer: footer, 55 | row: row, 56 | rowBackground: { _ in EmptyView() }, 57 | rowOverlay: rowOverlay, 58 | results: results) 59 | } 60 | 61 | // omitting Header AND Overlay 62 | init(_ config: Config = .init(), 63 | @ViewBuilder footer: @escaping FooterContent, 64 | @ViewBuilder row: @escaping RowContent, 65 | @ViewBuilder rowBackground: @escaping RowBackground, 66 | results: Results) 67 | where Header == EmptyView, RowOver == EmptyView 68 | { 69 | self.init(config, 70 | header: { _ in EmptyView() }, 71 | footer: footer, 72 | row: row, 73 | rowBackground: rowBackground, 74 | rowOverlay: { _ in EmptyView() }, 75 | results: results) 76 | } 77 | 78 | // omitting Header AND Background 79 | init(_ config: Config = .init(), 80 | @ViewBuilder footer: @escaping FooterContent, 81 | @ViewBuilder row: @escaping RowContent, 82 | @ViewBuilder rowOverlay: @escaping RowOverlay, 83 | results: Results) 84 | where Header == EmptyView, RowBack == EmptyView 85 | { 86 | self.init(config, 87 | header: { _ in EmptyView() }, 88 | footer: footer, 89 | row: row, 90 | rowBackground: { _ in EmptyView() }, 91 | rowOverlay: rowOverlay, 92 | results: results) 93 | } 94 | 95 | // omitting Background AND Overlay 96 | init(_ config: Config = .init(), 97 | @ViewBuilder header: @escaping HeaderContent, 98 | @ViewBuilder footer: @escaping FooterContent, 99 | @ViewBuilder row: @escaping RowContent, 100 | results: Results) 101 | where RowBack == EmptyView, RowOver == EmptyView 102 | { 103 | self.init(config, 104 | header: header, 105 | footer: footer, 106 | row: row, 107 | rowBackground: { _ in EmptyView() }, 108 | rowOverlay: { _ in EmptyView() }, 109 | results: results) 110 | } 111 | 112 | // omitting Header, Background, AND Overlay 113 | init(_ config: Config = .init(), 114 | @ViewBuilder footer: @escaping FooterContent, 115 | @ViewBuilder row: @escaping RowContent, 116 | results: Results) 117 | 118 | where Header == EmptyView, RowBack == EmptyView, RowOver == EmptyView 119 | { 120 | self.init(config, 121 | header: { _ in EmptyView() }, 122 | footer: footer, 123 | row: row, 124 | rowBackground: { _ in EmptyView() }, 125 | rowOverlay: { _ in EmptyView() }, 126 | results: results) 127 | } 128 | 129 | // omitting Footer 130 | init(_ config: Config = .init(), 131 | @ViewBuilder header: @escaping HeaderContent, 132 | @ViewBuilder row: @escaping RowContent, 133 | @ViewBuilder rowBackground: @escaping RowBackground, 134 | @ViewBuilder rowOverlay: @escaping RowOverlay, 135 | results: Results) 136 | where Footer == EmptyView 137 | { 138 | self.init(config, 139 | header: header, 140 | footer: { _ in EmptyView() }, 141 | row: row, 142 | rowBackground: rowBackground, 143 | rowOverlay: rowOverlay, 144 | results: results) 145 | } 146 | 147 | // omitting Header, Footer 148 | init(_ config: Config = .init(), 149 | @ViewBuilder row: @escaping RowContent, 150 | @ViewBuilder rowBackground: @escaping RowBackground, 151 | @ViewBuilder rowOverlay: @escaping RowOverlay, 152 | results: Results) 153 | where Header == EmptyView, Footer == EmptyView 154 | { 155 | self.init(config, 156 | header: { _ in EmptyView() }, 157 | footer: { _ in EmptyView() }, 158 | row: row, 159 | rowBackground: rowBackground, 160 | rowOverlay: rowOverlay, 161 | results: results) 162 | } 163 | 164 | // omitting Footer, Overlay 165 | init(_ config: Config = .init(), 166 | @ViewBuilder header: @escaping HeaderContent, 167 | @ViewBuilder row: @escaping RowContent, 168 | @ViewBuilder rowBackground: @escaping RowBackground, 169 | results: Results) 170 | where Footer == EmptyView, RowOver == EmptyView 171 | { 172 | self.init(config, 173 | header: header, 174 | footer: { _ in EmptyView() }, 175 | row: row, 176 | rowBackground: rowBackground, 177 | rowOverlay: { _ in EmptyView() }, 178 | results: results) 179 | } 180 | 181 | // omitting Footer, Background 182 | init(_ config: Config = .init(), 183 | @ViewBuilder header: @escaping HeaderContent, 184 | @ViewBuilder row: @escaping RowContent, 185 | @ViewBuilder rowOverlay: @escaping RowOverlay, 186 | results: Results) 187 | where Footer == EmptyView, RowBack == EmptyView 188 | { 189 | self.init(config, 190 | header: header, 191 | footer: { _ in EmptyView() }, 192 | row: row, 193 | rowBackground: { _ in EmptyView() }, 194 | rowOverlay: rowOverlay, 195 | results: results) 196 | } 197 | 198 | // omitting Header, Footer AND Overlay 199 | init(_ config: Config = .init(), 200 | @ViewBuilder row: @escaping RowContent, 201 | @ViewBuilder rowBackground: @escaping RowBackground, 202 | results: Results) 203 | where Header == EmptyView, Footer == EmptyView, RowOver == EmptyView 204 | { 205 | self.init(config, 206 | header: { _ in EmptyView() }, 207 | footer: { _ in EmptyView() }, 208 | row: row, 209 | rowBackground: rowBackground, 210 | rowOverlay: { _ in EmptyView() }, 211 | results: results) 212 | } 213 | 214 | // omitting Header, Footer AND Background 215 | init(_ config: Config = .init(), 216 | @ViewBuilder row: @escaping RowContent, 217 | @ViewBuilder rowOverlay: @escaping RowOverlay, 218 | results: Results) 219 | where Header == EmptyView, Footer == EmptyView, RowBack == EmptyView 220 | { 221 | self.init(config, 222 | header: { _ in EmptyView() }, 223 | footer: { _ in EmptyView() }, 224 | row: row, 225 | rowBackground: { _ in EmptyView() }, 226 | rowOverlay: rowOverlay, 227 | results: results) 228 | } 229 | 230 | // omitting Footer, Background AND Overlay 231 | init(_ config: Config = .init(), 232 | @ViewBuilder header: @escaping HeaderContent, 233 | @ViewBuilder row: @escaping RowContent, 234 | results: Results) 235 | where Footer == EmptyView, RowBack == EmptyView, RowOver == EmptyView 236 | { 237 | self.init(config, 238 | header: header, 239 | footer: { _ in EmptyView() }, 240 | row: row, 241 | rowBackground: { _ in EmptyView() }, 242 | rowOverlay: { _ in EmptyView() }, 243 | results: results) 244 | } 245 | 246 | // omitting Header, Footer, Background, AND Overlay 247 | init(_ config: Config = .init(), 248 | @ViewBuilder row: @escaping RowContent, 249 | results: Results) 250 | 251 | where Header == EmptyView, Footer == EmptyView, RowBack == EmptyView, RowOver == EmptyView 252 | { 253 | self.init(config, 254 | header: { _ in EmptyView() }, 255 | footer: { _ in EmptyView() }, 256 | row: row, 257 | rowBackground: { _ in EmptyView() }, 258 | rowOverlay: { _ in EmptyView() }, 259 | results: results) 260 | } 261 | } 262 | -------------------------------------------------------------------------------- /Sources/Generated/TablerStack+AutoInit.generated.swift: -------------------------------------------------------------------------------- 1 | // Generated using Sourcery 1.8.1 — https://github.com/krzysztofzablocki/Sourcery 2 | // DO NOT EDIT 3 | 4 | import SwiftUI 5 | 6 | public extension TablerStack { 7 | // omitting Header 8 | init(_ config: Config = .init(), 9 | @ViewBuilder footer: @escaping FooterContent, 10 | @ViewBuilder row: @escaping RowContent, 11 | @ViewBuilder rowBackground: @escaping RowBackground, 12 | @ViewBuilder rowOverlay: @escaping RowOverlay, 13 | results: Results) 14 | where Header == EmptyView 15 | { 16 | self.init(config, 17 | header: { _ in EmptyView() }, 18 | footer: footer, 19 | row: row, 20 | rowBackground: rowBackground, 21 | rowOverlay: rowOverlay, 22 | results: results) 23 | } 24 | 25 | // omitting Overlay 26 | init(_ config: Config = .init(), 27 | @ViewBuilder header: @escaping HeaderContent, 28 | @ViewBuilder footer: @escaping FooterContent, 29 | @ViewBuilder row: @escaping RowContent, 30 | @ViewBuilder rowBackground: @escaping RowBackground, 31 | results: Results) 32 | where RowOver == EmptyView 33 | { 34 | self.init(config, 35 | header: header, 36 | footer: footer, 37 | row: row, 38 | rowBackground: rowBackground, 39 | rowOverlay: { _ in EmptyView() }, 40 | results: results) 41 | } 42 | 43 | // omitting Background 44 | init(_ config: Config = .init(), 45 | @ViewBuilder header: @escaping HeaderContent, 46 | @ViewBuilder footer: @escaping FooterContent, 47 | @ViewBuilder row: @escaping RowContent, 48 | @ViewBuilder rowOverlay: @escaping RowOverlay, 49 | results: Results) 50 | where RowBack == EmptyView 51 | { 52 | self.init(config, 53 | header: header, 54 | footer: footer, 55 | row: row, 56 | rowBackground: { _ in EmptyView() }, 57 | rowOverlay: rowOverlay, 58 | results: results) 59 | } 60 | 61 | // omitting Header AND Overlay 62 | init(_ config: Config = .init(), 63 | @ViewBuilder footer: @escaping FooterContent, 64 | @ViewBuilder row: @escaping RowContent, 65 | @ViewBuilder rowBackground: @escaping RowBackground, 66 | results: Results) 67 | where Header == EmptyView, RowOver == EmptyView 68 | { 69 | self.init(config, 70 | header: { _ in EmptyView() }, 71 | footer: footer, 72 | row: row, 73 | rowBackground: rowBackground, 74 | rowOverlay: { _ in EmptyView() }, 75 | results: results) 76 | } 77 | 78 | // omitting Header AND Background 79 | init(_ config: Config = .init(), 80 | @ViewBuilder footer: @escaping FooterContent, 81 | @ViewBuilder row: @escaping RowContent, 82 | @ViewBuilder rowOverlay: @escaping RowOverlay, 83 | results: Results) 84 | where Header == EmptyView, RowBack == EmptyView 85 | { 86 | self.init(config, 87 | header: { _ in EmptyView() }, 88 | footer: footer, 89 | row: row, 90 | rowBackground: { _ in EmptyView() }, 91 | rowOverlay: rowOverlay, 92 | results: results) 93 | } 94 | 95 | // omitting Background AND Overlay 96 | init(_ config: Config = .init(), 97 | @ViewBuilder header: @escaping HeaderContent, 98 | @ViewBuilder footer: @escaping FooterContent, 99 | @ViewBuilder row: @escaping RowContent, 100 | results: Results) 101 | where RowBack == EmptyView, RowOver == EmptyView 102 | { 103 | self.init(config, 104 | header: header, 105 | footer: footer, 106 | row: row, 107 | rowBackground: { _ in EmptyView() }, 108 | rowOverlay: { _ in EmptyView() }, 109 | results: results) 110 | } 111 | 112 | // omitting Header, Background, AND Overlay 113 | init(_ config: Config = .init(), 114 | @ViewBuilder footer: @escaping FooterContent, 115 | @ViewBuilder row: @escaping RowContent, 116 | results: Results) 117 | 118 | where Header == EmptyView, RowBack == EmptyView, RowOver == EmptyView 119 | { 120 | self.init(config, 121 | header: { _ in EmptyView() }, 122 | footer: footer, 123 | row: row, 124 | rowBackground: { _ in EmptyView() }, 125 | rowOverlay: { _ in EmptyView() }, 126 | results: results) 127 | } 128 | 129 | // omitting Footer 130 | init(_ config: Config = .init(), 131 | @ViewBuilder header: @escaping HeaderContent, 132 | @ViewBuilder row: @escaping RowContent, 133 | @ViewBuilder rowBackground: @escaping RowBackground, 134 | @ViewBuilder rowOverlay: @escaping RowOverlay, 135 | results: Results) 136 | where Footer == EmptyView 137 | { 138 | self.init(config, 139 | header: header, 140 | footer: { _ in EmptyView() }, 141 | row: row, 142 | rowBackground: rowBackground, 143 | rowOverlay: rowOverlay, 144 | results: results) 145 | } 146 | 147 | // omitting Header, Footer 148 | init(_ config: Config = .init(), 149 | @ViewBuilder row: @escaping RowContent, 150 | @ViewBuilder rowBackground: @escaping RowBackground, 151 | @ViewBuilder rowOverlay: @escaping RowOverlay, 152 | results: Results) 153 | where Header == EmptyView, Footer == EmptyView 154 | { 155 | self.init(config, 156 | header: { _ in EmptyView() }, 157 | footer: { _ in EmptyView() }, 158 | row: row, 159 | rowBackground: rowBackground, 160 | rowOverlay: rowOverlay, 161 | results: results) 162 | } 163 | 164 | // omitting Footer, Overlay 165 | init(_ config: Config = .init(), 166 | @ViewBuilder header: @escaping HeaderContent, 167 | @ViewBuilder row: @escaping RowContent, 168 | @ViewBuilder rowBackground: @escaping RowBackground, 169 | results: Results) 170 | where Footer == EmptyView, RowOver == EmptyView 171 | { 172 | self.init(config, 173 | header: header, 174 | footer: { _ in EmptyView() }, 175 | row: row, 176 | rowBackground: rowBackground, 177 | rowOverlay: { _ in EmptyView() }, 178 | results: results) 179 | } 180 | 181 | // omitting Footer, Background 182 | init(_ config: Config = .init(), 183 | @ViewBuilder header: @escaping HeaderContent, 184 | @ViewBuilder row: @escaping RowContent, 185 | @ViewBuilder rowOverlay: @escaping RowOverlay, 186 | results: Results) 187 | where Footer == EmptyView, RowBack == EmptyView 188 | { 189 | self.init(config, 190 | header: header, 191 | footer: { _ in EmptyView() }, 192 | row: row, 193 | rowBackground: { _ in EmptyView() }, 194 | rowOverlay: rowOverlay, 195 | results: results) 196 | } 197 | 198 | // omitting Header, Footer AND Overlay 199 | init(_ config: Config = .init(), 200 | @ViewBuilder row: @escaping RowContent, 201 | @ViewBuilder rowBackground: @escaping RowBackground, 202 | results: Results) 203 | where Header == EmptyView, Footer == EmptyView, RowOver == EmptyView 204 | { 205 | self.init(config, 206 | header: { _ in EmptyView() }, 207 | footer: { _ in EmptyView() }, 208 | row: row, 209 | rowBackground: rowBackground, 210 | rowOverlay: { _ in EmptyView() }, 211 | results: results) 212 | } 213 | 214 | // omitting Header, Footer AND Background 215 | init(_ config: Config = .init(), 216 | @ViewBuilder row: @escaping RowContent, 217 | @ViewBuilder rowOverlay: @escaping RowOverlay, 218 | results: Results) 219 | where Header == EmptyView, Footer == EmptyView, RowBack == EmptyView 220 | { 221 | self.init(config, 222 | header: { _ in EmptyView() }, 223 | footer: { _ in EmptyView() }, 224 | row: row, 225 | rowBackground: { _ in EmptyView() }, 226 | rowOverlay: rowOverlay, 227 | results: results) 228 | } 229 | 230 | // omitting Footer, Background AND Overlay 231 | init(_ config: Config = .init(), 232 | @ViewBuilder header: @escaping HeaderContent, 233 | @ViewBuilder row: @escaping RowContent, 234 | results: Results) 235 | where Footer == EmptyView, RowBack == EmptyView, RowOver == EmptyView 236 | { 237 | self.init(config, 238 | header: header, 239 | footer: { _ in EmptyView() }, 240 | row: row, 241 | rowBackground: { _ in EmptyView() }, 242 | rowOverlay: { _ in EmptyView() }, 243 | results: results) 244 | } 245 | 246 | // omitting Header, Footer, Background, AND Overlay 247 | init(_ config: Config = .init(), 248 | @ViewBuilder row: @escaping RowContent, 249 | results: Results) 250 | 251 | where Header == EmptyView, Footer == EmptyView, RowBack == EmptyView, RowOver == EmptyView 252 | { 253 | self.init(config, 254 | header: { _ in EmptyView() }, 255 | footer: { _ in EmptyView() }, 256 | row: row, 257 | rowBackground: { _ in EmptyView() }, 258 | rowOverlay: { _ in EmptyView() }, 259 | results: results) 260 | } 261 | } 262 | -------------------------------------------------------------------------------- /Sources/Generated/TablerStackC+AutoInit.generated.swift: -------------------------------------------------------------------------------- 1 | // Generated using Sourcery 1.8.1 — https://github.com/krzysztofzablocki/Sourcery 2 | // DO NOT EDIT 3 | 4 | import SwiftUI 5 | 6 | public extension TablerStackC { 7 | // omitting Header 8 | init(_ config: Config = .init(), 9 | @ViewBuilder footer: @escaping FooterContent, 10 | @ViewBuilder row: @escaping RowContent, 11 | @ViewBuilder rowBackground: @escaping RowBackground, 12 | @ViewBuilder rowOverlay: @escaping RowOverlay, 13 | results: Results) 14 | where Header == EmptyView 15 | { 16 | self.init(config, 17 | header: { _ in EmptyView() }, 18 | footer: footer, 19 | row: row, 20 | rowBackground: rowBackground, 21 | rowOverlay: rowOverlay, 22 | results: results) 23 | } 24 | 25 | // omitting Overlay 26 | init(_ config: Config = .init(), 27 | @ViewBuilder header: @escaping HeaderContent, 28 | @ViewBuilder footer: @escaping FooterContent, 29 | @ViewBuilder row: @escaping RowContent, 30 | @ViewBuilder rowBackground: @escaping RowBackground, 31 | results: Results) 32 | where RowOver == EmptyView 33 | { 34 | self.init(config, 35 | header: header, 36 | footer: footer, 37 | row: row, 38 | rowBackground: rowBackground, 39 | rowOverlay: { _ in EmptyView() }, 40 | results: results) 41 | } 42 | 43 | // omitting Background 44 | init(_ config: Config = .init(), 45 | @ViewBuilder header: @escaping HeaderContent, 46 | @ViewBuilder footer: @escaping FooterContent, 47 | @ViewBuilder row: @escaping RowContent, 48 | @ViewBuilder rowOverlay: @escaping RowOverlay, 49 | results: Results) 50 | where RowBack == EmptyView 51 | { 52 | self.init(config, 53 | header: header, 54 | footer: footer, 55 | row: row, 56 | rowBackground: { _ in EmptyView() }, 57 | rowOverlay: rowOverlay, 58 | results: results) 59 | } 60 | 61 | // omitting Header AND Overlay 62 | init(_ config: Config = .init(), 63 | @ViewBuilder footer: @escaping FooterContent, 64 | @ViewBuilder row: @escaping RowContent, 65 | @ViewBuilder rowBackground: @escaping RowBackground, 66 | results: Results) 67 | where Header == EmptyView, RowOver == EmptyView 68 | { 69 | self.init(config, 70 | header: { _ in EmptyView() }, 71 | footer: footer, 72 | row: row, 73 | rowBackground: rowBackground, 74 | rowOverlay: { _ in EmptyView() }, 75 | results: results) 76 | } 77 | 78 | // omitting Header AND Background 79 | init(_ config: Config = .init(), 80 | @ViewBuilder footer: @escaping FooterContent, 81 | @ViewBuilder row: @escaping RowContent, 82 | @ViewBuilder rowOverlay: @escaping RowOverlay, 83 | results: Results) 84 | where Header == EmptyView, RowBack == EmptyView 85 | { 86 | self.init(config, 87 | header: { _ in EmptyView() }, 88 | footer: footer, 89 | row: row, 90 | rowBackground: { _ in EmptyView() }, 91 | rowOverlay: rowOverlay, 92 | results: results) 93 | } 94 | 95 | // omitting Background AND Overlay 96 | init(_ config: Config = .init(), 97 | @ViewBuilder header: @escaping HeaderContent, 98 | @ViewBuilder footer: @escaping FooterContent, 99 | @ViewBuilder row: @escaping RowContent, 100 | results: Results) 101 | where RowBack == EmptyView, RowOver == EmptyView 102 | { 103 | self.init(config, 104 | header: header, 105 | footer: footer, 106 | row: row, 107 | rowBackground: { _ in EmptyView() }, 108 | rowOverlay: { _ in EmptyView() }, 109 | results: results) 110 | } 111 | 112 | // omitting Header, Background, AND Overlay 113 | init(_ config: Config = .init(), 114 | @ViewBuilder footer: @escaping FooterContent, 115 | @ViewBuilder row: @escaping RowContent, 116 | results: Results) 117 | 118 | where Header == EmptyView, RowBack == EmptyView, RowOver == EmptyView 119 | { 120 | self.init(config, 121 | header: { _ in EmptyView() }, 122 | footer: footer, 123 | row: row, 124 | rowBackground: { _ in EmptyView() }, 125 | rowOverlay: { _ in EmptyView() }, 126 | results: results) 127 | } 128 | 129 | // omitting Footer 130 | init(_ config: Config = .init(), 131 | @ViewBuilder header: @escaping HeaderContent, 132 | @ViewBuilder row: @escaping RowContent, 133 | @ViewBuilder rowBackground: @escaping RowBackground, 134 | @ViewBuilder rowOverlay: @escaping RowOverlay, 135 | results: Results) 136 | where Footer == EmptyView 137 | { 138 | self.init(config, 139 | header: header, 140 | footer: { _ in EmptyView() }, 141 | row: row, 142 | rowBackground: rowBackground, 143 | rowOverlay: rowOverlay, 144 | results: results) 145 | } 146 | 147 | // omitting Header, Footer 148 | init(_ config: Config = .init(), 149 | @ViewBuilder row: @escaping RowContent, 150 | @ViewBuilder rowBackground: @escaping RowBackground, 151 | @ViewBuilder rowOverlay: @escaping RowOverlay, 152 | results: Results) 153 | where Header == EmptyView, Footer == EmptyView 154 | { 155 | self.init(config, 156 | header: { _ in EmptyView() }, 157 | footer: { _ in EmptyView() }, 158 | row: row, 159 | rowBackground: rowBackground, 160 | rowOverlay: rowOverlay, 161 | results: results) 162 | } 163 | 164 | // omitting Footer, Overlay 165 | init(_ config: Config = .init(), 166 | @ViewBuilder header: @escaping HeaderContent, 167 | @ViewBuilder row: @escaping RowContent, 168 | @ViewBuilder rowBackground: @escaping RowBackground, 169 | results: Results) 170 | where Footer == EmptyView, RowOver == EmptyView 171 | { 172 | self.init(config, 173 | header: header, 174 | footer: { _ in EmptyView() }, 175 | row: row, 176 | rowBackground: rowBackground, 177 | rowOverlay: { _ in EmptyView() }, 178 | results: results) 179 | } 180 | 181 | // omitting Footer, Background 182 | init(_ config: Config = .init(), 183 | @ViewBuilder header: @escaping HeaderContent, 184 | @ViewBuilder row: @escaping RowContent, 185 | @ViewBuilder rowOverlay: @escaping RowOverlay, 186 | results: Results) 187 | where Footer == EmptyView, RowBack == EmptyView 188 | { 189 | self.init(config, 190 | header: header, 191 | footer: { _ in EmptyView() }, 192 | row: row, 193 | rowBackground: { _ in EmptyView() }, 194 | rowOverlay: rowOverlay, 195 | results: results) 196 | } 197 | 198 | // omitting Header, Footer AND Overlay 199 | init(_ config: Config = .init(), 200 | @ViewBuilder row: @escaping RowContent, 201 | @ViewBuilder rowBackground: @escaping RowBackground, 202 | results: Results) 203 | where Header == EmptyView, Footer == EmptyView, RowOver == EmptyView 204 | { 205 | self.init(config, 206 | header: { _ in EmptyView() }, 207 | footer: { _ in EmptyView() }, 208 | row: row, 209 | rowBackground: rowBackground, 210 | rowOverlay: { _ in EmptyView() }, 211 | results: results) 212 | } 213 | 214 | // omitting Header, Footer AND Background 215 | init(_ config: Config = .init(), 216 | @ViewBuilder row: @escaping RowContent, 217 | @ViewBuilder rowOverlay: @escaping RowOverlay, 218 | results: Results) 219 | where Header == EmptyView, Footer == EmptyView, RowBack == EmptyView 220 | { 221 | self.init(config, 222 | header: { _ in EmptyView() }, 223 | footer: { _ in EmptyView() }, 224 | row: row, 225 | rowBackground: { _ in EmptyView() }, 226 | rowOverlay: rowOverlay, 227 | results: results) 228 | } 229 | 230 | // omitting Footer, Background AND Overlay 231 | init(_ config: Config = .init(), 232 | @ViewBuilder header: @escaping HeaderContent, 233 | @ViewBuilder row: @escaping RowContent, 234 | results: Results) 235 | where Footer == EmptyView, RowBack == EmptyView, RowOver == EmptyView 236 | { 237 | self.init(config, 238 | header: header, 239 | footer: { _ in EmptyView() }, 240 | row: row, 241 | rowBackground: { _ in EmptyView() }, 242 | rowOverlay: { _ in EmptyView() }, 243 | results: results) 244 | } 245 | 246 | // omitting Header, Footer, Background, AND Overlay 247 | init(_ config: Config = .init(), 248 | @ViewBuilder row: @escaping RowContent, 249 | results: Results) 250 | 251 | where Header == EmptyView, Footer == EmptyView, RowBack == EmptyView, RowOver == EmptyView 252 | { 253 | self.init(config, 254 | header: { _ in EmptyView() }, 255 | footer: { _ in EmptyView() }, 256 | row: row, 257 | rowBackground: { _ in EmptyView() }, 258 | rowOverlay: { _ in EmptyView() }, 259 | results: results) 260 | } 261 | } 262 | -------------------------------------------------------------------------------- /Sources/Grid/Internal/BaseGrid.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BaseGrid.swift 3 | // 4 | // Copyright 2021, 2022 OpenAlloc LLC 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | // 18 | 19 | import SwiftUI 20 | 21 | // Grid-based list 22 | struct BaseGrid: View 23 | where Element: Identifiable, 24 | Header: View, 25 | Footer: View, 26 | Rows: View 27 | { 28 | typealias Config = TablerGridConfig 29 | typealias Context = TablerContext 30 | typealias HeaderContent = (Binding) -> Header 31 | typealias FooterContent = (Binding) -> Footer 32 | typealias RowContent = () -> Rows 33 | 34 | @Binding var context: Context 35 | @ViewBuilder let header: HeaderContent 36 | @ViewBuilder let footer: FooterContent 37 | @ViewBuilder let rows: RowContent 38 | 39 | var body: some View { 40 | BaseTable(context: $context, 41 | header: header, 42 | footer: footer) 43 | { buildHeader, buildFooter in 44 | 45 | VStack(spacing: 0) { 46 | // if config.headerFixed { 47 | buildHeader() 48 | .padding(.vertical, config.headerSpacing) 49 | // } 50 | 51 | ScrollView { 52 | // if !config.headerFixed { 53 | // buildHeader() 54 | // .padding(.vertical, config.headerSpacing) 55 | // } 56 | 57 | LazyVGrid(columns: config.gridItems, 58 | alignment: config.alignment, 59 | spacing: config.rowSpacing) 60 | { 61 | rows() 62 | } 63 | 64 | // if !config.footerFixed { 65 | // buildFooter() 66 | // .padding(.vertical, config.footerSpacing) 67 | // } 68 | } 69 | 70 | // if config.footerFixed { 71 | buildFooter() 72 | .padding(.vertical, config.footerSpacing) 73 | // } 74 | } 75 | } 76 | .padding(config.tablePadding) 77 | } 78 | 79 | private var config: Config { 80 | context.config as? Config ?? Config(gridItems: []) 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /Sources/Grid/Internal/GridItemMod.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GridItemMod.swift 3 | // 4 | // Copyright 2021, 2022 OpenAlloc LLC 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | // 18 | 19 | import SwiftUI 20 | 21 | struct GridItemMod: ViewModifier 22 | where Element: Identifiable 23 | { 24 | typealias Config = TablerGridConfig 25 | 26 | let config: Config 27 | let element: Element 28 | 29 | func body(content: Content) -> some View { 30 | content 31 | .padding(config.itemPadding) 32 | 33 | #if os(macOS) || targetEnvironment(macCatalyst) 34 | .onHover(perform: { config.onHover(element.id, $0) }) 35 | #endif 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Sources/Grid/Internal/GridItemMod1.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GridItemMod1.swift 3 | // 4 | // Copyright 2021, 2022 OpenAlloc LLC 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | // 18 | 19 | import SwiftUI 20 | 21 | /// Support for single-select Grid-based rows 22 | struct GridItemMod1: ViewModifier 23 | where Element: Identifiable 24 | { 25 | typealias Config = TablerGridConfig 26 | typealias Selected = Element.ID? 27 | 28 | let config: Config 29 | let element: Element 30 | @Binding var selected: Selected 31 | 32 | func body(content: Content) -> some View { 33 | content 34 | .padding(config.itemPadding) 35 | 36 | // simple tap to select (or unselect) 37 | .contentShape(Rectangle()) 38 | .onTapGesture { 39 | if selected == element.id { 40 | selected = nil 41 | } else { 42 | selected = element.id 43 | } 44 | } 45 | 46 | #if os(macOS) || targetEnvironment(macCatalyst) 47 | .onHover(perform: { config.onHover(element.id, $0) }) 48 | #endif 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /Sources/Grid/Internal/GridItemModM.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GridItemModM.swift 3 | // 4 | // Copyright 2021, 2022 OpenAlloc LLC 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | // 18 | 19 | import SwiftUI 20 | 21 | /// Support for multi-select Grid-based rows 22 | struct GridItemModM: ViewModifier 23 | where Element: Identifiable 24 | { 25 | typealias Config = TablerGridConfig 26 | typealias Selected = Set 27 | 28 | let config: Config 29 | let element: Element 30 | @Binding var selected: Selected 31 | 32 | func body(content: Content) -> some View { 33 | content 34 | .padding(config.itemPadding) 35 | 36 | // simple tap to select (or unselect) 37 | .contentShape(Rectangle()) 38 | .onTapGesture { 39 | if selected.contains(element.id) { 40 | selected.remove(element.id) 41 | } else { 42 | selected.insert(element.id) 43 | } 44 | } 45 | 46 | #if os(macOS) || targetEnvironment(macCatalyst) 47 | .onHover(perform: { config.onHover(element.id, $0) }) 48 | #endif 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /Sources/Grid/TablerGrid.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TablerGrid.swift 3 | // 4 | // Copyright 2021, 2022 OpenAlloc LLC 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | // 18 | 19 | import SwiftUI 20 | 21 | // sourcery: AutoInit 22 | /// Grid-based table 23 | public struct TablerGrid: View 24 | where Element: Identifiable, 25 | Header: View, 26 | Footer: View, 27 | Row: View, 28 | RowBack: View, 29 | RowOver: View, 30 | Results: RandomAccessCollection, 31 | Results.Element == Element 32 | { 33 | public typealias Config = TablerGridConfig 34 | public typealias Context = TablerContext 35 | public typealias HeaderContent = (Binding) -> Header 36 | public typealias FooterContent = (Binding) -> Footer 37 | public typealias RowContent = (Element) -> Row 38 | public typealias RowBackground = (Element) -> RowBack 39 | public typealias RowOverlay = (Element) -> RowOver 40 | 41 | // MARK: Parameters 42 | 43 | private let config: Config 44 | private let headerContent: HeaderContent 45 | private let footerContent: FooterContent 46 | private let rowContent: RowContent 47 | private let rowBackground: RowBackground 48 | private let rowOverlay: RowOverlay 49 | private var results: Results 50 | 51 | public init(_ config: Config, 52 | @ViewBuilder header: @escaping HeaderContent, 53 | @ViewBuilder footer: @escaping FooterContent, 54 | @ViewBuilder row: @escaping RowContent, 55 | @ViewBuilder rowBackground: @escaping RowBackground, 56 | @ViewBuilder rowOverlay: @escaping RowOverlay, 57 | results: Results) 58 | { 59 | self.config = config 60 | headerContent = header 61 | footerContent = footer 62 | rowContent = row 63 | self.rowBackground = rowBackground 64 | self.rowOverlay = rowOverlay 65 | self.results = results 66 | _context = State(initialValue: TablerContext(config)) 67 | } 68 | 69 | // MARK: Locals 70 | 71 | @State private var context: Context 72 | 73 | // MARK: Views 74 | 75 | public var body: some View { 76 | BaseGrid(context: $context, 77 | header: headerContent, 78 | footer: footerContent) 79 | { 80 | ForEach(results.filter(config.filter ?? { _ in true })) { element in 81 | rowContent(element) 82 | .modifier(GridItemMod(config: config, 83 | element: element)) 84 | .background(rowBackground(element)) 85 | .overlay(rowOverlay(element)) 86 | } 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /Sources/Grid/TablerGrid1.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TablerGrid1.swift 3 | // 4 | // Copyright 2021, 2022 OpenAlloc LLC 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | // 18 | 19 | import SwiftUI 20 | 21 | // sourcery: AutoInit, selectBinding 22 | /// Grid-based table, with support for single-select 23 | public struct TablerGrid1: View 24 | where Element: Identifiable, 25 | Header: View, 26 | Footer: View, 27 | Row: View, 28 | RowBack: View, 29 | RowOver: View, 30 | Results: RandomAccessCollection, 31 | Results.Element == Element 32 | { 33 | public typealias Config = TablerGridConfig 34 | public typealias Context = TablerContext 35 | public typealias HeaderContent = (Binding) -> Header 36 | public typealias FooterContent = (Binding) -> Footer 37 | public typealias RowContent = (Element) -> Row 38 | public typealias RowBackground = (Element) -> RowBack 39 | public typealias RowOverlay = (Element) -> RowOver 40 | public typealias Selected = Element.ID? 41 | 42 | // MARK: Parameters 43 | 44 | private let config: Config 45 | private let headerContent: HeaderContent 46 | private let footerContent: FooterContent 47 | private let rowContent: RowContent 48 | private let rowBackground: RowBackground 49 | private let rowOverlay: RowOverlay 50 | private var results: Results 51 | @Binding private var selected: Selected 52 | 53 | public init(_ config: Config, 54 | @ViewBuilder header: @escaping HeaderContent, 55 | @ViewBuilder footer: @escaping FooterContent, 56 | @ViewBuilder row: @escaping RowContent, 57 | @ViewBuilder rowBackground: @escaping RowBackground, 58 | @ViewBuilder rowOverlay: @escaping RowOverlay, 59 | results: Results, 60 | selected: Binding) 61 | { 62 | self.config = config 63 | headerContent = header 64 | footerContent = footer 65 | rowContent = row 66 | self.rowBackground = rowBackground 67 | self.rowOverlay = rowOverlay 68 | self.results = results 69 | _selected = selected 70 | _context = State(initialValue: TablerContext(config)) 71 | } 72 | 73 | // MARK: Locals 74 | 75 | @State private var context: Context 76 | 77 | // MARK: Views 78 | 79 | public var body: some View { 80 | BaseGrid(context: $context, 81 | header: headerContent, 82 | footer: footerContent) 83 | { 84 | ForEach(results.filter(config.filter ?? { _ in true })) { element in 85 | rowContent(element) 86 | .modifier(GridItemMod1(config: config, 87 | element: element, 88 | selected: $selected)) 89 | .background(rowBackground(element)) 90 | .overlay(rowOverlay(element)) 91 | } 92 | } 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /Sources/Grid/TablerGrid1B.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TablerGrid1B.swift 3 | // 4 | // Copyright 2021, 2022 OpenAlloc LLC 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | // 18 | 19 | import SwiftUI 20 | 21 | // sourcery: AutoInit, selectBinding, resultsBinding 22 | /// Grid-based table, with support for single-select and bound value types 23 | public struct TablerGrid1B: View 24 | where Element: Identifiable, 25 | Header: View, 26 | Footer: View, 27 | Row: View, 28 | RowBack: View, 29 | RowOver: View, 30 | Results: RandomAccessCollection & MutableCollection, 31 | Results.Element == Element, 32 | Results.Index: Hashable 33 | { 34 | public typealias Config = TablerGridConfig 35 | public typealias Context = TablerContext 36 | public typealias HeaderContent = (Binding) -> Header 37 | public typealias FooterContent = (Binding) -> Footer 38 | public typealias RowContent = (Binding) -> Row 39 | public typealias RowBackground = (Element) -> RowBack 40 | public typealias RowOverlay = (Element) -> RowOver 41 | public typealias Selected = Element.ID? 42 | 43 | // MARK: Parameters 44 | 45 | private let config: Config 46 | private let headerContent: HeaderContent 47 | private let footerContent: FooterContent 48 | private let rowContent: RowContent 49 | private let rowBackground: RowBackground 50 | private let rowOverlay: RowOverlay 51 | @Binding private var results: Results 52 | @Binding private var selected: Selected 53 | 54 | public init(_ config: Config, 55 | @ViewBuilder header: @escaping HeaderContent, 56 | @ViewBuilder footer: @escaping FooterContent, 57 | @ViewBuilder row: @escaping RowContent, 58 | @ViewBuilder rowBackground: @escaping RowBackground, 59 | @ViewBuilder rowOverlay: @escaping RowOverlay, 60 | results: Binding, 61 | selected: Binding) 62 | { 63 | self.config = config 64 | headerContent = header 65 | footerContent = footer 66 | rowContent = row 67 | self.rowBackground = rowBackground 68 | self.rowOverlay = rowOverlay 69 | _results = results 70 | _selected = selected 71 | _context = State(initialValue: TablerContext(config)) 72 | } 73 | 74 | // MARK: Locals 75 | 76 | @State private var context: Context 77 | 78 | // MARK: Views 79 | 80 | public var body: some View { 81 | BaseGrid(context: $context, 82 | header: headerContent, 83 | footer: footerContent) 84 | { 85 | ForEach($results) { $element in 86 | rowContent($element) 87 | .modifier(GridItemMod1(config: config, 88 | element: element, 89 | selected: $selected)) 90 | .background(rowBackground(element)) 91 | .overlay(rowOverlay(element)) 92 | } 93 | } 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /Sources/Grid/TablerGrid1C.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TablerGrid1C.swift 3 | // 4 | // Copyright 2021, 2022 OpenAlloc LLC 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | // 18 | 19 | import SwiftUI 20 | 21 | // sourcery: AutoInit, selectBinding 22 | /// Grid-based table, with support for reference types 23 | public struct TablerGrid1C: View 24 | where Element: Identifiable & ObservableObject, 25 | Header: View, 26 | Footer: View, 27 | Row: View, 28 | RowBack: View, 29 | RowOver: View, 30 | Results: RandomAccessCollection, 31 | Results.Element == Element 32 | { 33 | public typealias Config = TablerGridConfig 34 | public typealias Context = TablerContext 35 | public typealias HeaderContent = (Binding) -> Header 36 | public typealias FooterContent = (Binding) -> Footer 37 | public typealias ProjectedValue = ObservedObject.Wrapper 38 | public typealias RowContent = (ProjectedValue) -> Row 39 | public typealias RowBackground = (Element) -> RowBack 40 | public typealias RowOverlay = (Element) -> RowOver 41 | public typealias Selected = Element.ID? 42 | 43 | // MARK: Parameters 44 | 45 | private let config: Config 46 | private let headerContent: HeaderContent 47 | private let footerContent: FooterContent 48 | private let rowContent: RowContent 49 | private let rowBackground: RowBackground 50 | private let rowOverlay: RowOverlay 51 | private var results: Results 52 | @Binding private var selected: Selected 53 | 54 | public init(_ config: Config, 55 | @ViewBuilder header: @escaping HeaderContent, 56 | @ViewBuilder footer: @escaping FooterContent, 57 | @ViewBuilder row: @escaping RowContent, 58 | @ViewBuilder rowBackground: @escaping RowBackground, 59 | @ViewBuilder rowOverlay: @escaping RowOverlay, 60 | results: Results, 61 | selected: Binding) 62 | { 63 | self.config = config 64 | headerContent = header 65 | footerContent = footer 66 | rowContent = row 67 | self.rowBackground = rowBackground 68 | self.rowOverlay = rowOverlay 69 | self.results = results 70 | _selected = selected 71 | _context = State(initialValue: TablerContext(config)) 72 | } 73 | 74 | // MARK: Locals 75 | 76 | @State private var context: Context 77 | 78 | // MARK: Views 79 | 80 | public var body: some View { 81 | BaseGrid(context: $context, 82 | header: headerContent, 83 | footer: footerContent) 84 | { 85 | ForEach(results) { rawElem in 86 | ObservableHolder(element: rawElem) { obsElem in 87 | rowContent(obsElem) 88 | .modifier(GridItemMod1(config: config, 89 | element: rawElem, 90 | selected: $selected)) 91 | .background(rowBackground(rawElem)) 92 | .overlay(rowOverlay(rawElem)) 93 | } 94 | } 95 | } 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /Sources/Grid/TablerGridB.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TablerGridB.swift 3 | // 4 | // Copyright 2021, 2022 OpenAlloc LLC 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | // 18 | 19 | import SwiftUI 20 | 21 | // sourcery: AutoInit, resultsBinding 22 | /// Grid-based table, with support for bound value types 23 | public struct TablerGridB: View 24 | where Element: Identifiable, 25 | Header: View, 26 | Footer: View, 27 | Row: View, 28 | RowBack: View, 29 | RowOver: View, 30 | Results: RandomAccessCollection & MutableCollection, 31 | Results.Element == Element, 32 | Results.Index: Hashable 33 | { 34 | public typealias Config = TablerGridConfig 35 | public typealias Context = TablerContext 36 | public typealias HeaderContent = (Binding) -> Header 37 | public typealias FooterContent = (Binding) -> Footer 38 | public typealias RowContent = (Binding) -> Row 39 | public typealias RowBackground = (Element) -> RowBack 40 | public typealias RowOverlay = (Element) -> RowOver 41 | 42 | // MARK: Parameters 43 | 44 | private let config: Config 45 | private let headerContent: HeaderContent 46 | private let footerContent: FooterContent 47 | private let rowContent: RowContent 48 | private let rowBackground: RowBackground 49 | private let rowOverlay: RowOverlay 50 | @Binding private var results: Results 51 | 52 | public init(_ config: Config, 53 | @ViewBuilder header: @escaping HeaderContent, 54 | @ViewBuilder footer: @escaping FooterContent, 55 | @ViewBuilder row: @escaping RowContent, 56 | @ViewBuilder rowBackground: @escaping RowBackground, 57 | @ViewBuilder rowOverlay: @escaping RowOverlay, 58 | results: Binding) 59 | { 60 | self.config = config 61 | headerContent = header 62 | footerContent = footer 63 | rowContent = row 64 | self.rowBackground = rowBackground 65 | self.rowOverlay = rowOverlay 66 | _results = results 67 | _context = State(initialValue: TablerContext(config)) 68 | } 69 | 70 | // MARK: Locals 71 | 72 | @State private var context: Context 73 | 74 | // MARK: Views 75 | 76 | public var body: some View { 77 | BaseGrid(context: $context, 78 | header: headerContent, 79 | footer: footerContent) 80 | { 81 | ForEach($results) { $element in 82 | rowContent($element) 83 | .modifier(GridItemMod(config: config, 84 | element: element)) 85 | .background(rowBackground(element)) 86 | .overlay(rowOverlay(element)) 87 | } 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /Sources/Grid/TablerGridC.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TablerGridC.swift 3 | // 4 | // Copyright 2021, 2022 OpenAlloc LLC 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | // 18 | 19 | import SwiftUI 20 | 21 | // sourcery: AutoInit 22 | /// Grid-based table, with support for reference types 23 | public struct TablerGridC: View 24 | where Element: Identifiable & ObservableObject, 25 | Header: View, 26 | Footer: View, 27 | Row: View, 28 | RowBack: View, 29 | RowOver: View, 30 | Results: RandomAccessCollection, 31 | Results.Element == Element 32 | { 33 | public typealias Config = TablerGridConfig 34 | public typealias Context = TablerContext 35 | public typealias HeaderContent = (Binding) -> Header 36 | public typealias FooterContent = (Binding) -> Footer 37 | public typealias ProjectedValue = ObservedObject.Wrapper 38 | public typealias RowContent = (ProjectedValue) -> Row 39 | public typealias RowBackground = (Element) -> RowBack 40 | public typealias RowOverlay = (Element) -> RowOver 41 | 42 | // MARK: Parameters 43 | 44 | private let config: Config 45 | private let headerContent: HeaderContent 46 | private let footerContent: FooterContent 47 | private let rowContent: RowContent 48 | private let rowBackground: RowBackground 49 | private let rowOverlay: RowOverlay 50 | private var results: Results 51 | 52 | public init(_ config: Config, 53 | @ViewBuilder header: @escaping HeaderContent, 54 | @ViewBuilder footer: @escaping FooterContent, 55 | @ViewBuilder row: @escaping RowContent, 56 | @ViewBuilder rowBackground: @escaping RowBackground, 57 | @ViewBuilder rowOverlay: @escaping RowOverlay, 58 | results: Results) 59 | { 60 | self.config = config 61 | headerContent = header 62 | footerContent = footer 63 | rowContent = row 64 | self.rowBackground = rowBackground 65 | self.rowOverlay = rowOverlay 66 | self.results = results 67 | _context = State(initialValue: TablerContext(config)) 68 | } 69 | 70 | // MARK: Locals 71 | 72 | @State private var context: Context 73 | 74 | // MARK: Views 75 | 76 | public var body: some View { 77 | BaseGrid(context: $context, 78 | header: headerContent, 79 | footer: footerContent) 80 | { 81 | ForEach(results) { rawElem in 82 | ObservableHolder(element: rawElem) { obsElem in 83 | rowContent(obsElem) 84 | .modifier(GridItemMod(config: config, 85 | element: rawElem)) 86 | .background(rowBackground(rawElem)) 87 | .overlay(rowOverlay(rawElem)) 88 | } 89 | } 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /Sources/Grid/TablerGridConfig.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TablerGridConfig.swift 3 | // 4 | // Copyright 2021, 2022 OpenAlloc LLC 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | // 18 | 19 | import SwiftUI 20 | 21 | public enum TablerGridConfigDefaults { 22 | #if os(macOS) 23 | public static let itemPadding = EdgeInsets(top: 4, leading: 0, bottom: 4, trailing: 0) 24 | #elseif os(iOS) 25 | public static let itemPadding = EdgeInsets(top: 11.5, leading: 0, bottom: 12, trailing: 0) 26 | #endif 27 | 28 | public static let alignment: HorizontalAlignment = .leading 29 | } 30 | 31 | public class TablerGridConfig: TablerSpacedConfig 32 | where Element: Identifiable 33 | { 34 | public let gridItems: [GridItem] 35 | public let alignment: HorizontalAlignment 36 | public let itemPadding: EdgeInsets 37 | 38 | public init(gridItems: [GridItem] = [], 39 | alignment: HorizontalAlignment = TablerGridConfigDefaults.alignment, 40 | itemPadding: EdgeInsets = TablerGridConfigDefaults.itemPadding, 41 | headerSpacing: CGFloat = TablerSpacedConfigDefaults.headerSpacing, 42 | footerSpacing: CGFloat = TablerSpacedConfigDefaults.footerSpacing, 43 | rowSpacing: CGFloat = TablerSpacedConfigDefaults.rowSpacing, 44 | // headerFixed: Bool = TablerSpacedConfigDefaults.headerFixed, 45 | // footerFixed: Bool = TablerSpacedConfigDefaults.footerFixed, 46 | filter: Filter? = nil, 47 | onHover: @escaping OnHover = { _, _ in }, 48 | tablePadding: EdgeInsets = TablerSpacedConfigDefaults.tablePadding, 49 | sortIndicatorForward: AnyView = TablerConfigDefaults.sortIndicatorForward, 50 | sortIndicatorReverse: AnyView = TablerConfigDefaults.sortIndicatorReverse, 51 | sortIndicatorNeutral: AnyView = TablerConfigDefaults.sortIndicatorNeutral) 52 | { 53 | self.gridItems = gridItems 54 | self.alignment = alignment 55 | self.itemPadding = itemPadding 56 | 57 | super.init(headerSpacing: headerSpacing, 58 | footerSpacing: footerSpacing, 59 | rowSpacing: rowSpacing, 60 | // headerFixed: headerFixed, 61 | // footerFixed: footerFixed, 62 | filter: filter, 63 | onHover: onHover, 64 | tablePadding: tablePadding, 65 | sortIndicatorForward: sortIndicatorForward, 66 | sortIndicatorReverse: sortIndicatorReverse, 67 | sortIndicatorNeutral: sortIndicatorNeutral) 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /Sources/Grid/TablerGridM.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TablerGridM.swift 3 | // 4 | // Copyright 2021, 2022 OpenAlloc LLC 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | // 18 | 19 | import SwiftUI 20 | 21 | // sourcery: AutoInit, selectBinding 22 | /// Grid-based table, with support for multi-select 23 | public struct TablerGridM: View 24 | where Element: Identifiable, 25 | Header: View, 26 | Footer: View, 27 | Row: View, 28 | RowBack: View, 29 | RowOver: View, 30 | Results: RandomAccessCollection, 31 | Results.Element == Element 32 | { 33 | public typealias Config = TablerGridConfig 34 | public typealias Context = TablerContext 35 | public typealias HeaderContent = (Binding) -> Header 36 | public typealias FooterContent = (Binding) -> Footer 37 | public typealias RowContent = (Element) -> Row 38 | public typealias RowBackground = (Element) -> RowBack 39 | public typealias RowOverlay = (Element) -> RowOver 40 | public typealias Selected = Set 41 | 42 | // MARK: Parameters 43 | 44 | private let config: Config 45 | private let headerContent: HeaderContent 46 | private let footerContent: FooterContent 47 | private let rowContent: RowContent 48 | private let rowBackground: RowBackground 49 | private let rowOverlay: RowOverlay 50 | private var results: Results 51 | @Binding private var selected: Selected 52 | 53 | public init(_ config: Config, 54 | @ViewBuilder header: @escaping HeaderContent, 55 | @ViewBuilder footer: @escaping FooterContent, 56 | @ViewBuilder row: @escaping RowContent, 57 | @ViewBuilder rowBackground: @escaping RowBackground, 58 | @ViewBuilder rowOverlay: @escaping RowOverlay, 59 | results: Results, 60 | selected: Binding) 61 | { 62 | self.config = config 63 | headerContent = header 64 | footerContent = footer 65 | rowContent = row 66 | self.rowBackground = rowBackground 67 | self.rowOverlay = rowOverlay 68 | self.results = results 69 | _selected = selected 70 | _context = State(initialValue: TablerContext(config)) 71 | } 72 | 73 | // MARK: Locals 74 | 75 | @State private var context: Context 76 | 77 | // MARK: Views 78 | 79 | public var body: some View { 80 | BaseGrid(context: $context, 81 | header: headerContent, 82 | footer: footerContent) 83 | { 84 | ForEach(results.filter(config.filter ?? { _ in true })) { element in 85 | rowContent(element) 86 | .modifier(GridItemModM(config: config, 87 | element: element, 88 | selected: $selected)) 89 | .background(rowBackground(element)) 90 | .overlay(rowOverlay(element)) 91 | } 92 | } 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /Sources/Grid/TablerGridMB.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TablerGridMB.swift 3 | // 4 | // Copyright 2021, 2022 OpenAlloc LLC 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | // 18 | 19 | import SwiftUI 20 | 21 | // sourcery: AutoInit, selectBinding, resultsBinding 22 | /// Grid-based table, with support for multi-select and bound value types 23 | public struct TablerGridMB: View 24 | where Element: Identifiable, 25 | Header: View, 26 | Footer: View, 27 | Row: View, 28 | RowBack: View, 29 | RowOver: View, 30 | Results: RandomAccessCollection & MutableCollection, 31 | Results.Element == Element, 32 | Results.Index: Hashable 33 | { 34 | public typealias Config = TablerGridConfig 35 | public typealias Context = TablerContext 36 | public typealias HeaderContent = (Binding) -> Header 37 | public typealias FooterContent = (Binding) -> Footer 38 | public typealias RowContent = (Binding) -> Row 39 | public typealias RowBackground = (Element) -> RowBack 40 | public typealias RowOverlay = (Element) -> RowOver 41 | public typealias Selected = Set 42 | 43 | // MARK: Parameters 44 | 45 | private let config: Config 46 | private let headerContent: HeaderContent 47 | private let footerContent: FooterContent 48 | private let rowContent: RowContent 49 | private let rowBackground: RowBackground 50 | private let rowOverlay: RowOverlay 51 | @Binding private var results: Results 52 | @Binding private var selected: Selected 53 | 54 | public init(_ config: Config, 55 | @ViewBuilder header: @escaping HeaderContent, 56 | @ViewBuilder footer: @escaping FooterContent, 57 | @ViewBuilder row: @escaping RowContent, 58 | @ViewBuilder rowBackground: @escaping RowBackground, 59 | @ViewBuilder rowOverlay: @escaping RowOverlay, 60 | results: Binding, 61 | selected: Binding) 62 | { 63 | self.config = config 64 | headerContent = header 65 | footerContent = footer 66 | rowContent = row 67 | self.rowBackground = rowBackground 68 | self.rowOverlay = rowOverlay 69 | _results = results 70 | _selected = selected 71 | _context = State(initialValue: TablerContext(config)) 72 | } 73 | 74 | // MARK: Locals 75 | 76 | @State private var context: Context 77 | 78 | // MARK: Views 79 | 80 | public var body: some View { 81 | BaseGrid(context: $context, 82 | header: headerContent, 83 | footer: footerContent) 84 | { 85 | ForEach($results) { $element in 86 | rowContent($element) 87 | .modifier(GridItemModM(config: config, 88 | element: element, 89 | selected: $selected)) 90 | .background(rowBackground(element)) 91 | .overlay(rowOverlay(element)) 92 | } 93 | } 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /Sources/Grid/TablerGridMC.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TablerGridMC.swift 3 | // 4 | // Copyright 2021, 2022 OpenAlloc LLC 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | // 18 | 19 | import SwiftUI 20 | 21 | // sourcery: AutoInit, selectBinding 22 | /// Grid-based table, with support for multi-select and reference types 23 | public struct TablerGridMC: View 24 | where Element: Identifiable & ObservableObject, 25 | Header: View, 26 | Footer: View, 27 | Row: View, 28 | RowBack: View, 29 | RowOver: View, 30 | Results: RandomAccessCollection, 31 | Results.Element == Element 32 | { 33 | public typealias Config = TablerGridConfig 34 | public typealias Context = TablerContext 35 | public typealias HeaderContent = (Binding) -> Header 36 | public typealias FooterContent = (Binding) -> Footer 37 | public typealias ProjectedValue = ObservedObject.Wrapper 38 | public typealias RowContent = (ProjectedValue) -> Row 39 | public typealias RowBackground = (Element) -> RowBack 40 | public typealias RowOverlay = (Element) -> RowOver 41 | public typealias Selected = Set 42 | 43 | // MARK: Parameters 44 | 45 | private let config: Config 46 | private let headerContent: HeaderContent 47 | private let footerContent: FooterContent 48 | private let rowContent: RowContent 49 | private let rowBackground: RowBackground 50 | private let rowOverlay: RowOverlay 51 | private var results: Results 52 | @Binding private var selected: Selected 53 | 54 | public init(_ config: Config, 55 | @ViewBuilder header: @escaping HeaderContent, 56 | @ViewBuilder footer: @escaping FooterContent, 57 | @ViewBuilder row: @escaping RowContent, 58 | @ViewBuilder rowBackground: @escaping RowBackground, 59 | @ViewBuilder rowOverlay: @escaping RowOverlay, 60 | results: Results, 61 | selected: Binding) 62 | { 63 | self.config = config 64 | headerContent = header 65 | footerContent = footer 66 | rowContent = row 67 | self.rowBackground = rowBackground 68 | self.rowOverlay = rowOverlay 69 | self.results = results 70 | _selected = selected 71 | _context = State(initialValue: TablerContext(config)) 72 | } 73 | 74 | // MARK: Locals 75 | 76 | @State private var context: Context 77 | 78 | // MARK: Views 79 | 80 | public var body: some View { 81 | BaseGrid(context: $context, 82 | header: headerContent, 83 | footer: footerContent) 84 | { 85 | ForEach(results) { rawElem in 86 | ObservableHolder(element: rawElem) { obsElem in 87 | rowContent(obsElem) 88 | .modifier(GridItemModM(config: config, 89 | element: rawElem, 90 | selected: $selected)) 91 | .background(rowBackground(rawElem)) 92 | .overlay(rowOverlay(rawElem)) 93 | } 94 | } 95 | } 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /Sources/Internal/BaseTable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BaseTable.swift 3 | // 4 | // Copyright 2021, 2022 OpenAlloc LLC 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | // 18 | 19 | import SwiftUI 20 | 21 | struct BaseTable: View 22 | where Element: Identifiable, 23 | Header: View, 24 | Footer: View, 25 | Rows: View 26 | { 27 | typealias Context = TablerContext 28 | typealias HeaderContent = (Binding) -> Header 29 | typealias FooterContent = (Binding) -> Footer 30 | typealias HeaderBuilder = () -> Header 31 | typealias FooterBuilder = () -> Footer 32 | typealias TableBuilder = (@escaping HeaderBuilder, @escaping FooterBuilder) -> Rows 33 | 34 | // MARK: Parameters 35 | 36 | @Binding var context: Context 37 | let header: HeaderContent 38 | let footer: FooterContent 39 | let tableBuilder: TableBuilder 40 | 41 | // MARK: Views 42 | 43 | var body: some View { 44 | tableBuilder({ header($context) }, 45 | { footer($context) }) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /Sources/Internal/ObservableHolder.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ObservableHolder.swift 3 | // 4 | // Copyright 2021, 2022 OpenAlloc LLC 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | // 18 | 19 | import SwiftUI 20 | 21 | struct ObservableHolder: View 22 | where Element: Identifiable & ObservableObject, 23 | Row: View 24 | { 25 | public typealias ProjectedValue = ObservedObject.Wrapper 26 | public typealias RowContent = (ProjectedValue) -> Row 27 | 28 | @ObservedObject var element: Element 29 | let rowContent: RowContent 30 | 31 | var body: some View { 32 | rowContent($element) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Sources/Internal/TablerSpacedConfig.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TablerSpacedConfig.swift 3 | // 4 | // Copyright 2021, 2022 OpenAlloc LLC 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | // 18 | 19 | import SwiftUI 20 | 21 | public enum TablerSpacedConfigDefaults { 22 | #if os(macOS) 23 | public static let headerSpacing: CGFloat = 4 24 | public static let footerSpacing: CGFloat = -4 25 | public static let tablePadding = EdgeInsets(top: 10, leading: 16, bottom: 15, trailing: 16) 26 | #elseif os(iOS) 27 | public static let headerSpacing: CGFloat = 10 28 | public static let footerSpacing: CGFloat = 3 29 | public static let tablePadding = EdgeInsets(top: 36, leading: 32, bottom: 20, trailing: 32) 30 | #endif 31 | 32 | public static let rowSpacing: CGFloat = 0 33 | 34 | // public static let headerFixed: Bool = true 35 | // public static let footerFixed: Bool = false 36 | } 37 | 38 | public class TablerSpacedConfig: TablerConfig 39 | where Element: Identifiable 40 | { 41 | public let headerSpacing: CGFloat 42 | public let footerSpacing: CGFloat 43 | public let rowSpacing: CGFloat 44 | // public let headerFixed: Bool 45 | // public let footerFixed: Bool 46 | 47 | public init(headerSpacing: CGFloat = TablerSpacedConfigDefaults.headerSpacing, 48 | footerSpacing: CGFloat = TablerSpacedConfigDefaults.footerSpacing, 49 | rowSpacing: CGFloat = TablerSpacedConfigDefaults.rowSpacing, 50 | // headerFixed: Bool = TablerSpacedConfigDefaults.headerFixed, 51 | // footerFixed: Bool = TablerSpacedConfigDefaults.footerFixed, 52 | filter: Filter? = nil, 53 | onHover: @escaping OnHover = { _, _ in }, 54 | tablePadding: EdgeInsets = TablerConfigDefaults.tablePadding, 55 | sortIndicatorForward: AnyView = TablerConfigDefaults.sortIndicatorForward, 56 | sortIndicatorReverse: AnyView = TablerConfigDefaults.sortIndicatorReverse, 57 | sortIndicatorNeutral: AnyView = TablerConfigDefaults.sortIndicatorNeutral) 58 | { 59 | self.headerSpacing = headerSpacing 60 | self.footerSpacing = footerSpacing 61 | self.rowSpacing = rowSpacing 62 | // self.headerFixed = headerFixed 63 | // self.footerFixed = footerFixed 64 | 65 | super.init(filter: filter, 66 | onHover: onHover, 67 | tablePadding: tablePadding, 68 | sortIndicatorForward: sortIndicatorForward, 69 | sortIndicatorReverse: sortIndicatorReverse, 70 | sortIndicatorNeutral: sortIndicatorNeutral) 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /Sources/List/Internal/BaseList.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BaseList.swift 3 | // 4 | // Copyright 2021, 2022 OpenAlloc LLC 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | // 18 | 19 | import SwiftUI 20 | 21 | // List with no selection 22 | struct BaseList: View 23 | where Element: Identifiable, 24 | Header: View, 25 | Footer: View, 26 | Rows: View 27 | { 28 | typealias Config = TablerListConfig 29 | typealias Context = TablerContext 30 | typealias HeaderContent = (Binding) -> Header 31 | typealias FooterContent = (Binding) -> Footer 32 | typealias RowContent = () -> Rows 33 | 34 | @Binding var context: Context 35 | @ViewBuilder let header: HeaderContent 36 | @ViewBuilder let footer: FooterContent 37 | @ViewBuilder let rows: RowContent 38 | 39 | var body: some View { 40 | BaseTable(context: $context, 41 | header: header, 42 | footer: footer) 43 | { buildHeader, buildFooter in 44 | List { 45 | buildHeader() 46 | rows() 47 | buildFooter() 48 | } 49 | } 50 | .padding(config.tablePadding) 51 | } 52 | 53 | private var config: Config { 54 | context.config as? Config ?? Config() 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /Sources/List/Internal/BaseList1.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BaseList1.swift 3 | // 4 | // Copyright 2021, 2022 OpenAlloc LLC 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | // 18 | 19 | import SwiftUI 20 | 21 | // List with single-selection 22 | struct BaseList1: View 23 | where Element: Identifiable, 24 | Header: View, 25 | Footer: View, 26 | Rows: View 27 | { 28 | typealias Config = TablerListConfig 29 | typealias Context = TablerContext 30 | typealias HeaderContent = (Binding) -> Header 31 | typealias FooterContent = (Binding) -> Footer 32 | typealias RowContent = () -> Rows 33 | typealias Selected = Element.ID? 34 | 35 | @Binding var context: Context 36 | @Binding var selected: Selected 37 | @ViewBuilder let header: HeaderContent 38 | @ViewBuilder let footer: FooterContent 39 | @ViewBuilder let rows: RowContent 40 | 41 | var body: some View { 42 | BaseTable(context: $context, 43 | header: header, 44 | footer: footer) 45 | { buildHeader, buildFooter in 46 | List(selection: $selected) { 47 | buildHeader() 48 | rows() 49 | buildFooter() 50 | } 51 | } 52 | .padding(config.tablePadding) 53 | } 54 | 55 | private var config: Config { 56 | context.config as? Config ?? Config() 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /Sources/List/Internal/BaseListM.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BaseListM.swift 3 | // 4 | // Copyright 2021, 2022 OpenAlloc LLC 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | // 18 | 19 | import SwiftUI 20 | 21 | // List with multi-selection 22 | struct BaseListM: View 23 | where Element: Identifiable, 24 | Header: View, 25 | Footer: View, 26 | Rows: View 27 | { 28 | typealias Config = TablerListConfig 29 | typealias Context = TablerContext 30 | typealias HeaderContent = (Binding) -> Header 31 | typealias FooterContent = (Binding) -> Footer 32 | typealias RowContent = () -> Rows 33 | typealias Selected = Set 34 | 35 | @Binding var context: Context 36 | @Binding var selected: Selected 37 | @ViewBuilder let header: HeaderContent 38 | @ViewBuilder let footer: FooterContent 39 | @ViewBuilder let rows: RowContent 40 | 41 | var body: some View { 42 | BaseTable(context: $context, 43 | header: header, 44 | footer: footer) 45 | { buildHeader, buildFooter in 46 | List(selection: $selected) { 47 | buildHeader() 48 | rows() 49 | buildFooter() 50 | } 51 | } 52 | .padding(config.tablePadding) 53 | } 54 | 55 | private var config: Config { 56 | context.config as? Config ?? Config() 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /Sources/List/Internal/ListRowMod.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ListRowMod.swift 3 | // 4 | // Copyright 2021, 2022 OpenAlloc LLC 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | // 18 | 19 | import SwiftUI 20 | 21 | struct ListRowMod: ViewModifier 22 | where Element: Identifiable 23 | { 24 | typealias Config = TablerListConfig 25 | typealias Hovered = Element.ID? 26 | 27 | let config: Config 28 | let element: Element 29 | 30 | func body(content: Content) -> some View { 31 | content 32 | .deleteDisabled(!config.canDelete(element)) 33 | .moveDisabled(!config.canMove(element)) 34 | 35 | #if os(macOS) || targetEnvironment(macCatalyst) 36 | .onHover(perform: { config.onHover(element.id, $0) }) 37 | #endif 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Sources/List/TablerList.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TablerList.swift 3 | // 4 | // Copyright 2021, 2022 OpenAlloc LLC 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | // 18 | 19 | import SwiftUI 20 | 21 | // sourcery: AutoInit 22 | /// List-based table 23 | public struct TablerList: View 24 | where Element: Identifiable, 25 | Header: View, 26 | Footer: View, 27 | Row: View, 28 | RowBack: View, 29 | RowOver: View, 30 | Results: RandomAccessCollection, 31 | Results.Element == Element 32 | { 33 | public typealias Config = TablerListConfig 34 | public typealias Context = TablerContext 35 | public typealias HeaderContent = (Binding) -> Header 36 | public typealias FooterContent = (Binding) -> Footer 37 | public typealias RowContent = (Element) -> Row 38 | public typealias RowBackground = (Element) -> RowBack 39 | public typealias RowOverlay = (Element) -> RowOver 40 | 41 | // MARK: Parameters 42 | 43 | private let config: Config 44 | private let headerContent: HeaderContent 45 | private let footerContent: FooterContent 46 | private let rowContent: RowContent 47 | private let rowBackground: RowBackground 48 | private let rowOverlay: RowOverlay 49 | private var results: Results 50 | 51 | public init(_ config: Config = .init(), 52 | @ViewBuilder header: @escaping HeaderContent, 53 | @ViewBuilder footer: @escaping FooterContent, 54 | @ViewBuilder row: @escaping RowContent, 55 | @ViewBuilder rowBackground: @escaping RowBackground, 56 | @ViewBuilder rowOverlay: @escaping RowOverlay, 57 | results: Results) 58 | { 59 | self.config = config 60 | headerContent = header 61 | footerContent = footer 62 | rowContent = row 63 | self.rowBackground = rowBackground 64 | self.rowOverlay = rowOverlay 65 | self.results = results 66 | _context = State(initialValue: TablerContext(config)) 67 | } 68 | 69 | // MARK: Locals 70 | 71 | @State private var context: Context 72 | 73 | // MARK: Views 74 | 75 | public var body: some View { 76 | BaseList(context: $context, 77 | header: headerContent, 78 | footer: footerContent) 79 | { 80 | ForEach(results.filter(config.filter ?? { _ in true })) { element in 81 | rowContent(element) 82 | .modifier(ListRowMod(config: config, 83 | element: element)) 84 | .listRowBackground(rowBackground(element)) 85 | .overlay(rowOverlay(element)) 86 | } 87 | .onMove(perform: config.onMove) 88 | .onDelete(perform: config.onDelete) 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /Sources/List/TablerList1.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TablerList1.swift 3 | // 4 | // Copyright 2021, 2022 OpenAlloc LLC 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | // 18 | 19 | import SwiftUI 20 | 21 | // sourcery: AutoInit, selectBinding 22 | /// List-based table, with support for single-select 23 | public struct TablerList1: View 24 | where Element: Identifiable, 25 | Header: View, 26 | Footer: View, 27 | Row: View, 28 | RowBack: View, 29 | RowOver: View, 30 | Results: RandomAccessCollection, 31 | Results.Element == Element 32 | { 33 | public typealias Config = TablerListConfig 34 | public typealias Context = TablerContext 35 | public typealias HeaderContent = (Binding) -> Header 36 | public typealias FooterContent = (Binding) -> Footer 37 | public typealias RowContent = (Element) -> Row 38 | public typealias RowBackground = (Element) -> RowBack 39 | public typealias RowOverlay = (Element) -> RowOver 40 | public typealias Selected = Element.ID? 41 | 42 | // MARK: Parameters 43 | 44 | private let config: Config 45 | private let headerContent: HeaderContent 46 | private let footerContent: FooterContent 47 | private let rowContent: RowContent 48 | private let rowBackground: RowBackground 49 | private let rowOverlay: RowOverlay 50 | private var results: Results 51 | @Binding private var selected: Selected 52 | 53 | public init(_ config: Config = .init(), 54 | @ViewBuilder header: @escaping HeaderContent, 55 | @ViewBuilder footer: @escaping FooterContent, 56 | @ViewBuilder row: @escaping RowContent, 57 | @ViewBuilder rowBackground: @escaping RowBackground, 58 | @ViewBuilder rowOverlay: @escaping RowOverlay, 59 | results: Results, 60 | selected: Binding) 61 | { 62 | self.config = config 63 | headerContent = header 64 | footerContent = footer 65 | rowContent = row 66 | self.rowBackground = rowBackground 67 | self.rowOverlay = rowOverlay 68 | self.results = results 69 | _selected = selected 70 | _context = State(initialValue: TablerContext(config)) 71 | } 72 | 73 | // MARK: Locals 74 | 75 | @State private var context: Context 76 | 77 | // MARK: Views 78 | 79 | public var body: some View { 80 | BaseList1(context: $context, 81 | selected: $selected, 82 | header: headerContent, 83 | footer: footerContent) 84 | { 85 | ForEach(results.filter(config.filter ?? { _ in true })) { element in 86 | rowContent(element) 87 | .modifier(ListRowMod(config: config, 88 | element: element)) 89 | .listRowBackground(rowBackground(element)) 90 | .overlay(rowOverlay(element)) 91 | } 92 | .onMove(perform: config.onMove) 93 | .onDelete(perform: config.onDelete) 94 | } 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /Sources/List/TablerList1B.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TablerList1B.swift 3 | // 4 | // Copyright 2021, 2022 OpenAlloc LLC 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | // 18 | 19 | import SwiftUI 20 | 21 | // sourcery: AutoInit, selectBinding, resultsBinding 22 | /// List-based table, with support for single-select and bound value types 23 | public struct TablerList1B: View 24 | where Element: Identifiable, 25 | Header: View, 26 | Footer: View, 27 | Row: View, 28 | RowBack: View, 29 | RowOver: View, 30 | Results: RandomAccessCollection & MutableCollection, 31 | Results.Element == Element, 32 | Results.Index: Hashable 33 | { 34 | public typealias Config = TablerListConfig 35 | public typealias Context = TablerContext 36 | public typealias HeaderContent = (Binding) -> Header 37 | public typealias FooterContent = (Binding) -> Footer 38 | public typealias RowContent = (Binding) -> Row 39 | public typealias RowBackground = (Element) -> RowBack 40 | public typealias RowOverlay = (Element) -> RowOver 41 | public typealias Selected = Element.ID? 42 | 43 | // MARK: Parameters 44 | 45 | private let config: Config 46 | private let headerContent: HeaderContent 47 | private let footerContent: FooterContent 48 | private let rowContent: RowContent 49 | private let rowBackground: RowBackground 50 | private let rowOverlay: RowOverlay 51 | @Binding private var results: Results 52 | @Binding private var selected: Selected 53 | 54 | public init(_ config: Config = .init(), 55 | @ViewBuilder header: @escaping HeaderContent, 56 | @ViewBuilder footer: @escaping FooterContent, 57 | @ViewBuilder row: @escaping RowContent, 58 | @ViewBuilder rowBackground: @escaping RowBackground, 59 | @ViewBuilder rowOverlay: @escaping RowOverlay, 60 | results: Binding, 61 | selected: Binding) 62 | { 63 | self.config = config 64 | headerContent = header 65 | footerContent = footer 66 | rowContent = row 67 | self.rowBackground = rowBackground 68 | self.rowOverlay = rowOverlay 69 | _results = results 70 | _selected = selected 71 | _context = State(initialValue: TablerContext(config)) 72 | } 73 | 74 | // MARK: Locals 75 | 76 | @State private var context: Context 77 | 78 | // MARK: Views 79 | 80 | public var body: some View { 81 | BaseList1(context: $context, 82 | selected: $selected, 83 | header: headerContent, 84 | footer: footerContent) 85 | { 86 | // TODO: is there a better way to filter bound data source? 87 | if let _filter = config.filter { 88 | ForEach($results) { $element in 89 | if _filter(element) { 90 | row($element) 91 | } 92 | } 93 | .onMove(perform: config.onMove) 94 | .onDelete(perform: config.onDelete) 95 | } else { 96 | ForEach($results) { $element in 97 | row($element) 98 | } 99 | .onMove(perform: config.onMove) 100 | .onDelete(perform: config.onDelete) 101 | } 102 | } 103 | } 104 | 105 | private func row(_ element: Binding) -> some View { 106 | rowContent(element) 107 | .modifier(ListRowMod(config: config, 108 | element: element.wrappedValue)) 109 | .listRowBackground(rowBackground(element.wrappedValue)) 110 | .overlay(rowOverlay(element.wrappedValue)) 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /Sources/List/TablerList1C.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TablerList1C.swift 3 | // 4 | // Copyright 2021, 2022 OpenAlloc LLC 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | // 18 | 19 | import SwiftUI 20 | 21 | // sourcery: AutoInit, selectBinding 22 | /// List-based table, with support for single-select and reference types 23 | public struct TablerList1C: View 24 | where Element: Identifiable & ObservableObject, 25 | Header: View, 26 | Footer: View, 27 | Row: View, 28 | RowBack: View, 29 | RowOver: View, 30 | Results: RandomAccessCollection, 31 | Results.Element == Element 32 | { 33 | public typealias Config = TablerListConfig 34 | public typealias Context = TablerContext 35 | public typealias HeaderContent = (Binding) -> Header 36 | public typealias FooterContent = (Binding) -> Footer 37 | public typealias ProjectedValue = ObservedObject.Wrapper 38 | public typealias RowContent = (ProjectedValue) -> Row 39 | public typealias RowBackground = (Element) -> RowBack 40 | public typealias RowOverlay = (Element) -> RowOver 41 | public typealias Selected = Element.ID? 42 | 43 | // MARK: Parameters 44 | 45 | private let config: Config 46 | private let headerContent: HeaderContent 47 | private let footerContent: FooterContent 48 | private let rowContent: RowContent 49 | private let rowBackground: RowBackground 50 | private let rowOverlay: RowOverlay 51 | private var results: Results 52 | @Binding private var selected: Selected 53 | 54 | public init(_ config: Config = .init(), 55 | @ViewBuilder header: @escaping HeaderContent, 56 | @ViewBuilder footer: @escaping FooterContent, 57 | @ViewBuilder row: @escaping RowContent, 58 | @ViewBuilder rowBackground: @escaping RowBackground, 59 | @ViewBuilder rowOverlay: @escaping RowOverlay, 60 | results: Results, 61 | selected: Binding) 62 | { 63 | self.config = config 64 | headerContent = header 65 | footerContent = footer 66 | rowContent = row 67 | self.rowBackground = rowBackground 68 | self.rowOverlay = rowOverlay 69 | self.results = results 70 | _selected = selected 71 | _context = State(initialValue: TablerContext(config)) 72 | } 73 | 74 | // MARK: Locals 75 | 76 | @State private var context: Context 77 | 78 | // MARK: Views 79 | 80 | public var body: some View { 81 | BaseList1(context: $context, 82 | selected: $selected, 83 | header: headerContent, 84 | footer: footerContent) 85 | { 86 | ForEach(results) { rawElem in 87 | ObservableHolder(element: rawElem) { obsElem in 88 | rowContent(obsElem) 89 | .modifier(ListRowMod(config: config, 90 | element: rawElem)) 91 | .listRowBackground(rowBackground(rawElem)) 92 | .overlay(rowOverlay(rawElem)) 93 | } 94 | } 95 | .onMove(perform: config.onMove) 96 | .onDelete(perform: config.onDelete) 97 | } 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /Sources/List/TablerListB.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TablerListB.swift 3 | // 4 | // Copyright 2021, 2022 OpenAlloc LLC 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | // 18 | 19 | import SwiftUI 20 | 21 | // sourcery: AutoInit, resultsBinding 22 | /// List-based table, with support for bound value types 23 | public struct TablerListB: View 24 | where Element: Identifiable, 25 | Header: View, 26 | Footer: View, 27 | Row: View, 28 | RowBack: View, 29 | RowOver: View, 30 | Results: RandomAccessCollection & MutableCollection, 31 | Results.Element == Element, 32 | Results.Index: Hashable 33 | { 34 | public typealias Config = TablerListConfig 35 | public typealias Context = TablerContext 36 | public typealias HeaderContent = (Binding) -> Header 37 | public typealias FooterContent = (Binding) -> Footer 38 | public typealias RowContent = (Binding) -> Row 39 | public typealias RowBackground = (Element) -> RowBack 40 | public typealias RowOverlay = (Element) -> RowOver 41 | 42 | // MARK: Parameters 43 | 44 | private let config: Config 45 | private let headerContent: HeaderContent 46 | private let footerContent: FooterContent 47 | private let rowContent: RowContent 48 | private let rowBackground: RowBackground 49 | private let rowOverlay: RowOverlay 50 | @Binding private var results: Results 51 | 52 | public init(_ config: Config = .init(), 53 | @ViewBuilder header: @escaping HeaderContent, 54 | @ViewBuilder footer: @escaping FooterContent, 55 | @ViewBuilder row: @escaping RowContent, 56 | @ViewBuilder rowBackground: @escaping RowBackground, 57 | @ViewBuilder rowOverlay: @escaping RowOverlay, 58 | results: Binding) 59 | { 60 | self.config = config 61 | headerContent = header 62 | footerContent = footer 63 | rowContent = row 64 | self.rowBackground = rowBackground 65 | self.rowOverlay = rowOverlay 66 | _results = results 67 | _context = State(initialValue: TablerContext(config)) 68 | } 69 | 70 | // MARK: Locals 71 | 72 | @State private var context: Context 73 | 74 | // MARK: Views 75 | 76 | public var body: some View { 77 | BaseList(context: $context, 78 | header: headerContent, 79 | footer: footerContent) 80 | { 81 | // TODO: is there a better way to filter bound data source? 82 | if let _filter = config.filter { 83 | ForEach($results) { $element in 84 | if _filter(element) { 85 | row($element) 86 | } 87 | } 88 | .onMove(perform: config.onMove) 89 | .onDelete(perform: config.onDelete) 90 | } else { 91 | ForEach($results) { $element in 92 | row($element) 93 | } 94 | .onMove(perform: config.onMove) 95 | .onDelete(perform: config.onDelete) 96 | } 97 | } 98 | } 99 | 100 | private func row(_ element: Binding) -> some View { 101 | rowContent(element) 102 | .modifier(ListRowMod(config: config, 103 | element: element.wrappedValue)) 104 | .listRowBackground(rowBackground(element.wrappedValue)) 105 | .overlay(rowOverlay(element.wrappedValue)) 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /Sources/List/TablerListC.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TablerListC.swift 3 | // 4 | // Copyright 2021, 2022 OpenAlloc LLC 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | // 18 | 19 | import SwiftUI 20 | 21 | // sourcery: AutoInit 22 | /// List-based table, with support for reference types 23 | public struct TablerListC: View 24 | where Element: Identifiable & ObservableObject, 25 | Header: View, 26 | Footer: View, 27 | Row: View, 28 | RowBack: View, 29 | RowOver: View, 30 | Results: RandomAccessCollection, 31 | Results.Element == Element 32 | { 33 | public typealias Config = TablerListConfig 34 | public typealias Context = TablerContext 35 | public typealias HeaderContent = (Binding) -> Header 36 | public typealias FooterContent = (Binding) -> Footer 37 | public typealias ProjectedValue = ObservedObject.Wrapper 38 | public typealias RowContent = (ProjectedValue) -> Row 39 | public typealias RowBackground = (Element) -> RowBack 40 | public typealias RowOverlay = (Element) -> RowOver 41 | 42 | // MARK: Parameters 43 | 44 | private let config: Config 45 | private let headerContent: HeaderContent 46 | private let footerContent: FooterContent 47 | private let rowContent: RowContent 48 | private let rowBackground: RowBackground 49 | private let rowOverlay: RowOverlay 50 | private var results: Results 51 | 52 | public init(_ config: Config = .init(), 53 | @ViewBuilder header: @escaping HeaderContent, 54 | @ViewBuilder footer: @escaping FooterContent, 55 | @ViewBuilder row: @escaping RowContent, 56 | @ViewBuilder rowBackground: @escaping RowBackground, 57 | @ViewBuilder rowOverlay: @escaping RowOverlay, 58 | results: Results) 59 | { 60 | self.config = config 61 | headerContent = header 62 | footerContent = footer 63 | rowContent = row 64 | self.rowBackground = rowBackground 65 | self.rowOverlay = rowOverlay 66 | self.results = results 67 | _context = State(initialValue: TablerContext(config)) 68 | } 69 | 70 | // MARK: Locals 71 | 72 | @State private var context: Context 73 | 74 | // MARK: Views 75 | 76 | public var body: some View { 77 | BaseList(context: $context, 78 | header: headerContent, 79 | footer: footerContent) 80 | { 81 | ForEach(results) { rawElem in 82 | ObservableHolder(element: rawElem) { obsElem in 83 | rowContent(obsElem) 84 | .modifier(ListRowMod(config: config, 85 | element: rawElem)) 86 | .listRowBackground(rowBackground(rawElem)) 87 | .overlay(rowOverlay(rawElem)) 88 | } 89 | } 90 | .onMove(perform: config.onMove) 91 | .onDelete(perform: config.onDelete) 92 | } 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /Sources/List/TablerListConfig.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TablerListConfig.swift 3 | // 4 | // Copyright 2021, 2022 OpenAlloc LLC 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | // 18 | 19 | import SwiftUI 20 | 21 | public class TablerListConfig: TablerConfig 22 | where Element: Identifiable 23 | { 24 | public typealias CanMove = (Element) -> Bool 25 | public typealias OnMove = (IndexSet, Int) -> Void 26 | public typealias CanDelete = (Element) -> Bool 27 | public typealias OnDelete = (IndexSet) -> Void 28 | 29 | public let canMove: CanMove 30 | public let onMove: OnMove? 31 | public let canDelete: CanDelete 32 | public let onDelete: OnDelete? 33 | 34 | public init(canMove: @escaping CanMove = { _ in true }, 35 | onMove: OnMove? = nil, 36 | canDelete: @escaping CanDelete = { _ in true }, 37 | onDelete: OnDelete? = nil, 38 | filter: Filter? = nil, 39 | onHover: @escaping OnHover = { _, _ in }, 40 | tablePadding: EdgeInsets = TablerConfigDefaults.tablePadding, 41 | sortIndicatorForward: AnyView = TablerConfigDefaults.sortIndicatorForward, 42 | sortIndicatorReverse: AnyView = TablerConfigDefaults.sortIndicatorReverse, 43 | sortIndicatorNeutral: AnyView = TablerConfigDefaults.sortIndicatorNeutral) 44 | { 45 | self.canMove = canMove 46 | self.onMove = onMove 47 | self.canDelete = canDelete 48 | self.onDelete = onDelete 49 | 50 | super.init(filter: filter, 51 | onHover: onHover, 52 | tablePadding: tablePadding, 53 | sortIndicatorForward: sortIndicatorForward, 54 | sortIndicatorReverse: sortIndicatorReverse, 55 | sortIndicatorNeutral: sortIndicatorNeutral) 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /Sources/List/TablerListM.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TablerListM.swift 3 | // 4 | // Copyright 2021, 2022 OpenAlloc LLC 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | // 18 | 19 | import SwiftUI 20 | 21 | // sourcery: AutoInit, selectBinding 22 | /// List-based table, with support for multi-select 23 | public struct TablerListM: View 24 | where Element: Identifiable, 25 | Header: View, 26 | Footer: View, 27 | Row: View, 28 | RowBack: View, 29 | RowOver: View, 30 | Results: RandomAccessCollection, 31 | Results.Element == Element 32 | { 33 | public typealias Config = TablerListConfig 34 | public typealias Context = TablerContext 35 | public typealias HeaderContent = (Binding) -> Header 36 | public typealias FooterContent = (Binding) -> Footer 37 | public typealias RowContent = (Element) -> Row 38 | public typealias RowBackground = (Element) -> RowBack 39 | public typealias RowOverlay = (Element) -> RowOver 40 | public typealias Selected = Set 41 | 42 | // MARK: Parameters 43 | 44 | private let config: Config 45 | private let headerContent: HeaderContent 46 | private let footerContent: FooterContent 47 | private let rowContent: RowContent 48 | private let rowBackground: RowBackground 49 | private let rowOverlay: RowOverlay 50 | private var results: Results 51 | @Binding private var selected: Selected 52 | 53 | public init(_ config: Config = .init(), 54 | @ViewBuilder header: @escaping HeaderContent, 55 | @ViewBuilder footer: @escaping FooterContent, 56 | @ViewBuilder row: @escaping RowContent, 57 | @ViewBuilder rowBackground: @escaping RowBackground, 58 | @ViewBuilder rowOverlay: @escaping RowOverlay, 59 | results: Results, 60 | selected: Binding) 61 | { 62 | self.config = config 63 | headerContent = header 64 | footerContent = footer 65 | rowContent = row 66 | self.rowBackground = rowBackground 67 | self.rowOverlay = rowOverlay 68 | self.results = results 69 | _selected = selected 70 | _context = State(initialValue: TablerContext(config)) 71 | } 72 | 73 | // MARK: Locals 74 | 75 | @State private var context: Context 76 | 77 | // MARK: Views 78 | 79 | public var body: some View { 80 | BaseListM(context: $context, 81 | selected: $selected, 82 | header: headerContent, 83 | footer: footerContent) 84 | { 85 | ForEach(results.filter(config.filter ?? { _ in true })) { element in 86 | rowContent(element) 87 | .modifier(ListRowMod(config: config, 88 | element: element)) 89 | .listRowBackground(rowBackground(element)) 90 | .overlay(rowOverlay(element)) 91 | } 92 | .onMove(perform: config.onMove) 93 | .onDelete(perform: config.onDelete) 94 | } 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /Sources/List/TablerListMB.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TablerListMB.swift 3 | // 4 | // Copyright 2021, 2022 OpenAlloc LLC 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | // 18 | 19 | import SwiftUI 20 | 21 | // sourcery: AutoInit, selectBinding, resultsBinding 22 | /// List-based table, with support for multi-select and bound value types 23 | public struct TablerListMB: View 24 | where Element: Identifiable, 25 | Header: View, 26 | Footer: View, 27 | Row: View, 28 | RowBack: View, 29 | RowOver: View, 30 | Results: RandomAccessCollection & MutableCollection, 31 | Results.Element == Element, 32 | Results.Index: Hashable 33 | { 34 | public typealias Config = TablerListConfig 35 | public typealias Context = TablerContext 36 | public typealias HeaderContent = (Binding) -> Header 37 | public typealias FooterContent = (Binding) -> Footer 38 | public typealias RowContent = (Binding) -> Row 39 | public typealias RowBackground = (Element) -> RowBack 40 | public typealias RowOverlay = (Element) -> RowOver 41 | public typealias Selected = Set 42 | 43 | // MARK: Parameters 44 | 45 | private let config: Config 46 | private let headerContent: HeaderContent 47 | private let footerContent: FooterContent 48 | private let rowContent: RowContent 49 | private let rowBackground: RowBackground 50 | private let rowOverlay: RowOverlay 51 | @Binding private var results: Results 52 | @Binding private var selected: Selected 53 | 54 | public init(_ config: Config = .init(), 55 | @ViewBuilder header: @escaping HeaderContent, 56 | @ViewBuilder footer: @escaping FooterContent, 57 | @ViewBuilder row: @escaping RowContent, 58 | @ViewBuilder rowBackground: @escaping RowBackground, 59 | @ViewBuilder rowOverlay: @escaping RowOverlay, 60 | results: Binding, 61 | selected: Binding) 62 | { 63 | self.config = config 64 | headerContent = header 65 | footerContent = footer 66 | rowContent = row 67 | self.rowBackground = rowBackground 68 | self.rowOverlay = rowOverlay 69 | _results = results 70 | _selected = selected 71 | _context = State(initialValue: TablerContext(config)) 72 | } 73 | 74 | // MARK: Locals 75 | 76 | @State private var context: Context 77 | 78 | // MARK: Views 79 | 80 | public var body: some View { 81 | BaseListM(context: $context, 82 | selected: $selected, 83 | header: headerContent, 84 | footer: footerContent) 85 | { 86 | // TODO: is there a better way to filter bound data source? 87 | if let _filter = config.filter { 88 | ForEach($results) { $element in 89 | if _filter(element) { 90 | row($element) 91 | } 92 | } 93 | .onMove(perform: config.onMove) 94 | .onDelete(perform: config.onDelete) 95 | } else { 96 | ForEach($results) { $element in 97 | row($element) 98 | } 99 | .onMove(perform: config.onMove) 100 | .onDelete(perform: config.onDelete) 101 | } 102 | } 103 | } 104 | 105 | private func row(_ element: Binding) -> some View { 106 | rowContent(element) 107 | .modifier(ListRowMod(config: config, 108 | element: element.wrappedValue)) 109 | .listRowBackground(rowBackground(element.wrappedValue)) 110 | .overlay(rowOverlay(element.wrappedValue)) 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /Sources/List/TablerListMC.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TablerListMC.swift 3 | // 4 | // Copyright 2021, 2022 OpenAlloc LLC 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | // 18 | 19 | import SwiftUI 20 | 21 | // sourcery: AutoInit, selectBinding 22 | /// List-based table, with support for multi-select and reference types 23 | public struct TablerListMC: View 24 | where Element: Identifiable & ObservableObject, 25 | Header: View, 26 | Footer: View, 27 | Row: View, 28 | RowBack: View, 29 | RowOver: View, 30 | Results: RandomAccessCollection, 31 | Results.Element == Element 32 | { 33 | public typealias Config = TablerListConfig 34 | public typealias Context = TablerContext 35 | public typealias HeaderContent = (Binding) -> Header 36 | public typealias FooterContent = (Binding) -> Footer 37 | public typealias ProjectedValue = ObservedObject.Wrapper 38 | public typealias RowContent = (ProjectedValue) -> Row 39 | public typealias RowBackground = (Element) -> RowBack 40 | public typealias RowOverlay = (Element) -> RowOver 41 | public typealias Selected = Set 42 | 43 | // MARK: Parameters 44 | 45 | private let config: Config 46 | private let headerContent: HeaderContent 47 | private let footerContent: FooterContent 48 | private let rowContent: RowContent 49 | private let rowBackground: RowBackground 50 | private let rowOverlay: RowOverlay 51 | private var results: Results 52 | @Binding private var selected: Selected 53 | 54 | public init(_ config: Config = .init(), 55 | @ViewBuilder header: @escaping HeaderContent, 56 | @ViewBuilder footer: @escaping FooterContent, 57 | @ViewBuilder row: @escaping RowContent, 58 | @ViewBuilder rowBackground: @escaping RowBackground, 59 | @ViewBuilder rowOverlay: @escaping RowOverlay, 60 | results: Results, 61 | selected: Binding) 62 | { 63 | self.config = config 64 | headerContent = header 65 | footerContent = footer 66 | rowContent = row 67 | self.rowBackground = rowBackground 68 | self.rowOverlay = rowOverlay 69 | self.results = results 70 | _selected = selected 71 | _context = State(initialValue: TablerContext(config)) 72 | } 73 | 74 | // MARK: Locals 75 | 76 | @State private var context: Context 77 | 78 | // MARK: Views 79 | 80 | public var body: some View { 81 | BaseListM(context: $context, 82 | selected: $selected, 83 | header: headerContent, 84 | footer: footerContent) 85 | { 86 | ForEach(results) { rawElem in 87 | ObservableHolder(element: rawElem) { obsElem in 88 | rowContent(obsElem) 89 | .modifier(ListRowMod(config: config, 90 | element: rawElem)) 91 | .listRowBackground(rowBackground(rawElem)) 92 | .overlay(rowOverlay(rawElem)) 93 | } 94 | } 95 | .onMove(perform: config.onMove) 96 | .onDelete(perform: config.onDelete) 97 | } 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /Sources/Stack/Internal/BaseStack.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BaseStack.swift 3 | // 4 | // Copyright 2021, 2022 OpenAlloc LLC 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | // 18 | 19 | import SwiftUI 20 | 21 | // Stack-based list 22 | struct BaseStack: View 23 | where Element: Identifiable, 24 | Header: View, 25 | Footer: View, 26 | Rows: View 27 | { 28 | typealias Config = TablerStackConfig 29 | typealias Context = TablerContext 30 | typealias HeaderContent = (Binding) -> Header 31 | typealias FooterContent = (Binding) -> Footer 32 | typealias RowContent = () -> Rows 33 | 34 | @Binding var context: Context 35 | @ViewBuilder let header: HeaderContent 36 | @ViewBuilder let footer: FooterContent 37 | @ViewBuilder let rows: RowContent 38 | 39 | var body: some View { 40 | BaseTable(context: $context, 41 | header: header, 42 | footer: footer) 43 | { buildHeader, buildFooter in 44 | 45 | VStack(spacing: 0) { 46 | // if config.headerFixed { 47 | buildHeader() 48 | .padding(.vertical, config.headerSpacing) 49 | // } 50 | 51 | ScrollView { 52 | // if !config.headerFixed { 53 | // buildHeader() 54 | // .padding(.vertical, config.headerSpacing) 55 | // } 56 | 57 | LazyVStack(alignment: .leading, spacing: config.rowSpacing) { 58 | rows() 59 | } 60 | 61 | // if !config.footerFixed { 62 | // buildFooter() 63 | // .padding(.vertical, config.footerSpacing) 64 | // } 65 | } 66 | 67 | // if config.footerFixed { 68 | buildFooter() 69 | .padding(.vertical, config.footerSpacing) 70 | // } 71 | } 72 | } 73 | .padding(config.tablePadding) 74 | } 75 | 76 | private var config: Config { 77 | context.config as? Config ?? Config() 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /Sources/Stack/Internal/StackRowMod.swift: -------------------------------------------------------------------------------- 1 | // 2 | // StackRowMod.swift 3 | // 4 | // Copyright 2021, 2022 OpenAlloc LLC 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | // 18 | 19 | import SwiftUI 20 | 21 | struct StackRowMod: ViewModifier 22 | where Element: Identifiable 23 | { 24 | typealias Config = TablerStackConfig 25 | typealias Hovered = Element.ID? 26 | 27 | let config: Config 28 | let element: Element 29 | 30 | func body(content: Content) -> some View { 31 | content 32 | .padding(config.rowPadding) 33 | 34 | #if os(macOS) || targetEnvironment(macCatalyst) 35 | .onHover(perform: { config.onHover(element.id, $0) }) 36 | #endif 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Sources/Stack/Internal/StackRowMod1.swift: -------------------------------------------------------------------------------- 1 | // 2 | // StackRowMod1.swift 3 | // 4 | // Copyright 2021, 2022 OpenAlloc LLC 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | // 18 | 19 | import SwiftUI 20 | 21 | /// Support for single-select Stack-based rows 22 | struct StackRowMod1: ViewModifier 23 | where Element: Identifiable 24 | { 25 | typealias Config = TablerStackConfig 26 | typealias Selected = Element.ID? 27 | 28 | let config: Config 29 | let element: Element 30 | @Binding var selected: Selected 31 | 32 | func body(content: Content) -> some View { 33 | content 34 | .padding(config.rowPadding) 35 | 36 | // simple tap to select (or unselect) 37 | .contentShape(Rectangle()) 38 | .onTapGesture { 39 | if selected == element.id { 40 | selected = nil 41 | } else { 42 | selected = element.id 43 | } 44 | } 45 | 46 | #if os(macOS) || targetEnvironment(macCatalyst) 47 | .onHover(perform: { config.onHover(element.id, $0) }) 48 | #endif 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /Sources/Stack/Internal/StackRowModM.swift: -------------------------------------------------------------------------------- 1 | // 2 | // StackRowModM.swift 3 | // 4 | // Copyright 2021, 2022 OpenAlloc LLC 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | // 18 | 19 | import SwiftUI 20 | 21 | /// Support for multi-select Stack-based rows 22 | struct StackRowModM: ViewModifier 23 | where Element: Identifiable 24 | { 25 | typealias Config = TablerStackConfig 26 | typealias Hovered = Element.ID? 27 | typealias Selected = Set 28 | 29 | let config: Config 30 | let element: Element 31 | @Binding var selected: Selected 32 | 33 | func body(content: Content) -> some View { 34 | content 35 | .padding(config.rowPadding) 36 | 37 | // simple tap to select (or unselect) 38 | .contentShape(Rectangle()) 39 | .onTapGesture { 40 | if selected.contains(element.id) { 41 | selected.remove(element.id) 42 | } else { 43 | selected.insert(element.id) 44 | } 45 | } 46 | 47 | #if os(macOS) || targetEnvironment(macCatalyst) 48 | .onHover(perform: { config.onHover(element.id, $0) }) 49 | #endif 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /Sources/Stack/TablerStack.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TablerStack.swift 3 | // 4 | // Copyright 2021, 2022 OpenAlloc LLC 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | // 18 | 19 | import SwiftUI 20 | 21 | // sourcery: AutoInit 22 | /// Stack-based table 23 | public struct TablerStack: View 24 | where Element: Identifiable, 25 | Header: View, 26 | Footer: View, 27 | Row: View, 28 | RowBack: View, 29 | RowOver: View, 30 | Results: RandomAccessCollection, 31 | Results.Element == Element 32 | { 33 | public typealias Config = TablerStackConfig 34 | public typealias Context = TablerContext 35 | public typealias HeaderContent = (Binding) -> Header 36 | public typealias FooterContent = (Binding) -> Footer 37 | public typealias RowContent = (Element) -> Row 38 | public typealias RowBackground = (Element) -> RowBack 39 | public typealias RowOverlay = (Element) -> RowOver 40 | 41 | // MARK: Parameters 42 | 43 | private let config: Config 44 | private let headerContent: HeaderContent 45 | private let footerContent: FooterContent 46 | private let rowContent: RowContent 47 | private let rowBackground: RowBackground 48 | private let rowOverlay: RowOverlay 49 | private var results: Results 50 | 51 | public init(_ config: Config = .init(), 52 | @ViewBuilder header: @escaping HeaderContent, 53 | @ViewBuilder footer: @escaping FooterContent, 54 | @ViewBuilder row: @escaping RowContent, 55 | @ViewBuilder rowBackground: @escaping RowBackground, 56 | @ViewBuilder rowOverlay: @escaping RowOverlay, 57 | results: Results) 58 | { 59 | self.config = config 60 | headerContent = header 61 | footerContent = footer 62 | rowContent = row 63 | self.rowBackground = rowBackground 64 | self.rowOverlay = rowOverlay 65 | self.results = results 66 | _context = State(initialValue: TablerContext(config)) 67 | } 68 | 69 | // MARK: Locals 70 | 71 | @State private var context: Context 72 | 73 | // MARK: Views 74 | 75 | public var body: some View { 76 | BaseStack(context: $context, 77 | header: headerContent, 78 | footer: footerContent) 79 | { 80 | ForEach(results.filter(config.filter ?? { _ in true })) { element in 81 | rowContent(element) 82 | .modifier(StackRowMod(config: config, element: element)) 83 | .background(rowBackground(element)) 84 | .overlay(rowOverlay(element)) 85 | } 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /Sources/Stack/TablerStack1.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TablerStack1.swift 3 | // 4 | // Copyright 2021, 2022 OpenAlloc LLC 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | // 18 | 19 | import SwiftUI 20 | 21 | // sourcery: AutoInit, selectBinding 22 | /// Stack-based table, with support for single-select 23 | public struct TablerStack1: View 24 | where Element: Identifiable, 25 | Header: View, 26 | Footer: View, 27 | Row: View, 28 | RowBack: View, 29 | RowOver: View, 30 | Results: RandomAccessCollection, 31 | Results.Element == Element 32 | { 33 | public typealias Config = TablerStackConfig 34 | public typealias Context = TablerContext 35 | public typealias HeaderContent = (Binding) -> Header 36 | public typealias FooterContent = (Binding) -> Footer 37 | public typealias RowContent = (Element) -> Row 38 | public typealias RowBackground = (Element) -> RowBack 39 | public typealias RowOverlay = (Element) -> RowOver 40 | public typealias Selected = Element.ID? 41 | 42 | // MARK: Parameters 43 | 44 | private let config: Config 45 | private let headerContent: HeaderContent 46 | private let footerContent: FooterContent 47 | private let rowContent: RowContent 48 | private let rowBackground: RowBackground 49 | private let rowOverlay: RowOverlay 50 | private var results: Results 51 | @Binding private var selected: Selected 52 | 53 | public init(_ config: Config = .init(), 54 | @ViewBuilder header: @escaping HeaderContent, 55 | @ViewBuilder footer: @escaping FooterContent, 56 | @ViewBuilder row: @escaping RowContent, 57 | @ViewBuilder rowBackground: @escaping RowBackground, 58 | @ViewBuilder rowOverlay: @escaping RowOverlay, 59 | results: Results, 60 | selected: Binding) 61 | { 62 | self.config = config 63 | headerContent = header 64 | footerContent = footer 65 | rowContent = row 66 | self.rowBackground = rowBackground 67 | self.rowOverlay = rowOverlay 68 | self.results = results 69 | _selected = selected 70 | _context = State(initialValue: TablerContext(config)) 71 | } 72 | 73 | // MARK: Locals 74 | 75 | @State private var context: Context 76 | 77 | // MARK: Views 78 | 79 | public var body: some View { 80 | BaseStack(context: $context, 81 | header: headerContent, 82 | footer: footerContent) 83 | { 84 | ForEach(results.filter(config.filter ?? { _ in true })) { element in 85 | rowContent(element) 86 | .modifier(StackRowMod1(config: config, 87 | element: element, 88 | selected: $selected)) 89 | .background(rowBackground(element)) 90 | .overlay(rowOverlay(element)) 91 | } 92 | } 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /Sources/Stack/TablerStack1B.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TablerStack1B.swift 3 | // 4 | // Copyright 2021, 2022 OpenAlloc LLC 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | // 18 | 19 | import SwiftUI 20 | 21 | // sourcery: AutoInit, selectBinding, resultsBinding 22 | /// Stack-based table, with support for single-select and bound value types 23 | public struct TablerStack1B: View 24 | where Element: Identifiable, 25 | Header: View, 26 | Footer: View, 27 | Row: View, 28 | RowBack: View, 29 | RowOver: View, 30 | Results: RandomAccessCollection & MutableCollection, 31 | Results.Element == Element, 32 | Results.Index: Hashable 33 | { 34 | public typealias Config = TablerStackConfig 35 | public typealias Context = TablerContext 36 | public typealias HeaderContent = (Binding) -> Header 37 | public typealias FooterContent = (Binding) -> Footer 38 | public typealias RowContent = (Binding) -> Row 39 | public typealias RowBackground = (Element) -> RowBack 40 | public typealias RowOverlay = (Element) -> RowOver 41 | public typealias Selected = Element.ID? 42 | 43 | // MARK: Parameters 44 | 45 | private let config: Config 46 | private let headerContent: HeaderContent 47 | private let footerContent: FooterContent 48 | private let rowContent: RowContent 49 | private let rowBackground: RowBackground 50 | private let rowOverlay: RowOverlay 51 | @Binding private var results: Results 52 | @Binding private var selected: Selected 53 | 54 | public init(_ config: Config = .init(), 55 | @ViewBuilder header: @escaping HeaderContent, 56 | @ViewBuilder footer: @escaping FooterContent, 57 | @ViewBuilder row: @escaping RowContent, 58 | @ViewBuilder rowBackground: @escaping RowBackground, 59 | @ViewBuilder rowOverlay: @escaping RowOverlay, 60 | results: Binding, 61 | selected: Binding) 62 | { 63 | self.config = config 64 | headerContent = header 65 | footerContent = footer 66 | rowContent = row 67 | self.rowBackground = rowBackground 68 | self.rowOverlay = rowOverlay 69 | _results = results 70 | _selected = selected 71 | _context = State(initialValue: TablerContext(config)) 72 | } 73 | 74 | // MARK: Locals 75 | 76 | @State private var context: Context 77 | 78 | // MARK: Views 79 | 80 | public var body: some View { 81 | BaseStack(context: $context, 82 | header: headerContent, 83 | footer: footerContent) 84 | { 85 | // TODO: is there a better way to filter bound data source? 86 | if let _filter = config.filter { 87 | ForEach($results) { $element in 88 | if _filter(element) { 89 | row($element) 90 | } 91 | } 92 | } else { 93 | ForEach($results) { $element in 94 | row($element) 95 | } 96 | } 97 | } 98 | } 99 | 100 | private func row(_ element: Binding) -> some View { 101 | rowContent(element) 102 | .modifier(StackRowMod1(config: config, 103 | element: element.wrappedValue, 104 | selected: $selected)) 105 | .background(rowBackground(element.wrappedValue)) 106 | .overlay(rowOverlay(element.wrappedValue)) 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /Sources/Stack/TablerStack1C.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TablerStackC.swift 3 | // 4 | // Copyright 2021, 2022 OpenAlloc LLC 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | // 18 | 19 | import SwiftUI 20 | 21 | // sourcery: AutoInit, selectBinding 22 | /// Stack-based table, with support for reference types 23 | public struct TablerStack1C: View 24 | where Element: Identifiable & ObservableObject, 25 | Header: View, 26 | Footer: View, 27 | Row: View, 28 | RowBack: View, 29 | RowOver: View, 30 | Results: RandomAccessCollection, 31 | Results.Element == Element 32 | { 33 | public typealias Config = TablerStackConfig 34 | public typealias Context = TablerContext 35 | public typealias HeaderContent = (Binding) -> Header 36 | public typealias FooterContent = (Binding) -> Footer 37 | public typealias ProjectedValue = ObservedObject.Wrapper 38 | public typealias RowContent = (ProjectedValue) -> Row 39 | public typealias RowBackground = (Element) -> RowBack 40 | public typealias RowOverlay = (Element) -> RowOver 41 | public typealias Selected = Element.ID? 42 | 43 | // MARK: Parameters 44 | 45 | private let config: Config 46 | private let headerContent: HeaderContent 47 | private let footerContent: FooterContent 48 | private let rowContent: RowContent 49 | private let rowBackground: RowBackground 50 | private let rowOverlay: RowOverlay 51 | private var results: Results 52 | @Binding private var selected: Selected 53 | 54 | public init(_ config: Config = .init(), 55 | @ViewBuilder header: @escaping HeaderContent, 56 | @ViewBuilder footer: @escaping FooterContent, 57 | @ViewBuilder row: @escaping RowContent, 58 | @ViewBuilder rowBackground: @escaping RowBackground, 59 | @ViewBuilder rowOverlay: @escaping RowOverlay, 60 | results: Results, 61 | selected: Binding) 62 | { 63 | self.config = config 64 | headerContent = header 65 | footerContent = footer 66 | rowContent = row 67 | self.rowBackground = rowBackground 68 | self.rowOverlay = rowOverlay 69 | self.results = results 70 | _selected = selected 71 | _context = State(initialValue: TablerContext(config)) 72 | } 73 | 74 | // MARK: Locals 75 | 76 | @State private var context: Context 77 | 78 | // MARK: Views 79 | 80 | public var body: some View { 81 | BaseStack(context: $context, 82 | header: headerContent, 83 | footer: footerContent) 84 | { 85 | ForEach(results) { rawElem in 86 | ObservableHolder(element: rawElem) { obsElem in 87 | rowContent(obsElem) 88 | .modifier(StackRowMod1(config: config, 89 | element: rawElem, 90 | selected: $selected)) 91 | .background(rowBackground(rawElem)) 92 | .overlay(rowOverlay(rawElem)) 93 | } 94 | } 95 | } 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /Sources/Stack/TablerStackB.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TablerStackB.swift 3 | // 4 | // Copyright 2021, 2022 OpenAlloc LLC 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | // 18 | 19 | import SwiftUI 20 | 21 | // sourcery: AutoInit, resultsBinding 22 | /// Stack-based table, with support for bound value types 23 | public struct TablerStackB: View 24 | where Element: Identifiable, 25 | Header: View, 26 | Footer: View, 27 | Row: View, 28 | RowBack: View, 29 | RowOver: View, 30 | Results: RandomAccessCollection & MutableCollection, 31 | Results.Element == Element, 32 | Results.Index: Hashable 33 | { 34 | public typealias Config = TablerStackConfig 35 | public typealias Context = TablerContext 36 | public typealias HeaderContent = (Binding) -> Header 37 | public typealias FooterContent = (Binding) -> Footer 38 | public typealias RowContent = (Binding) -> Row 39 | public typealias RowBackground = (Element) -> RowBack 40 | public typealias RowOverlay = (Element) -> RowOver 41 | 42 | // MARK: Parameters 43 | 44 | private let config: Config 45 | private let headerContent: HeaderContent 46 | private let footerContent: FooterContent 47 | private let rowContent: RowContent 48 | private let rowBackground: RowBackground 49 | private let rowOverlay: RowOverlay 50 | @Binding private var results: Results 51 | 52 | public init(_ config: Config = .init(), 53 | @ViewBuilder header: @escaping HeaderContent, 54 | @ViewBuilder footer: @escaping FooterContent, 55 | @ViewBuilder row: @escaping RowContent, 56 | @ViewBuilder rowBackground: @escaping RowBackground, 57 | @ViewBuilder rowOverlay: @escaping RowOverlay, 58 | results: Binding) 59 | { 60 | self.config = config 61 | headerContent = header 62 | footerContent = footer 63 | rowContent = row 64 | self.rowBackground = rowBackground 65 | self.rowOverlay = rowOverlay 66 | _results = results 67 | _context = State(initialValue: TablerContext(config)) 68 | } 69 | 70 | // MARK: Locals 71 | 72 | @State private var context: Context 73 | 74 | // MARK: Views 75 | 76 | public var body: some View { 77 | BaseStack(context: $context, 78 | header: headerContent, 79 | footer: footerContent) 80 | { 81 | // TODO: is there a better way to filter bound data source? 82 | if let _filter = config.filter { 83 | ForEach($results) { $element in 84 | if _filter(element) { 85 | row($element) 86 | } 87 | } 88 | } else { 89 | ForEach($results) { $element in 90 | row($element) 91 | } 92 | } 93 | } 94 | } 95 | 96 | private func row(_ element: Binding) -> some View { 97 | rowContent(element) 98 | .modifier(StackRowMod(config: config, 99 | element: element.wrappedValue)) 100 | .background(rowBackground(element.wrappedValue)) 101 | .overlay(rowOverlay(element.wrappedValue)) 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /Sources/Stack/TablerStackC.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TablerStackC.swift 3 | // 4 | // Copyright 2021, 2022 OpenAlloc LLC 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | // 18 | 19 | import SwiftUI 20 | 21 | // sourcery: AutoInit 22 | /// Stack-based table, with support for reference types 23 | public struct TablerStackC: View 24 | where Element: Identifiable & ObservableObject, 25 | Header: View, 26 | Footer: View, 27 | Row: View, 28 | RowBack: View, 29 | RowOver: View, 30 | Results: RandomAccessCollection, 31 | Results.Element == Element 32 | { 33 | public typealias Config = TablerStackConfig 34 | public typealias Context = TablerContext 35 | public typealias HeaderContent = (Binding) -> Header 36 | public typealias FooterContent = (Binding) -> Footer 37 | public typealias ProjectedValue = ObservedObject.Wrapper 38 | public typealias RowContent = (ProjectedValue) -> Row 39 | public typealias RowBackground = (Element) -> RowBack 40 | public typealias RowOverlay = (Element) -> RowOver 41 | 42 | // MARK: Parameters 43 | 44 | private let config: Config 45 | private let headerContent: HeaderContent 46 | private let footerContent: FooterContent 47 | private let rowContent: RowContent 48 | private let rowBackground: RowBackground 49 | private let rowOverlay: RowOverlay 50 | private var results: Results 51 | 52 | public init(_ config: Config = .init(), 53 | @ViewBuilder header: @escaping HeaderContent, 54 | @ViewBuilder footer: @escaping FooterContent, 55 | @ViewBuilder row: @escaping RowContent, 56 | @ViewBuilder rowBackground: @escaping RowBackground, 57 | @ViewBuilder rowOverlay: @escaping RowOverlay, 58 | results: Results) 59 | { 60 | self.config = config 61 | headerContent = header 62 | footerContent = footer 63 | rowContent = row 64 | self.rowBackground = rowBackground 65 | self.rowOverlay = rowOverlay 66 | self.results = results 67 | _context = State(initialValue: TablerContext(config)) 68 | } 69 | 70 | // MARK: Locals 71 | 72 | @State private var context: Context 73 | 74 | // MARK: Views 75 | 76 | public var body: some View { 77 | BaseStack(context: $context, 78 | header: headerContent, 79 | footer: footerContent) 80 | { 81 | ForEach(results) { rawElem in 82 | ObservableHolder(element: rawElem) { obsElem in 83 | rowContent(obsElem) 84 | .modifier(StackRowMod(config: config, 85 | element: rawElem)) 86 | .background(rowBackground(rawElem)) 87 | .overlay(rowOverlay(rawElem)) 88 | } 89 | } 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /Sources/Stack/TablerStackConfig.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TablerStackConfig.swift 3 | // 4 | // Copyright 2021, 2022 OpenAlloc LLC 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | // 18 | 19 | import SwiftUI 20 | 21 | public enum TablerStackConfigDefaults { 22 | #if os(macOS) 23 | public static let rowPadding = EdgeInsets(top: 4, leading: 0, bottom: 4, trailing: 0) 24 | #elseif os(iOS) 25 | public static let rowPadding = EdgeInsets(top: 11, leading: 0, bottom: 12, trailing: 0) 26 | #endif 27 | } 28 | 29 | public class TablerStackConfig: TablerSpacedConfig 30 | where Element: Identifiable 31 | { 32 | public let rowPadding: EdgeInsets 33 | 34 | public init(rowPadding: EdgeInsets = TablerStackConfigDefaults.rowPadding, 35 | headerSpacing: CGFloat = TablerSpacedConfigDefaults.headerSpacing, 36 | footerSpacing: CGFloat = TablerSpacedConfigDefaults.footerSpacing, 37 | rowSpacing: CGFloat = TablerSpacedConfigDefaults.rowSpacing, 38 | // headerFixed: Bool = TablerSpacedConfigDefaults.headerFixed, 39 | // footerFixed: Bool = TablerSpacedConfigDefaults.footerFixed, 40 | filter: Filter? = nil, 41 | onHover: @escaping OnHover = { _, _ in }, 42 | tablePadding: EdgeInsets = TablerSpacedConfigDefaults.tablePadding, 43 | sortIndicatorForward: AnyView = TablerConfigDefaults.sortIndicatorForward, 44 | sortIndicatorReverse: AnyView = TablerConfigDefaults.sortIndicatorReverse, 45 | sortIndicatorNeutral: AnyView = TablerConfigDefaults.sortIndicatorNeutral) 46 | { 47 | self.rowPadding = rowPadding 48 | 49 | super.init(headerSpacing: headerSpacing, 50 | footerSpacing: footerSpacing, 51 | rowSpacing: rowSpacing, 52 | // headerFixed: headerFixed, 53 | // footerFixed: footerFixed, 54 | filter: filter, 55 | onHover: onHover, 56 | tablePadding: tablePadding, 57 | sortIndicatorForward: sortIndicatorForward, 58 | sortIndicatorReverse: sortIndicatorReverse, 59 | sortIndicatorNeutral: sortIndicatorNeutral) 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /Sources/Stack/TablerStackM.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TablerStackM.swift 3 | // 4 | // Copyright 2021, 2022 OpenAlloc LLC 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | // 18 | 19 | import SwiftUI 20 | 21 | // sourcery: AutoInit, selectBinding 22 | /// Stack-based table, with support for multi-select 23 | public struct TablerStackM: View 24 | where Element: Identifiable, 25 | Header: View, 26 | Footer: View, 27 | Row: View, 28 | RowBack: View, 29 | RowOver: View, 30 | Results: RandomAccessCollection, 31 | Results.Element == Element 32 | { 33 | public typealias Config = TablerStackConfig 34 | public typealias Context = TablerContext 35 | public typealias HeaderContent = (Binding) -> Header 36 | public typealias FooterContent = (Binding) -> Footer 37 | public typealias RowContent = (Element) -> Row 38 | public typealias RowBackground = (Element) -> RowBack 39 | public typealias RowOverlay = (Element) -> RowOver 40 | public typealias Selected = Set 41 | 42 | // MARK: Parameters 43 | 44 | private let config: Config 45 | private let headerContent: HeaderContent 46 | private let footerContent: FooterContent 47 | private let rowContent: RowContent 48 | private let rowBackground: RowBackground 49 | private let rowOverlay: RowOverlay 50 | private var results: Results 51 | @Binding private var selected: Selected 52 | 53 | public init(_ config: Config = .init(), 54 | @ViewBuilder header: @escaping HeaderContent, 55 | @ViewBuilder footer: @escaping FooterContent, 56 | @ViewBuilder row: @escaping RowContent, 57 | @ViewBuilder rowBackground: @escaping RowBackground, 58 | @ViewBuilder rowOverlay: @escaping RowOverlay, 59 | results: Results, 60 | selected: Binding) 61 | { 62 | self.config = config 63 | headerContent = header 64 | footerContent = footer 65 | rowContent = row 66 | self.rowBackground = rowBackground 67 | self.rowOverlay = rowOverlay 68 | self.results = results 69 | _selected = selected 70 | _context = State(initialValue: TablerContext(config)) 71 | } 72 | 73 | // MARK: Locals 74 | 75 | @State private var context: Context 76 | 77 | // MARK: Views 78 | 79 | public var body: some View { 80 | BaseStack(context: $context, 81 | header: headerContent, 82 | footer: footerContent) 83 | { 84 | ForEach(results.filter(config.filter ?? { _ in true })) { element in 85 | rowContent(element) 86 | .modifier(StackRowModM(config: config, 87 | element: element, 88 | selected: $selected)) 89 | .background(rowBackground(element)) 90 | .overlay(rowOverlay(element)) 91 | } 92 | } 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /Sources/Stack/TablerStackMB.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TablerStackMB.swift 3 | // 4 | // Copyright 2021, 2022 OpenAlloc LLC 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | // 18 | 19 | import SwiftUI 20 | 21 | // sourcery: AutoInit, selectBinding, resultsBinding 22 | /// Stack-based table, with support for multi-select and bound value types 23 | public struct TablerStackMB: View 24 | where Element: Identifiable, 25 | Header: View, 26 | Footer: View, 27 | Row: View, 28 | RowBack: View, 29 | RowOver: View, 30 | Results: RandomAccessCollection & MutableCollection, 31 | Results.Element == Element, 32 | Results.Index: Hashable 33 | { 34 | public typealias Config = TablerStackConfig 35 | public typealias Context = TablerContext 36 | public typealias HeaderContent = (Binding) -> Header 37 | public typealias FooterContent = (Binding) -> Footer 38 | public typealias RowContent = (Binding) -> Row 39 | public typealias RowBackground = (Element) -> RowBack 40 | public typealias RowOverlay = (Element) -> RowOver 41 | public typealias Selected = Set 42 | 43 | // MARK: Parameters 44 | 45 | private let config: Config 46 | private let headerContent: HeaderContent 47 | private let footerContent: FooterContent 48 | private let rowContent: RowContent 49 | private let rowBackground: RowBackground 50 | private let rowOverlay: RowOverlay 51 | @Binding private var results: Results 52 | @Binding private var selected: Selected 53 | 54 | public init(_ config: Config = .init(), 55 | @ViewBuilder header: @escaping HeaderContent, 56 | @ViewBuilder footer: @escaping FooterContent, 57 | @ViewBuilder row: @escaping RowContent, 58 | @ViewBuilder rowBackground: @escaping RowBackground, 59 | @ViewBuilder rowOverlay: @escaping RowOverlay, 60 | results: Binding, 61 | selected: Binding) 62 | { 63 | self.config = config 64 | headerContent = header 65 | footerContent = footer 66 | rowContent = row 67 | self.rowBackground = rowBackground 68 | self.rowOverlay = rowOverlay 69 | _results = results 70 | _selected = selected 71 | _context = State(initialValue: TablerContext(config)) 72 | } 73 | 74 | // MARK: Locals 75 | 76 | @State private var context: Context 77 | 78 | // MARK: Views 79 | 80 | public var body: some View { 81 | BaseStack(context: $context, 82 | header: headerContent, 83 | footer: footerContent) 84 | { 85 | // TODO: is there a better way to filter bound data source? 86 | if let _filter = config.filter { 87 | ForEach($results) { $element in 88 | if _filter(element) { 89 | row($element) 90 | } 91 | } 92 | } else { 93 | ForEach($results) { $element in 94 | row($element) 95 | } 96 | } 97 | } 98 | } 99 | 100 | private func row(_ element: Binding) -> some View { 101 | rowContent(element) 102 | .modifier(StackRowModM(config: config, 103 | element: element.wrappedValue, 104 | selected: $selected)) 105 | .background(rowBackground(element.wrappedValue)) 106 | .overlay(rowOverlay(element.wrappedValue)) 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /Sources/Stack/TablerStackMC.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TablerStackMC.swift 3 | // 4 | // Copyright 2021, 2022 OpenAlloc LLC 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | // 18 | 19 | import SwiftUI 20 | 21 | // sourcery: AutoInit, selectBinding 22 | /// Stack-based table, with support for multi-select and reference types 23 | public struct TablerStackMC: View 24 | where Element: Identifiable & ObservableObject, 25 | Header: View, 26 | Footer: View, 27 | Row: View, 28 | RowBack: View, 29 | RowOver: View, 30 | Results: RandomAccessCollection, 31 | Results.Element == Element 32 | { 33 | public typealias Config = TablerStackConfig 34 | public typealias Context = TablerContext 35 | public typealias HeaderContent = (Binding) -> Header 36 | public typealias FooterContent = (Binding) -> Footer 37 | public typealias ProjectedValue = ObservedObject.Wrapper 38 | public typealias RowContent = (ProjectedValue) -> Row 39 | public typealias RowBackground = (Element) -> RowBack 40 | public typealias RowOverlay = (Element) -> RowOver 41 | public typealias Selected = Set 42 | 43 | // MARK: Parameters 44 | 45 | private let config: Config 46 | private let headerContent: HeaderContent 47 | private let footerContent: FooterContent 48 | private let rowContent: RowContent 49 | private let rowBackground: RowBackground 50 | private let rowOverlay: RowOverlay 51 | private var results: Results 52 | @Binding private var selected: Selected 53 | 54 | public init(_ config: Config = .init(), 55 | @ViewBuilder header: @escaping HeaderContent, 56 | @ViewBuilder footer: @escaping FooterContent, 57 | @ViewBuilder row: @escaping RowContent, 58 | @ViewBuilder rowBackground: @escaping RowBackground, 59 | @ViewBuilder rowOverlay: @escaping RowOverlay, 60 | results: Results, 61 | selected: Binding) 62 | { 63 | self.config = config 64 | headerContent = header 65 | footerContent = footer 66 | rowContent = row 67 | self.rowBackground = rowBackground 68 | self.rowOverlay = rowOverlay 69 | self.results = results 70 | _selected = selected 71 | _context = State(initialValue: TablerContext(config)) 72 | } 73 | 74 | // MARK: Locals 75 | 76 | @State private var context: Context 77 | 78 | // MARK: Views 79 | 80 | public var body: some View { 81 | BaseStack(context: $context, 82 | header: headerContent, 83 | footer: footerContent) 84 | { 85 | ForEach(results) { rawElem in 86 | ObservableHolder(element: rawElem) { obsElem in 87 | rowContent(obsElem) 88 | .modifier(StackRowModM(config: config, 89 | element: rawElem, 90 | selected: $selected)) 91 | .background(rowBackground(rawElem)) 92 | .overlay(rowOverlay(rawElem)) 93 | } 94 | } 95 | } 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /Sources/TablerConfig.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TablerBaseConfig.swift 3 | // 4 | // Copyright 2021, 2022 OpenAlloc LLC 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | // 18 | 19 | import SwiftUI 20 | 21 | public enum TablerConfigDefaults { 22 | public static let tablePadding: EdgeInsets = .init() 23 | 24 | public static let sortIndicatorForward = AnyView( 25 | Image(systemName: "chevron.up") 26 | .foregroundColor(.secondary) 27 | ) 28 | public static let sortIndicatorReverse = AnyView( 29 | Image(systemName: "chevron.down") 30 | .foregroundColor(.secondary) 31 | ) 32 | public static let sortIndicatorNeutral = AnyView( 33 | // The same width as the other two, to avoid title changing position as indicator changes. 34 | Image(systemName: "chevron.up") 35 | .opacity(0) 36 | ) 37 | } 38 | 39 | open class TablerConfig 40 | where Element: Identifiable 41 | { 42 | public typealias Filter = (Element) -> Bool 43 | public typealias OnHover = (Element.ID, Bool) -> Void 44 | 45 | public let filter: Filter? 46 | public let onHover: OnHover 47 | public let tablePadding: EdgeInsets 48 | 49 | public let sortIndicatorForward: AnyView 50 | public let sortIndicatorReverse: AnyView 51 | public let sortIndicatorNeutral: AnyView 52 | 53 | public init(filter: Filter? = nil, 54 | onHover: @escaping OnHover = { _, _ in }, 55 | tablePadding: EdgeInsets = TablerConfigDefaults.tablePadding, 56 | sortIndicatorForward: AnyView = TablerConfigDefaults.sortIndicatorForward, 57 | sortIndicatorReverse: AnyView = TablerConfigDefaults.sortIndicatorReverse, 58 | sortIndicatorNeutral: AnyView = TablerConfigDefaults.sortIndicatorNeutral) 59 | { 60 | self.filter = filter 61 | self.onHover = onHover 62 | self.tablePadding = tablePadding 63 | self.sortIndicatorForward = sortIndicatorForward 64 | self.sortIndicatorReverse = sortIndicatorReverse 65 | self.sortIndicatorNeutral = sortIndicatorNeutral 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /Sources/TablerContext.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TablerContext.swift 3 | // 4 | // Copyright 2021, 2022 OpenAlloc LLC 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | // 18 | 19 | import SwiftUI 20 | 21 | public struct TablerContext 22 | where Element: Identifiable 23 | { 24 | public let config: TablerConfig 25 | public var sort: TablerSort? 26 | 27 | public init(_ config: TablerConfig) { 28 | self.config = config 29 | sort = nil 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Sources/TablerSort.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TablerSort.swift 3 | // 4 | // Copyright 2021, 2022 OpenAlloc LLC 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | // 18 | 19 | import SwiftUI 20 | 21 | public struct TablerSort 22 | where Element: Identifiable 23 | { 24 | public enum Direction { 25 | case forward 26 | case reverse 27 | case undefined 28 | 29 | public static func flipped(_ direction: TablerSort.Direction) -> Direction { 30 | direction == .forward ? .reverse : (direction == .reverse ? .forward : .undefined) 31 | } 32 | } 33 | 34 | // MARK: Parameters 35 | 36 | public let anyKeyPath: AnyKeyPath 37 | public let direction: Direction 38 | 39 | public init(_ anyKeyPath: AnyKeyPath, _ ascending: Direction) { 40 | self.anyKeyPath = anyKeyPath 41 | direction = ascending 42 | } 43 | 44 | // MARK: basic indicator 45 | 46 | public static func indicator(_ ctx: Binding>, _ keyPath: KeyPath) -> some View { 47 | let direction: TablerSort.Direction = { 48 | guard ctx.wrappedValue.sort?.anyKeyPath == keyPath else { 49 | return .undefined 50 | } 51 | return ctx.wrappedValue.sort?.direction ?? .undefined 52 | }() 53 | 54 | let image: AnyView = { 55 | switch direction { 56 | case .forward: 57 | return ctx.wrappedValue.config.sortIndicatorForward 58 | case .reverse: 59 | return ctx.wrappedValue.config.sortIndicatorReverse 60 | case .undefined: 61 | return ctx.wrappedValue.config.sortIndicatorNeutral 62 | } 63 | }() 64 | 65 | return image 66 | } 67 | 68 | // MARK: convenience methods 69 | 70 | /// Simple "Title " view generation, implemented as an HStack {} 71 | public static func columnTitle(_ title: String, _ ctx: Binding>, _ keyPath: KeyPath) -> some View { 72 | HStack { 73 | Text(title) 74 | Spacer() 75 | indicator(ctx, keyPath) 76 | } 77 | .contentShape(Rectangle()) // NOTE to support tap area across entire title view 78 | } 79 | } 80 | 81 | extension View { 82 | /// RandomAccessCollection support 83 | public func tablerSort(_ ctx: Binding>, 84 | _ results: inout Results, 85 | _ keyPath: KeyPath, 86 | _ onSort: (Element, Element) -> Bool) 87 | where Results: RandomAccessCollection & MutableCollection, 88 | Results.Element == Element 89 | { 90 | let otherDirection = updateSort(ctx, keyPath) 91 | 92 | if otherDirection == .forward { 93 | results.sort(by: onSort) 94 | } else { 95 | results.sort(by: { !onSort($0, $1) }) 96 | } 97 | } 98 | 99 | func updateSort(_ ctx: Binding>, 100 | _ keyPath: KeyPath) -> TablerSort.Direction 101 | { 102 | // NOTE type-erase to track sort state 103 | let anyKeyPath: AnyKeyPath = keyPath 104 | 105 | let origDirection: TablerSort.Direction = { 106 | if anyKeyPath == ctx.wrappedValue.sort?.anyKeyPath { 107 | return ctx.wrappedValue.sort?.direction ?? .reverse 108 | } 109 | return .reverse 110 | }() 111 | 112 | let otherDirection = TablerSort.Direction.flipped(origDirection) 113 | 114 | // store the new direction for future header taps for the field 115 | ctx.wrappedValue.sort = TablerSort(anyKeyPath, otherDirection) 116 | 117 | return otherDirection 118 | } 119 | } 120 | 121 | @available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) 122 | public extension View { 123 | /// Core-data support 124 | func tablerSort(_ c: Binding>, _ k: KeyPath) -> SortDescriptor { SortDescriptor(k, order: xlat(updateSort(c, k))) } 125 | func tablerSort(_ c: Binding>, _ k: KeyPath) -> SortDescriptor { SortDescriptor(k, order: xlat(updateSort(c, k))) } 126 | func tablerSort(_ c: Binding>, _ k: KeyPath) -> SortDescriptor { SortDescriptor(k, order: xlat(updateSort(c, k))) } 127 | func tablerSort(_ c: Binding>, _ k: KeyPath) -> SortDescriptor { SortDescriptor(k, order: xlat(updateSort(c, k))) } 128 | func tablerSort(_ c: Binding>, _ k: KeyPath) -> SortDescriptor { SortDescriptor(k, order: xlat(updateSort(c, k))) } 129 | func tablerSort(_ c: Binding>, _ k: KeyPath) -> SortDescriptor { SortDescriptor(k, order: xlat(updateSort(c, k))) } 130 | func tablerSort(_ c: Binding>, _ k: KeyPath) -> SortDescriptor { SortDescriptor(k, order: xlat(updateSort(c, k))) } 131 | func tablerSort(_ c: Binding>, _ k: KeyPath) -> SortDescriptor { SortDescriptor(k, order: xlat(updateSort(c, k))) } 132 | func tablerSort(_ c: Binding>, _ k: KeyPath) -> SortDescriptor { SortDescriptor(k, order: xlat(updateSort(c, k))) } 133 | func tablerSort(_ c: Binding>, _ k: KeyPath) -> SortDescriptor { SortDescriptor(k, order: xlat(updateSort(c, k))) } 134 | func tablerSort(_ c: Binding>, _ k: KeyPath) -> SortDescriptor { SortDescriptor(k, order: xlat(updateSort(c, k))) } 135 | func tablerSort(_ c: Binding>, _ k: KeyPath) -> SortDescriptor { SortDescriptor(k, order: xlat(updateSort(c, k))) } 136 | func tablerSort(_ c: Binding>, _ k: KeyPath) -> SortDescriptor { SortDescriptor(k, order: xlat(updateSort(c, k))) } 137 | func tablerSort(_ c: Binding>, _ k: KeyPath) -> SortDescriptor { SortDescriptor(k, order: xlat(updateSort(c, k))) } 138 | func tablerSort(_ c: Binding>, _ k: KeyPath) -> SortDescriptor { SortDescriptor(k, order: xlat(updateSort(c, k))) } 139 | func tablerSort(_ c: Binding>, _ k: KeyPath) -> SortDescriptor { SortDescriptor(k, order: xlat(updateSort(c, k))) } 140 | func tablerSort(_ c: Binding>, _ k: KeyPath) -> SortDescriptor { SortDescriptor(k, order: xlat(updateSort(c, k))) } 141 | func tablerSort(_ c: Binding>, _ k: KeyPath) -> SortDescriptor { SortDescriptor(k, order: xlat(updateSort(c, k))) } 142 | func tablerSort(_ c: Binding>, _ k: KeyPath) -> SortDescriptor { SortDescriptor(k, order: xlat(updateSort(c, k))) } 143 | func tablerSort(_ c: Binding>, _ k: KeyPath) -> SortDescriptor { SortDescriptor(k, order: xlat(updateSort(c, k))) } 144 | func tablerSort(_ c: Binding>, _ k: KeyPath) -> SortDescriptor { SortDescriptor(k, order: xlat(updateSort(c, k))) } 145 | func tablerSort(_ c: Binding>, _ k: KeyPath) -> SortDescriptor { SortDescriptor(k, order: xlat(updateSort(c, k))) } 146 | func tablerSort(_ c: Binding>, _ k: KeyPath) -> SortDescriptor { SortDescriptor(k, order: xlat(updateSort(c, k))) } 147 | func tablerSort(_ c: Binding>, _ k: KeyPath) -> SortDescriptor { SortDescriptor(k, order: xlat(updateSort(c, k))) } 148 | func tablerSort(_ c: Binding>, _ k: KeyPath) -> SortDescriptor { SortDescriptor(k, order: xlat(updateSort(c, k))) } 149 | func tablerSort(_ c: Binding>, _ k: KeyPath) -> SortDescriptor { SortDescriptor(k, order: xlat(updateSort(c, k))) } 150 | func tablerSort(_ c: Binding>, _ k: KeyPath) -> SortDescriptor { SortDescriptor(k, order: xlat(updateSort(c, k))) } 151 | func tablerSort(_ c: Binding>, _ k: KeyPath) -> SortDescriptor { SortDescriptor(k, order: xlat(updateSort(c, k))) } 152 | func tablerSort(_ c: Binding>, _ k: KeyPath) -> SortDescriptor { SortDescriptor(k, order: xlat(updateSort(c, k))) } 153 | func tablerSort(_ c: Binding>, _ k: KeyPath) -> SortDescriptor { SortDescriptor(k, order: xlat(updateSort(c, k))) } 154 | func tablerSort(_ c: Binding>, _ k: KeyPath) -> SortDescriptor { SortDescriptor(k, order: xlat(updateSort(c, k))) } 155 | func tablerSort(_ c: Binding>, _ k: KeyPath) -> SortDescriptor { SortDescriptor(k, order: xlat(updateSort(c, k))) } 156 | 157 | private func xlat(_ direction: TablerSort.Direction) -> SortOrder { 158 | direction == .forward ? SortOrder.forward : SortOrder.reverse 159 | } 160 | } 161 | --------------------------------------------------------------------------------