Browse Source

Merge branch 'master' into upd

Max Katz 3 years ago
parent
commit
6b6de2ff5b
97 changed files with 7161 additions and 163 deletions
  1. 1 0
      .gitignore
  2. 3 0
      .gitmodules
  3. 0 0
      .nuke
  4. 148 0
      .nuke/build.schema.json
  5. 4 0
      .nuke/parameters.json
  6. 15 3
      Avalonia.sln
  7. 3 2
      azure-pipelines-integrationtests.yml
  8. 24 20
      azure-pipelines.yml
  9. 7 0
      build.cmd
  10. 24 26
      build.ps1
  11. 46 14
      build.sh
  12. 2 1
      dirs.proj
  13. 0 4
      global.json
  14. 12 8
      nukebuild/Build.cs
  15. 2 2
      nukebuild/BuildParameters.cs
  16. 7 5
      nukebuild/BuildTasksPatcher.cs
  17. 2 2
      nukebuild/DotNetConfigHelper.cs
  18. 0 14
      nukebuild/MicroComGen.cs
  19. 15 0
      nukebuild/Shims.cs
  20. 24 17
      nukebuild/_build.csproj
  21. 1 0
      nukebuild/il-repack
  22. 25 10
      src/Avalonia.Base/Logging/LogArea.cs
  23. 2 0
      src/Avalonia.Base/Platform/Storage/IStorageFile.cs
  24. 1 0
      src/Avalonia.Base/Properties/AssemblyInfo.cs
  25. 0 2
      src/Avalonia.Base/Utilities/AvaloniaResourcesIndex.cs
  26. 1 1
      src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj
  27. 0 1
      src/Avalonia.Build.Tasks/GenerateAvaloniaResourcesTask.cs
  28. 359 0
      src/Avalonia.Controls.ColorPicker/ColorPalettes/MaterialColorPalette.cs
  29. 27 1
      src/Avalonia.Controls/ComboBox.cs
  30. 1 18
      src/Avalonia.Themes.Fluent/Controls/ComboBox.xaml
  31. 3 3
      src/Avalonia.Themes.Fluent/Controls/DatePicker.xaml
  32. 2 2
      src/Avalonia.Themes.Fluent/Controls/TimePicker.xaml
  33. 1 1
      src/Avalonia.Themes.Simple/Controls/DatePicker.xaml
  34. 1 1
      src/Avalonia.Themes.Simple/Controls/TimePicker.xaml
  35. 0 1
      src/Web/Avalonia.Web.Blazor/AvaloniaView.razor.cs
  36. 41 0
      src/Web/Avalonia.Web.Sample/Avalonia.Web.Sample.csproj
  37. 44 0
      src/Web/Avalonia.Web.Sample/EmbedSample.Browser.cs
  38. 5 0
      src/Web/Avalonia.Web.Sample/Logo.svg
  39. 19 0
      src/Web/Avalonia.Web.Sample/Program.cs
  40. 38 0
      src/Web/Avalonia.Web.Sample/app.css
  41. 11 0
      src/Web/Avalonia.Web.Sample/embed.js
  42. BIN
      src/Web/Avalonia.Web.Sample/favicon.ico
  43. 31 0
      src/Web/Avalonia.Web.Sample/index.html
  44. 19 0
      src/Web/Avalonia.Web.Sample/main.js
  45. 11 0
      src/Web/Avalonia.Web.Sample/runtimeconfig.template.json
  46. 55 0
      src/Web/Avalonia.Web/Avalonia.Web.csproj
  47. 5 0
      src/Web/Avalonia.Web/Avalonia.Web.props
  48. 7 0
      src/Web/Avalonia.Web/Avalonia.Web.targets
  49. 451 0
      src/Web/Avalonia.Web/AvaloniaView.cs
  50. 136 0
      src/Web/Avalonia.Web/BrowserNativeControlHost.cs
  51. 41 0
      src/Web/Avalonia.Web/BrowserSingleViewLifetime.cs
  52. 226 0
      src/Web/Avalonia.Web/BrowserTopLevelImpl.cs
  53. 29 0
      src/Web/Avalonia.Web/ClipboardImpl.cs
  54. 95 0
      src/Web/Avalonia.Web/Cursor.cs
  55. 43 0
      src/Web/Avalonia.Web/Interop/CanvasHelper.cs
  56. 28 0
      src/Web/Avalonia.Web/Interop/DomHelper.cs
  57. 78 0
      src/Web/Avalonia.Web/Interop/InputHelper.cs
  58. 28 0
      src/Web/Avalonia.Web/Interop/NativeControlHostHelper.cs
  59. 55 0
      src/Web/Avalonia.Web/Interop/StorageHelper.cs
  60. 40 0
      src/Web/Avalonia.Web/Interop/StreamHelper.cs
  61. 30 0
      src/Web/Avalonia.Web/JSObjectControlHandle.cs
  62. 129 0
      src/Web/Avalonia.Web/Keycodes.cs
  63. 18 0
      src/Web/Avalonia.Web/ManualTriggerRenderTimer.cs
  64. 26 0
      src/Web/Avalonia.Web/Skia/BrowserSkiaGpu.cs
  65. 36 0
      src/Web/Avalonia.Web/Skia/BrowserSkiaGpuRenderSession.cs
  66. 39 0
      src/Web/Avalonia.Web/Skia/BrowserSkiaGpuRenderTarget.cs
  67. 88 0
      src/Web/Avalonia.Web/Skia/BrowserSkiaRasterSurface.cs
  68. 30 0
      src/Web/Avalonia.Web/Skia/BrowserSkiaSurface.cs
  69. 9 0
      src/Web/Avalonia.Web/Skia/IBrowserSkiaSurface.cs
  70. 88 0
      src/Web/Avalonia.Web/Storage/BlobReadableStream.cs
  71. 257 0
      src/Web/Avalonia.Web/Storage/BrowserStorageProvider.cs
  72. 124 0
      src/Web/Avalonia.Web/Storage/WriteableStream.cs
  73. 68 0
      src/Web/Avalonia.Web/WebEmbeddableControlRoot.cs
  74. 48 0
      src/Web/Avalonia.Web/WinStubs.cs
  75. 105 0
      src/Web/Avalonia.Web/WindowingPlatform.cs
  76. 13 0
      src/Web/Avalonia.Web/interop.js
  77. 47 0
      src/Web/Avalonia.Web/webapp/.eslintrc.json
  78. 16 0
      src/Web/Avalonia.Web/webapp/build.js
  79. 20 0
      src/Web/Avalonia.Web/webapp/modules/avalonia.ts
  80. 13 0
      src/Web/Avalonia.Web/webapp/modules/avalonia/caniuse.ts
  81. 303 0
      src/Web/Avalonia.Web/webapp/modules/avalonia/canvas.ts
  82. 149 0
      src/Web/Avalonia.Web/webapp/modules/avalonia/caretHelper.ts
  83. 60 0
      src/Web/Avalonia.Web/webapp/modules/avalonia/dom.ts
  84. 204 0
      src/Web/Avalonia.Web/webapp/modules/avalonia/input.ts
  85. 55 0
      src/Web/Avalonia.Web/webapp/modules/avalonia/nativeControlHost.ts
  86. 40 0
      src/Web/Avalonia.Web/webapp/modules/avalonia/stream.ts
  87. 2 0
      src/Web/Avalonia.Web/webapp/modules/storage.ts
  88. 84 0
      src/Web/Avalonia.Web/webapp/modules/storage/indexedDb.ts
  89. 111 0
      src/Web/Avalonia.Web/webapp/modules/storage/storageItem.ts
  90. 70 0
      src/Web/Avalonia.Web/webapp/modules/storage/storageProvider.ts
  91. 2234 0
      src/Web/Avalonia.Web/webapp/package-lock.json
  92. 22 0
      src/Web/Avalonia.Web/webapp/package.json
  93. 19 0
      src/Web/Avalonia.Web/webapp/tsconfig.json
  94. 270 0
      src/Web/Avalonia.Web/webapp/types/dotnet.d.ts
  95. 6 0
      src/iOS/Avalonia.iOS/Avalonia.iOS.csproj
  96. 4 4
      src/iOS/Avalonia.iOS/CombinedSpan3.cs
  97. 25 0
      tests/Avalonia.Controls.UnitTests/ComboBoxTests.cs

+ 1 - 0
.gitignore

@@ -215,3 +215,4 @@ src/Web/Avalonia.Web.Blazor/Interop/Typescript/*.js
 node_modules
 src/Web/Avalonia.Web.Blazor/webapp/package-lock.json
 src/Web/Avalonia.Web.Blazor/wwwroot
+src/Web/Avalonia.Web/wwwroot

+ 3 - 0
.gitmodules

@@ -4,3 +4,6 @@
 [submodule "src/Markup/Avalonia.Markup.Xaml/XamlIl/xamlil.github"]
 	path = src/Markup/Avalonia.Markup.Xaml.Loader/xamlil.github
 	url = https://github.com/kekekeks/XamlX.git
+[submodule "nukebuild/il-repack"]
+	path = nukebuild/il-repack
+	url = https://github.com/Gillibald/il-repack

+ 0 - 0
.nuke


+ 148 - 0
.nuke/build.schema.json

@@ -0,0 +1,148 @@
+{
+  "$schema": "http://json-schema.org/draft-04/schema#",
+  "title": "Build Schema",
+  "$ref": "#/definitions/build",
+  "definitions": {
+    "build": {
+      "type": "object",
+      "properties": {
+        "Configuration": {
+          "type": "string",
+          "description": "configuration"
+        },
+        "Continue": {
+          "type": "boolean",
+          "description": "Indicates to continue a previously failed build attempt"
+        },
+        "ForceNugetVersion": {
+          "type": "string",
+          "description": "force-nuget-version"
+        },
+        "Help": {
+          "type": "boolean",
+          "description": "Shows the help text for this build assembly"
+        },
+        "Host": {
+          "type": "string",
+          "description": "Host for execution. Default is 'automatic'",
+          "enum": [
+            "AppVeyor",
+            "AzurePipelines",
+            "Bamboo",
+            "Bitbucket",
+            "Bitrise",
+            "GitHubActions",
+            "GitLab",
+            "Jenkins",
+            "Rider",
+            "SpaceAutomation",
+            "TeamCity",
+            "Terminal",
+            "TravisCI",
+            "VisualStudio",
+            "VSCode"
+          ]
+        },
+        "NoLogo": {
+          "type": "boolean",
+          "description": "Disables displaying the NUKE logo"
+        },
+        "Partition": {
+          "type": "string",
+          "description": "Partition to use on CI"
+        },
+        "Plan": {
+          "type": "boolean",
+          "description": "Shows the execution plan (HTML)"
+        },
+        "Profile": {
+          "type": "array",
+          "description": "Defines the profiles to load",
+          "items": {
+            "type": "string"
+          }
+        },
+        "Root": {
+          "type": "string",
+          "description": "Root directory during build execution"
+        },
+        "Skip": {
+          "type": "array",
+          "description": "List of targets to be skipped. Empty list skips all dependencies",
+          "items": {
+            "type": "string",
+            "enum": [
+              "CiAzureLinux",
+              "CiAzureOSX",
+              "CiAzureWindows",
+              "Clean",
+              "Compile",
+              "CompileHtmlPreviewer",
+              "CompileNative",
+              "CreateIntermediateNugetPackages",
+              "CreateNugetPackages",
+              "GenerateCppHeaders",
+              "Package",
+              "RunCoreLibsTests",
+              "RunDesignerTests",
+              "RunHtmlPreviewerTests",
+              "RunLeakTests",
+              "RunRenderTests",
+              "RunTests",
+              "ZipFiles"
+            ]
+          }
+        },
+        "SkipPreviewer": {
+          "type": "boolean",
+          "description": "skip-previewer"
+        },
+        "SkipTests": {
+          "type": "boolean",
+          "description": "skip-tests"
+        },
+        "Solution": {
+          "type": "string",
+          "description": "Path to a solution file that is automatically loaded. Default is Avalonia.sln"
+        },
+        "Target": {
+          "type": "array",
+          "description": "List of targets to be invoked. Default is '{default_target}'",
+          "items": {
+            "type": "string",
+            "enum": [
+              "CiAzureLinux",
+              "CiAzureOSX",
+              "CiAzureWindows",
+              "Clean",
+              "Compile",
+              "CompileHtmlPreviewer",
+              "CompileNative",
+              "CreateIntermediateNugetPackages",
+              "CreateNugetPackages",
+              "GenerateCppHeaders",
+              "Package",
+              "RunCoreLibsTests",
+              "RunDesignerTests",
+              "RunHtmlPreviewerTests",
+              "RunLeakTests",
+              "RunRenderTests",
+              "RunTests",
+              "ZipFiles"
+            ]
+          }
+        },
+        "Verbosity": {
+          "type": "string",
+          "description": "Logging verbosity during build execution. Default is 'Normal'",
+          "enum": [
+            "Minimal",
+            "Normal",
+            "Quiet",
+            "Verbose"
+          ]
+        }
+      }
+    }
+  }
+}

+ 4 - 0
.nuke/parameters.json

@@ -0,0 +1,4 @@
+{
+  "$schema": "./build.schema.json",
+  "Solution": ""
+}

+ 15 - 3
Avalonia.sln

@@ -212,7 +212,11 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Controls.ColorPick
 EndProject
 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.DesignerSupport.Tests", "tests\Avalonia.DesignerSupport.Tests\Avalonia.DesignerSupport.Tests.csproj", "{EABE2161-989B-42BF-BD8D-1E34B20C21F1}"
 EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DevGenerators", "src\tools\DevGenerators\DevGenerators.csproj", "{1BBFAD42-B99E-47E0-B00A-A4BC6B6BB4BB}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DevGenerators", "src\tools\DevGenerators\DevGenerators.csproj", "{1BBFAD42-B99E-47E0-B00A-A4BC6B6BB4BB}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Web", "src\Web\Avalonia.Web\Avalonia.Web.csproj", "{76D39FF6-6B4F-46C4-93CD-E6FC4665739E}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Web.Sample", "src\Web\Avalonia.Web.Sample\Avalonia.Web.Sample.csproj", "{1F61B6F1-B881-4E27-A5B0-09D1F543F7AC}"
 EndProject
 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MobileSandbox", "samples\MobileSandbox\MobileSandbox.csproj", "{3B8519C1-2F51-4F12-A348-120AB91D4532}"
 EndProject
@@ -407,9 +411,7 @@ Global
 		{BF28998D-072C-439A-AFBB-2FE5021241E0}.Release|Any CPU.ActiveCfg = Release|Any CPU
 		{BF28998D-072C-439A-AFBB-2FE5021241E0}.Release|Any CPU.Build.0 = Release|Any CPU
 		{3F00BC43-5095-477F-93D8-E65B08179A00}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
-		{3F00BC43-5095-477F-93D8-E65B08179A00}.Debug|Any CPU.Build.0 = Debug|Any CPU
 		{3F00BC43-5095-477F-93D8-E65B08179A00}.Release|Any CPU.ActiveCfg = Release|Any CPU
-		{3F00BC43-5095-477F-93D8-E65B08179A00}.Release|Any CPU.Build.0 = Release|Any CPU
 		{41B02319-965D-4945-8005-C1A3D1224165}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
 		{41B02319-965D-4945-8005-C1A3D1224165}.Debug|Any CPU.Build.0 = Debug|Any CPU
 		{41B02319-965D-4945-8005-C1A3D1224165}.Release|Any CPU.ActiveCfg = Release|Any CPU
@@ -510,6 +512,14 @@ Global
 		{1BBFAD42-B99E-47E0-B00A-A4BC6B6BB4BB}.Debug|Any CPU.Build.0 = Debug|Any CPU
 		{1BBFAD42-B99E-47E0-B00A-A4BC6B6BB4BB}.Release|Any CPU.ActiveCfg = Release|Any CPU
 		{1BBFAD42-B99E-47E0-B00A-A4BC6B6BB4BB}.Release|Any CPU.Build.0 = Release|Any CPU
+		{76D39FF6-6B4F-46C4-93CD-E6FC4665739E}.Debug|Any CPU.ActiveCfg = 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.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.Build.0 = Debug|Any CPU
 		{3B8519C1-2F51-4F12-A348-120AB91D4532}.Release|Any CPU.ActiveCfg = Release|Any CPU
@@ -583,6 +593,8 @@ Global
 		{2B390431-288C-435C-BB6B-A374033BD8D1} = {4ED8B739-6F4E-4CD4-B993-545E6B5CE637}
 		{EABE2161-989B-42BF-BD8D-1E34B20C21F1} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B}
 		{1BBFAD42-B99E-47E0-B00A-A4BC6B6BB4BB} = {4ED8B739-6F4E-4CD4-B993-545E6B5CE637}
+		{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}
 		{C90FE60B-B01E-4F35-91D6-379D6966030F} = {9B9E3891-2366-4253-A952-D08BCEB71098}

+ 3 - 2
azure-pipelines-integrationtests.yml

@@ -41,9 +41,9 @@ jobs:
 
   steps:
   - task: UseDotNet@2
-    displayName: 'Use .NET Core SDK 6.0.202'
+    displayName: 'Use .NET Core SDK 6.0.401'
     inputs:
-      version: 6.0.202
+      version: 6.0.401
 
   - task: Windows Application Driver@0
     inputs:
@@ -57,6 +57,7 @@ jobs:
       projects: 'samples/IntegrationTestApp/IntegrationTestApp.csproj'
 
   - task: DotNetCoreCLI@2
+    retryCountOnTaskFailure: 3
     inputs:
       command: 'test'
       projects: 'tests/Avalonia.IntegrationTests.Appium/Avalonia.IntegrationTests.Appium.csproj'

+ 24 - 20
azure-pipelines.yml

@@ -6,7 +6,6 @@ jobs:
   variables:
     SolutionDir: '$(Build.SourcesDirectory)'
   steps:
-    
   - task: PowerShell@2
     displayName: Get PR Number
     inputs:
@@ -31,14 +30,20 @@ jobs:
     vmImage: 'ubuntu-20.04'
   steps:
   - task: UseDotNet@2
-    displayName: 'Use .NET Core SDK 3.1.418'
+    displayName: 'Use .NET Core SDK 6.0.401'
     inputs:
-      version: 3.1.418
+      version: 6.0.401
 
   - task: UseDotNet@2
-    displayName: 'Use .NET Core SDK 6.0.202'
+    displayName: 'Use .NET Core SDK 7.0.100-rc.1.22431.12'
+    inputs:
+      version: 7.0.100-rc.1.22431.12
+
+  - task: CmdLine@2
+    displayName: 'Install Workloads'
     inputs:
-      version: 6.0.202
+      script: |
+       dotnet workload install wasm-tools wasm-experimental
 
   - task: CmdLine@2
     displayName: 'Run Build'
@@ -62,22 +67,21 @@ jobs:
     vmImage: 'macos-12'
   steps:
   - task: UseDotNet@2
-    displayName: 'Use .NET Core SDK 3.1.418'
+    displayName: 'Use .NET Core SDK 6.0.401'
     inputs:
-      version: 3.1.418
+      version: 6.0.401
 
   - task: UseDotNet@2
-    displayName: 'Use .NET Core SDK 6.0.202'
+    displayName: 'Use .NET Core SDK 7.0.100-rc.1.22431.12'
     inputs:
-      version: 6.0.202
-      
+      version: 7.0.100-rc.1.22431.12
+
   - task: CmdLine@2
-    displayName: 'Install Mono 5.18'
+    displayName: 'Install Workloads'
     inputs:
       script: |
-        curl -o ./mono.pkg https://download.mono-project.com/archive/5.18.0/macos-10-universal/MonoFramework-MDK-5.18.0.225.macos10.xamarin.universal.pkg 
-        sudo installer -verbose -pkg ./mono.pkg -target /
-
+       dotnet workload install wasm-tools wasm-experimental
+      
   - task: CmdLine@2
     displayName: 'Generate avalonia-native'
     inputs:
@@ -134,26 +138,26 @@ jobs:
     SolutionDir: '$(Build.SourcesDirectory)'
   steps:
   - task: UseDotNet@2
-    displayName: 'Use .NET Core SDK 3.1.418'
+    displayName: 'Use .NET Core SDK 6.0.401'
     inputs:
-      version: 3.1.418
+      version: 6.0.401
 
   - task: UseDotNet@2
-    displayName: 'Use .NET Core SDK 6.0.202'
+    displayName: 'Use .NET Core SDK 7.0.100-rc.1.22431.12'
     inputs:
-      version: 6.0.202
+      version: 7.0.100-rc.1.22431.12
 
   - task: CmdLine@2
     displayName: 'Install Workloads'
     inputs:
       script: |
-       dotnet workload install android ios
+       dotnet workload install android ios wasm-tools wasm-experimental
 
   - task: CmdLine@2
     displayName: 'Install Nuke'
     inputs:
       script: |
-       dotnet tool install --global Nuke.GlobalTool --version 0.24.0 
+       dotnet tool install --global Nuke.GlobalTool --version 6.2.1 
 
   - task: CmdLine@2
     displayName: 'Run Nuke'

+ 7 - 0
build.cmd

@@ -0,0 +1,7 @@
+:; set -eo pipefail
+:; SCRIPT_DIR=$(cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd)
+:; ${SCRIPT_DIR}/build.sh "$@"
+:; exit $?
+
+@ECHO OFF
+powershell -ExecutionPolicy ByPass -NoProfile -File "%~dp0build.ps1" %*

+ 24 - 26
build.ps1

@@ -1,13 +1,12 @@
 [CmdletBinding()]
 Param(
-    #[switch]$CustomParam,
     [Parameter(Position=0,Mandatory=$false,ValueFromRemainingArguments=$true)]
     [string[]]$BuildArguments
 )
 
-Write-Output "Windows PowerShell $($Host.Version)"
+Write-Output "PowerShell $($PSVersionTable.PSEdition) version $($PSVersionTable.PSVersion)"
 
-Set-StrictMode -Version 2.0; $ErrorActionPreference = "Stop"; $ConfirmPreference = "None"; trap { exit 1 }
+Set-StrictMode -Version 2.0; $ErrorActionPreference = "Stop"; $ConfirmPreference = "None"; trap { Write-Error $_ -ErrorAction Continue; exit 1 }
 $PSScriptRoot = Split-Path $MyInvocation.MyCommand.Path -Parent
 
 ###########################################################################
@@ -15,15 +14,15 @@ $PSScriptRoot = Split-Path $MyInvocation.MyCommand.Path -Parent
 ###########################################################################
 
 $BuildProjectFile = "$PSScriptRoot\nukebuild\_build.csproj"
-$TempDirectory = "$PSScriptRoot\\.tmp"
+$TempDirectory = "$PSScriptRoot\\.nuke\temp"
 
 $DotNetGlobalFile = "$PSScriptRoot\\global.json"
-$DotNetInstallUrl = "https://raw.githubusercontent.com/dotnet/cli/master/scripts/obtain/dotnet-install.ps1"
+$DotNetInstallUrl = "https://dot.net/v1/dotnet-install.ps1"
 $DotNetChannel = "Current"
 
 $env:DOTNET_SKIP_FIRST_TIME_EXPERIENCE = 1
 $env:DOTNET_CLI_TELEMETRY_OPTOUT = 1
-$env:NUGET_XMLDOC_MODE = "skip"
+$env:DOTNET_MULTILEVEL_LOOKUP = 0
 
 ###########################################################################
 # EXECUTION
@@ -34,38 +33,37 @@ function ExecSafe([scriptblock] $cmd) {
     if ($LASTEXITCODE) { exit $LASTEXITCODE }
 }
 
-# If global.json exists, load expected version
-if (Test-Path $DotNetGlobalFile) {
-    $DotNetGlobal = $(Get-Content $DotNetGlobalFile | Out-String | ConvertFrom-Json)
-    if ($DotNetGlobal.PSObject.Properties["sdk"] -and $DotNetGlobal.sdk.PSObject.Properties["version"]) {
-        $DotNetVersion = $DotNetGlobal.sdk.version
-    }
-}
-
-# If dotnet is installed locally, and expected version is not set or installation matches the expected version
+# If dotnet CLI is installed globally and it matches requested version, use for execution
 if ($null -ne (Get-Command "dotnet" -ErrorAction SilentlyContinue) -and `
-     (!(Test-Path variable:DotNetVersion) -or $(& dotnet --version) -eq $DotNetVersion)) {
+     $(dotnet --version) -and $LASTEXITCODE -eq 0) {
     $env:DOTNET_EXE = (Get-Command "dotnet").Path
 }
 else {
-    $DotNetDirectory = "$TempDirectory\dotnet-win"
-    $env:DOTNET_EXE = "$DotNetDirectory\dotnet.exe"
-
     # Download install script
     $DotNetInstallFile = "$TempDirectory\dotnet-install.ps1"
-    mkdir -force $TempDirectory > $null
+    New-Item -ItemType Directory -Path $TempDirectory -Force | Out-Null
+    [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
     (New-Object System.Net.WebClient).DownloadFile($DotNetInstallUrl, $DotNetInstallFile)
 
+    # If global.json exists, load expected version
+    if (Test-Path $DotNetGlobalFile) {
+        $DotNetGlobal = $(Get-Content $DotNetGlobalFile | Out-String | ConvertFrom-Json)
+        if ($DotNetGlobal.PSObject.Properties["sdk"] -and $DotNetGlobal.sdk.PSObject.Properties["version"]) {
+            $DotNetVersion = $DotNetGlobal.sdk.version
+        }
+    }
+
     # Install by channel or version
+    $DotNetDirectory = "$TempDirectory\dotnet-win"
     if (!(Test-Path variable:DotNetVersion)) {
-        ExecSafe { & $DotNetInstallFile -InstallDir $DotNetDirectory -Channel $DotNetChannel -NoPath }
+        ExecSafe { & powershell $DotNetInstallFile -InstallDir $DotNetDirectory -Channel $DotNetChannel -NoPath }
     } else {
-        ExecSafe { & $DotNetInstallFile -InstallDir $DotNetDirectory -Version $DotNetVersion -NoPath }
+        ExecSafe { & powershell $DotNetInstallFile -InstallDir $DotNetDirectory -Version $DotNetVersion -NoPath }
     }
-
-    $env:PATH="$DotNetDirectory;$env:PATH"
+    $env:DOTNET_EXE = "$DotNetDirectory\dotnet.exe"
 }
 
-Write-Output "Microsoft (R) .NET Core SDK version $(& $env:DOTNET_EXE --version)"
+Write-Output "Microsoft (R) .NET SDK version $(& $env:DOTNET_EXE --version)"
 
-ExecSafe { & $env:DOTNET_EXE run --project $BuildProjectFile -- $BuildArguments }
+ExecSafe { & $env:DOTNET_EXE build $BuildProjectFile /nodeReuse:false /p:UseSharedCompilation=false -nologo -clp:NoSummary --verbosity quiet }
+ExecSafe { & $env:DOTNET_EXE run --project $BuildProjectFile --no-build -- $BuildArguments }

+ 46 - 14
build.sh

@@ -1,16 +1,6 @@
 #!/usr/bin/env bash
 
-echo $(bash --version 2>&1 | head -n 1)
-
-#CUSTOMPARAM=0
-BUILD_ARGUMENTS=()
-for i in "$@"; do
-    case $(echo $1 | awk '{print tolower($0)}') in
-        # -custom-param) CUSTOMPARAM=1;;
-        *) BUILD_ARGUMENTS+=("$1") ;;
-    esac
-    shift
-done
+bash --version 2>&1 | head -n 1
 
 set -eo pipefail
 SCRIPT_DIR=$(cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd)
@@ -20,11 +10,53 @@ SCRIPT_DIR=$(cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd)
 ###########################################################################
 
 BUILD_PROJECT_FILE="$SCRIPT_DIR/nukebuild/_build.csproj"
+TEMP_DIRECTORY="$SCRIPT_DIR//.nuke/temp"
+
+DOTNET_GLOBAL_FILE="$SCRIPT_DIR//global.json"
+DOTNET_INSTALL_URL="https://dot.net/v1/dotnet-install.sh"
+DOTNET_CHANNEL="Current"
 
 export DOTNET_CLI_TELEMETRY_OPTOUT=1
 export DOTNET_SKIP_FIRST_TIME_EXPERIENCE=1
-export NUGET_XMLDOC_MODE="skip"
+export DOTNET_MULTILEVEL_LOOKUP=0
 
-dotnet --info
+###########################################################################
+# EXECUTION
+###########################################################################
 
-dotnet run --project "$BUILD_PROJECT_FILE" -- ${BUILD_ARGUMENTS[@]}
+function FirstJsonValue {
+    perl -nle 'print $1 if m{"'"$1"'": "([^"]+)",?}' <<< "${@:2}"
+}
+
+# If dotnet CLI is installed globally and it matches requested version, use for execution
+if [ -x "$(command -v dotnet)" ] && dotnet --version &>/dev/null; then
+    export DOTNET_EXE="$(command -v dotnet)"
+else
+    # Download install script
+    DOTNET_INSTALL_FILE="$TEMP_DIRECTORY/dotnet-install.sh"
+    mkdir -p "$TEMP_DIRECTORY"
+    curl -Lsfo "$DOTNET_INSTALL_FILE" "$DOTNET_INSTALL_URL"
+    chmod +x "$DOTNET_INSTALL_FILE"
+
+    # If global.json exists, load expected version
+    if [[ -f "$DOTNET_GLOBAL_FILE" ]]; then
+        DOTNET_VERSION=$(FirstJsonValue "version" "$(cat "$DOTNET_GLOBAL_FILE")")
+        if [[ "$DOTNET_VERSION" == ""  ]]; then
+            unset DOTNET_VERSION
+        fi
+    fi
+
+    # Install by channel or version
+    DOTNET_DIRECTORY="$TEMP_DIRECTORY/dotnet-unix"
+    if [[ -z ${DOTNET_VERSION+x} ]]; then
+        "$DOTNET_INSTALL_FILE" --install-dir "$DOTNET_DIRECTORY" --channel "$DOTNET_CHANNEL" --no-path
+    else
+        "$DOTNET_INSTALL_FILE" --install-dir "$DOTNET_DIRECTORY" --version "$DOTNET_VERSION" --no-path
+    fi
+    export DOTNET_EXE="$DOTNET_DIRECTORY/dotnet"
+fi
+
+echo "Microsoft (R) .NET SDK version $("$DOTNET_EXE" --version)"
+
+"$DOTNET_EXE" build "$BUILD_PROJECT_FILE" /nodeReuse:false /p:UseSharedCompilation=false -nologo -clp:NoSummary --verbosity quiet
+"$DOTNET_EXE" run --project "$BUILD_PROJECT_FILE" --no-build -- "$@"

+ 2 - 1
dirs.proj

@@ -23,12 +23,13 @@
     <ProjectReference Remove="samples/ControlCatalog.Desktop/*.*proj" />
   </ItemGroup>
   <!-- Build android and iOS projects only on Windows, where we have installed android workload -->
+
   <ItemGroup Condition="!$([MSBuild]::IsOsPlatform('Windows'))">
     <ProjectReference Remove="src/Android/**/*.*proj" />
     <ProjectReference Remove="src/iOS/**/*.*proj" />
   </ItemGroup>
 
   <ItemGroup>
-    <PackageReference Include="SlnGen" Version="2.0.40" PrivateAssets="all" />
+    <PackageReference Include="Microsoft.VisualStudio.SlnGen" Version="8.5.17" PrivateAssets="all" />
   </ItemGroup>
 </Project>

+ 0 - 4
global.json

@@ -1,8 +1,4 @@
 {
-    "sdk": {
-        "version": "6.0.202",
-        "rollForward": "latestFeature"
-    },
     "msbuild-sdks": {
         "Microsoft.Build.Traversal": "1.0.43",
         "MSBuild.Sdk.Extras": "3.0.22",

+ 12 - 8
nukebuild/Build.cs

@@ -23,6 +23,7 @@ using static Nuke.Common.Tools.MSBuild.MSBuildTasks;
 using static Nuke.Common.Tools.DotNet.DotNetTasks;
 using static Nuke.Common.Tools.Xunit.XunitTasks;
 using static Nuke.Common.Tools.VSWhere.VSWhereTasks;
+using MicroCom.CodeGenerator;
 
 /*
  Before editing this file, install support plugin for your IDE,
@@ -163,7 +164,7 @@ partial class Build : NukeBuild
                 .EnableNoBuild()
                 .EnableNoRestore()
                 .When(Parameters.PublishTestResults, _ => _
-                    .SetLogger("trx")
+                    .SetLoggers("trx")
                     .SetResultsDirectory(Parameters.TestResultsRoot)));
         }
     }
@@ -215,8 +216,6 @@ partial class Build : NukeBuild
             RunCoreTest("Avalonia.DesignerSupport.Tests");
         });
 
-    [PackageExecutable("JetBrains.dotMemoryUnit", "dotMemoryUnit.exe")] readonly Tool DotMemoryUnit;
-
     Target RunLeakTests => _ => _
         .OnlyWhenStatic(() => !Parameters.SkipTests && Parameters.IsRunningOnWindows)
         .DependsOn(Compile)
@@ -224,12 +223,9 @@ partial class Build : NukeBuild
         {
             void DoMemoryTest()
             {
-                var testAssembly = "tests\\Avalonia.LeakTests\\bin\\Release\\net461\\Avalonia.LeakTests.dll";
-                DotMemoryUnit(
-                    $"{XunitPath.DoubleQuoteIfNeeded()} --propagate-exit-code -- {testAssembly}",
-                    timeout: 120_000);
+                RunCoreTest("Avalonia.LeakTests");
             }
-            ControlFlow.ExecuteWithRetry(DoMemoryTest, waitInSeconds: 3);
+            ControlFlow.ExecuteWithRetry(DoMemoryTest, delay: TimeSpan.FromMilliseconds(3));
         });
 
     Target ZipFiles => _ => _
@@ -283,6 +279,14 @@ partial class Build : NukeBuild
         .DependsOn(Package)
         .DependsOn(ZipFiles);
 
+    Target GenerateCppHeaders => _ => _.Executes(() =>
+    {
+        var file = MicroComCodeGenerator.Parse(
+            File.ReadAllText(RootDirectory / "src" / "Avalonia.Native" / "avn.idl"));
+        File.WriteAllText(RootDirectory / "native" / "Avalonia.Native" / "inc" / "avalonia-native.h",
+            file.GenerateCppHeader());
+    });
+
 
     public static int Main() =>
         RuntimeInformation.IsOSPlatform(OSPlatform.Windows)

+ 2 - 2
nukebuild/BuildParameters.cs

@@ -74,11 +74,11 @@ public partial class Build
             MSBuildSolution = RootDirectory / "dirs.proj";
 
             // PARAMETERS
-            IsLocalBuild = Host == HostType.Console;
+            IsLocalBuild = NukeBuild.IsLocalBuild;
             IsRunningOnUnix = Environment.OSVersion.Platform == PlatformID.Unix ||
                               Environment.OSVersion.Platform == PlatformID.MacOSX;
             IsRunningOnWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
-            IsRunningOnAzure = Host == HostType.AzurePipelines ||
+            IsRunningOnAzure = Host is AzurePipelines ||
                                Environment.GetEnvironmentVariable("LOGNAME") == "vsts";
 
             if (IsRunningOnAzure)

+ 7 - 5
nukebuild/BuildTasksPatcher.cs

@@ -17,8 +17,12 @@ public class BuildTasksPatcher
             {
                 if (entry.Name == "Avalonia.Build.Tasks.dll")
                 {
-                    var temp = Path.Combine(Path.GetTempPath(), Guid.NewGuid() + ".dll");
+                    var tempDir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString());
+                    Directory.CreateDirectory(tempDir);
+                    var temp = Path.Combine(tempDir, Guid.NewGuid() + ".dll");
                     var output = temp + ".output";
+                    File.Copy(typeof(Microsoft.Build.Framework.ITask).Assembly.GetModules()[0].FullyQualifiedName,
+                        Path.Combine(tempDir, "Microsoft.Build.Framework.dll"));
                     var patched = new MemoryStream();
                     try
                     {
@@ -57,10 +61,8 @@ public class BuildTasksPatcher
                     {
                         try
                         {
-                            if (File.Exists(temp))
-                                File.Delete(temp);
-                            if (File.Exists(output))
-                                File.Delete(output);
+                            if(Directory.Exists(tempDir))
+                                Directory.Delete(tempDir, true);
                         }
                         catch
                         {

+ 2 - 2
nukebuild/DotNetConfigHelper.cs

@@ -46,7 +46,7 @@ public class DotNetConfigHelper
     public DotNetConfigHelper SetVerbosity(DotNetVerbosity verbosity)
     {
         Build = Build?.SetVerbosity(verbosity);
-        Pack = Pack?.SetVerbostiy(verbosity);
+        Pack = Pack?.SetVerbosity(verbosity);
         Test = Test?.SetVerbosity(verbosity);
         return this;
     }
@@ -54,4 +54,4 @@ public class DotNetConfigHelper
     public static implicit operator DotNetConfigHelper(DotNetBuildSettings s) => new DotNetConfigHelper(s);
     public static implicit operator DotNetConfigHelper(DotNetPackSettings s) => new DotNetConfigHelper(s);
     public static implicit operator DotNetConfigHelper(DotNetTestSettings s) => new DotNetConfigHelper(s);
-}
+}

+ 0 - 14
nukebuild/MicroComGen.cs

@@ -1,14 +0,0 @@
-using System.IO;
-using MicroCom.CodeGenerator;
-using Nuke.Common;
-
-partial class Build : NukeBuild
-{
-    Target GenerateCppHeaders => _ => _.Executes(() =>
-    {
-        var file = MicroComCodeGenerator.Parse(
-            File.ReadAllText(RootDirectory / "src" / "Avalonia.Native" / "avn.idl"));
-        File.WriteAllText(RootDirectory / "native" / "Avalonia.Native" / "inc" / "avalonia-native.h",
-            file.GenerateCppHeader());
-    });
-}

+ 15 - 0
nukebuild/Shims.cs

@@ -49,7 +49,11 @@ public partial class Build
                         {
                             if (fsEntry is FileInfo)
                             {
+#if NET6
                                 var relPath = Path.GetRelativePath(rootPath, fsEntry.FullName);
+#else
+                                var relPath = GetRelativePath(rootPath, fsEntry.FullName);
+#endif
                                 AddFile(fsEntry.FullName, relPath);
                             }
                         }
@@ -78,6 +82,17 @@ public partial class Build
         }
     }
 
+    private static string GetRelativePath(string relativeTo, string path)
+    {
+        var uri = new Uri(relativeTo);
+        var rel = Uri.UnescapeDataString(uri.MakeRelativeUri(new Uri(path)).ToString()).Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar);
+        if (rel.Contains(Path.DirectorySeparatorChar.ToString()) == false)
+        {
+            rel = $".{Path.DirectorySeparatorChar}{rel}";
+        }
+        return rel;
+    }
+
     class NumergeNukeLogger : INumergeLogger
     {
         public void Log(NumergeLogLevel level, string message)

+ 24 - 17
nukebuild/_build.csproj

@@ -1,41 +1,48 @@
 <Project Sdk="Microsoft.NET.Sdk">
-
   <PropertyGroup>
     <OutputType>Exe</OutputType>
-    <TargetFramework>netcoreapp3.1</TargetFramework>
     <AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
     <RootNamespace></RootNamespace>
     <IsPackable>False</IsPackable>
-    <NoWarn>CS0649;CS0169</NoWarn>
+    <NoWarn>CS0649;CS0169;SYSLIB0011</NoWarn>
+    <NukeTelemetryVersion>1</NukeTelemetryVersion>
+    <TargetFramework>net7.0</TargetFramework>
   </PropertyGroup>
+ 
  <Import Project="..\build\JetBrains.dotMemoryUnit.props" />
   <ItemGroup>
-    <PackageReference Include="Nuke.Common" Version="5.0.0" />
-    <PackageReference Include="xunit.runner.console" Version="2.3.1" />
+    <PackageReference Include="Nuke.Common" Version="6.2.1" />
     <PackageReference Include="vswhere" Version="2.6.7" Condition=" '$(OS)' == 'Windows_NT' " />
-    <PackageReference Include="ILRepack.NETStandard" Version="2.0.4" />
     <PackageReference Include="MicroCom.CodeGenerator" Version="0.10.4" />
     <!-- Keep in sync with Avalonia.Build.Tasks -->
-    <PackageReference Include="Mono.Cecil" Version="0.11.2" />
+    <PackageReference Include="Mono.Cecil" Version="0.11.4" />
+   <PackageReference Include="SourceLink" Version="1.1.0" GeneratePathProperty="true" />
+    <PackageReference Include="Microsoft.Build.Framework" Version="17.3.1" PrivateAssets="All" />
+    <PackageReference Include="xunit.runner.console" Version="2.4.2">
+      <PrivateAssets>all</PrivateAssets>
+      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
+    </PackageReference>
   </ItemGroup>
 
   <ItemGroup>
     <NukeMetadata Include="**\*.json" Exclude="bin\**;obj\**" />
     <NukeExternalFiles Include="**\*.*.ext" Exclude="bin\**;obj\**" />
-    <None Remove="*.csproj.DotSettings;*.ref.*.txt" />
 
     <!-- Common build related files -->
-    <None Include="..\build.ps1" />
-    <None Include="..\build.sh" />
-    <None Include="..\.nuke" />
-    <None Include="..\global.json" Condition="Exists('..\global.json')" />
-    <None Include="..\nuget.config" Condition="Exists('..\nuget.config')" />
-    <None Include="..\Jenkinsfile" Condition="Exists('..\Jenkinsfile')" />
-    <None Include="..\appveyor.yml" Condition="Exists('..\appveyor.yml')" />
-    <None Include="..\.travis.yml" Condition="Exists('..\.travis.yml')" />
-    <None Include="..\GitVersion.yml" Condition="Exists('..\GitVersion.yml')" />
     <Compile Remove="Numerge/**/*.*" />
     <Compile Include="Numerge/Numerge/**/*.cs" />
   </ItemGroup>
 
+  	<ItemGroup>
+		<EmbeddedResource Include="$(NuGetPackageRoot)sourcelink/1.1.0/tools/pdbstr.exe"></EmbeddedResource>
+	</ItemGroup>
+    
+  <ItemGroup>
+    <Compile Remove="il-repack\ILRepack\Application.cs" />
+  </ItemGroup>
+    
+  <ItemGroup>
+    <Folder Include="Numerge\Numerge.Console\" />
+  </ItemGroup>
+
 </Project>

+ 1 - 0
nukebuild/il-repack

@@ -0,0 +1 @@
+Subproject commit 892f079ea8cb0c178f0a68f53a7a7eac13acdda9

+ 25 - 10
src/Avalonia.Base/Logging/LogArea.cs

@@ -8,51 +8,66 @@ namespace Avalonia.Logging
         /// <summary>
         /// The log event comes from the property system.
         /// </summary>
-        public const string Property = "Property";
+        public const string Property = nameof(Property);
 
         /// <summary>
         /// The log event comes from the binding system.
         /// </summary>
-        public const string Binding = "Binding";
+        public const string Binding = nameof(Binding);
 
         /// <summary>
         /// The log event comes from the animations system.
         /// </summary>
-        public const string Animations = "Animations";
+        public const string Animations = nameof(Animations);
 
         /// <summary>
         /// The log event comes from the visual system.
         /// </summary>
-        public const string Visual = "Visual";
+        public const string Visual = nameof(Visual);
 
         /// <summary>
         /// The log event comes from the layout system.
         /// </summary>
-        public const string Layout = "Layout";
+        public const string Layout = nameof(Layout);
 
         /// <summary>
         /// The log event comes from the control system.
         /// </summary>
-        public const string Control = "Control";
+        public const string Control = nameof(Control);
 
         /// <summary>
-        /// The log event comes from Win32Platform.
+        /// The log event comes from Win32 Platform.
         /// </summary>
         public const string Win32Platform = nameof(Win32Platform);
         
         /// <summary>
-        /// The log event comes from X11Platform.
+        /// The log event comes from X11 Platform.
         /// </summary>
         public const string X11Platform = nameof(X11Platform);
 
         /// <summary>
-        /// The log event comes from AndroidPlatform.
+        /// The log event comes from Android Platform.
         /// </summary>
         public const string AndroidPlatform = nameof(AndroidPlatform);
         
         /// <summary>
-        /// The log event comes from IOSPlatform.
+        /// The log event comes from iOS Platform.
         /// </summary>
         public const string IOSPlatform = nameof(IOSPlatform);
+
+        /// <summary>
+        /// The log event comes from LinuxFramebuffer Platform
+        /// </summary>
+        public const string LinuxFramebufferPlatform = nameof(LinuxFramebufferPlatform);
+
+        /// <summary>
+        /// The log event comes from FreeDesktop Platform
+        /// </summary>
+        public const string FreeDesktopPlatform = nameof(FreeDesktopPlatform);
+
+        /// <summary>
+        /// The log event comes from macOS Platform
+        /// </summary>
+        public const string macOSPlatform = nameof(macOSPlatform);
     }
 }

+ 2 - 0
src/Avalonia.Base/Platform/Storage/IStorageFile.cs

@@ -18,6 +18,7 @@ public interface IStorageFile : IStorageItem
     /// <summary>
     /// Opens a stream for read access.
     /// </summary>
+    /// <exception cref="System.UnauthorizedAccessException" />
     Task<Stream> OpenReadAsync();
 
     /// <summary>
@@ -28,5 +29,6 @@ public interface IStorageFile : IStorageItem
     /// <summary>
     /// Opens stream for writing to the file.
     /// </summary>
+    /// <exception cref="System.UnauthorizedAccessException" />
     Task<Stream> OpenWriteAsync();
 }

+ 1 - 0
src/Avalonia.Base/Properties/AssemblyInfo.cs

@@ -30,4 +30,5 @@ using Avalonia.Metadata;
 [assembly: InternalsVisibleTo("Avalonia.Skia.UnitTests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c1bba1142285fe0419326fb25866ba62c47e6c2b5c1ab0c95b46413fad375471232cb81706932e1cef38781b9ebd39d5100401bacb651c6c5bbf59e571e81b3bc08d2a622004e08b1a6ece82a7e0b9857525c86d2b95fab4bc3dce148558d7f3ae61aa3a234086902aeface87d9dfdd32b9d2fe3c6dd4055b5ab4b104998bd87")]
 [assembly: InternalsVisibleTo("Avalonia.UnitTests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c1bba1142285fe0419326fb25866ba62c47e6c2b5c1ab0c95b46413fad375471232cb81706932e1cef38781b9ebd39d5100401bacb651c6c5bbf59e571e81b3bc08d2a622004e08b1a6ece82a7e0b9857525c86d2b95fab4bc3dce148558d7f3ae61aa3a234086902aeface87d9dfdd32b9d2fe3c6dd4055b5ab4b104998bd87")]
 [assembly: InternalsVisibleTo("Avalonia.Web.Blazor, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c1bba1142285fe0419326fb25866ba62c47e6c2b5c1ab0c95b46413fad375471232cb81706932e1cef38781b9ebd39d5100401bacb651c6c5bbf59e571e81b3bc08d2a622004e08b1a6ece82a7e0b9857525c86d2b95fab4bc3dce148558d7f3ae61aa3a234086902aeface87d9dfdd32b9d2fe3c6dd4055b5ab4b104998bd87")]
+[assembly: InternalsVisibleTo("Avalonia.Web, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c1bba1142285fe0419326fb25866ba62c47e6c2b5c1ab0c95b46413fad375471232cb81706932e1cef38781b9ebd39d5100401bacb651c6c5bbf59e571e81b3bc08d2a622004e08b1a6ece82a7e0b9857525c86d2b95fab4bc3dce148558d7f3ae61aa3a234086902aeface87d9dfdd32b9d2fe3c6dd4055b5ab4b104998bd87")]
 [assembly: InternalsVisibleTo("DynamicProxyGenAssembly2, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7")]

+ 0 - 2
src/Avalonia.Base/Utilities/AvaloniaResourcesIndex.cs

@@ -1,9 +1,7 @@
 using System;
 using System.Collections.Generic;
 using System.IO;
-using System.Runtime.CompilerServices;
 using System.Runtime.Serialization;
-using System.Runtime.Serialization.Json;
 using System.Xml.Linq;
 using System.Linq;
 

+ 1 - 1
src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj

@@ -105,7 +105,7 @@
       <Compile Include="..\Avalonia.Base\Metadata\NullableAttributes.cs" Link="NullableAttributes.cs" />
       <Compile Remove="../Markup/Avalonia.Markup.Xaml.Loader\xamlil.github\**\obj\**\*.cs" />
       <Compile Remove="../Markup/Avalonia.Markup.Xaml.Loader\xamlil.github\src\XamlX\IL\SreTypeSystem.cs" />
-      <PackageReference Include="Mono.Cecil" Version="0.11.2" />
+      <PackageReference Include="Mono.Cecil" Version="0.11.4" />
       <PackageReference Include="Microsoft.Build.Framework" Version="15.1.548" PrivateAssets="All" />
       <PackageReference Include="System.Numerics.Vectors" Version="4.5.0" />
     </ItemGroup>

+ 0 - 1
src/Avalonia.Build.Tasks/GenerateAvaloniaResourcesTask.cs

@@ -3,7 +3,6 @@ using System.Collections.Generic;
 using System.IO;
 using System.Linq;
 using System.Runtime.Serialization;
-using System.Runtime.Serialization.Json;
 using System.Text;
 using Avalonia.Markup.Xaml.PortableXaml;
 using Avalonia.Utilities;

+ 359 - 0
src/Avalonia.Controls.ColorPicker/ColorPalettes/MaterialColorPalette.cs

@@ -0,0 +1,359 @@
+using Avalonia.Media;
+using Avalonia.Utilities;
+
+namespace Avalonia.Controls
+{
+    /// <summary>
+    /// Implements a reduced version of the 2014 Material Design color palette.
+    /// </summary>
+    /// <remarks>
+    /// This palette is based on the one outlined here:
+    ///
+    ///   https://material.io/design/color/the-color-system.html#tools-for-picking-colors
+    ///
+    /// In order to make the palette uniform and rectangular the following
+    /// alterations were made:
+    ///
+    ///  1. The A100-A700 shades of each color are excluded.
+    ///     These shades do not exist for all colors (brown/gray).
+    ///  2. Black/White are stand-alone and are also excluded.
+    ///
+    /// </remarks>
+    public class MaterialColorPalette : IColorPalette
+    {
+        // See: https://material.io/design/color/the-color-system.html#tools-for-picking-colors
+        // This is a reduced palette for uniformity
+        private static Color[,]? _colorChart = null;
+        private static int _colorChartColorCount = 0;
+        private static int _colorChartShadeCount = 0;
+        private static object _colorChartMutex = new object();
+
+        /// <summary>
+        /// Initializes all color chart colors.
+        /// </summary>
+        /// <remarks>
+        /// This is pulled out separately to lazy load for performance.
+        /// If no material color palette is ever used, no colors will be created.
+        /// </remarks>
+        private void InitColorChart()
+        {
+            lock (_colorChartMutex)
+            {
+                _colorChart = new Color[,]
+                {
+                    // Red
+                    {
+                        Color.FromArgb(0xFF, 0xFF, 0xEB, 0xEE),
+                        Color.FromArgb(0xFF, 0xFF, 0xCD, 0xD2),
+                        Color.FromArgb(0xFF, 0xEF, 0x9A, 0x9A),
+                        Color.FromArgb(0xFF, 0xE5, 0x73, 0x73),
+                        Color.FromArgb(0xFF, 0xEF, 0x53, 0x50),
+                        Color.FromArgb(0xFF, 0xF4, 0x43, 0x36),
+                        Color.FromArgb(0xFF, 0xE5, 0x39, 0x35),
+                        Color.FromArgb(0xFF, 0xD3, 0x2F, 0x2F),
+                        Color.FromArgb(0xFF, 0xC6, 0x28, 0x28),
+                        Color.FromArgb(0xFF, 0xB7, 0x1C, 0x1C),
+                    },
+
+                    // Pink
+                    {
+                        Color.FromArgb(0xFF, 0xFC, 0xE4, 0xEC),
+                        Color.FromArgb(0xFF, 0xF8, 0xBB, 0xD0),
+                        Color.FromArgb(0xFF, 0xF4, 0x8F, 0xB1),
+                        Color.FromArgb(0xFF, 0xF0, 0x62, 0x92),
+                        Color.FromArgb(0xFF, 0xEC, 0x40, 0x7A),
+                        Color.FromArgb(0xFF, 0xE9, 0x1E, 0x63),
+                        Color.FromArgb(0xFF, 0xD8, 0x1B, 0x60),
+                        Color.FromArgb(0xFF, 0xC2, 0x18, 0x5B),
+                        Color.FromArgb(0xFF, 0xAD, 0x14, 0x57),
+                        Color.FromArgb(0xFF, 0x88, 0x0E, 0x4F),
+                    },
+
+                    // Purple
+                    {
+                        Color.FromArgb(0xFF, 0xF3, 0xE5, 0xF5),
+                        Color.FromArgb(0xFF, 0xE1, 0xBE, 0xE7),
+                        Color.FromArgb(0xFF, 0xCE, 0x93, 0xD8),
+                        Color.FromArgb(0xFF, 0xBA, 0x68, 0xC8),
+                        Color.FromArgb(0xFF, 0xAB, 0x47, 0xBC),
+                        Color.FromArgb(0xFF, 0x9C, 0x27, 0xB0),
+                        Color.FromArgb(0xFF, 0x8E, 0x24, 0xAA),
+                        Color.FromArgb(0xFF, 0x7B, 0x1F, 0xA2),
+                        Color.FromArgb(0xFF, 0x6A, 0x1B, 0x9A),
+                        Color.FromArgb(0xFF, 0x4A, 0x14, 0x8C),
+                    },
+
+                    // Deep Purple
+                    {
+                        Color.FromArgb(0xFF, 0xED, 0xE7, 0xF6),
+                        Color.FromArgb(0xFF, 0xD1, 0xC4, 0xE9),
+                        Color.FromArgb(0xFF, 0xB3, 0x9D, 0xDB),
+                        Color.FromArgb(0xFF, 0x95, 0x75, 0xCD),
+                        Color.FromArgb(0xFF, 0x7E, 0x57, 0xC2),
+                        Color.FromArgb(0xFF, 0x67, 0x3A, 0xB7),
+                        Color.FromArgb(0xFF, 0x5E, 0x35, 0xB1),
+                        Color.FromArgb(0xFF, 0x51, 0x2D, 0xA8),
+                        Color.FromArgb(0xFF, 0x45, 0x27, 0xA0),
+                        Color.FromArgb(0xFF, 0x31, 0x1B, 0x92),
+                    },
+
+                    // Indigo
+                    {
+                        Color.FromArgb(0xFF, 0xE8, 0xEA, 0xF6),
+                        Color.FromArgb(0xFF, 0xC5, 0xCA, 0xE9),
+                        Color.FromArgb(0xFF, 0x9F, 0xA8, 0xDA),
+                        Color.FromArgb(0xFF, 0x79, 0x86, 0xCB),
+                        Color.FromArgb(0xFF, 0x5C, 0x6B, 0xC0),
+                        Color.FromArgb(0xFF, 0x3F, 0x51, 0xB5),
+                        Color.FromArgb(0xFF, 0x39, 0x49, 0xAB),
+                        Color.FromArgb(0xFF, 0x30, 0x3F, 0x9F),
+                        Color.FromArgb(0xFF, 0x28, 0x35, 0x93),
+                        Color.FromArgb(0xFF, 0x1A, 0x23, 0x7E),
+                    },
+
+                    // Blue
+                    {
+                        Color.FromArgb(0xFF, 0xE3, 0xF2, 0xFD),
+                        Color.FromArgb(0xFF, 0xBB, 0xDE, 0xFB),
+                        Color.FromArgb(0xFF, 0x90, 0xCA, 0xF9),
+                        Color.FromArgb(0xFF, 0x64, 0xB5, 0xF6),
+                        Color.FromArgb(0xFF, 0x42, 0xA5, 0xF5),
+                        Color.FromArgb(0xFF, 0x21, 0x96, 0xF3),
+                        Color.FromArgb(0xFF, 0x1E, 0x88, 0xE5),
+                        Color.FromArgb(0xFF, 0x19, 0x76, 0xD2),
+                        Color.FromArgb(0xFF, 0x15, 0x65, 0xC0),
+                        Color.FromArgb(0xFF, 0x0D, 0x47, 0xA1),
+                    },
+
+                    // Light Blue
+                    {
+                        Color.FromArgb(0xFF, 0xE1, 0xF5, 0xFE),
+                        Color.FromArgb(0xFF, 0xB3, 0xE5, 0xFC),
+                        Color.FromArgb(0xFF, 0x81, 0xD4, 0xFA),
+                        Color.FromArgb(0xFF, 0x4F, 0xC3, 0xF7),
+                        Color.FromArgb(0xFF, 0x29, 0xB6, 0xF6),
+                        Color.FromArgb(0xFF, 0x03, 0xA9, 0xF4),
+                        Color.FromArgb(0xFF, 0x03, 0x9B, 0xE5),
+                        Color.FromArgb(0xFF, 0x02, 0x88, 0xD1),
+                        Color.FromArgb(0xFF, 0x02, 0x77, 0xBD),
+                        Color.FromArgb(0xFF, 0x01, 0x57, 0x9B),
+                    },
+
+                    // Cyan
+                    {
+                        Color.FromArgb(0xFF, 0xE0, 0xF7, 0xFA),
+                        Color.FromArgb(0xFF, 0xB2, 0xEB, 0xF2),
+                        Color.FromArgb(0xFF, 0x80, 0xDE, 0xEA),
+                        Color.FromArgb(0xFF, 0x4D, 0xD0, 0xE1),
+                        Color.FromArgb(0xFF, 0x26, 0xC6, 0xDA),
+                        Color.FromArgb(0xFF, 0x00, 0xBC, 0xD4),
+                        Color.FromArgb(0xFF, 0x00, 0xAC, 0xC1),
+                        Color.FromArgb(0xFF, 0x00, 0x97, 0xA7),
+                        Color.FromArgb(0xFF, 0x00, 0x83, 0x8F),
+                        Color.FromArgb(0xFF, 0x00, 0x60, 0x64),
+                    },
+
+                    // Teal
+                    {
+                        Color.FromArgb(0xFF, 0xE0, 0xF2, 0xF1),
+                        Color.FromArgb(0xFF, 0xB2, 0xDF, 0xDB),
+                        Color.FromArgb(0xFF, 0x80, 0xCB, 0xC4),
+                        Color.FromArgb(0xFF, 0x4D, 0xB6, 0xAC),
+                        Color.FromArgb(0xFF, 0x26, 0xA6, 0x9A),
+                        Color.FromArgb(0xFF, 0x00, 0x96, 0x88),
+                        Color.FromArgb(0xFF, 0x00, 0x89, 0x7B),
+                        Color.FromArgb(0xFF, 0x00, 0x79, 0x6B),
+                        Color.FromArgb(0xFF, 0x00, 0x69, 0x5C),
+                        Color.FromArgb(0xFF, 0x00, 0x4D, 0x40),
+                    },
+
+                    // Green
+                    {
+                        Color.FromArgb(0xFF, 0xE8, 0xF5, 0xE9),
+                        Color.FromArgb(0xFF, 0xC8, 0xE6, 0xC9),
+                        Color.FromArgb(0xFF, 0xA5, 0xD6, 0xA7),
+                        Color.FromArgb(0xFF, 0x81, 0xC7, 0x84),
+                        Color.FromArgb(0xFF, 0x66, 0xBB, 0x6A),
+                        Color.FromArgb(0xFF, 0x4C, 0xAF, 0x50),
+                        Color.FromArgb(0xFF, 0x43, 0xA0, 0x47),
+                        Color.FromArgb(0xFF, 0x38, 0x8E, 0x3C),
+                        Color.FromArgb(0xFF, 0x2E, 0x7D, 0x32),
+                        Color.FromArgb(0xFF, 0x1B, 0x5E, 0x20),
+                    },
+
+                    // Light Green
+                    {
+                        Color.FromArgb(0xFF, 0xF1, 0xF8, 0xE9),
+                        Color.FromArgb(0xFF, 0xDC, 0xED, 0xC8),
+                        Color.FromArgb(0xFF, 0xC5, 0xE1, 0xA5),
+                        Color.FromArgb(0xFF, 0xAE, 0xD5, 0x81),
+                        Color.FromArgb(0xFF, 0x9C, 0xCC, 0x65),
+                        Color.FromArgb(0xFF, 0x8B, 0xC3, 0x4A),
+                        Color.FromArgb(0xFF, 0x7C, 0xB3, 0x42),
+                        Color.FromArgb(0xFF, 0x68, 0x9F, 0x38),
+                        Color.FromArgb(0xFF, 0x55, 0x8B, 0x2F),
+                        Color.FromArgb(0xFF, 0x33, 0x69, 0x1E),
+                    },
+
+                    // Lime
+                    {
+                        Color.FromArgb(0xFF, 0xF9, 0xFB, 0xE7),
+                        Color.FromArgb(0xFF, 0xF0, 0xF4, 0xC3),
+                        Color.FromArgb(0xFF, 0xE6, 0xEE, 0x9C),
+                        Color.FromArgb(0xFF, 0xDC, 0xE7, 0x75),
+                        Color.FromArgb(0xFF, 0xD4, 0xE1, 0x57),
+                        Color.FromArgb(0xFF, 0xCD, 0xDC, 0x39),
+                        Color.FromArgb(0xFF, 0xC0, 0xCA, 0x33),
+                        Color.FromArgb(0xFF, 0xAF, 0xB4, 0x2B),
+                        Color.FromArgb(0xFF, 0x9E, 0x9D, 0x24),
+                        Color.FromArgb(0xFF, 0x82, 0x77, 0x17),
+                    },
+
+                    // Yellow
+                    {
+                        Color.FromArgb(0xFF, 0xFF, 0xFD, 0xE7),
+                        Color.FromArgb(0xFF, 0xFF, 0xF9, 0xC4),
+                        Color.FromArgb(0xFF, 0xFF, 0xF5, 0x9D),
+                        Color.FromArgb(0xFF, 0xFF, 0xF1, 0x76),
+                        Color.FromArgb(0xFF, 0xFF, 0xEE, 0x58),
+                        Color.FromArgb(0xFF, 0xFF, 0xEB, 0x3B),
+                        Color.FromArgb(0xFF, 0xFD, 0xD8, 0x35),
+                        Color.FromArgb(0xFF, 0xFB, 0xC0, 0x2D),
+                        Color.FromArgb(0xFF, 0xF9, 0xA8, 0x25),
+                        Color.FromArgb(0xFF, 0xF5, 0x7F, 0x17),
+                    },
+
+                    // Amber
+                    {
+                        Color.FromArgb(0xFF, 0xFF, 0xF8, 0xE1),
+                        Color.FromArgb(0xFF, 0xFF, 0xEC, 0xB3),
+                        Color.FromArgb(0xFF, 0xFF, 0xE0, 0x82),
+                        Color.FromArgb(0xFF, 0xFF, 0xD5, 0x4F),
+                        Color.FromArgb(0xFF, 0xFF, 0xCA, 0x28),
+                        Color.FromArgb(0xFF, 0xFF, 0xC1, 0x07),
+                        Color.FromArgb(0xFF, 0xFF, 0xB3, 0x00),
+                        Color.FromArgb(0xFF, 0xFF, 0xA0, 0x00),
+                        Color.FromArgb(0xFF, 0xFF, 0x8F, 0x00),
+                        Color.FromArgb(0xFF, 0xFF, 0x6F, 0x00),
+                    },
+
+                    // Orange
+                    {
+                        Color.FromArgb(0xFF, 0xFF, 0xF3, 0xE0),
+                        Color.FromArgb(0xFF, 0xFF, 0xE0, 0xB2),
+                        Color.FromArgb(0xFF, 0xFF, 0xCC, 0x80),
+                        Color.FromArgb(0xFF, 0xFF, 0xB7, 0x4D),
+                        Color.FromArgb(0xFF, 0xFF, 0xA7, 0x26),
+                        Color.FromArgb(0xFF, 0xFF, 0x98, 0x00),
+                        Color.FromArgb(0xFF, 0xFB, 0x8C, 0x00),
+                        Color.FromArgb(0xFF, 0xF5, 0x7C, 0x00),
+                        Color.FromArgb(0xFF, 0xEF, 0x6C, 0x00),
+                        Color.FromArgb(0xFF, 0xE6, 0x51, 0x00),
+                    },
+
+                    // Deep Orange
+                    {
+                        Color.FromArgb(0xFF, 0xFB, 0xE9, 0xE7),
+                        Color.FromArgb(0xFF, 0xFF, 0xCC, 0xBC),
+                        Color.FromArgb(0xFF, 0xFF, 0xAB, 0x91),
+                        Color.FromArgb(0xFF, 0xFF, 0x8A, 0x65),
+                        Color.FromArgb(0xFF, 0xFF, 0x70, 0x43),
+                        Color.FromArgb(0xFF, 0xFF, 0x57, 0x22),
+                        Color.FromArgb(0xFF, 0xF4, 0x51, 0x1E),
+                        Color.FromArgb(0xFF, 0xE6, 0x4A, 0x19),
+                        Color.FromArgb(0xFF, 0xD8, 0x43, 0x15),
+                        Color.FromArgb(0xFF, 0xBF, 0x36, 0x0C),
+                    },
+
+                    // Brown
+                    {
+                        Color.FromArgb(0xFF, 0xEF, 0xEB, 0xE9),
+                        Color.FromArgb(0xFF, 0xD7, 0xCC, 0xC8),
+                        Color.FromArgb(0xFF, 0xBC, 0xAA, 0xA4),
+                        Color.FromArgb(0xFF, 0xA1, 0x88, 0x7F),
+                        Color.FromArgb(0xFF, 0x8D, 0x6E, 0x63),
+                        Color.FromArgb(0xFF, 0x79, 0x55, 0x48),
+                        Color.FromArgb(0xFF, 0x6D, 0x4C, 0x41),
+                        Color.FromArgb(0xFF, 0x5D, 0x40, 0x37),
+                        Color.FromArgb(0xFF, 0x4E, 0x34, 0x2E),
+                        Color.FromArgb(0xFF, 0x3E, 0x27, 0x23),
+                    },
+
+                    // Gray
+                    {
+                        Color.FromArgb(0xFF, 0xFA, 0xFA, 0xFA),
+                        Color.FromArgb(0xFF, 0xF5, 0xF5, 0xF5),
+                        Color.FromArgb(0xFF, 0xEE, 0xEE, 0xEE),
+                        Color.FromArgb(0xFF, 0xE0, 0xE0, 0xE0),
+                        Color.FromArgb(0xFF, 0xBD, 0xBD, 0xBD),
+                        Color.FromArgb(0xFF, 0x9E, 0x9E, 0x9E),
+                        Color.FromArgb(0xFF, 0x75, 0x75, 0x75),
+                        Color.FromArgb(0xFF, 0x61, 0x61, 0x61),
+                        Color.FromArgb(0xFF, 0x42, 0x42, 0x42),
+                        Color.FromArgb(0xFF, 0x21, 0x21, 0x21),
+                    },
+
+                    // Blue Gray
+                    {
+                        Color.FromArgb(0xFF, 0xEC, 0xEF, 0xF1),
+                        Color.FromArgb(0xFF, 0xCF, 0xD8, 0xDC),
+                        Color.FromArgb(0xFF, 0xB0, 0xBE, 0xC5),
+                        Color.FromArgb(0xFF, 0x90, 0xA4, 0xAE),
+                        Color.FromArgb(0xFF, 0x78, 0x90, 0x9C),
+                        Color.FromArgb(0xFF, 0x60, 0x7D, 0x8B),
+                        Color.FromArgb(0xFF, 0x54, 0x6E, 0x7A),
+                        Color.FromArgb(0xFF, 0x45, 0x5A, 0x64),
+                        Color.FromArgb(0xFF, 0x37, 0x47, 0x4F),
+                        Color.FromArgb(0xFF, 0x26, 0x32, 0x38),
+                    },
+                };
+
+                _colorChartColorCount = _colorChart.GetLength(0);
+                _colorChartShadeCount = _colorChart.GetLength(1);
+            }
+
+            return;
+        }
+
+        /// <inheritdoc/>
+        public int ColorCount
+        {
+            get
+            {
+                if (_colorChart == null)
+                {
+                    InitColorChart();
+                }
+
+                return _colorChartColorCount;
+            }
+        }
+
+        /// <inheritdoc/>
+        public int ShadeCount
+        {
+            get
+            {
+                if (_colorChart == null)
+                {
+                    InitColorChart();
+                }
+
+                return _colorChartShadeCount;
+            }
+        }
+
+        /// <inheritdoc/>
+        public Color GetColor(int colorIndex, int shadeIndex)
+        {
+            if (_colorChart == null)
+            {
+                InitColorChart();
+            }
+
+            return _colorChart![
+                MathUtilities.Clamp(colorIndex, 0, _colorChartColorCount - 1),
+                MathUtilities.Clamp(shadeIndex, 0, _colorChartShadeCount - 1)];
+        }
+    }
+}

+ 27 - 1
src/Avalonia.Controls/ComboBox.cs

@@ -21,8 +21,11 @@ namespace Avalonia.Controls
     /// A drop-down list control.
     /// </summary>
     [TemplatePart("PART_Popup", typeof(Popup))]
+    [PseudoClasses(pcDropdownOpen, pcPressed)]
     public class ComboBox : SelectingItemsControl
     {
+        public const string pcDropdownOpen = ":dropdownopen";
+        public const string pcPressed = ":pressed";
         /// <summary>
         /// The default value for the <see cref="ItemsControl.ItemsPanel"/> property.
         /// </summary>
@@ -95,6 +98,7 @@ namespace Avalonia.Controls
             SelectedItemProperty.Changed.AddClassHandler<ComboBox>((x, e) => x.SelectedItemChanged(e));
             KeyDownEvent.AddClassHandler<ComboBox>((x, e) => x.OnKeyDown(e), Interactivity.RoutingStrategies.Tunnel);
             IsTextSearchEnabledProperty.OverrideDefaultValue<ComboBox>(true);
+            IsDropDownOpenProperty.Changed.AddClassHandler<ComboBox>((x, e) => x.DropdownChanged(e));
         }
 
         /// <summary>
@@ -267,6 +271,20 @@ namespace Avalonia.Controls
             }
         }
 
+        /// <inheritdoc/>
+        protected override void OnPointerPressed(PointerPressedEventArgs e)
+        {
+            base.OnPointerPressed(e);
+            if(!e.Handled && e.Source is IVisual source)
+            {
+                if (_popup?.IsInsidePopup(source) == true)
+                {
+                    return;
+                }
+            }
+            PseudoClasses.Set(pcPressed, true);
+        }
+
         /// <inheritdoc/>
         protected override void OnPointerReleased(PointerReleasedEventArgs e)
         {
@@ -286,10 +304,12 @@ namespace Avalonia.Controls
                     e.Handled = true;
                 }
             }
-
+            PseudoClasses.Set(pcPressed, false);
             base.OnPointerReleased(e);
+            
         }
 
+
         /// <inheritdoc/>
         protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
         {
@@ -470,5 +490,11 @@ namespace Avalonia.Controls
                 MoveSelection(NavigationDirection.Previous, WrapSelection);
             }
         }
+
+        private void DropdownChanged(AvaloniaPropertyChangedEventArgs e)
+        {
+            bool newValue = e.GetNewValue<bool>();
+            PseudoClasses.Set(pcDropdownOpen, newValue);
+        }
     }
 }

+ 1 - 18
src/Avalonia.Themes.Fluent/Controls/ComboBox.xaml

@@ -57,17 +57,8 @@
     <Setter Property="Template">
       <ControlTemplate>
         <DataValidationErrors>
-          <Grid RowDefinitions="Auto, *, Auto" ColumnDefinitions="*,32">
-            <ContentPresenter x:Name="HeaderContentPresenter"
-                              Grid.Row="0"
-                              Grid.Column="0"
-                              Grid.ColumnSpan="2"
-                              IsVisible="False"
-                              FontWeight="{DynamicResource ComboBoxHeaderThemeFontWeight}"
-                              Margin="{DynamicResource ComboBoxTopHeaderMargin}"
-                              VerticalAlignment="Top" />
+          <Grid ColumnDefinitions="*,32">
             <Border x:Name="Background"
-                    Grid.Row="1"
                     Grid.Column="0"
                     Grid.ColumnSpan="2"
                     Background="{TemplateBinding Background}"
@@ -76,7 +67,6 @@
                     CornerRadius="{TemplateBinding CornerRadius}"
                     MinWidth="{DynamicResource ComboBoxThemeMinWidth}" />
             <Border x:Name="HighlightBackground"
-                    Grid.Row="1"
                     Grid.Column="0"
                     Grid.ColumnSpan="2"
                     Background="{DynamicResource ComboBoxBackgroundUnfocused}"
@@ -85,7 +75,6 @@
                     CornerRadius="{TemplateBinding CornerRadius}"
                     IsVisible="False"/>
             <TextBlock x:Name="PlaceholderTextBlock"
-                       Grid.Row="1"
                        Grid.Column="0"
                        HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
                        VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
@@ -96,14 +85,12 @@
             <ContentControl x:Name="ContentPresenter"
                             Content="{TemplateBinding SelectionBoxItem}"
                             ContentTemplate="{TemplateBinding ItemTemplate}"
-                            Grid.Row="1"
                             Grid.Column="0"
                             Margin="{TemplateBinding Padding}"
                             HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
                             VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}" />
 
             <Border x:Name="DropDownOverlay"
-                    Grid.Row="1"
                     Grid.Column="1"
                     Background="Transparent"
                     Margin="0,1,1,1"
@@ -112,7 +99,6 @@
                     HorizontalAlignment="Right" />
 
             <PathIcon x:Name="DropDownGlyph"
-                      Grid.Row="1"
                       Grid.Column="1"
                       UseLayoutRounding="False"
                       IsHitTestVisible="False"
@@ -208,9 +194,6 @@
         <Setter Property="Background" Value="{DynamicResource ComboBoxBackgroundDisabled}" />
         <Setter Property="BorderBrush" Value="{DynamicResource ComboBoxBorderBrushDisabled}" />
       </Style>
-      <Style Selector="^ /template/ ContentPresenter#HeaderContentPresenter">
-        <Setter Property="Foreground" Value="{DynamicResource ComboBoxForegroundDisabled}" />
-      </Style>
       <Style Selector="^ /template/ ContentControl#ContentPresenter">
         <Setter Property="Foreground" Value="{DynamicResource ComboBoxForegroundDisabled}" />
       </Style>

+ 3 - 3
src/Avalonia.Themes.Fluent/Controls/DatePicker.xaml

@@ -153,11 +153,11 @@
     </Style>
 
     <!-- Changes foreground for watermark text when SelectedDate is null-->
-    <Style Selector="^:hasnodate /template/ Button#FlyoutButton TextBlock">
+    <Style Selector="^:hasnodate /template/ Button#PART_FlyoutButton TextBlock">
       <Setter Property="Foreground" Value="{DynamicResource TextControlPlaceholderForeground}"/>
     </Style>
-    
-    <Style Selector="^:error /template/ Button#FlyoutButton">
+
+    <Style Selector="^:error /template/ Button#PART_FlyoutButton">
       <Setter Property="BorderBrush" Value="{DynamicResource SystemControlErrorTextForegroundBrush}"/>
     </Style>
   </ControlTheme>

+ 2 - 2
src/Avalonia.Themes.Fluent/Controls/TimePicker.xaml

@@ -181,11 +181,11 @@
       <Setter Property="Fill" Value="{DynamicResource TimePickerSpacerFillDisabled}"/>
     </Style>
 
-    <Style Selector="^:hasnotime /template/ Button#FlyoutButton TextBlock">
+    <Style Selector="^:hasnotime /template/ Button#PART_FlyoutButton TextBlock">
       <Setter Property="Foreground" Value="{DynamicResource TextControlPlaceholderForeground}"/>
     </Style>
 
-    <Style Selector="^:error /template/ Button#FlyoutButton">
+    <Style Selector="^:error /template/ Button#PART_FlyoutButton">
       <Setter Property="BorderBrush" Value="{DynamicResource SystemControlErrorTextForegroundBrush}"/>
     </Style>
   </ControlTheme>

+ 1 - 1
src/Avalonia.Themes.Simple/Controls/DatePicker.xaml

@@ -170,7 +170,7 @@
     </Style>
 
     <!--  Changes foreground for watermark text when SelectedDate is null  -->
-    <Style Selector="^:hasnodate /template/ Button#FlyoutButton TextBlock">
+    <Style Selector="^:hasnodate /template/ Button#PART_FlyoutButton TextBlock">
       <Setter Property="Foreground" Value="{DynamicResource ThemeForegroundLowBrush}" />
     </Style>
   </ControlTheme>

+ 1 - 1
src/Avalonia.Themes.Simple/Controls/TimePicker.xaml

@@ -189,7 +189,7 @@
       <Setter Property="Opacity" Value="{DynamicResource ThemeDisabledOpacity}" />
     </Style>
 
-    <Style Selector="^:hasnotime /template/ Button#FlyoutButton TextBlock">
+    <Style Selector="^:hasnotime /template/ Button#PART_FlyoutButton TextBlock">
       <Setter Property="Foreground" Value="{DynamicResource ThemeForegroundLowBrush}" />
     </Style>
   </ControlTheme>

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

@@ -287,7 +287,6 @@ namespace Avalonia.Web.Blazor
                     // create the SkiaSharp context
                     if (_context == null)
                     {
-                        Console.WriteLine("create glcontext");
                         _glInterface = GRGlInterface.Create();
                         _context = GRContext.CreateGl(_glInterface);
 

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

@@ -0,0 +1,41 @@
+<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>

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

@@ -0,0 +1,44 @@
+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);
+}

+ 5 - 0
src/Web/Avalonia.Web.Sample/Logo.svg

@@ -0,0 +1,5 @@
+<svg width="35" height="35" viewBox="0 0 35 35" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M30.4661 34.928C30.5364 34.928 30.6052 34.928 30.6754 34.928C32.8596 34.928 34.654 33.2918 34.9053 31.1752L34.9356 16.9955C34.6872 7.56697 26.9662 0 17.4777 0C7.83263 0 0.0137329 7.8189 0.0137329 17.464C0.0137329 27.0059 7.66618 34.7631 17.1687 34.928H30.4661Z" fill="white"/>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M17.5239 5.948C12.0268 5.948 7.42967 9.80117 6.286 14.954C7.38092 15.2609 8.18385 16.2664 8.18385 17.4593C8.18385 18.6523 7.38092 19.6577 6.286 19.9647C7.42966 25.1175 12.0268 28.9706 17.5239 28.9706C19.525 28.9706 21.4068 28.4601 23.0462 27.562V28.8927H29.0352V17.9365C29.0407 17.7908 29.0352 17.6063 29.0352 17.4593C29.0352 11.1018 23.8814 5.948 17.5239 5.948ZM12.0098 17.4593C12.0098 14.414 14.4786 11.9452 17.5239 11.9452C20.5693 11.9452 23.038 14.414 23.038 17.4593C23.038 20.5047 20.5693 22.9734 17.5239 22.9734C14.4786 22.9734 12.0098 20.5047 12.0098 17.4593Z" fill="#8B44AC"/>
+<path d="M7.36841 17.4517C7.36841 18.4691 6.54368 19.2938 5.52631 19.2938C4.50894 19.2938 3.6842 18.4691 3.6842 17.4517C3.6842 16.4343 4.50894 15.6096 5.52631 15.6096C6.54368 15.6096 7.36841 16.4343 7.36841 17.4517Z" fill="#8B44AC"/>
+</svg>

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

@@ -0,0 +1,19 @@
+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>();
+}

+ 38 - 0
src/Web/Avalonia.Web.Sample/app.css

@@ -0,0 +1,38 @@
+#out {
+    height: 100vh;
+    width: 100vw
+}
+
+#avalonia-splash {
+    position: absolute;
+    height: 100%;
+    width: 100%;
+    color: whitesmoke;
+    background: #171C2C;
+    font-family: 'Nunito', sans-serif;
+}
+
+#avalonia-splash a{
+    color: whitesmoke;
+    text-decoration: none;
+}
+
+.center {
+    display: flex;
+    justify-content: center;
+    height: 250px;
+}
+
+.splash-close {
+    animation: fadeOut 1s forwards;
+}
+
+@keyframes fadeOut {
+    from {
+        opacity: 1;
+    }
+
+    to {
+        opacity: 0;
+    }
+}

+ 11 - 0
src/Web/Avalonia.Web.Sample/embed.js

@@ -0,0 +1,11 @@
+export function addAppButton(parent) {
+    var button = globalThis.document.createElement('button');
+    button.innerText = 'Hello world';
+    var clickCount = 0;
+    button.onclick = () => {
+        clickCount++;
+        button.innerText = 'Click count ' + clickCount;
+    };
+    parent.appendChild(button);
+    return button;
+}

BIN
src/Web/Avalonia.Web.Sample/favicon.ico


+ 31 - 0
src/Web/Avalonia.Web.Sample/index.html

@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<!--  Licensed to the .NET Foundation under one or more agreements. -->
+<!-- The .NET Foundation licenses this file to you under the MIT license. -->
+<html>
+
+<head>
+    <title>Avalonia.Web.Sample</title>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <link rel="modulepreload" href="./main.js" />
+    <link rel="modulepreload" href="./dotnet.js" />
+    <link rel="modulepreload" href="./avalonia.js" />
+    <link rel="stylesheet" href="./app.css" />
+</head>
+
+<body style="margin: 0px">
+    <div id="out">
+        <div id="avalonia-splash">
+            <div class="center">
+                <h2>Powered by</h2>
+                <a class="navbar-brand" href="https://www.avaloniaui.net/" target="_blank">
+                    <img src="Logo.svg" alt="Avalonia Logo" width="30" height="24" />
+                    Avalonia
+                </a>
+            </div>
+        </div>
+    </div>
+    <script type='module' src="./main.js"></script>
+</body>
+
+</html>

+ 19 - 0
src/Web/Avalonia.Web.Sample/main.js

@@ -0,0 +1,19 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+import { dotnet } from './dotnet.js'
+import { createAvaloniaRuntime } from './avalonia.js';
+
+const is_browser = typeof window != "undefined";
+if (!is_browser) throw new Error(`Expected to be running in a browser`);
+
+const dotnetRuntime = await dotnet
+    .withDiagnosticTracing(false)
+    .withApplicationArgumentsFromQuery()
+    .create();
+
+await createAvaloniaRuntime(dotnetRuntime);
+
+const config = dotnetRuntime.getConfig();
+
+await dotnetRuntime.runMainAndExit(config.mainAssemblyName, ["dotnet", "is", "great!"]);

+ 11 - 0
src/Web/Avalonia.Web.Sample/runtimeconfig.template.json

@@ -0,0 +1,11 @@
+{
+    "wasmHostProperties": {
+        "perHostConfig": [
+            {
+                "name": "browser",
+                "html-path": "index.html",
+                "Host": "browser"
+            }
+        ]
+    }
+}

+ 55 - 0
src/Web/Avalonia.Web/Avalonia.Web.csproj

@@ -0,0 +1,55 @@
+<Project Sdk="Microsoft.NET.Sdk">
+  <PropertyGroup>
+    <TargetFramework>net7.0</TargetFramework>
+    <LangVersion>preview</LangVersion>
+    <Nullable>enable</Nullable>
+    <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <SupportedPlatform Include="browser" />
+  </ItemGroup>
+
+  <Import Project="..\..\..\build\BuildTargets.targets" />
+  <Import Project="..\..\..\build\SkiaSharp.props" />
+  <Import Project="..\..\..\build\HarfBuzzSharp.props" />
+  <Import Project="..\..\..\build\NullableEnable.props" />
+
+  <ItemGroup>
+    <ProjectReference Include="..\..\..\packages\Avalonia\Avalonia.csproj" />
+    <ProjectReference Include="..\..\Skia\Avalonia.Skia\Avalonia.Skia.csproj" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <Content Include="*.props">
+      <Pack>true</Pack>
+      <PackagePath>build\</PackagePath>
+    </Content>
+    <Content Include="*.targets">
+      <Pack>true</Pack>
+      <PackagePath>build\;buildTransitive\</PackagePath>
+    </Content>
+    <Content Include="interop.js">
+      <Pack>true</Pack>
+      <PackagePath>build/interop.js;buildTransitive/interop.js</PackagePath>
+    </Content>
+    <Content Include="wwwroot/**/*.*">
+      <Pack>true</Pack>
+      <PackagePath>build\wwwroot;buildTransitive\wwwroot</PackagePath>
+    </Content>
+  </ItemGroup>
+
+  <ItemGroup>
+    <Folder Include="wwwroot\" />
+  </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>
+
+</Project>

+ 5 - 0
src/Web/Avalonia.Web/Avalonia.Web.props

@@ -0,0 +1,5 @@
+<Project>  
+  <PropertyGroup>
+    <EmccExtraLDFlags>$(EmccExtraLDFlags) --js-library="$(MSBuildThisFileDirectory)\interop.js"</EmccExtraLDFlags>
+  </PropertyGroup>
+</Project>

+ 7 - 0
src/Web/Avalonia.Web/Avalonia.Web.targets

@@ -0,0 +1,7 @@
+<Project>
+  <ItemGroup>
+    <WasmExtraFilesToDeploy Include="$(MSBuildThisFileDirectory)/wwwroot/**/*.*" />
+    <NativeFileReference Include="$(HarfBuzzSharpStaticLibraryPath)\3.1.7\libHarfBuzzSharp.a" />
+    <NativeFileReference Include="$(SkiaSharpStaticLibraryPath)\3.1.7\libSkiaSharp.a" />
+  </ItemGroup>
+</Project>

+ 451 - 0
src/Web/Avalonia.Web/AvaloniaView.cs

@@ -0,0 +1,451 @@
+using System;
+using System.Runtime.InteropServices.JavaScript;
+using Avalonia.Controls;
+using Avalonia.Controls.Embedding;
+using Avalonia.Controls.Platform;
+using Avalonia.Input;
+using Avalonia.Input.Raw;
+using Avalonia.Input.TextInput;
+using Avalonia.Rendering.Composition;
+using Avalonia.Threading;
+using Avalonia.Web.Interop;
+using Avalonia.Web.Skia;
+
+using SkiaSharp;
+
+namespace Avalonia.Web
+{
+    [System.Runtime.Versioning.SupportedOSPlatform("browser")] // gets rid of callsite warnings
+    public partial class AvaloniaView : ITextInputMethodImpl
+    {
+        private readonly BrowserTopLevelImpl _topLevelImpl;
+        private EmbeddableControlRoot _topLevel;
+
+        private readonly JSObject _containerElement;
+        private readonly JSObject _canvas;
+        private readonly JSObject _nativeControlsContainer;
+        private readonly JSObject _inputElement;
+        private readonly JSObject? _splash;
+
+        private GLInfo? _jsGlInfo = null;
+        private double _dpi = 1;
+        private Size _canvasSize = new(100.0, 100.0);
+
+        private GRContext? _context;
+        private GRGlInterface? _glInterface;
+        private const SKColorType ColorType = SKColorType.Rgba8888;
+
+        private bool _useGL;        
+        private ITextInputMethodClient? _client;
+        private static int _canvasCount;
+
+        public AvaloniaView(string divId)
+        {
+            var host = DomHelper.GetElementById(divId);
+            if (host == null)
+            {
+                throw new Exception($"Element with id {divId} was not found in the html document.");
+            }
+
+            var hostContent = DomHelper.CreateAvaloniaHost(host);
+            if (hostContent == null)
+            {
+                throw new InvalidOperationException("Avalonia WASM host wasn't initialized.");
+            }
+
+            _containerElement = hostContent.GetPropertyAsJSObject("host")
+                ?? throw new InvalidOperationException("Host cannot be null");
+            _canvas = hostContent.GetPropertyAsJSObject("canvas")
+                ?? throw new InvalidOperationException("Canvas cannot be null");
+            _nativeControlsContainer = hostContent.GetPropertyAsJSObject("nativeHost")
+                ?? throw new InvalidOperationException("NativeHost cannot be null");
+            _inputElement = hostContent.GetPropertyAsJSObject("inputElement")
+                ?? throw new InvalidOperationException("InputElement cannot be null");
+
+            _splash = DomHelper.GetElementById("avalonia-splash");
+
+            _canvas.SetProperty("id", $"avaloniaCanvas{_canvasCount++}");
+
+            _topLevelImpl = new BrowserTopLevelImpl(this);
+
+            _topLevel = new WebEmbeddableControlRoot(_topLevelImpl, () =>
+            {
+                Dispatcher.UIThread.Post(() =>
+                {
+                    if (_splash != null)
+                    {
+                        DomHelper.AddCssClass(_splash, "splash-close");
+                    }
+                });
+            });
+
+            _topLevelImpl.SetCssCursor = (cursor) =>
+            {
+                InputHelper.SetCursor(_containerElement, cursor); // macOS
+                InputHelper.SetCursor(_canvas, cursor); // windows
+            };
+
+            _topLevel.Prepare();
+
+            _topLevel.Renderer.Start();
+
+            InputHelper.SubscribeKeyEvents(
+                _containerElement,
+                OnKeyDown,
+                OnKeyUp);
+
+            InputHelper.SubscribeTextEvents(
+                _inputElement,
+                OnTextInput,
+                OnCompositionStart,
+                OnCompositionUpdate,
+                OnCompositionEnd);
+
+            InputHelper.SubscribePointerEvents(_containerElement, OnPointerMove, OnPointerDown, OnPointerUp, OnWheel);
+
+            var skiaOptions = AvaloniaLocator.Current.GetService<SkiaOptions>();
+
+            _dpi = DomHelper.ObserveDpi(OnDpiChanged);
+
+            _useGL = skiaOptions?.CustomGpuFactory != null;
+
+            if (_useGL)
+            {
+                _jsGlInfo = CanvasHelper.InitialiseGL(_canvas, OnRenderFrame);
+                // 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);
+                }
+
+                _topLevelImpl.Surfaces = new[] { new BrowserSkiaSurface(_context, _jsGlInfo, ColorType, new PixelSize((int)_canvasSize.Width, (int)_canvasSize.Height), _dpi, GRSurfaceOrigin.BottomLeft) };
+            }
+            else
+            {
+                //var rasterInitialized = _interop.InitRaster();
+                //Console.WriteLine("raster initialized: {0}", rasterInitialized);
+
+                //_topLevelImpl.SetSurface(ColorType,
+                // new PixelSize((int)_canvasSize.Width, (int)_canvasSize.Height), _dpi, _interop.PutImageData);
+            }
+
+            CanvasHelper.SetCanvasSize(_canvas, (int)(_canvasSize.Width * _dpi), (int)(_canvasSize.Height * _dpi));
+
+            _topLevelImpl.SetClientSize(_canvasSize, _dpi);
+
+            DomHelper.ObserveSize(host, divId, OnSizeChanged);
+
+            CanvasHelper.RequestAnimationFrame(_canvas, true);
+        }
+
+        private static RawPointerPoint ExtractRawPointerFromJSArgs(JSObject args)
+        {
+            var point = new RawPointerPoint
+            {
+                Position = new Point(args.GetPropertyAsDouble("offsetX"), args.GetPropertyAsDouble("offsetY")),
+                Pressure = (float)args.GetPropertyAsDouble("pressure"),
+                XTilt = (float)args.GetPropertyAsDouble("tiltX"),
+                YTilt = (float)args.GetPropertyAsDouble("tiltY"),
+                Twist = (float)args.GetPropertyAsDouble("twist")
+            };
+
+            return point;
+        }
+
+        private bool OnPointerMove(JSObject args)
+        {
+            var type = args.GetPropertyAsString("pointertype");
+
+            var point = ExtractRawPointerFromJSArgs(args);
+
+            return _topLevelImpl.RawPointerEvent(RawPointerEventType.Move, type!, point, GetModifiers(args), args.GetPropertyAsInt32("pointerId"));
+        }
+
+        private bool OnPointerDown(JSObject args)
+        {
+            var pointerType = args.GetPropertyAsString("pointerType");
+
+            var type = pointerType switch
+            {
+                "touch" => RawPointerEventType.TouchBegin,
+                _ => args.GetPropertyAsInt32("button") switch
+                {
+                    0 => RawPointerEventType.LeftButtonDown,
+                    1 => RawPointerEventType.MiddleButtonDown,
+                    2 => RawPointerEventType.RightButtonDown,
+                    3 => RawPointerEventType.XButton1Down,
+                    4 => RawPointerEventType.XButton2Down,
+                    // 5 => Pen eraser button,
+                    _ => RawPointerEventType.Move
+                }
+            };
+
+            var point = ExtractRawPointerFromJSArgs(args);
+
+            return _topLevelImpl.RawPointerEvent(type, pointerType!, point, GetModifiers(args), args.GetPropertyAsInt32("pointerId"));
+        }
+
+        private bool OnPointerUp(JSObject args)
+        {
+            var pointerType = args.GetPropertyAsString("pointerType") ?? "mouse";
+
+            var type = pointerType switch
+            {
+                "touch" => RawPointerEventType.TouchEnd,
+                _ => args.GetPropertyAsInt32("button") switch
+                {
+                    0 => RawPointerEventType.LeftButtonUp,
+                    1 => RawPointerEventType.MiddleButtonUp,
+                    2 => RawPointerEventType.RightButtonUp,
+                    3 => RawPointerEventType.XButton1Up,
+                    4 => RawPointerEventType.XButton2Up,
+                    // 5 => Pen eraser button,
+                    _ => RawPointerEventType.Move
+                }
+            };
+
+            var point = ExtractRawPointerFromJSArgs(args);
+
+            return _topLevelImpl.RawPointerEvent(type, pointerType, point, GetModifiers(args), args.GetPropertyAsInt32("pointerId"));
+        }
+
+        private bool OnWheel(JSObject args)
+        {
+            return _topLevelImpl.RawMouseWheelEvent(new Point(args.GetPropertyAsDouble("clientX"), args.GetPropertyAsDouble("clientY")),
+                new Vector(-(args.GetPropertyAsDouble("deltaX") / 50), -(args.GetPropertyAsDouble("deltaY") / 50)), GetModifiers(args));
+        }
+
+        private static RawInputModifiers GetModifiers(JSObject e)
+        {
+            var modifiers = RawInputModifiers.None;
+
+            if (e.GetPropertyAsBoolean("ctrlKey"))
+                modifiers |= RawInputModifiers.Control;
+            if (e.GetPropertyAsBoolean("altKey"))
+                modifiers |= RawInputModifiers.Alt;
+            if (e.GetPropertyAsBoolean("shiftKey"))
+                modifiers |= RawInputModifiers.Shift;
+            if (e.GetPropertyAsBoolean("metaKey"))
+                modifiers |= RawInputModifiers.Meta;
+
+            var buttons = e.GetPropertyAsInt32("buttons");
+            if ((buttons & 1L) == 1)
+                modifiers |= RawInputModifiers.LeftMouseButton;
+
+            if ((buttons & 2L) == 2)
+                modifiers |= e.GetPropertyAsString("type") == "pen" ? RawInputModifiers.PenBarrelButton : RawInputModifiers.RightMouseButton;
+
+            if ((buttons & 4L) == 4)
+                modifiers |= RawInputModifiers.MiddleMouseButton;
+
+            if ((buttons & 8L) == 8)
+                modifiers |= RawInputModifiers.XButton1MouseButton;
+
+            if ((buttons & 16L) == 16)
+                modifiers |= RawInputModifiers.XButton2MouseButton;
+
+            if ((buttons & 32L) == 32)
+                modifiers |= RawInputModifiers.PenEraser;
+
+            return modifiers;
+        }
+
+        private bool OnKeyDown (string code, string key, int modifier)
+        {
+            return _topLevelImpl.RawKeyboardEvent(RawKeyEventType.KeyDown, code, key, (RawInputModifiers)modifier);
+        }
+
+        private bool OnKeyUp(string code, string key, int modifier)
+        {
+            return _topLevelImpl.RawKeyboardEvent(RawKeyEventType.KeyUp, code, key, (RawInputModifiers)modifier);
+        }
+
+        private bool OnTextInput (string type, string? data)
+        {
+            if(data == null || IsComposing)
+            {
+                return false;
+            }
+
+            return _topLevelImpl.RawTextEvent(data);
+        }
+
+        private bool OnCompositionStart (JSObject args)
+        {
+            if (_client == null)
+                return false;
+
+            _client.SetPreeditText(null);
+            IsComposing = true;
+
+            return false;
+        }
+
+        private bool OnCompositionUpdate(JSObject args)
+        {
+            if (_client == null)
+                return false;
+
+            _client.SetPreeditText(args.GetPropertyAsString("data"));
+
+            return false;
+        }
+
+        private bool OnCompositionEnd(JSObject args)
+        {
+            if (_client == null)
+                return false;
+
+            IsComposing = false;
+            _client.SetPreeditText(null);
+            _topLevelImpl.RawTextEvent(args.GetPropertyAsString("data")!);
+
+            return false;
+        }
+
+        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 Control? Content
+        {
+            get => (Control)_topLevel.Content!;
+            set => _topLevel.Content = value;
+        }
+
+        public bool IsComposing { get; private set; }
+
+        internal INativeControlHostImpl GetNativeControlHostImpl()
+        {
+            return new BrowserNativeControlHost(_nativeControlsContainer);
+        }
+
+        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 oldDpi, double newDpi)
+        {
+            if (Math.Abs(_dpi - newDpi) > 0.0001)
+            {
+                _dpi = newDpi;
+
+                CanvasHelper.SetCanvasSize(_canvas, (int)(_canvasSize.Width * _dpi), (int)(_canvasSize.Height * _dpi));
+
+                _topLevelImpl.SetClientSize(_canvasSize, _dpi);
+
+                ForceBlit();
+            }
+        }
+
+        private void OnSizeChanged(int height, int width)
+        {
+            var newSize = new Size(height, width);
+
+            if (_canvasSize != newSize)
+            {
+                _canvasSize = newSize;
+
+                CanvasHelper.SetCanvasSize(_canvas, (int)(_canvasSize.Width * _dpi), (int)(_canvasSize.Height * _dpi));
+
+                _topLevelImpl.SetClientSize(_canvasSize, _dpi);
+
+                ForceBlit();
+            }
+        }
+
+        private void HideIme()
+        {
+            InputHelper.HideElement(_inputElement);
+            InputHelper.FocusElement(_containerElement);
+        }
+
+        public void SetClient(ITextInputMethodClient? client)
+        {
+            Console.WriteLine("Set Client");
+            if (_client != null)
+            {
+                _client.SurroundingTextChanged -= SurroundingTextChanged;
+            }
+
+            if (client != null)
+            {
+                client.SurroundingTextChanged += SurroundingTextChanged;
+            }
+
+            InputHelper.ClearInputElement(_inputElement);
+
+            _client = client;
+
+            if (_client != null)
+            {
+                InputHelper.ShowElement(_inputElement);
+                InputHelper.FocusElement(_inputElement);
+
+                var surroundingText = _client.SurroundingText;
+
+                InputHelper.SetSurroundingText(_inputElement, surroundingText.Text, surroundingText.AnchorOffset, surroundingText.CursorOffset);
+
+                Console.WriteLine("Shown, focused and surrounded.");
+            }
+            else
+            {
+                HideIme();
+            }
+        }
+
+        private void SurroundingTextChanged(object? sender, EventArgs e)
+        {
+            if (_client != null)
+            {
+                var surroundingText = _client.SurroundingText;
+
+                InputHelper.SetSurroundingText(_inputElement, surroundingText.Text, surroundingText.AnchorOffset, surroundingText.CursorOffset);
+            }
+        }
+
+        public void SetCursorRect(Rect rect)
+        {
+            InputHelper.FocusElement(_inputElement);
+            InputHelper.SetBounds(_inputElement, (int)rect.X, (int)rect.Y, (int)rect.Width, (int)rect.Height, _client?.SurroundingText.CursorOffset ?? 0);
+            InputHelper.FocusElement(_inputElement);
+        }
+
+        public void SetOptions(TextInputOptions options)
+        {
+        }
+
+        public void Reset()
+        {
+            InputHelper.ClearInputElement(_inputElement);
+            InputHelper.SetSurroundingText(_inputElement, "", 0, 0);
+        }
+    }
+}

+ 136 - 0
src/Web/Avalonia.Web/BrowserNativeControlHost.cs

@@ -0,0 +1,136 @@
+using System;
+using System.Diagnostics.CodeAnalysis;
+using System.Runtime.InteropServices.JavaScript;
+
+using Avalonia.Controls.Platform;
+using Avalonia.Platform;
+using Avalonia.Web.Interop;
+
+namespace Avalonia.Web
+{
+    internal class BrowserNativeControlHost : INativeControlHostImpl
+    {
+        private readonly JSObject _hostElement;
+
+        public BrowserNativeControlHost(JSObject element)
+        {
+            _hostElement = element;
+        }
+
+        public INativeControlHostDestroyableControlHandle CreateDefaultChild(IPlatformHandle parent)
+        {
+            var element = NativeControlHostHelper.CreateDefaultChild(null);
+            return new JSObjectControlHandle(element);
+        }
+
+        public INativeControlHostControlTopLevelAttachment CreateNewAttachment(Func<IPlatformHandle, IPlatformHandle> create)
+        {
+            Attachment? a = null;
+            try
+            {
+                var child = create(new JSObjectControlHandle(_hostElement));
+                var attachmenetReference = NativeControlHostHelper.CreateAttachment();
+                // 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 = NativeControlHostHelper.CreateAttachment();
+            var a = new Attachment(attachmenetReference, handle);
+            a.AttachedTo = this;
+            return a;
+        }
+
+        public bool IsCompatibleWith(IPlatformHandle handle) => handle is JSObjectControlHandle;
+
+        private 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 JSObject? _native;
+            private BrowserNativeControlHost? _attachedTo;
+
+            public Attachment(JSObject native, IPlatformHandle handle)
+            {
+                _native = native;
+                NativeControlHostHelper.InitializeWithChildHandle(_native, ((JSObjectControlHandle)handle).Object);
+            }
+
+            public void Dispose()
+            {
+                if (_native != null)
+                {
+                    NativeControlHostHelper.ReleaseChild(_native);
+                    _native.Dispose();
+                    _native = null;
+                }
+            }
+
+            public INativeControlHostImpl? AttachedTo
+            {
+                get => _attachedTo!;
+                set
+                {
+                    CheckDisposed();
+
+                    var host = (BrowserNativeControlHost?)value;
+                    if (host == null)
+                    {
+                        NativeControlHostHelper.AttachTo(_native, null);
+                    }
+                    else
+                    {
+                        NativeControlHostHelper.AttachTo(_native, host._hostElement);
+                    }
+                    _attachedTo = host;
+                }
+            }
+
+            public bool IsCompatibleWith(INativeControlHostImpl host) => host is BrowserNativeControlHost;
+
+            public void HideWithSize(Size size)
+            {
+                CheckDisposed();
+                if (_attachedTo == null)
+                    return;
+
+                NativeControlHostHelper.HideWithSize(_native, Math.Max(1, size.Width), Math.Max(1, 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));
+
+                NativeControlHostHelper.ShowInBounds(_native, bounds.X, bounds.Y, bounds.Width, bounds.Height);
+            }
+
+            [MemberNotNull(nameof(_native))]
+            private void CheckDisposed()
+            {
+                if (_native == null)
+                    throw new ObjectDisposedException(nameof(Attachment));
+            }
+        }
+    }
+}

+ 41 - 0
src/Web/Avalonia.Web/BrowserSingleViewLifetime.cs

@@ -0,0 +1,41 @@
+using System.Runtime.InteropServices.JavaScript;
+using System;
+using Avalonia.Controls;
+using Avalonia.Controls.ApplicationLifetimes;
+using Avalonia.Media;
+using Avalonia.Web.Skia;
+
+namespace Avalonia.Web
+{
+    [System.Runtime.Versioning.SupportedOSPlatform("browser")] // gets rid of callsite warnings
+    public class BrowserSingleViewLifetime : ISingleViewApplicationLifetime
+    {
+        public AvaloniaView? View;
+
+        public Control? MainView
+        {
+            get => View!.Content;
+            set => View!.Content = value;
+        }
+    }
+
+    public static partial class WebAppBuilder
+    {
+        public static T SetupBrowserApp<T>(
+        this T builder, string mainDivId)
+        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);
+        }
+    }
+}

+ 226 - 0
src/Web/Avalonia.Web/BrowserTopLevelImpl.cs

@@ -0,0 +1,226 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+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.Skia;
+using Avalonia.Web.Storage;
+
+namespace Avalonia.Web
+{
+    [System.Runtime.Versioning.SupportedOSPlatform("browser")] // gets rid of callsite warnings
+    internal class BrowserTopLevelImpl : ITopLevelImplWithTextInputMethod, ITopLevelImplWithNativeControlHost, ITopLevelImplWithStorageProvider
+    {
+        private Size _clientSize;
+        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 BrowserTopLevelImpl(AvaloniaView avaloniaView)
+        {
+            Surfaces = Enumerable.Empty<object>();
+            _avaloniaView = avaloniaView;
+            TransparencyLevel = WindowTransparencyLevel.None;
+            AcrylicCompensationLevels = new AcrylicPlatformCompensationLevels(1, 1, 1);
+            _touchDevice = new TouchDevice();
+            _penDevice = new PenDevice();
+            NativeControlHost = _avaloniaView.GetNativeControlHostImpl();
+        }
+
+        public ulong Timestamp => (ulong)_sw.ElapsedMilliseconds;
+
+        public void SetClientSize(Size newSize, double dpi)
+        {
+            if (Math.Abs(RenderScaling - dpi) > 0.0001)
+            {
+                if (Surfaces.FirstOrDefault() is BrowserSkiaSurface surface)
+                {
+                    surface.Scaling = dpi;
+                }
+                
+                ScalingChanged?.Invoke(dpi);
+            }
+
+            if (newSize != _clientSize)
+            {
+                _clientSize = newSize;
+
+                if (Surfaces.FirstOrDefault() is BrowserSkiaSurface surface)
+                {
+                    surface.Size = new PixelSize((int)newSize.Width, (int)newSize.Height);
+                }
+
+                Resized?.Invoke(newSize, PlatformResizeReason.User);
+            }
+        }
+
+        public bool 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);
+
+                return args.Handled;
+            }
+
+            return false;
+        }
+
+        private IPointerDevice GetPointerDevice(string pointerType)
+        {
+            return pointerType switch
+            {
+                "touch" => _touchDevice,
+                "pen" => _penDevice,
+                _ => MouseDevice
+            };
+        }
+
+        public bool RawMouseWheelEvent(Point p, Vector v, RawInputModifiers modifiers)
+        {
+            if (_inputRoot is { })
+            {
+                var args = new RawMouseWheelEventArgs(MouseDevice, Timestamp, _inputRoot, p, v, modifiers);
+                
+                Input?.Invoke(args);
+
+                return args.Handled;
+            }
+
+            return false;
+        }
+
+        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 bool RawTextEvent(string text)
+        {
+            if (_inputRoot is { })
+            {
+                var args = new RawTextInputEventArgs(KeyboardDevice, Timestamp, _inputRoot, text);
+                Input?.Invoke(args);
+
+                return args.Handled;
+            }
+
+            return false;
+        }
+
+        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 => (Surfaces.FirstOrDefault() as BrowserSkiaSurface)?.Scaling ?? 1;
+
+        public IEnumerable<object> Surfaces { get; set; }
+
+        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; } = BrowserWindowingPlatform.Keyboard;
+        public WindowTransparencyLevel TransparencyLevel { get; }
+        public AcrylicPlatformCompensationLevels AcrylicCompensationLevels { get; }
+
+        public ITextInputMethodImpl TextInputMethod => _avaloniaView;
+
+        public INativeControlHostImpl? NativeControlHost { get; }
+        public IStorageProvider StorageProvider { get; } = new BrowserStorageProvider();
+    }
+}

+ 29 - 0
src/Web/Avalonia.Web/ClipboardImpl.cs

@@ -0,0 +1,29 @@
+using System;
+using System.Threading.Tasks;
+using Avalonia.Input;
+using Avalonia.Input.Platform;
+using Avalonia.Web.Interop;
+
+namespace Avalonia.Web
+{
+    internal class ClipboardImpl : IClipboard
+    {
+        public Task<string> GetTextAsync()
+        {
+            return InputHelper.ReadClipboardTextAsync();
+        }
+
+        public Task SetTextAsync(string text)
+        {
+            return InputHelper.WriteClipboardTextAsync(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());
+    }
+}

+ 95 - 0
src/Web/Avalonia.Web/Cursor.cs

@@ -0,0 +1,95 @@
+using System;
+using System.IO;
+using Avalonia.Input;
+using Avalonia.Platform;
+
+namespace Avalonia.Web
+{
+    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);
+        }
+    }
+}
+

+ 43 - 0
src/Web/Avalonia.Web/Interop/CanvasHelper.cs

@@ -0,0 +1,43 @@
+using System;
+using System.Runtime.InteropServices;
+using System.Runtime.InteropServices.JavaScript;
+
+namespace Avalonia.Web.Interop;
+
+internal record GLInfo(int ContextId, uint FboId, int Stencils, int Samples, int Depth);
+
+[System.Runtime.Versioning.SupportedOSPlatform("browser")] // gets rid of callsite warnings
+internal static partial class CanvasHelper
+{
+
+    [DllImport("libSkiaSharp", CallingConvention = CallingConvention.Cdecl)]
+    static extern JSObject InterceptGLObject();
+
+    public static GLInfo InitialiseGL(JSObject canvas, Action renderFrameCallback)
+    {
+        InterceptGLObject();
+
+        var info = InitGL(canvas, canvas.GetPropertyAsString("id")!, renderFrameCallback);
+
+        var glInfo = new GLInfo(
+            info.GetPropertyAsInt32("context"),
+            (uint)info.GetPropertyAsInt32("fboId"),
+            info.GetPropertyAsInt32("stencil"),
+            info.GetPropertyAsInt32("sample"),
+            info.GetPropertyAsInt32("depth"));
+
+        return glInfo;
+    }
+
+    [JSImport("Canvas.requestAnimationFrame", "avalonia.ts")]
+    public static partial void RequestAnimationFrame(JSObject canvas, bool renderLoop);
+
+    [JSImport("Canvas.setCanvasSize", "avalonia.ts")]
+    public static partial void SetCanvasSize(JSObject canvas, int height, int width);
+
+    [JSImport("Canvas.initGL", "avalonia.ts")]
+    private static partial JSObject InitGL(
+        JSObject canvas,
+        string canvasId,
+        [JSMarshalAs<JSType.Function>] Action renderFrameCallback);
+}

+ 28 - 0
src/Web/Avalonia.Web/Interop/DomHelper.cs

@@ -0,0 +1,28 @@
+using System;
+using System.Runtime.InteropServices.JavaScript;
+
+namespace Avalonia.Web.Interop;
+
+internal static partial class DomHelper
+{
+    [JSImport("globalThis.document.getElementById")]
+    internal static partial JSObject? GetElementById(string id);
+
+    [JSImport("AvaloniaDOM.createAvaloniaHost", "avalonia.ts")]
+    public static partial JSObject CreateAvaloniaHost(JSObject element);
+
+    [JSImport("AvaloniaDOM.addClass", "avalonia.ts")]
+    public static partial void AddCssClass(JSObject element, string className);
+
+    [JSImport("SizeWatcher.observe", "avalonia.ts")]
+    public static partial JSObject ObserveSize(
+        JSObject canvas,
+        string canvasId,
+        [JSMarshalAs<JSType.Function<JSType.Number, JSType.Number>>]
+        Action<int, int> onSizeChanged);
+
+    [JSImport("DpiWatcher.start", "avalonia.ts")]
+    public static partial double ObserveDpi(
+       [JSMarshalAs<JSType.Function<JSType.Number, JSType.Number>>]
+        Action<double, double> onDpiChanged);
+}

+ 78 - 0
src/Web/Avalonia.Web/Interop/InputHelper.cs

@@ -0,0 +1,78 @@
+using System;
+using System.Runtime.InteropServices.JavaScript;
+using System.Threading.Tasks;
+
+namespace Avalonia.Web.Interop;
+
+internal static partial class InputHelper
+{
+    [JSImport("InputHelper.subscribeKeyEvents", "avalonia.ts")]
+    public static partial void SubscribeKeyEvents(
+        JSObject htmlElement,
+        [JSMarshalAs<JSType.Function<JSType.String, JSType.String, JSType.Number, JSType.Boolean>>]
+        Func<string, string, int, bool> keyDown,
+        [JSMarshalAs<JSType.Function<JSType.String, JSType.String, JSType.Number, JSType.Boolean>>]
+        Func<string, string, int, bool> keyUp);
+
+    [JSImport("InputHelper.subscribeTextEvents", "avalonia.ts")]
+    public static partial void SubscribeTextEvents(
+        JSObject htmlElement,
+        [JSMarshalAs<JSType.Function<JSType.String, JSType.String, JSType.Boolean>>]
+        Func<string, string?, bool> onInput,
+        [JSMarshalAs<JSType.Function<JSType.Object, JSType.Boolean>>]
+        Func<JSObject, bool> onCompositionStart,
+        [JSMarshalAs<JSType.Function<JSType.Object, JSType.Boolean>>]
+        Func<JSObject, bool> onCompositionUpdate,
+        [JSMarshalAs<JSType.Function<JSType.Object, JSType.Boolean>>]
+        Func<JSObject, bool> onCompositionEnd);
+
+    [JSImport("InputHelper.subscribePointerEvents", "avalonia.ts")]
+    public static partial void SubscribePointerEvents(
+        JSObject htmlElement,
+        [JSMarshalAs<JSType.Function<JSType.Object, JSType.Boolean>>]
+        Func<JSObject, bool> pointerMove,
+        [JSMarshalAs<JSType.Function<JSType.Object, JSType.Boolean>>]
+        Func<JSObject, bool> pointerDown,
+        [JSMarshalAs<JSType.Function<JSType.Object, JSType.Boolean>>]
+        Func<JSObject, bool> pointerUp,
+        [JSMarshalAs<JSType.Function<JSType.Object, JSType.Boolean>>]
+        Func<JSObject, bool> wheel);
+
+
+    [JSImport("InputHelper.subscribeInputEvents", "avalonia.ts")]
+    public static partial void SubscribeInputEvents(
+        JSObject htmlElement,
+        [JSMarshalAs<JSType.Function<JSType.String, JSType.Boolean>>]
+        Func<string, bool> input);
+
+
+    [JSImport("InputHelper.clearInput", "avalonia.ts")]
+    public static partial void ClearInputElement(JSObject htmlElement);
+
+    [JSImport("InputHelper.isInputElement", "avalonia.ts")]
+    public static partial void IsInputElement(JSObject htmlElement);
+
+    [JSImport("InputHelper.focusElement", "avalonia.ts")]
+    public static partial void FocusElement(JSObject htmlElement);
+
+    [JSImport("InputHelper.setCursor", "avalonia.ts")]
+    public static partial void SetCursor(JSObject htmlElement, string kind);
+
+    [JSImport("InputHelper.hide", "avalonia.ts")]
+    public static partial void HideElement(JSObject htmlElement);
+
+    [JSImport("InputHelper.show", "avalonia.ts")]
+    public static partial void ShowElement(JSObject htmlElement);
+
+    [JSImport("InputHelper.setSurroundingText", "avalonia.ts")]
+    public static partial void SetSurroundingText(JSObject htmlElement, string text, int start, int end);
+
+    [JSImport("InputHelper.setBounds", "avalonia.ts")]
+    public static partial void SetBounds(JSObject htmlElement, int x, int y, int width, int height, int caret);
+
+    [JSImport("globalThis.navigator.clipboard.readText")]
+    public static partial Task<string> ReadClipboardTextAsync();
+
+    [JSImport("globalThis.navigator.clipboard.writeText")]
+    public static partial Task WriteClipboardTextAsync(string text);
+}

+ 28 - 0
src/Web/Avalonia.Web/Interop/NativeControlHostHelper.cs

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

+ 55 - 0
src/Web/Avalonia.Web/Interop/StorageHelper.cs

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

+ 40 - 0
src/Web/Avalonia.Web/Interop/StreamHelper.cs

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

+ 30 - 0
src/Web/Avalonia.Web/JSObjectControlHandle.cs

@@ -0,0 +1,30 @@
+using System;
+using System.Runtime.InteropServices.JavaScript;
+
+using Avalonia.Controls.Platform;
+
+namespace Avalonia.Web;
+
+public class JSObjectControlHandle : INativeControlHostDestroyableControlHandle
+{
+    internal const string ElementReferenceDescriptor = "JSObject";
+
+    public JSObjectControlHandle(JSObject reference)
+    {
+        Object = reference;
+    }
+
+    public JSObject Object { get; }
+
+    public IntPtr Handle => throw new NotSupportedException();
+
+    public string? HandleDescriptor => ElementReferenceDescriptor;
+
+    public void Destroy()
+    {
+        if (Object is JSObject inProcess && !inProcess.IsDisposed)
+        {
+            inProcess.Dispose();
+        }
+    }
+}

+ 129 - 0
src/Web/Avalonia.Web/Keycodes.cs

@@ -0,0 +1,129 @@
+using System.Collections.Generic;
+
+using Avalonia.Input;
+
+namespace Avalonia.Web
+{
+    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 }
+        };
+    }
+}

+ 18 - 0
src/Web/Avalonia.Web/ManualTriggerRenderTimer.cs

@@ -0,0 +1,18 @@
+using System;
+using System.Diagnostics;
+using Avalonia.Rendering;
+
+namespace Avalonia.Web
+{
+    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;
+    }
+}

+ 26 - 0
src/Web/Avalonia.Web/Skia/BrowserSkiaGpu.cs

@@ -0,0 +1,26 @@
+using System.Collections.Generic;
+using Avalonia.Skia;
+
+namespace Avalonia.Web.Skia
+{
+    public class BrowserSkiaGpu : ISkiaGpu
+    {
+        public ISkiaGpuRenderTarget? TryCreateRenderTarget(IEnumerable<object> surfaces)
+        {
+            foreach (var surface in surfaces)
+            {
+                if (surface is BrowserSkiaSurface browserSkiaSurface)
+                {
+                    return new BrowserSkiaGpuRenderTarget(browserSkiaSurface);
+                }
+            }
+
+            return null;
+        }
+
+        public ISkiaSurface? TryCreateSurface(PixelSize size, ISkiaGpuRenderSession session)
+        {
+            return null;
+        }
+    }
+}

+ 36 - 0
src/Web/Avalonia.Web/Skia/BrowserSkiaGpuRenderSession.cs

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

+ 39 - 0
src/Web/Avalonia.Web/Skia/BrowserSkiaGpuRenderTarget.cs

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

+ 88 - 0
src/Web/Avalonia.Web/Skia/BrowserSkiaRasterSurface.cs

@@ -0,0 +1,88 @@
+using System;
+using System.Runtime.InteropServices;
+using Avalonia.Controls.Platform.Surfaces;
+using Avalonia.Platform;
+using Avalonia.Skia;
+using SkiaSharp;
+
+namespace Avalonia.Web.Skia
+{
+    internal class BrowserSkiaRasterSurface : IBrowserSkiaSurface, 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 BrowserSkiaRasterSurface(
+            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);
+            }
+        }
+    }
+}

+ 30 - 0
src/Web/Avalonia.Web/Skia/BrowserSkiaSurface.cs

@@ -0,0 +1,30 @@
+using Avalonia.Web.Interop;
+using SkiaSharp;
+
+namespace Avalonia.Web.Skia
+{
+    internal class BrowserSkiaSurface : IBrowserSkiaSurface
+    {
+        public BrowserSkiaSurface(GRContext context, 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 GLInfo GlInfo { get; set; }
+    }
+}

+ 9 - 0
src/Web/Avalonia.Web/Skia/IBrowserSkiaSurface.cs

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

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

@@ -0,0 +1,88 @@
+using System;
+using System.IO;
+using System.Runtime.InteropServices.JavaScript;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Avalonia.Web.Storage;
+
+[System.Runtime.Versioning.SupportedOSPlatform("browser")]
+internal class BlobReadableStream : Stream
+{
+    private JSObject? _jSReference;
+    private long _position;
+    private readonly long _length;
+
+    public BlobReadableStream(JSObject jsStreamReference)
+    {
+        _jSReference = jsStreamReference;
+        _position = 0;
+        _length = StreamHelper.ByteLength(JSReference);
+    }
+
+    private JSObject JSReference => _jSReference ?? throw new ObjectDisposedException(nameof(WriteableStream));
+
+    public override bool CanRead => true;
+
+    public override bool CanSeek => false;
+
+    public override bool CanWrite => false;
+
+    public override long Length => _length;
+
+    public override long Position
+    {
+        get => _position;
+        set => throw new NotSupportedException();
+    }
+
+    public override void Flush() { }
+
+    public override long Seek(long offset, SeekOrigin origin)
+    {
+        return _position = origin switch
+        {
+            SeekOrigin.Current => _position + offset,
+            SeekOrigin.End => _length + offset,
+            _ => offset
+        };
+    }
+
+    public override void SetLength(long value)
+        => throw new NotSupportedException();
+
+    public override void Write(byte[] buffer, int offset, int count)
+        => throw new NotSupportedException();
+
+    public override int Read(byte[] buffer, int offset, int count)
+    {
+        throw new InvalidOperationException("Browser supports only ReadAsync");
+    }
+
+    public override async Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
+        => await ReadAsync(buffer.AsMemory(offset, count), cancellationToken);
+
+    public override async ValueTask<int> ReadAsync(Memory<byte> buffer, CancellationToken cancellationToken = default)
+    {
+        var numBytesToRead = (int)Math.Min(buffer.Length, Length - _position);
+        var bytesRead = await StreamHelper.SliceAsync(JSReference, _position, numBytesToRead);
+        if (bytesRead.Length != numBytesToRead)
+        {
+            throw new EndOfStreamException("Failed to read the requested number of bytes from the stream.");
+        }
+
+        _position += bytesRead.Length;
+        bytesRead.CopyTo(buffer);
+
+        return bytesRead.Length;
+    }
+
+    protected override void Dispose(bool disposing)
+    {
+        if (_jSReference is { } jsReference)
+        {
+            _jSReference = null;
+            jsReference.Dispose();
+        }
+    }
+}

+ 257 - 0
src/Web/Avalonia.Web/Storage/BrowserStorageProvider.cs

@@ -0,0 +1,257 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+using System.IO;
+using System.Linq;
+using System.Runtime.InteropServices.JavaScript;
+using System.Runtime.Versioning;
+using System.Threading.Tasks;
+
+using Avalonia.Platform.Storage;
+using Avalonia.Web.Interop;
+
+namespace Avalonia.Web.Storage;
+
+internal record FilePickerAcceptType(string Description, IReadOnlyDictionary<string, IReadOnlyList<string>> Accept);
+
+[SupportedOSPlatform("browser")]
+internal class BrowserStorageProvider : IStorageProvider
+{
+    internal const string PickerCancelMessage = "The user aborted a request";
+    internal const string NoPermissionsMessage = "Permissions denied";
+
+    private readonly Lazy<Task<JSObject>> _lazyModule = new(() => JSHost.ImportAsync("storage.ts", "./storage.js"));
+
+    public bool CanOpen => StorageHelper.CanShowOpenFilePicker();
+    public bool CanSave => StorageHelper.CanShowSaveFilePicker();
+    public bool CanPickFolder => StorageHelper.CanShowDirectoryPicker();
+
+    public async Task<IReadOnlyList<IStorageFile>> OpenFilePickerAsync(FilePickerOpenOptions options)
+    {
+        _ = await _lazyModule.Value;
+        var startIn = (options.SuggestedStartLocation as JSStorageItem)?.FileHandle;
+
+        var (types, exludeAll) = ConvertFileTypes(options.FileTypeFilter);
+
+        try
+        {
+            using var items = await StorageHelper.OpenFileDialog(startIn, options.AllowMultiple, types, exludeAll);
+            if (items is null)
+            {
+                return Array.Empty<IStorageFile>();
+            }
+
+            var itemsArray = StorageHelper.ItemsArray(items);
+            return itemsArray.Select(item => new JSStorageFile(item)).ToArray();
+        }
+        catch (JSException ex) when (ex.Message.Contains(PickerCancelMessage, StringComparison.Ordinal))
+        {
+            return Array.Empty<IStorageFile>();
+        }
+        finally
+        {
+            if (types is not null)
+            {
+                foreach (var type in types)
+                {
+                    type.Dispose();
+                }
+            }
+        }
+    }
+
+    public async Task<IStorageFile?> SaveFilePickerAsync(FilePickerSaveOptions options)
+    {
+        _ = await _lazyModule.Value;
+        var startIn = (options.SuggestedStartLocation as JSStorageItem)?.FileHandle;
+
+        var (types, exludeAll) = ConvertFileTypes(options.FileTypeChoices);
+
+        try
+        {
+            var item = await StorageHelper.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;
+        }
+        finally
+        {
+            if (types is not null)
+            {
+                foreach (var type in types)
+                {
+                    type.Dispose();
+                }
+            }
+        }
+    }
+
+    public async Task<IReadOnlyList<IStorageFolder>> OpenFolderPickerAsync(FolderPickerOpenOptions options)
+    {
+        _ = await _lazyModule.Value;
+        var startIn = (options.SuggestedStartLocation as JSStorageItem)?.FileHandle;
+
+        try
+        {
+            var item = await StorageHelper.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)
+    {
+        _ = await _lazyModule.Value;
+        var item = await StorageHelper.OpenBookmark(bookmark);
+        return item is not null ? new JSStorageFile(item) : null;
+    }
+
+    public async Task<IStorageBookmarkFolder?> OpenFolderBookmarkAsync(string bookmark)
+    {
+        _ = await _lazyModule.Value;
+        var item = await StorageHelper.OpenBookmark(bookmark);
+        return item is not null ? new JSStorageFolder(item) : null;
+    }
+
+    private static (JSObject[]? types, bool excludeAllOption) ConvertFileTypes(IEnumerable<FilePickerFileType>? input)
+    {
+        var types = input?
+            .Where(t => t.MimeTypes?.Any() == true && t != FilePickerFileTypes.All)
+            .Select(t => StorageHelper.CreateAcceptType(t.Name, t.MimeTypes!.ToArray()))
+            .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 JSObject? _fileHandle;
+
+    protected JSStorageItem(JSObject fileHandle)
+    {
+        _fileHandle = fileHandle ?? throw new ArgumentNullException(nameof(fileHandle));
+    }
+
+    internal JSObject FileHandle => _fileHandle ?? throw new ObjectDisposedException(nameof(JSStorageItem));
+
+    public string Name => FileHandle.GetPropertyAsString("name") ?? string.Empty;
+
+    public bool TryGetUri([NotNullWhen(true)] out Uri? uri)
+    {
+        uri = new Uri(Name, UriKind.Relative);
+        return false;
+    }
+
+    public async Task<StorageItemProperties> GetBasicPropertiesAsync()
+    {
+        using var properties = await StorageHelper.GetProperties(FileHandle);
+        var size = (long?)properties?.GetPropertyAsDouble("Size");
+        var lastModified = (long?)properties?.GetPropertyAsDouble("LastModified");
+
+        return new StorageItemProperties(
+            (ulong?)size,
+            dateCreated: null,
+            dateModified: lastModified > 0 ? DateTimeOffset.FromUnixTimeMilliseconds(lastModified.Value) : null);
+    }
+
+    public bool CanBookmark => true;
+
+    public Task<string?> SaveBookmarkAsync()
+    {
+        return StorageHelper.SaveBookmark(FileHandle);
+    }
+
+    public Task<IStorageFolder?> GetParentAsync()
+    {
+        return Task.FromResult<IStorageFolder?>(null);
+    }
+
+    public Task ReleaseBookmarkAsync()
+    {
+        return StorageHelper.DeleteBookmark(FileHandle);
+    }
+
+    public void Dispose()
+    {
+        _fileHandle?.Dispose();
+        _fileHandle = null;
+    }
+}
+
+internal class JSStorageFile : JSStorageItem, IStorageBookmarkFile
+{
+    public JSStorageFile(JSObject fileHandle) : base(fileHandle)
+    {
+    }
+
+    public bool CanOpenRead => true;
+    public async Task<Stream> OpenReadAsync()
+    {
+        try
+        {
+            var blob = await StorageHelper.OpenRead(FileHandle);
+            return new BlobReadableStream(blob);
+        }
+        catch (JSException ex) when (ex.Message == BrowserStorageProvider.NoPermissionsMessage)
+        {
+            throw new UnauthorizedAccessException("User denied permissions to open the file", ex);
+        }
+    }
+
+    public bool CanOpenWrite => true;
+    public async Task<Stream> OpenWriteAsync()
+    {
+        try
+        {
+            using var properties = await StorageHelper.GetProperties(FileHandle);
+            var streamWriter = await StorageHelper.OpenWrite(FileHandle);
+            var size = (long?)properties?.GetPropertyAsDouble("Size") ?? 0;
+
+            return new WriteableStream(streamWriter, size);
+        }
+        catch (JSException ex) when (ex.Message == BrowserStorageProvider.NoPermissionsMessage)
+        {
+            throw new UnauthorizedAccessException("User denied permissions to open the file", ex);
+        }
+    }
+}
+
+internal class JSStorageFolder : JSStorageItem, IStorageBookmarkFolder
+{
+    public JSStorageFolder(JSObject fileHandle) : base(fileHandle)
+    {
+    }
+
+    public async Task<IReadOnlyList<IStorageItem>> GetItemsAsync()
+    {
+        using var items = await StorageHelper.GetItems(FileHandle);
+        if (items is null)
+        {
+            return Array.Empty<IStorageItem>();
+        }
+
+        var itemsArray = StorageHelper.ItemsArray(items);
+
+        return itemsArray
+            .Select(reference => reference.GetPropertyAsString("kind") switch
+            {
+                "directory" => (IStorageItem)new JSStorageFolder(reference),
+                "file" => new JSStorageFile(reference),
+                _ => null
+            })
+            .Where(i => i is not null)
+            .ToArray()!;
+    }
+}

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

@@ -0,0 +1,124 @@
+using System;
+using System.IO;
+using System.Runtime.InteropServices.JavaScript;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Avalonia.Web.Storage;
+
+[System.Runtime.Versioning.SupportedOSPlatform("browser")]
+// Loose wrapper implementaion of a stream on top of FileAPI FileSystemWritableFileStream
+internal sealed class WriteableStream : Stream
+{
+    private JSObject? _jSReference;
+
+    // Unfortunatelly we can't read current length/position, so we need to keep it C#-side only.
+    private long _length, _position;
+
+    internal WriteableStream(JSObject jSReference, long initialLength)
+    {
+        _jSReference = jSReference;
+        _length = initialLength;
+    }
+
+    private JSObject JSReference => _jSReference ?? throw new ObjectDisposedException(nameof(WriteableStream));
+
+    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
+        };
+        StreamHelper.Seek(JSReference, 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;
+        }
+
+        StreamHelper.Truncate(JSReference, value);
+    }
+
+    public override void Write(byte[] buffer, int offset, int count)
+    {
+        throw new InvalidOperationException("Browser supports only WriteAsync");
+    }
+
+    public override ValueTask WriteAsync(ReadOnlyMemory<byte> buffer, CancellationToken cancellationToken = default)
+    {
+        return new ValueTask(WriteAsyncInternal(buffer.ToArray(), cancellationToken));
+    }
+
+    private Task WriteAsyncInternal(byte[] buffer, CancellationToken _)
+    {
+        _position += buffer.Length;
+
+        return StreamHelper.WriteAsync(JSReference, buffer);
+    }
+
+    protected override void Dispose(bool disposing)
+    {
+        if (_jSReference is { } jsReference)
+        {
+            _jSReference = null;
+            try
+            {
+                _ = StreamHelper.CloseAsync(jsReference);
+            }
+            finally
+            {
+                jsReference.Dispose();
+            }
+        }
+    }
+
+    public override async ValueTask DisposeAsync()
+    {
+        if (_jSReference is { } jsReference)
+        {
+            _jSReference = null;
+            try
+            {
+                await StreamHelper.CloseAsync(jsReference);
+            }
+            finally
+            {
+                jsReference.Dispose();
+            }
+        }
+    }
+}

+ 68 - 0
src/Web/Avalonia.Web/WebEmbeddableControlRoot.cs

@@ -0,0 +1,68 @@
+using System;
+using Avalonia.Controls.Embedding;
+using Avalonia.Media;
+using Avalonia.Platform;
+using Avalonia.Rendering.SceneGraph;
+
+namespace Avalonia.Web
+{
+    internal class WebEmbeddableControlRoot : EmbeddableControlRoot
+    {
+        class SplashScreenCloseCustomDrawingOperation : ICustomDrawOperation
+        {
+            private bool _hasRendered;
+            private Action _onFirstRender;
+
+            public SplashScreenCloseCustomDrawingOperation(Action onFirstRender)
+            {
+                _onFirstRender = onFirstRender;
+            }
+
+            public Rect Bounds => Rect.Empty;
+
+            public bool HasRendered => _hasRendered;
+
+            public void Dispose()
+            {
+                
+            }
+
+            public bool Equals(ICustomDrawOperation? other)
+            {
+                return false;
+            }
+
+            public bool HitTest(Point p)
+            {
+                return false;
+            }
+
+            public void Render(IDrawingContextImpl context)
+            {
+                _hasRendered = true;
+                _onFirstRender();
+            }
+        }
+
+        public WebEmbeddableControlRoot(ITopLevelImpl impl, Action onFirstRender) : base(impl)
+        {
+            _splashCloseOp = new SplashScreenCloseCustomDrawingOperation(() =>
+            {
+                _splashCloseOp = null;
+                onFirstRender();
+            });
+        }
+
+        private SplashScreenCloseCustomDrawingOperation? _splashCloseOp;
+
+        public override void Render(DrawingContext context)
+        {
+            base.Render(context);
+
+            if (_splashCloseOp != null)
+            {
+                context.Custom(_splashCloseOp);
+            }
+        }
+    }
+}

+ 48 - 0
src/Web/Avalonia.Web/WinStubs.cs

@@ -0,0 +1,48 @@
+using System.Collections.Generic;
+using System.IO;
+using Avalonia.Platform;
+
+#nullable enable
+
+namespace Avalonia.Web
+{
+    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);
+        }
+    }
+}

+ 105 - 0
src/Web/Avalonia.Web/WindowingPlatform.cs

@@ -0,0 +1,105 @@
+using System;
+using System.Threading;
+using Avalonia.Input;
+using Avalonia.Input.Platform;
+using Avalonia.Platform;
+using Avalonia.Rendering;
+using Avalonia.Threading;
+
+namespace Avalonia.Web
+{
+    public class BrowserWindowingPlatform : 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("BrowserWindowingPlatform not registered.");
+
+        public static void Register()
+        {
+            var instance = new BrowserWindowingPlatform();
+            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; // Browser is single threaded.
+            }
+        }
+
+        public event Action<DispatcherPriority?>? Signaled;
+
+        private static IRuntimePlatform GetRuntimePlatform()
+        {
+            return AvaloniaLocator.Current.GetRequiredService<IRuntimePlatform>();
+        }
+    }
+}

+ 13 - 0
src/Web/Avalonia.Web/interop.js

@@ -0,0 +1,13 @@
+var LibraryExample = {
+    // Internal functions
+    $EXAMPLE: {
+        internal_func: function () {
+        }
+    },
+    InterceptGLObject: function () {
+        globalThis.AvaloniaGL = GL
+    }
+}
+
+autoAddDeps(LibraryExample, '$EXAMPLE')
+mergeInto(LibraryManager.library, LibraryExample)

+ 47 - 0
src/Web/Avalonia.Web/webapp/.eslintrc.json

@@ -0,0 +1,47 @@
+{
+  "env": {
+    "browser": true,
+    "es6": true
+  },
+  "extends": "standard-with-typescript",
+  "overrides": [],
+  "parserOptions": {
+    "ecmaVersion": "latest",
+    "sourceType": "module",
+    "project": [
+      "tsconfig.json"
+    ]
+  },
+  "rules": {
+    "indent": [
+      "warn",
+      4
+    ],
+    "@typescript-eslint/indent": [
+      "warn",
+      4
+    ],
+    "quotes": ["warn", "double"],
+    "semi": ["error", "always"],
+    "@typescript-eslint/quotes": ["warn", "double"],
+    "@typescript-eslint/explicit-function-return-type": "off",
+    "@typescript-eslint/no-extraneous-class": "off",
+    "@typescript-eslint/strict-boolean-expressions": "off",
+    "@typescript-eslint/space-before-function-paren": "off",
+    "@typescript-eslint/semi": ["error", "always"],
+    "@typescript-eslint/member-delimiter-style": [
+      "error",
+      {
+        "multiline": {
+          "delimiter": "semi",
+          "requireLast": true
+        },
+        "singleline": {
+          "delimiter": "semi",
+          "requireLast": false
+        }
+      }
+    ]
+  },
+  "ignorePatterns": ["types/*"]
+}

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

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

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

@@ -0,0 +1,20 @@
+import { RuntimeAPI } from "../types/dotnet";
+import { SizeWatcher, DpiWatcher, Canvas } from "./avalonia/canvas";
+import { InputHelper } from "./avalonia/input";
+import { AvaloniaDOM } from "./avalonia/dom";
+import { Caniuse } from "./avalonia/caniuse";
+import { StreamHelper } from "./avalonia/stream";
+import { NativeControlHost } from "./avalonia/nativeControlHost";
+
+export async function createAvaloniaRuntime(api: RuntimeAPI): Promise<void> {
+    api.setModuleImports("avalonia.ts", {
+        Caniuse,
+        Canvas,
+        InputHelper,
+        SizeWatcher,
+        DpiWatcher,
+        AvaloniaDOM,
+        StreamHelper,
+        NativeControlHost
+    });
+}

+ 13 - 0
src/Web/Avalonia.Web/webapp/modules/avalonia/caniuse.ts

@@ -0,0 +1,13 @@
+export class Caniuse {
+    public static canShowOpenFilePicker(): boolean {
+        return typeof window.showOpenFilePicker !== "undefined";
+    }
+
+    public static canShowSaveFilePicker(): boolean {
+        return typeof window.showSaveFilePicker !== "undefined";
+    }
+
+    public static canShowDirectoryPicker(): boolean {
+        return typeof window.showDirectoryPicker !== "undefined";
+    }
+}

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

@@ -0,0 +1,303 @@
+interface SKGLViewInfo {
+    context: WebGLRenderingContext | WebGL2RenderingContext | undefined;
+    fboId: number;
+    stencil: number;
+    sample: number;
+    depth: number;
+}
+
+type CanvasElement = {
+    Canvas: Canvas | undefined;
+} & HTMLCanvasElement;
+
+export class Canvas {
+    static elements: Map<string, HTMLCanvasElement>;
+
+    htmlCanvas: HTMLCanvasElement;
+    glInfo?: SKGLViewInfo;
+    renderFrameCallback: () => void;
+    renderLoopEnabled: boolean = false;
+    renderLoopRequest: number = 0;
+    newWidth?: number;
+    newHeight?: number;
+
+    public static initGL(element: HTMLCanvasElement, elementId: string, renderFrameCallback: () => void): SKGLViewInfo | null {
+        const view = Canvas.init(true, element, elementId, renderFrameCallback);
+        if (!view || !view.glInfo) {
+            return null;
+        }
+
+        return view.glInfo;
+    }
+
+    static init(useGL: boolean, element: HTMLCanvasElement, elementId: string, renderFrameCallback: () => void): Canvas | null {
+        const htmlCanvas = element as CanvasElement;
+        if (!htmlCanvas) {
+            console.error("No canvas element was provided.");
+            return null;
+        }
+
+        if (!Canvas.elements) {
+            Canvas.elements = new Map<string, HTMLCanvasElement>();
+        }
+        Canvas.elements.set(elementId, element);
+
+        const view = new Canvas(useGL, element, renderFrameCallback);
+
+        htmlCanvas.Canvas = view;
+
+        return view;
+    }
+
+    public constructor(useGL: boolean, element: HTMLCanvasElement, renderFrameCallback: () => void) {
+        this.htmlCanvas = element;
+        this.renderFrameCallback = renderFrameCallback;
+
+        if (useGL) {
+            const ctx = Canvas.createWebGLContext(element);
+            if (!ctx) {
+                console.error("Failed to create WebGL context");
+                return;
+            }
+
+            const GL = (globalThis as any).AvaloniaGL;
+
+            // make current
+            GL.makeContextCurrent(ctx);
+
+            const GLctx = GL.currentContext.GLctx as WebGLRenderingContext;
+
+            // 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 setEnableRenderLoop(enable: boolean): void {
+        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 requestAnimationFrame(renderLoop?: boolean): void {
+        // 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) {
+                const GL = (globalThis as any).AvaloniaGL;
+                // 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();
+            this.renderLoopRequest = 0;
+
+            // we may want to draw the next frame
+            if (this.renderLoopEnabled) {
+                this.requestAnimationFrame();
+            }
+        });
+    }
+
+    public setCanvasSize(width: number, height: number): void {
+        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) {
+            const GL = (globalThis as any).AvaloniaGL;
+            // make current
+            GL.makeContextCurrent(this.glInfo.context);
+        }
+    }
+
+    public static setCanvasSize(element: HTMLCanvasElement, width: number, height: number): void {
+        const htmlCanvas = element as CanvasElement;
+        if (!htmlCanvas || !htmlCanvas.Canvas) {
+            return;
+        }
+
+        htmlCanvas.Canvas.setCanvasSize(width, height);
+    }
+
+    public static requestAnimationFrame(element: HTMLCanvasElement, renderLoop?: boolean): void {
+        const htmlCanvas = element as CanvasElement;
+        if (!htmlCanvas || !htmlCanvas.Canvas) {
+            return;
+        }
+
+        htmlCanvas.Canvas.requestAnimationFrame(renderLoop);
+    }
+
+    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
+        };
+
+        const GL = (globalThis as any).AvaloniaGL;
+
+        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;
+    }
+}
+
+type SizeWatcherElement = {
+    SizeWatcher: SizeWatcherInstance;
+} & HTMLElement;
+
+interface SizeWatcherInstance {
+    callback: (width: number, height: number) => void;
+}
+
+export class SizeWatcher {
+    static observer: ResizeObserver;
+    static elements: Map<string, HTMLElement>;
+
+    public static observe(element: HTMLElement, elementId: string, callback: (width: number, height: number) => void): void {
+        if (!element || !callback) {
+            return;
+        }
+
+        SizeWatcher.init();
+
+        const watcherElement = element as SizeWatcherElement;
+        watcherElement.SizeWatcher = {
+            callback
+        };
+
+        SizeWatcher.elements.set(elementId, element);
+        SizeWatcher.observer.observe(element);
+
+        SizeWatcher.invoke(element);
+    }
+
+    public static unobserve(elementId: string): void {
+        if (!elementId || !SizeWatcher.observer) {
+            return;
+        }
+
+        const element = SizeWatcher.elements.get(elementId);
+        if (element) {
+            SizeWatcher.elements.delete(elementId);
+            SizeWatcher.observer.unobserve(element);
+        }
+    }
+
+    static init(): void {
+        if (SizeWatcher.observer) {
+            return;
+        }
+
+        SizeWatcher.elements = new Map<string, HTMLElement>();
+        SizeWatcher.observer = new ResizeObserver((entries) => {
+            for (const entry of entries) {
+                SizeWatcher.invoke(entry.target);
+            }
+        });
+    }
+
+    static invoke(element: Element): void {
+        const watcherElement = element as SizeWatcherElement;
+        const instance = watcherElement.SizeWatcher;
+
+        if (!instance || !instance.callback) {
+            return;
+        }
+
+        return instance.callback(element.clientWidth, element.clientHeight);
+    }
+}
+
+export class DpiWatcher {
+    static lastDpi: number;
+    static timerId: number;
+    static callback: (old: number, newdpi: number) => void;
+
+    public static getDpi(): number {
+        return window.devicePixelRatio;
+    }
+
+    public static start(callback: (old: number, newdpi: number) => void): number {
+        DpiWatcher.lastDpi = window.devicePixelRatio;
+        DpiWatcher.timerId = window.setInterval(DpiWatcher.update, 1000);
+        DpiWatcher.callback = callback;
+
+        return DpiWatcher.lastDpi;
+    }
+
+    public static stop(): void {
+        window.clearInterval(DpiWatcher.timerId);
+    }
+
+    static update(): void {
+        if (!DpiWatcher.callback) {
+            return;
+        }
+
+        const currentDpi = window.devicePixelRatio;
+        const lastDpi = DpiWatcher.lastDpi;
+        DpiWatcher.lastDpi = currentDpi;
+
+        if (Math.abs(lastDpi - currentDpi) > 0.001) {
+            DpiWatcher.callback(lastDpi, currentDpi);
+        }
+    }
+}

+ 149 - 0
src/Web/Avalonia.Web/webapp/modules/avalonia/caretHelper.ts

@@ -0,0 +1,149 @@
+// 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?.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 - http://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;
+    }
+}
+
+const 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;

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

@@ -0,0 +1,60 @@
+export class AvaloniaDOM {
+    public static addClass(element: HTMLElement, className: string): void {
+        element.classList.add(className);
+    }
+
+    static createAvaloniaHost(host: HTMLElement) {
+        // Root element
+        host.classList.add("avalonia-container");
+        host.tabIndex = 0;
+        host.oncontextmenu = function () { return false; };
+
+        // Rendering target canvas
+        const canvas = document.createElement("canvas");
+        canvas.classList.add("avalonia-canvas");
+        canvas.style.backgroundColor = "#ccc";
+        canvas.style.width = "100%";
+        canvas.style.height = "100%";
+        canvas.style.position = "absolute";
+
+        // Native controls host
+        const nativeHost = document.createElement("div");
+        nativeHost.classList.add("avalonia-native-host");
+        nativeHost.style.left = "0px";
+        nativeHost.style.top = "0px";
+        nativeHost.style.width = "100%";
+        nativeHost.style.height = "100%";
+        nativeHost.style.position = "absolute";
+
+        // IME
+        const inputElement = document.createElement("input");
+        inputElement.classList.add("avalonia-input-element");
+        inputElement.autocapitalize = "none";
+        inputElement.type = "text";
+        inputElement.spellcheck = false;
+        inputElement.style.padding = "0";
+        inputElement.style.margin = "0";
+        inputElement.style.position = "absolute";
+        inputElement.style.overflow = "hidden";
+        inputElement.style.borderStyle = "hidden";
+        inputElement.style.outline = "none";
+        inputElement.style.background = "transparent";
+        inputElement.style.color = "transparent";
+        inputElement.style.display = "none";
+        inputElement.style.height = "20px";
+        inputElement.onpaste = function () { return false; };
+        inputElement.oncopy = function () { return false; };
+        inputElement.oncut = function () { return false; };
+
+        host.prepend(inputElement);
+        host.prepend(nativeHost);
+        host.prepend(canvas);
+
+        return {
+            host,
+            canvas,
+            nativeHost,
+            inputElement
+        };
+    }
+}

+ 204 - 0
src/Web/Avalonia.Web/webapp/modules/avalonia/input.ts

@@ -0,0 +1,204 @@
+import { CaretHelper } from "./caretHelper";
+
+enum RawInputModifiers {
+    None = 0,
+    Alt = 1,
+    Control = 2,
+    Shift = 4,
+    Meta = 8,
+
+    LeftMouseButton = 16,
+    RightMouseButton = 32,
+    MiddleMouseButton = 64,
+    XButton1MouseButton = 128,
+    XButton2MouseButton = 256,
+    KeyboardMask = Alt | Control | Shift | Meta,
+
+    PenInverted = 512,
+    PenEraser = 1024,
+    PenBarrelButton = 2048
+}
+
+export class InputHelper {
+    public static subscribeKeyEvents(
+        element: HTMLInputElement,
+        keyDownCallback: (code: string, key: string, modifiers: RawInputModifiers) => boolean,
+        keyUpCallback: (code: string, key: string, modifiers: RawInputModifiers) => boolean) {
+        const keyDownHandler = (args: KeyboardEvent) => {
+            if (keyDownCallback(args.code, args.key, this.getModifiers(args))) {
+                args.preventDefault();
+            }
+        };
+        element.addEventListener("keydown", keyDownHandler);
+
+        const keyUpHandler = (args: KeyboardEvent) => {
+            if (keyUpCallback(args.code, args.key, this.getModifiers(args))) {
+                args.preventDefault();
+            }
+        };
+
+        element.addEventListener("keyup", keyUpHandler);
+
+        return () => {
+            element.removeEventListener("keydown", keyDownHandler);
+            element.removeEventListener("keyup", keyUpHandler);
+        };
+    }
+
+    public static subscribeTextEvents(
+        element: HTMLInputElement,
+        inputCallback: (type: string, data: string | null) => boolean,
+        compositionStartCallback: (args: CompositionEvent) => boolean,
+        compositionUpdateCallback: (args: CompositionEvent) => boolean,
+        compositionEndCallback: (args: CompositionEvent) => boolean) {
+        const inputHandler = (args: Event) => {
+            const inputEvent = args as InputEvent;
+
+            // todo check cast
+            if (inputCallback(inputEvent.type, inputEvent.data)) {
+                args.preventDefault();
+            }
+        };
+        element.addEventListener("input", inputHandler);
+
+        const compositionStartHandler = (args: CompositionEvent) => {
+            if (compositionStartCallback(args)) {
+                args.preventDefault();
+            }
+        };
+        element.addEventListener("compositionstart", compositionStartHandler);
+
+        const compositionUpdateHandler = (args: CompositionEvent) => {
+            if (compositionUpdateCallback(args)) {
+                args.preventDefault();
+            }
+        };
+        element.addEventListener("compositionupdate", compositionUpdateHandler);
+
+        const compositionEndHandler = (args: CompositionEvent) => {
+            if (compositionEndCallback(args)) {
+                args.preventDefault();
+            }
+        };
+        element.addEventListener("compositionend", compositionEndHandler);
+
+        return () => {
+            element.removeEventListener("input", inputHandler);
+            element.removeEventListener("compositionstart", compositionStartHandler);
+            element.removeEventListener("compositionupdate", compositionUpdateHandler);
+            element.removeEventListener("compositionend", compositionEndHandler);
+        };
+    }
+
+    public static subscribePointerEvents(
+        element: HTMLInputElement,
+        pointerMoveCallback: (args: PointerEvent) => boolean,
+        pointerDownCallback: (args: PointerEvent) => boolean,
+        pointerUpCallback: (args: PointerEvent) => boolean,
+        wheelCallback: (args: WheelEvent) => boolean
+    ) {
+        const pointerMoveHandler = (args: PointerEvent) => {
+            if (pointerMoveCallback(args)) {
+                args.preventDefault();
+            }
+        };
+
+        const pointerDownHandler = (args: PointerEvent) => {
+            if (pointerDownCallback(args)) {
+                args.preventDefault();
+            }
+        };
+
+        const pointerUpHandler = (args: PointerEvent) => {
+            if (pointerUpCallback(args)) {
+                args.preventDefault();
+            }
+        };
+
+        const wheelHandler = (args: WheelEvent) => {
+            if (wheelCallback(args)) {
+                args.preventDefault();
+            }
+        };
+
+        element.addEventListener("pointermove", pointerMoveHandler);
+        element.addEventListener("pointerdown", pointerDownHandler);
+        element.addEventListener("pointerup", pointerUpHandler);
+        element.addEventListener("wheel", wheelHandler);
+
+        return () => {
+            element.removeEventListener("pointerover", pointerMoveHandler);
+            element.removeEventListener("pointerdown", pointerDownHandler);
+            element.removeEventListener("pointerup", pointerUpHandler);
+            element.removeEventListener("wheel", wheelHandler);
+        };
+    }
+
+    public static subscribeInputEvents(
+        element: HTMLInputElement,
+        inputCallback: (value: string) => boolean
+    ) {
+        const inputHandler = (args: Event) => {
+            if (inputCallback((args as any).value)) {
+                args.preventDefault();
+            }
+        };
+        element.addEventListener("input", inputHandler);
+
+        return () => {
+            element.removeEventListener("input", inputHandler);
+        };
+    }
+
+    public static clearInput(inputElement: HTMLInputElement) {
+        inputElement.value = "";
+    }
+
+    public static focusElement(inputElement: HTMLElement) {
+        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";
+
+        const { 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 getModifiers(args: KeyboardEvent): RawInputModifiers {
+        let modifiers = RawInputModifiers.None;
+
+        if (args.ctrlKey) { modifiers |= RawInputModifiers.Control; }
+        if (args.altKey) { modifiers |= RawInputModifiers.Alt; }
+        if (args.shiftKey) { modifiers |= RawInputModifiers.Shift; }
+        if (args.metaKey) { modifiers |= RawInputModifiers.Meta; }
+
+        return modifiers;
+    }
+}

+ 55 - 0
src/Web/Avalonia.Web/webapp/modules/avalonia/nativeControlHost.ts

@@ -0,0 +1,55 @@
+class NativeControlHostTopLevelAttachment {
+    _child?: HTMLElement;
+    _host?: HTMLElement;
+}
+
+export class NativeControlHost {
+    public static createDefaultChild(parent?: HTMLElement): HTMLElement {
+        return document.createElement("div");
+    }
+
+    public static createAttachment(): NativeControlHostTopLevelAttachment {
+        return new NativeControlHostTopLevelAttachment();
+    }
+
+    public static initializeWithChildHandle(element: NativeControlHostTopLevelAttachment, child: HTMLElement): void {
+        element._child = child;
+        element._child.style.position = "absolute";
+    }
+
+    public static attachTo(element: NativeControlHostTopLevelAttachment, host?: HTMLElement): void {
+        if (element._host && element._child) {
+            element._host.removeChild(element._child);
+        }
+
+        element._host = host;
+
+        if (element._host && element._child) {
+            element._host.appendChild(element._child);
+        }
+    }
+
+    public static showInBounds(element: NativeControlHostTopLevelAttachment, x: number, y: number, width: number, height: number): void {
+        if (element._child) {
+            element._child.style.top = `${y}px`;
+            element._child.style.left = `${x}px`;
+            element._child.style.width = `${width}px`;
+            element._child.style.height = `${height}px`;
+            element._child.style.display = "block";
+        }
+    }
+
+    public static hideWithSize(element: NativeControlHostTopLevelAttachment, width: number, height: number): void {
+        if (element._child) {
+            element._child.style.width = `${width}px`;
+            element._child.style.height = `${height}px`;
+            element._child.style.display = "none";
+        }
+    }
+
+    public static releaseChild(element: NativeControlHostTopLevelAttachment): void {
+        if (element._child) {
+            element._child = undefined;
+        }
+    }
+}

+ 40 - 0
src/Web/Avalonia.Web/webapp/modules/avalonia/stream.ts

@@ -0,0 +1,40 @@
+import { IMemoryView } from "../../types/dotnet";
+
+export class StreamHelper {
+    public static async seek(stream: FileSystemWritableFileStream, position: number) {
+        return await stream.seek(position);
+    }
+
+    public static async truncate(stream: FileSystemWritableFileStream, size: number) {
+        return await stream.truncate(size);
+    }
+
+    public static async close(stream: FileSystemWritableFileStream) {
+        return await stream.close();
+    }
+
+    public static async write(stream: FileSystemWritableFileStream, span: IMemoryView) {
+        const array = new Uint8Array(span.byteLength);
+        span.copyTo(array);
+
+        const data: WriteParams = {
+            type: "write",
+            data: array
+        };
+
+        return await stream.write(data);
+    }
+
+    public static byteLength(stream: Blob) {
+        return stream.size;
+    }
+
+    public static async sliceArrayBuffer(stream: Blob, offset: number, count: number) {
+        const buffer = await stream.slice(offset, offset + count).arrayBuffer();
+        return new Uint8Array(buffer);
+    }
+
+    public static toMemoryView(buffer: Uint8Array): Uint8Array {
+        return buffer;
+    }
+}

+ 2 - 0
src/Web/Avalonia.Web/webapp/modules/storage.ts

@@ -0,0 +1,2 @@
+export { StorageItem, StorageItems } from "./storage/storageItem";
+export { StorageProvider } from "./storage/storageProvider";

+ 84 - 0
src/Web/Avalonia.Web/webapp/modules/storage/indexedDb.ts

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

+ 111 - 0
src/Web/Avalonia.Web/webapp/modules/storage/storageItem.ts

@@ -0,0 +1,111 @@
+import { avaloniaDb, fileBookmarksStore } from "./indexedDb";
+
+export class StorageItem {
+    constructor(public handle: FileSystemHandle, private readonly bookmarkId?: string) { }
+
+    public get name(): string {
+        return this.handle.name;
+    }
+
+    public get kind(): string {
+        return this.handle.kind;
+    }
+
+    public static async openRead(item: StorageItem): Promise<Blob> {
+        if (!(item.handle instanceof FileSystemFileHandle)) {
+            throw new Error("StorageItem is not a file");
+        }
+
+        await item.verityPermissions("read");
+
+        const file = await item.handle.getFile();
+        return file;
+    }
+
+    public static async openWrite(item: StorageItem): Promise<FileSystemWritableFileStream> {
+        if (!(item.handle instanceof FileSystemFileHandle)) {
+            throw new Error("StorageItem is not a file");
+        }
+
+        await item.verityPermissions("readwrite");
+
+        return await item.handle.createWritable({ keepExistingData: true });
+    }
+
+    public static async getProperties(item: StorageItem): Promise<{ Size: number; LastModified: number; Type: string } | null> {
+        const file = item.handle instanceof FileSystemFileHandle &&
+            await item.handle.getFile();
+
+        if (!file) {
+            return null;
+        }
+
+        return {
+            Size: file.size,
+            LastModified: file.lastModified,
+            Type: file.type
+        };
+    }
+
+    public static async getItems(item: StorageItem): Promise<StorageItems> {
+        if (item.handle.kind !== "directory") {
+            return new StorageItems([]);
+        }
+
+        const items: StorageItem[] = [];
+        for await (const [, value] of (item.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("Permissions denied");
+        }
+    }
+
+    public static async saveBookmark(item: StorageItem): Promise<string> {
+        // If file was previously bookmarked, just return old one.
+        if (item.bookmarkId) {
+            return item.bookmarkId;
+        }
+
+        const connection = await avaloniaDb.connect();
+        try {
+            const key = await connection.put(fileBookmarksStore, item.handle, item.generateBookmarkId());
+            return key as string;
+        } finally {
+            connection.close();
+        }
+    }
+
+    public static async deleteBookmark(item: StorageItem): Promise<void> {
+        if (!item.bookmarkId) {
+            return;
+        }
+
+        const connection = await avaloniaDb.connect();
+        try {
+            await connection.delete(fileBookmarksStore, item.bookmarkId);
+        } finally {
+            connection.close();
+        }
+    }
+
+    private generateBookmarkId(): string {
+        return Date.now().toString(36) + Math.random().toString(36).substring(2);
+    }
+}
+
+export class StorageItems {
+    constructor(private readonly items: StorageItem[]) { }
+
+    public static itemsArray(instance: StorageItems): StorageItem[] {
+        return instance.items;
+    }
+}

+ 70 - 0
src/Web/Avalonia.Web/webapp/modules/storage/storageProvider.ts

@@ -0,0 +1,70 @@
+import { avaloniaDb, fileBookmarksStore } from "./indexedDb";
+import { StorageItem, StorageItems } from "./storageItem";
+
+declare global {
+    type WellKnownDirectory = "desktop" | "documents" | "downloads" | "music" | "pictures" | "videos";
+    type StartInDirectory = WellKnownDirectory | FileSystemHandle;
+    interface OpenFilePickerOptions {
+        startIn?: StartInDirectory;
+    }
+    interface SaveFilePickerOptions {
+        startIn?: StartInDirectory;
+    }
+}
+
+export class StorageProvider {
+    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();
+        }
+    }
+
+    public static createAcceptType(description: string, mimeTypes: string[]): FilePickerAcceptType {
+        const accept: Record<string, string[]> = {};
+        mimeTypes.forEach(a => { accept[a] = []; });
+        return { description, accept };
+    }
+}

+ 2234 - 0
src/Web/Avalonia.Web/webapp/package-lock.json

@@ -0,0 +1,2234 @@
+{
+  "name": "avalonia.web",
+  "requires": true,
+  "lockfileVersion": 1,
+  "dependencies": {
+    "@esbuild/android-arm": {
+      "version": "0.15.9",
+      "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.15.9.tgz",
+      "integrity": "sha512-VZPy/ETF3fBG5PiinIkA0W/tlsvlEgJccyN2DzWZEl0DlVKRbu91PvY2D6Lxgluj4w9QtYHjOWjAT44C+oQ+EQ==",
+      "dev": true,
+      "optional": true
+    },
+    "@esbuild/linux-loong64": {
+      "version": "0.15.9",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.15.9.tgz",
+      "integrity": "sha512-O+NfmkfRrb3uSsTa4jE3WApidSe3N5++fyOVGP1SmMZi4A3BZELkhUUvj5hwmMuNdlpzAZ8iAPz2vmcR7DCFQA==",
+      "dev": true,
+      "optional": true
+    },
+    "@eslint/eslintrc": {
+      "version": "1.3.2",
+      "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.3.2.tgz",
+      "integrity": "sha512-AXYd23w1S/bv3fTs3Lz0vjiYemS08jWkI3hYyS9I1ry+0f+Yjs1wm+sU0BS8qDOPrBIkp4qHYC16I8uVtpLajQ==",
+      "dev": true,
+      "requires": {
+        "ajv": "^6.12.4",
+        "debug": "^4.3.2",
+        "espree": "^9.4.0",
+        "globals": "^13.15.0",
+        "ignore": "^5.2.0",
+        "import-fresh": "^3.2.1",
+        "js-yaml": "^4.1.0",
+        "minimatch": "^3.1.2",
+        "strip-json-comments": "^3.1.1"
+      }
+    },
+    "@humanwhocodes/config-array": {
+      "version": "0.10.5",
+      "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.10.5.tgz",
+      "integrity": "sha512-XVVDtp+dVvRxMoxSiSfasYaG02VEe1qH5cKgMQJWhol6HwzbcqoCMJi8dAGoYAO57jhUyhI6cWuRiTcRaDaYug==",
+      "dev": true,
+      "requires": {
+        "@humanwhocodes/object-schema": "^1.2.1",
+        "debug": "^4.1.1",
+        "minimatch": "^3.0.4"
+      }
+    },
+    "@humanwhocodes/gitignore-to-minimatch": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/@humanwhocodes/gitignore-to-minimatch/-/gitignore-to-minimatch-1.0.2.tgz",
+      "integrity": "sha512-rSqmMJDdLFUsyxR6FMtD00nfQKKLFb1kv+qBbOVKqErvloEIJLo5bDTJTQNTYgeyp78JsA7u/NPi5jT1GR/MuA==",
+      "dev": true
+    },
+    "@humanwhocodes/module-importer": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz",
+      "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==",
+      "dev": true
+    },
+    "@humanwhocodes/object-schema": {
+      "version": "1.2.1",
+      "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz",
+      "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==",
+      "dev": true
+    },
+    "@nodelib/fs.scandir": {
+      "version": "2.1.5",
+      "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
+      "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==",
+      "dev": true,
+      "requires": {
+        "@nodelib/fs.stat": "2.0.5",
+        "run-parallel": "^1.1.9"
+      }
+    },
+    "@nodelib/fs.stat": {
+      "version": "2.0.5",
+      "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz",
+      "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==",
+      "dev": true
+    },
+    "@nodelib/fs.walk": {
+      "version": "1.2.8",
+      "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz",
+      "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==",
+      "dev": true,
+      "requires": {
+        "@nodelib/fs.scandir": "2.1.5",
+        "fastq": "^1.6.0"
+      }
+    },
+    "@types/emscripten": {
+      "version": "1.39.6",
+      "resolved": "https://registry.npmjs.org/@types/emscripten/-/emscripten-1.39.6.tgz",
+      "integrity": "sha512-H90aoynNhhkQP6DRweEjJp5vfUVdIj7tdPLsu7pq89vODD/lcugKfZOsfgwpvM6XUewEp2N5dCg1Uf3Qe55Dcg==",
+      "dev": true
+    },
+    "@types/json-schema": {
+      "version": "7.0.11",
+      "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz",
+      "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==",
+      "dev": true
+    },
+    "@types/json5": {
+      "version": "0.0.29",
+      "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz",
+      "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==",
+      "dev": true
+    },
+    "@types/wicg-file-system-access": {
+      "version": "2020.9.5",
+      "resolved": "https://registry.npmjs.org/@types/wicg-file-system-access/-/wicg-file-system-access-2020.9.5.tgz",
+      "integrity": "sha512-UYK244awtmcUYQfs7FR8710MJcefL2WvkyHMjA8yJzxd1mo0Gfn88sRZ1Bls7hiUhA2w7ne1gpJ9T5g3G0wOyA==",
+      "dev": true
+    },
+    "@typescript-eslint/eslint-plugin": {
+      "version": "5.38.1",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.38.1.tgz",
+      "integrity": "sha512-ky7EFzPhqz3XlhS7vPOoMDaQnQMn+9o5ICR9CPr/6bw8HrFkzhMSxuA3gRfiJVvs7geYrSeawGJjZoZQKCOglQ==",
+      "dev": true,
+      "requires": {
+        "@typescript-eslint/scope-manager": "5.38.1",
+        "@typescript-eslint/type-utils": "5.38.1",
+        "@typescript-eslint/utils": "5.38.1",
+        "debug": "^4.3.4",
+        "ignore": "^5.2.0",
+        "regexpp": "^3.2.0",
+        "semver": "^7.3.7",
+        "tsutils": "^3.21.0"
+      }
+    },
+    "@typescript-eslint/parser": {
+      "version": "5.38.1",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.38.1.tgz",
+      "integrity": "sha512-LDqxZBVFFQnQRz9rUZJhLmox+Ep5kdUmLatLQnCRR6523YV+XhRjfYzStQ4MheFA8kMAfUlclHSbu+RKdRwQKw==",
+      "dev": true,
+      "requires": {
+        "@typescript-eslint/scope-manager": "5.38.1",
+        "@typescript-eslint/types": "5.38.1",
+        "@typescript-eslint/typescript-estree": "5.38.1",
+        "debug": "^4.3.4"
+      }
+    },
+    "@typescript-eslint/scope-manager": {
+      "version": "5.38.1",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.38.1.tgz",
+      "integrity": "sha512-BfRDq5RidVU3RbqApKmS7RFMtkyWMM50qWnDAkKgQiezRtLKsoyRKIvz1Ok5ilRWeD9IuHvaidaLxvGx/2eqTQ==",
+      "dev": true,
+      "requires": {
+        "@typescript-eslint/types": "5.38.1",
+        "@typescript-eslint/visitor-keys": "5.38.1"
+      }
+    },
+    "@typescript-eslint/type-utils": {
+      "version": "5.38.1",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.38.1.tgz",
+      "integrity": "sha512-UU3j43TM66gYtzo15ivK2ZFoDFKKP0k03MItzLdq0zV92CeGCXRfXlfQX5ILdd4/DSpHkSjIgLLLh1NtkOJOAw==",
+      "dev": true,
+      "requires": {
+        "@typescript-eslint/typescript-estree": "5.38.1",
+        "@typescript-eslint/utils": "5.38.1",
+        "debug": "^4.3.4",
+        "tsutils": "^3.21.0"
+      }
+    },
+    "@typescript-eslint/types": {
+      "version": "5.38.1",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.38.1.tgz",
+      "integrity": "sha512-QTW1iHq1Tffp9lNfbfPm4WJabbvpyaehQ0SrvVK2yfV79SytD9XDVxqiPvdrv2LK7DGSFo91TB2FgWanbJAZXg==",
+      "dev": true
+    },
+    "@typescript-eslint/typescript-estree": {
+      "version": "5.38.1",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.38.1.tgz",
+      "integrity": "sha512-99b5e/Enoe8fKMLdSuwrfH/C0EIbpUWmeEKHmQlGZb8msY33qn1KlkFww0z26o5Omx7EVjzVDCWEfrfCDHfE7g==",
+      "dev": true,
+      "requires": {
+        "@typescript-eslint/types": "5.38.1",
+        "@typescript-eslint/visitor-keys": "5.38.1",
+        "debug": "^4.3.4",
+        "globby": "^11.1.0",
+        "is-glob": "^4.0.3",
+        "semver": "^7.3.7",
+        "tsutils": "^3.21.0"
+      }
+    },
+    "@typescript-eslint/utils": {
+      "version": "5.38.1",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.38.1.tgz",
+      "integrity": "sha512-oIuUiVxPBsndrN81oP8tXnFa/+EcZ03qLqPDfSZ5xIJVm7A9V0rlkQwwBOAGtrdN70ZKDlKv+l1BeT4eSFxwXA==",
+      "dev": true,
+      "requires": {
+        "@types/json-schema": "^7.0.9",
+        "@typescript-eslint/scope-manager": "5.38.1",
+        "@typescript-eslint/types": "5.38.1",
+        "@typescript-eslint/typescript-estree": "5.38.1",
+        "eslint-scope": "^5.1.1",
+        "eslint-utils": "^3.0.0"
+      }
+    },
+    "@typescript-eslint/visitor-keys": {
+      "version": "5.38.1",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.38.1.tgz",
+      "integrity": "sha512-bSHr1rRxXt54+j2n4k54p4fj8AHJ49VDWtjpImOpzQj4qjAiOpPni+V1Tyajh19Api1i844F757cur8wH3YvOA==",
+      "dev": true,
+      "requires": {
+        "@typescript-eslint/types": "5.38.1",
+        "eslint-visitor-keys": "^3.3.0"
+      }
+    },
+    "acorn": {
+      "version": "8.8.0",
+      "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.0.tgz",
+      "integrity": "sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w==",
+      "dev": true
+    },
+    "acorn-jsx": {
+      "version": "5.3.2",
+      "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
+      "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==",
+      "dev": true
+    },
+    "ajv": {
+      "version": "6.12.6",
+      "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
+      "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
+      "dev": true,
+      "requires": {
+        "fast-deep-equal": "^3.1.1",
+        "fast-json-stable-stringify": "^2.0.0",
+        "json-schema-traverse": "^0.4.1",
+        "uri-js": "^4.2.2"
+      }
+    },
+    "ansi-regex": {
+      "version": "5.0.1",
+      "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+      "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+      "dev": true
+    },
+    "ansi-styles": {
+      "version": "4.3.0",
+      "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+      "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+      "dev": true,
+      "requires": {
+        "color-convert": "^2.0.1"
+      }
+    },
+    "argparse": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
+      "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
+      "dev": true
+    },
+    "array-includes": {
+      "version": "3.1.5",
+      "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.5.tgz",
+      "integrity": "sha512-iSDYZMMyTPkiFasVqfuAQnWAYcvO/SeBSCGKePoEthjp4LEMTe4uLc7b025o4jAZpHhihh8xPo99TNWUWWkGDQ==",
+      "dev": true,
+      "requires": {
+        "call-bind": "^1.0.2",
+        "define-properties": "^1.1.4",
+        "es-abstract": "^1.19.5",
+        "get-intrinsic": "^1.1.1",
+        "is-string": "^1.0.7"
+      }
+    },
+    "array-union": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz",
+      "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==",
+      "dev": true
+    },
+    "array.prototype.flat": {
+      "version": "1.3.0",
+      "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.0.tgz",
+      "integrity": "sha512-12IUEkHsAhA4DY5s0FPgNXIdc8VRSqD9Zp78a5au9abH/SOBrsp082JOWFNTjkMozh8mqcdiKuaLGhPeYztxSw==",
+      "dev": true,
+      "requires": {
+        "call-bind": "^1.0.2",
+        "define-properties": "^1.1.3",
+        "es-abstract": "^1.19.2",
+        "es-shim-unscopables": "^1.0.0"
+      }
+    },
+    "balanced-match": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
+      "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
+      "dev": true
+    },
+    "brace-expansion": {
+      "version": "1.1.11",
+      "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
+      "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
+      "dev": true,
+      "requires": {
+        "balanced-match": "^1.0.0",
+        "concat-map": "0.0.1"
+      }
+    },
+    "braces": {
+      "version": "3.0.2",
+      "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
+      "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
+      "dev": true,
+      "requires": {
+        "fill-range": "^7.0.1"
+      }
+    },
+    "builtins": {
+      "version": "5.0.1",
+      "resolved": "https://registry.npmjs.org/builtins/-/builtins-5.0.1.tgz",
+      "integrity": "sha512-qwVpFEHNfhYJIzNRBvd2C1kyo6jz3ZSMPyyuR47OPdiKWlbYnZNyDWuyR175qDnAJLiCo5fBBqPb3RiXgWlkOQ==",
+      "dev": true,
+      "requires": {
+        "semver": "^7.0.0"
+      }
+    },
+    "call-bind": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz",
+      "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==",
+      "dev": true,
+      "requires": {
+        "function-bind": "^1.1.1",
+        "get-intrinsic": "^1.0.2"
+      }
+    },
+    "callsites": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
+      "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
+      "dev": true
+    },
+    "chalk": {
+      "version": "4.1.2",
+      "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
+      "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+      "dev": true,
+      "requires": {
+        "ansi-styles": "^4.1.0",
+        "supports-color": "^7.1.0"
+      }
+    },
+    "color-convert": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+      "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+      "dev": true,
+      "requires": {
+        "color-name": "~1.1.4"
+      }
+    },
+    "color-name": {
+      "version": "1.1.4",
+      "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+      "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+      "dev": true
+    },
+    "concat-map": {
+      "version": "0.0.1",
+      "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
+      "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
+      "dev": true
+    },
+    "cross-spawn": {
+      "version": "7.0.3",
+      "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
+      "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==",
+      "dev": true,
+      "requires": {
+        "path-key": "^3.1.0",
+        "shebang-command": "^2.0.0",
+        "which": "^2.0.1"
+      }
+    },
+    "debug": {
+      "version": "4.3.4",
+      "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
+      "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
+      "dev": true,
+      "requires": {
+        "ms": "2.1.2"
+      }
+    },
+    "deep-is": {
+      "version": "0.1.4",
+      "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
+      "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==",
+      "dev": true
+    },
+    "define-properties": {
+      "version": "1.1.4",
+      "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.4.tgz",
+      "integrity": "sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA==",
+      "dev": true,
+      "requires": {
+        "has-property-descriptors": "^1.0.0",
+        "object-keys": "^1.1.1"
+      }
+    },
+    "dir-glob": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz",
+      "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==",
+      "dev": true,
+      "requires": {
+        "path-type": "^4.0.0"
+      }
+    },
+    "doctrine": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz",
+      "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==",
+      "dev": true,
+      "requires": {
+        "esutils": "^2.0.2"
+      }
+    },
+    "error-ex": {
+      "version": "1.3.2",
+      "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz",
+      "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==",
+      "dev": true,
+      "requires": {
+        "is-arrayish": "^0.2.1"
+      }
+    },
+    "es-abstract": {
+      "version": "1.20.3",
+      "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.20.3.tgz",
+      "integrity": "sha512-AyrnaKVpMzljIdwjzrj+LxGmj8ik2LckwXacHqrJJ/jxz6dDDBcZ7I7nlHM0FvEW8MfbWJwOd+yT2XzYW49Frw==",
+      "dev": true,
+      "requires": {
+        "call-bind": "^1.0.2",
+        "es-to-primitive": "^1.2.1",
+        "function-bind": "^1.1.1",
+        "function.prototype.name": "^1.1.5",
+        "get-intrinsic": "^1.1.3",
+        "get-symbol-description": "^1.0.0",
+        "has": "^1.0.3",
+        "has-property-descriptors": "^1.0.0",
+        "has-symbols": "^1.0.3",
+        "internal-slot": "^1.0.3",
+        "is-callable": "^1.2.6",
+        "is-negative-zero": "^2.0.2",
+        "is-regex": "^1.1.4",
+        "is-shared-array-buffer": "^1.0.2",
+        "is-string": "^1.0.7",
+        "is-weakref": "^1.0.2",
+        "object-inspect": "^1.12.2",
+        "object-keys": "^1.1.1",
+        "object.assign": "^4.1.4",
+        "regexp.prototype.flags": "^1.4.3",
+        "safe-regex-test": "^1.0.0",
+        "string.prototype.trimend": "^1.0.5",
+        "string.prototype.trimstart": "^1.0.5",
+        "unbox-primitive": "^1.0.2"
+      }
+    },
+    "es-shim-unscopables": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.0.tgz",
+      "integrity": "sha512-Jm6GPcCdC30eMLbZ2x8z2WuRwAws3zTBBKuusffYVUrNj/GVSUAZ+xKMaUpfNDR5IbyNA5LJbaecoUVbmUcB1w==",
+      "dev": true,
+      "requires": {
+        "has": "^1.0.3"
+      }
+    },
+    "es-to-primitive": {
+      "version": "1.2.1",
+      "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz",
+      "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==",
+      "dev": true,
+      "requires": {
+        "is-callable": "^1.1.4",
+        "is-date-object": "^1.0.1",
+        "is-symbol": "^1.0.2"
+      }
+    },
+    "esbuild": {
+      "version": "0.15.9",
+      "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.15.9.tgz",
+      "integrity": "sha512-OnYr1rkMVxtmMHIAKZLMcEUlJmqcbxBz9QoBU8G9v455na0fuzlT/GLu6l+SRghrk0Mm2fSSciMmzV43Q8e0Gg==",
+      "dev": true,
+      "requires": {
+        "@esbuild/android-arm": "0.15.9",
+        "@esbuild/linux-loong64": "0.15.9",
+        "esbuild-android-64": "0.15.9",
+        "esbuild-android-arm64": "0.15.9",
+        "esbuild-darwin-64": "0.15.9",
+        "esbuild-darwin-arm64": "0.15.9",
+        "esbuild-freebsd-64": "0.15.9",
+        "esbuild-freebsd-arm64": "0.15.9",
+        "esbuild-linux-32": "0.15.9",
+        "esbuild-linux-64": "0.15.9",
+        "esbuild-linux-arm": "0.15.9",
+        "esbuild-linux-arm64": "0.15.9",
+        "esbuild-linux-mips64le": "0.15.9",
+        "esbuild-linux-ppc64le": "0.15.9",
+        "esbuild-linux-riscv64": "0.15.9",
+        "esbuild-linux-s390x": "0.15.9",
+        "esbuild-netbsd-64": "0.15.9",
+        "esbuild-openbsd-64": "0.15.9",
+        "esbuild-sunos-64": "0.15.9",
+        "esbuild-windows-32": "0.15.9",
+        "esbuild-windows-64": "0.15.9",
+        "esbuild-windows-arm64": "0.15.9"
+      }
+    },
+    "esbuild-android-64": {
+      "version": "0.15.9",
+      "resolved": "https://registry.npmjs.org/esbuild-android-64/-/esbuild-android-64-0.15.9.tgz",
+      "integrity": "sha512-HQCX7FJn9T4kxZQkhPjNZC7tBWZqJvhlLHPU2SFzrQB/7nDXjmTIFpFTjt7Bd1uFpeXmuwf5h5fZm+x/hLnhbw==",
+      "dev": true,
+      "optional": true
+    },
+    "esbuild-android-arm64": {
+      "version": "0.15.9",
+      "resolved": "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.15.9.tgz",
+      "integrity": "sha512-E6zbLfqbFVCNEKircSHnPiSTsm3fCRxeIMPfrkS33tFjIAoXtwegQfVZqMGR0FlsvVxp2NEDOUz+WW48COCjSg==",
+      "dev": true,
+      "optional": true
+    },
+    "esbuild-darwin-64": {
+      "version": "0.15.9",
+      "resolved": "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.15.9.tgz",
+      "integrity": "sha512-gI7dClcDN/HHVacZhTmGjl0/TWZcGuKJ0I7/xDGJwRQQn7aafZGtvagOFNmuOq+OBFPhlPv1T6JElOXb0unkSQ==",
+      "dev": true,
+      "optional": true
+    },
+    "esbuild-darwin-arm64": {
+      "version": "0.15.9",
+      "resolved": "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.15.9.tgz",
+      "integrity": "sha512-VZIMlcRN29yg/sv7DsDwN+OeufCcoTNaTl3Vnav7dL/nvsApD7uvhVRbgyMzv0zU/PP0xRhhIpTyc7lxEzHGSw==",
+      "dev": true,
+      "optional": true
+    },
+    "esbuild-freebsd-64": {
+      "version": "0.15.9",
+      "resolved": "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.15.9.tgz",
+      "integrity": "sha512-uM4z5bTvuAXqPxrI204txhlsPIolQPWRMLenvGuCPZTnnGlCMF2QLs0Plcm26gcskhxewYo9LkkmYSS5Czrb5A==",
+      "dev": true,
+      "optional": true
+    },
+    "esbuild-freebsd-arm64": {
+      "version": "0.15.9",
+      "resolved": "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.15.9.tgz",
+      "integrity": "sha512-HHDjT3O5gWzicGdgJ5yokZVN9K9KG05SnERwl9nBYZaCjcCgj/sX8Ps1jvoFSfNCO04JSsHSOWo4qvxFuj8FoA==",
+      "dev": true,
+      "optional": true
+    },
+    "esbuild-linux-32": {
+      "version": "0.15.9",
+      "resolved": "https://registry.npmjs.org/esbuild-linux-32/-/esbuild-linux-32-0.15.9.tgz",
+      "integrity": "sha512-AQIdE8FugGt1DkcekKi5ycI46QZpGJ/wqcMr7w6YUmOmp2ohQ8eO4sKUsOxNOvYL7hGEVwkndSyszR6HpVHLFg==",
+      "dev": true,
+      "optional": true
+    },
+    "esbuild-linux-64": {
+      "version": "0.15.9",
+      "resolved": "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.15.9.tgz",
+      "integrity": "sha512-4RXjae7g6Qs7StZyiYyXTZXBlfODhb1aBVAjd+ANuPmMhWthQilWo7rFHwJwL7DQu1Fjej2sODAVwLbcIVsAYQ==",
+      "dev": true,
+      "optional": true
+    },
+    "esbuild-linux-arm": {
+      "version": "0.15.9",
+      "resolved": "https://registry.npmjs.org/esbuild-linux-arm/-/esbuild-linux-arm-0.15.9.tgz",
+      "integrity": "sha512-3Zf2GVGUOI7XwChH3qrnTOSqfV1V4CAc/7zLVm4lO6JT6wbJrTgEYCCiNSzziSju+J9Jhf9YGWk/26quWPC6yQ==",
+      "dev": true,
+      "optional": true
+    },
+    "esbuild-linux-arm64": {
+      "version": "0.15.9",
+      "resolved": "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.15.9.tgz",
+      "integrity": "sha512-a+bTtxJmYmk9d+s2W4/R1SYKDDAldOKmWjWP0BnrWtDbvUBNOm++du0ysPju4mZVoEFgS1yLNW+VXnG/4FNwdQ==",
+      "dev": true,
+      "optional": true
+    },
+    "esbuild-linux-mips64le": {
+      "version": "0.15.9",
+      "resolved": "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.15.9.tgz",
+      "integrity": "sha512-Zn9HSylDp89y+TRREMDoGrc3Z4Hs5u56ozZLQCiZAUx2+HdbbXbWdjmw3FdTJ/i7t5Cew6/Q+6kfO3KCcFGlyw==",
+      "dev": true,
+      "optional": true
+    },
+    "esbuild-linux-ppc64le": {
+      "version": "0.15.9",
+      "resolved": "https://registry.npmjs.org/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.15.9.tgz",
+      "integrity": "sha512-OEiOxNAMH9ENFYqRsWUj3CWyN3V8P3ZXyfNAtX5rlCEC/ERXrCEFCJji/1F6POzsXAzxvUJrTSTCy7G6BhA6Fw==",
+      "dev": true,
+      "optional": true
+    },
+    "esbuild-linux-riscv64": {
+      "version": "0.15.9",
+      "resolved": "https://registry.npmjs.org/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.15.9.tgz",
+      "integrity": "sha512-ukm4KsC3QRausEFjzTsOZ/qqazw0YvJsKmfoZZm9QW27OHjk2XKSQGGvx8gIEswft/Sadp03/VZvAaqv5AIwNA==",
+      "dev": true,
+      "optional": true
+    },
+    "esbuild-linux-s390x": {
+      "version": "0.15.9",
+      "resolved": "https://registry.npmjs.org/esbuild-linux-s390x/-/esbuild-linux-s390x-0.15.9.tgz",
+      "integrity": "sha512-uDOQEH55wQ6ahcIKzQr3VyjGc6Po/xblLGLoUk3fVL1qjlZAibtQr6XRfy5wPJLu/M2o0vQKLq4lyJ2r1tWKcw==",
+      "dev": true,
+      "optional": true
+    },
+    "esbuild-netbsd-64": {
+      "version": "0.15.9",
+      "resolved": "https://registry.npmjs.org/esbuild-netbsd-64/-/esbuild-netbsd-64-0.15.9.tgz",
+      "integrity": "sha512-yWgxaYTQz+TqX80wXRq6xAtb7GSBAp6gqLKfOdANg9qEmAI1Bxn04IrQr0Mzm4AhxvGKoHzjHjMgXbCCSSDxcw==",
+      "dev": true,
+      "optional": true
+    },
+    "esbuild-openbsd-64": {
+      "version": "0.15.9",
+      "resolved": "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.15.9.tgz",
+      "integrity": "sha512-JmS18acQl4iSAjrEha1MfEmUMN4FcnnrtTaJ7Qg0tDCOcgpPPQRLGsZqhes0vmx8VA6IqRyScqXvaL7+Q0Uf3A==",
+      "dev": true,
+      "optional": true
+    },
+    "esbuild-sunos-64": {
+      "version": "0.15.9",
+      "resolved": "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.15.9.tgz",
+      "integrity": "sha512-UKynGSWpzkPmXW3D2UMOD9BZPIuRaSqphxSCwScfEE05Be3KAmvjsBhht1fLzKpiFVJb0BYMd4jEbWMyJ/z1hQ==",
+      "dev": true,
+      "optional": true
+    },
+    "esbuild-windows-32": {
+      "version": "0.15.9",
+      "resolved": "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.15.9.tgz",
+      "integrity": "sha512-aqXvu4/W9XyTVqO/hw3rNxKE1TcZiEYHPsXM9LwYmKSX9/hjvfIJzXwQBlPcJ/QOxedfoMVH0YnhhQ9Ffb0RGA==",
+      "dev": true,
+      "optional": true
+    },
+    "esbuild-windows-64": {
+      "version": "0.15.9",
+      "resolved": "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.15.9.tgz",
+      "integrity": "sha512-zm7h91WUmlS4idMtjvCrEeNhlH7+TNOmqw5dJPJZrgFaxoFyqYG6CKDpdFCQXdyKpD5yvzaQBOMVTCBVKGZDEg==",
+      "dev": true,
+      "optional": true
+    },
+    "esbuild-windows-arm64": {
+      "version": "0.15.9",
+      "resolved": "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.15.9.tgz",
+      "integrity": "sha512-yQEVIv27oauAtvtuhJVfSNMztJJX47ismRS6Sv2QMVV9RM+6xjbMWuuwM2nxr5A2/gj/mu2z9YlQxiwoFRCfZA==",
+      "dev": true,
+      "optional": true
+    },
+    "escape-string-regexp": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
+      "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
+      "dev": true
+    },
+    "eslint": {
+      "version": "8.24.0",
+      "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.24.0.tgz",
+      "integrity": "sha512-dWFaPhGhTAiPcCgm3f6LI2MBWbogMnTJzFBbhXVRQDJPkr9pGZvVjlVfXd+vyDcWPA2Ic9L2AXPIQM0+vk/cSQ==",
+      "dev": true,
+      "requires": {
+        "@eslint/eslintrc": "^1.3.2",
+        "@humanwhocodes/config-array": "^0.10.5",
+        "@humanwhocodes/gitignore-to-minimatch": "^1.0.2",
+        "@humanwhocodes/module-importer": "^1.0.1",
+        "ajv": "^6.10.0",
+        "chalk": "^4.0.0",
+        "cross-spawn": "^7.0.2",
+        "debug": "^4.3.2",
+        "doctrine": "^3.0.0",
+        "escape-string-regexp": "^4.0.0",
+        "eslint-scope": "^7.1.1",
+        "eslint-utils": "^3.0.0",
+        "eslint-visitor-keys": "^3.3.0",
+        "espree": "^9.4.0",
+        "esquery": "^1.4.0",
+        "esutils": "^2.0.2",
+        "fast-deep-equal": "^3.1.3",
+        "file-entry-cache": "^6.0.1",
+        "find-up": "^5.0.0",
+        "glob-parent": "^6.0.1",
+        "globals": "^13.15.0",
+        "globby": "^11.1.0",
+        "grapheme-splitter": "^1.0.4",
+        "ignore": "^5.2.0",
+        "import-fresh": "^3.0.0",
+        "imurmurhash": "^0.1.4",
+        "is-glob": "^4.0.0",
+        "js-sdsl": "^4.1.4",
+        "js-yaml": "^4.1.0",
+        "json-stable-stringify-without-jsonify": "^1.0.1",
+        "levn": "^0.4.1",
+        "lodash.merge": "^4.6.2",
+        "minimatch": "^3.1.2",
+        "natural-compare": "^1.4.0",
+        "optionator": "^0.9.1",
+        "regexpp": "^3.2.0",
+        "strip-ansi": "^6.0.1",
+        "strip-json-comments": "^3.1.0",
+        "text-table": "^0.2.0"
+      },
+      "dependencies": {
+        "eslint-scope": {
+          "version": "7.1.1",
+          "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz",
+          "integrity": "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==",
+          "dev": true,
+          "requires": {
+            "esrecurse": "^4.3.0",
+            "estraverse": "^5.2.0"
+          }
+        },
+        "estraverse": {
+          "version": "5.3.0",
+          "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
+          "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
+          "dev": true
+        },
+        "glob-parent": {
+          "version": "6.0.2",
+          "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
+          "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
+          "dev": true,
+          "requires": {
+            "is-glob": "^4.0.3"
+          }
+        }
+      }
+    },
+    "eslint-config-standard": {
+      "version": "17.0.0",
+      "resolved": "https://registry.npmjs.org/eslint-config-standard/-/eslint-config-standard-17.0.0.tgz",
+      "integrity": "sha512-/2ks1GKyqSOkH7JFvXJicu0iMpoojkwB+f5Du/1SC0PtBL+s8v30k9njRZ21pm2drKYm2342jFnGWzttxPmZVg==",
+      "dev": true
+    },
+    "eslint-config-standard-with-typescript": {
+      "version": "23.0.0",
+      "resolved": "https://registry.npmjs.org/eslint-config-standard-with-typescript/-/eslint-config-standard-with-typescript-23.0.0.tgz",
+      "integrity": "sha512-iaaWifImn37Z1OXbNW1es7KI+S7D408F9ys0bpaQf2temeBWlvb0Nc5qHkOgYaRb5QxTZT32GGeN1gtswASOXA==",
+      "dev": true,
+      "requires": {
+        "@typescript-eslint/parser": "^5.0.0",
+        "eslint-config-standard": "17.0.0"
+      }
+    },
+    "eslint-import-resolver-node": {
+      "version": "0.3.6",
+      "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.6.tgz",
+      "integrity": "sha512-0En0w03NRVMn9Uiyn8YRPDKvWjxCWkslUEhGNTdGx15RvPJYQ+lbOlqrlNI2vEAs4pDYK4f/HN2TbDmk5TP0iw==",
+      "dev": true,
+      "requires": {
+        "debug": "^3.2.7",
+        "resolve": "^1.20.0"
+      },
+      "dependencies": {
+        "debug": {
+          "version": "3.2.7",
+          "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz",
+          "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==",
+          "dev": true,
+          "requires": {
+            "ms": "^2.1.1"
+          }
+        }
+      }
+    },
+    "eslint-module-utils": {
+      "version": "2.7.4",
+      "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.7.4.tgz",
+      "integrity": "sha512-j4GT+rqzCoRKHwURX7pddtIPGySnX9Si/cgMI5ztrcqOPtk5dDEeZ34CQVPphnqkJytlc97Vuk05Um2mJ3gEQA==",
+      "dev": true,
+      "requires": {
+        "debug": "^3.2.7"
+      },
+      "dependencies": {
+        "debug": {
+          "version": "3.2.7",
+          "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz",
+          "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==",
+          "dev": true,
+          "requires": {
+            "ms": "^2.1.1"
+          }
+        }
+      }
+    },
+    "eslint-plugin-es": {
+      "version": "4.1.0",
+      "resolved": "https://registry.npmjs.org/eslint-plugin-es/-/eslint-plugin-es-4.1.0.tgz",
+      "integrity": "sha512-GILhQTnjYE2WorX5Jyi5i4dz5ALWxBIdQECVQavL6s7cI76IZTDWleTHkxz/QT3kvcs2QlGHvKLYsSlPOlPXnQ==",
+      "dev": true,
+      "requires": {
+        "eslint-utils": "^2.0.0",
+        "regexpp": "^3.0.0"
+      },
+      "dependencies": {
+        "eslint-utils": {
+          "version": "2.1.0",
+          "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz",
+          "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==",
+          "dev": true,
+          "requires": {
+            "eslint-visitor-keys": "^1.1.0"
+          }
+        },
+        "eslint-visitor-keys": {
+          "version": "1.3.0",
+          "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz",
+          "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==",
+          "dev": true
+        }
+      }
+    },
+    "eslint-plugin-import": {
+      "version": "2.26.0",
+      "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.26.0.tgz",
+      "integrity": "sha512-hYfi3FXaM8WPLf4S1cikh/r4IxnO6zrhZbEGz2b660EJRbuxgpDS5gkCuYgGWg2xxh2rBuIr4Pvhve/7c31koA==",
+      "dev": true,
+      "requires": {
+        "array-includes": "^3.1.4",
+        "array.prototype.flat": "^1.2.5",
+        "debug": "^2.6.9",
+        "doctrine": "^2.1.0",
+        "eslint-import-resolver-node": "^0.3.6",
+        "eslint-module-utils": "^2.7.3",
+        "has": "^1.0.3",
+        "is-core-module": "^2.8.1",
+        "is-glob": "^4.0.3",
+        "minimatch": "^3.1.2",
+        "object.values": "^1.1.5",
+        "resolve": "^1.22.0",
+        "tsconfig-paths": "^3.14.1"
+      },
+      "dependencies": {
+        "debug": {
+          "version": "2.6.9",
+          "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+          "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+          "dev": true,
+          "requires": {
+            "ms": "2.0.0"
+          }
+        },
+        "doctrine": {
+          "version": "2.1.0",
+          "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz",
+          "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==",
+          "dev": true,
+          "requires": {
+            "esutils": "^2.0.2"
+          }
+        },
+        "ms": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+          "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
+          "dev": true
+        }
+      }
+    },
+    "eslint-plugin-n": {
+      "version": "15.3.0",
+      "resolved": "https://registry.npmjs.org/eslint-plugin-n/-/eslint-plugin-n-15.3.0.tgz",
+      "integrity": "sha512-IyzPnEWHypCWasDpxeJnim60jhlumbmq0pubL6IOcnk8u2y53s5QfT8JnXy7skjHJ44yWHRb11PLtDHuu1kg/Q==",
+      "dev": true,
+      "requires": {
+        "builtins": "^5.0.1",
+        "eslint-plugin-es": "^4.1.0",
+        "eslint-utils": "^3.0.0",
+        "ignore": "^5.1.1",
+        "is-core-module": "^2.10.0",
+        "minimatch": "^3.1.2",
+        "resolve": "^1.22.1",
+        "semver": "^7.3.7"
+      }
+    },
+    "eslint-plugin-promise": {
+      "version": "6.0.1",
+      "resolved": "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-6.0.1.tgz",
+      "integrity": "sha512-uM4Tgo5u3UWQiroOyDEsYcVMOo7re3zmno0IZmB5auxoaQNIceAbXEkSt8RNrKtaYehARHG06pYK6K1JhtP0Zw==",
+      "dev": true
+    },
+    "eslint-scope": {
+      "version": "5.1.1",
+      "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz",
+      "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==",
+      "dev": true,
+      "requires": {
+        "esrecurse": "^4.3.0",
+        "estraverse": "^4.1.1"
+      }
+    },
+    "eslint-utils": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz",
+      "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==",
+      "dev": true,
+      "requires": {
+        "eslint-visitor-keys": "^2.0.0"
+      },
+      "dependencies": {
+        "eslint-visitor-keys": {
+          "version": "2.1.0",
+          "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz",
+          "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==",
+          "dev": true
+        }
+      }
+    },
+    "eslint-visitor-keys": {
+      "version": "3.3.0",
+      "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz",
+      "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==",
+      "dev": true
+    },
+    "espree": {
+      "version": "9.4.0",
+      "resolved": "https://registry.npmjs.org/espree/-/espree-9.4.0.tgz",
+      "integrity": "sha512-DQmnRpLj7f6TgN/NYb0MTzJXL+vJF9h3pHy4JhCIs3zwcgez8xmGg3sXHcEO97BrmO2OSvCwMdfdlyl+E9KjOw==",
+      "dev": true,
+      "requires": {
+        "acorn": "^8.8.0",
+        "acorn-jsx": "^5.3.2",
+        "eslint-visitor-keys": "^3.3.0"
+      }
+    },
+    "esquery": {
+      "version": "1.4.0",
+      "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz",
+      "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==",
+      "dev": true,
+      "requires": {
+        "estraverse": "^5.1.0"
+      },
+      "dependencies": {
+        "estraverse": {
+          "version": "5.3.0",
+          "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
+          "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
+          "dev": true
+        }
+      }
+    },
+    "esrecurse": {
+      "version": "4.3.0",
+      "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz",
+      "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==",
+      "dev": true,
+      "requires": {
+        "estraverse": "^5.2.0"
+      },
+      "dependencies": {
+        "estraverse": {
+          "version": "5.3.0",
+          "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
+          "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
+          "dev": true
+        }
+      }
+    },
+    "estraverse": {
+      "version": "4.3.0",
+      "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz",
+      "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==",
+      "dev": true
+    },
+    "esutils": {
+      "version": "2.0.3",
+      "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
+      "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
+      "dev": true
+    },
+    "fast-deep-equal": {
+      "version": "3.1.3",
+      "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
+      "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
+      "dev": true
+    },
+    "fast-glob": {
+      "version": "3.2.12",
+      "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz",
+      "integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==",
+      "dev": true,
+      "requires": {
+        "@nodelib/fs.stat": "^2.0.2",
+        "@nodelib/fs.walk": "^1.2.3",
+        "glob-parent": "^5.1.2",
+        "merge2": "^1.3.0",
+        "micromatch": "^4.0.4"
+      }
+    },
+    "fast-json-stable-stringify": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
+      "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
+      "dev": true
+    },
+    "fast-levenshtein": {
+      "version": "2.0.6",
+      "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
+      "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==",
+      "dev": true
+    },
+    "fastq": {
+      "version": "1.13.0",
+      "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz",
+      "integrity": "sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==",
+      "dev": true,
+      "requires": {
+        "reusify": "^1.0.4"
+      }
+    },
+    "file-entry-cache": {
+      "version": "6.0.1",
+      "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz",
+      "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==",
+      "dev": true,
+      "requires": {
+        "flat-cache": "^3.0.4"
+      }
+    },
+    "fill-range": {
+      "version": "7.0.1",
+      "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
+      "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
+      "dev": true,
+      "requires": {
+        "to-regex-range": "^5.0.1"
+      }
+    },
+    "find-up": {
+      "version": "5.0.0",
+      "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
+      "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==",
+      "dev": true,
+      "requires": {
+        "locate-path": "^6.0.0",
+        "path-exists": "^4.0.0"
+      }
+    },
+    "flat-cache": {
+      "version": "3.0.4",
+      "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz",
+      "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==",
+      "dev": true,
+      "requires": {
+        "flatted": "^3.1.0",
+        "rimraf": "^3.0.2"
+      }
+    },
+    "flatted": {
+      "version": "3.2.7",
+      "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz",
+      "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==",
+      "dev": true
+    },
+    "fs.realpath": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
+      "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
+      "dev": true
+    },
+    "function-bind": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
+      "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",
+      "dev": true
+    },
+    "function.prototype.name": {
+      "version": "1.1.5",
+      "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.5.tgz",
+      "integrity": "sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==",
+      "dev": true,
+      "requires": {
+        "call-bind": "^1.0.2",
+        "define-properties": "^1.1.3",
+        "es-abstract": "^1.19.0",
+        "functions-have-names": "^1.2.2"
+      }
+    },
+    "functions-have-names": {
+      "version": "1.2.3",
+      "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz",
+      "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==",
+      "dev": true
+    },
+    "get-intrinsic": {
+      "version": "1.1.3",
+      "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.3.tgz",
+      "integrity": "sha512-QJVz1Tj7MS099PevUG5jvnt9tSkXN8K14dxQlikJuPt4uD9hHAHjLyLBiLR5zELelBdD9QNRAXZzsJx0WaDL9A==",
+      "dev": true,
+      "requires": {
+        "function-bind": "^1.1.1",
+        "has": "^1.0.3",
+        "has-symbols": "^1.0.3"
+      }
+    },
+    "get-symbol-description": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz",
+      "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==",
+      "dev": true,
+      "requires": {
+        "call-bind": "^1.0.2",
+        "get-intrinsic": "^1.1.1"
+      }
+    },
+    "glob": {
+      "version": "7.2.3",
+      "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
+      "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
+      "dev": true,
+      "requires": {
+        "fs.realpath": "^1.0.0",
+        "inflight": "^1.0.4",
+        "inherits": "2",
+        "minimatch": "^3.1.1",
+        "once": "^1.3.0",
+        "path-is-absolute": "^1.0.0"
+      }
+    },
+    "glob-parent": {
+      "version": "5.1.2",
+      "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
+      "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
+      "dev": true,
+      "requires": {
+        "is-glob": "^4.0.1"
+      }
+    },
+    "globals": {
+      "version": "13.17.0",
+      "resolved": "https://registry.npmjs.org/globals/-/globals-13.17.0.tgz",
+      "integrity": "sha512-1C+6nQRb1GwGMKm2dH/E7enFAMxGTmGI7/dEdhy/DNelv85w9B72t3uc5frtMNXIbzrarJJ/lTCjcaZwbLJmyw==",
+      "dev": true,
+      "requires": {
+        "type-fest": "^0.20.2"
+      }
+    },
+    "globby": {
+      "version": "11.1.0",
+      "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz",
+      "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==",
+      "dev": true,
+      "requires": {
+        "array-union": "^2.1.0",
+        "dir-glob": "^3.0.1",
+        "fast-glob": "^3.2.9",
+        "ignore": "^5.2.0",
+        "merge2": "^1.4.1",
+        "slash": "^3.0.0"
+      }
+    },
+    "graceful-fs": {
+      "version": "4.2.10",
+      "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz",
+      "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==",
+      "dev": true
+    },
+    "grapheme-splitter": {
+      "version": "1.0.4",
+      "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz",
+      "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==",
+      "dev": true
+    },
+    "has": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
+      "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
+      "dev": true,
+      "requires": {
+        "function-bind": "^1.1.1"
+      }
+    },
+    "has-bigints": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz",
+      "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==",
+      "dev": true
+    },
+    "has-flag": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+      "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+      "dev": true
+    },
+    "has-property-descriptors": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz",
+      "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==",
+      "dev": true,
+      "requires": {
+        "get-intrinsic": "^1.1.1"
+      }
+    },
+    "has-symbols": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz",
+      "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==",
+      "dev": true
+    },
+    "has-tostringtag": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz",
+      "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==",
+      "dev": true,
+      "requires": {
+        "has-symbols": "^1.0.2"
+      }
+    },
+    "hosted-git-info": {
+      "version": "2.8.9",
+      "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz",
+      "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==",
+      "dev": true
+    },
+    "ignore": {
+      "version": "5.2.0",
+      "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz",
+      "integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==",
+      "dev": true
+    },
+    "import-fresh": {
+      "version": "3.3.0",
+      "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
+      "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==",
+      "dev": true,
+      "requires": {
+        "parent-module": "^1.0.0",
+        "resolve-from": "^4.0.0"
+      }
+    },
+    "imurmurhash": {
+      "version": "0.1.4",
+      "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
+      "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==",
+      "dev": true
+    },
+    "inflight": {
+      "version": "1.0.6",
+      "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
+      "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
+      "dev": true,
+      "requires": {
+        "once": "^1.3.0",
+        "wrappy": "1"
+      }
+    },
+    "inherits": {
+      "version": "2.0.4",
+      "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
+      "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
+      "dev": true
+    },
+    "internal-slot": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.3.tgz",
+      "integrity": "sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA==",
+      "dev": true,
+      "requires": {
+        "get-intrinsic": "^1.1.0",
+        "has": "^1.0.3",
+        "side-channel": "^1.0.4"
+      }
+    },
+    "is-arrayish": {
+      "version": "0.2.1",
+      "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
+      "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==",
+      "dev": true
+    },
+    "is-bigint": {
+      "version": "1.0.4",
+      "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz",
+      "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==",
+      "dev": true,
+      "requires": {
+        "has-bigints": "^1.0.1"
+      }
+    },
+    "is-boolean-object": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz",
+      "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==",
+      "dev": true,
+      "requires": {
+        "call-bind": "^1.0.2",
+        "has-tostringtag": "^1.0.0"
+      }
+    },
+    "is-callable": {
+      "version": "1.2.7",
+      "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz",
+      "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==",
+      "dev": true
+    },
+    "is-core-module": {
+      "version": "2.10.0",
+      "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.10.0.tgz",
+      "integrity": "sha512-Erxj2n/LDAZ7H8WNJXd9tw38GYM3dv8rk8Zcs+jJuxYTW7sozH+SS8NtrSjVL1/vpLvWi1hxy96IzjJ3EHTJJg==",
+      "dev": true,
+      "requires": {
+        "has": "^1.0.3"
+      }
+    },
+    "is-date-object": {
+      "version": "1.0.5",
+      "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz",
+      "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==",
+      "dev": true,
+      "requires": {
+        "has-tostringtag": "^1.0.0"
+      }
+    },
+    "is-extglob": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
+      "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
+      "dev": true
+    },
+    "is-glob": {
+      "version": "4.0.3",
+      "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
+      "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
+      "dev": true,
+      "requires": {
+        "is-extglob": "^2.1.1"
+      }
+    },
+    "is-negative-zero": {
+      "version": "2.0.2",
+      "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz",
+      "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==",
+      "dev": true
+    },
+    "is-number": {
+      "version": "7.0.0",
+      "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
+      "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
+      "dev": true
+    },
+    "is-number-object": {
+      "version": "1.0.7",
+      "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz",
+      "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==",
+      "dev": true,
+      "requires": {
+        "has-tostringtag": "^1.0.0"
+      }
+    },
+    "is-regex": {
+      "version": "1.1.4",
+      "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz",
+      "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==",
+      "dev": true,
+      "requires": {
+        "call-bind": "^1.0.2",
+        "has-tostringtag": "^1.0.0"
+      }
+    },
+    "is-shared-array-buffer": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz",
+      "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==",
+      "dev": true,
+      "requires": {
+        "call-bind": "^1.0.2"
+      }
+    },
+    "is-string": {
+      "version": "1.0.7",
+      "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz",
+      "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==",
+      "dev": true,
+      "requires": {
+        "has-tostringtag": "^1.0.0"
+      }
+    },
+    "is-symbol": {
+      "version": "1.0.4",
+      "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz",
+      "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==",
+      "dev": true,
+      "requires": {
+        "has-symbols": "^1.0.2"
+      }
+    },
+    "is-weakref": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz",
+      "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==",
+      "dev": true,
+      "requires": {
+        "call-bind": "^1.0.2"
+      }
+    },
+    "isexe": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
+      "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
+      "dev": true
+    },
+    "js-sdsl": {
+      "version": "4.1.4",
+      "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.1.4.tgz",
+      "integrity": "sha512-Y2/yD55y5jteOAmY50JbUZYwk3CP3wnLPEZnlR1w9oKhITrBEtAxwuWKebFf8hMrPMgbYwFoWK/lH2sBkErELw==",
+      "dev": true
+    },
+    "js-yaml": {
+      "version": "4.1.0",
+      "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
+      "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
+      "dev": true,
+      "requires": {
+        "argparse": "^2.0.1"
+      }
+    },
+    "json-parse-better-errors": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz",
+      "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==",
+      "dev": true
+    },
+    "json-schema-traverse": {
+      "version": "0.4.1",
+      "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
+      "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
+      "dev": true
+    },
+    "json-stable-stringify-without-jsonify": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz",
+      "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==",
+      "dev": true
+    },
+    "json5": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz",
+      "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==",
+      "dev": true,
+      "requires": {
+        "minimist": "^1.2.0"
+      }
+    },
+    "levn": {
+      "version": "0.4.1",
+      "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
+      "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==",
+      "dev": true,
+      "requires": {
+        "prelude-ls": "^1.2.1",
+        "type-check": "~0.4.0"
+      }
+    },
+    "load-json-file": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz",
+      "integrity": "sha512-Kx8hMakjX03tiGTLAIdJ+lL0htKnXjEZN6hk/tozf/WOuYGdZBJrZ+rCJRbVCugsjB3jMLn9746NsQIf5VjBMw==",
+      "dev": true,
+      "requires": {
+        "graceful-fs": "^4.1.2",
+        "parse-json": "^4.0.0",
+        "pify": "^3.0.0",
+        "strip-bom": "^3.0.0"
+      }
+    },
+    "locate-path": {
+      "version": "6.0.0",
+      "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
+      "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==",
+      "dev": true,
+      "requires": {
+        "p-locate": "^5.0.0"
+      }
+    },
+    "lodash.merge": {
+      "version": "4.6.2",
+      "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
+      "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
+      "dev": true
+    },
+    "lru-cache": {
+      "version": "6.0.0",
+      "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
+      "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
+      "dev": true,
+      "requires": {
+        "yallist": "^4.0.0"
+      }
+    },
+    "memorystream": {
+      "version": "0.3.1",
+      "resolved": "https://registry.npmjs.org/memorystream/-/memorystream-0.3.1.tgz",
+      "integrity": "sha512-S3UwM3yj5mtUSEfP41UZmt/0SCoVYUcU1rkXv+BQ5Ig8ndL4sPoJNBUJERafdPb5jjHJGuMgytgKvKIf58XNBw==",
+      "dev": true
+    },
+    "merge2": {
+      "version": "1.4.1",
+      "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
+      "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==",
+      "dev": true
+    },
+    "micromatch": {
+      "version": "4.0.5",
+      "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz",
+      "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==",
+      "dev": true,
+      "requires": {
+        "braces": "^3.0.2",
+        "picomatch": "^2.3.1"
+      }
+    },
+    "minimatch": {
+      "version": "3.1.2",
+      "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
+      "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+      "dev": true,
+      "requires": {
+        "brace-expansion": "^1.1.7"
+      }
+    },
+    "minimist": {
+      "version": "1.2.6",
+      "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz",
+      "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==",
+      "dev": true
+    },
+    "ms": {
+      "version": "2.1.2",
+      "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+      "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
+      "dev": true
+    },
+    "natural-compare": {
+      "version": "1.4.0",
+      "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
+      "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==",
+      "dev": true
+    },
+    "nice-try": {
+      "version": "1.0.5",
+      "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz",
+      "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==",
+      "dev": true
+    },
+    "normalize-package-data": {
+      "version": "2.5.0",
+      "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz",
+      "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==",
+      "dev": true,
+      "requires": {
+        "hosted-git-info": "^2.1.4",
+        "resolve": "^1.10.0",
+        "semver": "2 || 3 || 4 || 5",
+        "validate-npm-package-license": "^3.0.1"
+      },
+      "dependencies": {
+        "semver": {
+          "version": "5.7.1",
+          "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
+          "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
+          "dev": true
+        }
+      }
+    },
+    "npm-run-all": {
+      "version": "4.1.5",
+      "resolved": "https://registry.npmjs.org/npm-run-all/-/npm-run-all-4.1.5.tgz",
+      "integrity": "sha512-Oo82gJDAVcaMdi3nuoKFavkIHBRVqQ1qvMb+9LHk/cF4P6B2m8aP04hGf7oL6wZ9BuGwX1onlLhpuoofSyoQDQ==",
+      "dev": true,
+      "requires": {
+        "ansi-styles": "^3.2.1",
+        "chalk": "^2.4.1",
+        "cross-spawn": "^6.0.5",
+        "memorystream": "^0.3.1",
+        "minimatch": "^3.0.4",
+        "pidtree": "^0.3.0",
+        "read-pkg": "^3.0.0",
+        "shell-quote": "^1.6.1",
+        "string.prototype.padend": "^3.0.0"
+      },
+      "dependencies": {
+        "ansi-styles": {
+          "version": "3.2.1",
+          "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
+          "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
+          "dev": true,
+          "requires": {
+            "color-convert": "^1.9.0"
+          }
+        },
+        "chalk": {
+          "version": "2.4.2",
+          "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
+          "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
+          "dev": true,
+          "requires": {
+            "ansi-styles": "^3.2.1",
+            "escape-string-regexp": "^1.0.5",
+            "supports-color": "^5.3.0"
+          }
+        },
+        "color-convert": {
+          "version": "1.9.3",
+          "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
+          "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
+          "dev": true,
+          "requires": {
+            "color-name": "1.1.3"
+          }
+        },
+        "color-name": {
+          "version": "1.1.3",
+          "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
+          "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==",
+          "dev": true
+        },
+        "cross-spawn": {
+          "version": "6.0.5",
+          "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz",
+          "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==",
+          "dev": true,
+          "requires": {
+            "nice-try": "^1.0.4",
+            "path-key": "^2.0.1",
+            "semver": "^5.5.0",
+            "shebang-command": "^1.2.0",
+            "which": "^1.2.9"
+          }
+        },
+        "escape-string-regexp": {
+          "version": "1.0.5",
+          "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
+          "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==",
+          "dev": true
+        },
+        "has-flag": {
+          "version": "3.0.0",
+          "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
+          "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
+          "dev": true
+        },
+        "path-key": {
+          "version": "2.0.1",
+          "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz",
+          "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==",
+          "dev": true
+        },
+        "semver": {
+          "version": "5.7.1",
+          "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
+          "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
+          "dev": true
+        },
+        "shebang-command": {
+          "version": "1.2.0",
+          "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz",
+          "integrity": "sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==",
+          "dev": true,
+          "requires": {
+            "shebang-regex": "^1.0.0"
+          }
+        },
+        "shebang-regex": {
+          "version": "1.0.0",
+          "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz",
+          "integrity": "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==",
+          "dev": true
+        },
+        "supports-color": {
+          "version": "5.5.0",
+          "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
+          "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
+          "dev": true,
+          "requires": {
+            "has-flag": "^3.0.0"
+          }
+        },
+        "which": {
+          "version": "1.3.1",
+          "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz",
+          "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==",
+          "dev": true,
+          "requires": {
+            "isexe": "^2.0.0"
+          }
+        }
+      }
+    },
+    "object-inspect": {
+      "version": "1.12.2",
+      "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.2.tgz",
+      "integrity": "sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ==",
+      "dev": true
+    },
+    "object-keys": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz",
+      "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==",
+      "dev": true
+    },
+    "object.assign": {
+      "version": "4.1.4",
+      "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz",
+      "integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==",
+      "dev": true,
+      "requires": {
+        "call-bind": "^1.0.2",
+        "define-properties": "^1.1.4",
+        "has-symbols": "^1.0.3",
+        "object-keys": "^1.1.1"
+      }
+    },
+    "object.values": {
+      "version": "1.1.5",
+      "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.5.tgz",
+      "integrity": "sha512-QUZRW0ilQ3PnPpbNtgdNV1PDbEqLIiSFB3l+EnGtBQ/8SUTLj1PZwtQHABZtLgwpJZTSZhuGLOGk57Drx2IvYg==",
+      "dev": true,
+      "requires": {
+        "call-bind": "^1.0.2",
+        "define-properties": "^1.1.3",
+        "es-abstract": "^1.19.1"
+      }
+    },
+    "once": {
+      "version": "1.4.0",
+      "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
+      "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
+      "dev": true,
+      "requires": {
+        "wrappy": "1"
+      }
+    },
+    "optionator": {
+      "version": "0.9.1",
+      "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz",
+      "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==",
+      "dev": true,
+      "requires": {
+        "deep-is": "^0.1.3",
+        "fast-levenshtein": "^2.0.6",
+        "levn": "^0.4.1",
+        "prelude-ls": "^1.2.1",
+        "type-check": "^0.4.0",
+        "word-wrap": "^1.2.3"
+      }
+    },
+    "p-limit": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
+      "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
+      "dev": true,
+      "requires": {
+        "yocto-queue": "^0.1.0"
+      }
+    },
+    "p-locate": {
+      "version": "5.0.0",
+      "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz",
+      "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==",
+      "dev": true,
+      "requires": {
+        "p-limit": "^3.0.2"
+      }
+    },
+    "parent-module": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
+      "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
+      "dev": true,
+      "requires": {
+        "callsites": "^3.0.0"
+      }
+    },
+    "parse-json": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz",
+      "integrity": "sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==",
+      "dev": true,
+      "requires": {
+        "error-ex": "^1.3.1",
+        "json-parse-better-errors": "^1.0.1"
+      }
+    },
+    "path-exists": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
+      "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
+      "dev": true
+    },
+    "path-is-absolute": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
+      "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==",
+      "dev": true
+    },
+    "path-key": {
+      "version": "3.1.1",
+      "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
+      "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
+      "dev": true
+    },
+    "path-parse": {
+      "version": "1.0.7",
+      "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
+      "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
+      "dev": true
+    },
+    "path-type": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz",
+      "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==",
+      "dev": true
+    },
+    "picomatch": {
+      "version": "2.3.1",
+      "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
+      "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
+      "dev": true
+    },
+    "pidtree": {
+      "version": "0.3.1",
+      "resolved": "https://registry.npmjs.org/pidtree/-/pidtree-0.3.1.tgz",
+      "integrity": "sha512-qQbW94hLHEqCg7nhby4yRC7G2+jYHY4Rguc2bjw7Uug4GIJuu1tvf2uHaZv5Q8zdt+WKJ6qK1FOI6amaWUo5FA==",
+      "dev": true
+    },
+    "pify": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz",
+      "integrity": "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==",
+      "dev": true
+    },
+    "prelude-ls": {
+      "version": "1.2.1",
+      "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
+      "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==",
+      "dev": true
+    },
+    "punycode": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
+      "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==",
+      "dev": true
+    },
+    "queue-microtask": {
+      "version": "1.2.3",
+      "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
+      "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==",
+      "dev": true
+    },
+    "read-pkg": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz",
+      "integrity": "sha512-BLq/cCO9two+lBgiTYNqD6GdtK8s4NpaWrl6/rCO9w0TUS8oJl7cmToOZfRYllKTISY6nt1U7jQ53brmKqY6BA==",
+      "dev": true,
+      "requires": {
+        "load-json-file": "^4.0.0",
+        "normalize-package-data": "^2.3.2",
+        "path-type": "^3.0.0"
+      },
+      "dependencies": {
+        "path-type": {
+          "version": "3.0.0",
+          "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz",
+          "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==",
+          "dev": true,
+          "requires": {
+            "pify": "^3.0.0"
+          }
+        }
+      }
+    },
+    "regexp.prototype.flags": {
+      "version": "1.4.3",
+      "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz",
+      "integrity": "sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA==",
+      "dev": true,
+      "requires": {
+        "call-bind": "^1.0.2",
+        "define-properties": "^1.1.3",
+        "functions-have-names": "^1.2.2"
+      }
+    },
+    "regexpp": {
+      "version": "3.2.0",
+      "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz",
+      "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==",
+      "dev": true
+    },
+    "resolve": {
+      "version": "1.22.1",
+      "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz",
+      "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==",
+      "dev": true,
+      "requires": {
+        "is-core-module": "^2.9.0",
+        "path-parse": "^1.0.7",
+        "supports-preserve-symlinks-flag": "^1.0.0"
+      }
+    },
+    "resolve-from": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
+      "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
+      "dev": true
+    },
+    "reusify": {
+      "version": "1.0.4",
+      "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz",
+      "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==",
+      "dev": true
+    },
+    "rimraf": {
+      "version": "3.0.2",
+      "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
+      "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
+      "dev": true,
+      "requires": {
+        "glob": "^7.1.3"
+      }
+    },
+    "run-parallel": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
+      "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==",
+      "dev": true,
+      "requires": {
+        "queue-microtask": "^1.2.2"
+      }
+    },
+    "safe-regex-test": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.0.tgz",
+      "integrity": "sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==",
+      "dev": true,
+      "requires": {
+        "call-bind": "^1.0.2",
+        "get-intrinsic": "^1.1.3",
+        "is-regex": "^1.1.4"
+      }
+    },
+    "semver": {
+      "version": "7.3.7",
+      "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz",
+      "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==",
+      "dev": true,
+      "requires": {
+        "lru-cache": "^6.0.0"
+      }
+    },
+    "shebang-command": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
+      "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
+      "dev": true,
+      "requires": {
+        "shebang-regex": "^3.0.0"
+      }
+    },
+    "shebang-regex": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
+      "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
+      "dev": true
+    },
+    "shell-quote": {
+      "version": "1.7.3",
+      "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.7.3.tgz",
+      "integrity": "sha512-Vpfqwm4EnqGdlsBFNmHhxhElJYrdfcxPThu+ryKS5J8L/fhAwLazFZtq+S+TWZ9ANj2piSQLGj6NQg+lKPmxrw==",
+      "dev": true
+    },
+    "side-channel": {
+      "version": "1.0.4",
+      "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz",
+      "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==",
+      "dev": true,
+      "requires": {
+        "call-bind": "^1.0.0",
+        "get-intrinsic": "^1.0.2",
+        "object-inspect": "^1.9.0"
+      }
+    },
+    "slash": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz",
+      "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==",
+      "dev": true
+    },
+    "spdx-correct": {
+      "version": "3.1.1",
+      "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz",
+      "integrity": "sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==",
+      "dev": true,
+      "requires": {
+        "spdx-expression-parse": "^3.0.0",
+        "spdx-license-ids": "^3.0.0"
+      }
+    },
+    "spdx-exceptions": {
+      "version": "2.3.0",
+      "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz",
+      "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==",
+      "dev": true
+    },
+    "spdx-expression-parse": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz",
+      "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==",
+      "dev": true,
+      "requires": {
+        "spdx-exceptions": "^2.1.0",
+        "spdx-license-ids": "^3.0.0"
+      }
+    },
+    "spdx-license-ids": {
+      "version": "3.0.12",
+      "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.12.tgz",
+      "integrity": "sha512-rr+VVSXtRhO4OHbXUiAF7xW3Bo9DuuF6C5jH+q/x15j2jniycgKbxU09Hr0WqlSLUs4i4ltHGXqTe7VHclYWyA==",
+      "dev": true
+    },
+    "string.prototype.padend": {
+      "version": "3.1.3",
+      "resolved": "https://registry.npmjs.org/string.prototype.padend/-/string.prototype.padend-3.1.3.tgz",
+      "integrity": "sha512-jNIIeokznm8SD/TZISQsZKYu7RJyheFNt84DUPrh482GC8RVp2MKqm2O5oBRdGxbDQoXrhhWtPIWQOiy20svUg==",
+      "dev": true,
+      "requires": {
+        "call-bind": "^1.0.2",
+        "define-properties": "^1.1.3",
+        "es-abstract": "^1.19.1"
+      }
+    },
+    "string.prototype.trimend": {
+      "version": "1.0.5",
+      "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.5.tgz",
+      "integrity": "sha512-I7RGvmjV4pJ7O3kdf+LXFpVfdNOxtCW/2C8f6jNiW4+PQchwxkCDzlk1/7p+Wl4bqFIZeF47qAHXLuHHWKAxog==",
+      "dev": true,
+      "requires": {
+        "call-bind": "^1.0.2",
+        "define-properties": "^1.1.4",
+        "es-abstract": "^1.19.5"
+      }
+    },
+    "string.prototype.trimstart": {
+      "version": "1.0.5",
+      "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.5.tgz",
+      "integrity": "sha512-THx16TJCGlsN0o6dl2o6ncWUsdgnLRSA23rRE5pyGBw/mLr3Ej/R2LaqCtgP8VNMGZsvMWnf9ooZPyY2bHvUFg==",
+      "dev": true,
+      "requires": {
+        "call-bind": "^1.0.2",
+        "define-properties": "^1.1.4",
+        "es-abstract": "^1.19.5"
+      }
+    },
+    "strip-ansi": {
+      "version": "6.0.1",
+      "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+      "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+      "dev": true,
+      "requires": {
+        "ansi-regex": "^5.0.1"
+      }
+    },
+    "strip-bom": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz",
+      "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==",
+      "dev": true
+    },
+    "strip-json-comments": {
+      "version": "3.1.1",
+      "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
+      "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
+      "dev": true
+    },
+    "supports-color": {
+      "version": "7.2.0",
+      "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
+      "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+      "dev": true,
+      "requires": {
+        "has-flag": "^4.0.0"
+      }
+    },
+    "supports-preserve-symlinks-flag": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
+      "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
+      "dev": true
+    },
+    "text-table": {
+      "version": "0.2.0",
+      "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
+      "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==",
+      "dev": true
+    },
+    "to-regex-range": {
+      "version": "5.0.1",
+      "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
+      "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
+      "dev": true,
+      "requires": {
+        "is-number": "^7.0.0"
+      }
+    },
+    "tsconfig-paths": {
+      "version": "3.14.1",
+      "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.1.tgz",
+      "integrity": "sha512-fxDhWnFSLt3VuTwtvJt5fpwxBHg5AdKWMsgcPOOIilyjymcYVZoCQF8fvFRezCNfblEXmi+PcM1eYHeOAgXCOQ==",
+      "dev": true,
+      "requires": {
+        "@types/json5": "^0.0.29",
+        "json5": "^1.0.1",
+        "minimist": "^1.2.6",
+        "strip-bom": "^3.0.0"
+      }
+    },
+    "tslib": {
+      "version": "1.14.1",
+      "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
+      "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==",
+      "dev": true
+    },
+    "tsutils": {
+      "version": "3.21.0",
+      "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz",
+      "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==",
+      "dev": true,
+      "requires": {
+        "tslib": "^1.8.1"
+      }
+    },
+    "type-check": {
+      "version": "0.4.0",
+      "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
+      "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==",
+      "dev": true,
+      "requires": {
+        "prelude-ls": "^1.2.1"
+      }
+    },
+    "type-fest": {
+      "version": "0.20.2",
+      "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz",
+      "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==",
+      "dev": true
+    },
+    "typescript": {
+      "version": "4.8.3",
+      "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.8.3.tgz",
+      "integrity": "sha512-goMHfm00nWPa8UvR/CPSvykqf6dVV8x/dp0c5mFTMTIu0u0FlGWRioyy7Nn0PGAdHxpJZnuO/ut+PpQ8UiHAig==",
+      "dev": true
+    },
+    "unbox-primitive": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz",
+      "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==",
+      "dev": true,
+      "requires": {
+        "call-bind": "^1.0.2",
+        "has-bigints": "^1.0.2",
+        "has-symbols": "^1.0.3",
+        "which-boxed-primitive": "^1.0.2"
+      }
+    },
+    "uri-js": {
+      "version": "4.4.1",
+      "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
+      "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
+      "dev": true,
+      "requires": {
+        "punycode": "^2.1.0"
+      }
+    },
+    "validate-npm-package-license": {
+      "version": "3.0.4",
+      "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz",
+      "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==",
+      "dev": true,
+      "requires": {
+        "spdx-correct": "^3.0.0",
+        "spdx-expression-parse": "^3.0.0"
+      }
+    },
+    "which": {
+      "version": "2.0.2",
+      "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
+      "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
+      "dev": true,
+      "requires": {
+        "isexe": "^2.0.0"
+      }
+    },
+    "which-boxed-primitive": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz",
+      "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==",
+      "dev": true,
+      "requires": {
+        "is-bigint": "^1.0.1",
+        "is-boolean-object": "^1.1.0",
+        "is-number-object": "^1.0.4",
+        "is-string": "^1.0.5",
+        "is-symbol": "^1.0.3"
+      }
+    },
+    "word-wrap": {
+      "version": "1.2.3",
+      "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz",
+      "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==",
+      "dev": true
+    },
+    "wrappy": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
+      "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
+      "dev": true
+    },
+    "yallist": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
+      "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
+      "dev": true
+    },
+    "yocto-queue": {
+      "version": "0.1.0",
+      "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
+      "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
+      "dev": true
+    }
+  }
+}

+ 22 - 0
src/Web/Avalonia.Web/webapp/package.json

@@ -0,0 +1,22 @@
+{
+  "name": "avalonia.web",
+  "scripts": {
+    "typecheck": "npx tsc -noEmit",
+    "eslint": "npx eslint . --fix",
+    "prebuild": "npm-run-all typecheck eslint",
+    "build": "node build.js"
+  },
+  "devDependencies": {
+    "@types/emscripten": "^1.39.6",
+    "@types/wicg-file-system-access": "^2020.9.5",
+    "@typescript-eslint/eslint-plugin": "^5.38.1",
+    "esbuild": "^0.15.7",
+    "eslint": "^8.24.0",
+    "eslint-config-standard-with-typescript": "^23.0.0",
+    "eslint-plugin-import": "^2.26.0",
+    "eslint-plugin-n": "^15.3.0",
+    "eslint-plugin-promise": "^6.0.1",
+    "npm-run-all": "^4.1.5",
+    "typescript": "^4.8.3"
+  }
+}

+ 19 - 0
src/Web/Avalonia.Web/webapp/tsconfig.json

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

+ 270 - 0
src/Web/Avalonia.Web/webapp/types/dotnet.d.ts

@@ -0,0 +1,270 @@
+// See https://raw.githubusercontent.com/dotnet/runtime/main/src/mono/wasm/runtime/dotnet.d.ts
+
+//! Licensed to the .NET Foundation under one or more agreements.
+//! The .NET Foundation licenses this file to you under the MIT license.
+//!
+//! This is generated file, see src/mono/wasm/runtime/rollup.config.js
+
+//! This is not considered public API with backward compatibility guarantees. 
+
+interface DotnetHostBuilder {
+    withConfig(config: MonoConfig): DotnetHostBuilder;
+    withConfigSrc(configSrc: string): DotnetHostBuilder;
+    withApplicationArguments(...args: string[]): DotnetHostBuilder;
+    withEnvironmentVariable(name: string, value: string): DotnetHostBuilder;
+    withEnvironmentVariables(variables: {
+        [i: string]: string;
+    }): DotnetHostBuilder;
+    withVirtualWorkingDirectory(vfsPath: string): DotnetHostBuilder;
+    withDiagnosticTracing(enabled: boolean): DotnetHostBuilder;
+    withDebugging(level: number): DotnetHostBuilder;
+    withMainAssembly(mainAssemblyName: string): DotnetHostBuilder;
+    withApplicationArgumentsFromQuery(): DotnetHostBuilder;
+    create(): Promise<RuntimeAPI>;
+    run(): Promise<number>;
+}
+
+declare interface NativePointer {
+    __brandNativePointer: "NativePointer";
+}
+declare interface VoidPtr extends NativePointer {
+    __brand: "VoidPtr";
+}
+declare interface CharPtr extends NativePointer {
+    __brand: "CharPtr";
+}
+declare interface Int32Ptr extends NativePointer {
+    __brand: "Int32Ptr";
+}
+declare interface EmscriptenModule {
+    HEAP8: Int8Array;
+    HEAP16: Int16Array;
+    HEAP32: Int32Array;
+    HEAPU8: Uint8Array;
+    HEAPU16: Uint16Array;
+    HEAPU32: Uint32Array;
+    HEAPF32: Float32Array;
+    HEAPF64: Float64Array;
+    _malloc(size: number): VoidPtr;
+    _free(ptr: VoidPtr): void;
+    print(message: string): void;
+    printErr(message: string): void;
+    ccall<T>(ident: string, returnType?: string | null, argTypes?: string[], args?: any[], opts?: any): T;
+    cwrap<T extends Function>(ident: string, returnType: string, argTypes?: string[], opts?: any): T;
+    cwrap<T extends Function>(ident: string, ...args: any[]): T;
+    setValue(ptr: VoidPtr, value: number, type: string, noSafe?: number | boolean): void;
+    setValue(ptr: Int32Ptr, value: number, type: string, noSafe?: number | boolean): void;
+    getValue(ptr: number, type: string, noSafe?: number | boolean): number;
+    UTF8ToString(ptr: CharPtr, maxBytesToRead?: number): string;
+    UTF8ArrayToString(u8Array: Uint8Array, idx?: number, maxBytesToRead?: number): string;
+    FS_createPath(parent: string, path: string, canRead?: boolean, canWrite?: boolean): string;
+    FS_createDataFile(parent: string, name: string, data: TypedArray, canRead: boolean, canWrite: boolean, canOwn?: boolean): string;
+    FS_readFile(filename: string, opts: any): any;
+    removeRunDependency(id: string): void;
+    addRunDependency(id: string): void;
+    stackSave(): VoidPtr;
+    stackRestore(stack: VoidPtr): void;
+    stackAlloc(size: number): VoidPtr;
+    ready: Promise<unknown>;
+    instantiateWasm?: InstantiateWasmCallBack;
+    preInit?: (() => any)[] | (() => any);
+    preRun?: (() => any)[] | (() => any);
+    onRuntimeInitialized?: () => any;
+    postRun?: (() => any)[] | (() => any);
+    onAbort?: {
+        (error: any): void;
+    };
+}
+declare type InstantiateWasmSuccessCallback = (instance: WebAssembly.Instance, module: WebAssembly.Module) => void;
+declare type InstantiateWasmCallBack = (imports: WebAssembly.Imports, successCallback: InstantiateWasmSuccessCallback) => any;
+declare type TypedArray = Int8Array | Uint8Array | Uint8ClampedArray | Int16Array | Uint16Array | Int32Array | Uint32Array | Float32Array | Float64Array;
+
+declare type MonoConfig = {
+    /**
+     * The subfolder containing managed assemblies and pdbs. This is relative to dotnet.js script.
+     */
+    assemblyRootFolder?: string;
+    /**
+     * A list of assets to load along with the runtime.
+     */
+    assets?: AssetEntry[];
+    /**
+     * Additional search locations for assets.
+     */
+    remoteSources?: string[];
+    /**
+     * It will not fail the startup is .pdb files can't be downloaded
+     */
+    ignorePdbLoadErrors?: boolean;
+    /**
+     * We are throttling parallel downloads in order to avoid net::ERR_INSUFFICIENT_RESOURCES on chrome. The default value is 16.
+     */
+    maxParallelDownloads?: number;
+    /**
+     * Name of the assembly with main entrypoint
+     */
+    mainAssemblyName?: string;
+    /**
+     * Configures the runtime's globalization mode
+     */
+    globalizationMode?: GlobalizationMode;
+    /**
+     * debugLevel > 0 enables debugging and sets the debug log level to debugLevel
+     * debugLevel == 0 disables debugging and enables interpreter optimizations
+     * debugLevel < 0 enabled debugging and disables debug logging.
+     */
+    debugLevel?: number;
+    /**
+    * Enables diagnostic log messages during startup
+    */
+    diagnosticTracing?: boolean;
+    /**
+     * Dictionary-style Object containing environment variables
+     */
+    environmentVariables?: {
+        [i: string]: string;
+    };
+    /**
+     * initial number of workers to add to the emscripten pthread pool
+     */
+    pthreadPoolSize?: number;
+};
+interface ResourceRequest {
+    name: string;
+    behavior: AssetBehaviours;
+    resolvedUrl?: string;
+    hash?: string;
+}
+interface LoadingResource {
+    name: string;
+    url: string;
+    response: Promise<Response>;
+}
+interface AssetEntry extends ResourceRequest {
+    /**
+     * If specified, overrides the path of the asset in the virtual filesystem and similar data structures once downloaded.
+     */
+    virtualPath?: string;
+    /**
+     * Culture code
+     */
+    culture?: string;
+    /**
+     * If true, an attempt will be made to load the asset from each location in MonoConfig.remoteSources.
+     */
+    loadRemote?: boolean;
+    /**
+     * If true, the runtime startup would not fail if the asset download was not successful.
+     */
+    isOptional?: boolean;
+    /**
+     * If provided, runtime doesn't have to fetch the data.
+     * Runtime would set the buffer to null after instantiation to free the memory.
+     */
+    buffer?: ArrayBuffer;
+    /**
+     * It's metadata + fetch-like Promise<Response>
+     * If provided, the runtime doesn't have to initiate the download. It would just await the response.
+     */
+    pendingDownload?: LoadingResource;
+}
+declare type AssetBehaviours = "resource" | "assembly" | "pdb" | "heap" | "icu" | "vfs" | "dotnetwasm" | "js-module-threads";
+declare type GlobalizationMode = "icu" | // load ICU globalization data from any runtime assets with behavior "icu".
+"invariant" | //  operate in invariant globalization mode.
+"auto";
+declare type DotnetModuleConfig = {
+    disableDotnet6Compatibility?: boolean;
+    config?: MonoConfig;
+    configSrc?: string;
+    onConfigLoaded?: (config: MonoConfig) => void | Promise<void>;
+    onDotnetReady?: () => void | Promise<void>;
+    imports?: any;
+    exports?: string[];
+    downloadResource?: (request: ResourceRequest) => LoadingResource | undefined;
+} & Partial<EmscriptenModule>;
+declare type APIType = {
+    runMain: (mainAssemblyName: string, args: string[]) => Promise<number>;
+    runMainAndExit: (mainAssemblyName: string, args: string[]) => Promise<number>;
+    setEnvironmentVariable: (name: string, value: string) => void;
+    getAssemblyExports(assemblyName: string): Promise<any>;
+    setModuleImports(moduleName: string, moduleImports: any): void;
+    getConfig: () => MonoConfig;
+    setHeapB32: (offset: NativePointer, value: number | boolean) => void;
+    setHeapU8: (offset: NativePointer, value: number) => void;
+    setHeapU16: (offset: NativePointer, value: number) => void;
+    setHeapU32: (offset: NativePointer, value: NativePointer | number) => void;
+    setHeapI8: (offset: NativePointer, value: number) => void;
+    setHeapI16: (offset: NativePointer, value: number) => void;
+    setHeapI32: (offset: NativePointer, value: number) => void;
+    setHeapI52: (offset: NativePointer, value: number) => void;
+    setHeapU52: (offset: NativePointer, value: number) => void;
+    setHeapI64Big: (offset: NativePointer, value: bigint) => void;
+    setHeapF32: (offset: NativePointer, value: number) => void;
+    setHeapF64: (offset: NativePointer, value: number) => void;
+    getHeapB32: (offset: NativePointer) => boolean;
+    getHeapU8: (offset: NativePointer) => number;
+    getHeapU16: (offset: NativePointer) => number;
+    getHeapU32: (offset: NativePointer) => number;
+    getHeapI8: (offset: NativePointer) => number;
+    getHeapI16: (offset: NativePointer) => number;
+    getHeapI32: (offset: NativePointer) => number;
+    getHeapI52: (offset: NativePointer) => number;
+    getHeapU52: (offset: NativePointer) => number;
+    getHeapI64Big: (offset: NativePointer) => bigint;
+    getHeapF32: (offset: NativePointer) => number;
+    getHeapF64: (offset: NativePointer) => number;
+};
+declare type RuntimeAPI = {
+    /**
+     * @deprecated Please use API object instead. See also MONOType in dotnet-legacy.d.ts
+     */
+    MONO: any;
+    /**
+     * @deprecated Please use API object instead. See also BINDINGType in dotnet-legacy.d.ts
+     */
+    BINDING: any;
+    INTERNAL: any;
+    Module: EmscriptenModule;
+    runtimeId: number;
+    runtimeBuildInfo: {
+        productVersion: string;
+        gitHash: string;
+        buildConfiguration: string;
+    };
+} & APIType;
+declare type ModuleAPI = {
+    dotnet: DotnetHostBuilder;
+    exit: (code: number, reason?: any) => void;
+};
+declare function createDotnetRuntime(moduleFactory: DotnetModuleConfig | ((api: RuntimeAPI) => DotnetModuleConfig)): Promise<RuntimeAPI>;
+declare type CreateDotnetRuntimeType = typeof createDotnetRuntime;
+
+declare global {
+    function getDotnetRuntime(runtimeId: number): RuntimeAPI | undefined;
+}
+
+declare const dotnet: ModuleAPI["dotnet"];
+declare const exit: ModuleAPI["exit"];
+
+export { CreateDotnetRuntimeType, DotnetModuleConfig, EmscriptenModule, ModuleAPI, MonoConfig, RuntimeAPI, createDotnetRuntime as default, dotnet, exit };
+
+export interface IMemoryView {
+    /**
+     * copies elements from provided source to the wasm memory.
+     * target has to have the elements of the same type as the underlying C# array.
+     * same as TypedArray.set()
+     */
+    set(source: TypedArray, targetOffset?: number): void;
+    /**
+     * copies elements from wasm memory to provided target.
+     * target has to have the elements of the same type as the underlying C# array.
+     */
+    copyTo(target: TypedArray, sourceOffset?: number): void;
+    /**
+     * same as TypedArray.slice()
+     */
+    slice(start?: number, end?: number): TypedArray;
+
+    get length(): number;
+    get byteLength(): number;
+}

+ 6 - 0
src/iOS/Avalonia.iOS/Avalonia.iOS.csproj

@@ -4,6 +4,12 @@
     <SupportedOSPlatformVersion>13.0</SupportedOSPlatformVersion>
     <MSBuildEnableWorkloadResolver>true</MSBuildEnableWorkloadResolver>
   </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
+    <NoWarn></NoWarn>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
+    <NoWarn></NoWarn>
+  </PropertyGroup>
   <ItemGroup>
     <ProjectReference Include="..\..\Avalonia.Base\Avalonia.Base.csproj" />
     <ProjectReference Include="..\..\Skia\Avalonia.Skia\Avalonia.Skia.csproj" />

+ 4 - 4
src/iOS/Avalonia.iOS/CombinedSpan3.cs

@@ -16,7 +16,7 @@ internal ref struct CombinedSpan3<T>
 
     public int Length => Span1.Length + Span2.Length + Span3.Length;
 
-    void CopyFromSpan(ReadOnlySpan<T> from, ref int offset, ref Span<T> to)
+    void CopyFromSpan(ReadOnlySpan<T> from, int offset, ref Span<T> to)
     {
         if(to.Length == 0)
             return;
@@ -33,8 +33,8 @@ internal ref struct CombinedSpan3<T>
     
     public void CopyTo(Span<T> to, int offset)
     {
-        CopyFromSpan(Span1, ref offset, ref to);
-        CopyFromSpan(Span2, ref offset, ref to);
-        CopyFromSpan(Span3, ref offset, ref to);
+        CopyFromSpan(Span1, offset, ref to);
+        CopyFromSpan(Span2, offset, ref to);
+        CopyFromSpan(Span3, offset, ref to);
     }
 }

+ 25 - 0
tests/Avalonia.Controls.UnitTests/ComboBoxTests.cs

@@ -29,11 +29,36 @@ namespace Avalonia.Controls.UnitTests
             _helper.Down(target);
             _helper.Up(target);
             Assert.True(target.IsDropDownOpen);
+            Assert.True(target.Classes.Contains(ComboBox.pcDropdownOpen));
 
             _helper.Down(target);
             _helper.Up(target);
 
             Assert.False(target.IsDropDownOpen);
+            Assert.True(!target.Classes.Contains(ComboBox.pcDropdownOpen));
+        }
+
+        [Fact]
+        public void Clicking_On_Control_PseudoClass()
+        {
+            var target = new ComboBox
+            {
+                Items = new[] { "Foo", "Bar" },
+            };
+
+            _helper.Down(target);
+            Assert.True(target.Classes.Contains(ComboBox.pcPressed));
+            _helper.Up(target);
+            Assert.True(!target.Classes.Contains(ComboBox.pcPressed));
+            Assert.True(target.Classes.Contains(ComboBox.pcDropdownOpen));
+
+            _helper.Down(target);
+            Assert.True(target.Classes.Contains(ComboBox.pcPressed));
+            _helper.Up(target);
+            Assert.True(!target.Classes.Contains(ComboBox.pcPressed));
+
+            Assert.False(target.IsDropDownOpen);
+            Assert.True(!target.Classes.Contains(ComboBox.pcDropdownOpen));
         }
 
         [Fact]