├── .github └── workflows │ └── azure-static-web-apps-mango-flower-090e9130f.yml ├── .gitignore ├── App.razor ├── BlazorSortable.csproj ├── BlazorSortable.sln ├── Item.cs ├── LICENSE ├── MainLayout.razor ├── Pages └── Index.razor ├── Program.cs ├── Properties └── launchSettings.json ├── README.MD ├── Shared ├── Demos │ ├── Cloning.razor │ ├── DisablingSorting.razor │ ├── DragHandles.razor │ ├── Filtering.razor │ ├── Filtering.razor.css │ ├── ForceFallback.razor │ ├── SharedLists.razor │ ├── SimpleList.razor │ └── Tabs.razor ├── SortableList.razor ├── SortableList.razor.css └── SortableList.razor.js ├── _Imports.razor └── wwwroot ├── css └── app.css └── index.html /.github/workflows/azure-static-web-apps-mango-flower-090e9130f.yml: -------------------------------------------------------------------------------- 1 | name: Azure Static Web Apps CI/CD 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | types: [opened, synchronize, reopened, closed] 9 | branches: 10 | - main 11 | 12 | jobs: 13 | build_and_deploy_job: 14 | if: github.event_name == 'push' || (github.event_name == 'pull_request' && github.event.action != 'closed') 15 | runs-on: ubuntu-latest 16 | name: Build and Deploy Job 17 | steps: 18 | - uses: actions/checkout@v3 19 | with: 20 | submodules: true 21 | lfs: false 22 | - name: Build And Deploy 23 | id: builddeploy 24 | uses: Azure/static-web-apps-deploy@v1 25 | with: 26 | azure_static_web_apps_api_token: ${{ secrets.AZURE_STATIC_WEB_APPS_API_TOKEN_MANGO_FLOWER_090E9130F }} 27 | repo_token: ${{ secrets.GITHUB_TOKEN }} # Used for Github integrations (i.e. PR comments) 28 | action: "upload" 29 | ###### Repository/Build Configurations - These values can be configured to match your app requirements. ###### 30 | # For more information regarding Static Web App workflow configurations, please visit: https://aka.ms/swaworkflowconfig 31 | app_location: "/" # App source code path 32 | api_location: "Api" # Api source code path - optional 33 | output_location: "wwwroot" # Built app content directory - optional 34 | ###### End of Repository/Build Configurations ###### 35 | 36 | close_pull_request_job: 37 | if: github.event_name == 'pull_request' && github.event.action == 'closed' 38 | runs-on: ubuntu-latest 39 | name: Close Pull Request Job 40 | steps: 41 | - name: Close Pull Request 42 | id: closepullrequest 43 | uses: Azure/static-web-apps-deploy@v1 44 | with: 45 | azure_static_web_apps_api_token: ${{ secrets.AZURE_STATIC_WEB_APPS_API_TOKEN_MANGO_FLOWER_090E9130F }} 46 | action: "close" 47 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.rsuser 8 | *.suo 9 | *.user 10 | *.userosscache 11 | *.sln.docstates 12 | 13 | # User-specific files (MonoDevelop/Xamarin Studio) 14 | *.userprefs 15 | 16 | # Build results 17 | [Dd]ebug/ 18 | [Dd]ebugPublic/ 19 | [Rr]elease/ 20 | [Rr]eleases/ 21 | x64/ 22 | x86/ 23 | [Aa][Rr][Mm]/ 24 | [Aa][Rr][Mm]64/ 25 | bld/ 26 | [Bb]in/ 27 | [Oo]bj/ 28 | [Ll]og/ 29 | 30 | # Visual Studio 2015/2017 cache/options directory 31 | .vs/ 32 | # Uncomment if you have tasks that create the project's static files in wwwroot 33 | #wwwroot/ 34 | 35 | # Visual Studio 2017 auto generated files 36 | Generated\ Files/ 37 | 38 | # MSTest test Results 39 | [Tt]est[Rr]esult*/ 40 | [Bb]uild[Ll]og.* 41 | 42 | # NUNIT 43 | *.VisualState.xml 44 | TestResult.xml 45 | 46 | # Build Results of an ATL Project 47 | [Dd]ebugPS/ 48 | [Rr]eleasePS/ 49 | dlldata.c 50 | 51 | # Benchmark Results 52 | BenchmarkDotNet.Artifacts/ 53 | 54 | # .NET Core 55 | project.lock.json 56 | project.fragment.lock.json 57 | artifacts/ 58 | 59 | # StyleCop 60 | StyleCopReport.xml 61 | 62 | # Files built by Visual Studio 63 | obj 64 | bin 65 | *_i.c 66 | *_p.c 67 | *_h.h 68 | *.ilk 69 | *.meta 70 | *.obj 71 | *.iobj 72 | *.pch 73 | *.pdb 74 | *.ipdb 75 | *.pgc 76 | *.pgd 77 | *.rsp 78 | *.sbr 79 | *.tlb 80 | *.tli 81 | *.tlh 82 | *.tmp 83 | *.tmp_proj 84 | *_wpftmp.csproj 85 | *.log 86 | *.vspscc 87 | *.vssscc 88 | .builds 89 | *.pidb 90 | *.svclog 91 | *.scc 92 | 93 | # Chutzpah Test files 94 | _Chutzpah* 95 | 96 | # Visual C++ cache files 97 | ipch/ 98 | *.aps 99 | *.ncb 100 | *.opendb 101 | *.opensdf 102 | *.sdf 103 | *.cachefile 104 | *.VC.db 105 | *.VC.VC.opendb 106 | 107 | # Visual Studio profiler 108 | *.psess 109 | *.vsp 110 | *.vspx 111 | *.sap 112 | 113 | # Visual Studio Trace Files 114 | *.e2e 115 | 116 | # TFS 2012 Local Workspace 117 | $tf/ 118 | 119 | # Guidance Automation Toolkit 120 | *.gpState 121 | 122 | # ReSharper is a .NET coding add-in 123 | _ReSharper*/ 124 | *.[Rr]e[Ss]harper 125 | *.DotSettings.user 126 | 127 | # JustCode is a .NET coding add-in 128 | .JustCode 129 | 130 | # TeamCity is a build add-in 131 | _TeamCity* 132 | 133 | # DotCover is a Code Coverage Tool 134 | *.dotCover 135 | 136 | # AxoCover is a Code Coverage Tool 137 | .axoCover/* 138 | !.axoCover/settings.json 139 | 140 | # Visual Studio code coverage results 141 | *.coverage 142 | *.coveragexml 143 | 144 | # NCrunch 145 | _NCrunch_* 146 | .*crunch*.local.xml 147 | nCrunchTemp_* 148 | 149 | # MightyMoose 150 | *.mm.* 151 | AutoTest.Net/ 152 | 153 | # Web workbench (sass) 154 | .sass-cache/ 155 | 156 | # Installshield output folder 157 | [Ee]xpress/ 158 | 159 | # DocProject is a documentation generator add-in 160 | DocProject/buildhelp/ 161 | DocProject/Help/*.HxT 162 | DocProject/Help/*.HxC 163 | DocProject/Help/*.hhc 164 | DocProject/Help/*.hhk 165 | DocProject/Help/*.hhp 166 | DocProject/Help/Html2 167 | DocProject/Help/html 168 | 169 | # Click-Once directory 170 | publish/ 171 | 172 | # Publish Web Output 173 | *.[Pp]ublish.xml 174 | *.azurePubxml 175 | # Note: Comment the next line if you want to checkin your web deploy settings, 176 | # but database connection strings (with potential passwords) will be unencrypted 177 | *.pubxml 178 | *.publishproj 179 | 180 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 181 | # checkin your Azure Web App publish settings, but sensitive information contained 182 | # in these scripts will be unencrypted 183 | PublishScripts/ 184 | 185 | # NuGet Packages 186 | *.nupkg 187 | # The packages folder can be ignored because of Package Restore 188 | **/[Pp]ackages/* 189 | # except build/, which is used as an MSBuild target. 190 | !**/[Pp]ackages/build/ 191 | # Uncomment if necessary however generally it will be regenerated when needed 192 | #!**/[Pp]ackages/repositories.config 193 | # NuGet v3's project.json files produces more ignorable files 194 | *.nuget.props 195 | *.nuget.targets 196 | 197 | # Microsoft Azure Build Output 198 | csx/ 199 | *.build.csdef 200 | 201 | # Microsoft Azure Emulator 202 | ecf/ 203 | rcf/ 204 | 205 | # Windows Store app package directories and files 206 | AppPackages/ 207 | BundleArtifacts/ 208 | Package.StoreAssociation.xml 209 | _pkginfo.txt 210 | *.appx 211 | 212 | # Visual Studio cache files 213 | # files ending in .cache can be ignored 214 | *.[Cc]ache 215 | # but keep track of directories ending in .cache 216 | !?*.[Cc]ache/ 217 | 218 | # Others 219 | ClientBin/ 220 | ~$* 221 | *~ 222 | *.dbmdl 223 | *.dbproj.schemaview 224 | *.jfm 225 | *.pfx 226 | *.publishsettings 227 | orleans.codegen.cs 228 | 229 | # Including strong name files can present a security risk 230 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 231 | #*.snk 232 | 233 | # Since there are multiple workflows, uncomment next line to ignore bower_components 234 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 235 | #bower_components/ 236 | 237 | # RIA/Silverlight projects 238 | Generated_Code/ 239 | 240 | # Backup & report files from converting an old project file 241 | # to a newer Visual Studio version. Backup files are not needed, 242 | # because we have git ;-) 243 | _UpgradeReport_Files/ 244 | Backup*/ 245 | UpgradeLog*.XML 246 | UpgradeLog*.htm 247 | ServiceFabricBackup/ 248 | *.rptproj.bak 249 | 250 | # SQL Server files 251 | *.mdf 252 | *.ldf 253 | *.ndf 254 | 255 | # Business Intelligence projects 256 | *.rdl.data 257 | *.bim.layout 258 | *.bim_*.settings 259 | *.rptproj.rsuser 260 | *- Backup*.rdl 261 | 262 | # Microsoft Fakes 263 | FakesAssemblies/ 264 | 265 | # GhostDoc plugin setting file 266 | *.GhostDoc.xml 267 | 268 | # Node.js Tools for Visual Studio 269 | .ntvs_analysis.dat 270 | node_modules/ 271 | 272 | # Visual Studio 6 build log 273 | *.plg 274 | 275 | # Visual Studio 6 workspace options file 276 | *.opt 277 | 278 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 279 | *.vbw 280 | 281 | # Visual Studio LightSwitch build output 282 | **/*.HTMLClient/GeneratedArtifacts 283 | **/*.DesktopClient/GeneratedArtifacts 284 | **/*.DesktopClient/ModelManifest.xml 285 | **/*.Server/GeneratedArtifacts 286 | **/*.Server/ModelManifest.xml 287 | _Pvt_Extensions 288 | 289 | # Paket dependency manager 290 | .paket/paket.exe 291 | paket-files/ 292 | 293 | # FAKE - F# Make 294 | .fake/ 295 | 296 | # JetBrains Rider 297 | .idea/ 298 | *.sln.iml 299 | 300 | # CodeRush personal settings 301 | .cr/personal 302 | 303 | # Python Tools for Visual Studio (PTVS) 304 | __pycache__/ 305 | *.pyc 306 | 307 | # Cake - Uncomment if you are using it 308 | # tools/** 309 | # !tools/packages.config 310 | 311 | # Tabs Studio 312 | *.tss 313 | 314 | # Telerik's JustMock configuration file 315 | *.jmconfig 316 | 317 | # BizTalk build output 318 | *.btp.cs 319 | *.btm.cs 320 | *.odx.cs 321 | *.xsd.cs 322 | 323 | # OpenCover UI analysis results 324 | OpenCover/ 325 | 326 | # Azure Stream Analytics local run output 327 | ASALocalRun/ 328 | 329 | # MSBuild Binary and Structured Log 330 | *.binlog 331 | 332 | # NVidia Nsight GPU debugger configuration file 333 | *.nvuser 334 | 335 | # MFractors (Xamarin productivity tool) working folder 336 | .mfractor/ 337 | 338 | # Local History for Visual Studio 339 | .localhistory/ 340 | 341 | # BeatPulse healthcheck temp database 342 | healthchecksdb 343 | /test-results/ 344 | /playwright-report/ 345 | /playwright/.cache/ 346 | -------------------------------------------------------------------------------- /App.razor: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | Not found 8 | 9 |

Sorry, there's nothing at this address.

10 |
11 |
12 |
13 | -------------------------------------------------------------------------------- /BlazorSortable.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net7.0 5 | enable 6 | enable 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /BlazorSortable.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.5.002.0 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BlazorSortable", "BlazorSortable.csproj", "{41997150-7264-49CD-AB05-B7E9C34C2A7D}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {41997150-7264-49CD-AB05-B7E9C34C2A7D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {41997150-7264-49CD-AB05-B7E9C34C2A7D}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {41997150-7264-49CD-AB05-B7E9C34C2A7D}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {41997150-7264-49CD-AB05-B7E9C34C2A7D}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | GlobalSection(ExtensibilityGlobals) = postSolution 23 | SolutionGuid = {8BC5FBD3-9B58-42CE-A559-156D5A0D0791} 24 | EndGlobalSection 25 | EndGlobal 26 | -------------------------------------------------------------------------------- /Item.cs: -------------------------------------------------------------------------------- 1 | namespace BlazorSortable; 2 | 3 | 4 | public class Item 5 | { 6 | public int Id { get; set; } 7 | public string Name { get; set; } = ""; 8 | 9 | public bool Disabled { get; set; } = false; 10 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 The Urlist 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /MainLayout.razor: -------------------------------------------------------------------------------- 1 | @inherits LayoutComponentBase 2 | 3 |
4 | @Body 5 |
6 | -------------------------------------------------------------------------------- /Pages/Index.razor: -------------------------------------------------------------------------------- 1 | @page "/" 2 | 3 | @using BlazorSortable.Shared.Demos 4 | 5 | @inject IJSRuntime JS 6 | 7 |
8 |
9 |
10 |

Blazor Sortable

11 |

An integration of the SortableJS library with Blazor.

12 |
13 | 14 | 15 | 16 | 17 | 18 | View on GitHub 19 | 20 | 21 |
22 |
23 | 24 |
25 |
26 | 27 |
28 |
29 | 30 |
31 |
32 | 33 |
34 |
35 | 36 |
37 |
38 | 39 |
40 |
41 | 42 |
43 |
-------------------------------------------------------------------------------- /Program.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Components.Web; 2 | using Microsoft.AspNetCore.Components.WebAssembly.Hosting; 3 | using BlazorSortable; 4 | 5 | var builder = WebAssemblyHostBuilder.CreateDefault(args); 6 | builder.RootComponents.Add("#app"); 7 | builder.RootComponents.Add("head::after"); 8 | 9 | builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) }); 10 | 11 | await builder.Build().RunAsync(); 12 | -------------------------------------------------------------------------------- /Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "iisExpress": { 4 | "applicationUrl": "http://localhost:14975", 5 | "sslPort": 44313 6 | } 7 | }, 8 | "profiles": { 9 | "http": { 10 | "commandName": "Project", 11 | "dotnetRunMessages": true, 12 | "launchBrowser": true, 13 | "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}", 14 | "applicationUrl": "http://localhost:5219", 15 | "environmentVariables": { 16 | "ASPNETCORE_ENVIRONMENT": "Development" 17 | } 18 | }, 19 | "https": { 20 | "commandName": "Project", 21 | "dotnetRunMessages": true, 22 | "launchBrowser": true, 23 | "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}", 24 | "applicationUrl": "https://localhost:7263;http://localhost:5219", 25 | "environmentVariables": { 26 | "ASPNETCORE_ENVIRONMENT": "Development" 27 | } 28 | }, 29 | "IIS Express": { 30 | "commandName": "IISExpress", 31 | "launchBrowser": true, 32 | "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}", 33 | "environmentVariables": { 34 | "ASPNETCORE_ENVIRONMENT": "Development" 35 | } 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /README.MD: -------------------------------------------------------------------------------- 1 | # Blazor Sortable 2 | 3 | An implementation of the [SortableJS library](https://sortablejs.github.io/Sortable/) for Blazor. This example shows you how to reorder elements within a list using drag and drop. 4 | 5 | [View Demos](https://mango-flower-090e9130f.4.azurestaticapps.net/) 6 | 7 | ## Prerequisites 8 | 9 | - [dotnet 7](https://dotnet.microsoft.com/download/dotnet/7.0) 10 | 11 | ## Running Locally 12 | 13 | 1. Clone this repository. 14 | 1. Run the project using `dotnet watch`. 15 | 16 | ## How to use it in your own project 17 | 18 | 1. Add SortableJS to your `index.html` file. You can either download from the [SortableJS website](https://sortablejs.github.io/Sortable/) or use a CDN... 19 | 20 | 21 | 22 | 1. Add the `SortableList.razor`, `SortableList.razor.css` and `SortableList.razor.js` files to your project. 23 | 1. Add the SortableList component to your page and define the SortableItemTemplate... 24 | 25 | 26 | 27 |
28 |

@item.Name

29 |
30 |
31 |
32 | 33 | - Items: The list of items to be displayed. Can be of any type. 34 | - OnUpdate: The method to be called when the list is updated. 35 | - Context: The name of the variable to be used in the template. 36 | 37 | The SortableItemTemplate can contain any markup or components that you want. 38 | 39 | ## API 40 | 41 | ### Properties 42 | 43 | All proerties are optional and have default values. 44 | 45 | `Id`: String (Default: GUID): The id of the HTML element that will be turned into a sortable. If you don't supply this, a random id (GUID) will be generated for you. 46 | 47 | `Group`: String (Default: null) - Used for dragging between lists. Set the group to the same value on both lists to enable. 48 | 49 | `Pull`: String (Default: null) - Used when you want to clone elments into a list instead of moving them. Set `Pull` to "clone". 50 | 51 | `Put`: Boolean (Default: True) - Set to false to disable dropping from another list onto the current list. 52 | 53 | `Sort`: Boolean (Default: True) - Disable sorting within a list by setting to `false`. 54 | 55 | `Handle` (Default: ""): The CSS class you want the sortable to respect as the drag handle. 56 | 57 | `Filter` (Default: ""): The CSS class you want to use to identify elements that cannot be sorted or moved. 58 | 59 | `ForceFallback`: Boolean (Default: True) - Enable HTML5 drag and drop by setting to `false`. 60 | 61 | ### Methods 62 | 63 | `OnUpdate` (Optional): The method to be called when the list is updated. You can name this method whatever you like, but it expects a `oldIndex` and `newIndex` parameters when the drag and drop occurs. 64 | 65 | `OnRemove` (Optional): The method to be called when an item is removed from a list. This method is useful for handling the drop even between multiple lists. Like `OnUpdate`, it expects `oldIndex` and `newIndex` parameters. 66 | 67 | You will need to handle all events yourself. **If you don't handle any events, no sort or move will happen** as Blazor needs to make the changes to the underlying data structures so it can re-render the list. 68 | 69 | Here is an example of how to reorder your list when the OnUpdate is fired... 70 | 71 | private void SortList((int oldIndex, int newIndex) indices) 72 | { 73 | var (oldIndex, newIndex) = indices; 74 | 75 | var items = this.items; 76 | var itemToMove = items[oldIndex]; 77 | items.RemoveAt(oldIndex); 78 | 79 | if (newIndex < items.Count) 80 | { 81 | items.Insert(newIndex, itemToMove); 82 | } 83 | else 84 | { 85 | items.Add(itemToMove); 86 | } 87 | 88 | StateHasChanged(); 89 | } 90 | -------------------------------------------------------------------------------- /Shared/Demos/Cloning.razor: -------------------------------------------------------------------------------- 1 |
2 |

Cloning

3 |

Cloning is enabled by the "Pull='Clone'" property. This allows cloning of an item by dropping it into a shared list.

4 | 5 | 6 |
7 |
8 | 9 | 10 |
11 |

@item.Name

12 |
13 |
14 |
15 |
16 |
17 | 18 | 19 |
20 |

@item.Name

21 | 22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 | 31 | @code { 32 | 33 | private string codeContent = $@" 34 | 35 | 36 |
37 |

@item.Name

38 |
39 |
40 |
41 | 42 | 43 |
44 |

@item.Name

45 |
46 |
47 |
48 | 49 | @code {{ 50 | private void ListOneRemove((int oldIndex, int newIndex) indices) 51 | {{ 52 | var item = items1[indices.oldIndex]; 53 | var clone = item; 54 | 55 | items1.Remove(items1[indices.oldIndex]); 56 | }} 57 | 58 | private void ListTwoRemove((int oldIndex, int newIndex) indices) 59 | {{ 60 | var item = items2[indices.oldIndex]; 61 | var clone = item; 62 | 63 | items2.Remove(items2[indices.oldIndex]); 64 | }} 65 | }} 66 | "; 67 | 68 | public List items1 = Enumerable.Range(1, 10).Select(i => new Item { Id = i, Name = $"Item {i}" }).ToList(); 69 | 70 | public List items2 = Enumerable.Range(11, 10).Select(i => new Item { Id = i, Name = $"Item {i}" }).ToList(); 71 | 72 | private void ListOneRemove((int oldIndex, int newIndex) indices) 73 | { 74 | // get the item at the old index in list 1 75 | var item = items1[indices.oldIndex]; 76 | 77 | var clone = item; 78 | 79 | // add it to the new index in list 2 80 | items2.Insert(indices.newIndex, clone); 81 | } 82 | 83 | private void ListTwoRemove((int oldIndex, int newIndex) indices) 84 | { 85 | // get the item at the old index in list 2 86 | var item = items2[indices.oldIndex]; 87 | 88 | // make a copy 89 | var clone = item; 90 | 91 | // add it to the new index in list 1 92 | items1.Insert(indices.newIndex, clone); 93 | } 94 | 95 | } 96 | -------------------------------------------------------------------------------- /Shared/Demos/DisablingSorting.razor: -------------------------------------------------------------------------------- 1 |
2 |

Disabling Sorting

3 |

You can disable sorting with the `Sort` option set to `false`. You can also disable dropping items on a list by setting the `Put` to `false`. In the example below, you can drag from list 1 to list 2, but not from list 2 to list 1. You can sort list 2, but not list 1.

4 | 5 | 6 |
7 |
8 | 9 | 10 |
11 |

@item.Name

12 |
13 |
14 |
15 |
16 |
17 | 18 | 19 |
20 |

@item.Name

21 | 22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 | 31 | @code { 32 | private string codeContent = @" 33 | 34 | 35 |
36 |

@item.Name

37 |
38 |
39 |
40 | 41 | 42 | 43 |
44 |

@item.Name

45 | 46 |
47 |
48 |
49 | 50 | @code {{ 51 | private void ListOneRemove((int oldIndex, int newIndex) indices) 52 | {{ 53 | var item = items1[indices.oldIndex]; 54 | 55 | var clone = item; 56 | 57 | items2.Insert(indices.newIndex, clone); 58 | }} 59 | 60 | private void SortList((int oldIndex, int newIndex) indices) 61 | {{ 62 | // deconstruct the tuple 63 | var (oldIndex, newIndex) = indices; 64 | 65 | var items = this.items2; 66 | var itemToMove = items[oldIndex]; 67 | items.RemoveAt(oldIndex); 68 | 69 | if (newIndex < items2.Count) 70 | {{ 71 | items.Insert(newIndex, itemToMove); 72 | }} 73 | else 74 | {{ 75 | items.Add(itemToMove); 76 | }} 77 | 78 | StateHasChanged(); 79 | }} 80 | }}"; 81 | 82 | public List items1 = Enumerable.Range(1, 10).Select(i => new Item { Id = i, Name = $"Item {i}" }).ToList(); 83 | 84 | public List items2 = Enumerable.Range(11, 10).Select(i => new Item { Id = i, Name = $"Item {i}" }).ToList(); 85 | 86 | private void ListOneRemove((int oldIndex, int newIndex) indices) 87 | { 88 | var item = items1[indices.oldIndex]; 89 | 90 | var clone = item; 91 | 92 | items2.Insert(indices.newIndex, clone); 93 | } 94 | 95 | private void SortList((int oldIndex, int newIndex) indices) 96 | { 97 | // deconstruct the tuple 98 | var (oldIndex, newIndex) = indices; 99 | 100 | var items = this.items2; 101 | var itemToMove = items[oldIndex]; 102 | items.RemoveAt(oldIndex); 103 | 104 | if (newIndex < items2.Count) 105 | { 106 | items.Insert(newIndex, itemToMove); 107 | } 108 | else 109 | { 110 | items.Add(itemToMove); 111 | } 112 | 113 | StateHasChanged(); 114 | } 115 | 116 | } -------------------------------------------------------------------------------- /Shared/Demos/DragHandles.razor: -------------------------------------------------------------------------------- 1 |
2 |

Drag Handles

3 |

Drag handles are specified by the `Handle` property which specifies which CSS class denotes the drag handle. In the example below, the items can only be sorted using the drag handle itself.

4 | 5 | 6 |
7 |
8 | 9 | 10 |
11 |
12 |
13 | 14 |
15 |
16 |
17 |
18 |

@item.Name

19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 | 30 | @code { 31 | 32 | private string codeContent = @" 33 | 34 | 35 |
36 |
37 |
38 | 39 |
40 |
41 |
42 |
43 |

@item.Name

44 |
45 |
46 |
47 |
48 |
49 | 50 | @code {{ 51 | private void SortList((int oldIndex, int newIndex) indices) 52 | {{ 53 | // deconstruct the tuple 54 | var (oldIndex, newIndex) = indices; 55 | 56 | var items = this.items; 57 | var itemToMove = items[oldIndex]; 58 | items.RemoveAt(oldIndex); 59 | 60 | if (newIndex < items.Count) 61 | {{ 62 | items.Insert(newIndex, itemToMove); 63 | }} 64 | else 65 | {{ 66 | items.Add(itemToMove); 67 | }} 68 | 69 | StateHasChanged(); 70 | }} 71 | }}"; 72 | 73 | public List items = Enumerable.Range(1, 10).Select(i => new Item { Id = i, Name = $"Item {i}" }).ToList(); 74 | 75 | private void SortList((int oldIndex, int newIndex) indices) 76 | { 77 | // deconstruct the tuple 78 | var (oldIndex, newIndex) = indices; 79 | 80 | var items = this.items; 81 | var itemToMove = items[oldIndex]; 82 | items.RemoveAt(oldIndex); 83 | 84 | if (newIndex < items.Count) 85 | { 86 | items.Insert(newIndex, itemToMove); 87 | } 88 | else 89 | { 90 | items.Add(itemToMove); 91 | } 92 | 93 | StateHasChanged(); 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /Shared/Demos/Filtering.razor: -------------------------------------------------------------------------------- 1 |
2 |

Filtering

3 |

In the list below, you cannot drag the item in red. This is because it is filtered out with a filter CSS class. Pass the name of this class to the `Filter` property to filter out an item.

4 | 5 | 6 |
7 |
8 | 9 | 10 |
11 |

@item.Name

12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 | 21 | @code { 22 | 23 | private string codeContent = $@" 24 | 25 | 26 |
27 |

@item.Name

28 |
29 |
30 |
31 | 32 | @code {{ 33 | private void SortList((int oldIndex, int newIndex) indices) 34 | {{ 35 | // deconstruct the tuple 36 | var (oldIndex, newIndex) = indices; 37 | 38 | var items = this.items; 39 | var itemToMove = items[oldIndex]; 40 | items.RemoveAt(oldIndex); 41 | 42 | if (newIndex < items.Count) 43 | {{ 44 | items.Insert(newIndex, itemToMove); 45 | }} 46 | else 47 | {{ 48 | items.Add(itemToMove); 49 | }} 50 | 51 | StateHasChanged(); 52 | }} 53 | }}"; 54 | 55 | public List items = Enumerable.Range(1, 10).Select(i => new Item { Id = i, Name = $"Item {i}" }).ToList(); 56 | 57 | // on initialized, set a random item in the list to disabled 58 | protected override void OnInitialized() 59 | { 60 | var random = new Random(); 61 | var randomIndex = random.Next(0, items.Count); 62 | items[randomIndex].Disabled = true; 63 | } 64 | 65 | 66 | private void SortList((int oldIndex, int newIndex) indices) 67 | { 68 | // deconstruct the tuple 69 | var (oldIndex, newIndex) = indices; 70 | 71 | var items = this.items; 72 | var itemToMove = items[oldIndex]; 73 | items.RemoveAt(oldIndex); 74 | 75 | if (newIndex < items.Count) 76 | { 77 | items.Insert(newIndex, itemToMove); 78 | } 79 | else 80 | { 81 | items.Add(itemToMove); 82 | } 83 | 84 | StateHasChanged(); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /Shared/Demos/Filtering.razor.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/the-urlist/BlazorSortable/5b9ee8db115a296f09638ddc8b4c95e2ad6afe57/Shared/Demos/Filtering.razor.css -------------------------------------------------------------------------------- /Shared/Demos/ForceFallback.razor: -------------------------------------------------------------------------------- 1 | 
2 |

Force Fallback to JavaScript

3 |

Due to HTML5 drag and drop not being universally supported and the loss of control over the styling forceFallback is set to true by default for this library so the HTML5 drag and drop is not used. If preferred, you can set the 'ForceFallback' to false to get SortableJS's default behavior. The list on the left has 'ForceFallback' set to true, and the list on the right has ForceFallback set to false.

4 | 5 | 6 |
7 |
8 | 9 | 10 |
11 |

@item.Name

12 |
13 |
14 |
15 |
16 |
17 | 18 | 19 |
20 |

@item.Name

21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 | 30 | @code { 31 | 32 | private string codeContent = $@" 33 | 34 | 35 |
36 |

@item.Name

37 |
38 |
39 |
40 | 41 | 42 |
43 |

@item.Name

44 |
45 |
46 |
47 | 48 | @code {{ 49 | public List items = Enumerable.Range(1, 10).Select(i => new Item {{ Id = i, Name = $""Item {{i}}"" }}).ToList(); 50 | 51 | public List items2 = Enumerable.Range(1, 10).Select(i => new Item {{ Id = i, Name = $""Item {{i}}"" }}).ToList(); 52 | 53 | private void SortList((int oldIndex, int newIndex) indices) 54 | {{ 55 | // deconstruct the tuple 56 | var (oldIndex, newIndex) = indices; 57 | 58 | var items = this.items; 59 | var itemToMove = items[oldIndex]; 60 | items.RemoveAt(oldIndex); 61 | 62 | if (newIndex < items.Count) 63 | {{ 64 | items.Insert(newIndex, itemToMove); 65 | }} 66 | else 67 | {{ 68 | items.Add(itemToMove); 69 | }} 70 | 71 | StateHasChanged(); 72 | }} 73 | }} 74 | "; 75 | 76 | public List items = Enumerable.Range(1, 10).Select(i => new Item { Id = i, Name = $"Item {i}" }).ToList(); 77 | 78 | public List items2 = Enumerable.Range(1, 10).Select(i => new Item { Id = i, Name = $"Item {i}" }).ToList(); 79 | 80 | private void SortList((int oldIndex, int newIndex) indices) 81 | { 82 | // deconstruct the tuple 83 | var (oldIndex, newIndex) = indices; 84 | 85 | var items = this.items; 86 | var itemToMove = items[oldIndex]; 87 | items.RemoveAt(oldIndex); 88 | 89 | if (newIndex < items.Count) 90 | { 91 | items.Insert(newIndex, itemToMove); 92 | } 93 | else 94 | { 95 | items.Add(itemToMove); 96 | } 97 | 98 | StateHasChanged(); 99 | } 100 | } -------------------------------------------------------------------------------- /Shared/Demos/SharedLists.razor: -------------------------------------------------------------------------------- 1 |
2 |

Shared Lists

3 |

Shared lists are lists where items can be dragged from one list to the other and vice-versa. Providing the same "Group" string name for both lists is what links them together. Note that when an item is dragged into a different list, it assumes the visual style of that list. This is because Blazor controls the rendering of the list items.

4 | 5 | 6 |
7 |
8 | 9 | 10 |
11 |

@item.Name

12 |
13 |
14 |
15 |
16 |
17 | 18 | 19 |
20 |

@item.Name

21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 | 30 | @code { 31 | 32 | private string codeContent = $@" 33 | 34 | 35 |
36 |

@item.Name

37 |
38 |
39 |
40 | 41 | 42 |
43 |

@item.Name

44 |
45 |
46 |
47 | 48 | @code {{ 49 | private void ListOneRemove((int oldIndex, int newIndex) indices) 50 | {{ 51 | // get the item at the old index in list 1 52 | var item = items1[indices.oldIndex]; 53 | 54 | // add it to the new index in list 2 55 | items2.Insert(indices.newIndex, item); 56 | 57 | // remove the item from the old index in list 1 58 | items1.Remove(items1[indices.oldIndex]); 59 | }} 60 | 61 | private void ListTwoRemove((int oldIndex, int newIndex) indices) 62 | {{ 63 | // get the item at the old index in list 2 64 | var item = items2[indices.oldIndex]; 65 | 66 | // add it to the new index in list 1 67 | items1.Insert(indices.newIndex, item); 68 | 69 | // remove the item from the old index in list 2 70 | items2.Remove(items2[indices.oldIndex]); 71 | }} 72 | }} 73 | "; 74 | 75 | public List items1 = Enumerable.Range(1, 10).Select(i => new Item { Id = i, Name = $"Item {i}" }).ToList(); 76 | 77 | public List items2 = Enumerable.Range(11, 10).Select(i => new Item { Id = i, Name = $"Item {i}" }).ToList(); 78 | 79 | private void ListOneRemove((int oldIndex, int newIndex) indices) 80 | { 81 | // get the item at the old index in list 1 82 | var item = items1[indices.oldIndex]; 83 | 84 | // add it to the new index in list 2 85 | items2.Insert(indices.newIndex, item); 86 | 87 | // remove the item from the old index in list 1 88 | items1.Remove(items1[indices.oldIndex]); 89 | } 90 | 91 | private void ListTwoRemove((int oldIndex, int newIndex) indices) 92 | { 93 | // get the item at the old index in list 2 94 | var item = items2[indices.oldIndex]; 95 | 96 | // add it to the new index in list 1 97 | items1.Insert(indices.newIndex, item); 98 | 99 | // remove the item from the old index in list 2 100 | items2.Remove(items2[indices.oldIndex]); 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /Shared/Demos/SimpleList.razor: -------------------------------------------------------------------------------- 1 |
2 |

Simple List

3 | 4 | 5 |
6 |
7 | 8 | 9 |
10 |

@item.Name

11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 | 20 | @code { 21 | 22 | private string codeContent = $@" 23 | 24 | 25 |
26 |

@item.Name

27 |
28 |
29 |
30 | 31 | @code {{ 32 | public class Item 33 | {{ 34 | public int Id {{ get; set; }} 35 | public string Name {{ get; set; }} 36 | }} 37 | 38 | private void SortList((int oldIndex, int newIndex) indices) 39 | {{ 40 | // deconstruct the tuple 41 | var (oldIndex, newIndex) = indices; 42 | 43 | var items = this.items; 44 | var itemToMove = items[oldIndex]; 45 | items.RemoveAt(oldIndex); 46 | 47 | if (newIndex < items.Count) 48 | {{ 49 | items.Insert(newIndex, itemToMove); 50 | }} 51 | else 52 | {{ 53 | items.Add(itemToMove); 54 | }} 55 | 56 | StateHasChanged(); 57 | }} 58 | }}"; 59 | 60 | public List items = Enumerable.Range(1, 10).Select(i => new Item { Id = i, Name = $"Item {i}" }).ToList(); 61 | 62 | 63 | private void SortList((int oldIndex, int newIndex) indices) 64 | { 65 | // deconstruct the tuple 66 | var (oldIndex, newIndex) = indices; 67 | 68 | var items = this.items; 69 | var itemToMove = items[oldIndex]; 70 | items.RemoveAt(oldIndex); 71 | 72 | if (newIndex < items.Count) 73 | { 74 | items.Insert(newIndex, itemToMove); 75 | } 76 | else 77 | { 78 | items.Add(itemToMove); 79 | } 80 | 81 | StateHasChanged(); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /Shared/Demos/Tabs.razor: -------------------------------------------------------------------------------- 1 |
2 |
3 | 7 |
8 |
9 | @if (showCode) 10 | { 11 |
@CodeContent
12 | } 13 | else 14 | { 15 | @ExampleContent 16 | } 17 |
18 |
19 | 20 | @code { 21 | private bool showCode = false; 22 | 23 | // render fragment for tab content 24 | [Parameter] 25 | public RenderFragment? ExampleContent { get; set; } 26 | 27 | [Parameter] 28 | public string CodeContent { get; set; } = ""; 29 | 30 | private void ShowCode() 31 | { 32 | showCode = true; 33 | } 34 | } -------------------------------------------------------------------------------- /Shared/SortableList.razor: -------------------------------------------------------------------------------- 1 | @using System.Collections.Generic 2 | @using System.Diagnostics.CodeAnalysis 3 | 4 | @inject IJSRuntime JS 5 | 6 | @typeparam T 7 | 8 |
9 | @foreach (var item in Items) 10 | { 11 | @if (SortableItemTemplate is not null) 12 | { 13 | @SortableItemTemplate(item) 14 | } 15 | } 16 |
17 | 18 | @code { 19 | 20 | [Parameter] 21 | public RenderFragment? SortableItemTemplate { get; set; } 22 | 23 | [Parameter, AllowNull] 24 | public List Items { get; set; } 25 | 26 | [Parameter] 27 | public EventCallback<(int oldIndex, int newIndex)> OnUpdate { get; set; } 28 | 29 | [Parameter] 30 | public EventCallback<(int oldIndex, int newIndex)> OnRemove { get; set; } 31 | 32 | [Parameter] 33 | public string Id { get; set; } = Guid.NewGuid().ToString(); 34 | 35 | [Parameter] 36 | public string Group { get; set; } = Guid.NewGuid().ToString(); 37 | 38 | [Parameter] 39 | public string? Pull { get; set; } 40 | 41 | [Parameter] 42 | public bool Put { get; set; } = true; 43 | 44 | [Parameter] 45 | public bool Sort { get; set; } = true; 46 | 47 | [Parameter] 48 | public string Handle { get; set; } = string.Empty; 49 | 50 | [Parameter] 51 | public string? Filter { get; set; } 52 | 53 | [Parameter] 54 | public bool ForceFallback { get; set; } = true; 55 | 56 | private DotNetObjectReference>? selfReference; 57 | 58 | protected override async Task OnAfterRenderAsync(bool firstRender) 59 | { 60 | if (firstRender) 61 | { 62 | selfReference = DotNetObjectReference.Create(this); 63 | var module = await JS.InvokeAsync("import", "./Shared/SortableList.razor.js"); 64 | await module.InvokeAsync("init", Id, Group, Pull, Put, Sort, Handle, Filter, selfReference, ForceFallback); 65 | } 66 | } 67 | 68 | [JSInvokable] 69 | public void OnUpdateJS(int oldIndex, int newIndex) 70 | { 71 | // invoke the OnUpdate event passing in the oldIndex and the newIndex 72 | OnUpdate.InvokeAsync((oldIndex, newIndex)); 73 | } 74 | 75 | [JSInvokable] 76 | public void OnRemoveJS(int oldIndex, int newIndex) 77 | { 78 | // remove the item from the list 79 | OnRemove.InvokeAsync((oldIndex, newIndex)); 80 | } 81 | 82 | public void Dispose() => selfReference?.Dispose(); 83 | } 84 | -------------------------------------------------------------------------------- /Shared/SortableList.razor.css: -------------------------------------------------------------------------------- 1 | /* 2 | you need the ::deep identifier if you are using scoped styles like this 3 | because scoped styles are only applied to markup in the component, not 4 | to the markup inside the render fragment. 5 | */ 6 | 7 | ::deep .sortable-ghost { 8 | visibility: hidden; 9 | } 10 | 11 | ::deep .sortable-fallback { 12 | opacity: 1 !important 13 | } -------------------------------------------------------------------------------- /Shared/SortableList.razor.js: -------------------------------------------------------------------------------- 1 | export function init(id, group, pull, put, sort, handle, filter, component, forceFallback) { 2 | var sortable = new Sortable(document.getElementById(id), { 3 | animation: 200, 4 | group: { 5 | name: group, 6 | pull: pull || true, 7 | put: put 8 | }, 9 | filter: filter || undefined, 10 | sort: sort, 11 | forceFallback: forceFallback, 12 | handle: handle || undefined, 13 | onUpdate: (event) => { 14 | // Revert the DOM to match the .NET state 15 | event.item.remove(); 16 | event.to.insertBefore(event.item, event.to.childNodes[event.oldIndex]); 17 | 18 | // Notify .NET to update its model and re-render 19 | component.invokeMethodAsync('OnUpdateJS', event.oldDraggableIndex, event.newDraggableIndex); 20 | }, 21 | onRemove: (event) => { 22 | if (event.pullMode === 'clone') { 23 | // Remove the clone 24 | event.clone.remove(); 25 | } 26 | 27 | event.item.remove(); 28 | event.from.insertBefore(event.item, event.from.childNodes[event.oldIndex]); 29 | 30 | // Notify .NET to update its model and re-render 31 | component.invokeMethodAsync('OnRemoveJS', event.oldDraggableIndex, event.newDraggableIndex); 32 | } 33 | }); 34 | } 35 | -------------------------------------------------------------------------------- /_Imports.razor: -------------------------------------------------------------------------------- 1 | @using System.Net.Http 2 | @using System.Net.Http.Json 3 | @using Microsoft.AspNetCore.Components.Routing 4 | @using Microsoft.AspNetCore.Components.Web 5 | @using Microsoft.AspNetCore.Components.WebAssembly.Http 6 | @using Microsoft.JSInterop 7 | @using BlazorSortable 8 | @using BlazorSortable.Shared 9 | -------------------------------------------------------------------------------- /wwwroot/css/app.css: -------------------------------------------------------------------------------- 1 | h1:focus { 2 | outline: none; 3 | } 4 | 5 | #blazor-error-ui { 6 | background: lightyellow; 7 | bottom: 0; 8 | box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2); 9 | display: none; 10 | left: 0; 11 | padding: 0.6rem 1.25rem 0.7rem 1.25rem; 12 | position: fixed; 13 | width: 100%; 14 | z-index: 1000; 15 | } 16 | 17 | #blazor-error-ui .dismiss { 18 | cursor: pointer; 19 | position: absolute; 20 | right: 0.75rem; 21 | top: 0.5rem; 22 | } 23 | 24 | .blazor-error-boundary { 25 | background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTYiIGhlaWdodD0iNDkiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIG92ZXJmbG93PSJoaWRkZW4iPjxkZWZzPjxjbGlwUGF0aCBpZD0iY2xpcDAiPjxyZWN0IHg9IjIzNSIgeT0iNTEiIHdpZHRoPSI1NiIgaGVpZ2h0PSI0OSIvPjwvY2xpcFBhdGg+PC9kZWZzPjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMCkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMzUgLTUxKSI+PHBhdGggZD0iTTI2My41MDYgNTFDMjY0LjcxNyA1MSAyNjUuODEzIDUxLjQ4MzcgMjY2LjYwNiA1Mi4yNjU4TDI2Ny4wNTIgNTIuNzk4NyAyNjcuNTM5IDUzLjYyODMgMjkwLjE4NSA5Mi4xODMxIDI5MC41NDUgOTIuNzk1IDI5MC42NTYgOTIuOTk2QzI5MC44NzcgOTMuNTEzIDI5MSA5NC4wODE1IDI5MSA5NC42NzgyIDI5MSA5Ny4wNjUxIDI4OS4wMzggOTkgMjg2LjYxNyA5OUwyNDAuMzgzIDk5QzIzNy45NjMgOTkgMjM2IDk3LjA2NTEgMjM2IDk0LjY3ODIgMjM2IDk0LjM3OTkgMjM2LjAzMSA5NC4wODg2IDIzNi4wODkgOTMuODA3MkwyMzYuMzM4IDkzLjAxNjIgMjM2Ljg1OCA5Mi4xMzE0IDI1OS40NzMgNTMuNjI5NCAyNTkuOTYxIDUyLjc5ODUgMjYwLjQwNyA1Mi4yNjU4QzI2MS4yIDUxLjQ4MzcgMjYyLjI5NiA1MSAyNjMuNTA2IDUxWk0yNjMuNTg2IDY2LjAxODNDMjYwLjczNyA2Ni4wMTgzIDI1OS4zMTMgNjcuMTI0NSAyNTkuMzEzIDY5LjMzNyAyNTkuMzEzIDY5LjYxMDIgMjU5LjMzMiA2OS44NjA4IDI1OS4zNzEgNzAuMDg4N0wyNjEuNzk1IDg0LjAxNjEgMjY1LjM4IDg0LjAxNjEgMjY3LjgyMSA2OS43NDc1QzI2Ny44NiA2OS43MzA5IDI2Ny44NzkgNjkuNTg3NyAyNjcuODc5IDY5LjMxNzkgMjY3Ljg3OSA2Ny4xMTgyIDI2Ni40NDggNjYuMDE4MyAyNjMuNTg2IDY2LjAxODNaTTI2My41NzYgODYuMDU0N0MyNjEuMDQ5IDg2LjA1NDcgMjU5Ljc4NiA4Ny4zMDA1IDI1OS43ODYgODkuNzkyMSAyNTkuNzg2IDkyLjI4MzcgMjYxLjA0OSA5My41Mjk1IDI2My41NzYgOTMuNTI5NSAyNjYuMTE2IDkzLjUyOTUgMjY3LjM4NyA5Mi4yODM3IDI2Ny4zODcgODkuNzkyMSAyNjcuMzg3IDg3LjMwMDUgMjY2LjExNiA4Ni4wNTQ3IDI2My41NzYgODYuMDU0N1oiIGZpbGw9IiNGRkU1MDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=) no-repeat 1rem/1.8rem, #b32121; 26 | padding: 1rem 1rem 1rem 3.7rem; 27 | color: white; 28 | } 29 | 30 | .blazor-error-boundary::after { 31 | content: "An error has occurred." 32 | } 33 | 34 | .has-border-light { 35 | border: 1px solid #fff; 36 | cursor: grab; 37 | user-select: none; 38 | -webkit-user-select: none; 39 | } 40 | 41 | .has-border-dark { 42 | border: 1px solid #ddd; 43 | cursor: grab; 44 | user-select: none; 45 | -webkit-user-select: none; 46 | } 47 | 48 | .has-background-blazor { 49 | background-color: #592b8f !important; 50 | } 51 | 52 | .has-cursor-grab { 53 | cursor: grab; 54 | } 55 | 56 | /* links should be white */ 57 | a { 58 | color: white; 59 | } -------------------------------------------------------------------------------- /wwwroot/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | BlazorSortable 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 |
Loading...
18 | 19 |
20 | An unhandled error has occurred. 21 | Reload 22 | 🗙 23 |
24 | 25 | 26 | 27 | 28 | --------------------------------------------------------------------------------