Browse Source

Merge pull request #9142 from AvaloniaUI/wasm-updates

Remove old blazor backend. Keep blazor components with new WASM implementation.
Dan Walmsley 3 years ago
parent
commit
0dd39b8421
96 changed files with 435 additions and 3954 deletions
  1. 18 18
      Avalonia.sln
  2. 0 0
      samples/ControlCatalog.Blazor.Web/App.razor
  3. 17 0
      samples/ControlCatalog.Blazor.Web/App.razor.cs
  4. 29 0
      samples/ControlCatalog.Blazor.Web/ControlCatalog.Blazor.Web.csproj
  5. 0 0
      samples/ControlCatalog.Blazor.Web/Pages/Index.razor
  6. 29 0
      samples/ControlCatalog.Blazor.Web/Program.cs
  7. 0 0
      samples/ControlCatalog.Blazor.Web/Properties/launchSettings.json
  8. 0 0
      samples/ControlCatalog.Blazor.Web/Shared/MainLayout.razor
  9. 1 2
      samples/ControlCatalog.Blazor.Web/_Imports.razor
  10. 0 0
      samples/ControlCatalog.Blazor.Web/wwwroot/css/app.css
  11. 0 0
      samples/ControlCatalog.Blazor.Web/wwwroot/favicon.ico
  12. 0 0
      samples/ControlCatalog.Blazor.Web/wwwroot/index.html
  13. 0 20
      samples/ControlCatalog.Web/App.razor.cs
  14. 28 40
      samples/ControlCatalog.Web/ControlCatalog.Web.csproj
  15. 20 12
      samples/ControlCatalog.Web/EmbedSample.Browser.cs
  16. 0 0
      samples/ControlCatalog.Web/Logo.svg
  17. 12 22
      samples/ControlCatalog.Web/Program.cs
  18. 6 0
      samples/ControlCatalog.Web/Roots.xml
  19. 17 6
      samples/ControlCatalog.Web/app.css
  20. 0 0
      samples/ControlCatalog.Web/embed.js
  21. 0 0
      samples/ControlCatalog.Web/favicon.ico
  22. 1 1
      samples/ControlCatalog.Web/index.html
  23. 2 2
      samples/ControlCatalog.Web/main.js
  24. 0 0
      samples/ControlCatalog.Web/runtimeconfig.template.json
  25. 0 7
      src/Web/Avalonia.Web.Blazor/Avalonia.Web.Blazor.CompilationTuning.props
  26. 20 33
      src/Web/Avalonia.Web.Blazor/Avalonia.Web.Blazor.csproj
  27. 0 4
      src/Web/Avalonia.Web.Blazor/Avalonia.Web.Blazor.props
  28. 0 6
      src/Web/Avalonia.Web.Blazor/Avalonia.Web.Blazor.targets
  29. 0 20
      src/Web/Avalonia.Web.Blazor/AvaloniaBlazorAppBuilder.cs
  30. 44 0
      src/Web/Avalonia.Web.Blazor/AvaloniaView.cs
  31. 0 67
      src/Web/Avalonia.Web.Blazor/AvaloniaView.razor
  32. 0 500
      src/Web/Avalonia.Web.Blazor/AvaloniaView.razor.cs
  33. 28 20
      src/Web/Avalonia.Web.Blazor/BlazorSingleViewLifetime.cs
  34. 0 25
      src/Web/Avalonia.Web.Blazor/BlazorSkiaGpu.cs
  35. 0 37
      src/Web/Avalonia.Web.Blazor/BlazorSkiaGpuRenderSession.cs
  36. 0 39
      src/Web/Avalonia.Web.Blazor/BlazorSkiaGpuRenderTarget.cs
  37. 0 87
      src/Web/Avalonia.Web.Blazor/BlazorSkiaRasterSurface.cs
  38. 0 30
      src/Web/Avalonia.Web.Blazor/BlazorSkiaSurface.cs
  39. 0 34
      src/Web/Avalonia.Web.Blazor/ClipboardImpl.cs
  40. 0 93
      src/Web/Avalonia.Web.Blazor/Cursor.cs
  41. 0 9
      src/Web/Avalonia.Web.Blazor/IBlazorSkiaSurface.cs
  42. 0 81
      src/Web/Avalonia.Web.Blazor/Interop/ActionHelper.cs
  43. 0 18
      src/Web/Avalonia.Web.Blazor/Interop/AvaloniaModule.cs
  44. 0 72
      src/Web/Avalonia.Web.Blazor/Interop/DpiWatcherInterop.cs
  45. 0 22
      src/Web/Avalonia.Web.Blazor/Interop/FocusHelperInterop.cs
  46. 0 130
      src/Web/Avalonia.Web.Blazor/Interop/InputHelperInterop.cs
  47. 0 46
      src/Web/Avalonia.Web.Blazor/Interop/JSModuleInterop.cs
  48. 0 144
      src/Web/Avalonia.Web.Blazor/Interop/NativeControlHostImpl.cs
  49. 0 76
      src/Web/Avalonia.Web.Blazor/Interop/SKHtmlCanvasInterop.cs
  50. 0 50
      src/Web/Avalonia.Web.Blazor/Interop/SizeWatcherInterop.cs
  51. 0 225
      src/Web/Avalonia.Web.Blazor/Interop/Storage/StorageProviderInterop.cs
  52. 0 124
      src/Web/Avalonia.Web.Blazor/Interop/Storage/WriteableStream.cs
  53. 0 35
      src/Web/Avalonia.Web.Blazor/JSObjectControlHandle.cs
  54. 0 127
      src/Web/Avalonia.Web.Blazor/Keycodes.cs
  55. 0 17
      src/Web/Avalonia.Web.Blazor/ManualTriggerRenderTimer.cs
  56. 0 222
      src/Web/Avalonia.Web.Blazor/RazorViewTopLevelImpl.cs
  57. 0 50
      src/Web/Avalonia.Web.Blazor/WinStubs.cs
  58. 0 106
      src/Web/Avalonia.Web.Blazor/WindowingPlatform.cs
  59. 0 1
      src/Web/Avalonia.Web.Blazor/_Imports.razor
  60. 0 16
      src/Web/Avalonia.Web.Blazor/webapp/build.js
  61. 0 7
      src/Web/Avalonia.Web.Blazor/webapp/modules/Avalonia.ts
  62. 0 149
      src/Web/Avalonia.Web.Blazor/webapp/modules/Avalonia/CaretHelper.ts
  63. 0 40
      src/Web/Avalonia.Web.Blazor/webapp/modules/Avalonia/DpiWatcher.ts
  64. 0 9
      src/Web/Avalonia.Web.Blazor/webapp/modules/Avalonia/FocusHelper.ts
  65. 0 86
      src/Web/Avalonia.Web.Blazor/webapp/modules/Avalonia/InputHelper.ts
  66. 0 61
      src/Web/Avalonia.Web.Blazor/webapp/modules/Avalonia/NativeControlHost.ts
  67. 0 255
      src/Web/Avalonia.Web.Blazor/webapp/modules/Avalonia/SKHtmlCanvas.ts
  68. 0 67
      src/Web/Avalonia.Web.Blazor/webapp/modules/Avalonia/SizeWatcher.ts
  69. 0 1
      src/Web/Avalonia.Web.Blazor/webapp/modules/Storage.ts
  70. 0 79
      src/Web/Avalonia.Web.Blazor/webapp/modules/Storage/IndexedDbWrapper.ts
  71. 0 204
      src/Web/Avalonia.Web.Blazor/webapp/modules/Storage/StorageProvider.ts
  72. 0 13
      src/Web/Avalonia.Web.Blazor/webapp/package.json
  73. 0 18
      src/Web/Avalonia.Web.Blazor/webapp/tsconfig.json
  74. 0 56
      src/Web/Avalonia.Web.Blazor/webapp/types/dotnet/index.d.ts
  75. 0 41
      src/Web/Avalonia.Web.Sample/Avalonia.Web.Sample.csproj
  76. 0 44
      src/Web/Avalonia.Web.Sample/EmbedSample.Browser.cs
  77. 0 19
      src/Web/Avalonia.Web.Sample/Program.cs
  78. 4 4
      src/Web/Avalonia.Web/Avalonia.Web.csproj
  79. 9 13
      src/Web/Avalonia.Web/AvaloniaView.cs
  80. 42 29
      src/Web/Avalonia.Web/BrowserSingleViewLifetime.cs
  81. 1 1
      src/Web/Avalonia.Web/Cursor.cs
  82. 22 0
      src/Web/Avalonia.Web/Interop/AvaloniaModule.cs
  83. 3 3
      src/Web/Avalonia.Web/Interop/CanvasHelper.cs
  84. 5 5
      src/Web/Avalonia.Web/Interop/DomHelper.cs
  85. 12 12
      src/Web/Avalonia.Web/Interop/InputHelper.cs
  86. 7 7
      src/Web/Avalonia.Web/Interop/NativeControlHostHelper.cs
  87. 15 15
      src/Web/Avalonia.Web/Interop/StorageHelper.cs
  88. 8 8
      src/Web/Avalonia.Web/Interop/StreamHelper.cs
  89. 1 1
      src/Web/Avalonia.Web/ManualTriggerRenderTimer.cs
  90. 2 0
      src/Web/Avalonia.Web/Storage/BlobReadableStream.cs
  91. 6 6
      src/Web/Avalonia.Web/Storage/BrowserStorageProvider.cs
  92. 2 0
      src/Web/Avalonia.Web/Storage/WriteableStream.cs
  93. 1 1
      src/Web/Avalonia.Web/WindowingPlatform.cs
  94. 14 2
      src/Web/Avalonia.Web/webapp/modules/avalonia.ts
  95. 2 2
      src/Web/Avalonia.Web/webapp/modules/avalonia/canvas.ts
  96. 7 0
      src/Web/Avalonia.Web/webapp/modules/avalonia/dom.ts

+ 18 - 18
Avalonia.sln

@@ -198,8 +198,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Web", "Web", "{86A3F706-DC3
 EndProject
 EndProject
 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Web.Blazor", "src\Web\Avalonia.Web.Blazor\Avalonia.Web.Blazor.csproj", "{25831348-EB2A-483E-9576-E8F6528674A5}"
 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Web.Blazor", "src\Web\Avalonia.Web.Blazor\Avalonia.Web.Blazor.csproj", "{25831348-EB2A-483E-9576-E8F6528674A5}"
 EndProject
 EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ControlCatalog.Web", "samples\ControlCatalog.Web\ControlCatalog.Web.csproj", "{C08E9894-AA92-426E-BF56-033E262CAD3E}"
-EndProject
 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WindowsInteropTest", "samples\interop\WindowsInteropTest\WindowsInteropTest.csproj", "{26A98DA1-D89D-4A95-8152-349F404DA2E2}"
 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WindowsInteropTest", "samples\interop\WindowsInteropTest\WindowsInteropTest.csproj", "{26A98DA1-D89D-4A95-8152-349F404DA2E2}"
 EndProject
 EndProject
 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ControlSamples", "samples\SampleControls\ControlSamples.csproj", "{A0D0A6A4-5C72-4ADA-9B27-621C7D94F270}"
 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ControlSamples", "samples\SampleControls\ControlSamples.csproj", "{A0D0A6A4-5C72-4ADA-9B27-621C7D94F270}"
@@ -216,15 +214,17 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DevGenerators", "src\tools\
 EndProject
 EndProject
 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Web", "src\Web\Avalonia.Web\Avalonia.Web.csproj", "{76D39FF6-6B4F-46C4-93CD-E6FC4665739E}"
 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Web", "src\Web\Avalonia.Web\Avalonia.Web.csproj", "{76D39FF6-6B4F-46C4-93CD-E6FC4665739E}"
 EndProject
 EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Web.Sample", "src\Web\Avalonia.Web.Sample\Avalonia.Web.Sample.csproj", "{1F61B6F1-B881-4E27-A5B0-09D1F543F7AC}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MobileSandbox", "samples\MobileSandbox\MobileSandbox.csproj", "{3B8519C1-2F51-4F12-A348-120AB91D4532}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MobileSandbox.Android", "samples\MobileSandbox.Android\MobileSandbox.Android.csproj", "{C90FE60B-B01E-4F35-91D6-379D6966030F}"
 EndProject
 EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MobileSandbox", "samples\MobileSandbox\MobileSandbox.csproj", "{3B8519C1-2F51-4F12-A348-120AB91D4532}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MobileSandbox.iOS", "samples\MobileSandbox.iOS\MobileSandbox.iOS.csproj", "{FED9A71D-00D7-4F40-A9E4-1229EEA28EEB}"
 EndProject
 EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MobileSandbox.Android", "samples\MobileSandbox.Android\MobileSandbox.Android.csproj", "{C90FE60B-B01E-4F35-91D6-379D6966030F}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MobileSandbox.Desktop", "samples\MobileSandbox.Desktop\MobileSandbox.Desktop.csproj", "{62D392C9-81CF-487F-92E8-598B2AF3FDCE}"
 EndProject
 EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MobileSandbox.iOS", "samples\MobileSandbox.iOS\MobileSandbox.iOS.csproj", "{FED9A71D-00D7-4F40-A9E4-1229EEA28EEB}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ControlCatalog.Blazor.Web", "samples\ControlCatalog.Blazor.Web\ControlCatalog.Blazor.Web.csproj", "{6A710364-AE6D-40BD-968B-024311527AC2}"
 EndProject
 EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MobileSandbox.Desktop", "samples\MobileSandbox.Desktop\MobileSandbox.Desktop.csproj", "{62D392C9-81CF-487F-92E8-598B2AF3FDCE}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ControlCatalog.Web", "samples\ControlCatalog.Web\ControlCatalog.Web.csproj", "{8B3E8405-DE18-4048-A459-9CA4AC3319A2}"
 EndProject
 EndProject
 Global
 Global
 	GlobalSection(SolutionConfigurationPlatforms) = preSolution
 	GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -480,10 +480,6 @@ Global
 		{25831348-EB2A-483E-9576-E8F6528674A5}.Debug|Any CPU.Build.0 = Debug|Any CPU
 		{25831348-EB2A-483E-9576-E8F6528674A5}.Debug|Any CPU.Build.0 = Debug|Any CPU
 		{25831348-EB2A-483E-9576-E8F6528674A5}.Release|Any CPU.ActiveCfg = Release|Any CPU
 		{25831348-EB2A-483E-9576-E8F6528674A5}.Release|Any CPU.ActiveCfg = Release|Any CPU
 		{25831348-EB2A-483E-9576-E8F6528674A5}.Release|Any CPU.Build.0 = Release|Any CPU
 		{25831348-EB2A-483E-9576-E8F6528674A5}.Release|Any CPU.Build.0 = Release|Any CPU
-		{C08E9894-AA92-426E-BF56-033E262CAD3E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
-		{C08E9894-AA92-426E-BF56-033E262CAD3E}.Debug|Any CPU.Build.0 = Debug|Any CPU
-		{C08E9894-AA92-426E-BF56-033E262CAD3E}.Release|Any CPU.ActiveCfg = Release|Any CPU
-		{C08E9894-AA92-426E-BF56-033E262CAD3E}.Release|Any CPU.Build.0 = Release|Any CPU
 		{26A98DA1-D89D-4A95-8152-349F404DA2E2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
 		{26A98DA1-D89D-4A95-8152-349F404DA2E2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
 		{26A98DA1-D89D-4A95-8152-349F404DA2E2}.Debug|Any CPU.Build.0 = Debug|Any CPU
 		{26A98DA1-D89D-4A95-8152-349F404DA2E2}.Debug|Any CPU.Build.0 = Debug|Any CPU
 		{26A98DA1-D89D-4A95-8152-349F404DA2E2}.Release|Any CPU.ActiveCfg = Release|Any CPU
 		{26A98DA1-D89D-4A95-8152-349F404DA2E2}.Release|Any CPU.ActiveCfg = Release|Any CPU
@@ -516,10 +512,6 @@ Global
 		{76D39FF6-6B4F-46C4-93CD-E6FC4665739E}.Debug|Any CPU.Build.0 = Debug|Any CPU
 		{76D39FF6-6B4F-46C4-93CD-E6FC4665739E}.Debug|Any CPU.Build.0 = Debug|Any CPU
 		{76D39FF6-6B4F-46C4-93CD-E6FC4665739E}.Release|Any CPU.ActiveCfg = Release|Any CPU
 		{76D39FF6-6B4F-46C4-93CD-E6FC4665739E}.Release|Any CPU.ActiveCfg = Release|Any CPU
 		{76D39FF6-6B4F-46C4-93CD-E6FC4665739E}.Release|Any CPU.Build.0 = Release|Any CPU
 		{76D39FF6-6B4F-46C4-93CD-E6FC4665739E}.Release|Any CPU.Build.0 = Release|Any CPU
-		{1F61B6F1-B881-4E27-A5B0-09D1F543F7AC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
-		{1F61B6F1-B881-4E27-A5B0-09D1F543F7AC}.Debug|Any CPU.Build.0 = Debug|Any CPU
-		{1F61B6F1-B881-4E27-A5B0-09D1F543F7AC}.Release|Any CPU.ActiveCfg = Release|Any CPU
-		{1F61B6F1-B881-4E27-A5B0-09D1F543F7AC}.Release|Any CPU.Build.0 = Release|Any CPU
 		{3B8519C1-2F51-4F12-A348-120AB91D4532}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
 		{3B8519C1-2F51-4F12-A348-120AB91D4532}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
 		{3B8519C1-2F51-4F12-A348-120AB91D4532}.Debug|Any CPU.Build.0 = Debug|Any CPU
 		{3B8519C1-2F51-4F12-A348-120AB91D4532}.Debug|Any CPU.Build.0 = Debug|Any CPU
 		{3B8519C1-2F51-4F12-A348-120AB91D4532}.Release|Any CPU.ActiveCfg = Release|Any CPU
 		{3B8519C1-2F51-4F12-A348-120AB91D4532}.Release|Any CPU.ActiveCfg = Release|Any CPU
@@ -536,6 +528,14 @@ Global
 		{62D392C9-81CF-487F-92E8-598B2AF3FDCE}.Debug|Any CPU.Build.0 = Debug|Any CPU
 		{62D392C9-81CF-487F-92E8-598B2AF3FDCE}.Debug|Any CPU.Build.0 = Debug|Any CPU
 		{62D392C9-81CF-487F-92E8-598B2AF3FDCE}.Release|Any CPU.ActiveCfg = Release|Any CPU
 		{62D392C9-81CF-487F-92E8-598B2AF3FDCE}.Release|Any CPU.ActiveCfg = Release|Any CPU
 		{62D392C9-81CF-487F-92E8-598B2AF3FDCE}.Release|Any CPU.Build.0 = Release|Any CPU
 		{62D392C9-81CF-487F-92E8-598B2AF3FDCE}.Release|Any CPU.Build.0 = Release|Any CPU
+		{6A710364-AE6D-40BD-968B-024311527AC2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{6A710364-AE6D-40BD-968B-024311527AC2}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{6A710364-AE6D-40BD-968B-024311527AC2}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{6A710364-AE6D-40BD-968B-024311527AC2}.Release|Any CPU.Build.0 = Release|Any CPU
+		{8B3E8405-DE18-4048-A459-9CA4AC3319A2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{8B3E8405-DE18-4048-A459-9CA4AC3319A2}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{8B3E8405-DE18-4048-A459-9CA4AC3319A2}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{8B3E8405-DE18-4048-A459-9CA4AC3319A2}.Release|Any CPU.Build.0 = Release|Any CPU
 	EndGlobalSection
 	EndGlobalSection
 	GlobalSection(SolutionProperties) = preSolution
 	GlobalSection(SolutionProperties) = preSolution
 		HideSolutionNode = FALSE
 		HideSolutionNode = FALSE
@@ -579,6 +579,7 @@ Global
 		{41B02319-965D-4945-8005-C1A3D1224165} = {86C53C40-57AA-45B8-AD42-FAE0EFDF0F2B}
 		{41B02319-965D-4945-8005-C1A3D1224165} = {86C53C40-57AA-45B8-AD42-FAE0EFDF0F2B}
 		{D775DECB-4E00-4ED5-A75A-5FCE58ADFF0B} = {9B9E3891-2366-4253-A952-D08BCEB71098}
 		{D775DECB-4E00-4ED5-A75A-5FCE58ADFF0B} = {9B9E3891-2366-4253-A952-D08BCEB71098}
 		{AF915D5C-AB00-4EA0-B5E6-001F4AE84E68} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B}
 		{AF915D5C-AB00-4EA0-B5E6-001F4AE84E68} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B}
+		{4D36CEC8-53F2-40A5-9A37-79AAE356E2DA} = {86C53C40-57AA-45B8-AD42-FAE0EFDF0F2B}
 		{351337F5-D66F-461B-A957-4EF60BDB4BA6} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B}
 		{351337F5-D66F-461B-A957-4EF60BDB4BA6} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B}
 		{909A8CBD-7D0E-42FD-B841-022AD8925820} = {8B6A8209-894F-4BA1-B880-965FD453982C}
 		{909A8CBD-7D0E-42FD-B841-022AD8925820} = {8B6A8209-894F-4BA1-B880-965FD453982C}
 		{11BE52AF-E2DD-4CF0-B19A-05285ACAF571} = {9B9E3891-2366-4253-A952-D08BCEB71098}
 		{11BE52AF-E2DD-4CF0-B19A-05285ACAF571} = {9B9E3891-2366-4253-A952-D08BCEB71098}
@@ -586,7 +587,6 @@ Global
 		{676D6BFD-029D-4E43-BFC7-3892265CE251} = {9B9E3891-2366-4253-A952-D08BCEB71098}
 		{676D6BFD-029D-4E43-BFC7-3892265CE251} = {9B9E3891-2366-4253-A952-D08BCEB71098}
 		{F2CE566B-E7F6-447A-AB1A-3F574A6FE43A} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B}
 		{F2CE566B-E7F6-447A-AB1A-3F574A6FE43A} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B}
 		{25831348-EB2A-483E-9576-E8F6528674A5} = {86A3F706-DC3C-43C6-BE1B-B98F5BAAA268}
 		{25831348-EB2A-483E-9576-E8F6528674A5} = {86A3F706-DC3C-43C6-BE1B-B98F5BAAA268}
-		{C08E9894-AA92-426E-BF56-033E262CAD3E} = {9B9E3891-2366-4253-A952-D08BCEB71098}
 		{26A98DA1-D89D-4A95-8152-349F404DA2E2} = {A0CC0258-D18C-4AB3-854F-7101680FC3F9}
 		{26A98DA1-D89D-4A95-8152-349F404DA2E2} = {A0CC0258-D18C-4AB3-854F-7101680FC3F9}
 		{A0D0A6A4-5C72-4ADA-9B27-621C7D94F270} = {9B9E3891-2366-4253-A952-D08BCEB71098}
 		{A0D0A6A4-5C72-4ADA-9B27-621C7D94F270} = {9B9E3891-2366-4253-A952-D08BCEB71098}
 		{70B9F5CC-E2F9-4314-9514-EDE762ACCC4B} = {9B9E3891-2366-4253-A952-D08BCEB71098}
 		{70B9F5CC-E2F9-4314-9514-EDE762ACCC4B} = {9B9E3891-2366-4253-A952-D08BCEB71098}
@@ -594,12 +594,12 @@ Global
 		{EABE2161-989B-42BF-BD8D-1E34B20C21F1} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B}
 		{EABE2161-989B-42BF-BD8D-1E34B20C21F1} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B}
 		{1BBFAD42-B99E-47E0-B00A-A4BC6B6BB4BB} = {4ED8B739-6F4E-4CD4-B993-545E6B5CE637}
 		{1BBFAD42-B99E-47E0-B00A-A4BC6B6BB4BB} = {4ED8B739-6F4E-4CD4-B993-545E6B5CE637}
 		{76D39FF6-6B4F-46C4-93CD-E6FC4665739E} = {86A3F706-DC3C-43C6-BE1B-B98F5BAAA268}
 		{76D39FF6-6B4F-46C4-93CD-E6FC4665739E} = {86A3F706-DC3C-43C6-BE1B-B98F5BAAA268}
-		{1F61B6F1-B881-4E27-A5B0-09D1F543F7AC} = {86A3F706-DC3C-43C6-BE1B-B98F5BAAA268}
-		{4D36CEC8-53F2-40A5-9A37-79AAE356E2DA} = {86C53C40-57AA-45B8-AD42-FAE0EFDF0F2B}
 		{3B8519C1-2F51-4F12-A348-120AB91D4532} = {9B9E3891-2366-4253-A952-D08BCEB71098}
 		{3B8519C1-2F51-4F12-A348-120AB91D4532} = {9B9E3891-2366-4253-A952-D08BCEB71098}
 		{C90FE60B-B01E-4F35-91D6-379D6966030F} = {9B9E3891-2366-4253-A952-D08BCEB71098}
 		{C90FE60B-B01E-4F35-91D6-379D6966030F} = {9B9E3891-2366-4253-A952-D08BCEB71098}
 		{FED9A71D-00D7-4F40-A9E4-1229EEA28EEB} = {9B9E3891-2366-4253-A952-D08BCEB71098}
 		{FED9A71D-00D7-4F40-A9E4-1229EEA28EEB} = {9B9E3891-2366-4253-A952-D08BCEB71098}
 		{62D392C9-81CF-487F-92E8-598B2AF3FDCE} = {9B9E3891-2366-4253-A952-D08BCEB71098}
 		{62D392C9-81CF-487F-92E8-598B2AF3FDCE} = {9B9E3891-2366-4253-A952-D08BCEB71098}
+		{6A710364-AE6D-40BD-968B-024311527AC2} = {9B9E3891-2366-4253-A952-D08BCEB71098}
+		{8B3E8405-DE18-4048-A459-9CA4AC3319A2} = {9B9E3891-2366-4253-A952-D08BCEB71098}
 	EndGlobalSection
 	EndGlobalSection
 	GlobalSection(ExtensibilityGlobals) = postSolution
 	GlobalSection(ExtensibilityGlobals) = postSolution
 		SolutionGuid = {87366D66-1391-4D90-8999-95A620AD786A}
 		SolutionGuid = {87366D66-1391-4D90-8999-95A620AD786A}

+ 0 - 0
samples/ControlCatalog.Web/App.razor → samples/ControlCatalog.Blazor.Web/App.razor


+ 17 - 0
samples/ControlCatalog.Blazor.Web/App.razor.cs

@@ -0,0 +1,17 @@
+using Avalonia;
+using Avalonia.Web.Blazor;
+
+namespace ControlCatalog.Blazor.Web;
+
+public partial class App
+{
+    protected override void OnParametersSet()
+    {
+        AppBuilder.Configure<ControlCatalog.App>()
+            .UseBlazor()
+            // .With(new SkiaOptions { CustomGpuFactory = null }) // uncomment to disable GPU/GL rendering
+            .SetupWithSingleViewLifetime();
+
+        base.OnParametersSet();
+    }
+}

+ 29 - 0
samples/ControlCatalog.Blazor.Web/ControlCatalog.Blazor.Web.csproj

@@ -0,0 +1,29 @@
+<Project Sdk="Microsoft.NET.Sdk.BlazorWebAssembly">
+  <PropertyGroup>
+    <TargetFramework>net7.0</TargetFramework>
+    <RuntimeIdentifier>browser-wasm</RuntimeIdentifier>
+    <Nullable>enable</Nullable>
+    <EmccTotalMemory>16777216</EmccTotalMemory>
+    <BlazorEnableTimeZoneSupport>false</BlazorEnableTimeZoneSupport>
+    <BlazorWebAssemblyPreserveCollationData>false</BlazorWebAssemblyPreserveCollationData>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="7.0.0-rc.1.22427.2" />
+    <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="7.0.0-rc.1.22427.2" PrivateAssets="all" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <ProjectReference Include="..\..\src\Skia\Avalonia.Skia\Avalonia.Skia.csproj" />
+    <ProjectReference Include="..\..\src\Web\Avalonia.Web.Blazor\Avalonia.Web.Blazor.csproj" />
+    <ProjectReference Include="..\ControlCatalog\ControlCatalog.csproj" />
+  </ItemGroup>
+
+  <Import Project="..\..\build\ReferenceCoreLibraries.props" />
+  <Import Project="..\..\build\BuildTargets.targets" />
+
+  <Import Project="..\..\src\Web\Avalonia.Web\Avalonia.Web.props" />
+  <Import Project="..\..\src\Web\Avalonia.Web\Avalonia.Web.targets" />
+
+</Project>
+

+ 0 - 0
samples/ControlCatalog.Web/Pages/Index.razor → samples/ControlCatalog.Blazor.Web/Pages/Index.razor


+ 29 - 0
samples/ControlCatalog.Blazor.Web/Program.cs

@@ -0,0 +1,29 @@
+using System;
+using System.Net.Http;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
+using Microsoft.Extensions.DependencyInjection;
+using ControlCatalog.Blazor.Web;
+
+public class Program
+{
+    public static async Task  Main(string[] args)
+    {
+        await CreateHostBuilder(args).Build().RunAsync();
+    }
+
+    public static WebAssemblyHostBuilder CreateHostBuilder(string[] args)
+    {
+        var builder = WebAssemblyHostBuilder.CreateDefault(args);
+        
+        builder.RootComponents.Add<App>("#app");
+
+        builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });
+
+        return builder;
+    }
+}
+
+
+
+

+ 0 - 0
samples/ControlCatalog.Web/Properties/launchSettings.json → samples/ControlCatalog.Blazor.Web/Properties/launchSettings.json


+ 0 - 0
samples/ControlCatalog.Web/Shared/MainLayout.razor → samples/ControlCatalog.Blazor.Web/Shared/MainLayout.razor


+ 1 - 2
samples/ControlCatalog.Web/_Imports.razor → samples/ControlCatalog.Blazor.Web/_Imports.razor

@@ -6,6 +6,5 @@
 @using Microsoft.AspNetCore.Components.Web.Virtualization
 @using Microsoft.AspNetCore.Components.Web.Virtualization
 @using Microsoft.AspNetCore.Components.WebAssembly.Http
 @using Microsoft.AspNetCore.Components.WebAssembly.Http
 @using Microsoft.JSInterop
 @using Microsoft.JSInterop
-@using ControlCatalog.Web
-@using ControlCatalog.Web.Shared
+@using ControlCatalog.Blazor.Web.Shared
 @using SkiaSharp
 @using SkiaSharp

+ 0 - 0
samples/ControlCatalog.Web/wwwroot/css/app.css → samples/ControlCatalog.Blazor.Web/wwwroot/css/app.css


+ 0 - 0
samples/ControlCatalog.Web/wwwroot/favicon.ico → samples/ControlCatalog.Blazor.Web/wwwroot/favicon.ico


+ 0 - 0
samples/ControlCatalog.Web/wwwroot/index.html → samples/ControlCatalog.Blazor.Web/wwwroot/index.html


+ 0 - 20
samples/ControlCatalog.Web/App.razor.cs

@@ -1,20 +0,0 @@
-using Avalonia;
-using Avalonia.Web.Blazor;
-
-namespace ControlCatalog.Web;
-
-public partial class App
-{
-    protected override void OnParametersSet()
-    {
-        WebAppBuilder.Configure<ControlCatalog.App>()
-            .AfterSetup(_ =>
-            {
-                ControlCatalog.Pages.EmbedSample.Implementation = new EmbedSampleWeb();
-            })
-            .With(new SkiaOptions { CustomGpuFactory = null }) // uncomment to disable GPU/GL rendering
-            .SetupWithSingleViewLifetime();
-
-        base.OnParametersSet();
-    }
-}

+ 28 - 40
samples/ControlCatalog.Web/ControlCatalog.Web.csproj

@@ -1,57 +1,45 @@
-<Project Sdk="Microsoft.NET.Sdk.BlazorWebAssembly">
+<Project Sdk="Microsoft.NET.Sdk">
   <PropertyGroup>
   <PropertyGroup>
-    <TargetFramework>net6.0</TargetFramework>
-    <Nullable>enable</Nullable>
-    <!--Temporal hack that fixes compilation in VS-->
-    <TypeScriptCompileBlocked>true</TypeScriptCompileBlocked>
-    <EmccTotalMemory>16777216</EmccTotalMemory>
-    <BlazorEnableTimeZoneSupport>false</BlazorEnableTimeZoneSupport>
-    <BlazorWebAssemblyPreserveCollationData>false</BlazorWebAssemblyPreserveCollationData>
+    <TargetFramework>net7.0</TargetFramework>
+    <RuntimeIdentifier>browser-wasm</RuntimeIdentifier>
+    <WasmMainJSPath>main.js</WasmMainJSPath>
+    <OutputType>Exe</OutputType>
+    <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
+    <MSBuildEnableWorkloadResolver>true</MSBuildEnableWorkloadResolver>
+    <WasmBuildNative>true</WasmBuildNative>
+    <EmccFlags>-sVERBOSE -sERROR_ON_UNDEFINED_SYMBOLS=0</EmccFlags>
   </PropertyGroup>
   </PropertyGroup>
 
 
-  <!-- In debug, make builds faster by reducing optimizations -->
-  <PropertyGroup Condition="'$(Configuration)' == 'Debug'">
-    <WasmNativeStrip>false</WasmNativeStrip>
-    <EmccCompileOptimizationFlag>-O1</EmccCompileOptimizationFlag>
-    <RunAOTCompilation>false</RunAOTCompilation>
-  </PropertyGroup>
-
-  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
-    <Optimize>true</Optimize>
-    <WasmNativeStrip>true</WasmNativeStrip>
+  <PropertyGroup Condition="'$(Configuration)'=='Release'">
+    <RunAOTCompilation>true</RunAOTCompilation>
+    <PublishTrimmed>true</PublishTrimmed>
+    <TrimMode>full</TrimMode>
+    <WasmBuildNative>true</WasmBuildNative>
+    <InvariantGlobalization>true</InvariantGlobalization>
+    <WasmEnableSIMD>true</WasmEnableSIMD>
     <EmccCompileOptimizationFlag>-O3</EmccCompileOptimizationFlag>
     <EmccCompileOptimizationFlag>-O3</EmccCompileOptimizationFlag>
     <EmccLinkOptimizationFlag>-O3</EmccLinkOptimizationFlag>
     <EmccLinkOptimizationFlag>-O3</EmccLinkOptimizationFlag>
-    <RunAOTCompilation>false</RunAOTCompilation>
-    <DebuggerSupport>false</DebuggerSupport>
-    <EnableUnsafeBinaryFormatterSerialization>false</EnableUnsafeBinaryFormatterSerialization>
-    <EnableUnsafeUTF7Encoding>false</EnableUnsafeUTF7Encoding>
-    <EventSourceSupport>false</EventSourceSupport>
-    <HttpActivityPropagationSupport>false</HttpActivityPropagationSupport>
-    <InvariantGlobalization>true</InvariantGlobalization>
-    <MetadataUpdaterSupport>false</MetadataUpdaterSupport>
-    <UseNativeHttpHandler>true</UseNativeHttpHandler>
-    <UseSystemResourceKeys>true</UseSystemResourceKeys>
-    <PublishTrimmed>true</PublishTrimmed>
-    <TrimMode>link</TrimMode>
-    <TrimmerRemoveSymbols>true</TrimmerRemoveSymbols>
   </PropertyGroup>
   </PropertyGroup>
 
 
   <ItemGroup>
   <ItemGroup>
-    <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="6.0.0" />
-    <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="6.0.0" PrivateAssets="all" />
+    <TrimmerRootDescriptor Include="Roots.xml" />
   </ItemGroup>
   </ItemGroup>
 
 
   <ItemGroup>
   <ItemGroup>
     <ProjectReference Include="..\..\src\Skia\Avalonia.Skia\Avalonia.Skia.csproj" />
     <ProjectReference Include="..\..\src\Skia\Avalonia.Skia\Avalonia.Skia.csproj" />
-    <ProjectReference Include="..\..\src\Web\Avalonia.Web.Blazor\Avalonia.Web.Blazor.csproj" />
+    <ProjectReference Include="..\..\src\Web\Avalonia.Web\Avalonia.Web.csproj" />
     <ProjectReference Include="..\ControlCatalog\ControlCatalog.csproj" />
     <ProjectReference Include="..\ControlCatalog\ControlCatalog.csproj" />
   </ItemGroup>
   </ItemGroup>
 
 
-  <Import Project="..\..\build\ReferenceCoreLibraries.props" />
-  <Import Project="..\..\build\BuildTargets.targets" />
-
-  <Import Project="..\..\src\Web\Avalonia.Web.Blazor\Avalonia.Web.Blazor.targets" />
-  <Import Project="..\..\src\Web\Avalonia.Web.Blazor\Avalonia.Web.Blazor.CompilationTuning.props" />
+  <ItemGroup>
+    <WasmExtraFilesToDeploy Include="index.html" />
+    <WasmExtraFilesToDeploy Include="main.js" />
+    <WasmExtraFilesToDeploy Include="embed.js" />
+    <WasmExtraFilesToDeploy Include="favicon.ico" />
+    <WasmExtraFilesToDeploy Include="Logo.svg" />
+    <WasmExtraFilesToDeploy Include="app.css" />
+  </ItemGroup>
 
 
+  <Import Project="..\..\src\Web\Avalonia.Web\Avalonia.Web.props" />
+  <Import Project="..\..\src\Web\Avalonia.Web\Avalonia.Web.targets" />
 </Project>
 </Project>
-

+ 20 - 12
samples/ControlCatalog.Web/EmbedSample.Browser.cs

@@ -1,34 +1,42 @@
 using System;
 using System;
-
-using Avalonia;
+using System.Runtime.InteropServices.JavaScript;
 using Avalonia.Platform;
 using Avalonia.Platform;
-using Avalonia.Web.Blazor;
+using Avalonia.Web;
 
 
 using ControlCatalog.Pages;
 using ControlCatalog.Pages;
 
 
-using Microsoft.JSInterop;
-
 namespace ControlCatalog.Web;
 namespace ControlCatalog.Web;
 
 
 public class EmbedSampleWeb : INativeDemoControl
 public class EmbedSampleWeb : INativeDemoControl
 {
 {
     public IPlatformHandle CreateControl(bool isSecond, IPlatformHandle parent, Func<IPlatformHandle> createDefault)
     public IPlatformHandle CreateControl(bool isSecond, IPlatformHandle parent, Func<IPlatformHandle> createDefault)
     {
     {
-        var runtime = AvaloniaLocator.Current.GetRequiredService<IJSInProcessRuntime>();
-
         if (isSecond)
         if (isSecond)
         {
         {
-            var iframe = runtime.Invoke<IJSInProcessObjectReference>("document.createElement", "iframe");
-            iframe.InvokeVoid("setAttribute", "src", "https://www.youtube.com/embed/kZCIporjJ70");
+            var iframe = EmbedInterop.CreateElement("iframe");
+            iframe.SetProperty("src", "https://www.youtube.com/embed/kZCIporjJ70");
 
 
             return new JSObjectControlHandle(iframe);
             return new JSObjectControlHandle(iframe);
         }
         }
         else
         else
         {
         {
-            // window.createAppButton source is defined in "app.js" file.
-            var button = runtime.Invoke<IJSInProcessObjectReference>("window.createAppButton");
+            var defaultHandle = (JSObjectControlHandle)createDefault();
+
+            _ = JSHost.ImportAsync("embed.js", "./embed.js").ContinueWith(_ =>
+            {
+                EmbedInterop.AddAppButton(defaultHandle.Object);
+            });
 
 
-            return new JSObjectControlHandle(button);
+            return defaultHandle;
         }
         }
     }
     }
 }
 }
+
+internal static partial class EmbedInterop
+{
+    [JSImport("globalThis.document.createElement")]
+    public static partial JSObject CreateElement(string tagName);
+
+    [JSImport("addAppButton", "embed.js")]
+    public static partial void AddAppButton(JSObject parentObject);
+}

+ 0 - 0
src/Web/Avalonia.Web.Sample/Logo.svg → samples/ControlCatalog.Web/Logo.svg


+ 12 - 22
samples/ControlCatalog.Web/Program.cs

@@ -1,29 +1,19 @@
-using System;
-using System.Net.Http;
-using System.Threading.Tasks;
-using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
-using Microsoft.Extensions.DependencyInjection;
+using Avalonia;
+using Avalonia.Web;
+using ControlCatalog;
 using ControlCatalog.Web;
 using ControlCatalog.Web;
 
 
-public class Program
+internal partial class Program
 {
 {
-    public static async Task  Main(string[] args)
+    private static void Main(string[] args)
     {
     {
-        await CreateHostBuilder(args).Build().RunAsync();
+        BuildAvaloniaApp()
+            .AfterSetup(_ =>
+            {
+                ControlCatalog.Pages.EmbedSample.Implementation = new EmbedSampleWeb();
+            }).SetupBrowserApp("out");
     }
     }
 
 
-    public static WebAssemblyHostBuilder CreateHostBuilder(string[] args)
-    {
-        var builder = WebAssemblyHostBuilder.CreateDefault(args);
-        
-        builder.RootComponents.Add<App>("#app");
-
-        builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });
-
-        return builder;
-    }
+    public static AppBuilder BuildAvaloniaApp()
+           => AppBuilder.Configure<App>();
 }
 }
-
-
-
-

+ 6 - 0
samples/ControlCatalog.Web/Roots.xml

@@ -0,0 +1,6 @@
+<linker>
+  <assembly fullname="ControlCatalog" preserve="All" />
+  <assembly fullname="ControlCatalog.Web" preserve="All" />
+  <assembly fullname="Avalonia.Themes.Fluent" preserve="All" />
+  <assembly fullname="Avalonia.Themes.Simple" preserve="All" />
+</linker>

+ 17 - 6
src/Web/Avalonia.Web.Sample/app.css → samples/ControlCatalog.Web/app.css

@@ -4,12 +4,15 @@
 }
 }
 
 
 #avalonia-splash {
 #avalonia-splash {
-    position: absolute;
+    position: relative;
     height: 100%;
     height: 100%;
     width: 100%;
     width: 100%;
     color: whitesmoke;
     color: whitesmoke;
     background: #171C2C;
     background: #171C2C;
     font-family: 'Nunito', sans-serif;
     font-family: 'Nunito', sans-serif;
+    background-position: center;
+    background-size: cover;
+    background-repeat: no-repeat;
 }
 }
 
 
 #avalonia-splash a{
 #avalonia-splash a{
@@ -24,15 +27,23 @@
 }
 }
 
 
 .splash-close {
 .splash-close {
-    animation: fadeOut 1s forwards;
+    animation: slide 0.5s linear 1s forwards;
 }
 }
 
 
-@keyframes fadeOut {
-    from {
-        opacity: 1;
+@keyframes slide {
+    0% {
+        top: 0%;
     }
     }
 
 
-    to {
+    50% {
+        opacity: 80%;
+    }
+
+    100% {
+        top: 100%;
+        overflow: hidden;
         opacity: 0;
         opacity: 0;
+        display: none;
+        visibility: collapse;
     }
     }
 }
 }

+ 0 - 0
src/Web/Avalonia.Web.Sample/embed.js → samples/ControlCatalog.Web/embed.js


+ 0 - 0
src/Web/Avalonia.Web.Sample/favicon.ico → samples/ControlCatalog.Web/favicon.ico


+ 1 - 1
src/Web/Avalonia.Web.Sample/index.html → samples/ControlCatalog.Web/index.html

@@ -4,7 +4,7 @@
 <html>
 <html>
 
 
 <head>
 <head>
-    <title>Avalonia.Web.Sample</title>
+    <title>AvaloniaUI - ControlCatalog</title>
     <meta charset="UTF-8">
     <meta charset="UTF-8">
     <meta name="viewport" content="width=device-width, initial-scale=1.0">
     <meta name="viewport" content="width=device-width, initial-scale=1.0">
     <link rel="modulepreload" href="./main.js" />
     <link rel="modulepreload" href="./main.js" />

+ 2 - 2
src/Web/Avalonia.Web.Sample/main.js → samples/ControlCatalog.Web/main.js

@@ -2,7 +2,7 @@
 // The .NET Foundation licenses this file to you under the MIT license.
 // The .NET Foundation licenses this file to you under the MIT license.
 
 
 import { dotnet } from './dotnet.js'
 import { dotnet } from './dotnet.js'
-import { createAvaloniaRuntime } from './avalonia.js';
+import { registerAvaloniaModule } from './avalonia.js';
 
 
 const is_browser = typeof window != "undefined";
 const is_browser = typeof window != "undefined";
 if (!is_browser) throw new Error(`Expected to be running in a browser`);
 if (!is_browser) throw new Error(`Expected to be running in a browser`);
@@ -12,7 +12,7 @@ const dotnetRuntime = await dotnet
     .withApplicationArgumentsFromQuery()
     .withApplicationArgumentsFromQuery()
     .create();
     .create();
 
 
-await createAvaloniaRuntime(dotnetRuntime);
+await registerAvaloniaModule(dotnetRuntime);
 
 
 const config = dotnetRuntime.getConfig();
 const config = dotnetRuntime.getConfig();
 
 

+ 0 - 0
src/Web/Avalonia.Web.Sample/runtimeconfig.template.json → samples/ControlCatalog.Web/runtimeconfig.template.json


+ 0 - 7
src/Web/Avalonia.Web.Blazor/Avalonia.Web.Blazor.CompilationTuning.props

@@ -1,7 +0,0 @@
-<Project>
-  <PropertyGroup>
-    <EmccTotalMemory>16777216</EmccTotalMemory>
-    <BlazorEnableTimeZoneSupport>false</BlazorEnableTimeZoneSupport>
-    <BlazorWebAssemblyPreserveCollationData>false</BlazorWebAssemblyPreserveCollationData>
-  </PropertyGroup>
-</Project>

+ 20 - 33
src/Web/Avalonia.Web.Blazor/Avalonia.Web.Blazor.csproj

@@ -1,53 +1,40 @@
 <Project Sdk="Microsoft.NET.Sdk.Razor">
 <Project Sdk="Microsoft.NET.Sdk.Razor">
 
 
   <PropertyGroup>
   <PropertyGroup>
-    <TargetFramework>net6.0</TargetFramework>
-    <ImplicitUsings>enable</ImplicitUsings>
+    <TargetFramework>net7.0</TargetFramework>
     <PackageId>Avalonia.Web.Blazor</PackageId>
     <PackageId>Avalonia.Web.Blazor</PackageId>
-    <LangVersion>preview</LangVersion>
-    <MSBuildEnableWorkloadResolver>false</MSBuildEnableWorkloadResolver>
-    <StaticWebAssetsDisableProjectBuildPropsFileGeneration>true</StaticWebAssetsDisableProjectBuildPropsFileGeneration>
+    <ResolveStaticWebAssetsInputsDependsOn>_IncludeGeneratedAvaloniaStaticFiles;$(ResolveStaticWebAssetsInputsDependsOn)</ResolveStaticWebAssetsInputsDependsOn>
   </PropertyGroup>
   </PropertyGroup>
 
 
   <ItemGroup>
   <ItemGroup>
     <SupportedPlatform Include="browser" />
     <SupportedPlatform Include="browser" />
   </ItemGroup>
   </ItemGroup>
 
 
-  <Import Project="..\..\..\build\BuildTargets.targets" />
-  <Import Project="..\..\..\build\SkiaSharp.props" />
-  <Import Project="..\..\..\build\HarfBuzzSharp.props" />
   <Import Project="..\..\..\build\NullableEnable.props" />
   <Import Project="..\..\..\build\NullableEnable.props" />
 
 
   <ItemGroup>
   <ItemGroup>
-    <Content Include="*.props">
-      <Pack>true</Pack>
-      <PackagePath>build\</PackagePath>
-    </Content>
-    <Content Include="*.targets">
-      <Pack>true</Pack>
-      <PackagePath>build\;buildTransitive\</PackagePath>
-    </Content>
+    <PackageReference Include="Microsoft.AspNetCore.Components.Web" Version="7.0.0-*" />
   </ItemGroup>
   </ItemGroup>
 
 
   <ItemGroup>
   <ItemGroup>
-    <PackageReference Include="Microsoft.AspNetCore.Components.Web" Version="6.0.8" />
-  </ItemGroup>
-  
-  <ItemGroup>
-    <ProjectReference Include="..\..\Avalonia.Base\Avalonia.Base.csproj" />
-    <ProjectReference Include="..\..\Skia\Avalonia.Skia\Avalonia.Skia.csproj" />
-  </ItemGroup>
-
-  <ItemGroup>
-    <Folder Include="wwwroot\" />
+    <ProjectReference Include="..\Avalonia.Web\Avalonia.Web.csproj" />
   </ItemGroup>
   </ItemGroup>
 
 
-  <Target Name="NpmInstall" Inputs="webapp/package.json" Outputs="webapp/node_modules/.install-stamp">
-    <Exec Command="npm install" WorkingDirectory="webapp" />
-    <!-- Write the stamp file, so incremental builds work -->
-    <Touch Files="webapp/node_modules/.install-stamp" AlwaysCreate="true" />
-  </Target>
-  <Target Name="NpmRunBuild" DependsOnTargets="NpmInstall" BeforeTargets="BeforeBuild">
-    <Exec Command="npm run build" WorkingDirectory="webapp" />
+  <Target Name="_IncludeGeneratedAvaloniaStaticFiles">
+    <ItemGroup>
+      <_AvaloniaWebAssets Include="$(MSBuildThisFileDirectory)../Avalonia.Web/wwwroot/**/*.*" />
+    </ItemGroup>
+    <DefineStaticWebAssets SourceId="$(PackageId)"
+                           SourceType="Computed"
+                           AssetKind="All"
+                           AssetRole="Primary"
+                           CopyToOutputDirectory="PreserveNewest"
+                           CopyToPublishDirectory="PreserveNewest"
+                           ContentRoot="$(MSBuildThisFileDirectory)../Avalonia.Web/wwwroot/"
+                           BasePath="_content\$(PackageId)"
+                           CandidateAssets="@(_AvaloniaWebAssets)"
+                           RelativePathFilter="**.js">
+      <Output TaskParameter="Assets" ItemName="StaticWebAsset" />
+    </DefineStaticWebAssets>
   </Target>
   </Target>
 </Project>
 </Project>

+ 0 - 4
src/Web/Avalonia.Web.Blazor/Avalonia.Web.Blazor.props

@@ -1,4 +0,0 @@
-<Project>
-  <Import Project="Microsoft.AspNetCore.StaticWebAssets.props" />
-  <Import Project="Avalonia.Web.Blazor.CompilationTuning.props" />
-</Project>

+ 0 - 6
src/Web/Avalonia.Web.Blazor/Avalonia.Web.Blazor.targets

@@ -1,6 +0,0 @@
-<Project>
-  <ItemGroup>
-    <NativeFileReference Include="$(HarfBuzzSharpStaticLibraryPath)\2.0.23\libHarfBuzzSharp.a" />
-    <NativeFileReference Include="$(SkiaSharpStaticLibraryPath)\2.0.23\libSkiaSharp.a" />
-  </ItemGroup>
-</Project>

+ 0 - 20
src/Web/Avalonia.Web.Blazor/AvaloniaBlazorAppBuilder.cs

@@ -1,20 +0,0 @@
-using Avalonia.Controls;
-using Avalonia.Platform;
-
-namespace Avalonia.Web.Blazor
-{
-    public class AvaloniaBlazorAppBuilder : AppBuilderBase<AvaloniaBlazorAppBuilder>
-    {
-        public AvaloniaBlazorAppBuilder(IRuntimePlatform platform, Action<AvaloniaBlazorAppBuilder> platformServices)
-            : base(platform, platformServices)
-        {
-        }
-
-        public AvaloniaBlazorAppBuilder()
-            : base(new StandardRuntimePlatform(),
-                builder => StandardRuntimePlatformServices.Register(builder.ApplicationType!.Assembly))
-        {
-            UseWindowingSubsystem(BlazorWindowingPlatform.Register);
-        }
-    }
-}

+ 44 - 0
src/Web/Avalonia.Web.Blazor/AvaloniaView.cs

@@ -0,0 +1,44 @@
+using System.Runtime.InteropServices.JavaScript;
+using System.Runtime.Versioning;
+using System.Threading.Tasks;
+using System;
+using Avalonia.Controls.ApplicationLifetimes;
+using Microsoft.AspNetCore.Components;
+using Microsoft.AspNetCore.Components.Rendering;
+using BrowserView = Avalonia.Web.AvaloniaView;
+
+namespace Avalonia.Web.Blazor;
+
+[SupportedOSPlatform("browser")]
+public class AvaloniaView : ComponentBase
+{
+    private BrowserView? _browserView;
+    private readonly string _containerId;
+
+    public AvaloniaView()
+    {
+        _containerId = "av_container_" + Guid.NewGuid();
+    }
+
+    protected override void BuildRenderTree(RenderTreeBuilder builder)
+    {
+        builder.OpenElement(0, "div");
+        builder.AddAttribute(1, "id", _containerId);
+        builder.AddAttribute(2, "style", "width:100vw;height:100vh");
+        builder.CloseElement();
+    }
+
+    protected override async Task OnInitializedAsync()
+    {
+        if (OperatingSystem.IsBrowser())
+        {
+            await Avalonia.Web.Interop.AvaloniaModule.ImportMain();
+
+            _browserView = new BrowserView(_containerId);
+            if (Application.Current?.ApplicationLifetime is ISingleViewApplicationLifetime lifetime)
+            {
+                _browserView.Content = lifetime.MainView;
+            }
+        }
+    }
+}

+ 0 - 67
src/Web/Avalonia.Web.Blazor/AvaloniaView.razor

@@ -1,67 +0,0 @@
-<div id="container" @ref="_containerElement"  
-     class="avalonia-container" 
-     tabindex="0" oncontextmenu="return false;"
-     @onwheel="OnWheel"
-     @onkeydown="OnKeyDown"
-     @onkeyup="OnKeyUp"
-     @onkeyup:preventDefault="@KeyPreventDefault"
-     @onkeydown:preventDefault="@KeyPreventDefault"
-     @onpointerdown="OnPointerDown"
-     @onpointerup="OnPointerUp"
-     @onpointermove="OnPointerMove"
-     @onpointercancel="OnPointerCancel"
-     @onfocus="OnFocus">
-    
-    <canvas id="htmlCanvas" @ref="_htmlCanvas" @attributes="AdditionalAttributes"/>
-
-	<div id="nativeControlsContainer" @ref="_nativeControlsContainer" />
-
-    <input id="inputElement" @ref="_inputElement" type="text" 
-           spellcheck="false" onpaste="return false;"
-        oncopy="return false;" 
-        oncut="return false;"
-        autocapitalize="none"/>
-</div>
-
-<style>
-#container{
-    touch-action: none;
-}
-#htmlCanvas {
-    opacity: 1;
-    background-color: #ccc;
-    position: fixed;
-    width: 100vw;
-    height: 100vh;
-    top: 0px;
-    left: 0px;
-    z-index: 500;
-}
-
-#nativeControlsContainer {
-    position: fixed;
-    width: 100vw;
-    height: 100vh;
-    top: 0px;
-    left: 0px;
-    z-index: 700;
-}
-
-#inputElement {
-    padding: 0;
-    margin: 0;
-    position: absolute;
-    height: 20px;
-    z-index: 1000;
-    overflow: hidden;
-    caret-color: transparent;
-    border-top-style: hidden;
-    border-bottom-style: hidden;
-    border-right-style: hidden;
-    border-left-style: hidden;
-    outline: none;
-    background: transparent;
-    color: transparent;
-}
-
-</style>

+ 0 - 500
src/Web/Avalonia.Web.Blazor/AvaloniaView.razor.cs

@@ -1,500 +0,0 @@
-using System.Diagnostics;
-using Avalonia.Controls.ApplicationLifetimes;
-using Avalonia.Controls.Embedding;
-using Avalonia.Controls.Platform;
-using Avalonia.Input;
-using Avalonia.Input.Raw;
-using Avalonia.Input.TextInput;
-using Avalonia.Platform.Storage;
-using Avalonia.Rendering;
-using Avalonia.Rendering.Composition;
-using Avalonia.Web.Blazor.Interop;
-using Avalonia.Web.Blazor.Interop.Storage;
-
-using Microsoft.AspNetCore.Components;
-using Microsoft.AspNetCore.Components.Forms;
-using Microsoft.AspNetCore.Components.Web;
-using Microsoft.JSInterop;
-
-using SkiaSharp;
-using HTMLPointerEventArgs = Microsoft.AspNetCore.Components.Web.PointerEventArgs;
-
-namespace Avalonia.Web.Blazor
-{
-    public partial class AvaloniaView : ITextInputMethodImpl
-    {
-        private readonly RazorViewTopLevelImpl _topLevelImpl;
-        private EmbeddableControlRoot _topLevel;
-
-        // Interop
-        private SKHtmlCanvasInterop? _interop = null;
-        private SizeWatcherInterop? _sizeWatcher = null;
-        private DpiWatcherInterop? _dpiWatcher = null;
-        private SKHtmlCanvasInterop.GLInfo? _jsGlInfo = null;
-        private AvaloniaModule? _avaloniaModule = null;
-        private InputHelperInterop? _inputHelper = null;
-        private FocusHelperInterop? _canvasHelper = null;
-        private FocusHelperInterop? _containerHelper = null;
-        private NativeControlHostInterop? _nativeControlHost = null;
-        private StorageProviderInterop? _storageProvider = null;
-        private ElementReference _htmlCanvas;
-        private ElementReference _inputElement;
-        private ElementReference _containerElement;
-        private ElementReference _nativeControlsContainer;
-        private double _dpi = 1;
-        private SKSize _canvasSize = new (100, 100);
-
-        private GRContext? _context;
-        private GRGlInterface? _glInterface;
-        private const SKColorType ColorType = SKColorType.Rgba8888;
-
-        private bool _useGL;
-        private bool _inputElementFocused;
-
-        private ITextInputMethodClient? _client;
-
-
-        [Inject] private IJSRuntime Js { get; set; } = null!;
-
-        public AvaloniaView()
-        {
-            _topLevelImpl = new RazorViewTopLevelImpl(this);
-
-            _topLevel = new EmbeddableControlRoot(_topLevelImpl);
-
-            if (Application.Current?.ApplicationLifetime is ISingleViewApplicationLifetime lifetime)
-            {
-                _topLevel.Content = lifetime.MainView;
-            }
-        }
-
-        public bool KeyPreventDefault { get; set; }
-        
-        public ITextInputMethodClient? Client => _client;
-
-        public bool IsActive => _client != null;
-
-        public bool IsComposing { get; private set; }
-
-        internal INativeControlHostImpl GetNativeControlHostImpl()
-        {
-            return _nativeControlHost ?? throw new InvalidOperationException("Blazor View wasn't initialized yet");
-        }
-
-        internal IStorageProvider GetStorageProvider()
-        {
-            return _storageProvider ?? throw new InvalidOperationException("Blazor View wasn't initialized yet");
-        }
-
-        private void OnPointerCancel(HTMLPointerEventArgs e)
-        {
-            if (e.PointerType == "touch")
-            {
-                _topLevelImpl.RawPointerEvent(RawPointerEventType.TouchCancel, e.PointerType, GetPointFromEventArgs(e),
-                    GetModifiers(e), e.PointerId);
-            }
-        }
-
-        private void OnPointerMove(HTMLPointerEventArgs e)
-        {
-            var type = e.PointerType switch
-            {
-                "touch" => RawPointerEventType.TouchUpdate,
-                _ => RawPointerEventType.Move
-            };
-
-            _topLevelImpl.RawPointerEvent(type, e.PointerType, GetPointFromEventArgs(e), GetModifiers(e), e.PointerId);
-        }
-
-        private void OnPointerUp(HTMLPointerEventArgs e)
-        {
-            var type = e.PointerType switch
-            {
-                "touch" => RawPointerEventType.TouchEnd,
-                _ => e.Button switch
-                {
-                    0 => RawPointerEventType.LeftButtonUp,
-                    1 => RawPointerEventType.MiddleButtonUp,
-                    2 => RawPointerEventType.RightButtonUp,
-                    3 => RawPointerEventType.XButton1Up,
-                    4 => RawPointerEventType.XButton2Up,
-                    // 5 => Pen eraser button,
-                    _ => RawPointerEventType.Move
-                }
-            };
-
-            _topLevelImpl.RawPointerEvent(type, e.PointerType, GetPointFromEventArgs(e), GetModifiers(e), e.PointerId);
-        }
-
-        private void OnPointerDown(HTMLPointerEventArgs e)
-        {
-            var type = e.PointerType switch
-            {
-                "touch" => RawPointerEventType.TouchBegin,
-                _ => e.Button switch
-                {
-                    0 => RawPointerEventType.LeftButtonDown,
-                    1 => RawPointerEventType.MiddleButtonDown,
-                    2 => RawPointerEventType.RightButtonDown,
-                    3 => RawPointerEventType.XButton1Down,
-                    4 => RawPointerEventType.XButton2Down,
-                    // 5 => Pen eraser button,
-                    _ => RawPointerEventType.Move
-                }
-            };
-
-            _topLevelImpl.RawPointerEvent(type, e.PointerType, GetPointFromEventArgs(e), GetModifiers(e), e.PointerId);
-        }
-
-        private static RawPointerPoint GetPointFromEventArgs(HTMLPointerEventArgs args)
-        {
-            return new RawPointerPoint
-            {
-                Position = new Point(args.ClientX, args.ClientY),
-                Pressure = args.Pressure,
-                XTilt = args.TiltX,
-                YTilt = args.TiltY
-                // Twist = args.Twist - read from JS code directly when
-            };
-        }
-
-        private void OnWheel(WheelEventArgs e)
-        {
-            _topLevelImpl.RawMouseWheelEvent( new Point(e.ClientX, e.ClientY),
-                new Vector(-(e.DeltaX / 50), -(e.DeltaY / 50)), GetModifiers(e));
-        }
-
-        private static RawInputModifiers GetModifiers(MouseEventArgs e)
-        {
-            var modifiers = RawInputModifiers.None;
-
-            if (e.CtrlKey)
-                modifiers |= RawInputModifiers.Control;
-            if (e.AltKey)
-                modifiers |= RawInputModifiers.Alt;
-            if (e.ShiftKey)
-                modifiers |= RawInputModifiers.Shift;
-            if (e.MetaKey)
-                modifiers |= RawInputModifiers.Meta;
-
-            if ((e.Buttons & 1L) == 1)
-                modifiers |= RawInputModifiers.LeftMouseButton;
-
-            if ((e.Buttons & 2L) == 2)
-                modifiers |= e.Type == "pen" ? RawInputModifiers.PenBarrelButton : RawInputModifiers.RightMouseButton;
-
-            if ((e.Buttons & 4L) == 4)
-                modifiers |= RawInputModifiers.MiddleMouseButton;
-            
-            if ((e.Buttons & 8L) == 8)
-                modifiers |= RawInputModifiers.XButton1MouseButton;
-            
-            if ((e.Buttons & 16L) == 16)
-                modifiers |= RawInputModifiers.XButton2MouseButton;
-            
-            if ((e.Buttons & 32L) == 32)
-                modifiers |= RawInputModifiers.PenEraser;
-
-            return modifiers;
-        }
-
-        private static RawInputModifiers GetModifiers(KeyboardEventArgs e)
-        {
-            var modifiers = RawInputModifiers.None;
-
-            if (e.CtrlKey)
-                modifiers |= RawInputModifiers.Control;
-            if (e.AltKey)
-                modifiers |= RawInputModifiers.Alt;
-            if (e.ShiftKey)
-                modifiers |= RawInputModifiers.Shift;
-            if (e.MetaKey)
-                modifiers |= RawInputModifiers.Meta;
-
-            return modifiers;
-        }
-
-        private void OnKeyDown(KeyboardEventArgs e)
-        {
-            KeyPreventDefault = _topLevelImpl.RawKeyboardEvent(RawKeyEventType.KeyDown, e.Code, e.Key, GetModifiers(e));
-        }
-
-        private void OnKeyUp(KeyboardEventArgs e)
-        {
-            KeyPreventDefault = _topLevelImpl.RawKeyboardEvent(RawKeyEventType.KeyUp, e.Code, e.Key, GetModifiers(e));
-        }
-
-        private void OnFocus(FocusEventArgs e)
-        {
-            // if focus has unexpectedly moved from the input element to the container element,
-            // shift it back to the input element
-            if (_inputElementFocused && _inputHelper is not null)
-            {
-                _inputHelper.Focus();
-            }
-        }
-
-        [Parameter(CaptureUnmatchedValues = true)]
-        public IReadOnlyDictionary<string, object>? AdditionalAttributes { get; set; }
-
-        protected override async Task OnAfterRenderAsync(bool firstRender)
-        {
-            if (firstRender)
-            {
-                AvaloniaLocator.CurrentMutable.Bind<IJSInProcessRuntime>().ToConstant((IJSInProcessRuntime)Js);
-
-                _avaloniaModule = await AvaloniaModule.ImportAsync(Js);
-                
-                _canvasHelper = new FocusHelperInterop(_avaloniaModule, _htmlCanvas);
-                _containerHelper = new FocusHelperInterop(_avaloniaModule, _containerElement);
-                _inputHelper = new InputHelperInterop(_avaloniaModule, _inputElement);
-                
-                _inputHelper.CompositionEvent += InputHelperOnCompositionEvent;
-                _inputHelper.InputEvent += InputHelperOnInputEvent;
-
-                HideIme();
-                _canvasHelper.SetCursor("default");
-                _topLevelImpl.SetCssCursor = x =>
-                {
-                    _inputHelper.SetCursor(x); //macOS
-                    _canvasHelper.SetCursor(x); //windows
-                };
-
-                _nativeControlHost = new NativeControlHostInterop(_avaloniaModule, _nativeControlsContainer);
-                _storageProvider = await StorageProviderInterop.ImportAsync(Js);
-                
-                Console.WriteLine("starting html canvas setup");
-                _interop = new SKHtmlCanvasInterop(_avaloniaModule, _htmlCanvas, OnRenderFrame);
-
-                Console.WriteLine("Interop created");
-                
-                var skiaOptions = AvaloniaLocator.Current.GetService<SkiaOptions>();
-                _useGL = skiaOptions?.CustomGpuFactory != null;
-
-                if (_useGL)
-                {
-                    _jsGlInfo = _interop.InitGL();
-                    Console.WriteLine("jsglinfo created - init gl");
-                }
-                else
-                {
-                    var rasterInitialized = _interop.InitRaster();
-                    Console.WriteLine("raster initialized: {0}", rasterInitialized);
-                }
-
-                if (_useGL)
-                {
-                    // create the SkiaSharp context
-                    if (_context == null)
-                    {
-                        _glInterface = GRGlInterface.Create();
-                        _context = GRContext.CreateGl(_glInterface);
-
-                        
-                        // bump the default resource cache limit
-                        _context.SetResourceCacheLimit(skiaOptions?.MaxGpuResourceSizeBytes ?? 32 * 1024 * 1024);
-                        Console.WriteLine("glcontext created and resource limit set");
-                    }
-
-                    _topLevelImpl.SetSurface(_context, _jsGlInfo!, ColorType,
-                        new PixelSize((int)_canvasSize.Width, (int)_canvasSize.Height), _dpi);
-                }
-                else
-                {
-                    _topLevelImpl.SetSurface(ColorType,
-                        new PixelSize((int)_canvasSize.Width, (int)_canvasSize.Height), _dpi, _interop.PutImageData);
-                }
-                
-                _interop.SetCanvasSize((int)(_canvasSize.Width * _dpi), (int)(_canvasSize.Height * _dpi));
-
-                Threading.Dispatcher.UIThread.Post(async () =>
-                {
-                    _interop.RequestAnimationFrame(true);
-                    
-                    _sizeWatcher = new SizeWatcherInterop(_avaloniaModule, _htmlCanvas, OnSizeChanged);
-                    _dpiWatcher = new DpiWatcherInterop(_avaloniaModule, OnDpiChanged);
-                    
-                    _sizeWatcher.Start();
-                    _topLevel.Prepare();
-
-                    _topLevel.Renderer.Start();
-                });
-            }
-        }
-
-        private void InputHelperOnInputEvent(object? sender, WebInputEventArgs e)
-        {
-            if (IsComposing)
-            {
-                return;
-            }
-
-            _topLevelImpl.RawTextEvent(e.Data);
-
-            e.Handled = true;
-        }
-
-        private void InputHelperOnCompositionEvent(object? sender, WebCompositionEventArgs e)
-        {
-            if(_client == null)
-            {
-                return;
-            }
-
-            switch (e.Type)
-            {
-                case WebCompositionEventArgs.WebCompositionEventType.Start:
-                    _client.SetPreeditText(null);
-                    IsComposing = true;
-                    break;
-                case WebCompositionEventArgs.WebCompositionEventType.Update:
-                    _client.SetPreeditText(e.Data);
-                    break;
-                case WebCompositionEventArgs.WebCompositionEventType.End:
-                    IsComposing = false;
-                    _client.SetPreeditText(null);
-                    _topLevelImpl.RawTextEvent(e.Data);              
-                    break;
-            }
-        }
-
-        private void OnRenderFrame()
-        {
-            if (_useGL && (_jsGlInfo == null))
-            {
-                Console.WriteLine("nothing to render");
-                return;
-            }
-            if (_canvasSize.Width <= 0 || _canvasSize.Height <= 0 || _dpi <= 0)
-            {
-                Console.WriteLine("nothing to render");
-                return;
-            }
-
-            ManualTriggerRenderTimer.Instance.RaiseTick();
-        }
-
-        public void Dispose()
-        {
-            _dpiWatcher?.Unsubscribe(OnDpiChanged);
-            _sizeWatcher?.Dispose();
-            _interop?.Dispose();
-        }
-
-        private void ForceBlit()
-        {
-            // Note: this is technically a hack, but it's a kinda unique use case when
-            // we want to blit the previous frame
-            // renderer doesn't have much control over the render target
-            // we render on the UI thread
-            // We also don't want to have it as a meaningful public API.
-            // Therefore we have InternalsVisibleTo hack here.
-
-            if (_topLevel.Renderer is CompositingRenderer dr)
-            {
-                dr.CompositionTarget.ImmediateUIThreadRender();
-            }
-        }
-
-        private void OnDpiChanged(double newDpi)
-        {
-            if (Math.Abs(_dpi - newDpi) > 0.0001)
-            {
-                _dpi = newDpi;
-
-                _interop!.SetCanvasSize((int)(_canvasSize.Width * _dpi), (int)(_canvasSize.Height * _dpi));
-
-                _topLevelImpl.SetClientSize(_canvasSize, _dpi);
-
-                ForceBlit();
-            }
-        }
-
-        private void OnSizeChanged(SKSize newSize)
-        {
-            if (_canvasSize != newSize)
-            {
-                _canvasSize = newSize;
-
-                _interop!.SetCanvasSize((int)(_canvasSize.Width * _dpi), (int)(_canvasSize.Height * _dpi));
-
-                _topLevelImpl.SetClientSize(_canvasSize, _dpi);
-
-                ForceBlit();
-            }
-        }
-
-        private void HideIme()
-        {
-            _inputHelper?.Hide();
-            _containerHelper?.Focus();
-        }
-
-        public void SetClient(ITextInputMethodClient? client)
-        {
-            if (_inputHelper is null)
-            {
-                return;
-            }
-
-            if(_client != null)
-            {
-                _client.SurroundingTextChanged -= SurroundingTextChanged;
-            }
-
-            if(client != null)
-            {
-                client.SurroundingTextChanged += SurroundingTextChanged;
-            }
-
-            _inputHelper.Clear();
-
-            _client = client;
-            
-            if (IsActive && _client != null)
-            {
-                _inputHelper.Show();
-                _inputElementFocused = true;
-                _inputHelper.Focus();
-                
-                var surroundingText = _client.SurroundingText;
-
-                _inputHelper?.SetSurroundingText(surroundingText.Text, surroundingText.AnchorOffset, surroundingText.CursorOffset);
-            }
-            else
-            {
-                _inputElementFocused = false;
-                HideIme();
-            }
-        }
-
-        private void SurroundingTextChanged(object? sender, EventArgs e)
-        {
-            if(_client != null)
-            {
-                var surroundingText = _client.SurroundingText;
-                
-                _inputHelper?.SetSurroundingText(surroundingText.Text, surroundingText.AnchorOffset, surroundingText.CursorOffset);
-            }
-        }
-
-        public void SetCursorRect(Rect rect)
-        {
-            _inputHelper?.Focus();
-            var bounds = new PixelRect((int)rect.X, (int) rect.Y, (int) rect.Width, (int) rect.Height);
-            
-            _inputHelper?.SetBounds(bounds, _client?.SurroundingText.CursorOffset ?? 0);
-            _inputHelper?.Focus();
-        }
-
-        public void SetOptions(TextInputOptions options)
-        {
-        }
-
-        public void Reset()
-        {
-            _inputHelper?.Clear();
-            _inputHelper?.SetSurroundingText("", 0, 0);
-        }
-    }
-}

+ 28 - 20
src/Web/Avalonia.Web.Blazor/BlazorSingleViewLifetime.cs

@@ -1,31 +1,39 @@
-using Avalonia.Controls;
+using System.Runtime.Versioning;
+
+using Avalonia.Controls;
 using Avalonia.Controls.ApplicationLifetimes;
 using Avalonia.Controls.ApplicationLifetimes;
-using Avalonia.Media;
 
 
-namespace Avalonia.Web.Blazor
+namespace Avalonia.Web.Blazor;
+
+[SupportedOSPlatform("browser")]
+public static class WebAppBuilder
 {
 {
-    public class BlazorSingleViewLifetime : ISingleViewApplicationLifetime
+    public static T SetupWithSingleViewLifetime<T>(
+        this T builder)
+        where T : AppBuilderBase<T>, new()
     {
     {
-        public Control? MainView { get; set; }
+        return builder.SetupWithLifetime(new BlazorSingleViewLifetime());
     }
     }
 
 
-    public static class WebAppBuilder
+    public static T UseBlazor<T>(this T builder) where T : AppBuilderBase<T>, new()
     {
     {
-        public static T SetupWithSingleViewLifetime<T>(
-            this T builder)
-            where T : AppBuilderBase<T>, new()
-        {
-            return builder.SetupWithLifetime(new BlazorSingleViewLifetime());
-        }
+        return builder
+            .UseBrowser()
+            .With(new BrowserPlatformOptions
+            {
+                FrameworkAssetPathResolver = new(filePath => $"/_content/Avalonia.Web.Blazor/{filePath}")
+            });
+    }
 
 
-        public static AvaloniaBlazorAppBuilder Configure<TApp>()
-            where TApp : Application, new()
-        {
-            var builder = AvaloniaBlazorAppBuilder.Configure<TApp>()
-                .UseSkia()
-                .With(new SkiaOptions { CustomGpuFactory = () => new BlazorSkiaGpu() });
+    public static AppBuilder Configure<TApp>()
+        where TApp : Application, new()
+    {
+        return AppBuilder.Configure<TApp>()
+            .UseBlazor();
+    }
 
 
-            return builder;
-        }
+    internal class BlazorSingleViewLifetime : ISingleViewApplicationLifetime
+    {
+        public Control? MainView { get; set; }
     }
     }
 }
 }

+ 0 - 25
src/Web/Avalonia.Web.Blazor/BlazorSkiaGpu.cs

@@ -1,25 +0,0 @@
-using Avalonia.Skia;
-
-namespace Avalonia.Web.Blazor
-{
-    public class BlazorSkiaGpu : ISkiaGpu
-    {
-        public ISkiaGpuRenderTarget? TryCreateRenderTarget(IEnumerable<object> surfaces)
-        {
-            foreach (var surface in surfaces)
-            {
-                if (surface is BlazorSkiaSurface blazorSkiaSurface)
-                {
-                    return new BlazorSkiaGpuRenderTarget(blazorSkiaSurface);
-                }
-            }
-
-            return null;
-        }
-
-        public ISkiaSurface? TryCreateSurface(PixelSize size, ISkiaGpuRenderSession session)
-        {
-            return null;
-        }
-    }
-}

+ 0 - 37
src/Web/Avalonia.Web.Blazor/BlazorSkiaGpuRenderSession.cs

@@ -1,37 +0,0 @@
-using Avalonia.Skia;
-using SkiaSharp;
-
-namespace Avalonia.Web.Blazor
-{
-    internal class BlazorSkiaGpuRenderSession : ISkiaGpuRenderSession
-    {
-        private readonly SKSurface _surface;
-
-
-        public BlazorSkiaGpuRenderSession(BlazorSkiaSurface blazorSkiaSurface, GRBackendRenderTarget renderTarget)
-        {
-            _surface = SKSurface.Create(blazorSkiaSurface.Context, renderTarget, blazorSkiaSurface.Origin, blazorSkiaSurface.ColorType);
-
-            GrContext = blazorSkiaSurface.Context;
-
-            ScaleFactor = blazorSkiaSurface.Scaling;
-
-            SurfaceOrigin = blazorSkiaSurface.Origin;
-        }
-
-        public void Dispose()
-        {
-            _surface.Flush();
-
-            _surface.Dispose();
-        }
-
-        public GRContext GrContext { get; }
-
-        public SKSurface SkSurface => _surface;
-
-        public double ScaleFactor { get; }
-
-        public GRSurfaceOrigin SurfaceOrigin { get; }
-    }
-}

+ 0 - 39
src/Web/Avalonia.Web.Blazor/BlazorSkiaGpuRenderTarget.cs

@@ -1,39 +0,0 @@
-using Avalonia.Skia;
-using SkiaSharp;
-
-namespace Avalonia.Web.Blazor
-{
-    internal class BlazorSkiaGpuRenderTarget : ISkiaGpuRenderTarget
-    {
-        private readonly GRBackendRenderTarget _renderTarget;
-        private readonly BlazorSkiaSurface _blazorSkiaSurface;
-        private readonly PixelSize _size;
-
-        public BlazorSkiaGpuRenderTarget(BlazorSkiaSurface blazorSkiaSurface)
-        {
-            _size = blazorSkiaSurface.Size;
-
-            var glFbInfo = new GRGlFramebufferInfo(blazorSkiaSurface.GlInfo.FboId, blazorSkiaSurface.ColorType.ToGlSizedFormat());
-            {
-                _blazorSkiaSurface = blazorSkiaSurface;
-                _renderTarget = new GRBackendRenderTarget(
-                    (int)(blazorSkiaSurface.Size.Width * blazorSkiaSurface.Scaling),
-                    (int)(blazorSkiaSurface.Size.Height * blazorSkiaSurface.Scaling),
-                    blazorSkiaSurface.GlInfo.Samples,
-                    blazorSkiaSurface.GlInfo.Stencils, glFbInfo);
-            }
-        }
-
-        public void Dispose()
-        {
-            _renderTarget.Dispose();
-        }
-
-        public ISkiaGpuRenderSession BeginRenderingSession()
-        {
-            return new BlazorSkiaGpuRenderSession(_blazorSkiaSurface, _renderTarget);
-        }
-
-        public bool IsCorrupted => _blazorSkiaSurface.Size != _size;
-    }
-}

+ 0 - 87
src/Web/Avalonia.Web.Blazor/BlazorSkiaRasterSurface.cs

@@ -1,87 +0,0 @@
-using System.Runtime.InteropServices;
-using Avalonia.Controls.Platform.Surfaces;
-using Avalonia.Platform;
-using Avalonia.Skia;
-using SkiaSharp;
-
-namespace Avalonia.Web.Blazor
-{
-    internal class BlazorSkiaRasterSurface : IBlazorSkiaSurface, IFramebufferPlatformSurface, IDisposable
-    {
-        public SKColorType ColorType { get; set; }
-
-        public PixelSize Size { get; set; }
-
-        public double Scaling { get; set; }
-
-        private FramebufferData? _fbData;
-        private readonly Action<IntPtr, SKSizeI> _blitCallback;
-        private readonly Action _onDisposeAction;
-
-        public BlazorSkiaRasterSurface(
-            SKColorType colorType, PixelSize size, double scaling, Action<IntPtr, SKSizeI> blitCallback)
-        {
-            ColorType = colorType;
-            Size = size;
-            Scaling = scaling;
-            _blitCallback = blitCallback;
-            _onDisposeAction = Blit;
-        }
-
-        public void Dispose()
-        {
-            _fbData?.Dispose();
-            _fbData = null;
-        }
-
-        public ILockedFramebuffer Lock()
-        {
-            var bytesPerPixel = 4; // TODO: derive from ColorType
-            var dpi = Scaling * 96.0;
-            var width = (int)(Size.Width * Scaling);
-            var height = (int)(Size.Height * Scaling);
-
-            if (_fbData is null || _fbData?.Size.Width != width || _fbData?.Size.Height != height)
-            {
-                _fbData?.Dispose();
-                _fbData = new FramebufferData(width, height, bytesPerPixel);
-            }
-
-            var pixelFormat = ColorType.ToPixelFormat();
-            var data = _fbData.Value;
-            return new LockedFramebuffer(
-                data.Address, data.Size, data.RowBytes,
-                new Vector(dpi, dpi), pixelFormat, _onDisposeAction);
-        }
-
-        private void Blit()
-        {
-            if (_fbData != null)
-            {
-                var data = _fbData.Value;
-                _blitCallback(data.Address, new SKSizeI(data.Size.Width, data.Size.Height));
-            }
-        }
-
-        private readonly struct FramebufferData
-        {
-            public PixelSize Size { get; }
-
-            public int RowBytes { get; }
-
-            public IntPtr Address { get; }
-
-            public FramebufferData(int width, int height, int bytesPerPixel)
-            {
-                Size = new PixelSize(width, height);
-                RowBytes = width * bytesPerPixel;
-                Address = Marshal.AllocHGlobal(width * height * bytesPerPixel);
-            }
-
-            public void Dispose()
-            {
-                Marshal.FreeHGlobal(Address);
-            }
-        }
-    }
-}

+ 0 - 30
src/Web/Avalonia.Web.Blazor/BlazorSkiaSurface.cs

@@ -1,30 +0,0 @@
-using Avalonia.Web.Blazor.Interop;
-using SkiaSharp;
-
-namespace Avalonia.Web.Blazor
-{
-    internal class BlazorSkiaSurface : IBlazorSkiaSurface
-    {
-        public BlazorSkiaSurface(GRContext context, SKHtmlCanvasInterop.GLInfo glInfo, SKColorType colorType, PixelSize size, double scaling, GRSurfaceOrigin origin)
-        {
-            Context = context;
-            GlInfo = glInfo;
-            ColorType = colorType;
-            Size = size;
-            Scaling = scaling;
-            Origin = origin;
-        }
-        
-        public SKColorType ColorType { get; set; }
-
-        public PixelSize Size { get; set; }
-
-        public GRContext Context { get; set; }
-
-        public GRSurfaceOrigin Origin { get; set; }
-
-        public double Scaling { get; set; }
-
-        public SKHtmlCanvasInterop.GLInfo GlInfo { get; set; }
-    }
-}

+ 0 - 34
src/Web/Avalonia.Web.Blazor/ClipboardImpl.cs

@@ -1,34 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-using Avalonia.Input;
-using Avalonia.Input.Platform;
-using Microsoft.JSInterop;
-
-namespace Avalonia.Web.Blazor
-{
-    internal class ClipboardImpl : IClipboard
-    {
-        public async Task<string> GetTextAsync()
-        {
-            return await AvaloniaLocator.Current.GetRequiredService<IJSInProcessRuntime>().
-                InvokeAsync<string>("navigator.clipboard.readText");
-        }
-
-        public async Task SetTextAsync(string text)
-        {
-            await AvaloniaLocator.Current.GetRequiredService<IJSInProcessRuntime>().
-                InvokeAsync<string>("navigator.clipboard.writeText",text);
-        }
-
-        public async Task ClearAsync() => await SetTextAsync("");
-
-        public Task SetDataObjectAsync(IDataObject data) => Task.CompletedTask;
-
-        public Task<string[]> GetFormatsAsync() => Task.FromResult(Array.Empty<string>());
-
-        public Task<object> GetDataAsync(string format) => Task.FromResult<object>(new());
-    }
-}

+ 0 - 93
src/Web/Avalonia.Web.Blazor/Cursor.cs

@@ -1,93 +0,0 @@
-using Avalonia.Input;
-using Avalonia.Platform;
-
-namespace Avalonia.Web.Blazor
-{
-    public class CssCursor : ICursorImpl
-    {
-        public const string Default = "default";
-        public string? Value { get; set; }
-        
-        public CssCursor(StandardCursorType type)
-        {
-            Value = ToKeyword(type);
-        }
-
-        /// <summary>
-        /// Create a cursor from base64 image
-        /// </summary>
-        public CssCursor(string base64, string format, PixelPoint hotspot, StandardCursorType fallback)
-        {
-            Value = $"url(\"data:image/{format};base64,{base64}\") {hotspot.X} {hotspot.Y}, {ToKeyword(fallback)}";
-        }
-        
-        /// <summary>
-        /// Create a cursor from url to *.cur file.
-        /// </summary>
-        public CssCursor(string url, StandardCursorType fallback)
-        {
-            Value = $"url('{url}'), {ToKeyword(fallback)}";
-        }
-        
-        /// <summary>
-        /// Create a cursor from png/svg and hotspot position
-        /// </summary>
-        public CssCursor(string url, PixelPoint hotSpot, StandardCursorType fallback)
-        {
-            Value = $"url('{url}') {hotSpot.X} {hotSpot.Y}, {ToKeyword(fallback)}";
-        }
-        
-        private static string ToKeyword(StandardCursorType type) => type switch
-        {
-            StandardCursorType.Hand => "pointer",
-            StandardCursorType.Cross => "crosshair",
-            StandardCursorType.Help => "help",
-            StandardCursorType.Ibeam => "text",
-            StandardCursorType.No => "not-allowed",
-            StandardCursorType.None => "none",
-            StandardCursorType.Wait => "progress",
-            StandardCursorType.AppStarting => "wait",
-            
-            StandardCursorType.DragMove => "move",
-            StandardCursorType.DragCopy => "copy",
-            StandardCursorType.DragLink => "alias",
-            
-            StandardCursorType.UpArrow => "default",/*not found matching one*/
-            StandardCursorType.SizeWestEast => "ew-resize",
-            StandardCursorType.SizeNorthSouth => "ns-resize",
-            StandardCursorType.SizeAll => "move",
-            
-            StandardCursorType.TopSide => "n-resize",
-            StandardCursorType.BottomSide => "s-resize",
-            StandardCursorType.LeftSide => "w-resize",
-            StandardCursorType.RightSide => "e-resize",
-            StandardCursorType.TopLeftCorner => "nw-resize",
-            StandardCursorType.TopRightCorner => "ne-resize",
-            StandardCursorType.BottomLeftCorner => "sw-resize",
-            StandardCursorType.BottomRightCorner => "se-resize",
-            
-            _ => Default,
-        };
-        
-        public void Dispose() {}
-    }
-
-    internal class CssCursorFactory : ICursorFactory
-    {
-        public ICursorImpl CreateCursor(IBitmapImpl cursor, PixelPoint hotSpot)
-        {
-            using var imageStream = new MemoryStream();
-            cursor.Save(imageStream);
-            
-            //not memory optimized because CryptoStream with ToBase64Transform is not supported in the browser.
-            var base64String = Convert.ToBase64String(imageStream.ToArray());
-            return new CssCursor(base64String, "png", hotSpot, StandardCursorType.Arrow);
-        }
-
-        ICursorImpl ICursorFactory.GetCursor(StandardCursorType cursorType)
-        {
-            return new CssCursor(cursorType);
-        }
-    }
-}
-

+ 0 - 9
src/Web/Avalonia.Web.Blazor/IBlazorSkiaSurface.cs

@@ -1,9 +0,0 @@
-namespace Avalonia.Web.Blazor
-{
-    internal interface IBlazorSkiaSurface
-    {
-        public PixelSize Size { get; set; }
-
-        public double Scaling { get; set; }
-    }
-}

+ 0 - 81
src/Web/Avalonia.Web.Blazor/Interop/ActionHelper.cs

@@ -1,81 +0,0 @@
-using System;
-using System.ComponentModel;
-using Microsoft.JSInterop;
-
-namespace Avalonia.Web.Blazor.Interop
-{
-    [EditorBrowsable(EditorBrowsableState.Never)]
-    public class ActionHelper
-    {
-        private readonly Action action;
-
-        public ActionHelper(Action action)
-        {
-            this.action = action;
-        }
-
-        [JSInvokable]
-        public void Invoke() => action?.Invoke();
-    }
-    
-    [EditorBrowsable(EditorBrowsableState.Never)]
-    public class ActionHelper<T>
-    {
-        private readonly Action<T> action;
-
-        public ActionHelper(Action<T> action)
-        {
-            this.action = action;
-        }
-
-        [JSInvokable]
-        public void Invoke(T param1) => action?.Invoke(param1);
-    }
-    
-    [EditorBrowsable(EditorBrowsableState.Never)]
-    public class ActionHelper<T1, T2>
-    {
-        private readonly Action<T1, T2> action;
-
-        public ActionHelper(Action<T1, T2> action)
-        {
-            this.action = action;
-        }
-
-        [JSInvokable]
-        public void Invoke(T1 p1, T2 p2) => action?.Invoke(p1, p2);
-    }
-    
-    [EditorBrowsable(EditorBrowsableState.Never)]
-    public class ActionHelper<T1, T2, T3>
-    {
-        private readonly Action<T1, T2, T3> action;
-
-        public ActionHelper(Action<T1, T2, T3> action)
-        {
-            this.action = action;
-        }
-
-        [JSInvokable]
-        public void Invoke(T1 p1, T2 p2, T3 p3) => action?.Invoke(p1, p2, p3);
-    }
-    
-    [EditorBrowsable(EditorBrowsableState.Never)]
-    public class ActionHelper<T1, T2, T3, T4>
-    {
-        private readonly Action<T1, T2, T3, T4> action;
-
-        public ActionHelper(Action<T1, T2, T3, T4> action)
-        {
-            this.action = action;
-        }
-
-        [JSInvokable]
-        public void Invoke(T1 p1, T2 p2, T3 p3, T4 p4) => action?.Invoke(p1, p2, p3, p4);
-    }
-    
-    
-    
-    
-    
-}

+ 0 - 18
src/Web/Avalonia.Web.Blazor/Interop/AvaloniaModule.cs

@@ -1,18 +0,0 @@
-using Microsoft.JSInterop;
-
-namespace Avalonia.Web.Blazor.Interop
-{
-    internal class AvaloniaModule : JSModuleInterop
-    {
-        private AvaloniaModule(IJSRuntime js) : base(js, "./_content/Avalonia.Web.Blazor/Avalonia.js")
-        {
-        }
-
-        public static async Task<AvaloniaModule> ImportAsync(IJSRuntime js)
-        {
-            var interop = new AvaloniaModule(js);
-            await interop.ImportAsync();
-            return interop;
-        }
-    }
-}

+ 0 - 72
src/Web/Avalonia.Web.Blazor/Interop/DpiWatcherInterop.cs

@@ -1,72 +0,0 @@
-using Microsoft.JSInterop;
-
-namespace Avalonia.Web.Blazor.Interop
-{
-    internal class DpiWatcherInterop : IDisposable
-    {
-        private const string StartSymbol = "DpiWatcher.start";
-        private const string StopSymbol = "DpiWatcher.stop";
-        private const string GetDpiSymbol = "DpiWatcher.getDpi";
-
-        private event Action<double>? callbacksEvent;
-        private readonly ActionHelper<float, float> _callbackHelper;
-        private readonly AvaloniaModule _module;
-
-        private DotNetObjectReference<ActionHelper<float, float>>? callbackReference;
-
-        public DpiWatcherInterop(AvaloniaModule module, Action<double>? callback = null)
-        {
-            _module = module;
-            _callbackHelper = new ActionHelper<float, float>((o, n) => callbacksEvent?.Invoke(n));
-
-            if (callback != null)
-                Subscribe(callback);
-        }
-
-        public void Dispose() => Stop();
-
-        public void Subscribe(Action<double> callback)
-        {
-            var shouldStart = callbacksEvent == null;
-
-            callbacksEvent += callback;
-
-            var dpi = shouldStart
-                ? Start()
-                : GetDpi();
-
-            callback(dpi);
-        }
-
-        public void Unsubscribe(Action<double> callback)
-        {
-            callbacksEvent -= callback;
-
-            if (callbacksEvent == null)
-                Stop();
-        }
-
-        private double Start()
-        {
-            if (callbackReference != null)
-                return GetDpi();
-
-            callbackReference = DotNetObjectReference.Create(_callbackHelper);
-
-            return _module.Invoke<double>(StartSymbol, callbackReference);
-        }
-
-        private void Stop()
-        {
-            if (callbackReference == null)
-                return;
-
-            _module.Invoke(StopSymbol);
-
-            callbackReference?.Dispose();
-            callbackReference = null;
-        }
-
-        public double GetDpi() => _module.Invoke<double>(GetDpiSymbol);
-    }
-}

+ 0 - 22
src/Web/Avalonia.Web.Blazor/Interop/FocusHelperInterop.cs

@@ -1,22 +0,0 @@
-using Microsoft.AspNetCore.Components;
-
-namespace Avalonia.Web.Blazor.Interop;
-
-internal class FocusHelperInterop
-{
-    private const string FocusSymbol = "FocusHelper.focus";
-    private const string SetCursorSymbol = "FocusHelper.setCursor";
-    
-    private readonly AvaloniaModule _module;
-    private readonly ElementReference _inputElement;
-
-    public FocusHelperInterop(AvaloniaModule module, ElementReference inputElement)
-    {
-        _module = module;
-        _inputElement = inputElement;
-    }
-            
-    public void Focus() => _module.Invoke(FocusSymbol, _inputElement);
-    
-    public void SetCursor(string kind) => _module.Invoke(SetCursorSymbol, _inputElement, kind);
-}

+ 0 - 130
src/Web/Avalonia.Web.Blazor/Interop/InputHelperInterop.cs

@@ -1,130 +0,0 @@
-using Microsoft.AspNetCore.Components;
-using Microsoft.JSInterop;
-
-namespace Avalonia.Web.Blazor.Interop
-{
-    internal class WebCompositionEventArgs : EventArgs
-    {
-        public enum WebCompositionEventType
-        {
-            Start,
-            Update,
-            End
-        }
-
-        public WebCompositionEventArgs(string type, string data)
-        {
-            Type = type switch
-            {
-                "compositionstart" => WebCompositionEventType.Start,
-                "compositionupdate" => WebCompositionEventType.Update,
-                "compositionend" => WebCompositionEventType.End,
-                _ => Type
-            };
-
-            Data = data;
-        }
-        
-        public WebCompositionEventType Type { get; }
-        
-        public string Data { get; }
-    }
-
-    internal class WebInputEventArgs
-    {
-        public WebInputEventArgs(string type, string data)
-        {
-            Type = type;
-            Data = data;
-        }
-
-        public string Type { get; }
-
-        public string Data { get; }
-
-        public bool Handled { get; set; }
-    }
-    
-    internal class InputHelperInterop
-    {
-        private const string ClearSymbol = "InputHelper.clear";
-        private const string FocusSymbol = "InputHelper.focus";
-        private const string SetCursorSymbol = "InputHelper.setCursor";
-        private const string HideSymbol = "InputHelper.hide";
-        private const string ShowSymbol = "InputHelper.show";
-        private const string StartSymbol = "InputHelper.start";
-        private const string SetSurroundingTextSymbol = "InputHelper.setSurroundingText";
-        private const string SetBoundsSymbol = "InputHelper.setBounds";
-
-        private readonly AvaloniaModule _module;
-        private readonly ElementReference _inputElement;
-        private readonly ActionHelper<string, string> _compositionAction;
-        private readonly ActionHelper<string, string> _inputAction;
-
-        private DotNetObjectReference<ActionHelper<string, string>>? compositionActionReference;
-        private DotNetObjectReference<ActionHelper<string, string>>? inputActionReference;
-
-        public InputHelperInterop(AvaloniaModule module, ElementReference inputElement)
-        {
-            _module = module;
-            _inputElement = inputElement;
-
-            _compositionAction = new ActionHelper<string, string>(OnCompositionEvent);
-            _inputAction = new ActionHelper<string, string>(OnInputEvent);
-
-            Start();
-        }
-
-        public event EventHandler<WebCompositionEventArgs>? CompositionEvent;
-        public event EventHandler<WebInputEventArgs>? InputEvent;
-
-        private void OnCompositionEvent(string type, string data)
-        {
-            Console.WriteLine($"CompositionEvent Handler Helper {CompositionEvent == null} ");
-            CompositionEvent?.Invoke(this, new WebCompositionEventArgs(type, data));
-        }
-
-        private void OnInputEvent(string type, string data)
-        {
-            Console.WriteLine($"InputEvent Handler Helper {InputEvent == null} ");
-
-            var args = new WebInputEventArgs(type, data);
-
-            InputEvent?.Invoke(this, args);
-        }
-
-        public void Clear() => _module.Invoke(ClearSymbol, _inputElement);
-
-        public void Focus() => _module.Invoke(FocusSymbol, _inputElement);
-
-        public void SetCursor(string kind) => _module.Invoke(SetCursorSymbol, _inputElement, kind);
-
-        public void Hide() => _module.Invoke(HideSymbol, _inputElement);
-
-        public void Show() => _module.Invoke(ShowSymbol, _inputElement);
-
-        private void Start()
-        {
-            if(compositionActionReference != null)
-            {
-                return;
-            }
-                          
-            compositionActionReference = DotNetObjectReference.Create(_compositionAction);
-
-            inputActionReference = DotNetObjectReference.Create(_inputAction);
-
-            _module.Invoke(StartSymbol, _inputElement, compositionActionReference, inputActionReference);
-        }
-
-        public void SetSurroundingText(string text, int start, int end)
-        {
-            _module.Invoke(SetSurroundingTextSymbol, _inputElement, text, start, end);
-        }
-
-        public void SetBounds(PixelRect bounds, int caret)
-        {
-            _module.Invoke(SetBoundsSymbol, _inputElement, bounds.X, bounds.Y, bounds.Width, bounds.Height, caret);
-        }
-    }
-}

+ 0 - 46
src/Web/Avalonia.Web.Blazor/Interop/JSModuleInterop.cs

@@ -1,46 +0,0 @@
-using Microsoft.JSInterop;
-
-namespace Avalonia.Web.Blazor.Interop
-{
-    internal class JSModuleInterop : IDisposable
-    {
-        private readonly Task<IJSUnmarshalledObjectReference> moduleTask;
-        private IJSUnmarshalledObjectReference? module;
-
-        public JSModuleInterop(IJSRuntime js, string filename)
-        {
-            if (js is not IJSInProcessRuntime)
-                throw new NotSupportedException("SkiaSharp currently only works on Web Assembly.");
-
-            moduleTask = js.InvokeAsync<IJSUnmarshalledObjectReference>("import", filename).AsTask();
-        }
-
-        public async Task ImportAsync()
-        {
-            module = await moduleTask;
-        }
-
-        public void Dispose()
-        {
-            OnDisposingModule();
-            Module.Dispose();
-        }
-
-        protected IJSUnmarshalledObjectReference Module =>
-            module ?? throw new InvalidOperationException("Make sure to run ImportAsync() first.");
-
-        internal void Invoke(string identifier, params object?[]? args) =>
-            Module.InvokeVoid(identifier, args);
-
-        internal TValue Invoke<TValue>(string identifier, params object?[]? args) =>
-            Module.Invoke<TValue>(identifier, args);
-
-        internal ValueTask InvokeAsync(string identifier, params object?[]? args) =>
-            Module.InvokeVoidAsync(identifier, args);
-
-        internal ValueTask<TValue> InvokeAsync<TValue>(string identifier, params object?[]? args) =>
-            Module.InvokeAsync<TValue>(identifier, args);
-
-        protected virtual void OnDisposingModule() { }
-    }
-}

+ 0 - 144
src/Web/Avalonia.Web.Blazor/Interop/NativeControlHostImpl.cs

@@ -1,144 +0,0 @@
-using System.Diagnostics.CodeAnalysis;
-
-using Avalonia.Controls.Platform;
-using Avalonia.Platform;
-
-using Microsoft.AspNetCore.Components;
-using Microsoft.JSInterop;
-
-namespace Avalonia.Web.Blazor.Interop
-{
-
-    internal class NativeControlHostInterop : INativeControlHostImpl
-    {
-        private const string CreateDefaultChildSymbol = "NativeControlHost.CreateDefaultChild";
-        private const string CreateAttachmentSymbol = "NativeControlHost.CreateAttachment";
-        private const string GetReferenceSymbol = "NativeControlHost.GetReference";
-
-        private readonly AvaloniaModule _module;
-        private readonly ElementReference _hostElement;
-
-        public NativeControlHostInterop(AvaloniaModule module, ElementReference element)
-        {
-            _module = module;
-            _hostElement = element;
-        }
-
-        public INativeControlHostDestroyableControlHandle CreateDefaultChild(IPlatformHandle parent)
-        {
-            var element = _module.Invoke<IJSInProcessObjectReference>(CreateDefaultChildSymbol);
-            return new JSObjectControlHandle(element);
-        }
-
-        public INativeControlHostControlTopLevelAttachment CreateNewAttachment(Func<IPlatformHandle, IPlatformHandle> create)
-        {
-            Attachment? a = null;
-            try
-            {
-                using var hostElementJsReference = _module.Invoke<IJSInProcessObjectReference>(GetReferenceSymbol, _hostElement);                
-                var child = create(new JSObjectControlHandle(hostElementJsReference));
-                var attachmenetReference = _module.Invoke<IJSInProcessObjectReference>(CreateAttachmentSymbol);
-                // It has to be assigned to the variable before property setter is called so we dispose it on exception
-#pragma warning disable IDE0017 // Simplify object initialization
-                a = new Attachment(attachmenetReference, child);
-#pragma warning restore IDE0017 // Simplify object initialization
-                a.AttachedTo = this;
-                return a;
-            }
-            catch
-            {
-                a?.Dispose();
-                throw;
-            }
-        }
-
-        public INativeControlHostControlTopLevelAttachment CreateNewAttachment(IPlatformHandle handle)
-        {
-            var attachmenetReference = _module.Invoke<IJSInProcessObjectReference>(CreateAttachmentSymbol);
-            var a = new Attachment(attachmenetReference, handle);
-            a.AttachedTo = this;
-            return a;
-        }
-
-        public bool IsCompatibleWith(IPlatformHandle handle) => handle is JSObjectControlHandle;
-
-        class Attachment : INativeControlHostControlTopLevelAttachment
-        {
-            private const string InitializeWithChildHandleSymbol = "InitializeWithChildHandle";
-            private const string AttachToSymbol = "AttachTo";
-            private const string ShowInBoundsSymbol = "ShowInBounds";
-            private const string HideWithSizeSymbol = "HideWithSize";
-            private const string ReleaseChildSymbol = "ReleaseChild";
-
-            private IJSInProcessObjectReference? _native;
-            private NativeControlHostInterop? _attachedTo;
-
-            public Attachment(IJSInProcessObjectReference native, IPlatformHandle handle)
-            {
-                _native = native;
-                _native.InvokeVoid(InitializeWithChildHandleSymbol, ((JSObjectControlHandle)handle).Object);
-            }
-
-            public void Dispose()
-            {
-                if (_native != null)
-                {
-                    _native.InvokeVoid(ReleaseChildSymbol);
-                    _native.Dispose();
-                    _native = null;
-                }
-            }
-
-            public INativeControlHostImpl? AttachedTo
-            {
-                get => _attachedTo!;
-                set
-                {
-                    CheckDisposed();
-
-                    var host = (NativeControlHostInterop?)value;
-                    if (host == null)
-                    {
-                        _native.InvokeVoid(AttachToSymbol);
-                    }
-                    else
-                    {
-                        _native.InvokeVoid(AttachToSymbol, host._hostElement);
-                    }
-                    _attachedTo = host;
-                }
-            }
-
-            public bool IsCompatibleWith(INativeControlHostImpl host) => host is NativeControlHostInterop;
-
-            public void HideWithSize(Size size)
-            {
-                CheckDisposed();
-                if (_attachedTo == null)
-                    return;
-
-                _native.InvokeVoid(HideWithSizeSymbol, Math.Max(1, (float)size.Width), Math.Max(1, (float)size.Height));
-            }
-
-            public void ShowInBounds(Rect bounds)
-            {
-                CheckDisposed();
-
-                if (_attachedTo == null)
-                    throw new InvalidOperationException("Native control isn't attached to a toplevel");
-
-                bounds = new Rect(bounds.X, bounds.Y, Math.Max(1, bounds.Width),
-                    Math.Max(1, bounds.Height));
-
-                _native.InvokeVoid(ShowInBoundsSymbol, (float)bounds.X, (float)bounds.Y, (float)bounds.Width, (float)bounds.Height);
-            }
-
-            [MemberNotNull(nameof(_native))]
-            private void CheckDisposed()
-            {
-                if (_native == null)
-                    throw new ObjectDisposedException(nameof(Attachment));
-            }
-        }
-    }
-}

+ 0 - 76
src/Web/Avalonia.Web.Blazor/Interop/SKHtmlCanvasInterop.cs

@@ -1,76 +0,0 @@
-using Microsoft.AspNetCore.Components;
-using Microsoft.JSInterop;
-using SkiaSharp;
-
-namespace Avalonia.Web.Blazor.Interop
-{
-    internal class SKHtmlCanvasInterop : IDisposable
-    {
-        private const string JsFilename = "./_content/Avalonia.Web.Blazor/SKHtmlCanvas.js";
-        private const string InitGLSymbol = "SKHtmlCanvas.initGL";
-        private const string InitRasterSymbol = "SKHtmlCanvas.initRaster";
-        private const string DeinitSymbol = "SKHtmlCanvas.deinit";
-        private const string RequestAnimationFrameSymbol = "SKHtmlCanvas.requestAnimationFrame";
-        private const string SetCanvasSizeSymbol = "SKHtmlCanvas.setCanvasSize";
-        private const string PutImageDataSymbol = "SKHtmlCanvas.putImageData";
-
-        private readonly AvaloniaModule _module;
-        private readonly ElementReference _htmlCanvas;
-        private readonly string _htmlElementId;
-        private readonly ActionHelper _callbackHelper;
-
-        private DotNetObjectReference<ActionHelper>? callbackReference;
-
-        public SKHtmlCanvasInterop(AvaloniaModule module, ElementReference element, Action renderFrameCallback)
-        {
-            _module = module;
-            _htmlCanvas = element;
-            _htmlElementId = element.Id;
-
-            _callbackHelper = new ActionHelper(renderFrameCallback);
-        }
-
-        public void Dispose() => Deinit();
-
-        public GLInfo InitGL()
-        {
-            if (callbackReference != null)
-                throw new InvalidOperationException("Unable to initialize the same canvas more than once.");
-
-            callbackReference = DotNetObjectReference.Create(_callbackHelper);
-
-            return _module.Invoke<GLInfo>(InitGLSymbol, _htmlCanvas, _htmlElementId, callbackReference);
-        }
-
-        public bool InitRaster()
-        {
-            if (callbackReference != null)
-                throw new InvalidOperationException("Unable to initialize the same canvas more than once.");
-
-            callbackReference = DotNetObjectReference.Create(_callbackHelper);
-
-            return _module.Invoke<bool>(InitRasterSymbol, _htmlCanvas, _htmlElementId, callbackReference);
-        }
-
-        public void Deinit()
-        {
-            if (callbackReference == null)
-                return;
-
-            _module.Invoke(DeinitSymbol, _htmlElementId);
-
-            callbackReference?.Dispose();
-        }
-
-        public void RequestAnimationFrame(bool enableRenderLoop) =>
-            _module.Invoke(RequestAnimationFrameSymbol, _htmlCanvas, enableRenderLoop);
-
-        public void SetCanvasSize(int rawWidth, int rawHeight) =>
-            _module.Invoke(SetCanvasSizeSymbol, _htmlCanvas, rawWidth, rawHeight);
-
-        public void PutImageData(IntPtr intPtr, SKSizeI rawSize) =>
-            _module.Invoke(PutImageDataSymbol, _htmlCanvas, intPtr.ToInt64(), rawSize.Width, rawSize.Height);
-
-        public record GLInfo(int ContextId, uint FboId, int Stencils, int Samples, int Depth);
-    }
-}

+ 0 - 50
src/Web/Avalonia.Web.Blazor/Interop/SizeWatcherInterop.cs

@@ -1,50 +0,0 @@
-using Microsoft.AspNetCore.Components;
-using Microsoft.JSInterop;
-using SkiaSharp;
-
-namespace Avalonia.Web.Blazor.Interop
-{
-    internal class SizeWatcherInterop : IDisposable
-    {
-        private const string ObserveSymbol = "SizeWatcher.observe";
-        private const string UnobserveSymbol = "SizeWatcher.unobserve";
-
-        private readonly AvaloniaModule _module;
-        private readonly ElementReference _htmlElement;
-        private readonly string _htmlElementId;
-        private readonly ActionHelper<float, float> _callbackHelper;
-
-        private DotNetObjectReference<ActionHelper<float, float>>? callbackReference;
-
-        public SizeWatcherInterop(AvaloniaModule module, ElementReference element, Action<SKSize> callback)
-        {
-            _module = module;
-            _htmlElement = element;
-            _htmlElementId = element.Id;
-            _callbackHelper = new ActionHelper<float, float>((x, y) => callback(new SKSize(x, y)));
-        }
-
-        public void Dispose() => Stop();
-
-        public void Start()
-        {
-            if (callbackReference != null)
-                return;
-
-            callbackReference = DotNetObjectReference.Create(_callbackHelper);
-
-            _module.Invoke(ObserveSymbol, _htmlElement, _htmlElementId, callbackReference);
-        }
-
-        public void Stop()
-        {
-            if (callbackReference == null)
-                return;
-
-            _module.Invoke(UnobserveSymbol, _htmlElementId);
-
-            callbackReference?.Dispose();
-            callbackReference = null;
-        }
-    }
-}

+ 0 - 225
src/Web/Avalonia.Web.Blazor/Interop/Storage/StorageProviderInterop.cs

@@ -1,225 +0,0 @@
-using System.Diagnostics.CodeAnalysis;
-
-using Avalonia.Platform.Storage;
-
-using Microsoft.JSInterop;
-
-namespace Avalonia.Web.Blazor.Interop.Storage
-{
-    internal record FilePickerAcceptType(string Description, IReadOnlyDictionary<string, IReadOnlyList<string>> Accept);
-
-    internal record FileProperties(ulong Size, long LastModified, string? Type);
-
-    internal class StorageProviderInterop : JSModuleInterop, IStorageProvider
-    {
-        private const string JsFilename = "./_content/Avalonia.Web.Blazor/Storage.js";
-        private const string PickerCancelMessage = "The user aborted a request";
-
-        public static async Task<StorageProviderInterop> ImportAsync(IJSRuntime js)
-        {
-            var interop = new StorageProviderInterop(js);
-            await interop.ImportAsync();
-            return interop;
-        }
-
-        public StorageProviderInterop(IJSRuntime js)
-            : base(js, JsFilename)
-        {
-        }
-
-        public bool CanOpen => Invoke<bool>("StorageProvider.canOpen");
-        public bool CanSave => Invoke<bool>("StorageProvider.canSave");
-        public bool CanPickFolder => Invoke<bool>("StorageProvider.canPickFolder");
-
-        public async Task<IReadOnlyList<IStorageFile>> OpenFilePickerAsync(FilePickerOpenOptions options)
-        {
-            try
-            {
-                var startIn = (options.SuggestedStartLocation as JSStorageItem)?.FileHandle;
-
-                var (types, exludeAll) = ConvertFileTypes(options.FileTypeFilter);
-                var items = await InvokeAsync<IJSInProcessObjectReference>("StorageProvider.openFileDialog", startIn, options.AllowMultiple, types, exludeAll);
-                var count = items.Invoke<int>("count");
-
-                return Enumerable.Range(0, count)
-                    .Select(index => new JSStorageFile(items.Invoke<IJSInProcessObjectReference>("at", index)))
-                    .ToArray();
-            }
-            catch (JSException ex) when (ex.Message.Contains(PickerCancelMessage, StringComparison.Ordinal))
-            {
-                return Array.Empty<IStorageFile>();
-            }
-        }
-
-        public async Task<IStorageFile?> SaveFilePickerAsync(FilePickerSaveOptions options)
-        {
-            try
-            {
-                var startIn = (options.SuggestedStartLocation as JSStorageItem)?.FileHandle;
-
-                var (types, exludeAll) = ConvertFileTypes(options.FileTypeChoices);
-                var item = await InvokeAsync<IJSInProcessObjectReference>("StorageProvider.saveFileDialog", startIn, options.SuggestedFileName, types, exludeAll);
-
-                return item is not null ? new JSStorageFile(item) : null;
-            }
-            catch (JSException ex) when (ex.Message.Contains(PickerCancelMessage, StringComparison.Ordinal))
-            {
-                return null;
-            }
-        }
-
-        public async Task<IReadOnlyList<IStorageFolder>> OpenFolderPickerAsync(FolderPickerOpenOptions options)
-        {
-            try
-            {
-                var startIn = (options.SuggestedStartLocation as JSStorageItem)?.FileHandle;
-
-                var item = await InvokeAsync<IJSInProcessObjectReference>("StorageProvider.selectFolderDialog", startIn);
-
-                return item is not null ? new[] { new JSStorageFolder(item) } : Array.Empty<IStorageFolder>();
-            }
-            catch (JSException ex) when (ex.Message.Contains(PickerCancelMessage, StringComparison.Ordinal))
-            {
-                return Array.Empty<IStorageFolder>();
-            }
-        }
-
-        public async Task<IStorageBookmarkFile?> OpenFileBookmarkAsync(string bookmark)
-        {
-            var item = await InvokeAsync<IJSInProcessObjectReference>("StorageProvider.openBookmark", bookmark);
-            return item is not null ? new JSStorageFile(item) : null;
-        }
-
-        public async Task<IStorageBookmarkFolder?> OpenFolderBookmarkAsync(string bookmark)
-        {
-            var item = await InvokeAsync<IJSInProcessObjectReference>("StorageProvider.openBookmark", bookmark);
-            return item is not null ? new JSStorageFolder(item) : null;
-        }
-
-        private static (FilePickerAcceptType[]? types, bool excludeAllOption) ConvertFileTypes(IEnumerable<FilePickerFileType>? input)
-        {
-            var types = input?
-                .Where(t => t.MimeTypes?.Any() == true && t != FilePickerFileTypes.All)
-                .Select(t => new FilePickerAcceptType(t.Name, t.MimeTypes!
-                    .ToDictionary(m => m, _ => (IReadOnlyList<string>)Array.Empty<string>())))
-                .ToArray();
-            if (types?.Length == 0)
-            {
-                types = null;
-            }
-
-            var inlcudeAll = input?.Contains(FilePickerFileTypes.All) == true || types is null;
-
-            return (types, !inlcudeAll);
-        }
-    }
-
-    internal abstract class JSStorageItem : IStorageBookmarkItem
-    {
-        internal IJSInProcessObjectReference? _fileHandle;
-
-        protected JSStorageItem(IJSInProcessObjectReference fileHandle)
-        {
-            _fileHandle = fileHandle ?? throw new ArgumentNullException(nameof(fileHandle));
-        }
-
-        internal IJSInProcessObjectReference FileHandle => _fileHandle ?? throw new ObjectDisposedException(nameof(JSStorageItem));
-
-        public string Name => FileHandle.Invoke<string>("getName");
-
-        public bool TryGetUri([NotNullWhen(true)] out Uri? uri)
-        {
-            uri = new Uri(Name, UriKind.Relative);
-            return false;
-        }
-
-        public async Task<StorageItemProperties> GetBasicPropertiesAsync()
-        {
-            var properties = await FileHandle.InvokeAsync<FileProperties?>("getProperties");
-
-            return new StorageItemProperties(
-                properties?.Size,
-                dateCreated: null,
-                dateModified: properties?.LastModified > 0 ? DateTimeOffset.FromUnixTimeMilliseconds(properties.LastModified) : null);
-        }
-
-        public bool CanBookmark => true;
-
-        public Task<string?> SaveBookmarkAsync()
-        {
-            return FileHandle.InvokeAsync<string?>("saveBookmark").AsTask();
-        }
-
-        public Task<IStorageFolder?> GetParentAsync()
-        {
-            return Task.FromResult<IStorageFolder?>(null);
-        }
-
-        public Task ReleaseBookmarkAsync()
-        {
-            return FileHandle.InvokeAsync<string?>("deleteBookmark").AsTask();
-        }
-
-        public void Dispose()
-        {
-            _fileHandle?.Dispose();
-            _fileHandle = null;
-        }
-    }
-
-    internal class JSStorageFile : JSStorageItem, IStorageBookmarkFile
-    {
-        public JSStorageFile(IJSInProcessObjectReference fileHandle) : base(fileHandle)
-        {
-        }
-
-        public bool CanOpenRead => true;
-        public async Task<Stream> OpenReadAsync()
-        {
-            var stream = await FileHandle.InvokeAsync<IJSStreamReference>("openRead");
-            // Remove maxAllowedSize limit, as developer can decide if they read only small part or everything.
-            return await stream.OpenReadStreamAsync(long.MaxValue, CancellationToken.None);
-        }
-
-        public bool CanOpenWrite => true;
-        public async Task<Stream> OpenWriteAsync()
-        {
-            var properties = await FileHandle.InvokeAsync<FileProperties?>("getProperties");
-            var streamWriter = await FileHandle.InvokeAsync<IJSInProcessObjectReference>("openWrite");
-
-            return new JSWriteableStream(streamWriter, (long)(properties?.Size ?? 0));
-        }
-    }
-
-    internal class JSStorageFolder : JSStorageItem, IStorageBookmarkFolder
-    {
-        public JSStorageFolder(IJSInProcessObjectReference fileHandle) : base(fileHandle)
-        {
-        }
-
-        public async Task<IReadOnlyList<IStorageItem>> GetItemsAsync()
-        {
-            var items = await FileHandle.InvokeAsync<IJSInProcessObjectReference?>("getItems");
-            if (items is null)
-            {
-                return Array.Empty<IStorageItem>();
-            }
-
-            var count = items.Invoke<int>("count");
-
-            return Enumerable.Range(0, count)
-                .Select(index =>
-                {
-                    var reference = items.Invoke<IJSInProcessObjectReference>("at", index);
-                    return reference.Invoke<string>("getKind") switch
-                    {
-                        "directory" => (IStorageItem)new JSStorageFolder(reference),
-                        "file" => new JSStorageFile(reference),
-                        _ => null
-                    };
-                })
-                .Where(i => i is not null)
-                .ToArray()!;
-        }
-    }
-}

+ 0 - 124
src/Web/Avalonia.Web.Blazor/Interop/Storage/WriteableStream.cs

@@ -1,124 +0,0 @@
-using System.Buffers;
-using System.Text.Json.Serialization;
-
-using Microsoft.JSInterop;
-
-namespace Avalonia.Web.Blazor.Interop.Storage
-{
-    // Loose wrapper implementaion of a stream on top of FileAPI FileSystemWritableFileStream
-    internal sealed class JSWriteableStream : Stream
-    {
-        private IJSInProcessObjectReference? _jSReference;
-
-        // Unfortunatelly we can't read current length/position, so we need to keep it C#-side only.
-        private long _length, _position;
-
-        internal JSWriteableStream(IJSInProcessObjectReference jSReference, long initialLength)
-        {
-            _jSReference = jSReference;
-            _length = initialLength;
-        }
-
-        private IJSInProcessObjectReference JSReference => _jSReference ?? throw new ObjectDisposedException(nameof(JSWriteableStream));
-
-        public override bool CanRead => false;
-
-        public override bool CanSeek => true;
-
-        public override bool CanWrite => true;
-
-        public override long Length => _length;
-
-        public override long Position
-        {
-            get => _position;
-            set => Seek(_position, SeekOrigin.Begin);
-        }
-
-        public override void Flush()
-        {
-            // no-op
-        }
-
-        public override int Read(byte[] buffer, int offset, int count)
-        {
-            throw new NotSupportedException();
-        }
-
-        public override long Seek(long offset, SeekOrigin origin)
-        {
-            var position = origin switch
-            {
-                SeekOrigin.Current => _position + offset,
-                SeekOrigin.End => _length + offset,
-                _ => offset
-            };
-            JSReference.InvokeVoid("seek", position);
-            return position;
-        }
-
-        public override void SetLength(long value)
-        {
-            _length = value;
-
-            // See https://docs.w3cub.com/dom/filesystemwritablefilestream/truncate
-            // If the offset is smaller than the size, it remains unchanged. If the offset is larger than size, the offset is set to that size
-            if (_position > _length)
-            {
-                _position = _length;
-            }
-
-            JSReference.InvokeVoid("truncate", value);
-        }
-
-        public override void Write(byte[] buffer, int offset, int count)
-        {
-            throw new NotSupportedException("Synchronous writes are not supported.");
-        }
-
-        public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
-        {
-            if (offset != 0 || count != buffer.Length)
-            {
-                // TODO, we need to pass prepared buffer to the JS
-                // Can't use ArrayPool as it can return bigger array than requested
-                // Can't use Span/Memory, as it's not supported by JS interop yet.
-                // Alternatively we can pass original buffer and offset+count, so it can be trimmed on the JS side (but is it more efficient tho?)
-                buffer = buffer.AsMemory(offset, count).ToArray();
-            }
-            return WriteAsyncInternal(buffer, cancellationToken).AsTask();
-        }
-
-        public override ValueTask WriteAsync(ReadOnlyMemory<byte> buffer, CancellationToken cancellationToken = default)
-        {
-            return WriteAsyncInternal(buffer.ToArray(), cancellationToken);
-        }
-
-        private ValueTask WriteAsyncInternal(byte[] buffer, CancellationToken _)
-        {
-            _position += buffer.Length;
-
-            return JSReference.InvokeVoidAsync("write", buffer);
-        }
-
-        protected override void Dispose(bool disposing)
-        {
-            if (_jSReference is { } jsReference)
-            {
-                _jSReference = null;
-                jsReference.InvokeVoid("close");
-                jsReference.Dispose();
-            }
-        }
-
-        public override async ValueTask DisposeAsync()
-        {
-            if (_jSReference is { } jsReference)
-            {
-                _jSReference = null;
-                await jsReference.InvokeVoidAsync("close");
-                await jsReference.DisposeAsync();
-            }
-        }
-    }
-}

+ 0 - 35
src/Web/Avalonia.Web.Blazor/JSObjectControlHandle.cs

@@ -1,35 +0,0 @@
-#nullable enable
-using Avalonia.Controls.Platform;
-
-using Microsoft.JSInterop;
-
-namespace Avalonia.Web.Blazor
-{
-    public class JSObjectControlHandle : INativeControlHostDestroyableControlHandle
-    {
-        internal const string ElementReferenceDescriptor = "JSObjectReference";
-
-        public JSObjectControlHandle(IJSObjectReference reference)
-        {
-            Object = reference;
-        }
-
-        public IJSObjectReference Object { get; }
-
-        public IntPtr Handle => throw new NotSupportedException();
-
-        public string? HandleDescriptor => ElementReferenceDescriptor;
-
-        public void Destroy()
-        {
-            if (Object is IJSInProcessObjectReference inProcess)
-            {
-                inProcess.Dispose();
-            }
-            else
-            {
-                _ = Object.DisposeAsync();
-            }
-        }
-    }
-}

+ 0 - 127
src/Web/Avalonia.Web.Blazor/Keycodes.cs

@@ -1,127 +0,0 @@
-using Avalonia.Input;
-
-namespace Avalonia.Web.Blazor
-{
-    internal static class Keycodes
-    {
-        public static Dictionary<string, Key> KeyCodes = new()
-        {
-            { "Escape", Key.Escape },
-            { "Digit1", Key.D1 },
-            { "Digit2", Key.D2 },
-            { "Digit3", Key.D3 },
-            { "Digit4", Key.D4 },
-            { "Digit5", Key.D5 },
-            { "Digit6", Key.D6 },
-            { "Digit7", Key.D7 },
-            { "Digit8", Key.D8 },
-            { "Digit9", Key.D9 },
-            { "Digit0", Key.D0 },
-            { "Minus", Key.OemMinus },
-            //{	"Equal"	, Key. },
-            { "Backspace", Key.Back },
-            { "Tab", Key.Tab },
-            { "KeyQ", Key.Q },
-            { "KeyW", Key.W },
-            { "KeyE", Key.E },
-            { "KeyR", Key.R },
-            { "KeyT", Key.T },
-            { "KeyY", Key.Y },
-            { "KeyU", Key.U },
-            { "KeyI", Key.I },
-            { "KeyO", Key.O },
-            { "KeyP", Key.P },
-            { "BracketLeft", Key.OemOpenBrackets },
-            { "BracketRight", Key.OemCloseBrackets },
-            { "Enter", Key.Enter },
-            { "ControlLeft", Key.LeftCtrl },
-            { "KeyA", Key.A },
-            { "KeyS", Key.S },
-            { "KeyD", Key.D },
-            { "KeyF", Key.F },
-            { "KeyG", Key.G },
-            { "KeyH", Key.H },
-            { "KeyJ", Key.J },
-            { "KeyK", Key.K },
-            { "KeyL", Key.L },
-            { "Semicolon", Key.OemSemicolon },
-            { "Quote", Key.OemQuotes },
-            //{	"Backquote"	, Key. },
-            { "ShiftLeft", Key.LeftShift },
-            { "Backslash", Key.OemBackslash },
-            { "KeyZ", Key.Z },
-            { "KeyX", Key.X },
-            { "KeyC", Key.C },
-            { "KeyV", Key.V },
-            { "KeyB", Key.B },
-            { "KeyN", Key.N },
-            { "KeyM", Key.M },
-            { "Comma", Key.OemComma },
-            { "Period", Key.OemPeriod },
-            //{	"Slash"	, Key. },
-            { "ShiftRight", Key.RightShift },
-            { "NumpadMultiply", Key.Multiply },
-            { "AltLeft", Key.LeftAlt },
-            { "Space", Key.Space },
-            { "CapsLock", Key.CapsLock },
-            { "F1", Key.F1 },
-            { "F2", Key.F2 },
-            { "F3", Key.F3 },
-            { "F4", Key.F4 },
-            { "F5", Key.F5 },
-            { "F6", Key.F6 },
-            { "F7", Key.F7 },
-            { "F8", Key.F8 },
-            { "F9", Key.F9 },
-            { "F10", Key.F10 },
-            { "NumLock", Key.NumLock },
-            { "ScrollLock", Key.Scroll },
-            { "Numpad7", Key.NumPad7 },
-            { "Numpad8", Key.NumPad8 },
-            { "Numpad9", Key.NumPad9 },
-            { "NumpadSubtract", Key.Subtract },
-            { "Numpad4", Key.NumPad4 },
-            { "Numpad5", Key.NumPad5 },
-            { "Numpad6", Key.NumPad6 },
-            { "NumpadAdd", Key.Add },
-            { "Numpad1", Key.NumPad1 },
-            { "Numpad2", Key.NumPad2 },
-            { "Numpad3", Key.NumPad3 },
-            { "Numpad0", Key.NumPad0 },
-            { "NumpadDecimal", Key.Decimal },
-            { "Unidentified", Key.NoName },
-            //{	"IntlBackslash"	, Key.bac },
-            { "F11", Key.F11 },
-            { "F12", Key.F12 },
-            //{	"IntlRo"	, Key.Ro },
-            //{	"Unidentified"	, Key. },
-            { "Convert", Key.ImeConvert },
-            { "KanaMode", Key.KanaMode },
-            { "NonConvert", Key.ImeNonConvert },
-            //{	"Unidentified"	, Key. },
-            { "NumpadEnter", Key.Enter },
-            { "ControlRight", Key.RightCtrl },
-            { "NumpadDivide", Key.Divide },
-            { "PrintScreen", Key.PrintScreen },
-            { "AltRight", Key.RightAlt },
-            //{	"Unidentified"	, Key. },
-            { "Home", Key.Home },
-            { "ArrowUp", Key.Up },
-            { "PageUp", Key.PageUp },
-            { "ArrowLeft", Key.Left },
-            { "ArrowRight", Key.Right },
-            { "End", Key.End },
-            { "ArrowDown", Key.Down },
-            { "PageDown", Key.PageDown },
-            { "Insert", Key.Insert },
-            { "Delete", Key.Delete },
-            //{	"Unidentified"	, Key. },
-            { "AudioVolumeMute", Key.VolumeMute },
-            { "AudioVolumeDown", Key.VolumeDown },
-            { "AudioVolumeUp", Key.VolumeUp },
-            //{	"NumpadEqual"	, Key. },
-            { "Pause", Key.Pause },
-            { "NumpadComma", Key.OemComma }
-        };
-    }
-}

+ 0 - 17
src/Web/Avalonia.Web.Blazor/ManualTriggerRenderTimer.cs

@@ -1,17 +0,0 @@
-using System.Diagnostics;
-using Avalonia.Rendering;
-
-namespace Avalonia.Web.Blazor
-{
-    public class ManualTriggerRenderTimer : IRenderTimer
-    {
-        private static readonly Stopwatch s_sw = Stopwatch.StartNew();
-
-        public static ManualTriggerRenderTimer Instance { get; } = new();
-
-        public void RaiseTick() => Tick?.Invoke(s_sw.Elapsed);
-
-        public event Action<TimeSpan>? Tick;
-        public bool RunsInBackground => false;
-    }
-}

+ 0 - 222
src/Web/Avalonia.Web.Blazor/RazorViewTopLevelImpl.cs

@@ -1,222 +0,0 @@
-using System.Diagnostics;
-using Avalonia.Controls;
-using Avalonia.Controls.Platform;
-using Avalonia.Input;
-using Avalonia.Input.Raw;
-using Avalonia.Input.TextInput;
-using Avalonia.Platform;
-using Avalonia.Platform.Storage;
-using Avalonia.Rendering;
-using Avalonia.Rendering.Composition;
-using Avalonia.Web.Blazor.Interop;
-using SkiaSharp;
-
-#nullable enable
-
-namespace Avalonia.Web.Blazor
-{
-    internal class RazorViewTopLevelImpl : ITopLevelImplWithTextInputMethod, ITopLevelImplWithNativeControlHost, ITopLevelImplWithStorageProvider
-    {
-        private Size _clientSize;
-        private IBlazorSkiaSurface? _currentSurface;
-        private IInputRoot? _inputRoot;
-        private readonly Stopwatch _sw = Stopwatch.StartNew();
-        private readonly AvaloniaView _avaloniaView;
-        private readonly TouchDevice _touchDevice;
-        private readonly PenDevice _penDevice;
-        private string _currentCursor = CssCursor.Default;
-
-        public RazorViewTopLevelImpl(AvaloniaView avaloniaView)
-        {
-            _avaloniaView = avaloniaView;
-            TransparencyLevel = WindowTransparencyLevel.None;
-            AcrylicCompensationLevels = new AcrylicPlatformCompensationLevels(1, 1, 1);
-            _touchDevice = new TouchDevice();
-            _penDevice = new PenDevice();
-        }
-
-        public ulong Timestamp => (ulong)_sw.ElapsedMilliseconds;
-
-
-        internal void SetSurface(GRContext context, SKHtmlCanvasInterop.GLInfo glInfo, SKColorType colorType, PixelSize size, double scaling)
-        {
-            _currentSurface =
-                new BlazorSkiaSurface(context, glInfo, colorType, size, scaling, GRSurfaceOrigin.BottomLeft);
-        }
-
-        internal void SetSurface(SKColorType colorType, PixelSize size, double scaling, Action<IntPtr, SKSizeI> blitCallback)
-        {
-            _currentSurface = new BlazorSkiaRasterSurface(colorType, size, scaling, blitCallback);
-        }
-
-        public void SetClientSize(SKSize size, double dpi)
-        {
-            var newSize = new Size(size.Width, size.Height);
-
-            if (Math.Abs(RenderScaling - dpi) > 0.0001)
-            {
-                if (_currentSurface is { })
-                {
-                    _currentSurface.Scaling = dpi;
-                }
-                
-                ScalingChanged?.Invoke(dpi);
-            }
-
-            if (newSize != _clientSize)
-            {
-                _clientSize = newSize;
-
-                if (_currentSurface is { })
-                {
-                    _currentSurface.Size = new PixelSize((int)size.Width, (int)size.Height);
-                }
-
-                Resized?.Invoke(newSize, PlatformResizeReason.User);
-            }
-        }
-
-        public void RawPointerEvent(
-            RawPointerEventType eventType, string pointerType,
-            RawPointerPoint p, RawInputModifiers modifiers, long touchPointId)
-        {
-            if (_inputRoot is { }
-                && Input is { } input)
-            {
-                var device = GetPointerDevice(pointerType);
-                var args = device is TouchDevice ?
-                    new RawTouchEventArgs(device, Timestamp, _inputRoot, eventType, p, modifiers, touchPointId) :
-                    new RawPointerEventArgs(device, Timestamp, _inputRoot, eventType, p, modifiers)
-                    {
-                        RawPointerId = touchPointId
-                    };
-
-                input.Invoke(args);
-            }
-        }
-
-        private IPointerDevice GetPointerDevice(string pointerType)
-        {
-            return pointerType switch
-            {
-                "touch" => _touchDevice,
-                "pen" => _penDevice,
-                _ => MouseDevice
-            };
-        }
-
-        public void RawMouseWheelEvent(Point p, Vector v, RawInputModifiers modifiers)
-        {
-            if (_inputRoot is { })
-            {
-                Input?.Invoke(new RawMouseWheelEventArgs(MouseDevice, Timestamp, _inputRoot, p, v, modifiers));
-            }
-        }
-
-        public bool RawKeyboardEvent(RawKeyEventType type, string code, string key, RawInputModifiers modifiers)
-        {
-            if (Keycodes.KeyCodes.TryGetValue(code, out var avkey))
-            {
-                if (_inputRoot is { })
-                {
-                    var args = new RawKeyEventArgs(KeyboardDevice, Timestamp, _inputRoot, type, avkey, modifiers);
-                    
-                    Input?.Invoke(args);
-                    
-                    return args.Handled;
-                }
-            }
-            else if (Keycodes.KeyCodes.TryGetValue(key, out avkey))
-            {
-                if (_inputRoot is { })
-                {
-                    var args = new RawKeyEventArgs(KeyboardDevice, Timestamp, _inputRoot, type, avkey, modifiers);
-                    
-                    Input?.Invoke(args);
-
-                    return args.Handled;
-                }
-            }
-
-            return false;
-        }
-
-        public void RawTextEvent(string text)
-        {
-            if (_inputRoot is { })
-            {
-                Input?.Invoke(new RawTextInputEventArgs(KeyboardDevice, Timestamp, _inputRoot, text));
-            }
-        }
-
-        public void Dispose()
-        {
-
-        }
-
-        public IRenderer CreateRenderer(IRenderRoot root)
-        {
-            var loop = AvaloniaLocator.Current.GetRequiredService<IRenderLoop>();
-            return new CompositingRenderer(root, new Compositor(loop, null));
-        }
-
-        public void Invalidate(Rect rect)
-        {
-            //Console.WriteLine("invalidate rect called");
-        }
-
-        public void SetInputRoot(IInputRoot inputRoot)
-        {
-            _inputRoot = inputRoot;
-        }
-
-        public Point PointToClient(PixelPoint point) => new Point(point.X, point.Y);
-
-        public PixelPoint PointToScreen(Point point) => new PixelPoint((int)point.X, (int)point.Y);
-
-        public void SetCursor(ICursorImpl? cursor)
-        {
-            var val = (cursor as CssCursor)?.Value ?? CssCursor.Default;
-            if (_currentCursor != val)
-            {
-                SetCssCursor?.Invoke(val);
-                _currentCursor = val;
-            }
-        }
-
-        public IPopupImpl? CreatePopup()
-        {
-            return null;
-        }
-
-        public void SetTransparencyLevelHint(WindowTransparencyLevel transparencyLevel)
-        {
-
-        }
-
-        public Size ClientSize => _clientSize;
-        public Size? FrameSize => null;
-        public double RenderScaling => _currentSurface?.Scaling ?? 1;
-
-        public IEnumerable<object> Surfaces => new object[] { _currentSurface! };
-
-        public Action<string>? SetCssCursor { get; set; }
-        public Action<RawInputEventArgs>? Input { get; set; }
-        public Action<Rect>? Paint { get; set; }
-        public Action<Size, PlatformResizeReason>? Resized { get; set; }
-        public Action<double>? ScalingChanged { get; set; }
-        public Action<WindowTransparencyLevel>? TransparencyLevelChanged { get; set; }
-        public Action? Closed { get; set; }
-        public Action? LostFocus { get; set; }
-        public IMouseDevice MouseDevice { get; } = new MouseDevice();
-
-        public IKeyboardDevice KeyboardDevice { get; } = BlazorWindowingPlatform.Keyboard;
-        public WindowTransparencyLevel TransparencyLevel { get; }
-        public AcrylicPlatformCompensationLevels AcrylicCompensationLevels { get; }
-
-        public ITextInputMethodImpl TextInputMethod => _avaloniaView;
-
-        public INativeControlHostImpl? NativeControlHost => _avaloniaView.GetNativeControlHostImpl();
-        public IStorageProvider StorageProvider => _avaloniaView.GetStorageProvider();
-    }
-}

+ 0 - 50
src/Web/Avalonia.Web.Blazor/WinStubs.cs

@@ -1,50 +0,0 @@
-using Avalonia.Controls;
-using Avalonia.Controls.Platform;
-using Avalonia.Input;
-using Avalonia.Input.Platform;
-using Avalonia.Platform;
-
-#nullable enable
-
-namespace Avalonia.Web.Blazor
-{
-    internal class IconLoaderStub : IPlatformIconLoader
-    {
-        private class IconStub : IWindowIconImpl
-        {
-            public void Save(Stream outputStream)
-            {
-
-            }
-        }
-
-        public IWindowIconImpl LoadIcon(string fileName) => new IconStub();
-
-        public IWindowIconImpl LoadIcon(Stream stream) => new IconStub();
-
-        public IWindowIconImpl LoadIcon(IBitmapImpl bitmap) => new IconStub();
-    }
-
-    internal class ScreenStub : IScreenImpl
-    {
-        public int ScreenCount => 1;
-
-        public IReadOnlyList<Screen> AllScreens { get; } =
-            new[] { new Screen(96, new PixelRect(0, 0, 4000, 4000), new PixelRect(0, 0, 4000, 4000), true) };
-
-        public Screen? ScreenFromPoint(PixelPoint point)
-        {
-            return ScreenHelper.ScreenFromPoint(point, AllScreens);
-        }
-
-        public Screen? ScreenFromRect(PixelRect rect)
-        {
-            return ScreenHelper.ScreenFromRect(rect, AllScreens);
-        }
-
-        public Screen? ScreenFromWindow(IWindowBaseImpl window)
-        {
-            return ScreenHelper.ScreenFromWindow(window, AllScreens);
-        }
-    }
-}

+ 0 - 106
src/Web/Avalonia.Web.Blazor/WindowingPlatform.cs

@@ -1,106 +0,0 @@
-using Avalonia.Controls.Platform;
-using Avalonia.Input;
-using Avalonia.Input.Platform;
-using Avalonia.Platform;
-using Avalonia.Rendering;
-using Avalonia.Threading;
-
-#nullable enable
-
-namespace Avalonia.Web.Blazor
-{
-    public class BlazorWindowingPlatform : IWindowingPlatform, IPlatformSettings, IPlatformThreadingInterface
-    {
-        private bool _signaled;
-        private static KeyboardDevice? s_keyboard;
-
-        public IWindowImpl CreateWindow() => throw new NotSupportedException();
-
-        IWindowImpl IWindowingPlatform.CreateEmbeddableWindow()
-        {
-            throw new NotImplementedException();
-        }
-
-        public ITrayIconImpl? CreateTrayIcon()
-        {
-            return null;
-        }
-
-        public static KeyboardDevice Keyboard => s_keyboard ??
-            throw new InvalidOperationException("BlazorWindowingPlatform not registered.");
-
-        public static void Register()
-        {
-            var instance = new BlazorWindowingPlatform();
-            s_keyboard = new KeyboardDevice();
-            AvaloniaLocator.CurrentMutable
-                .Bind<IClipboard>().ToSingleton<ClipboardImpl>()
-                .Bind<ICursorFactory>().ToSingleton<CssCursorFactory>()
-                .Bind<IKeyboardDevice>().ToConstant(s_keyboard)
-                .Bind<IPlatformSettings>().ToConstant(instance)
-                .Bind<IPlatformThreadingInterface>().ToConstant(instance)
-                .Bind<IRenderLoop>().ToConstant(new RenderLoop())
-                .Bind<IRenderTimer>().ToConstant(ManualTriggerRenderTimer.Instance)
-                .Bind<IWindowingPlatform>().ToConstant(instance)
-                .Bind<IPlatformIconLoader>().ToSingleton<IconLoaderStub>()
-                .Bind<PlatformHotkeyConfiguration>().ToSingleton<PlatformHotkeyConfiguration>();
-        }
-
-        public Size DoubleClickSize { get; } = new Size(2, 2);
-
-        public TimeSpan DoubleClickTime { get; } = TimeSpan.FromMilliseconds(500);
-
-        public Size TouchDoubleClickSize => new Size(16, 16);
-
-        public TimeSpan TouchDoubleClickTime => DoubleClickTime;
-        public void RunLoop(CancellationToken cancellationToken)
-        {
-            throw new NotSupportedException();
-        }
-
-        public IDisposable StartTimer(DispatcherPriority priority, TimeSpan interval, Action tick)
-        {
-            return GetRuntimePlatform()
-                .StartSystemTimer(interval, () =>
-                {
-                    Dispatcher.UIThread.RunJobs(priority);
-                    tick();
-                });
-        }
-
-        public void Signal(DispatcherPriority priority)
-        {
-            if (_signaled)
-                return;
-            
-            _signaled = true;
-            
-            IDisposable? disp = null;
-            
-            disp = GetRuntimePlatform()
-                .StartSystemTimer(TimeSpan.FromMilliseconds(1),
-                    () =>
-                    {
-                        _signaled = false;
-                        disp?.Dispose();
-
-                        Signaled?.Invoke(null);
-                    });
-        }
-
-        public bool CurrentThreadIsLoopThread
-        {
-            get
-            {
-                return true; // Blazor is single threaded.
-            }
-        }
-
-        public event Action<DispatcherPriority?>? Signaled;
-
-        private static IRuntimePlatform GetRuntimePlatform()
-        {
-            return AvaloniaLocator.Current.GetRequiredService<IRuntimePlatform>();
-        }
-    }
-}

+ 0 - 1
src/Web/Avalonia.Web.Blazor/_Imports.razor

@@ -1 +0,0 @@
-@using Microsoft.AspNetCore.Components.Web

+ 0 - 16
src/Web/Avalonia.Web.Blazor/webapp/build.js

@@ -1,16 +0,0 @@
-require("esbuild").build({
-    entryPoints: [
-        "./modules/Avalonia.ts",
-        "./modules/Storage.ts"
-    ],
-    outdir: "../wwwroot",
-    bundle: true,
-    minify: true,
-    format: "esm",
-    target: "es2016",
-    platform: "browser",
-    sourcemap: "linked",
-    loader: {".ts": "ts"}
-  })
-  .then(() => console.log("⚡ Done"))
-  .catch(() => process.exit(1));

+ 0 - 7
src/Web/Avalonia.Web.Blazor/webapp/modules/Avalonia.ts

@@ -1,7 +0,0 @@
-export { DpiWatcher } from "./Avalonia/DpiWatcher"
-export { InputHelper } from "./Avalonia/InputHelper"
-export { FocusHelper } from "./Avalonia/FocusHelper"
-export { NativeControlHost } from "./Avalonia/NativeControlHost"
-export { SizeWatcher } from "./Avalonia/SizeWatcher"
-export { SKHtmlCanvas } from "./Avalonia/SKHtmlCanvas"
-export { CaretHelper } from "./Avalonia/CaretHelper"

+ 0 - 149
src/Web/Avalonia.Web.Blazor/webapp/modules/Avalonia/CaretHelper.ts

@@ -1,149 +0,0 @@
-// Based on https://github.com/component/textarea-caret-position/blob/master/index.js
-export class CaretHelper {
-    public static getCaretCoordinates(
-        element: HTMLInputElement | HTMLTextAreaElement,
-        position: number,
-        options?: { debug: boolean }
-    ) {
-        if (!isBrowser) {
-            throw new Error(
-                "textarea-caret-position#getCaretCoordinates should only be called in a browser"
-            );
-        }
-
-        const debug = (options && options.debug) || false;
-        if (debug) {
-            const el = document.querySelector(
-                "#input-textarea-caret-position-mirror-div"
-            );
-            if (el) el.parentNode?.removeChild(el);
-        }
-
-        // The mirror div will replicate the textarea's style
-        const div = document.createElement("div");
-        div.id = "input-textarea-caret-position-mirror-div";
-        document.body.appendChild(div);
-
-        const style = div.style;
-        const computed = window.getComputedStyle
-            ? window.getComputedStyle(element)
-            : ((element as any)["currentStyle"] as CSSStyleDeclaration); // currentStyle for IE < 9
-        const isInput = element.nodeName === "INPUT";
-
-        // Default textarea styles
-        style.whiteSpace = "pre-wrap";
-        if (!isInput) style.wordWrap = "break-word"; // only for textarea-s
-
-        // Position off-screen
-        style.position = "absolute"; // required to return coordinates properly
-        if (!debug) style.visibility = "hidden"; // not 'display: none' because we want rendering
-
-        // Transfer the element's properties to the div
-        properties.forEach((prop: string) => {
-            if (isInput && prop === "lineHeight") {
-                // Special case for <input>s because text is rendered centered and line height may be != height
-                if (computed.boxSizing === "border-box") {
-                    const height = parseInt(computed.height);
-                    const outerHeight =
-                        parseInt(computed.paddingTop) +
-                        parseInt(computed.paddingBottom) +
-                        parseInt(computed.borderTopWidth) +
-                        parseInt(computed.borderBottomWidth);
-                    const targetHeight = outerHeight + parseInt(computed.lineHeight);
-                    if (height > targetHeight) {
-                        style.lineHeight = height - outerHeight + "px";
-                    } else if (height === targetHeight) {
-                        style.lineHeight = computed.lineHeight;
-                    } else {
-                        style.lineHeight = "0";
-                    }
-                } else {
-                    style.lineHeight = computed.height;
-                }
-            } else {
-                (style as any)[prop] = (computed as any)[prop];
-            }
-        });
-
-        if (isFirefox) {
-            // Firefox lies about the overflow property for textareas: https://bugzilla.mozilla.org/show_bug.cgi?id=984275
-            if (element.scrollHeight > parseInt(computed.height))
-                style.overflowY = "scroll";
-        } else {
-            style.overflow = "hidden"; // for Chrome to not render a scrollbar; IE keeps overflowY = 'scroll'
-        }
-
-        div.textContent = element.value.substring(0, position);
-        // The second special handling for input type="text" vs textarea:
-        // spaces need to be replaced with non-breaking spaces - https://stackoverflow.com/a/13402035/1269037
-        if (isInput) div.textContent = div.textContent.replace(/\s/g, "\u00a0");
-
-        const span = document.createElement("span");
-        // Wrapping must be replicated *exactly*, including when a long word gets
-        // onto the next line, with whitespace at the end of the line before (#7).
-        // The  *only* reliable way to do that is to copy the *entire* rest of the
-        // textarea's content into the <span> created at the caret position.
-        // For inputs, just '.' would be enough, but no need to bother.
-        span.textContent = element.value.substring(position) || "."; // || because a completely empty faux span doesn't render at all
-        div.appendChild(span);
-
-        const coordinates = {
-            top: span.offsetTop + parseInt(computed["borderTopWidth"]),
-            left: span.offsetLeft + parseInt(computed["borderLeftWidth"]),
-            height: parseInt(computed["lineHeight"]),
-        };
-
-        if (debug) {
-            span.style.backgroundColor = "#aaa";
-        } else {
-            document.body.removeChild(div);
-        }
-
-        return coordinates;
-    }
-}
-
-
-var properties = [
-    "direction", // RTL support
-    "boxSizing",
-    "width", // on Chrome and IE, exclude the scrollbar, so the mirror div wraps exactly as the textarea does
-    "height",
-    "overflowX",
-    "overflowY", // copy the scrollbar for IE
-
-    "borderTopWidth",
-    "borderRightWidth",
-    "borderBottomWidth",
-    "borderLeftWidth",
-    "borderStyle",
-
-    "paddingTop",
-    "paddingRight",
-    "paddingBottom",
-    "paddingLeft",
-
-    // https://developer.mozilla.org/en-US/docs/Web/CSS/font
-    "fontStyle",
-    "fontVariant",
-    "fontWeight",
-    "fontStretch",
-    "fontSize",
-    "fontSizeAdjust",
-    "lineHeight",
-    "fontFamily",
-
-    "textAlign",
-    "textTransform",
-    "textIndent",
-    "textDecoration", // might not make a difference, but better be safe
-
-    "letterSpacing",
-    "wordSpacing",
-
-    "tabSize",
-    "MozTabSize",
-];
-
-const isBrowser = typeof window !== "undefined";
-const isFirefox = isBrowser && (window as any).mozInnerScreenX != null;

+ 0 - 40
src/Web/Avalonia.Web.Blazor/webapp/modules/Avalonia/DpiWatcher.ts

@@ -1,40 +0,0 @@
-export class DpiWatcher {
-    static lastDpi: number;
-    static timerId: number;
-    static callback?: DotNet.DotNetObject;
-
-    public static getDpi() {
-        return window.devicePixelRatio;
-    }
-
-    public static start(callback: DotNet.DotNetObject): number {
-        //console.info(`Starting DPI watcher with callback ${callback._id}...`);
-
-        DpiWatcher.lastDpi = window.devicePixelRatio;
-        DpiWatcher.timerId = window.setInterval(DpiWatcher.update, 1000);
-        DpiWatcher.callback = callback;
-
-        return DpiWatcher.lastDpi;
-    }
-
-    public static stop() {
-        //console.info(`Stopping DPI watcher with callback ${DpiWatcher.callback._id}...`);
-
-        window.clearInterval(DpiWatcher.timerId);
-
-        DpiWatcher.callback = undefined;
-    }
-
-    static update() {
-        if (!DpiWatcher.callback)
-            return;
-
-        const currentDpi = window.devicePixelRatio;
-        const lastDpi = DpiWatcher.lastDpi;
-        DpiWatcher.lastDpi = currentDpi;
-
-        if (Math.abs(lastDpi - currentDpi) > 0.001) {
-            DpiWatcher.callback.invokeMethod('Invoke', lastDpi, currentDpi);
-        }
-    }
-}

+ 0 - 9
src/Web/Avalonia.Web.Blazor/webapp/modules/Avalonia/FocusHelper.ts

@@ -1,9 +0,0 @@
-export class FocusHelper {
-    public static focus(inputElement: HTMLElement) {
-        inputElement.focus();
-    }
-
-    public static setCursor(inputElement: HTMLInputElement, kind: string) {
-        inputElement.style.cursor = kind;
-    }
-}

+ 0 - 86
src/Web/Avalonia.Web.Blazor/webapp/modules/Avalonia/InputHelper.ts

@@ -1,86 +0,0 @@
-import {CaretHelper} from "./CaretHelper";
-
-export class InputHelper {
-    static inputCallback?: DotNet.DotNetObject;
-    static compositionCallback?: DotNet.DotNetObject
-    
-    public static start(inputElement: HTMLInputElement, compositionCallback: DotNet.DotNetObject, inputCallback: DotNet.DotNetObject)
-    {       
-        InputHelper.compositionCallback = compositionCallback;
-
-        inputElement.addEventListener('compositionstart', InputHelper.onCompositionEvent);
-        inputElement.addEventListener('compositionupdate', InputHelper.onCompositionEvent);
-        inputElement.addEventListener('compositionend', InputHelper.onCompositionEvent);    
-
-        InputHelper.inputCallback = inputCallback;
-
-        inputElement.addEventListener('input', InputHelper.onInputEvent);
-    }
-    
-    public static clear(inputElement: HTMLInputElement) {
-        inputElement.value = "";
-    }
-    public static focus(inputElement: HTMLInputElement) {
-        inputElement.focus();
-    }
-
-    public static setCursor(inputElement: HTMLInputElement, kind: string) {
-        inputElement.style.cursor = kind;
-    }
-    
-    public static setBounds(inputElement: HTMLInputElement, x: number, y: number, caretWidth: number, caretHeight: number, caret: number) {
-        inputElement.style.left = (x).toFixed(0) + "px";
-        inputElement.style.top = (y).toFixed(0) + "px";
-        
-        let {height, left, top} = CaretHelper.getCaretCoordinates(inputElement, caret);
-
-        inputElement.style.left = (x - left).toFixed(0) + "px";
-        inputElement.style.top = (y - top).toFixed(0) + "px";
-    }
-
-    public static hide(inputElement: HTMLInputElement) {
-        inputElement.style.display = 'none';
-    }
-
-    public static show(inputElement: HTMLInputElement) {
-        inputElement.style.display = 'block';
-    }
-
-    public static setSurroundingText(inputElement: HTMLInputElement, text: string, start: number, end: number) {
-        if (!inputElement) {
-            return;
-        }
-
-        inputElement.value = text;
-        inputElement.setSelectionRange(start, end);
-        inputElement.style.width = "20px";
-        inputElement.style.width = inputElement.scrollWidth + "px";
-    }
-
-    private static onCompositionEvent(ev: CompositionEvent)
-    {
-        if(!InputHelper.compositionCallback)
-            return;
-        
-        switch (ev.type)
-        {
-            case "compositionstart":
-            case "compositionupdate":
-            case "compositionend":
-                InputHelper.compositionCallback.invokeMethod('Invoke', ev.type, ev.data);
-                break;
-        }
-    }
-
-    private static onInputEvent(ev: Event) {
-        if (!InputHelper.inputCallback)
-            return;
-
-        var inputEvent = ev as InputEvent;
-
-        InputHelper.inputCallback.invokeMethod('Invoke', ev.type, inputEvent.data);
-    }
-}
-
-
-

+ 0 - 61
src/Web/Avalonia.Web.Blazor/webapp/modules/Avalonia/NativeControlHost.ts

@@ -1,61 +0,0 @@
-export class NativeControlHost {
-    public static CreateDefaultChild(parent: HTMLElement): HTMLElement {
-        return document.createElement("div");
-    }
-
-    // Used to convert ElementReference to JSObjectReference.
-    // Is there a better way?
-    public static GetReference(element: Element): Element {
-        return element;
-    }
-
-    public static CreateAttachment(): NativeControlHostTopLevelAttachment {
-        return new NativeControlHostTopLevelAttachment();
-    }
-}
-
-class NativeControlHostTopLevelAttachment {
-    _child?: HTMLElement;
-    _host?: HTMLElement;
-
-    InitializeWithChildHandle(child: HTMLElement) {
-        this._child = child;
-        this._child.style.position = "absolute";
-    }
-
-    AttachTo(host: HTMLElement): void {
-        if (this._host && this._child) {
-            this._host.removeChild(this._child);
-        }
-
-        this._host = host;
-
-        if (this._host && this._child) {
-            this._host.appendChild(this._child);
-        }
-    }
-
-    ShowInBounds(x: number, y: number, width: number, height: number): void {
-        if (this._child) {
-            this._child.style.top = y + "px";
-            this._child.style.left = x + "px";
-            this._child.style.width = width + "px";
-            this._child.style.height = height + "px";
-            this._child.style.display = "block";
-        }
-    }
-
-    HideWithSize(width: number, height: number): void {
-        if (this._child) {
-            this._child.style.width = width + "px";
-            this._child.style.height = height + "px";
-            this._child.style.display = "none";
-        }
-    }
-
-    ReleaseChild(): void {
-        if (this._child) {
-            this._child = undefined;
-        }
-    }
-}

+ 0 - 255
src/Web/Avalonia.Web.Blazor/webapp/modules/Avalonia/SKHtmlCanvas.ts

@@ -1,255 +0,0 @@
-// aliases for emscripten
-declare let GL: any;
-declare let GLctx: WebGLRenderingContext;
-declare let Module: EmscriptenModule;
-
-// container for gl info
-type SKGLViewInfo = {
-    context: WebGLRenderingContext | WebGL2RenderingContext | undefined;
-    fboId: number;
-    stencil: number;
-    sample: number;
-    depth: number;
-}
-
-// alias for a potential skia html canvas
-type SKHtmlCanvasElement = {
-    SKHtmlCanvas: SKHtmlCanvas | undefined
-} & HTMLCanvasElement
-
-export class SKHtmlCanvas {
-    static elements: Map<string, HTMLCanvasElement>;
-
-    htmlCanvas: HTMLCanvasElement;
-    glInfo?: SKGLViewInfo;
-    renderFrameCallback: DotNet.DotNetObject;
-    renderLoopEnabled: boolean = false;
-    renderLoopRequest: number = 0;
-    newWidth?: number;
-    newHeight?: number;
-
-    public static initGL(element: HTMLCanvasElement, elementId: string, callback: DotNet.DotNetObject): SKGLViewInfo | null {
-        var view = SKHtmlCanvas.init(true, element, elementId, callback);
-        if (!view || !view.glInfo)
-            return null;
-
-        return view.glInfo;
-    }
-
-    public static initRaster(element: HTMLCanvasElement, elementId: string, callback: DotNet.DotNetObject): boolean {
-        var view = SKHtmlCanvas.init(false, element, elementId, callback);
-        if (!view)
-            return false;
-
-        return true;
-    }
-
-    static init(useGL: boolean, element: HTMLCanvasElement, elementId: string, callback: DotNet.DotNetObject): SKHtmlCanvas | null {
-        var htmlCanvas = element as SKHtmlCanvasElement;
-        if (!htmlCanvas) {
-            console.error(`No canvas element was provided.`);
-            return null;
-        }
-
-        if (!SKHtmlCanvas.elements)
-            SKHtmlCanvas.elements = new Map<string, HTMLCanvasElement>();
-        SKHtmlCanvas.elements.set(elementId, element);
-
-        const view = new SKHtmlCanvas(useGL, element, callback);
-
-        htmlCanvas.SKHtmlCanvas = view;
-
-        return view;
-    }
-
-    public static deinit(elementId: string) {
-        if (!elementId)
-            return;
-
-        const element = SKHtmlCanvas.elements.get(elementId);
-        SKHtmlCanvas.elements.delete(elementId);
-
-        const htmlCanvas = element as SKHtmlCanvasElement;
-        if (!htmlCanvas || !htmlCanvas.SKHtmlCanvas)
-            return;
-
-        htmlCanvas.SKHtmlCanvas.deinit();
-        htmlCanvas.SKHtmlCanvas = undefined;
-    }
-
-    public static requestAnimationFrame(element: HTMLCanvasElement, renderLoop?: boolean) {
-        const htmlCanvas = element as SKHtmlCanvasElement;
-        if (!htmlCanvas || !htmlCanvas.SKHtmlCanvas)
-            return;
-
-        htmlCanvas.SKHtmlCanvas.requestAnimationFrame(renderLoop);
-    }
-
-    public static setCanvasSize(element: HTMLCanvasElement, width: number, height: number) {
-        const htmlCanvas = element as SKHtmlCanvasElement;
-        if (!htmlCanvas || !htmlCanvas.SKHtmlCanvas)
-            return;
-
-        htmlCanvas.SKHtmlCanvas.setCanvasSize(width, height);
-    }
-
-    public static setEnableRenderLoop(element: HTMLCanvasElement, enable: boolean) {
-        const htmlCanvas = element as SKHtmlCanvasElement;
-        if (!htmlCanvas || !htmlCanvas.SKHtmlCanvas)
-            return;
-
-        htmlCanvas.SKHtmlCanvas.setEnableRenderLoop(enable);
-    }
-
-    public static putImageData(element: HTMLCanvasElement, pData: number, width: number, height: number) {
-        const htmlCanvas = element as SKHtmlCanvasElement;
-        if (!htmlCanvas || !htmlCanvas.SKHtmlCanvas)
-            return;
-
-        htmlCanvas.SKHtmlCanvas.putImageData(pData, width, height);
-    }
-
-    public constructor(useGL: boolean, element: HTMLCanvasElement, callback: DotNet.DotNetObject) {
-        this.htmlCanvas = element;
-        this.renderFrameCallback = callback;
-
-        if (useGL) {
-            const ctx = SKHtmlCanvas.createWebGLContext(this.htmlCanvas);
-            if (!ctx) {
-                console.error(`Failed to create WebGL context: err ${ctx}`);
-                return;
-            }
-
-            // make current
-            GL.makeContextCurrent(ctx);
-
-            // read values
-            const fbo = GLctx.getParameter(GLctx.FRAMEBUFFER_BINDING);
-            this.glInfo = {
-                context: ctx,
-                fboId: fbo ? fbo.id : 0,
-                stencil: GLctx.getParameter(GLctx.STENCIL_BITS),
-                sample: 0, // TODO: GLctx.getParameter(GLctx.SAMPLES)
-                depth: GLctx.getParameter(GLctx.DEPTH_BITS),
-            };
-        }
-    }
-
-    public deinit() {
-        this.setEnableRenderLoop(false);
-    }
-
-    public setCanvasSize(width: number, height: number) {
-        this.newWidth = width;
-        this.newHeight = height;
-
-        if (this.htmlCanvas.width != this.newWidth) {
-            this.htmlCanvas.width = this.newWidth;
-        }
-
-        if (this.htmlCanvas.height != this.newHeight) {
-            this.htmlCanvas.height = this.newHeight;
-        }
-
-        if (this.glInfo) {
-            // make current
-            GL.makeContextCurrent(this.glInfo.context);
-        }
-    }
-
-    public requestAnimationFrame(renderLoop?: boolean) {
-        // optionally update the render loop
-        if (renderLoop !== undefined && this.renderLoopEnabled !== renderLoop)
-            this.setEnableRenderLoop(renderLoop);
-
-        // skip because we have a render loop
-        if (this.renderLoopRequest !== 0)
-            return;
-
-        // add the draw to the next frame
-        this.renderLoopRequest = window.requestAnimationFrame(() => {
-            if (this.glInfo) {
-                // make current
-                GL.makeContextCurrent(this.glInfo.context);
-            }
-
-            if (this.htmlCanvas.width != this.newWidth) {
-                this.htmlCanvas.width = this.newWidth || 0;
-            }
-
-            if (this.htmlCanvas.height != this.newHeight) {
-                this.htmlCanvas.height = this.newHeight || 0;
-            }
-
-            this.renderFrameCallback.invokeMethod('Invoke');
-            this.renderLoopRequest = 0;
-
-            // we may want to draw the next frame
-            if (this.renderLoopEnabled)
-                this.requestAnimationFrame();
-        });
-    }
-
-    public setEnableRenderLoop(enable: boolean) {
-        this.renderLoopEnabled = enable;
-
-        // either start the new frame or cancel the existing one
-        if (enable) {
-            //console.info(`Enabling render loop with callback ${this.renderFrameCallback._id}...`);
-            this.requestAnimationFrame();
-        } else if (this.renderLoopRequest !== 0) {
-            window.cancelAnimationFrame(this.renderLoopRequest);
-            this.renderLoopRequest = 0;
-        }
-    }
-
-    public putImageData(pData: number, width: number, height: number): boolean {
-        if (this.glInfo || !pData || width <= 0 || width <= 0)
-            return false;
-
-        var ctx = this.htmlCanvas.getContext('2d');
-        if (!ctx) {
-            console.error(`Failed to obtain 2D canvas context.`);
-            return false;
-        }
-
-        // make sure the canvas is scaled correctly for the drawing
-        this.htmlCanvas.width = width;
-        this.htmlCanvas.height = height;
-
-        // set the canvas to be the bytes
-        var buffer = new Uint8ClampedArray(Module.HEAPU8.buffer, pData, width * height * 4);
-        var imageData = new ImageData(buffer, width, height);
-        ctx.putImageData(imageData, 0, 0);
-
-        return true;
-    }
-
-    static createWebGLContext(htmlCanvas: HTMLCanvasElement): WebGLRenderingContext | WebGL2RenderingContext {
-        const contextAttributes = {
-            alpha: 1,
-            depth: 1,
-            stencil: 8,
-            antialias: 0,
-            premultipliedAlpha: 1,
-            preserveDrawingBuffer: 0,
-            preferLowPowerToHighPerformance: 0,
-            failIfMajorPerformanceCaveat: 0,
-            majorVersion: 2,
-            minorVersion: 0,
-            enableExtensionsByDefault: 1,
-            explicitSwapControl: 0,
-            renderViaOffscreenBackBuffer: 1,
-        };
-
-        let ctx: WebGLRenderingContext = GL.createContext(htmlCanvas, contextAttributes);
-        if (!ctx && contextAttributes.majorVersion > 1) {
-            console.warn('Falling back to WebGL 1.0');
-            contextAttributes.majorVersion = 1;
-            contextAttributes.minorVersion = 0;
-            ctx = GL.createContext(htmlCanvas, contextAttributes);
-        }
-
-        return ctx;
-    }
-}

+ 0 - 67
src/Web/Avalonia.Web.Blazor/webapp/modules/Avalonia/SizeWatcher.ts

@@ -1,67 +0,0 @@
-type SizeWatcherElement = {
-    SizeWatcher: SizeWatcherInstance;
-} & HTMLElement
-
-type SizeWatcherInstance = {
-    callback: DotNet.DotNetObject;
-}
-
-export class SizeWatcher {
-    static observer: ResizeObserver;
-    static elements: Map<string, HTMLElement>;
-
-    public static observe(element: HTMLElement, elementId: string, callback: DotNet.DotNetObject) {
-        if (!element || !callback)
-            return;
-
-        //console.info(`Adding size watcher observation with callback ${callback._id}...`);
-
-        SizeWatcher.init();
-
-        const watcherElement = element as SizeWatcherElement;
-        watcherElement.SizeWatcher = {
-            callback: callback
-        };
-
-        SizeWatcher.elements.set(elementId, element);
-        SizeWatcher.observer.observe(element);
-
-        SizeWatcher.invoke(element);
-    }
-
-    public static unobserve(elementId: string) {
-        if (!elementId || !SizeWatcher.observer)
-            return;
-
-        //console.info('Removing size watcher observation...');
-
-        const element = SizeWatcher.elements.get(elementId)!;
-
-        SizeWatcher.elements.delete(elementId);
-        SizeWatcher.observer.unobserve(element);
-    }
-
-    static init() {
-        if (SizeWatcher.observer)
-            return;
-
-        //console.info('Starting size watcher...');
-
-        SizeWatcher.elements = new Map<string, HTMLElement>();
-        SizeWatcher.observer = new ResizeObserver((entries) => {
-            for (let entry of entries) {
-                SizeWatcher.invoke(entry.target);
-            }
-        });
-    }
-
-    static invoke(element: Element) {
-        const watcherElement = element as SizeWatcherElement;
-        const instance = watcherElement.SizeWatcher;
-
-        if (!instance || !instance.callback)
-            return;
-
-        return instance.callback.invokeMethod('Invoke', element.clientWidth, element.clientHeight);
-    }
-}

+ 0 - 1
src/Web/Avalonia.Web.Blazor/webapp/modules/Storage.ts

@@ -1 +0,0 @@
-export { StorageProvider } from "./Storage/StorageProvider"

+ 0 - 79
src/Web/Avalonia.Web.Blazor/webapp/modules/Storage/IndexedDbWrapper.ts

@@ -1,79 +0,0 @@
-class InnerDbConnection {
-    constructor(private database: IDBDatabase) { }
-
-    private openStore(store: string, mode: IDBTransactionMode): IDBObjectStore {
-        const tx = this.database.transaction(store, mode);
-        return tx.objectStore(store);
-    }
-
-    public put(store: string, obj: any, key?: IDBValidKey): Promise<IDBValidKey> {
-        const os = this.openStore(store, "readwrite");
-
-        return new Promise((resolve, reject) => {
-            const response = os.put(obj, key);
-            response.onsuccess = () => {
-                resolve(response.result);
-            };
-            response.onerror = () => {
-                reject(response.error);
-            };
-        });
-    }
-
-    public get(store: string, key: IDBValidKey): any {
-        const os = this.openStore(store, "readonly");
-
-        return new Promise((resolve, reject) => {
-            const response = os.get(key);
-            response.onsuccess = () => {
-                resolve(response.result);
-            };
-            response.onerror = () => {
-                reject(response.error);
-            };
-        });
-    }
-
-    public delete(store: string, key: IDBValidKey): Promise<void> {
-        const os = this.openStore(store, "readwrite");
-
-        return new Promise((resolve, reject) => {
-            const response = os.delete(key);
-            response.onsuccess = () => {
-                resolve();
-            };
-            response.onerror = () => {
-                reject(response.error);
-            };
-        });
-    }
-
-    public close() {
-        this.database.close();
-    }
-}
-
-export class IndexedDbWrapper {
-    constructor(private databaseName: string, private objectStores: [string]) {
-    }
-
-    public connect(): Promise<InnerDbConnection> {
-        const conn = window.indexedDB.open(this.databaseName, 1);
-
-        conn.onupgradeneeded = event => {
-            const db = (<IDBRequest<IDBDatabase>>event.target).result;
-            this.objectStores.forEach(store => {
-                db.createObjectStore(store);
-            });
-        };
-
-        return new Promise((resolve, reject) => {
-            conn.onsuccess = event => {
-                resolve(new InnerDbConnection((<IDBRequest<IDBDatabase>>event.target).result));
-            };
-            conn.onerror = event => {
-                reject((<IDBRequest<IDBDatabase>>event.target).error);
-            };
-        });
-    }
-}

+ 0 - 204
src/Web/Avalonia.Web.Blazor/webapp/modules/Storage/StorageProvider.ts

@@ -1,204 +0,0 @@
-import { IndexedDbWrapper } from "./IndexedDbWrapper";
-
-declare global {
-    type WellKnownDirectory = "desktop" | "documents" | "downloads" | "music" | "pictures" | "videos";
-    type StartInDirectory = WellKnownDirectory | FileSystemHandle;
-    interface OpenFilePickerOptions {
-        startIn?: StartInDirectory
-    }
-    interface SaveFilePickerOptions {
-        startIn?: StartInDirectory
-    }
-}
-
-const fileBookmarksStore: string = "fileBookmarks";
-const avaloniaDb = new IndexedDbWrapper("AvaloniaDb", [
-    fileBookmarksStore
-]);
-
-class StorageItem {
-    constructor(public handle: FileSystemHandle, private bookmarkId?: string) { }
-
-    public getName(): string {
-        return this.handle.name
-    }
-
-    public getKind(): string {
-        return this.handle.kind;
-    }
-
-    public async openRead(): Promise<Blob> {
-        if (!(this.handle instanceof FileSystemFileHandle)) {
-            throw new Error("StorageItem is not a file");
-        }
-
-        await this.verityPermissions('read');
-
-        const file = await this.handle.getFile();
-        return file;
-    }
-
-    public async openWrite(): Promise<FileSystemWritableFileStream> {
-        if (!(this.handle instanceof FileSystemFileHandle)) {
-            throw new Error("StorageItem is not a file");
-        }
-
-        await this.verityPermissions('readwrite');
-
-        return await this.handle.createWritable({ keepExistingData: true });
-    }
-
-    public async getProperties(): Promise<{ Size: number, LastModified: number, Type: string } | null> {
-        const file = this.handle instanceof FileSystemFileHandle
-            && await this.handle.getFile();
-
-        if (!file) {
-            return null;
-        }
-
-        return {
-            Size: file.size,
-            LastModified: file.lastModified,
-            Type: file.type
-        }
-    }
-
-    public async getItems(): Promise<StorageItems> {
-        if (this.handle.kind !== "directory"){
-            return new StorageItems([]);
-        }
-        
-        const items: StorageItem[] = [];
-        for await (const [key, value] of (this.handle as any).entries()) {
-            items.push(new StorageItem(value));
-        }
-        return new StorageItems(items);
-    }
-    
-    private async verityPermissions(mode: FileSystemPermissionMode): Promise<void | never> {
-        if (await this.handle.queryPermission({ mode }) === 'granted') {
-            return;
-        }
-
-        if (await this.handle.requestPermission({ mode }) === "denied") {
-            throw new Error("Read permissions denied");
-        }
-    }
-
-    public async saveBookmark(): Promise<string> {
-        // If file was previously bookmarked, just return old one.
-        if (this.bookmarkId) {
-            return this.bookmarkId;
-        }
-        
-        const connection = await avaloniaDb.connect();
-        try {
-            const key = await connection.put(fileBookmarksStore, this.handle, this.generateBookmarkId());
-            return <string>key;
-        }
-        finally {
-            connection.close();
-        }
-    }
-
-    public async deleteBookmark(): Promise<void> {
-        if (!this.bookmarkId) {
-            return;
-        }
-
-        const connection = await avaloniaDb.connect();
-        try {
-            const key = await connection.delete(fileBookmarksStore, this.bookmarkId);
-        }
-        finally {
-            connection.close();
-        }
-    }
-
-    private generateBookmarkId(): string {
-        return Date.now().toString(36) + Math.random().toString(36).substring(2);
-    }
-}
-
-class StorageItems {
-    constructor(private items: StorageItem[]) { }
-
-    public count(): number {
-        return this.items.length;
-    }
-
-    public at(index: number): StorageItem {
-        return this.items[index];
-    }
-}
-
-export class StorageProvider {
-
-    public static canOpen(): boolean {
-        return typeof window.showOpenFilePicker !== 'undefined';
-    }
-
-    public static canSave(): boolean {
-        return typeof window.showSaveFilePicker !== 'undefined';
-    }
-
-    public static canPickFolder(): boolean {
-        return typeof window.showDirectoryPicker !== 'undefined';
-    }
-
-    public static async selectFolderDialog(
-        startIn: StorageItem | null)
-        : Promise<StorageItem> {
-
-        // 'Picker' API doesn't accept "null" as a parameter, so it should be set to undefined.
-        const options: DirectoryPickerOptions = {
-            startIn: (startIn?.handle || undefined)
-        };
-
-        const handle = await window.showDirectoryPicker(options);
-        return new StorageItem(handle);
-    }
-
-    public static async openFileDialog(
-        startIn: StorageItem | null, multiple: boolean,
-        types: FilePickerAcceptType[] | null, excludeAcceptAllOption: boolean)
-        : Promise<StorageItems> {
-
-        const options: OpenFilePickerOptions = {
-            startIn: (startIn?.handle || undefined),
-            multiple,
-            excludeAcceptAllOption,
-            types: (types || undefined)
-        };
-
-        const handles = await window.showOpenFilePicker(options);
-        return new StorageItems(handles.map((handle: FileSystemHandle) => new StorageItem(handle)));
-    }
-
-    public static async saveFileDialog(
-        startIn: StorageItem | null, suggestedName: string | null,
-        types: FilePickerAcceptType[] | null, excludeAcceptAllOption: boolean)
-        : Promise<StorageItem> {
-
-        const options: SaveFilePickerOptions = {
-            startIn: (startIn?.handle || undefined),
-            suggestedName: (suggestedName || undefined),
-            excludeAcceptAllOption,
-            types: (types || undefined)
-        };
-
-        const handle = await window.showSaveFilePicker(options);
-        return new StorageItem(handle);
-    }
-
-    public static async openBookmark(key: string): Promise<StorageItem | null> {
-        const connection = await avaloniaDb.connect();
-        try {
-            const handle = await connection.get(fileBookmarksStore, key);
-            return handle && new StorageItem(handle, key);
-        }
-        finally {
-            connection.close();
-        }
-    }
-}

+ 0 - 13
src/Web/Avalonia.Web.Blazor/webapp/package.json

@@ -1,13 +0,0 @@
-{
-  "name": "avalonia.web",
-  "scripts": {
-    "prebuild": "tsc -noEmit",
-    "build": "node build.js"
-  },
-  "devDependencies": {
-    "@types/emscripten": "^1.39.6",
-    "@types/wicg-file-system-access": "^2020.9.5",
-    "typescript": "^4.7.4",
-    "esbuild": "^0.15.7"
-  }
-}

+ 0 - 18
src/Web/Avalonia.Web.Blazor/webapp/tsconfig.json

@@ -1,18 +0,0 @@
-{
-  "compilerOptions": {
-    "target": "es2016",
-    "strict": true,
-    "sourceMap": true,
-    "outDir": "../wwwroot",
-    "noEmitOnError": true,
-    "isolatedModules": true, // we need it for esbuild
-    "lib": [
-      "dom",
-      "es2016",
-      "esnext.asynciterable"
-    ]
-  },
-  "exclude": [
-    "node_modules"
-  ]
-}

+ 0 - 56
src/Web/Avalonia.Web.Blazor/webapp/types/dotnet/index.d.ts

@@ -1,56 +0,0 @@
-// Type definitions for non-npm package @blazor/javascript-interop 3.1
-// Project: https://docs.microsoft.com/en-us/aspnet/core/blazor/javascript-interop?view=aspnetcore-3.1
-// Definitions by: Piotr Błażejewicz (Peter Blazejewicz) <https://github.com/peterblazejewicz>
-// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
-// Minimum TypeScript Version: 3.0
-
-// Here be dragons!
-// This is community-maintained definition file intended to ease the process of developing
-// high quality JavaScript interop code to be used in Blazor application from your C# .NET code.
-// Could be removed without a notice in case official definition types ships with Blazor itself.
-
-// tslint:disable:no-unnecessary-generics
-
-declare namespace DotNet {
-    /**
-     * Invokes the specified .NET public method synchronously. Not all hosting scenarios support
-     * synchronous invocation, so if possible use invokeMethodAsync instead.
-     *
-     * @param assemblyName The short name (without key/version or .dll extension) of the .NET assembly containing the method.
-     * @param methodIdentifier The identifier of the method to invoke. The method must have a [JSInvokable] attribute specifying this identifier.
-     * @param args Arguments to pass to the method, each of which must be JSON-serializable.
-     * @returns The result of the operation.
-     */
-    function invokeMethod<T>(assemblyName: string, methodIdentifier: string, ...args: any[]): T;
-    /**
-     * Invokes the specified .NET public method asynchronously.
-     *
-     * @param assemblyName The short name (without key/version or .dll extension) of the .NET assembly containing the method.
-     * @param methodIdentifier The identifier of the method to invoke. The method must have a [JSInvokable] attribute specifying this identifier.
-     * @param args Arguments to pass to the method, each of which must be JSON-serializable.
-     * @returns A promise representing the result of the operation.
-     */
-    function invokeMethodAsync<T>(assemblyName: string, methodIdentifier: string, ...args: any[]): Promise<T>;
-    /**
-     * Represents the .NET instance passed by reference to JavaScript.
-     */
-    interface DotNetObject {
-        /**
-         * Invokes the specified .NET instance public method synchronously. Not all hosting scenarios support
-         * synchronous invocation, so if possible use invokeMethodAsync instead.
-         *
-         * @param methodIdentifier The identifier of the method to invoke. The method must have a [JSInvokable] attribute specifying this identifier.
-         * @param args Arguments to pass to the method, each of which must be JSON-serializable.
-         * @returns The result of the operation.
-         */
-        invokeMethod<T>(methodIdentifier: string, ...args: any[]): T;
-        /**
-         * Invokes the specified .NET instance public method asynchronously.
-         *
-         * @param methodIdentifier The identifier of the method to invoke. The method must have a [JSInvokable] attribute specifying this identifier.
-         * @param args Arguments to pass to the method, each of which must be JSON-serializable.
-         * @returns A promise representing the result of the operation.
-         */
-        invokeMethodAsync<T>(methodIdentifier: string, ...args: any[]): Promise<T>;
-    }
-}

+ 0 - 41
src/Web/Avalonia.Web.Sample/Avalonia.Web.Sample.csproj

@@ -1,41 +0,0 @@
-<Project Sdk="Microsoft.NET.Sdk">
-  <PropertyGroup>
-    <TargetFramework>net7.0</TargetFramework>
-    <RuntimeIdentifier>browser-wasm</RuntimeIdentifier>
-    <WasmMainJSPath>main.js</WasmMainJSPath>
-    <OutputType>Exe</OutputType>
-    <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
-    <MSBuildEnableWorkloadResolver>true</MSBuildEnableWorkloadResolver>
-    <WasmBuildNative>true</WasmBuildNative>
-    <EmccFlags>-sVERBOSE -sERROR_ON_UNDEFINED_SYMBOLS=0</EmccFlags>
-  </PropertyGroup>
-
-  <PropertyGroup Condition="'$(Configuration)'=='Release'">
-    <RunAOTCompilation>true</RunAOTCompilation>
-    <PublishTrimmed>true</PublishTrimmed>
-    <TrimMode>full</TrimMode>
-    <WasmBuildNative>true</WasmBuildNative>
-    <InvariantGlobalization>true</InvariantGlobalization>
-    <WasmEnableSIMD>true</WasmEnableSIMD>
-    <EmccCompileOptimizationFlag>-O3</EmccCompileOptimizationFlag>
-    <EmccLinkOptimizationFlag>-O3</EmccLinkOptimizationFlag>
-  </PropertyGroup>
-
-  <ItemGroup>
-    <ProjectReference Include="..\..\..\samples\ControlCatalog\ControlCatalog.csproj" />
-    <ProjectReference Include="..\Avalonia.Web\Avalonia.Web.csproj" />
-    <ProjectReference Include="..\..\..\src\Skia\Avalonia.Skia\Avalonia.Skia.csproj" />
-  </ItemGroup>
-
-  <ItemGroup>
-    <WasmExtraFilesToDeploy Include="index.html" />
-    <WasmExtraFilesToDeploy Include="main.js" />
-    <WasmExtraFilesToDeploy Include="embed.js" />
-    <WasmExtraFilesToDeploy Include="favicon.ico" />
-    <WasmExtraFilesToDeploy Include="Logo.svg" />
-    <WasmExtraFilesToDeploy Include="app.css" />
-  </ItemGroup>
-
-  <Import Project="..\Avalonia.Web\Avalonia.Web.props" />
-  <Import Project="..\Avalonia.Web\Avalonia.Web.targets" />
-</Project>

+ 0 - 44
src/Web/Avalonia.Web.Sample/EmbedSample.Browser.cs

@@ -1,44 +0,0 @@
-using System;
-using System.Runtime.InteropServices.JavaScript;
-
-using Avalonia;
-using Avalonia.Platform;
-using Avalonia.Web;
-
-using ControlCatalog.Pages;
-
-namespace ControlCatalog.Web;
-
-public class EmbedSampleWeb : INativeDemoControl
-{
-    public IPlatformHandle CreateControl(bool isSecond, IPlatformHandle parent, Func<IPlatformHandle> createDefault)
-    {
-        if (isSecond)
-        {
-            var iframe = EmbedInterop.CreateElement("iframe");
-            iframe.SetProperty("src", "https://www.youtube.com/embed/kZCIporjJ70");
-
-            return new JSObjectControlHandle(iframe);
-        }
-        else
-        {
-            var defaultHandle = (JSObjectControlHandle)createDefault();
-
-            _ = JSHost.ImportAsync("embed.js", "./embed.js").ContinueWith(_ =>
-            {
-                EmbedInterop.AddAppButton(defaultHandle.Object);
-            });
-
-            return defaultHandle;
-        }
-    }
-}
-
-internal static partial class EmbedInterop
-{
-    [JSImport("globalThis.document.createElement")]
-    public static partial JSObject CreateElement(string tagName);
-
-    [JSImport("addAppButton", "embed.js")]
-    public static partial void AddAppButton(JSObject parentObject);
-}

+ 0 - 19
src/Web/Avalonia.Web.Sample/Program.cs

@@ -1,19 +0,0 @@
-using Avalonia;
-using Avalonia.Web;
-using ControlCatalog;
-using ControlCatalog.Web;
-
-internal partial class Program
-{
-    private static void Main(string[] args)
-    {
-        BuildAvaloniaApp()
-            .AfterSetup(_ =>
-            {
-                ControlCatalog.Pages.EmbedSample.Implementation = new EmbedSampleWeb();
-            }).SetupBrowserApp("out");
-    }
-
-    public static AppBuilder BuildAvaloniaApp()
-           => AppBuilder.Configure<App>();
-}

+ 4 - 4
src/Web/Avalonia.Web/Avalonia.Web.csproj

@@ -39,10 +39,6 @@
     </Content>
     </Content>
   </ItemGroup>
   </ItemGroup>
 
 
-  <ItemGroup>
-    <Folder Include="wwwroot\" />
-  </ItemGroup>
-
   <Target Name="NpmInstall" Inputs="webapp/package.json" Outputs="webapp/node_modules/.install-stamp">
   <Target Name="NpmInstall" Inputs="webapp/package.json" Outputs="webapp/node_modules/.install-stamp">
     <Exec Command="npm install" WorkingDirectory="webapp" />
     <Exec Command="npm install" WorkingDirectory="webapp" />
     <!-- Write the stamp file, so incremental builds work -->
     <!-- Write the stamp file, so incremental builds work -->
@@ -52,4 +48,8 @@
     <Exec Command="npm run build" WorkingDirectory="webapp" />
     <Exec Command="npm run build" WorkingDirectory="webapp" />
   </Target>
   </Target>
 
 
+  <ItemGroup Label="InternalsVisibleTo">
+    <InternalsVisibleTo Include="Avalonia.Web.Blazor, PublicKey=$(AvaloniaPublicKey)" />
+  </ItemGroup>
+  
 </Project>
 </Project>

+ 9 - 13
src/Web/Avalonia.Web/AvaloniaView.cs

@@ -37,16 +37,14 @@ namespace Avalonia.Web
 
 
         private bool _useGL;        
         private bool _useGL;        
         private ITextInputMethodClient? _client;
         private ITextInputMethodClient? _client;
-        private static int _canvasCount;
 
 
         public AvaloniaView(string divId)
         public AvaloniaView(string divId)
+            : this(DomHelper.GetElementById(divId) ?? throw new Exception($"Element with id {divId} was not found in the html document."))
         {
         {
-            var host = DomHelper.GetElementById(divId);
-            if (host == null)
-            {
-                throw new Exception($"Element with id {divId} was not found in the html document.");
-            }
+        }
 
 
+        public AvaloniaView(JSObject host)
+        {
             var hostContent = DomHelper.CreateAvaloniaHost(host);
             var hostContent = DomHelper.CreateAvaloniaHost(host);
             if (hostContent == null)
             if (hostContent == null)
             {
             {
@@ -64,8 +62,6 @@ namespace Avalonia.Web
 
 
             _splash = DomHelper.GetElementById("avalonia-splash");
             _splash = DomHelper.GetElementById("avalonia-splash");
 
 
-            _canvas.SetProperty("id", $"avaloniaCanvas{_canvasCount++}");
-
             _topLevelImpl = new BrowserTopLevelImpl(this);
             _topLevelImpl = new BrowserTopLevelImpl(this);
 
 
             _topLevel = new WebEmbeddableControlRoot(_topLevelImpl, () =>
             _topLevel = new WebEmbeddableControlRoot(_topLevelImpl, () =>
@@ -137,7 +133,7 @@ namespace Avalonia.Web
 
 
             _topLevelImpl.SetClientSize(_canvasSize, _dpi);
             _topLevelImpl.SetClientSize(_canvasSize, _dpi);
 
 
-            DomHelper.ObserveSize(host, divId, OnSizeChanged);
+            DomHelper.ObserveSize(host, null, OnSizeChanged);
 
 
             CanvasHelper.RequestAnimationFrame(_canvas, true);
             CanvasHelper.RequestAnimationFrame(_canvas, true);
         }
         }
@@ -387,7 +383,7 @@ namespace Avalonia.Web
             InputHelper.FocusElement(_containerElement);
             InputHelper.FocusElement(_containerElement);
         }
         }
 
 
-        public void SetClient(ITextInputMethodClient? client)
+        void ITextInputMethodImpl.SetClient(ITextInputMethodClient? client)
         {
         {
             Console.WriteLine("Set Client");
             Console.WriteLine("Set Client");
             if (_client != null)
             if (_client != null)
@@ -431,18 +427,18 @@ namespace Avalonia.Web
             }
             }
         }
         }
 
 
-        public void SetCursorRect(Rect rect)
+        void ITextInputMethodImpl.SetCursorRect(Rect rect)
         {
         {
             InputHelper.FocusElement(_inputElement);
             InputHelper.FocusElement(_inputElement);
             InputHelper.SetBounds(_inputElement, (int)rect.X, (int)rect.Y, (int)rect.Width, (int)rect.Height, _client?.SurroundingText.CursorOffset ?? 0);
             InputHelper.SetBounds(_inputElement, (int)rect.X, (int)rect.Y, (int)rect.Width, (int)rect.Height, _client?.SurroundingText.CursorOffset ?? 0);
             InputHelper.FocusElement(_inputElement);
             InputHelper.FocusElement(_inputElement);
         }
         }
 
 
-        public void SetOptions(TextInputOptions options)
+        void ITextInputMethodImpl.SetOptions(TextInputOptions options)
         {
         {
         }
         }
 
 
-        public void Reset()
+        void ITextInputMethodImpl.Reset()
         {
         {
             InputHelper.ClearInputElement(_inputElement);
             InputHelper.ClearInputElement(_inputElement);
             InputHelper.SetSurroundingText(_inputElement, "", 0, 0);
             InputHelper.SetSurroundingText(_inputElement, "", 0, 0);

+ 42 - 29
src/Web/Avalonia.Web/BrowserSingleViewLifetime.cs

@@ -1,41 +1,54 @@
-using System.Runtime.InteropServices.JavaScript;
-using System;
+using System;
 using Avalonia.Controls;
 using Avalonia.Controls;
 using Avalonia.Controls.ApplicationLifetimes;
 using Avalonia.Controls.ApplicationLifetimes;
-using Avalonia.Media;
 using Avalonia.Web.Skia;
 using Avalonia.Web.Skia;
+using System.Runtime.Versioning;
 
 
-namespace Avalonia.Web
+namespace Avalonia.Web;
+
+[SupportedOSPlatform("browser")]
+public class BrowserSingleViewLifetime : ISingleViewApplicationLifetime
 {
 {
-    [System.Runtime.Versioning.SupportedOSPlatform("browser")] // gets rid of callsite warnings
-    public class BrowserSingleViewLifetime : ISingleViewApplicationLifetime
-    {
-        public AvaloniaView? View;
+    public AvaloniaView? View;
 
 
-        public Control? MainView
-        {
-            get => View!.Content;
-            set => View!.Content = value;
-        }
+    public Control? MainView
+    {
+        get => View!.Content;
+        set => View!.Content = value;
     }
     }
+}
 
 
-    public static partial class WebAppBuilder
-    {
-        public static T SetupBrowserApp<T>(
+public class BrowserPlatformOptions
+{
+    public Func<string, string> FrameworkAssetPathResolver { get; set; } = new(fileName => $"./{fileName}");
+}
+
+
+[SupportedOSPlatform("browser")]
+public static class WebAppBuilder
+{
+    public static T SetupBrowserApp<T>(
         this T builder, string mainDivId)
         this T builder, string mainDivId)
         where T : AppBuilderBase<T>, new()
         where T : AppBuilderBase<T>, new()
-        {
-            var lifetime = new BrowserSingleViewLifetime();
-
-            return builder
-                .UseWindowingSubsystem(BrowserWindowingPlatform.Register)
-                .UseSkia()
-                .With(new SkiaOptions { CustomGpuFactory = () => new BrowserSkiaGpu() })
-                .AfterSetup(b =>
-                {
-                    lifetime.View = new AvaloniaView(mainDivId);
-                })
-                .SetupWithLifetime(lifetime);
-        }
+    {
+        var lifetime = new BrowserSingleViewLifetime();
+
+        return builder
+            .UseBrowser()
+            .AfterSetup(b =>
+            {
+                lifetime.View = new AvaloniaView(mainDivId);
+            })
+            .SetupWithLifetime(lifetime);
+    }
+
+    public static T UseBrowser<T>(
+        this T builder)
+        where T : AppBuilderBase<T>, new()
+    {
+        return builder
+            .UseWindowingSubsystem(BrowserWindowingPlatform.Register)
+            .UseSkia()
+            .With(new SkiaOptions { CustomGpuFactory = () => new BrowserSkiaGpu() });
     }
     }
 }
 }

+ 1 - 1
src/Web/Avalonia.Web/Cursor.cs

@@ -5,7 +5,7 @@ using Avalonia.Platform;
 
 
 namespace Avalonia.Web
 namespace Avalonia.Web
 {
 {
-    public class CssCursor : ICursorImpl
+    internal class CssCursor : ICursorImpl
     {
     {
         public const string Default = "default";
         public const string Default = "default";
         public string? Value { get; set; }
         public string? Value { get; set; }

+ 22 - 0
src/Web/Avalonia.Web/Interop/AvaloniaModule.cs

@@ -0,0 +1,22 @@
+using System.Runtime.InteropServices.JavaScript;
+using System.Threading.Tasks;
+
+namespace Avalonia.Web.Interop;
+
+internal static class AvaloniaModule
+{
+    public const string MainModuleName = "avalonia";
+    public const string StorageModuleName = "storage";
+
+    public static Task ImportMain()
+    {
+        var options = AvaloniaLocator.Current.GetService<BrowserPlatformOptions>() ?? new BrowserPlatformOptions();
+        return JSHost.ImportAsync(MainModuleName, options.FrameworkAssetPathResolver("avalonia.js"));
+    }
+
+    public static Task ImportStorage()
+    {
+        var options = AvaloniaLocator.Current.GetService<BrowserPlatformOptions>() ?? new BrowserPlatformOptions();
+        return JSHost.ImportAsync(StorageModuleName, options.FrameworkAssetPathResolver("storage.js"));
+    }
+}

+ 3 - 3
src/Web/Avalonia.Web/Interop/CanvasHelper.cs

@@ -29,13 +29,13 @@ internal static partial class CanvasHelper
         return glInfo;
         return glInfo;
     }
     }
 
 
-    [JSImport("Canvas.requestAnimationFrame", "avalonia.ts")]
+    [JSImport("Canvas.requestAnimationFrame", AvaloniaModule.MainModuleName)]
     public static partial void RequestAnimationFrame(JSObject canvas, bool renderLoop);
     public static partial void RequestAnimationFrame(JSObject canvas, bool renderLoop);
 
 
-    [JSImport("Canvas.setCanvasSize", "avalonia.ts")]
+    [JSImport("Canvas.setCanvasSize", AvaloniaModule.MainModuleName)]
     public static partial void SetCanvasSize(JSObject canvas, int height, int width);
     public static partial void SetCanvasSize(JSObject canvas, int height, int width);
 
 
-    [JSImport("Canvas.initGL", "avalonia.ts")]
+    [JSImport("Canvas.initGL", AvaloniaModule.MainModuleName)]
     private static partial JSObject InitGL(
     private static partial JSObject InitGL(
         JSObject canvas,
         JSObject canvas,
         string canvasId,
         string canvasId,

+ 5 - 5
src/Web/Avalonia.Web/Interop/DomHelper.cs

@@ -8,20 +8,20 @@ internal static partial class DomHelper
     [JSImport("globalThis.document.getElementById")]
     [JSImport("globalThis.document.getElementById")]
     internal static partial JSObject? GetElementById(string id);
     internal static partial JSObject? GetElementById(string id);
 
 
-    [JSImport("AvaloniaDOM.createAvaloniaHost", "avalonia.ts")]
+    [JSImport("AvaloniaDOM.createAvaloniaHost", AvaloniaModule.MainModuleName)]
     public static partial JSObject CreateAvaloniaHost(JSObject element);
     public static partial JSObject CreateAvaloniaHost(JSObject element);
 
 
-    [JSImport("AvaloniaDOM.addClass", "avalonia.ts")]
+    [JSImport("AvaloniaDOM.addClass", AvaloniaModule.MainModuleName)]
     public static partial void AddCssClass(JSObject element, string className);
     public static partial void AddCssClass(JSObject element, string className);
 
 
-    [JSImport("SizeWatcher.observe", "avalonia.ts")]
+    [JSImport("SizeWatcher.observe", AvaloniaModule.MainModuleName)]
     public static partial JSObject ObserveSize(
     public static partial JSObject ObserveSize(
         JSObject canvas,
         JSObject canvas,
-        string canvasId,
+        string? canvasId,
         [JSMarshalAs<JSType.Function<JSType.Number, JSType.Number>>]
         [JSMarshalAs<JSType.Function<JSType.Number, JSType.Number>>]
         Action<int, int> onSizeChanged);
         Action<int, int> onSizeChanged);
 
 
-    [JSImport("DpiWatcher.start", "avalonia.ts")]
+    [JSImport("DpiWatcher.start", AvaloniaModule.MainModuleName)]
     public static partial double ObserveDpi(
     public static partial double ObserveDpi(
        [JSMarshalAs<JSType.Function<JSType.Number, JSType.Number>>]
        [JSMarshalAs<JSType.Function<JSType.Number, JSType.Number>>]
         Action<double, double> onDpiChanged);
         Action<double, double> onDpiChanged);

+ 12 - 12
src/Web/Avalonia.Web/Interop/InputHelper.cs

@@ -6,7 +6,7 @@ namespace Avalonia.Web.Interop;
 
 
 internal static partial class InputHelper
 internal static partial class InputHelper
 {
 {
-    [JSImport("InputHelper.subscribeKeyEvents", "avalonia.ts")]
+    [JSImport("InputHelper.subscribeKeyEvents", AvaloniaModule.MainModuleName)]
     public static partial void SubscribeKeyEvents(
     public static partial void SubscribeKeyEvents(
         JSObject htmlElement,
         JSObject htmlElement,
         [JSMarshalAs<JSType.Function<JSType.String, JSType.String, JSType.Number, JSType.Boolean>>]
         [JSMarshalAs<JSType.Function<JSType.String, JSType.String, JSType.Number, JSType.Boolean>>]
@@ -14,7 +14,7 @@ internal static partial class InputHelper
         [JSMarshalAs<JSType.Function<JSType.String, JSType.String, JSType.Number, JSType.Boolean>>]
         [JSMarshalAs<JSType.Function<JSType.String, JSType.String, JSType.Number, JSType.Boolean>>]
         Func<string, string, int, bool> keyUp);
         Func<string, string, int, bool> keyUp);
 
 
-    [JSImport("InputHelper.subscribeTextEvents", "avalonia.ts")]
+    [JSImport("InputHelper.subscribeTextEvents", AvaloniaModule.MainModuleName)]
     public static partial void SubscribeTextEvents(
     public static partial void SubscribeTextEvents(
         JSObject htmlElement,
         JSObject htmlElement,
         [JSMarshalAs<JSType.Function<JSType.String, JSType.String, JSType.Boolean>>]
         [JSMarshalAs<JSType.Function<JSType.String, JSType.String, JSType.Boolean>>]
@@ -26,7 +26,7 @@ internal static partial class InputHelper
         [JSMarshalAs<JSType.Function<JSType.Object, JSType.Boolean>>]
         [JSMarshalAs<JSType.Function<JSType.Object, JSType.Boolean>>]
         Func<JSObject, bool> onCompositionEnd);
         Func<JSObject, bool> onCompositionEnd);
 
 
-    [JSImport("InputHelper.subscribePointerEvents", "avalonia.ts")]
+    [JSImport("InputHelper.subscribePointerEvents", AvaloniaModule.MainModuleName)]
     public static partial void SubscribePointerEvents(
     public static partial void SubscribePointerEvents(
         JSObject htmlElement,
         JSObject htmlElement,
         [JSMarshalAs<JSType.Function<JSType.Object, JSType.Boolean>>]
         [JSMarshalAs<JSType.Function<JSType.Object, JSType.Boolean>>]
@@ -39,35 +39,35 @@ internal static partial class InputHelper
         Func<JSObject, bool> wheel);
         Func<JSObject, bool> wheel);
 
 
 
 
-    [JSImport("InputHelper.subscribeInputEvents", "avalonia.ts")]
+    [JSImport("InputHelper.subscribeInputEvents", AvaloniaModule.MainModuleName)]
     public static partial void SubscribeInputEvents(
     public static partial void SubscribeInputEvents(
         JSObject htmlElement,
         JSObject htmlElement,
         [JSMarshalAs<JSType.Function<JSType.String, JSType.Boolean>>]
         [JSMarshalAs<JSType.Function<JSType.String, JSType.Boolean>>]
         Func<string, bool> input);
         Func<string, bool> input);
 
 
 
 
-    [JSImport("InputHelper.clearInput", "avalonia.ts")]
+    [JSImport("InputHelper.clearInput", AvaloniaModule.MainModuleName)]
     public static partial void ClearInputElement(JSObject htmlElement);
     public static partial void ClearInputElement(JSObject htmlElement);
 
 
-    [JSImport("InputHelper.isInputElement", "avalonia.ts")]
+    [JSImport("InputHelper.isInputElement", AvaloniaModule.MainModuleName)]
     public static partial void IsInputElement(JSObject htmlElement);
     public static partial void IsInputElement(JSObject htmlElement);
 
 
-    [JSImport("InputHelper.focusElement", "avalonia.ts")]
+    [JSImport("InputHelper.focusElement", AvaloniaModule.MainModuleName)]
     public static partial void FocusElement(JSObject htmlElement);
     public static partial void FocusElement(JSObject htmlElement);
 
 
-    [JSImport("InputHelper.setCursor", "avalonia.ts")]
+    [JSImport("InputHelper.setCursor", AvaloniaModule.MainModuleName)]
     public static partial void SetCursor(JSObject htmlElement, string kind);
     public static partial void SetCursor(JSObject htmlElement, string kind);
 
 
-    [JSImport("InputHelper.hide", "avalonia.ts")]
+    [JSImport("InputHelper.hide", AvaloniaModule.MainModuleName)]
     public static partial void HideElement(JSObject htmlElement);
     public static partial void HideElement(JSObject htmlElement);
 
 
-    [JSImport("InputHelper.show", "avalonia.ts")]
+    [JSImport("InputHelper.show", AvaloniaModule.MainModuleName)]
     public static partial void ShowElement(JSObject htmlElement);
     public static partial void ShowElement(JSObject htmlElement);
 
 
-    [JSImport("InputHelper.setSurroundingText", "avalonia.ts")]
+    [JSImport("InputHelper.setSurroundingText", AvaloniaModule.MainModuleName)]
     public static partial void SetSurroundingText(JSObject htmlElement, string text, int start, int end);
     public static partial void SetSurroundingText(JSObject htmlElement, string text, int start, int end);
 
 
-    [JSImport("InputHelper.setBounds", "avalonia.ts")]
+    [JSImport("InputHelper.setBounds", AvaloniaModule.MainModuleName)]
     public static partial void SetBounds(JSObject htmlElement, int x, int y, int width, int height, int caret);
     public static partial void SetBounds(JSObject htmlElement, int x, int y, int width, int height, int caret);
 
 
     [JSImport("globalThis.navigator.clipboard.readText")]
     [JSImport("globalThis.navigator.clipboard.readText")]

+ 7 - 7
src/Web/Avalonia.Web/Interop/NativeControlHostHelper.cs

@@ -5,24 +5,24 @@ namespace Avalonia.Web.Interop;
 
 
 internal static partial class NativeControlHostHelper
 internal static partial class NativeControlHostHelper
 {
 {
-    [JSImport("NativeControlHost.createDefaultChild", "avalonia.ts")]
+    [JSImport("NativeControlHost.createDefaultChild", AvaloniaModule.MainModuleName)]
     internal static partial JSObject CreateDefaultChild(JSObject? parent);
     internal static partial JSObject CreateDefaultChild(JSObject? parent);
 
 
-    [JSImport("NativeControlHost.createAttachment", "avalonia.ts")]
+    [JSImport("NativeControlHost.createAttachment", AvaloniaModule.MainModuleName)]
     internal static partial JSObject CreateAttachment();
     internal static partial JSObject CreateAttachment();
 
 
-    [JSImport("NativeControlHost.initializeWithChildHandle", "avalonia.ts")]
+    [JSImport("NativeControlHost.initializeWithChildHandle", AvaloniaModule.MainModuleName)]
     internal static partial void InitializeWithChildHandle(JSObject element, JSObject child);
     internal static partial void InitializeWithChildHandle(JSObject element, JSObject child);
 
 
-    [JSImport("NativeControlHost.attachTo", "avalonia.ts")]
+    [JSImport("NativeControlHost.attachTo", AvaloniaModule.MainModuleName)]
     internal static partial void AttachTo(JSObject element, JSObject? host);
     internal static partial void AttachTo(JSObject element, JSObject? host);
 
 
-    [JSImport("NativeControlHost.showInBounds", "avalonia.ts")]
+    [JSImport("NativeControlHost.showInBounds", AvaloniaModule.MainModuleName)]
     internal static partial void ShowInBounds(JSObject element, double x, double y, double width, double height);
     internal static partial void ShowInBounds(JSObject element, double x, double y, double width, double height);
 
 
-    [JSImport("NativeControlHost.hideWithSize", "avalonia.ts")]
+    [JSImport("NativeControlHost.hideWithSize", AvaloniaModule.MainModuleName)]
     internal static partial void HideWithSize(JSObject element, double width, double height);
     internal static partial void HideWithSize(JSObject element, double width, double height);
 
 
-    [JSImport("NativeControlHost.releaseChild", "avalonia.ts")]
+    [JSImport("NativeControlHost.releaseChild", AvaloniaModule.MainModuleName)]
     internal static partial void ReleaseChild(JSObject element);
     internal static partial void ReleaseChild(JSObject element);
 }
 }

+ 15 - 15
src/Web/Avalonia.Web/Interop/StorageHelper.cs

@@ -5,51 +5,51 @@ namespace Avalonia.Web.Interop;
 
 
 internal static partial class StorageHelper
 internal static partial class StorageHelper
 {
 {
-    [JSImport("Caniuse.canShowOpenFilePicker", "avalonia.ts")]
+    [JSImport("Caniuse.canShowOpenFilePicker", AvaloniaModule.MainModuleName)]
     public static partial bool CanShowOpenFilePicker();
     public static partial bool CanShowOpenFilePicker();
 
 
-    [JSImport("Caniuse.canShowSaveFilePicker", "avalonia.ts")]
+    [JSImport("Caniuse.canShowSaveFilePicker", AvaloniaModule.MainModuleName)]
     public static partial bool CanShowSaveFilePicker();
     public static partial bool CanShowSaveFilePicker();
 
 
-    [JSImport("Caniuse.canShowDirectoryPicker", "avalonia.ts")]
+    [JSImport("Caniuse.canShowDirectoryPicker", AvaloniaModule.MainModuleName)]
     public static partial bool CanShowDirectoryPicker();
     public static partial bool CanShowDirectoryPicker();
 
 
-    [JSImport("StorageProvider.selectFolderDialog", "storage.ts")]
+    [JSImport("StorageProvider.selectFolderDialog", AvaloniaModule.StorageModuleName)]
     public static partial Task<JSObject?> SelectFolderDialog(JSObject? startIn);
     public static partial Task<JSObject?> SelectFolderDialog(JSObject? startIn);
 
 
-    [JSImport("StorageProvider.openFileDialog", "storage.ts")]
+    [JSImport("StorageProvider.openFileDialog", AvaloniaModule.StorageModuleName)]
     public static partial Task<JSObject?> OpenFileDialog(JSObject? startIn, bool multiple,
     public static partial Task<JSObject?> OpenFileDialog(JSObject? startIn, bool multiple,
         [JSMarshalAs<JSType.Array<JSType.Any>>] object[]? types, bool excludeAcceptAllOption);
         [JSMarshalAs<JSType.Array<JSType.Any>>] object[]? types, bool excludeAcceptAllOption);
 
 
-    [JSImport("StorageProvider.saveFileDialog", "storage.ts")]
+    [JSImport("StorageProvider.saveFileDialog", AvaloniaModule.StorageModuleName)]
     public static partial Task<JSObject?> SaveFileDialog(JSObject? startIn, string? suggestedName,
     public static partial Task<JSObject?> SaveFileDialog(JSObject? startIn, string? suggestedName,
         [JSMarshalAs<JSType.Array<JSType.Any>>] object[]? types, bool excludeAcceptAllOption);
         [JSMarshalAs<JSType.Array<JSType.Any>>] object[]? types, bool excludeAcceptAllOption);
 
 
-    [JSImport("StorageProvider.openBookmark", "storage.ts")]
+    [JSImport("StorageProvider.openBookmark", AvaloniaModule.StorageModuleName)]
     public static partial Task<JSObject?> OpenBookmark(string key);
     public static partial Task<JSObject?> OpenBookmark(string key);
 
 
-    [JSImport("StorageItem.saveBookmark", "storage.ts")]
+    [JSImport("StorageItem.saveBookmark", AvaloniaModule.StorageModuleName)]
     public static partial Task<string?> SaveBookmark(JSObject item);
     public static partial Task<string?> SaveBookmark(JSObject item);
 
 
-    [JSImport("StorageItem.deleteBookmark", "storage.ts")]
+    [JSImport("StorageItem.deleteBookmark", AvaloniaModule.StorageModuleName)]
     public static partial Task DeleteBookmark(JSObject item);
     public static partial Task DeleteBookmark(JSObject item);
 
 
-    [JSImport("StorageItem.getProperties", "storage.ts")]
+    [JSImport("StorageItem.getProperties", AvaloniaModule.StorageModuleName)]
     public static partial Task<JSObject?> GetProperties(JSObject item);
     public static partial Task<JSObject?> GetProperties(JSObject item);
 
 
-    [JSImport("StorageItem.openWrite", "storage.ts")]
+    [JSImport("StorageItem.openWrite", AvaloniaModule.StorageModuleName)]
     public static partial Task<JSObject> OpenWrite(JSObject item);
     public static partial Task<JSObject> OpenWrite(JSObject item);
 
 
-    [JSImport("StorageItem.openRead", "storage.ts")]
+    [JSImport("StorageItem.openRead", AvaloniaModule.StorageModuleName)]
     public static partial Task<JSObject> OpenRead(JSObject item);
     public static partial Task<JSObject> OpenRead(JSObject item);
 
 
-    [JSImport("StorageItem.getItems", "storage.ts")]
+    [JSImport("StorageItem.getItems", AvaloniaModule.StorageModuleName)]
     [return: JSMarshalAs<JSType.Promise<JSType.Object>>]
     [return: JSMarshalAs<JSType.Promise<JSType.Object>>]
     public static partial Task<JSObject> GetItems(JSObject item);
     public static partial Task<JSObject> GetItems(JSObject item);
 
 
-    [JSImport("StorageItems.itemsArray", "storage.ts")]
+    [JSImport("StorageItems.itemsArray", AvaloniaModule.StorageModuleName)]
     public static partial JSObject[] ItemsArray(JSObject item);
     public static partial JSObject[] ItemsArray(JSObject item);
 
 
-    [JSImport("StorageProvider.createAcceptType", "storage.ts")]
+    [JSImport("StorageProvider.createAcceptType", AvaloniaModule.StorageModuleName)]
     public static partial JSObject CreateAcceptType(string description, string[] mimeTypes);
     public static partial JSObject CreateAcceptType(string description, string[] mimeTypes);
 }
 }

+ 8 - 8
src/Web/Avalonia.Web/Interop/StreamHelper.cs

@@ -2,33 +2,33 @@
 using System.Runtime.InteropServices.JavaScript;
 using System.Runtime.InteropServices.JavaScript;
 using System.Threading.Tasks;
 using System.Threading.Tasks;
 
 
-namespace Avalonia.Web.Storage;
+namespace Avalonia.Web.Interop;
 
 
 /// <summary>
 /// <summary>
 /// Set of FileSystemWritableFileStream and Blob methods.
 /// Set of FileSystemWritableFileStream and Blob methods.
 /// </summary>
 /// </summary>
 internal static partial class StreamHelper
 internal static partial class StreamHelper
 {
 {
-    [JSImport("StreamHelper.seek", "avalonia.ts")]
+    [JSImport("StreamHelper.seek", AvaloniaModule.MainModuleName)]
     public static partial void Seek(JSObject stream, [JSMarshalAs<JSType.Number>] long position);
     public static partial void Seek(JSObject stream, [JSMarshalAs<JSType.Number>] long position);
 
 
-    [JSImport("StreamHelper.truncate", "avalonia.ts")]
+    [JSImport("StreamHelper.truncate", AvaloniaModule.MainModuleName)]
     public static partial void Truncate(JSObject stream, [JSMarshalAs<JSType.Number>] long size);
     public static partial void Truncate(JSObject stream, [JSMarshalAs<JSType.Number>] long size);
 
 
-    [JSImport("StreamHelper.write", "avalonia.ts")]
+    [JSImport("StreamHelper.write", AvaloniaModule.MainModuleName)]
     public static partial Task WriteAsync(JSObject stream, [JSMarshalAs<JSType.MemoryView>] ArraySegment<byte> data);
     public static partial Task WriteAsync(JSObject stream, [JSMarshalAs<JSType.MemoryView>] ArraySegment<byte> data);
 
 
-    [JSImport("StreamHelper.close", "avalonia.ts")]
+    [JSImport("StreamHelper.close", AvaloniaModule.MainModuleName)]
     public static partial Task CloseAsync(JSObject stream);
     public static partial Task CloseAsync(JSObject stream);
 
 
-    [JSImport("StreamHelper.byteLength", "avalonia.ts")]
+    [JSImport("StreamHelper.byteLength", AvaloniaModule.MainModuleName)]
     [return: JSMarshalAs<JSType.Number>]
     [return: JSMarshalAs<JSType.Number>]
     public static partial long ByteLength(JSObject stream);
     public static partial long ByteLength(JSObject stream);
 
 
-    [JSImport("StreamHelper.sliceArrayBuffer", "avalonia.ts")]
+    [JSImport("StreamHelper.sliceArrayBuffer", AvaloniaModule.MainModuleName)]
     private static partial Task<JSObject> SliceToArrayBuffer(JSObject stream, [JSMarshalAs<JSType.Number>] long offset, int count);
     private static partial Task<JSObject> SliceToArrayBuffer(JSObject stream, [JSMarshalAs<JSType.Number>] long offset, int count);
 
 
-    [JSImport("StreamHelper.toMemoryView", "avalonia.ts")]
+    [JSImport("StreamHelper.toMemoryView", AvaloniaModule.MainModuleName)]
     [return: JSMarshalAs<JSType.Array<JSType.Number>>]
     [return: JSMarshalAs<JSType.Array<JSType.Number>>]
     private static partial byte[] ArrayBufferToMemoryView(JSObject stream);
     private static partial byte[] ArrayBufferToMemoryView(JSObject stream);
 
 

+ 1 - 1
src/Web/Avalonia.Web/ManualTriggerRenderTimer.cs

@@ -4,7 +4,7 @@ using Avalonia.Rendering;
 
 
 namespace Avalonia.Web
 namespace Avalonia.Web
 {
 {
-    public class ManualTriggerRenderTimer : IRenderTimer
+    internal class ManualTriggerRenderTimer : IRenderTimer
     {
     {
         private static readonly Stopwatch s_sw = Stopwatch.StartNew();
         private static readonly Stopwatch s_sw = Stopwatch.StartNew();
 
 

+ 2 - 0
src/Web/Avalonia.Web/Storage/BlobReadableStream.cs

@@ -4,6 +4,8 @@ using System.Runtime.InteropServices.JavaScript;
 using System.Threading;
 using System.Threading;
 using System.Threading.Tasks;
 using System.Threading.Tasks;
 
 
+using Avalonia.Web.Interop;
+
 namespace Avalonia.Web.Storage;
 namespace Avalonia.Web.Storage;
 
 
 [System.Runtime.Versioning.SupportedOSPlatform("browser")]
 [System.Runtime.Versioning.SupportedOSPlatform("browser")]

+ 6 - 6
src/Web/Avalonia.Web/Storage/BrowserStorageProvider.cs

@@ -20,7 +20,7 @@ internal class BrowserStorageProvider : IStorageProvider
     internal const string PickerCancelMessage = "The user aborted a request";
     internal const string PickerCancelMessage = "The user aborted a request";
     internal const string NoPermissionsMessage = "Permissions denied";
     internal const string NoPermissionsMessage = "Permissions denied";
 
 
-    private readonly Lazy<Task<JSObject>> _lazyModule = new(() => JSHost.ImportAsync("storage.ts", "./storage.js"));
+    private readonly Lazy<Task> _lazyModule = new(() => AvaloniaModule.ImportStorage());
 
 
     public bool CanOpen => StorageHelper.CanShowOpenFilePicker();
     public bool CanOpen => StorageHelper.CanShowOpenFilePicker();
     public bool CanSave => StorageHelper.CanShowSaveFilePicker();
     public bool CanSave => StorageHelper.CanShowSaveFilePicker();
@@ -28,7 +28,7 @@ internal class BrowserStorageProvider : IStorageProvider
 
 
     public async Task<IReadOnlyList<IStorageFile>> OpenFilePickerAsync(FilePickerOpenOptions options)
     public async Task<IReadOnlyList<IStorageFile>> OpenFilePickerAsync(FilePickerOpenOptions options)
     {
     {
-        _ = await _lazyModule.Value;
+        await _lazyModule.Value;
         var startIn = (options.SuggestedStartLocation as JSStorageItem)?.FileHandle;
         var startIn = (options.SuggestedStartLocation as JSStorageItem)?.FileHandle;
 
 
         var (types, exludeAll) = ConvertFileTypes(options.FileTypeFilter);
         var (types, exludeAll) = ConvertFileTypes(options.FileTypeFilter);
@@ -62,7 +62,7 @@ internal class BrowserStorageProvider : IStorageProvider
 
 
     public async Task<IStorageFile?> SaveFilePickerAsync(FilePickerSaveOptions options)
     public async Task<IStorageFile?> SaveFilePickerAsync(FilePickerSaveOptions options)
     {
     {
-        _ = await _lazyModule.Value;
+        await _lazyModule.Value;
         var startIn = (options.SuggestedStartLocation as JSStorageItem)?.FileHandle;
         var startIn = (options.SuggestedStartLocation as JSStorageItem)?.FileHandle;
 
 
         var (types, exludeAll) = ConvertFileTypes(options.FileTypeChoices);
         var (types, exludeAll) = ConvertFileTypes(options.FileTypeChoices);
@@ -90,7 +90,7 @@ internal class BrowserStorageProvider : IStorageProvider
 
 
     public async Task<IReadOnlyList<IStorageFolder>> OpenFolderPickerAsync(FolderPickerOpenOptions options)
     public async Task<IReadOnlyList<IStorageFolder>> OpenFolderPickerAsync(FolderPickerOpenOptions options)
     {
     {
-        _ = await _lazyModule.Value;
+        await _lazyModule.Value;
         var startIn = (options.SuggestedStartLocation as JSStorageItem)?.FileHandle;
         var startIn = (options.SuggestedStartLocation as JSStorageItem)?.FileHandle;
 
 
         try
         try
@@ -106,14 +106,14 @@ internal class BrowserStorageProvider : IStorageProvider
 
 
     public async Task<IStorageBookmarkFile?> OpenFileBookmarkAsync(string bookmark)
     public async Task<IStorageBookmarkFile?> OpenFileBookmarkAsync(string bookmark)
     {
     {
-        _ = await _lazyModule.Value;
+        await _lazyModule.Value;
         var item = await StorageHelper.OpenBookmark(bookmark);
         var item = await StorageHelper.OpenBookmark(bookmark);
         return item is not null ? new JSStorageFile(item) : null;
         return item is not null ? new JSStorageFile(item) : null;
     }
     }
 
 
     public async Task<IStorageBookmarkFolder?> OpenFolderBookmarkAsync(string bookmark)
     public async Task<IStorageBookmarkFolder?> OpenFolderBookmarkAsync(string bookmark)
     {
     {
-        _ = await _lazyModule.Value;
+        await _lazyModule.Value;
         var item = await StorageHelper.OpenBookmark(bookmark);
         var item = await StorageHelper.OpenBookmark(bookmark);
         return item is not null ? new JSStorageFolder(item) : null;
         return item is not null ? new JSStorageFolder(item) : null;
     }
     }

+ 2 - 0
src/Web/Avalonia.Web/Storage/WriteableStream.cs

@@ -4,6 +4,8 @@ using System.Runtime.InteropServices.JavaScript;
 using System.Threading;
 using System.Threading;
 using System.Threading.Tasks;
 using System.Threading.Tasks;
 
 
+using Avalonia.Web.Interop;
+
 namespace Avalonia.Web.Storage;
 namespace Avalonia.Web.Storage;
 
 
 [System.Runtime.Versioning.SupportedOSPlatform("browser")]
 [System.Runtime.Versioning.SupportedOSPlatform("browser")]

+ 1 - 1
src/Web/Avalonia.Web/WindowingPlatform.cs

@@ -8,7 +8,7 @@ using Avalonia.Threading;
 
 
 namespace Avalonia.Web
 namespace Avalonia.Web
 {
 {
-    public class BrowserWindowingPlatform : IWindowingPlatform, IPlatformSettings, IPlatformThreadingInterface
+    internal class BrowserWindowingPlatform : IWindowingPlatform, IPlatformSettings, IPlatformThreadingInterface
     {
     {
         private bool _signaled;
         private bool _signaled;
         private static KeyboardDevice? s_keyboard;
         private static KeyboardDevice? s_keyboard;

+ 14 - 2
src/Web/Avalonia.Web/webapp/modules/avalonia.ts

@@ -6,8 +6,8 @@ import { Caniuse } from "./avalonia/caniuse";
 import { StreamHelper } from "./avalonia/stream";
 import { StreamHelper } from "./avalonia/stream";
 import { NativeControlHost } from "./avalonia/nativeControlHost";
 import { NativeControlHost } from "./avalonia/nativeControlHost";
 
 
-export async function createAvaloniaRuntime(api: RuntimeAPI): Promise<void> {
-    api.setModuleImports("avalonia.ts", {
+async function registerAvaloniaModule(api: RuntimeAPI): Promise<void> {
+    api.setModuleImports("avalonia", {
         Caniuse,
         Caniuse,
         Canvas,
         Canvas,
         InputHelper,
         InputHelper,
@@ -18,3 +18,15 @@ export async function createAvaloniaRuntime(api: RuntimeAPI): Promise<void> {
         NativeControlHost
         NativeControlHost
     });
     });
 }
 }
+export {
+    Caniuse,
+    Canvas,
+    InputHelper,
+    SizeWatcher,
+    DpiWatcher,
+    AvaloniaDOM,
+    StreamHelper,
+    NativeControlHost,
+
+    registerAvaloniaModule
+};

+ 2 - 2
src/Web/Avalonia.Web/webapp/modules/avalonia/canvas.ts

@@ -211,7 +211,7 @@ export class SizeWatcher {
     static observer: ResizeObserver;
     static observer: ResizeObserver;
     static elements: Map<string, HTMLElement>;
     static elements: Map<string, HTMLElement>;
 
 
-    public static observe(element: HTMLElement, elementId: string, callback: (width: number, height: number) => void): void {
+    public static observe(element: HTMLElement, elementId: string | undefined, callback: (width: number, height: number) => void): void {
         if (!element || !callback) {
         if (!element || !callback) {
             return;
             return;
         }
         }
@@ -223,7 +223,7 @@ export class SizeWatcher {
             callback
             callback
         };
         };
 
 
-        SizeWatcher.elements.set(elementId, element);
+        SizeWatcher.elements.set(elementId ?? element.id, element);
         SizeWatcher.observer.observe(element);
         SizeWatcher.observer.observe(element);
 
 
         SizeWatcher.invoke(element);
         SizeWatcher.invoke(element);

+ 7 - 0
src/Web/Avalonia.Web/webapp/modules/avalonia/dom.ts

@@ -4,13 +4,17 @@ export class AvaloniaDOM {
     }
     }
 
 
     static createAvaloniaHost(host: HTMLElement) {
     static createAvaloniaHost(host: HTMLElement) {
+        const randomIdPart = Math.random().toString(36).replace(/[^a-z]+/g, "").substr(2, 10);
+
         // Root element
         // Root element
         host.classList.add("avalonia-container");
         host.classList.add("avalonia-container");
         host.tabIndex = 0;
         host.tabIndex = 0;
         host.oncontextmenu = function () { return false; };
         host.oncontextmenu = function () { return false; };
+        host.style.overflow = "hidden";
 
 
         // Rendering target canvas
         // Rendering target canvas
         const canvas = document.createElement("canvas");
         const canvas = document.createElement("canvas");
+        canvas.id = `canvas${randomIdPart}`;
         canvas.classList.add("avalonia-canvas");
         canvas.classList.add("avalonia-canvas");
         canvas.style.backgroundColor = "#ccc";
         canvas.style.backgroundColor = "#ccc";
         canvas.style.width = "100%";
         canvas.style.width = "100%";
@@ -19,6 +23,7 @@ export class AvaloniaDOM {
 
 
         // Native controls host
         // Native controls host
         const nativeHost = document.createElement("div");
         const nativeHost = document.createElement("div");
+        canvas.id = `nativeHost${randomIdPart}`;
         nativeHost.classList.add("avalonia-native-host");
         nativeHost.classList.add("avalonia-native-host");
         nativeHost.style.left = "0px";
         nativeHost.style.left = "0px";
         nativeHost.style.top = "0px";
         nativeHost.style.top = "0px";
@@ -28,6 +33,7 @@ export class AvaloniaDOM {
 
 
         // IME
         // IME
         const inputElement = document.createElement("input");
         const inputElement = document.createElement("input");
+        canvas.id = `input${randomIdPart}`;
         inputElement.classList.add("avalonia-input-element");
         inputElement.classList.add("avalonia-input-element");
         inputElement.autocapitalize = "none";
         inputElement.autocapitalize = "none";
         inputElement.type = "text";
         inputElement.type = "text";
@@ -42,6 +48,7 @@ export class AvaloniaDOM {
         inputElement.style.color = "transparent";
         inputElement.style.color = "transparent";
         inputElement.style.display = "none";
         inputElement.style.display = "none";
         inputElement.style.height = "20px";
         inputElement.style.height = "20px";
+        inputElement.style.zIndex = "-1";
         inputElement.onpaste = function () { return false; };
         inputElement.onpaste = function () { return false; };
         inputElement.oncopy = function () { return false; };
         inputElement.oncopy = function () { return false; };
         inputElement.oncut = function () { return false; };
         inputElement.oncut = function () { return false; };