From 7eb95255cb1055c7d981b6ff20b86a014c9dc570 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 15 Dec 2022 14:16:04 +0100 Subject: [PATCH 01/20] Don't mark all owned windows as dialogs. This was preventing owner windows of non-modal dialogs from receiving key window status, meaning that input was broken in combination with embedding (Avalonia controls did work however because the routing was carried out internal to Avalonia). --- native/Avalonia.Native/src/OSX/WindowImpl.mm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/native/Avalonia.Native/src/OSX/WindowImpl.mm b/native/Avalonia.Native/src/OSX/WindowImpl.mm index b6dacb6ce4..bbf419bccd 100644 --- a/native/Avalonia.Native/src/OSX/WindowImpl.mm +++ b/native/Avalonia.Native/src/OSX/WindowImpl.mm @@ -63,7 +63,7 @@ HRESULT WindowImpl::Show(bool activate, bool isDialog) { START_COM_CALL; @autoreleasepool { - _isDialog = isDialog || _parent != nullptr; + _isDialog = isDialog; WindowBaseImpl::Show(activate, isDialog); From 102b1b351bce7a75be28c51ac70e19d50f786126 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 15 Dec 2022 14:17:19 +0100 Subject: [PATCH 02/20] Add null check, Customer was seeing a crash in this method, don't have a repro but other uses of `_parent` are surrounded by a null check so this makes sense I think. --- native/Avalonia.Native/src/OSX/AvnWindow.mm | 3 +++ 1 file changed, 3 insertions(+) diff --git a/native/Avalonia.Native/src/OSX/AvnWindow.mm b/native/Avalonia.Native/src/OSX/AvnWindow.mm index 6611dcc73f..42c500716c 100644 --- a/native/Avalonia.Native/src/OSX/AvnWindow.mm +++ b/native/Avalonia.Native/src/OSX/AvnWindow.mm @@ -281,6 +281,9 @@ - (void)windowDidBecomeKey:(NSNotification *_Nonnull)notification { + if (_parent == nullptr) + return; + _parent->BringToFront(); dispatch_async(dispatch_get_main_queue(), ^{ From 872eca05b2df322077c273be1922201381520e12 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 16 Dec 2022 09:01:15 +0100 Subject: [PATCH 03/20] Disable minimize button on owned windows again. 7eb95255c regressed disabling the minimize button on owned windows. Split what was previously `IsDialog` into two flags: `IsModal` and `IsOwned` and use `IsOwned` to control the disabled state of the minimize button. --- native/Avalonia.Native/src/OSX/AvnWindow.mm | 2 +- .../Avalonia.Native/src/OSX/WindowBaseImpl.h | 2 +- .../Avalonia.Native/src/OSX/WindowBaseImpl.mm | 2 +- native/Avalonia.Native/src/OSX/WindowImpl.h | 6 ++++-- native/Avalonia.Native/src/OSX/WindowImpl.mm | 18 +++++++++++------- 5 files changed, 18 insertions(+), 12 deletions(-) diff --git a/native/Avalonia.Native/src/OSX/AvnWindow.mm b/native/Avalonia.Native/src/OSX/AvnWindow.mm index 42c500716c..d3b7b4ede6 100644 --- a/native/Avalonia.Native/src/OSX/AvnWindow.mm +++ b/native/Avalonia.Native/src/OSX/AvnWindow.mm @@ -44,7 +44,7 @@ -(bool) isDialog { - return _parent->IsDialog(); + return _parent->IsModal(); } -(double) getExtendedTitleBarHeight diff --git a/native/Avalonia.Native/src/OSX/WindowBaseImpl.h b/native/Avalonia.Native/src/OSX/WindowBaseImpl.h index 7fb002e54f..bb8d5fae75 100644 --- a/native/Avalonia.Native/src/OSX/WindowBaseImpl.h +++ b/native/Avalonia.Native/src/OSX/WindowBaseImpl.h @@ -96,7 +96,7 @@ BEGIN_INTERFACE_MAP() IAvnClipboard *clipboard, IAvnDndResultCallback *cb, void *sourceHandle) override; - virtual bool IsDialog(); + virtual bool IsModal(); id GetWindowProtocol (); diff --git a/native/Avalonia.Native/src/OSX/WindowBaseImpl.mm b/native/Avalonia.Native/src/OSX/WindowBaseImpl.mm index 9946ad9b10..9650290fbb 100644 --- a/native/Avalonia.Native/src/OSX/WindowBaseImpl.mm +++ b/native/Avalonia.Native/src/OSX/WindowBaseImpl.mm @@ -542,7 +542,7 @@ HRESULT WindowBaseImpl::BeginDragAndDropOperation(AvnDragDropEffects effects, Av return S_OK; } -bool WindowBaseImpl::IsDialog() { +bool WindowBaseImpl::IsModal() { return false; } diff --git a/native/Avalonia.Native/src/OSX/WindowImpl.h b/native/Avalonia.Native/src/OSX/WindowImpl.h index b4b1d4e70b..3861aaf170 100644 --- a/native/Avalonia.Native/src/OSX/WindowImpl.h +++ b/native/Avalonia.Native/src/OSX/WindowImpl.h @@ -23,7 +23,7 @@ private: NSRect _preZoomSize; bool _transitioningWindowState; bool _isClientAreaExtended; - bool _isDialog; + bool _isModal; WindowImpl* _parent; std::list _children; AvnExtendClientAreaChromeHints _extendClientHints; @@ -91,7 +91,9 @@ BEGIN_INTERFACE_MAP() virtual HRESULT SetWindowState (AvnWindowState state) override; - virtual bool IsDialog() override; + virtual bool IsModal() override; + + bool IsOwned(); virtual void BringToFront () override; diff --git a/native/Avalonia.Native/src/OSX/WindowImpl.mm b/native/Avalonia.Native/src/OSX/WindowImpl.mm index bbf419bccd..f345043f61 100644 --- a/native/Avalonia.Native/src/OSX/WindowImpl.mm +++ b/native/Avalonia.Native/src/OSX/WindowImpl.mm @@ -63,7 +63,7 @@ HRESULT WindowImpl::Show(bool activate, bool isDialog) { START_COM_CALL; @autoreleasepool { - _isDialog = isDialog; + _isModal = isDialog; WindowBaseImpl::Show(activate, isDialog); @@ -97,7 +97,7 @@ HRESULT WindowImpl::SetParent(IAvnWindow *parent) { _parent = cparent; - _isDialog = _parent != nullptr; + _isModal = _parent != nullptr; if(_parent != nullptr && Window != nullptr){ // If one tries to show a child window with a minimized parent window, then the parent window will be @@ -123,7 +123,7 @@ void WindowImpl::BringToFront() { if ([Window isVisible] && ![Window isMiniaturized]) { - if(IsDialog()) + if(IsModal()) { Activate(); } @@ -150,7 +150,7 @@ bool WindowImpl::CanBecomeKeyWindow() { for(auto iterator = _children.begin(); iterator != _children.end(); iterator++) { - if((*iterator)->IsDialog()) + if((*iterator)->IsModal()) { return false; } @@ -569,8 +569,12 @@ HRESULT WindowImpl::SetWindowState(AvnWindowState state) { } } -bool WindowImpl::IsDialog() { - return _isDialog; +bool WindowImpl::IsModal() { + return _isModal; +} + +bool WindowImpl::IsOwned() { + return _parent != nullptr; } NSWindowStyleMask WindowImpl::GetStyle() { @@ -599,7 +603,7 @@ NSWindowStyleMask WindowImpl::GetStyle() { break; } - if (!IsDialog()) { + if (!IsOwned()) { s |= NSWindowStyleMaskMiniaturizable; } From fd7c89b611f3cb18d92cf06d726689fd00eaf804 Mon Sep 17 00:00:00 2001 From: Max Katz Date: Fri, 6 Jan 2023 02:01:55 -0500 Subject: [PATCH 04/20] Initial API design for the PlatformColorValues --- .../Platform/DefaultPlatformSettings.cs | 24 ++++++++--- .../Platform/IPlatformSettings.cs | 15 ++++++- .../Platform/PlatformColorValues.cs | 42 +++++++++++++++++++ 3 files changed, 74 insertions(+), 7 deletions(-) create mode 100644 src/Avalonia.Base/Platform/PlatformColorValues.cs diff --git a/src/Avalonia.Base/Platform/DefaultPlatformSettings.cs b/src/Avalonia.Base/Platform/DefaultPlatformSettings.cs index d0b27b057f..98f2367819 100644 --- a/src/Avalonia.Base/Platform/DefaultPlatformSettings.cs +++ b/src/Avalonia.Base/Platform/DefaultPlatformSettings.cs @@ -1,15 +1,15 @@ using System; using Avalonia.Input; +using Avalonia.Media; namespace Avalonia.Platform { /// - /// A default implementation of for platforms which don't have - /// an OS-specific implementation. + /// A default implementation of for platforms. /// public class DefaultPlatformSettings : IPlatformSettings { - public Size GetTapSize(PointerType type) + public virtual Size GetTapSize(PointerType type) { return type switch { @@ -17,7 +17,7 @@ namespace Avalonia.Platform _ => new(4, 4), }; } - public Size GetDoubleTapSize(PointerType type) + public virtual Size GetDoubleTapSize(PointerType type) { return type switch { @@ -25,8 +25,20 @@ namespace Avalonia.Platform _ => new(4, 4), }; } - public TimeSpan GetDoubleTapTime(PointerType type) => TimeSpan.FromMilliseconds(500); + public virtual TimeSpan GetDoubleTapTime(PointerType type) => TimeSpan.FromMilliseconds(500); - public TimeSpan HoldWaitDuration { get; set; } = TimeSpan.FromMilliseconds(300); + public virtual TimeSpan HoldWaitDuration => TimeSpan.FromMilliseconds(300); + + public virtual PlatformColorValues GetColorValues() + { + return new PlatformColorValues(PlatformThemeVariant.Light); + } + + public event EventHandler? ColorValuesChanged; + + protected void OnColorValuesChanged(PlatformColorValues colorValues) + { + ColorValuesChanged?.Invoke(this, colorValues); + } } } diff --git a/src/Avalonia.Base/Platform/IPlatformSettings.cs b/src/Avalonia.Base/Platform/IPlatformSettings.cs index 7c4c1420eb..b167cf0edc 100644 --- a/src/Avalonia.Base/Platform/IPlatformSettings.cs +++ b/src/Avalonia.Base/Platform/IPlatformSettings.cs @@ -28,6 +28,19 @@ namespace Avalonia.Platform /// TimeSpan GetDoubleTapTime(PointerType type); - TimeSpan HoldWaitDuration { get; set; } + /// + /// Holding duration between pointer press and when event is fired. + /// + TimeSpan HoldWaitDuration { get; } + + /// + /// Gets current system color values including dark mode and accent colors. + /// + PlatformColorValues GetColorValues(); + + /// + /// Raises when current system color values are changed. Including changing of a dark mode and accent colors. + /// + event EventHandler? ColorValuesChanged; } } diff --git a/src/Avalonia.Base/Platform/PlatformColorValues.cs b/src/Avalonia.Base/Platform/PlatformColorValues.cs new file mode 100644 index 0000000000..3071cc217f --- /dev/null +++ b/src/Avalonia.Base/Platform/PlatformColorValues.cs @@ -0,0 +1,42 @@ +using Avalonia.Media; + +namespace Avalonia.Platform; + +/// +/// System theme variant or mode. +/// +public enum PlatformThemeVariant +{ + Light, + Dark +} + +/// +/// Information about current system color values, including information about dark mode and accent colors. +/// +/// System theme variant or mode. +/// Primary system accent color. +/// Secondary system accent color. On some platforms can return the same value as AccentColor1. +/// Tertiary system accent color. On some platforms can return the same value as AccentColor1. +public record struct PlatformColorValues( + PlatformThemeVariant ThemeVariant, + Color AccentColor1, + Color AccentColor2, + Color AccentColor3) +{ + public PlatformColorValues( + PlatformThemeVariant ThemeVariant, + Color AccentColor1) + : this(ThemeVariant, AccentColor1, AccentColor1, AccentColor1) + { + + } + + public PlatformColorValues(PlatformThemeVariant ThemeVariant) + : this(ThemeVariant, DefaultAccent) + { + + } + + private static Color DefaultAccent => new(255, 0, 120, 215); +} From ad4005ef05ba4b4a6fa0679ea0e4132119c12216 Mon Sep 17 00:00:00 2001 From: Max Katz Date: Fri, 6 Jan 2023 02:02:12 -0500 Subject: [PATCH 05/20] Use PlatformColorValues in the control catalog --- samples/ControlCatalog/MainView.xaml.cs | 73 ++++++++++++++++++++++++- 1 file changed, 72 insertions(+), 1 deletion(-) diff --git a/samples/ControlCatalog/MainView.xaml.cs b/samples/ControlCatalog/MainView.xaml.cs index f3c1a68e72..ecd9d0bf52 100644 --- a/samples/ControlCatalog/MainView.xaml.cs +++ b/samples/ControlCatalog/MainView.xaml.cs @@ -3,9 +3,12 @@ using System.Collections; using Avalonia; using Avalonia.Controls; using Avalonia.Controls.ApplicationLifetimes; +using Avalonia.LogicalTree; using Avalonia.Markup.Xaml; using Avalonia.Media; using Avalonia.Media.Immutable; +using Avalonia.Platform; +using Avalonia.Styling; using Avalonia.VisualTree; using ControlCatalog.Models; using ControlCatalog.Pages; @@ -14,10 +17,14 @@ namespace ControlCatalog { public class MainView : UserControl { + private readonly IPlatformSettings _platformSettings; + public MainView() { AvaloniaXamlLoader.Load(this); - + _platformSettings = AvaloniaLocator.Current.GetRequiredService(); + PlatformSettingsOnColorValuesChanged(_platformSettings, _platformSettings.GetColorValues()); + var sideBar = this.Get("Sidebar"); if (Application.Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime) @@ -37,6 +44,15 @@ namespace ControlCatalog if (themes.SelectedItem is CatalogTheme theme) { App.SetThemeVariant(theme); + + ((TopLevel?)this.GetVisualRoot())?.PlatformImpl?.SetFrameThemeVariant(theme switch + { + CatalogTheme.FluentLight => PlatformThemeVariant.Light, + CatalogTheme.FluentDark => PlatformThemeVariant.Dark, + CatalogTheme.SimpleLight => PlatformThemeVariant.Light, + CatalogTheme.SimpleDark => PlatformThemeVariant.Dark, + _ => throw new ArgumentOutOfRangeException() + }); } }; @@ -89,6 +105,61 @@ namespace ControlCatalog var decorations = this.Get("Decorations"); if (VisualRoot is Window window) decorations.SelectedIndex = (int)window.SystemDecorations; + + _platformSettings.ColorValuesChanged += PlatformSettingsOnColorValuesChanged; + } + + protected override void OnDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e) + { + base.OnDetachedFromLogicalTree(e); + + _platformSettings.ColorValuesChanged -= PlatformSettingsOnColorValuesChanged; + } + + private void PlatformSettingsOnColorValuesChanged(object? sender, PlatformColorValues e) + { + var themes = this.Get("Themes"); + var currentTheme = (CatalogTheme?)themes.SelectedItem ?? CatalogTheme.FluentLight; + var newTheme = (currentTheme, e.ThemeVariant) switch + { + (CatalogTheme.FluentDark, PlatformThemeVariant.Light) => CatalogTheme.FluentLight, + (CatalogTheme.FluentLight, PlatformThemeVariant.Dark) => CatalogTheme.FluentDark, + (CatalogTheme.SimpleDark, PlatformThemeVariant.Light) => CatalogTheme.SimpleLight, + (CatalogTheme.SimpleLight, PlatformThemeVariant.Dark) => CatalogTheme.SimpleDark, + _ => currentTheme + }; + themes.SelectedItem = newTheme; + + Application.Current!.Resources["SystemAccentColor"] = e.AccentColor1; + Application.Current.Resources["SystemAccentColorDark1"] = ChangeColorLuminosity(e.AccentColor1, -0.3); + Application.Current.Resources["SystemAccentColorDark2"] = ChangeColorLuminosity(e.AccentColor1, -0.5); + Application.Current.Resources["SystemAccentColorDark3"] = ChangeColorLuminosity(e.AccentColor1, -0.7); + Application.Current.Resources["SystemAccentColorLight1"] = ChangeColorLuminosity(e.AccentColor1, -0.3); + Application.Current.Resources["SystemAccentColorLight2"] = ChangeColorLuminosity(e.AccentColor1, -0.5); + Application.Current.Resources["SystemAccentColorLight3"] = ChangeColorLuminosity(e.AccentColor1, -0.7); + + static Color ChangeColorLuminosity(Color color, double luminosityFactor) + { + var red = (double)color.R; + var green = (double)color.G; + var blue = (double)color.B; + + if (luminosityFactor < 0) + { + luminosityFactor = 1 + luminosityFactor; + red *= luminosityFactor; + green *= luminosityFactor; + blue *= luminosityFactor; + } + else if (luminosityFactor >= 0) + { + red = (255 - red) * luminosityFactor + red; + green = (255 - green) * luminosityFactor + green; + blue = (255 - blue) * luminosityFactor + blue; + } + + return new Color(color.A, (byte)red, (byte)green, (byte)blue); + } } } } From 36f93ae7bc71fd2f54cc36b78706546e0d9ca501 Mon Sep 17 00:00:00 2001 From: Max Katz Date: Fri, 6 Jan 2023 02:14:16 -0500 Subject: [PATCH 06/20] Update tests and designer --- .../Offscreen/OffscreenTopLevelImpl.cs | 2 + .../Platform/ITopLevelImpl.cs | 6 + src/Avalonia.DesignerSupport/Remote/Stubs.cs | 4 +- src/Avalonia.Headless/HeadlessWindowImpl.cs | 5 + .../Rendering/CompositorTestsBase.cs | 2 + .../HtmlTransport/webapp/package-lock.json | 2665 ++++++++++++++++- 6 files changed, 2682 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs b/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs index 4a227a0c00..64f96b6987 100644 --- a/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs +++ b/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs @@ -58,6 +58,8 @@ namespace Avalonia.Controls.Embedding.Offscreen public Action? TransparencyLevelChanged { get; set; } + public void SetFrameThemeVariant(PlatformThemeVariant themeVariant) { } + /// public AcrylicPlatformCompensationLevels AcrylicCompensationLevels { get; } = new AcrylicPlatformCompensationLevels(1, 1, 1); diff --git a/src/Avalonia.Controls/Platform/ITopLevelImpl.cs b/src/Avalonia.Controls/Platform/ITopLevelImpl.cs index 6bb94c6070..bf74e0f8f4 100644 --- a/src/Avalonia.Controls/Platform/ITopLevelImpl.cs +++ b/src/Avalonia.Controls/Platform/ITopLevelImpl.cs @@ -163,6 +163,12 @@ namespace Avalonia.Platform /// WindowTransparencyLevel TransparencyLevel { get; } + /// + /// Sets the on the frame if it should be dark or light. + /// Also applies for the mobile status bar. + /// + void SetFrameThemeVariant(PlatformThemeVariant themeVariant); + /// /// Gets the for the platform. /// diff --git a/src/Avalonia.DesignerSupport/Remote/Stubs.cs b/src/Avalonia.DesignerSupport/Remote/Stubs.cs index 94679e8ade..4bb98c0267 100644 --- a/src/Avalonia.DesignerSupport/Remote/Stubs.cs +++ b/src/Avalonia.DesignerSupport/Remote/Stubs.cs @@ -182,7 +182,9 @@ namespace Avalonia.DesignerSupport.Remote public bool IsClientAreaExtendedToDecorations { get; } public bool NeedsManagedDecorations => false; - + + public void SetFrameThemeVariant(PlatformThemeVariant themeVariant) { } + public AcrylicPlatformCompensationLevels AcrylicCompensationLevels { get; } = new AcrylicPlatformCompensationLevels(1, 1, 1); } diff --git a/src/Avalonia.Headless/HeadlessWindowImpl.cs b/src/Avalonia.Headless/HeadlessWindowImpl.cs index 8eafce208b..195328cd65 100644 --- a/src/Avalonia.Headless/HeadlessWindowImpl.cs +++ b/src/Avalonia.Headless/HeadlessWindowImpl.cs @@ -345,5 +345,10 @@ namespace Avalonia.Headless { } + + public void SetFrameThemeVariant(PlatformThemeVariant themeVariant) + { + + } } } diff --git a/tests/Avalonia.Base.UnitTests/Rendering/CompositorTestsBase.cs b/tests/Avalonia.Base.UnitTests/Rendering/CompositorTestsBase.cs index 7f4e160000..12db249f04 100644 --- a/tests/Avalonia.Base.UnitTests/Rendering/CompositorTestsBase.cs +++ b/tests/Avalonia.Base.UnitTests/Rendering/CompositorTestsBase.cs @@ -104,6 +104,8 @@ public class CompositorTestsBase } public WindowTransparencyLevel TransparencyLevel { get; } + public void SetFrameThemeVariant(PlatformThemeVariant themeVariant) { } + public AcrylicPlatformCompensationLevels AcrylicCompensationLevels { get; } } diff --git a/tests/Avalonia.DesignerSupport.Tests/Remote/HtmlTransport/webapp/package-lock.json b/tests/Avalonia.DesignerSupport.Tests/Remote/HtmlTransport/webapp/package-lock.json index 0c0857b680..0b92805712 100644 --- a/tests/Avalonia.DesignerSupport.Tests/Remote/HtmlTransport/webapp/package-lock.json +++ b/tests/Avalonia.DesignerSupport.Tests/Remote/HtmlTransport/webapp/package-lock.json @@ -1,8 +1,2671 @@ { "name": "simple-test", "version": "1.0.0", - "lockfileVersion": 1, + "lockfileVersion": 2, "requires": true, + "packages": { + "": { + "name": "simple-test", + "version": "1.0.0", + "license": "MIT", + "devDependencies": { + "@types/chai": "4.2.12", + "@types/mocha": "8.0.3", + "@types/react": "^16.3.14", + "chai": "^4.2.0", + "mocha": "^10.2.0", + "moq.ts": "^6.4.0", + "nyc": "^15.1.0", + "react": "^16.3.2", + "ts-node": "^9.0.0", + "typescript": "^4.0.2" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.4.tgz", + "integrity": "sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg==", + "dev": true, + "dependencies": { + "@babel/highlight": "^7.10.4" + } + }, + "node_modules/@babel/core": { + "version": "7.11.6", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.11.6.tgz", + "integrity": "sha512-Wpcv03AGnmkgm6uS6k8iwhIwTrcP0m17TL1n1sy7qD0qelDu4XNeW0dN0mHfa+Gei211yDaLoEe/VlbXQzM4Bg==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.10.4", + "@babel/generator": "^7.11.6", + "@babel/helper-module-transforms": "^7.11.0", + "@babel/helpers": "^7.10.4", + "@babel/parser": "^7.11.5", + "@babel/template": "^7.10.4", + "@babel/traverse": "^7.11.5", + "@babel/types": "^7.11.5", + "convert-source-map": "^1.7.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.1", + "json5": "^2.1.2", + "lodash": "^4.17.19", + "resolve": "^1.3.2", + "semver": "^5.4.1", + "source-map": "^0.5.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/core/node_modules/semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/@babel/generator": { + "version": "7.11.6", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.11.6.tgz", + "integrity": "sha512-DWtQ1PV3r+cLbySoHrwn9RWEgKMBLLma4OBQloPRyDYvc5msJM9kvTLo1YnlJd1P/ZuKbdli3ijr5q3FvAF3uA==", + "dev": true, + "dependencies": { + "@babel/types": "^7.11.5", + "jsesc": "^2.5.1", + "source-map": "^0.5.0" + } + }, + "node_modules/@babel/helper-function-name": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.10.4.tgz", + "integrity": "sha512-YdaSyz1n8gY44EmN7x44zBn9zQ1Ry2Y+3GTA+3vH6Mizke1Vw0aWDM66FOYEPw8//qKkmqOckrGgTYa+6sceqQ==", + "dev": true, + "dependencies": { + "@babel/helper-get-function-arity": "^7.10.4", + "@babel/template": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "node_modules/@babel/helper-get-function-arity": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.10.4.tgz", + "integrity": "sha512-EkN3YDB+SRDgiIUnNgcmiD361ti+AVbL3f3Henf6dqqUyr5dMsorno0lJWJuLhDhkI5sYEpgj6y9kB8AOU1I2A==", + "dev": true, + "dependencies": { + "@babel/types": "^7.10.4" + } + }, + "node_modules/@babel/helper-member-expression-to-functions": { + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.11.0.tgz", + "integrity": "sha512-JbFlKHFntRV5qKw3YC0CvQnDZ4XMwgzzBbld7Ly4Mj4cbFy3KywcR8NtNctRToMWJOVvLINJv525Gd6wwVEx/Q==", + "dev": true, + "dependencies": { + "@babel/types": "^7.11.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.10.4.tgz", + "integrity": "sha512-nEQJHqYavI217oD9+s5MUBzk6x1IlvoS9WTPfgG43CbMEeStE0v+r+TucWdx8KFGowPGvyOkDT9+7DHedIDnVw==", + "dev": true, + "dependencies": { + "@babel/types": "^7.10.4" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.11.0.tgz", + "integrity": "sha512-02EVu8COMuTRO1TAzdMtpBPbe6aQ1w/8fePD2YgQmxZU4gpNWaL9gK3Jp7dxlkUlUCJOTaSeA+Hrm1BRQwqIhg==", + "dev": true, + "dependencies": { + "@babel/helper-module-imports": "^7.10.4", + "@babel/helper-replace-supers": "^7.10.4", + "@babel/helper-simple-access": "^7.10.4", + "@babel/helper-split-export-declaration": "^7.11.0", + "@babel/template": "^7.10.4", + "@babel/types": "^7.11.0", + "lodash": "^4.17.19" + } + }, + "node_modules/@babel/helper-optimise-call-expression": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.10.4.tgz", + "integrity": "sha512-n3UGKY4VXwXThEiKrgRAoVPBMqeoPgHVqiHZOanAJCG9nQUL2pLRQirUzl0ioKclHGpGqRgIOkgcIJaIWLpygg==", + "dev": true, + "dependencies": { + "@babel/types": "^7.10.4" + } + }, + "node_modules/@babel/helper-replace-supers": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.10.4.tgz", + "integrity": "sha512-sPxZfFXocEymYTdVK1UNmFPBN+Hv5mJkLPsYWwGBxZAxaWfFu+xqp7b6qWD0yjNuNL2VKc6L5M18tOXUP7NU0A==", + "dev": true, + "dependencies": { + "@babel/helper-member-expression-to-functions": "^7.10.4", + "@babel/helper-optimise-call-expression": "^7.10.4", + "@babel/traverse": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "node_modules/@babel/helper-simple-access": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.10.4.tgz", + "integrity": "sha512-0fMy72ej/VEvF8ULmX6yb5MtHG4uH4Dbd6I/aHDb/JVg0bbivwt9Wg+h3uMvX+QSFtwr5MeItvazbrc4jtRAXw==", + "dev": true, + "dependencies": { + "@babel/template": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "node_modules/@babel/helper-split-export-declaration": { + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.11.0.tgz", + "integrity": "sha512-74Vejvp6mHkGE+m+k5vHY93FX2cAtrw1zXrZXRlG4l410Nm9PxfEiVTn1PjDPV5SnmieiueY4AFg2xqhNFuuZg==", + "dev": true, + "dependencies": { + "@babel/types": "^7.11.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.4.tgz", + "integrity": "sha512-3U9y+43hz7ZM+rzG24Qe2mufW5KhvFg/NhnNph+i9mgCtdTCtMJuI1TMkrIUiK7Ix4PYlRF9I5dhqaLYA/ADXw==", + "dev": true + }, + "node_modules/@babel/helpers": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.10.4.tgz", + "integrity": "sha512-L2gX/XeUONeEbI78dXSrJzGdz4GQ+ZTA/aazfUsFaWjSe95kiCuOZ5HsXvkiw3iwF+mFHSRUfJU8t6YavocdXA==", + "dev": true, + "dependencies": { + "@babel/template": "^7.10.4", + "@babel/traverse": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "node_modules/@babel/highlight": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.4.tgz", + "integrity": "sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA==", + "dev": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.10.4", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + } + }, + "node_modules/@babel/highlight/node_modules/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, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/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, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/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, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/@babel/highlight/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "node_modules/@babel/highlight/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@babel/highlight/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/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, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/parser": { + "version": "7.11.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.11.5.tgz", + "integrity": "sha512-X9rD8qqm695vgmeaQ4fvz/o3+Wk4ZzQvSHkDBgpYKxpD4qTAUm88ZKtHkVqIOsYFFbIQ6wQYhC6q7pjqVK0E0Q==", + "dev": true, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/template": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.10.4.tgz", + "integrity": "sha512-ZCjD27cGJFUB6nmCB1Enki3r+L5kJveX9pq1SvAUKoICy6CZ9yD8xO086YXdYhvNjBdnekm4ZnaP5yC8Cs/1tA==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.10.4", + "@babel/parser": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "node_modules/@babel/traverse": { + "version": "7.11.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.11.5.tgz", + "integrity": "sha512-EjiPXt+r7LiCZXEfRpSJd+jUMnBd4/9OUv7Nx3+0u9+eimMwJmG0Q98lw4/289JCoxSE8OolDMNZaaF/JZ69WQ==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.10.4", + "@babel/generator": "^7.11.5", + "@babel/helper-function-name": "^7.10.4", + "@babel/helper-split-export-declaration": "^7.11.0", + "@babel/parser": "^7.11.5", + "@babel/types": "^7.11.5", + "debug": "^4.1.0", + "globals": "^11.1.0", + "lodash": "^4.17.19" + } + }, + "node_modules/@babel/types": { + "version": "7.11.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.11.5.tgz", + "integrity": "sha512-bvM7Qz6eKnJVFIn+1LPtjlBFPVN5jNDc1XmN15vWe7Q3DPBufWWsLiIvUu7xW87uTG6QoggpIDnUgLQvPheU+Q==", + "dev": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.10.4", + "lodash": "^4.17.19", + "to-fast-properties": "^2.0.0" + } + }, + "node_modules/@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "dev": true, + "dependencies": { + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.2.tgz", + "integrity": "sha512-tsAQNx32a8CoFhjhijUIhI4kccIAgmGhy8LZMZgGfmXcpMbPRUqn5LWmgRttILi6yeGmBJd2xsPkFMs0PzgPCw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@types/chai": { + "version": "4.2.12", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.2.12.tgz", + "integrity": "sha512-aN5IAC8QNtSUdQzxu7lGBgYAOuU1tmRU4c9dIq5OKGf/SBVjXo+ffM2wEjudAWbgpOhy60nLoAGH1xm8fpCKFQ==", + "dev": true + }, + "node_modules/@types/color-name": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz", + "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==", + "dev": true + }, + "node_modules/@types/mocha": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-8.0.3.tgz", + "integrity": "sha512-vyxR57nv8NfcU0GZu8EUXZLTbCMupIUwy95LJ6lllN+JRPG25CwMHoB1q5xKh8YKhQnHYRAn4yW2yuHbf/5xgg==", + "dev": true + }, + "node_modules/@types/prop-types": { + "version": "15.7.3", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.3.tgz", + "integrity": "sha512-KfRL3PuHmqQLOG+2tGpRO26Ctg+Cq1E01D2DMriKEATHgWLfeNDmq9e29Q9WIky0dQ3NPkd1mzYH8Lm936Z9qw==", + "dev": true + }, + "node_modules/@types/react": { + "version": "16.9.49", + "resolved": "https://registry.npmjs.org/@types/react/-/react-16.9.49.tgz", + "integrity": "sha512-DtLFjSj0OYAdVLBbyjhuV9CdGVHCkHn2R+xr3XkBvK2rS1Y1tkc14XSGjYgm5Fjjr90AxH9tiSzc1pCFMGO06g==", + "dev": true, + "dependencies": { + "@types/prop-types": "*", + "csstype": "^3.0.2" + } + }, + "node_modules/aggregate-error": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", + "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", + "dev": true, + "dependencies": { + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-colors": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", + "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/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, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", + "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "dev": true, + "dependencies": { + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/append-transform": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/append-transform/-/append-transform-2.0.0.tgz", + "integrity": "sha512-7yeyCEurROLQJFv5Xj4lEGTy0borxepjFv1g22oAdqFu//SrAlDl1O1Nxx15SH1RoliUml6p8dwJW9jvZughhg==", + "dev": true, + "dependencies": { + "default-require-extensions": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/archy": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz", + "integrity": "sha1-+cjBN1fMHde8N5rHeyxipcKGjEA=", + "dev": true + }, + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true + }, + "node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/assertion-error": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", + "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true + }, + "node_modules/binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/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, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browser-stdout": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", + "dev": true + }, + "node_modules/buffer-from": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", + "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", + "dev": true + }, + "node_modules/caching-transform": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/caching-transform/-/caching-transform-4.0.0.tgz", + "integrity": "sha512-kpqOvwXnjjN44D89K5ccQC+RUrsy7jB/XLlRrx0D7/2HNcTPqzsb6XgYoErwko6QsV184CA2YgS1fxDiiDZMWA==", + "dev": true, + "dependencies": { + "hasha": "^5.0.0", + "make-dir": "^3.0.0", + "package-hash": "^4.0.0", + "write-file-atomic": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/chai": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.2.0.tgz", + "integrity": "sha512-XQU3bhBukrOsQCuwZndwGcCVQHyZi53fQ6Ys1Fym7E4olpIqqZZhhoFJoaKVvV17lWQoXYwgWN2nF5crA8J2jw==", + "dev": true, + "dependencies": { + "assertion-error": "^1.1.0", + "check-error": "^1.0.2", + "deep-eql": "^3.0.1", + "get-func-name": "^2.0.0", + "pathval": "^1.1.0", + "type-detect": "^4.0.5" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/check-error": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", + "integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/chokidar": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ], + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/clean-stack": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", + "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "node_modules/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, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/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 + }, + "node_modules/commondir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=", + "dev": true + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "node_modules/convert-source-map": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.7.0.tgz", + "integrity": "sha512-4FJkXzKXEDB1snCFZlLP4gpC3JILicCpGbzG9f9G7tGqGCzETQ2hWPrcinA9oU4wtf2biUaEH5065UnMeR33oA==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.1.1" + } + }, + "node_modules/convert-source-map/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "node_modules/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, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/csstype": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.3.tgz", + "integrity": "sha512-jPl+wbWPOWJ7SXsWyqGRk3lGecbar0Cb0OvZF/r/ZU011R4YqiRehgkQ9p4eQfo9DSDLqLL3wHwfxeJiuIsNag==", + "dev": true + }, + "node_modules/debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "deprecated": "Debug versions >=3.2.0 <3.2.7 || >=4 <4.3.1 have a low-severity ReDos regression when used in a Node.js environment. It is recommended you upgrade to 3.2.7 or 4.3.1. (https://github.com/visionmedia/debug/issues/797)", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/deep-eql": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz", + "integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==", + "dev": true, + "dependencies": { + "type-detect": "^4.0.0" + }, + "engines": { + "node": ">=0.12" + } + }, + "node_modules/default-require-extensions": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/default-require-extensions/-/default-require-extensions-3.0.0.tgz", + "integrity": "sha512-ek6DpXq/SCpvjhpFsLFRVtIxJCRw6fUR42lYMVZuUMK7n8eMz4Uh5clckdBjEpLhn/gEBZo7hDJnJcwdKLKQjg==", + "dev": true, + "dependencies": { + "strip-bom": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/es6-error": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz", + "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==", + "dev": true + }, + "node_modules/escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/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, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/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, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-cache-dir": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.1.tgz", + "integrity": "sha512-t2GDMt3oGC/v+BMwzmllWDuJF/xcDtE5j/fCGbqDD7OLuJkj0cfh1YSA5VKPvwMeLFLNDBkwOKZ2X85jGLVftQ==", + "dev": true, + "dependencies": { + "commondir": "^1.0.1", + "make-dir": "^3.0.2", + "pkg-dir": "^4.1.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/avajs/find-cache-dir?sponsor=1" + } + }, + "node_modules/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, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "dev": true, + "bin": { + "flat": "cli.js" + } + }, + "node_modules/foreground-child": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-2.0.0.tgz", + "integrity": "sha512-dCIq9FpEcyQyXKCkyzmlPTFNgrCzPudOe+mhvJU5zAtlBnGVy2yKxtfsxK2tQBThwq225jcvBjpw1Gr40uzZCA==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/fromentries": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/fromentries/-/fromentries-1.2.1.tgz", + "integrity": "sha512-Xu2Qh8yqYuDhQGOhD5iJGninErSfI9A3FrriD3tjUgV5VbJFeH8vfgZ9HnC6jWN80QDVNQK5vmxRAmEAp7Mevw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.1", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.1.tgz", + "integrity": "sha512-r8EC6NO1sngH/zdD9fiRDLdcgnbayXah+mLgManTaIZJqEC1MZstmnox8KpnI2/fxQwrp5OpCOYWLp4rBl4Jcg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-func-name": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", + "integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "dev": true, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/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, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", + "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==", + "dev": true + }, + "node_modules/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, + "engines": { + "node": ">=8" + } + }, + "node_modules/hasha": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/hasha/-/hasha-5.2.0.tgz", + "integrity": "sha512-2W+jKdQbAdSIrggA8Q35Br8qKadTrqCTC8+XZvBWepKDK6m9XkX6Iz1a2yh2KP01kzAR/dpuMeUnocoLYDcskw==", + "dev": true, + "dependencies": { + "is-stream": "^2.0.0", + "type-fest": "^0.8.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true, + "bin": { + "he": "bin/he" + } + }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", + "dev": true, + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/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, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/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, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-plain-obj": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz", + "integrity": "sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", + "dev": true + }, + "node_modules/is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-windows": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", + "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "node_modules/istanbul-lib-coverage": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.0.0.tgz", + "integrity": "sha512-UiUIqxMgRDET6eR+o5HbfRYP1l0hqkWOs7vNxC/mggutCMUIhWMm8gAHb8tHlyfD3/l6rlgNA5cKdDzEAf6hEg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-hook": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-hook/-/istanbul-lib-hook-3.0.0.tgz", + "integrity": "sha512-Pt/uge1Q9s+5VAZ+pCo16TYMWPBIl+oaNIjgLQxcX0itS6ueeaA+pEfThZpH8WxhFgCiEb8sAJY6MdUKgiIWaQ==", + "dev": true, + "dependencies": { + "append-transform": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-instrument": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-4.0.3.tgz", + "integrity": "sha512-BXgQl9kf4WTCPCCpmFGoJkz/+uhvm7h7PFKUYxh7qarQd3ER33vHG//qaE8eN25l07YqZPpHXU9I09l/RD5aGQ==", + "dev": true, + "dependencies": { + "@babel/core": "^7.7.5", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.0.0", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-processinfo": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-processinfo/-/istanbul-lib-processinfo-2.0.2.tgz", + "integrity": "sha512-kOwpa7z9hme+IBPZMzQ5vdQj8srYgAtaRqeI48NGmAQ+/5yKiHLV0QbYqQpxsdEF0+w14SoB8YbnHKcXE2KnYw==", + "dev": true, + "dependencies": { + "archy": "^1.0.0", + "cross-spawn": "^7.0.0", + "istanbul-lib-coverage": "^3.0.0-alpha.1", + "make-dir": "^3.0.0", + "p-map": "^3.0.0", + "rimraf": "^3.0.0", + "uuid": "^3.3.3" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", + "integrity": "sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==", + "dev": true, + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^3.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-source-maps": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.0.tgz", + "integrity": "sha512-c16LpFRkR8vQXyHZ5nLpY35JZtzj1PQY1iZmesUbf1FZHbIupcWfjgOXBY9YHkLEQ6puz1u4Dgj6qmU/DisrZg==", + "dev": true, + "dependencies": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-source-maps/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/istanbul-reports": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.0.2.tgz", + "integrity": "sha512-9tZvz7AiR3PEDNGiV9vIouQ/EAcqMXFmkcA1CDFTwOB98OZVDL0PH9glHotf5Ugp6GCOTypfzGWI/OqjWNCRUw==", + "dev": true, + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "node_modules/js-yaml": { + "version": "3.14.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.0.tgz", + "integrity": "sha512-/4IbIeHcD9VMHFqDR/gQ7EdZdLimOvW2DdcxFjdyyZ9NsbS+ccrXqVWDtab/lRl5AlUqmpBx8EhPaWR+OtY17A==", + "dev": true, + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "dev": true, + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/json5": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.2.tgz", + "integrity": "sha512-46Tk9JiOL2z7ytNQWFLpj99RZkVgeHf87yGQKsIkaPz1qSH9UczKH1rO7K3wgRselo0tYMUNfecYpm/p1vC7tQ==", + "dev": true, + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/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, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash": { + "version": "4.17.20", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz", + "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==", + "dev": true + }, + "node_modules/lodash.flattendeep": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz", + "integrity": "sha1-+wMJF/hqMTTlvJvsDWngAT3f7bI=", + "dev": true + }, + "node_modules/log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "dev": true, + "dependencies": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "dev": true, + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dev": true, + "dependencies": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true + }, + "node_modules/minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/mocha": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.2.0.tgz", + "integrity": "sha512-IDY7fl/BecMwFHzoqF2sg/SHHANeBoMMXFlS9r0OXKDssYE1M5O43wUY/9BVPeIvfH2zmEbBfseqN9gBQZzXkg==", + "dev": true, + "dependencies": { + "ansi-colors": "4.1.1", + "browser-stdout": "1.3.1", + "chokidar": "3.5.3", + "debug": "4.3.4", + "diff": "5.0.0", + "escape-string-regexp": "4.0.0", + "find-up": "5.0.0", + "glob": "7.2.0", + "he": "1.2.0", + "js-yaml": "4.1.0", + "log-symbols": "4.1.0", + "minimatch": "5.0.1", + "ms": "2.1.3", + "nanoid": "3.3.3", + "serialize-javascript": "6.0.0", + "strip-json-comments": "3.1.1", + "supports-color": "8.1.1", + "workerpool": "6.2.1", + "yargs": "16.2.0", + "yargs-parser": "20.2.4", + "yargs-unparser": "2.0.0" + }, + "bin": { + "_mocha": "bin/_mocha", + "mocha": "bin/mocha.js" + }, + "engines": { + "node": ">= 14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mochajs" + } + }, + "node_modules/mocha/node_modules/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 + }, + "node_modules/mocha/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/mocha/node_modules/debug/node_modules/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 + }, + "node_modules/mocha/node_modules/diff": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", + "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/mocha/node_modules/glob": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", + "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/mocha/node_modules/glob/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/mocha/node_modules/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, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/mocha/node_modules/minimatch": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.0.1.tgz", + "integrity": "sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/mocha/node_modules/minimatch/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/mocha/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + "node_modules/mocha/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/moq.ts": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/moq.ts/-/moq.ts-6.4.0.tgz", + "integrity": "sha512-g9siyKOvYGVhfDCl9mpLptch93egRC3yXIbPLssxukOROl368XBB+6M7YjLTkjVRHbKbpJBD6P7od+Ky/0xWIw==", + "dev": true, + "dependencies": { + "tslib": "^2.0.0" + } + }, + "node_modules/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 + }, + "node_modules/nanoid": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.3.tgz", + "integrity": "sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w==", + "dev": true, + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/node-preload": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/node-preload/-/node-preload-0.2.1.tgz", + "integrity": "sha512-RM5oyBy45cLEoHqCeh+MNuFAxO0vTFBLskvQbOKnEE7YTTSN4tbN8QWDIPQ6L+WvKsB/qLEGpYe2ZZ9d4W9OIQ==", + "dev": true, + "dependencies": { + "process-on-spawn": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/nyc": { + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/nyc/-/nyc-15.1.0.tgz", + "integrity": "sha512-jMW04n9SxKdKi1ZMGhvUTHBN0EICCRkHemEoE5jm6mTYcqcdas0ATzgUgejlQUHMvpnOZqGB5Xxsv9KxJW1j8A==", + "dev": true, + "dependencies": { + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "caching-transform": "^4.0.0", + "convert-source-map": "^1.7.0", + "decamelize": "^1.2.0", + "find-cache-dir": "^3.2.0", + "find-up": "^4.1.0", + "foreground-child": "^2.0.0", + "get-package-type": "^0.1.0", + "glob": "^7.1.6", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-hook": "^3.0.0", + "istanbul-lib-instrument": "^4.0.0", + "istanbul-lib-processinfo": "^2.0.2", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.0", + "istanbul-reports": "^3.0.2", + "make-dir": "^3.0.0", + "node-preload": "^0.2.1", + "p-map": "^3.0.0", + "process-on-spawn": "^1.0.0", + "resolve-from": "^5.0.0", + "rimraf": "^3.0.0", + "signal-exit": "^3.0.2", + "spawn-wrap": "^2.0.0", + "test-exclude": "^6.0.0", + "yargs": "^15.0.2" + }, + "bin": { + "nyc": "bin/nyc.js" + }, + "engines": { + "node": ">=8.9" + } + }, + "node_modules/nyc/node_modules/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, + "engines": { + "node": ">=8" + } + }, + "node_modules/nyc/node_modules/cliui": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", + "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^6.2.0" + } + }, + "node_modules/nyc/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/nyc/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/nyc/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/nyc/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/nyc/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/nyc/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/nyc/node_modules/string-width": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", + "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/nyc/node_modules/strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/nyc/node_modules/wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/nyc/node_modules/yargs": { + "version": "15.4.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", + "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", + "dev": true, + "dependencies": { + "cliui": "^6.0.0", + "decamelize": "^1.2.0", + "find-up": "^4.1.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^4.2.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^18.1.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/nyc/node_modules/yargs-parser": { + "version": "18.1.3", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", + "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", + "dev": true, + "dependencies": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/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, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/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, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-map": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-3.0.0.tgz", + "integrity": "sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ==", + "dev": true, + "dependencies": { + "aggregate-error": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/package-hash": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/package-hash/-/package-hash-4.0.0.tgz", + "integrity": "sha512-whdkPIooSu/bASggZ96BWVvZTRMOFxnyUG5PnTSGKoJE2gd5mbVNmR2Nj20QFzxYYgAXpoqC+AiXzl+UMRh7zQ==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.1.15", + "hasha": "^5.0.0", + "lodash.flattendeep": "^4.4.0", + "release-zalgo": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/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, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/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, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", + "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", + "dev": true + }, + "node_modules/pathval": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", + "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pkg-dir/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/process-on-spawn": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/process-on-spawn/-/process-on-spawn-1.0.0.tgz", + "integrity": "sha512-1WsPDsUSMmZH5LeMLegqkPDrsGgsWwk1Exipy2hvB0o/F0ASzbpIctSCcZIK1ykJvtTJULEH+20WOFjMvGnCTg==", + "dev": true, + "dependencies": { + "fromentries": "^1.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/prop-types": { + "version": "15.7.2", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz", + "integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==", + "dev": true, + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.8.1" + } + }, + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, + "node_modules/react": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react/-/react-16.13.1.tgz", + "integrity": "sha512-YMZQQq32xHLX0bz5Mnibv1/LHb3Sqzngu7xstSM+vrkE5Kzr9xE0yMByK5kMoTK30YVJE61WfbxIFFvfeDKT1w==", + "dev": true, + "dependencies": { + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1", + "prop-types": "^15.6.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "dev": true + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/release-zalgo": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/release-zalgo/-/release-zalgo-1.0.0.tgz", + "integrity": "sha1-CXALflB0Mpc5Mw5TXFqQ+2eFFzA=", + "dev": true, + "dependencies": { + "es6-error": "^4.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-main-filename": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", + "dev": true + }, + "node_modules/resolve": { + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.17.0.tgz", + "integrity": "sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w==", + "dev": true, + "dependencies": { + "path-parse": "^1.0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/serialize-javascript": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", + "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", + "dev": true, + "dependencies": { + "randombytes": "^2.1.0" + } + }, + "node_modules/set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", + "dev": true + }, + "node_modules/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, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/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, + "engines": { + "node": ">=8" + } + }, + "node_modules/signal-exit": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", + "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==", + "dev": true + }, + "node_modules/source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.19", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz", + "integrity": "sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==", + "dev": true, + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/source-map-support/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/spawn-wrap": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/spawn-wrap/-/spawn-wrap-2.0.0.tgz", + "integrity": "sha512-EeajNjfN9zMnULLwhZZQU3GWBoFNkbngTUPfaawT4RkMiviTxcX0qfhVbGey39mfctfDHkWtuecgQ8NJcyQWHg==", + "dev": true, + "dependencies": { + "foreground-child": "^2.0.0", + "is-windows": "^1.0.2", + "make-dir": "^3.0.0", + "rimraf": "^3.0.0", + "signal-exit": "^3.0.2", + "which": "^2.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "dev": true + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/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, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-bom": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", + "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/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, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", + "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "dev": true, + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/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, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/ts-node": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-9.0.0.tgz", + "integrity": "sha512-/TqB4SnererCDR/vb4S/QvSZvzQMJN8daAslg7MeaiHvD8rDZsSfXmNeNumyZZzMned72Xoq/isQljYSt8Ynfg==", + "dev": true, + "dependencies": { + "arg": "^4.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "source-map-support": "^0.5.17", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "typescript": ">=2.7" + } + }, + "node_modules/tslib": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.0.1.tgz", + "integrity": "sha512-SgIkNheinmEBgx1IUNirK0TUD4X9yjjBRTqqjggWCU3pUEqIk3/Uwl3yRixYKT6WjQuGiwDv4NomL3wqRCj+CQ==", + "dev": true + }, + "node_modules/type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/type-fest": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/typedarray-to-buffer": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", + "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", + "dev": true, + "dependencies": { + "is-typedarray": "^1.0.0" + } + }, + "node_modules/typescript": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.0.2.tgz", + "integrity": "sha512-e4ERvRV2wb+rRZ/IQeb3jm2VxBsirQLpQhdxplZ2MEzGvDkkMmPglecnNDfSUBivMjP93vRbngYYDQqQ/78bcQ==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + }, + "node_modules/uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", + "deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.", + "dev": true, + "bin": { + "uuid": "bin/uuid" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/which-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", + "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", + "dev": true + }, + "node_modules/workerpool": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.1.tgz", + "integrity": "sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw==", + "dev": true + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "node_modules/write-file-atomic": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", + "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", + "dev": true, + "dependencies": { + "imurmurhash": "^0.1.4", + "is-typedarray": "^1.0.0", + "signal-exit": "^3.0.2", + "typedarray-to-buffer": "^3.1.5" + } + }, + "node_modules/y18n": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", + "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==", + "dev": true + }, + "node_modules/yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dev": true, + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs-parser": { + "version": "20.2.4", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", + "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs-unparser": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", + "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", + "dev": true, + "dependencies": { + "camelcase": "^6.0.0", + "decamelize": "^4.0.0", + "flat": "^5.0.2", + "is-plain-obj": "^2.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs-unparser/node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/yargs-unparser/node_modules/decamelize": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", + "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/yargs/node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/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, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + }, "dependencies": { "@babel/code-frame": { "version": "7.10.4", From 4105bb09c95491c510b1c0e050480d00e3daf87f Mon Sep 17 00:00:00 2001 From: Max Katz Date: Fri, 6 Jan 2023 02:14:45 -0500 Subject: [PATCH 07/20] Windows implementation --- .../Wpf/WpfTopLevelImpl.cs | 2 + src/Windows/Avalonia.Win32/Win32Platform.cs | 29 ++------- .../Avalonia.Win32/Win32PlatformSettings.cs | 62 +++++++++++++++++++ .../Avalonia.Win32/WinRT/WinRTColor.cs | 3 + src/Windows/Avalonia.Win32/WinRT/winrt.idl | 20 ++++++ .../Avalonia.Win32/WindowImpl.AppWndProc.cs | 8 +++ src/Windows/Avalonia.Win32/WindowImpl.cs | 13 ++++ 7 files changed, 113 insertions(+), 24 deletions(-) create mode 100644 src/Windows/Avalonia.Win32/Win32PlatformSettings.cs diff --git a/src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs b/src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs index 16a20c855f..76fc4fa21d 100644 --- a/src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs +++ b/src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs @@ -258,6 +258,8 @@ namespace Avalonia.Win32.Interop.Wpf public WindowTransparencyLevel TransparencyLevel { get; private set; } + public void SetFrameThemeVariant(PlatformThemeVariant themeVariant) { } + public AcrylicPlatformCompensationLevels AcrylicCompensationLevels { get; } = new AcrylicPlatformCompensationLevels(1, 1, 1); } } diff --git a/src/Windows/Avalonia.Win32/Win32Platform.cs b/src/Windows/Avalonia.Win32/Win32Platform.cs index 585a9cdf19..c0a0ce4da0 100644 --- a/src/Windows/Avalonia.Win32/Win32Platform.cs +++ b/src/Windows/Avalonia.Win32/Win32Platform.cs @@ -11,6 +11,7 @@ using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Controls.Platform; using Avalonia.Input; using Avalonia.Input.Platform; +using Avalonia.Media; using Avalonia.OpenGL; using Avalonia.Platform; using Avalonia.Rendering; @@ -19,6 +20,7 @@ using Avalonia.Threading; using Avalonia.Utilities; using Avalonia.Win32.Input; using Avalonia.Win32.Interop; +using Avalonia.Win32.WinRT; using static Avalonia.Win32.Interop.UnmanagedMethods; namespace Avalonia @@ -111,7 +113,7 @@ namespace Avalonia namespace Avalonia.Win32 { - public class Win32Platform : IPlatformThreadingInterface, IPlatformSettings, IWindowingPlatform, IPlatformIconLoader, IPlatformLifetimeEventsImpl + public class Win32Platform : IPlatformThreadingInterface, IWindowingPlatform, IPlatformIconLoader, IPlatformLifetimeEventsImpl { private static readonly Win32Platform s_instance = new Win32Platform(); private static Thread _uiThread; @@ -126,6 +128,7 @@ namespace Avalonia.Win32 } internal static Win32Platform Instance => s_instance; + internal static IPlatformSettings PlatformSettings => AvaloniaLocator.Current.GetRequiredService(); internal IntPtr Handle => _hwnd; @@ -153,7 +156,7 @@ namespace Avalonia.Win32 .Bind().ToSingleton() .Bind().ToConstant(CursorFactory.Instance) .Bind().ToConstant(WindowsKeyboardDevice.Instance) - .Bind().ToConstant(s_instance) + .Bind().ToSingleton() .Bind().ToConstant(s_instance) .Bind().ToConstant(new RenderLoop()) .Bind().ToConstant(new DefaultRenderTimer(60)) @@ -258,8 +261,6 @@ namespace Avalonia.Win32 public bool CurrentThreadIsLoopThread => _uiThread == Thread.CurrentThread; - public TimeSpan HoldWaitDuration { get; set; } = TimeSpan.FromMilliseconds(300); - public event Action Signaled; public event EventHandler ShutdownRequested; @@ -403,25 +404,5 @@ namespace Avalonia.Win32 SetProcessDPIAware(); } - - Size IPlatformSettings.GetTapSize(PointerType type) - { - return type switch - { - PointerType.Touch => new(10, 10), - _ => new(GetSystemMetrics(SystemMetric.SM_CXDRAG), GetSystemMetrics(SystemMetric.SM_CYDRAG)), - }; - } - - Size IPlatformSettings.GetDoubleTapSize(PointerType type) - { - return type switch - { - PointerType.Touch => new(16, 16), - _ => new(GetSystemMetrics(SystemMetric.SM_CXDOUBLECLK), GetSystemMetrics(SystemMetric.SM_CYDOUBLECLK)), - }; - } - - TimeSpan IPlatformSettings.GetDoubleTapTime(PointerType type) => TimeSpan.FromMilliseconds(GetDoubleClickTime()); } } diff --git a/src/Windows/Avalonia.Win32/Win32PlatformSettings.cs b/src/Windows/Avalonia.Win32/Win32PlatformSettings.cs new file mode 100644 index 0000000000..73ac5bf5a9 --- /dev/null +++ b/src/Windows/Avalonia.Win32/Win32PlatformSettings.cs @@ -0,0 +1,62 @@ +using System; +using Avalonia.Input; +using Avalonia.Platform; +using Avalonia.Win32.Interop; +using Avalonia.Win32.WinRT; +using static Avalonia.Win32.Interop.UnmanagedMethods; + +namespace Avalonia.Win32; + +internal class Win32PlatformSettings : DefaultPlatformSettings +{ + private PlatformColorValues _lastColorValues; + + public override Size GetTapSize(PointerType type) + { + return type switch + { + PointerType.Touch => new(10, 10), + _ => new(GetSystemMetrics(UnmanagedMethods.SystemMetric.SM_CXDRAG), GetSystemMetrics(UnmanagedMethods.SystemMetric.SM_CYDRAG)), + }; + } + + public override Size GetDoubleTapSize(PointerType type) + { + return type switch + { + PointerType.Touch => new(16, 16), + _ => new(GetSystemMetrics(UnmanagedMethods.SystemMetric.SM_CXDOUBLECLK), GetSystemMetrics(UnmanagedMethods.SystemMetric.SM_CYDOUBLECLK)), + }; + } + + public override TimeSpan GetDoubleTapTime(PointerType type) => TimeSpan.FromMilliseconds(GetDoubleClickTime()); + + public override PlatformColorValues GetColorValues() + { + if (Win32Platform.WindowsVersion.Major < 10) + { + return base.GetColorValues(); + } + + var settings = NativeWinRTMethods.CreateInstance("Windows.UI.ViewManagement.UISettings"); + var accent = settings.GetColorValue(UIColorType.Accent).ToAvalonia(); + var background = settings.GetColorValue(UIColorType.Background).ToAvalonia(); + + return _lastColorValues = new PlatformColorValues( + background.R + background.G + background.B < (255 * 3 - background.R - background.G - background.B) + ? PlatformThemeVariant.Dark + : PlatformThemeVariant.Light, + accent, accent, accent); + } + + internal void OnColorValuesChanged(IntPtr handle) + { + var oldColorValues = _lastColorValues; + var colorValues = GetColorValues(); + + if (oldColorValues != colorValues) + { + OnColorValuesChanged(colorValues); + } + } +} diff --git a/src/Windows/Avalonia.Win32/WinRT/WinRTColor.cs b/src/Windows/Avalonia.Win32/WinRT/WinRTColor.cs index 35f0737d5f..90b2022054 100644 --- a/src/Windows/Avalonia.Win32/WinRT/WinRTColor.cs +++ b/src/Windows/Avalonia.Win32/WinRT/WinRTColor.cs @@ -1,4 +1,5 @@ using System.Runtime.InteropServices; +using Avalonia.Media; namespace Avalonia.Win32.WinRT { @@ -14,5 +15,7 @@ namespace Avalonia.Win32.WinRT { A = a, R = r, G = g, B = b }; + + public Color ToAvalonia() => new(A, R, G, B); } } diff --git a/src/Windows/Avalonia.Win32/WinRT/winrt.idl b/src/Windows/Avalonia.Win32/WinRT/winrt.idl index ffb98fafc8..8dee275d50 100644 --- a/src/Windows/Avalonia.Win32/WinRT/winrt.idl +++ b/src/Windows/Avalonia.Win32/WinRT/winrt.idl @@ -844,3 +844,23 @@ interface ICompositor6 : IInspectable [overload("CreateGeometricClip")] HRESULT CreateGeometricClip([out] [retval] ICompositionGeometricClip** result); [overload("CreateGeometricClip")] HRESULT CreateGeometricClipWithGeometry([in] ICompositionGeometry* geometry, [out] [retval] ICompositionGeometricClip** result); } + +enum UIColorType +{ + Background = 0, + Foreground = 1, + AccentDark3 = 2, + AccentDark2 = 3, + AccentDark1 = 4, + Accent = 5, + AccentLight1 = 6, + AccentLight2 = 7, + AccentLight3 = 8, + Complement = 9 +} + +[uuid(03021BE4-5254-4781-8194-5168F7D06D7B)] +interface IUISettings3 : IInspectable +{ + HRESULT GetColorValue([in] UIColorType desiredColor, [out][retval] Color* value); +} diff --git a/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs b/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs index 8c362b0c29..af634fd760 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs @@ -732,6 +732,14 @@ namespace Avalonia.Win32 var node = AutomationNode.GetOrCreate(peer); return UiaCoreProviderApi.UiaReturnRawElementProvider(_hwnd, wParam, lParam, node); } + break; + case WindowsMessage.WM_SETTINGCHANGE: + if (Marshal.PtrToStringAuto(lParam) == "ImmersiveColorSet" + && Win32Platform.PlatformSettings is Win32PlatformSettings win32PlatformSettings) + { + win32PlatformSettings.OnColorValuesChanged(_hwnd); + } + break; } diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index dbcc31c5f5..a7741477e5 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -805,6 +805,19 @@ namespace Avalonia.Win32 _topmost = value; } + public unsafe void SetFrameThemeVariant(PlatformThemeVariant themeVariant) + { + if (Win32Platform.WindowsVersion.Build >= 22000) + { + var pvUseBackdropBrush = themeVariant == PlatformThemeVariant.Dark ? 1 : 0; + DwmSetWindowAttribute( + _hwnd, + (int)DwmWindowAttribute.DWMWA_USE_IMMERSIVE_DARK_MODE, + &pvUseBackdropBrush, + sizeof(int)); + } + } + protected virtual IntPtr CreateWindowOverride(ushort atom) { return CreateWindowEx( From d76bdd7807f9612793708ac50033b2e806d53cd1 Mon Sep 17 00:00:00 2001 From: Max Katz Date: Fri, 6 Jan 2023 02:15:00 -0500 Subject: [PATCH 08/20] FreeDesktop/x11 implementation --- .../DBusPlatformSettings.cs | 74 +++++++++++++++++++ src/Avalonia.FreeDesktop/DBusSettings.cs | 16 ++++ src/Avalonia.X11/X11Platform.cs | 2 +- src/Avalonia.X11/X11Window.cs | 2 + .../FramebufferToplevelImpl.cs | 2 + 5 files changed, 95 insertions(+), 1 deletion(-) create mode 100644 src/Avalonia.FreeDesktop/DBusPlatformSettings.cs create mode 100644 src/Avalonia.FreeDesktop/DBusSettings.cs diff --git a/src/Avalonia.FreeDesktop/DBusPlatformSettings.cs b/src/Avalonia.FreeDesktop/DBusPlatformSettings.cs new file mode 100644 index 0000000000..82b4a72e74 --- /dev/null +++ b/src/Avalonia.FreeDesktop/DBusPlatformSettings.cs @@ -0,0 +1,74 @@ +using System; +using System.Threading.Tasks; +using Avalonia.Logging; +using Avalonia.Platform; + +namespace Avalonia.FreeDesktop; + +internal class DBusPlatformSettings : DefaultPlatformSettings +{ + private readonly IDBusSettings? _settings; + private PlatformColorValues? _lastColorValues; + + public DBusPlatformSettings() + { + _settings = DBusHelper.TryInitialize()? + .CreateProxy("org.freedesktop.portal.Desktop", "/org/freedesktop/portal/desktop"); + + if (_settings is not null) + { + _ = _settings.WatchSettingChangedAsync(SettingsChangedHandler); + + _ = TryGetInitialValue(); + } + } + + public override PlatformColorValues GetColorValues() + { + return _lastColorValues ?? base.GetColorValues(); + } + + private async Task TryGetInitialValue() + { + var colorSchemeTask = _settings!.ReadAsync("org.freedesktop.appearance", "color-scheme"); + if (colorSchemeTask.Status == TaskStatus.RanToCompletion) + { + _lastColorValues = GetColorValuesFromSetting(colorSchemeTask.Result); + } + else + { + try + { + var value = await colorSchemeTask; + _lastColorValues = GetColorValuesFromSetting(value); + OnColorValuesChanged(_lastColorValues.Value); + } + catch (Exception ex) + { + _lastColorValues = base.GetColorValues(); + Logger.TryGet(LogEventLevel.Error, LogArea.FreeDesktopPlatform)?.Log(this, "Unable to get setting value", ex); + } + } + } + + private void SettingsChangedHandler((string @namespace, string key, object value) tuple) + { + if (tuple.@namespace == "org.freedesktop.appearance" + && tuple.key == "color-scheme") + { + /* + 0: No preference + 1: Prefer dark appearance + 2: Prefer light appearance + */ + _lastColorValues = GetColorValuesFromSetting(tuple.value); + OnColorValuesChanged(_lastColorValues.Value); + } + } + + private static PlatformColorValues GetColorValuesFromSetting(object value) + { + var isDark = value?.ToString() == "1"; + return new PlatformColorValues(isDark ? PlatformThemeVariant.Dark : PlatformThemeVariant.Light); + } +} diff --git a/src/Avalonia.FreeDesktop/DBusSettings.cs b/src/Avalonia.FreeDesktop/DBusSettings.cs new file mode 100644 index 0000000000..05911981c7 --- /dev/null +++ b/src/Avalonia.FreeDesktop/DBusSettings.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Tmds.DBus; + +namespace Avalonia.FreeDesktop; + +[DBusInterface("org.freedesktop.portal.Settings")] +internal interface IDBusSettings : IDBusObject +{ + Task<(string @namespace, IDictionary)> ReadAllAsync(string[] namespaces); + + Task ReadAsync(string @namespace, string key); + + Task WatchSettingChangedAsync(Action<(string @namespace, string key, object value)> handler, Action? onError = null); +} diff --git a/src/Avalonia.X11/X11Platform.cs b/src/Avalonia.X11/X11Platform.cs index 9692e0a384..7820f8a3c2 100644 --- a/src/Avalonia.X11/X11Platform.cs +++ b/src/Avalonia.X11/X11Platform.cs @@ -80,7 +80,7 @@ namespace Avalonia.X11 .Bind().ToFunc(() => KeyboardDevice) .Bind().ToConstant(new X11CursorFactory(Display)) .Bind().ToConstant(new X11Clipboard(this)) - .Bind().ToSingleton() + .Bind().ToSingleton() .Bind().ToConstant(new X11IconLoader()) .Bind().ToConstant(new LinuxMountedVolumeInfoProvider()) .Bind().ToConstant(new X11PlatformLifetimeEvents(this)); diff --git a/src/Avalonia.X11/X11Window.cs b/src/Avalonia.X11/X11Window.cs index 810a806c8a..4b0e8473cc 100644 --- a/src/Avalonia.X11/X11Window.cs +++ b/src/Avalonia.X11/X11Window.cs @@ -1225,6 +1225,8 @@ namespace Avalonia.X11 public WindowTransparencyLevel TransparencyLevel => _transparencyHelper?.CurrentLevel ?? WindowTransparencyLevel.None; + public void SetFrameThemeVariant(PlatformThemeVariant themeVariant) { } + public AcrylicPlatformCompensationLevels AcrylicCompensationLevels { get; } = new AcrylicPlatformCompensationLevels(1, 0.8, 0.8); public bool NeedsManagedDecorations => false; diff --git a/src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs b/src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs index ac54365f51..1c2fad56a6 100644 --- a/src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs +++ b/src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs @@ -83,6 +83,8 @@ namespace Avalonia.LinuxFramebuffer public WindowTransparencyLevel TransparencyLevel { get; private set; } + public void SetFrameThemeVariant(PlatformThemeVariant themeVariant) { } + public AcrylicPlatformCompensationLevels AcrylicCompensationLevels { get; } = new AcrylicPlatformCompensationLevels(1, 1, 1); } } From a4bffbf660595ea70e3073401fd3df43746fd73f Mon Sep 17 00:00:00 2001 From: Max Katz Date: Fri, 6 Jan 2023 02:15:18 -0500 Subject: [PATCH 09/20] iOS and Android implementations --- .../Avalonia.Android/AndroidPlatform.cs | 2 +- src/Android/Avalonia.Android/AvaloniaView.cs | 16 ++++ .../Platform/AndroidPlatformSettings.cs | 73 +++++++++++++++++++ .../Platform/SkiaPlatform/TopLevelImpl.cs | 6 ++ src/iOS/Avalonia.iOS/AvaloniaView.cs | 27 ++++++- .../Avalonia.iOS/AvaloniaViewController.cs | 6 ++ src/iOS/Avalonia.iOS/Platform.cs | 2 +- src/iOS/Avalonia.iOS/PlatformSettings.cs | 34 +++++++++ 8 files changed, 163 insertions(+), 3 deletions(-) create mode 100644 src/Android/Avalonia.Android/Platform/AndroidPlatformSettings.cs create mode 100644 src/iOS/Avalonia.iOS/AvaloniaViewController.cs create mode 100644 src/iOS/Avalonia.iOS/PlatformSettings.cs diff --git a/src/Android/Avalonia.Android/AndroidPlatform.cs b/src/Android/Avalonia.Android/AndroidPlatform.cs index 01b48ffd89..c73fd92423 100644 --- a/src/Android/Avalonia.Android/AndroidPlatform.cs +++ b/src/Android/Avalonia.Android/AndroidPlatform.cs @@ -43,7 +43,7 @@ namespace Avalonia.Android .Bind().ToTransient() .Bind().ToConstant(new WindowingPlatformStub()) .Bind().ToSingleton() - .Bind().ToSingleton() + .Bind().ToSingleton() .Bind().ToConstant(new AndroidThreadingInterface()) .Bind().ToSingleton() .Bind().ToConstant(new ChoreographerTimer()) diff --git a/src/Android/Avalonia.Android/AvaloniaView.cs b/src/Android/Avalonia.Android/AvaloniaView.cs index f7e32f99db..2a345a857c 100644 --- a/src/Android/Avalonia.Android/AvaloniaView.cs +++ b/src/Android/Avalonia.Android/AvaloniaView.cs @@ -1,11 +1,14 @@ using System; using Android.Content; +using Android.Content.Res; using Android.Runtime; using Android.Views; using Android.Widget; +using Avalonia.Android.Platform; using Avalonia.Android.Platform.SkiaPlatform; using Avalonia.Controls; using Avalonia.Controls.Embedding; +using Avalonia.Platform; using Avalonia.Rendering; namespace Avalonia.Android @@ -26,6 +29,7 @@ namespace Avalonia.Android _root.Prepare(); this.SetBackgroundColor(global::Android.Graphics.Color.Transparent); + OnConfigurationChanged(); } internal TopLevelImpl TopLevelImpl => _view; @@ -70,6 +74,18 @@ namespace Avalonia.Android _timerSubscription?.Dispose(); } } + + protected override void OnConfigurationChanged(Configuration newConfig) + { + base.OnConfigurationChanged(newConfig); + OnConfigurationChanged(); + } + + private void OnConfigurationChanged() + { + var settings = AvaloniaLocator.Current.GetRequiredService() as AndroidPlatformSettings; + settings?.OnViewConfigurationChanged(Context); + } class ViewImpl : TopLevelImpl { diff --git a/src/Android/Avalonia.Android/Platform/AndroidPlatformSettings.cs b/src/Android/Avalonia.Android/Platform/AndroidPlatformSettings.cs new file mode 100644 index 0000000000..f560dea3ce --- /dev/null +++ b/src/Android/Avalonia.Android/Platform/AndroidPlatformSettings.cs @@ -0,0 +1,73 @@ +using System; +using Android; +using Android.Content; +using Android.Content.Res; +using Android.Graphics; +using AndroidX.Core.Content.Resources; +using Avalonia.Media; +using Avalonia.Platform; +using Color = Avalonia.Media.Color; + +namespace Avalonia.Android.Platform; + +// TODO: ideally should be created per view/activity. +internal class AndroidPlatformSettings : DefaultPlatformSettings +{ + private PlatformColorValues _latestValues; + + public AndroidPlatformSettings() + { + _latestValues = base.GetColorValues(); + } + + public override PlatformColorValues GetColorValues() + { + return _latestValues; + } + + internal void OnViewConfigurationChanged(Context context) + { + if (context.Resources?.Configuration is null) + { + return; + } + + var systemTheme = (context.Resources.Configuration.UiMode & UiMode.NightMask) switch + { + UiMode.NightYes => PlatformThemeVariant.Dark, + UiMode.NightNo => PlatformThemeVariant.Light, + _ => throw new ArgumentOutOfRangeException() + }; + + if (OperatingSystem.IsAndroidVersionAtLeast(31)) + { + // See https://developer.android.com/reference/android/R.color + var accent1 = context.Resources.GetColor(17170494, context.Theme); // Resource.Color.SystemAccent1500 + var accent2 = context.Resources.GetColor(17170507, context.Theme); // Resource.Color.SystemAccent2500 + var accent3 = context.Resources.GetColor(17170520, context.Theme); // Resource.Color.SystemAccent3500 + + _latestValues = new PlatformColorValues( + systemTheme, + new Color(accent1.A, accent1.R, accent1.G, accent1.B), + new Color(accent2.A, accent2.R, accent2.G, accent2.B), + new Color(accent3.A, accent3.R, accent3.G, accent3.B)); + } + else if (OperatingSystem.IsAndroidVersionAtLeast(23)) + { + // See https://developer.android.com/reference/android/R.attr + var array = context.Theme.ObtainStyledAttributes(new[] { 16843829 }); // Resource.Attribute.ColorAccent + var accent = array.GetColor(0, 0); + + _latestValues = new PlatformColorValues( + systemTheme, + new Color(accent.A, accent.R, accent.G, accent.B)); + array.Recycle(); + } + else + { + _latestValues = _latestValues with { ThemeVariant = systemTheme }; + } + + OnColorValuesChanged(_latestValues); + } +} diff --git a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs index 1aac567dda..1f997af26d 100644 --- a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs +++ b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs @@ -26,6 +26,7 @@ using Avalonia.Rendering.Composition; using Java.Lang; using Math = System.Math; using AndroidRect = Android.Graphics.Rect; +using Window = Android.Views.Window; using Android.Graphics.Drawables; namespace Avalonia.Android.Platform.SkiaPlatform @@ -286,6 +287,11 @@ namespace Avalonia.Android.Platform.SkiaPlatform public WindowTransparencyLevel TransparencyLevel { get; private set; } + public void SetFrameThemeVariant(PlatformThemeVariant themeVariant) + { + // TODO adjust status bar depending on full screen mode. + } + public AcrylicPlatformCompensationLevels AcrylicCompensationLevels => new AcrylicPlatformCompensationLevels(1, 1, 1); IntPtr EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo.Handle => ((IPlatformHandle)_view).Handle; diff --git a/src/iOS/Avalonia.iOS/AvaloniaView.cs b/src/iOS/Avalonia.iOS/AvaloniaView.cs index 48358c745f..6d36ca7814 100644 --- a/src/iOS/Avalonia.iOS/AvaloniaView.cs +++ b/src/iOS/Avalonia.iOS/AvaloniaView.cs @@ -52,6 +52,14 @@ namespace Avalonia.iOS public override bool CanResignFirstResponder => true; + public override void TraitCollectionDidChange(UITraitCollection previousTraitCollection) + { + base.TraitCollectionDidChange(previousTraitCollection); + + var settings = AvaloniaLocator.Current.GetRequiredService() as PlatformSettings; + settings?.TraitCollectionDidChange(); + } + internal class TopLevelImpl : ITopLevelImplWithTextInputMethod, ITopLevelImplWithNativeControlHost, ITopLevelImplWithStorageProvider { @@ -120,7 +128,24 @@ namespace Avalonia.iOS // legacy no-op public IMouseDevice MouseDevice { get; } = new MouseDevice(); public WindowTransparencyLevel TransparencyLevel { get; } - + + public void SetFrameThemeVariant(PlatformThemeVariant themeVariant) + { + // TODO adjust status bar depending on full screen mode. + if (OperatingSystem.IsIOSVersionAtLeast(13)) + { + var uiStatusBarStyle = themeVariant switch + { + PlatformThemeVariant.Light => UIStatusBarStyle.DarkContent, + PlatformThemeVariant.Dark => UIStatusBarStyle.LightContent, + _ => throw new ArgumentOutOfRangeException(nameof(themeVariant), themeVariant, null) + }; + + // Consider using UIViewController.PreferredStatusBarStyle in the future. + UIApplication.SharedApplication.SetStatusBarStyle(uiStatusBarStyle, true); + } + } + public AcrylicPlatformCompensationLevels AcrylicCompensationLevels { get; } = new AcrylicPlatformCompensationLevels(); diff --git a/src/iOS/Avalonia.iOS/AvaloniaViewController.cs b/src/iOS/Avalonia.iOS/AvaloniaViewController.cs new file mode 100644 index 0000000000..c6856789b8 --- /dev/null +++ b/src/iOS/Avalonia.iOS/AvaloniaViewController.cs @@ -0,0 +1,6 @@ +namespace Avalonia.iOS; + +public class AvaloniaViewController +{ + +} diff --git a/src/iOS/Avalonia.iOS/Platform.cs b/src/iOS/Avalonia.iOS/Platform.cs index 63025f7f0a..5bcd1eae84 100644 --- a/src/iOS/Avalonia.iOS/Platform.cs +++ b/src/iOS/Avalonia.iOS/Platform.cs @@ -40,7 +40,7 @@ namespace Avalonia.iOS .Bind().ToConstant(new CursorFactoryStub()) .Bind().ToConstant(new WindowingPlatformStub()) .Bind().ToConstant(new ClipboardImpl()) - .Bind().ToSingleton() + .Bind().ToSingleton() .Bind().ToConstant(new PlatformIconLoaderStub()) .Bind().ToSingleton() .Bind().ToSingleton() diff --git a/src/iOS/Avalonia.iOS/PlatformSettings.cs b/src/iOS/Avalonia.iOS/PlatformSettings.cs new file mode 100644 index 0000000000..736e94ffc6 --- /dev/null +++ b/src/iOS/Avalonia.iOS/PlatformSettings.cs @@ -0,0 +1,34 @@ +using System; +using System.Linq; +using Avalonia.Media; +using Avalonia.Platform; +using Foundation; +using UIKit; + +namespace Avalonia.iOS; + +// TODO: ideally should be created per view/activity. +internal class PlatformSettings : DefaultPlatformSettings +{ + private PlatformColorValues _lastColorValues; + + public override PlatformColorValues GetColorValues() + { + var themeVariant = UITraitCollection.CurrentTraitCollection.UserInterfaceStyle == UIUserInterfaceStyle.Dark ? + PlatformThemeVariant.Dark : + PlatformThemeVariant.Light; + + return _lastColorValues = new PlatformColorValues(themeVariant); + } + + public void TraitCollectionDidChange() + { + var oldColorValues = _lastColorValues; + var colorValues = GetColorValues(); + + if (oldColorValues != colorValues) + { + OnColorValuesChanged(colorValues); + } + } +} From 70dcf71910afa309d6ec5ae35729e501a3073ec7 Mon Sep 17 00:00:00 2001 From: Max Katz Date: Fri, 6 Jan 2023 02:15:26 -0500 Subject: [PATCH 10/20] Browser implementation --- .../BrowserPlatformSettings.cs | 26 +++++++++++++++++++ .../Avalonia.Browser/BrowserTopLevelImpl.cs | 5 ++++ .../Avalonia.Browser/Interop/DomHelper.cs | 5 ++++ .../Avalonia.Browser/WindowingPlatform.cs | 2 +- .../webapp/modules/avalonia/dom.ts | 13 ++++++++++ 5 files changed, 50 insertions(+), 1 deletion(-) create mode 100644 src/Browser/Avalonia.Browser/BrowserPlatformSettings.cs diff --git a/src/Browser/Avalonia.Browser/BrowserPlatformSettings.cs b/src/Browser/Avalonia.Browser/BrowserPlatformSettings.cs new file mode 100644 index 0000000000..8a5a089752 --- /dev/null +++ b/src/Browser/Avalonia.Browser/BrowserPlatformSettings.cs @@ -0,0 +1,26 @@ +using Avalonia.Browser.Interop; +using Avalonia.Platform; + +namespace Avalonia.Browser; + +internal class BrowserPlatformSettings : DefaultPlatformSettings +{ + private bool _isDarkMode; + + public BrowserPlatformSettings() + { + _isDarkMode = DomHelper.ObserveDarkMode(m => + { + _isDarkMode = m; + OnColorValuesChanged(GetColorValues()); + }); + } + + public override PlatformColorValues GetColorValues() + { + return base.GetColorValues() with + { + ThemeVariant = _isDarkMode ? PlatformThemeVariant.Dark : PlatformThemeVariant.Light + }; + } +} diff --git a/src/Browser/Avalonia.Browser/BrowserTopLevelImpl.cs b/src/Browser/Avalonia.Browser/BrowserTopLevelImpl.cs index 4e6be3268b..a1f293757c 100644 --- a/src/Browser/Avalonia.Browser/BrowserTopLevelImpl.cs +++ b/src/Browser/Avalonia.Browser/BrowserTopLevelImpl.cs @@ -228,6 +228,11 @@ namespace Avalonia.Browser public IKeyboardDevice KeyboardDevice { get; } = BrowserWindowingPlatform.Keyboard; public WindowTransparencyLevel TransparencyLevel { get; private set; } + public void SetFrameThemeVariant(PlatformThemeVariant themeVariant) + { + // not in the standard, but we potentially can use "apple-mobile-web-app-status-bar-style" for iOS and "theme-color" for android. + } + public AcrylicPlatformCompensationLevels AcrylicCompensationLevels { get; } public ITextInputMethodImpl TextInputMethod => _avaloniaView; diff --git a/src/Browser/Avalonia.Browser/Interop/DomHelper.cs b/src/Browser/Avalonia.Browser/Interop/DomHelper.cs index b97a03209b..090f03c4f1 100644 --- a/src/Browser/Avalonia.Browser/Interop/DomHelper.cs +++ b/src/Browser/Avalonia.Browser/Interop/DomHelper.cs @@ -25,4 +25,9 @@ internal static partial class DomHelper public static partial double ObserveDpi( [JSMarshalAs>] Action onDpiChanged); + + [JSImport("AvaloniaDOM.observeDarkMode", AvaloniaModule.MainModuleName)] + public static partial bool ObserveDarkMode( + [JSMarshalAs>] + Action onDpiChanged); } diff --git a/src/Browser/Avalonia.Browser/WindowingPlatform.cs b/src/Browser/Avalonia.Browser/WindowingPlatform.cs index 6493374a50..46db8936eb 100644 --- a/src/Browser/Avalonia.Browser/WindowingPlatform.cs +++ b/src/Browser/Avalonia.Browser/WindowingPlatform.cs @@ -39,7 +39,7 @@ namespace Avalonia.Browser .Bind().ToSingleton() .Bind().ToSingleton() .Bind().ToConstant(s_keyboard) - .Bind().ToSingleton() + .Bind().ToSingleton() .Bind().ToConstant(instance) .Bind().ToConstant(new RenderLoop()) .Bind().ToConstant(ManualTriggerRenderTimer.Instance) diff --git a/src/Browser/Avalonia.Browser/webapp/modules/avalonia/dom.ts b/src/Browser/Avalonia.Browser/webapp/modules/avalonia/dom.ts index e8e37faf58..9760fccc3b 100644 --- a/src/Browser/Avalonia.Browser/webapp/modules/avalonia/dom.ts +++ b/src/Browser/Avalonia.Browser/webapp/modules/avalonia/dom.ts @@ -3,6 +3,19 @@ export class AvaloniaDOM { element.classList.add(className); } + static observeDarkMode(observer: (isDarkMode: boolean) => boolean): boolean { + if (globalThis.matchMedia === undefined) { + return false; + } + + const media = globalThis.matchMedia("(prefers-color-scheme: dark)"); + media.addEventListener("change", (args: MediaQueryListEvent) => { + observer(args.matches); + }); + + return media.matches; + } + static createAvaloniaHost(host: HTMLElement) { const randomIdPart = Math.random().toString(36).replace(/[^a-z]+/g, "").substr(2, 10); From 8f11c5a6ede15080385e8fd51c2cfdc5e356ea0a Mon Sep 17 00:00:00 2001 From: Max Katz Date: Fri, 6 Jan 2023 02:15:36 -0500 Subject: [PATCH 11/20] macOS implementation --- .../project.pbxproj | 4 + .../src/OSX/PlatformSettings.mm | 111 ++++++++++++++++++ .../Avalonia.Native/src/OSX/WindowBaseImpl.h | 2 + .../Avalonia.Native/src/OSX/WindowBaseImpl.mm | 18 +++ native/Avalonia.Native/src/OSX/common.h | 1 + native/Avalonia.Native/src/OSX/main.mm | 10 ++ src/Avalonia.Native/AvaloniaNativePlatform.cs | 2 +- src/Avalonia.Native/NativePlatformSettings.cs | 61 ++++++++++ src/Avalonia.Native/WindowImplBase.cs | 5 + src/Avalonia.Native/avn.idl | 16 +++ 10 files changed, 229 insertions(+), 1 deletion(-) create mode 100644 native/Avalonia.Native/src/OSX/PlatformSettings.mm create mode 100644 src/Avalonia.Native/NativePlatformSettings.cs diff --git a/native/Avalonia.Native/src/OSX/Avalonia.Native.OSX.xcodeproj/project.pbxproj b/native/Avalonia.Native/src/OSX/Avalonia.Native.OSX.xcodeproj/project.pbxproj index ace4a71a56..41d1534f8d 100644 --- a/native/Avalonia.Native/src/OSX/Avalonia.Native.OSX.xcodeproj/project.pbxproj +++ b/native/Avalonia.Native/src/OSX/Avalonia.Native.OSX.xcodeproj/project.pbxproj @@ -50,6 +50,7 @@ BC11A5BE2608D58F0017BAD0 /* automation.h in Headers */ = {isa = PBXBuildFile; fileRef = BC11A5BC2608D58F0017BAD0 /* automation.h */; }; BC11A5BF2608D58F0017BAD0 /* automation.mm in Sources */ = {isa = PBXBuildFile; fileRef = BC11A5BD2608D58F0017BAD0 /* automation.mm */; }; ED3791C42862E1F40080BD62 /* UniformTypeIdentifiers.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = ED3791C32862E1F40080BD62 /* UniformTypeIdentifiers.framework */; }; + EDF8CDCD2964CB01001EE34F /* PlatformSettings.mm in Sources */ = {isa = PBXBuildFile; fileRef = EDF8CDCC2964CB01001EE34F /* PlatformSettings.mm */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ @@ -103,6 +104,7 @@ BC11A5BC2608D58F0017BAD0 /* automation.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = automation.h; sourceTree = ""; }; BC11A5BD2608D58F0017BAD0 /* automation.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = automation.mm; sourceTree = ""; }; ED3791C32862E1F40080BD62 /* UniformTypeIdentifiers.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UniformTypeIdentifiers.framework; path = System/Library/Frameworks/UniformTypeIdentifiers.framework; sourceTree = SDKROOT; }; + EDF8CDCC2964CB01001EE34F /* PlatformSettings.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = PlatformSettings.mm; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -163,6 +165,7 @@ 1A3E5EA723E9E83B00EDE661 /* rendertarget.mm */, 37A517B22159597E00FBA241 /* Screens.mm */, 37C09D8721580FE4006A6758 /* SystemDialogs.mm */, + EDF8CDCC2964CB01001EE34F /* PlatformSettings.mm */, AB7A61F02147C815003C5833 /* Products */, AB661C1C2148230E00291242 /* Frameworks */, 18391676ECF0E983F4964357 /* WindowBaseImpl.mm */, @@ -299,6 +302,7 @@ 1839151F32D1BB1AB51A7BB6 /* AvnPanelWindow.mm in Sources */, 18391AC16726CBC45856233B /* AvnWindow.mm in Sources */, 18391D8CD1756DC858DC1A09 /* PopupImpl.mm in Sources */, + EDF8CDCD2964CB01001EE34F /* PlatformSettings.mm in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/native/Avalonia.Native/src/OSX/PlatformSettings.mm b/native/Avalonia.Native/src/OSX/PlatformSettings.mm new file mode 100644 index 0000000000..e709cd5135 --- /dev/null +++ b/native/Avalonia.Native/src/OSX/PlatformSettings.mm @@ -0,0 +1,111 @@ +#include "common.h" + +@interface CocoaThemeObserver : NSObject +-(id)initWithCallback:(IAvnActionCallback *)callback; +@end + +class PlatformSettings : public ComSingleObject +{ + CocoaThemeObserver* observer; + +public: + FORWARD_IUNKNOWN() + virtual AvnPlatformThemeVariant GetPlatformTheme() override + { + @autoreleasepool + { + if (@available(macOS 10.14, *)) + { + if (NSApplication.sharedApplication.effectiveAppearance.name == NSAppearanceNameAqua + || NSApplication.sharedApplication.effectiveAppearance.name == NSAppearanceNameVibrantLight + || NSApplication.sharedApplication.effectiveAppearance.name == NSAppearanceNameAccessibilityHighContrastAqua + || NSApplication.sharedApplication.effectiveAppearance.name == NSAppearanceNameAccessibilityHighContrastVibrantLight) { + return AvnPlatformThemeVariant::Light; + } else if (NSApplication.sharedApplication.effectiveAppearance.name == NSAppearanceNameDarkAqua + || NSApplication.sharedApplication.effectiveAppearance.name == NSAppearanceNameVibrantDark + || NSApplication.sharedApplication.effectiveAppearance.name == NSAppearanceNameAccessibilityHighContrastDarkAqua + || NSApplication.sharedApplication.effectiveAppearance.name == NSAppearanceNameAccessibilityHighContrastVibrantDark) { + return AvnPlatformThemeVariant::Dark; + } + } + return AvnPlatformThemeVariant::Light; + } + } + + virtual unsigned int GetAccentColor() override + { + @autoreleasepool + { + if (@available(macOS 10.14, *)) + { + auto color = [NSColor controlAccentColor]; + return to_argb(color); + } + else + { + return 0; + } + } + } + + virtual void RegisterColorsChange(IAvnActionCallback *callback) override + { + if (@available(macOS 10.14, *)) + { + observer = [[CocoaThemeObserver alloc] initWithCallback: callback]; + [[NSApplication sharedApplication] addObserver:observer forKeyPath:@"effectiveAppearance" options:NSKeyValueObservingOptionNew context:nil]; + } + } + +private: + unsigned int to_argb(NSColor* color) + { + const CGFloat* components = CGColorGetComponents(color.CGColor); + unsigned int alpha = static_cast(CGColorGetAlpha(color.CGColor) * 0xFF); + unsigned int red = static_cast(components[0] * 0xFF); + unsigned int green = static_cast(components[1] * 0xFF); + unsigned int blue = static_cast(components[2] * 0xFF); + return (alpha << 24) + (red << 16) + (green << 8) + blue; + } +}; + +@implementation CocoaThemeObserver +{ + ComPtr _callback; +} +- (id) initWithCallback:(IAvnActionCallback *)callback{ + self = [super init]; + if (self) { + _callback = callback; + } + return self; +} + +/*- (void)didChangeValueForKey:(NSString *)key { + if([key isEqualToString:@"effectiveAppearance"]) { + _callback->Run(); + } + else { + [super didChangeValueForKey:key]; + } +}*/ + +- (void)observeValueForKeyPath:(NSString *)keyPath + ofObject:(id)object + change:(NSDictionary *)change + context:(void *)context { + if([keyPath isEqualToString:@"effectiveAppearance"]) { + _callback->Run(); + } else { + [super observeValueForKeyPath:keyPath + ofObject:object + change:change + context:context]; + } +} +@end + +extern IAvnPlatformSettings* CreatePlatformSettings() +{ + return new PlatformSettings(); +} diff --git a/native/Avalonia.Native/src/OSX/WindowBaseImpl.h b/native/Avalonia.Native/src/OSX/WindowBaseImpl.h index 7fb002e54f..1404ec361c 100644 --- a/native/Avalonia.Native/src/OSX/WindowBaseImpl.h +++ b/native/Avalonia.Native/src/OSX/WindowBaseImpl.h @@ -92,6 +92,8 @@ BEGIN_INTERFACE_MAP() virtual HRESULT SetTransparencyMode(AvnWindowTransparencyMode mode) override; + virtual HRESULT SetFrameThemeVariant(AvnPlatformThemeVariant variant) override; + virtual HRESULT BeginDragAndDropOperation(AvnDragDropEffects effects, AvnPoint point, IAvnClipboard *clipboard, IAvnDndResultCallback *cb, void *sourceHandle) override; diff --git a/native/Avalonia.Native/src/OSX/WindowBaseImpl.mm b/native/Avalonia.Native/src/OSX/WindowBaseImpl.mm index 9946ad9b10..39dde5a582 100644 --- a/native/Avalonia.Native/src/OSX/WindowBaseImpl.mm +++ b/native/Avalonia.Native/src/OSX/WindowBaseImpl.mm @@ -498,6 +498,24 @@ HRESULT WindowBaseImpl::SetTransparencyMode(AvnWindowTransparencyMode mode) { return S_OK; } +HRESULT WindowBaseImpl::SetFrameThemeVariant(AvnPlatformThemeVariant variant) { + START_COM_CALL; + + NSAppearanceName appearanceName; + if (@available(macOS 10.14, *)) + { + appearanceName = variant == AvnPlatformThemeVariant::Dark ? NSAppearanceNameDarkAqua : NSAppearanceNameAqua; + } + else + { + appearanceName = variant == AvnPlatformThemeVariant::Dark ? NSAppearanceNameVibrantDark : NSAppearanceNameAqua; + } + + [Window setAppearance: [NSAppearance appearanceNamed: appearanceName]]; + + return S_OK; +} + HRESULT WindowBaseImpl::BeginDragAndDropOperation(AvnDragDropEffects effects, AvnPoint point, IAvnClipboard *clipboard, IAvnDndResultCallback *cb, void *sourceHandle) { START_COM_CALL; diff --git a/native/Avalonia.Native/src/OSX/common.h b/native/Avalonia.Native/src/OSX/common.h index 7ee7205776..972927b99d 100644 --- a/native/Avalonia.Native/src/OSX/common.h +++ b/native/Avalonia.Native/src/OSX/common.h @@ -27,6 +27,7 @@ extern IAvnMenuItem* CreateAppMenuItem(); extern IAvnMenuItem* CreateAppMenuItemSeparator(); extern IAvnApplicationCommands* CreateApplicationCommands(); extern IAvnNativeControlHost* CreateNativeControlHost(NSView* parent); +extern IAvnPlatformSettings* CreatePlatformSettings(); extern void SetAppMenu(IAvnMenu *menu); extern void SetServicesMenu (IAvnMenu* menu); extern IAvnMenu* GetAppMenu (); diff --git a/native/Avalonia.Native/src/OSX/main.mm b/native/Avalonia.Native/src/OSX/main.mm index c29d4108d0..99063e600e 100644 --- a/native/Avalonia.Native/src/OSX/main.mm +++ b/native/Avalonia.Native/src/OSX/main.mm @@ -398,6 +398,16 @@ public: } } + virtual HRESULT CreatePlatformSettings (IAvnPlatformSettings** ppv) override + { + START_COM_CALL; + + @autoreleasepool + { + *ppv = ::CreatePlatformSettings(); + return S_OK; + } + } }; extern "C" IAvaloniaNativeFactory* CreateAvaloniaNative() diff --git a/src/Avalonia.Native/AvaloniaNativePlatform.cs b/src/Avalonia.Native/AvaloniaNativePlatform.cs index 6d5925c0ae..fc23fbb226 100644 --- a/src/Avalonia.Native/AvaloniaNativePlatform.cs +++ b/src/Avalonia.Native/AvaloniaNativePlatform.cs @@ -104,7 +104,7 @@ namespace Avalonia.Native .Bind().ToConstant(new CursorFactory(_factory.CreateCursorFactory())) .Bind().ToSingleton() .Bind().ToConstant(KeyboardDevice) - .Bind().ToSingleton() + .Bind().ToConstant(new NativePlatformSettings(_factory.CreatePlatformSettings())) .Bind().ToConstant(this) .Bind().ToConstant(new ClipboardImpl(_factory.CreateClipboard())) .Bind().ToConstant(new DefaultRenderTimer(60)) diff --git a/src/Avalonia.Native/NativePlatformSettings.cs b/src/Avalonia.Native/NativePlatformSettings.cs new file mode 100644 index 0000000000..cea748bbae --- /dev/null +++ b/src/Avalonia.Native/NativePlatformSettings.cs @@ -0,0 +1,61 @@ +using System; +using Avalonia.Media; +using Avalonia.Native.Interop; +using Avalonia.Platform; + +namespace Avalonia.Native; + +internal class NativePlatformSettings : DefaultPlatformSettings +{ + private readonly IAvnPlatformSettings _platformSettings; + private PlatformColorValues _lastColorValues; + + public NativePlatformSettings(IAvnPlatformSettings platformSettings) + { + _platformSettings = platformSettings; + platformSettings.RegisterColorsChange(new ColorsChangeCallback(this)); + } + + public override PlatformColorValues GetColorValues() + { + var theme = (PlatformThemeVariant)_platformSettings.PlatformTheme; + var color = _platformSettings.AccentColor; + + if (color > 0) + { + _lastColorValues = new PlatformColorValues(theme, Color.FromUInt32(color)); + } + else + { + _lastColorValues = new PlatformColorValues(theme); + } + + return _lastColorValues; + } + + public void OnColorValuesChanged() + { + var oldColorValues = _lastColorValues; + var colorValues = GetColorValues(); + + if (oldColorValues != colorValues) + { + OnColorValuesChanged(colorValues); + } + } + + private class ColorsChangeCallback : NativeCallbackBase, IAvnActionCallback + { + private readonly NativePlatformSettings _settings; + + public ColorsChangeCallback(NativePlatformSettings settings) + { + _settings = settings; + } + + public void Run() + { + _settings.OnColorValuesChanged(); + } + } +} diff --git a/src/Avalonia.Native/WindowImplBase.cs b/src/Avalonia.Native/WindowImplBase.cs index ca57e30b1c..7686b42275 100644 --- a/src/Avalonia.Native/WindowImplBase.cs +++ b/src/Avalonia.Native/WindowImplBase.cs @@ -519,6 +519,11 @@ namespace Avalonia.Native public WindowTransparencyLevel TransparencyLevel { get; private set; } = WindowTransparencyLevel.Transparent; + public void SetFrameThemeVariant(PlatformThemeVariant themeVariant) + { + _native.SetFrameThemeVariant((AvnPlatformThemeVariant)themeVariant); + } + public AcrylicPlatformCompensationLevels AcrylicCompensationLevels { get; } = new AcrylicPlatformCompensationLevels(1, 0, 0); public IPlatformHandle Handle { get; private set; } diff --git a/src/Avalonia.Native/avn.idl b/src/Avalonia.Native/avn.idl index 8057b162f5..29f839c826 100644 --- a/src/Avalonia.Native/avn.idl +++ b/src/Avalonia.Native/avn.idl @@ -473,6 +473,12 @@ enum AvnWindowTransparencyMode Blur } +enum AvnPlatformThemeVariant +{ + Light, + Dark +} + [uuid(809c652e-7396-11d2-9771-00a0c9b4d50c)] interface IAvaloniaNativeFactory : IUnknown { @@ -494,6 +500,7 @@ interface IAvaloniaNativeFactory : IUnknown HRESULT CreateMenuItemSeparator(IAvnMenuItem** ppv); HRESULT CreateTrayIcon(IAvnTrayIcon** ppv); HRESULT CreateApplicationCommands(IAvnApplicationCommands** ppv); + HRESULT CreatePlatformSettings(IAvnPlatformSettings** ppv); } [uuid(233e094f-9b9f-44a3-9a6e-6948bbdd9fb1)] @@ -535,6 +542,7 @@ interface IAvnWindowBase : IUnknown HRESULT BeginDragAndDropOperation(AvnDragDropEffects effects, AvnPoint point, IAvnClipboard* clipboard, IAvnDndResultCallback* cb, [intptr]void* sourceHandle); HRESULT SetTransparencyMode(AvnWindowTransparencyMode mode); + HRESULT SetFrameThemeVariant(AvnPlatformThemeVariant mode); } [uuid(83e588f3-6981-4e48-9ea0-e1e569f79a91), cpp-virtual-inherits] @@ -906,3 +914,11 @@ interface IAvnAutomationNode : IUnknown void PropertyChanged(AvnAutomationProperty property); void FocusChanged(); } + +[uuid(d1f009cc-9d2d-493b-845d-90d2c104baae)] +interface IAvnPlatformSettings : IUnknown +{ + AvnPlatformThemeVariant GetPlatformTheme(); + uint GetAccentColor(); + void RegisterColorsChange(IAvnActionCallback* callback); +} From f01ea6a8a11c8750cc5c72a9be5a0867709834a5 Mon Sep 17 00:00:00 2001 From: Max Katz Date: Fri, 6 Jan 2023 02:26:41 -0500 Subject: [PATCH 12/20] Fix control catalog on mobile --- samples/ControlCatalog/MainView.xaml.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/samples/ControlCatalog/MainView.xaml.cs b/samples/ControlCatalog/MainView.xaml.cs index ecd9d0bf52..8c8be2638e 100644 --- a/samples/ControlCatalog/MainView.xaml.cs +++ b/samples/ControlCatalog/MainView.xaml.cs @@ -107,6 +107,7 @@ namespace ControlCatalog decorations.SelectedIndex = (int)window.SystemDecorations; _platformSettings.ColorValuesChanged += PlatformSettingsOnColorValuesChanged; + PlatformSettingsOnColorValuesChanged(_platformSettings, _platformSettings.GetColorValues()); } protected override void OnDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e) From 148b4a9aca16754a6b22835d6b3983fb060fa92a Mon Sep 17 00:00:00 2001 From: Max Katz Date: Fri, 6 Jan 2023 17:11:50 -0500 Subject: [PATCH 13/20] Changes after review --- samples/ControlCatalog/MainView.xaml.cs | 1 - src/iOS/Avalonia.iOS/AvaloniaViewController.cs | 6 ------ 2 files changed, 7 deletions(-) delete mode 100644 src/iOS/Avalonia.iOS/AvaloniaViewController.cs diff --git a/samples/ControlCatalog/MainView.xaml.cs b/samples/ControlCatalog/MainView.xaml.cs index 8c8be2638e..1726028a3f 100644 --- a/samples/ControlCatalog/MainView.xaml.cs +++ b/samples/ControlCatalog/MainView.xaml.cs @@ -8,7 +8,6 @@ using Avalonia.Markup.Xaml; using Avalonia.Media; using Avalonia.Media.Immutable; using Avalonia.Platform; -using Avalonia.Styling; using Avalonia.VisualTree; using ControlCatalog.Models; using ControlCatalog.Pages; diff --git a/src/iOS/Avalonia.iOS/AvaloniaViewController.cs b/src/iOS/Avalonia.iOS/AvaloniaViewController.cs deleted file mode 100644 index c6856789b8..0000000000 --- a/src/iOS/Avalonia.iOS/AvaloniaViewController.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Avalonia.iOS; - -public class AvaloniaViewController -{ - -} From 13c14e7360d032168b9a04f4fdd3089f800808bb Mon Sep 17 00:00:00 2001 From: Max Katz Date: Fri, 6 Jan 2023 22:40:23 -0500 Subject: [PATCH 14/20] Add ColorContrastPreference to the PlatformColorValues --- .../src/OSX/PlatformSettings.mm | 14 ++-- .../Platform/AndroidPlatformSettings.cs | 37 ++++++++--- .../Platform/DefaultPlatformSettings.cs | 5 +- .../Platform/PlatformColorValues.cs | 64 +++++++++++++------ .../DBusPlatformSettings.cs | 9 ++- src/Avalonia.Native/NativePlatformSettings.cs | 22 ++++++- src/Avalonia.Native/avn.idl | 4 +- .../BrowserPlatformSettings.cs | 13 ++-- .../Avalonia.Browser/Interop/DomHelper.cs | 6 +- .../webapp/modules/avalonia/dom.ts | 18 ++++-- src/Windows/Avalonia.Win32/Win32Platform.cs | 11 ++++ .../Avalonia.Win32/Win32PlatformSettings.cs | 45 ++++++++++--- .../WinRT/NativeWinRTMethods.cs | 26 +++++++- src/Windows/Avalonia.Win32/WinRT/winrt.idl | 7 ++ .../Avalonia.Win32/WindowImpl.AppWndProc.cs | 8 --- src/iOS/Avalonia.iOS/PlatformSettings.cs | 11 +++- 16 files changed, 228 insertions(+), 72 deletions(-) diff --git a/native/Avalonia.Native/src/OSX/PlatformSettings.mm b/native/Avalonia.Native/src/OSX/PlatformSettings.mm index e709cd5135..fe88214c8d 100644 --- a/native/Avalonia.Native/src/OSX/PlatformSettings.mm +++ b/native/Avalonia.Native/src/OSX/PlatformSettings.mm @@ -17,15 +17,17 @@ public: if (@available(macOS 10.14, *)) { if (NSApplication.sharedApplication.effectiveAppearance.name == NSAppearanceNameAqua - || NSApplication.sharedApplication.effectiveAppearance.name == NSAppearanceNameVibrantLight - || NSApplication.sharedApplication.effectiveAppearance.name == NSAppearanceNameAccessibilityHighContrastAqua - || NSApplication.sharedApplication.effectiveAppearance.name == NSAppearanceNameAccessibilityHighContrastVibrantLight) { + || NSApplication.sharedApplication.effectiveAppearance.name == NSAppearanceNameVibrantLight) { return AvnPlatformThemeVariant::Light; } else if (NSApplication.sharedApplication.effectiveAppearance.name == NSAppearanceNameDarkAqua - || NSApplication.sharedApplication.effectiveAppearance.name == NSAppearanceNameVibrantDark - || NSApplication.sharedApplication.effectiveAppearance.name == NSAppearanceNameAccessibilityHighContrastDarkAqua - || NSApplication.sharedApplication.effectiveAppearance.name == NSAppearanceNameAccessibilityHighContrastVibrantDark) { + || NSApplication.sharedApplication.effectiveAppearance.name == NSAppearanceNameVibrantDark) { return AvnPlatformThemeVariant::Dark; + } else if (NSApplication.sharedApplication.effectiveAppearance.name == NSAppearanceNameAccessibilityHighContrastAqua + || NSApplication.sharedApplication.effectiveAppearance.name == NSAppearanceNameAccessibilityHighContrastVibrantLight) { + return AvnPlatformThemeVariant::HighContrastLight; + } else if (|| NSApplication.sharedApplication.effectiveAppearance.name == NSAppearanceNameAccessibilityHighContrastDarkAqua + || NSApplication.sharedApplication.effectiveAppearance.name == NSAppearanceNameAccessibilityHighContrastVibrantDark) { + return AvnPlatformThemeVariant::HighContrastDark; } } return AvnPlatformThemeVariant::Light; diff --git a/src/Android/Avalonia.Android/Platform/AndroidPlatformSettings.cs b/src/Android/Avalonia.Android/Platform/AndroidPlatformSettings.cs index f560dea3ce..0872603e7b 100644 --- a/src/Android/Avalonia.Android/Platform/AndroidPlatformSettings.cs +++ b/src/Android/Avalonia.Android/Platform/AndroidPlatformSettings.cs @@ -3,6 +3,8 @@ using Android; using Android.Content; using Android.Content.Res; using Android.Graphics; +using Android.Provider; +using Android.Views.Accessibility; using AndroidX.Core.Content.Resources; using Avalonia.Media; using Avalonia.Platform; @@ -46,11 +48,14 @@ internal class AndroidPlatformSettings : DefaultPlatformSettings var accent2 = context.Resources.GetColor(17170507, context.Theme); // Resource.Color.SystemAccent2500 var accent3 = context.Resources.GetColor(17170520, context.Theme); // Resource.Color.SystemAccent3500 - _latestValues = new PlatformColorValues( - systemTheme, - new Color(accent1.A, accent1.R, accent1.G, accent1.B), - new Color(accent2.A, accent2.R, accent2.G, accent2.B), - new Color(accent3.A, accent3.R, accent3.G, accent3.B)); + _latestValues = new PlatformColorValues + { + ThemeVariant = systemTheme, + ContrastPreference = IsHighContrast(context), + AccentColor1 = new Color(accent1.A, accent1.R, accent1.G, accent1.B), + AccentColor2 = new Color(accent2.A, accent2.R, accent2.G, accent2.B), + AccentColor3 = new Color(accent3.A, accent3.R, accent3.G, accent3.B), + }; } else if (OperatingSystem.IsAndroidVersionAtLeast(23)) { @@ -58,9 +63,12 @@ internal class AndroidPlatformSettings : DefaultPlatformSettings var array = context.Theme.ObtainStyledAttributes(new[] { 16843829 }); // Resource.Attribute.ColorAccent var accent = array.GetColor(0, 0); - _latestValues = new PlatformColorValues( - systemTheme, - new Color(accent.A, accent.R, accent.G, accent.B)); + _latestValues = new PlatformColorValues + { + ThemeVariant = systemTheme, + ContrastPreference = IsHighContrast(context), + AccentColor1 = new Color(accent.A, accent.R, accent.G, accent.B) + }; array.Recycle(); } else @@ -70,4 +78,17 @@ internal class AndroidPlatformSettings : DefaultPlatformSettings OnColorValuesChanged(_latestValues); } + + private static ColorContrastPreference IsHighContrast(Context context) + { + try + { + return Settings.Secure.GetInt(context.ContentResolver, "high_text_contrast_enabled", 0) == 1 + ? ColorContrastPreference.High : ColorContrastPreference.NoPreference; + } + catch + { + return ColorContrastPreference.NoPreference; + } + } } diff --git a/src/Avalonia.Base/Platform/DefaultPlatformSettings.cs b/src/Avalonia.Base/Platform/DefaultPlatformSettings.cs index 98f2367819..b5e7298b7e 100644 --- a/src/Avalonia.Base/Platform/DefaultPlatformSettings.cs +++ b/src/Avalonia.Base/Platform/DefaultPlatformSettings.cs @@ -31,7 +31,10 @@ namespace Avalonia.Platform public virtual PlatformColorValues GetColorValues() { - return new PlatformColorValues(PlatformThemeVariant.Light); + return new PlatformColorValues + { + ThemeVariant = PlatformThemeVariant.Light + }; } public event EventHandler? ColorValuesChanged; diff --git a/src/Avalonia.Base/Platform/PlatformColorValues.cs b/src/Avalonia.Base/Platform/PlatformColorValues.cs index 3071cc217f..d14b58e939 100644 --- a/src/Avalonia.Base/Platform/PlatformColorValues.cs +++ b/src/Avalonia.Base/Platform/PlatformColorValues.cs @@ -11,32 +11,58 @@ public enum PlatformThemeVariant Dark } +/// +/// System high contrast preference. +/// +public enum ColorContrastPreference +{ + NoPreference, + High +} + /// /// Information about current system color values, including information about dark mode and accent colors. /// -/// System theme variant or mode. -/// Primary system accent color. -/// Secondary system accent color. On some platforms can return the same value as AccentColor1. -/// Tertiary system accent color. On some platforms can return the same value as AccentColor1. -public record struct PlatformColorValues( - PlatformThemeVariant ThemeVariant, - Color AccentColor1, - Color AccentColor2, - Color AccentColor3) +public record PlatformColorValues { - public PlatformColorValues( - PlatformThemeVariant ThemeVariant, - Color AccentColor1) - : this(ThemeVariant, AccentColor1, AccentColor1, AccentColor1) + private static Color DefaultAccent => new(255, 0, 120, 215); + private Color _accentColor2, _accentColor3; + + /// + /// System theme variant or mode. + /// + public PlatformThemeVariant ThemeVariant { get; init; } + + /// + /// System high contrast preference. + /// + public ColorContrastPreference ContrastPreference { get; init; } + + /// + /// Primary system accent color. + /// + public Color AccentColor1 { get; init; } + + /// + /// Secondary system accent color. On some platforms can return the same value as . + /// + public Color AccentColor2 { - + get => _accentColor2 != default ? _accentColor2 : AccentColor1; + init => _accentColor2 = value; } - - public PlatformColorValues(PlatformThemeVariant ThemeVariant) - : this(ThemeVariant, DefaultAccent) + + /// + /// Tertiary system accent color. On some platforms can return the same value as . + /// + public Color AccentColor3 { + get => _accentColor3 != default ? _accentColor3 : AccentColor1; + init => _accentColor3 = value; + } + public PlatformColorValues() + { + AccentColor1 = DefaultAccent; } - - private static Color DefaultAccent => new(255, 0, 120, 215); } diff --git a/src/Avalonia.FreeDesktop/DBusPlatformSettings.cs b/src/Avalonia.FreeDesktop/DBusPlatformSettings.cs index 82b4a72e74..039fc7c088 100644 --- a/src/Avalonia.FreeDesktop/DBusPlatformSettings.cs +++ b/src/Avalonia.FreeDesktop/DBusPlatformSettings.cs @@ -41,7 +41,7 @@ internal class DBusPlatformSettings : DefaultPlatformSettings { var value = await colorSchemeTask; _lastColorValues = GetColorValuesFromSetting(value); - OnColorValuesChanged(_lastColorValues.Value); + OnColorValuesChanged(_lastColorValues); } catch (Exception ex) { @@ -62,13 +62,16 @@ internal class DBusPlatformSettings : DefaultPlatformSettings 2: Prefer light appearance */ _lastColorValues = GetColorValuesFromSetting(tuple.value); - OnColorValuesChanged(_lastColorValues.Value); + OnColorValuesChanged(_lastColorValues); } } private static PlatformColorValues GetColorValuesFromSetting(object value) { var isDark = value?.ToString() == "1"; - return new PlatformColorValues(isDark ? PlatformThemeVariant.Dark : PlatformThemeVariant.Light); + return new PlatformColorValues + { + ThemeVariant = isDark ? PlatformThemeVariant.Dark : PlatformThemeVariant.Light + }; } } diff --git a/src/Avalonia.Native/NativePlatformSettings.cs b/src/Avalonia.Native/NativePlatformSettings.cs index cea748bbae..53d3da0378 100644 --- a/src/Avalonia.Native/NativePlatformSettings.cs +++ b/src/Avalonia.Native/NativePlatformSettings.cs @@ -18,16 +18,32 @@ internal class NativePlatformSettings : DefaultPlatformSettings public override PlatformColorValues GetColorValues() { - var theme = (PlatformThemeVariant)_platformSettings.PlatformTheme; + var (theme, contrast) = _platformSettings.PlatformTheme switch + { + AvnPlatformThemeVariant.Dark => (PlatformThemeVariant.Dark, ColorContrastPreference.NoPreference), + AvnPlatformThemeVariant.Light => (PlatformThemeVariant.Light, ColorContrastPreference.NoPreference), + AvnPlatformThemeVariant.HighContrastDark => (PlatformThemeVariant.Dark, ColorContrastPreference.High), + AvnPlatformThemeVariant.HighContrastLight => (PlatformThemeVariant.Dark, ColorContrastPreference.High), + _ => throw new ArgumentOutOfRangeException() + }; var color = _platformSettings.AccentColor; if (color > 0) { - _lastColorValues = new PlatformColorValues(theme, Color.FromUInt32(color)); + _lastColorValues = new PlatformColorValues + { + ThemeVariant = theme, + ContrastPreference = contrast, + AccentColor1 = Color.FromUInt32(color) + }; } else { - _lastColorValues = new PlatformColorValues(theme); + _lastColorValues = new PlatformColorValues + { + ThemeVariant = theme, + ContrastPreference = contrast + }; } return _lastColorValues; diff --git a/src/Avalonia.Native/avn.idl b/src/Avalonia.Native/avn.idl index 29f839c826..a062fdc61d 100644 --- a/src/Avalonia.Native/avn.idl +++ b/src/Avalonia.Native/avn.idl @@ -476,7 +476,9 @@ enum AvnWindowTransparencyMode enum AvnPlatformThemeVariant { Light, - Dark + Dark, + HighContrastLight, + HighContrastDark, } [uuid(809c652e-7396-11d2-9771-00a0c9b4d50c)] diff --git a/src/Browser/Avalonia.Browser/BrowserPlatformSettings.cs b/src/Browser/Avalonia.Browser/BrowserPlatformSettings.cs index 8a5a089752..6084c5c7de 100644 --- a/src/Browser/Avalonia.Browser/BrowserPlatformSettings.cs +++ b/src/Browser/Avalonia.Browser/BrowserPlatformSettings.cs @@ -6,21 +6,26 @@ namespace Avalonia.Browser; internal class BrowserPlatformSettings : DefaultPlatformSettings { private bool _isDarkMode; + private bool _isHighContrast; public BrowserPlatformSettings() { - _isDarkMode = DomHelper.ObserveDarkMode(m => + var obj = DomHelper.ObserveDarkMode((isDarkMode, isHighContrast) => { - _isDarkMode = m; + _isDarkMode = isDarkMode; + _isHighContrast = isHighContrast; OnColorValuesChanged(GetColorValues()); }); + _isDarkMode = obj.GetPropertyAsBoolean("isDarkMode"); + _isHighContrast = obj.GetPropertyAsBoolean("isHighContrast"); } - + public override PlatformColorValues GetColorValues() { return base.GetColorValues() with { - ThemeVariant = _isDarkMode ? PlatformThemeVariant.Dark : PlatformThemeVariant.Light + ThemeVariant = _isDarkMode ? PlatformThemeVariant.Dark : PlatformThemeVariant.Light, + ContrastPreference = _isHighContrast ? ColorContrastPreference.High : ColorContrastPreference.NoPreference }; } } diff --git a/src/Browser/Avalonia.Browser/Interop/DomHelper.cs b/src/Browser/Avalonia.Browser/Interop/DomHelper.cs index 090f03c4f1..9ee96768ec 100644 --- a/src/Browser/Avalonia.Browser/Interop/DomHelper.cs +++ b/src/Browser/Avalonia.Browser/Interop/DomHelper.cs @@ -27,7 +27,7 @@ internal static partial class DomHelper Action onDpiChanged); [JSImport("AvaloniaDOM.observeDarkMode", AvaloniaModule.MainModuleName)] - public static partial bool ObserveDarkMode( - [JSMarshalAs>] - Action onDpiChanged); + public static partial JSObject ObserveDarkMode( + [JSMarshalAs>] + Action onDpiChanged); } diff --git a/src/Browser/Avalonia.Browser/webapp/modules/avalonia/dom.ts b/src/Browser/Avalonia.Browser/webapp/modules/avalonia/dom.ts index 9760fccc3b..b99f8e7907 100644 --- a/src/Browser/Avalonia.Browser/webapp/modules/avalonia/dom.ts +++ b/src/Browser/Avalonia.Browser/webapp/modules/avalonia/dom.ts @@ -3,17 +3,25 @@ export class AvaloniaDOM { element.classList.add(className); } - static observeDarkMode(observer: (isDarkMode: boolean) => boolean): boolean { + static observeDarkMode(observer: (isDarkMode: boolean, isHighContrast: boolean) => boolean) { if (globalThis.matchMedia === undefined) { return false; } - const media = globalThis.matchMedia("(prefers-color-scheme: dark)"); - media.addEventListener("change", (args: MediaQueryListEvent) => { - observer(args.matches); + const colorShemeMedia = globalThis.matchMedia("(prefers-color-scheme: dark)"); + const prefersContrastMedia = globalThis.matchMedia("(prefers-contrast: more)"); + + colorShemeMedia.addEventListener("change", (args: MediaQueryListEvent) => { + observer(args.matches, prefersContrastMedia.matches); + }); + prefersContrastMedia.addEventListener("change", (args: MediaQueryListEvent) => { + observer(colorShemeMedia.matches, args.matches); }); - return media.matches; + return { + isDarkMode: colorShemeMedia.matches, + isHighContrast: prefersContrastMedia.matches + }; } static createAvaloniaHost(host: HTMLElement) { diff --git a/src/Windows/Avalonia.Win32/Win32Platform.cs b/src/Windows/Avalonia.Win32/Win32Platform.cs index c0a0ce4da0..26c9fc7abe 100644 --- a/src/Windows/Avalonia.Win32/Win32Platform.cs +++ b/src/Windows/Avalonia.Win32/Win32Platform.cs @@ -287,6 +287,17 @@ namespace Avalonia.Win32 } } } + + if (msg == (uint)WindowsMessage.WM_SETTINGCHANGE + && PlatformSettings is Win32PlatformSettings win32PlatformSettings) + { + var changedSetting = Marshal.PtrToStringAuto(lParam); + if (changedSetting == "ImmersiveColorSet" // dark/light mode + || changedSetting == "WindowsThemeElement") // high contrast mode + { + win32PlatformSettings.OnColorValuesChanged(); + } + } TrayIconImpl.ProcWnd(hWnd, msg, wParam, lParam); diff --git a/src/Windows/Avalonia.Win32/Win32PlatformSettings.cs b/src/Windows/Avalonia.Win32/Win32PlatformSettings.cs index 73ac5bf5a9..a021a3c48c 100644 --- a/src/Windows/Avalonia.Win32/Win32PlatformSettings.cs +++ b/src/Windows/Avalonia.Win32/Win32PlatformSettings.cs @@ -1,4 +1,5 @@ using System; +using System.Runtime.InteropServices; using Avalonia.Input; using Avalonia.Platform; using Avalonia.Win32.Interop; @@ -38,18 +39,44 @@ internal class Win32PlatformSettings : DefaultPlatformSettings return base.GetColorValues(); } - var settings = NativeWinRTMethods.CreateInstance("Windows.UI.ViewManagement.UISettings"); - var accent = settings.GetColorValue(UIColorType.Accent).ToAvalonia(); - var background = settings.GetColorValue(UIColorType.Background).ToAvalonia(); + var uiSettings = NativeWinRTMethods.CreateInstance("Windows.UI.ViewManagement.UISettings"); + var accent = uiSettings.GetColorValue(UIColorType.Accent).ToAvalonia(); - return _lastColorValues = new PlatformColorValues( - background.R + background.G + background.B < (255 * 3 - background.R - background.G - background.B) - ? PlatformThemeVariant.Dark - : PlatformThemeVariant.Light, - accent, accent, accent); + var accessibilitySettings = NativeWinRTMethods.CreateInstance("Windows.UI.ViewManagement.AccessibilitySettings"); + if (accessibilitySettings.HighContrast == 1) + { + // Windows 11 has 4 different high contrast schemes: + // - Aquatic - High Contrast Black + // - Desert - High Contrast White + // - Dusk - High Contrast #1 + // - Night sky - High Contrast #2 + // Only "Desert" one can be considered a "light" preference. + using var highContrastScheme = new HStringInterop(accessibilitySettings.HighContrastScheme); + return _lastColorValues = new PlatformColorValues + { + ThemeVariant = highContrastScheme.Value?.Contains("White", StringComparison.OrdinalIgnoreCase) == true ? + PlatformThemeVariant.Light : + PlatformThemeVariant.Dark, + ContrastPreference = ColorContrastPreference.High, + // Windows provides more than one accent color for the HighContrast themes, but with no API for that (at least not in the WinRT) + AccentColor1 = accent + }; + } + else + { + var background = uiSettings.GetColorValue(UIColorType.Background).ToAvalonia(); + return _lastColorValues = new PlatformColorValues + { + ThemeVariant = background.R + background.G + background.B < (255 * 3 - background.R - background.G - background.B) ? + PlatformThemeVariant.Dark : + PlatformThemeVariant.Light, + ContrastPreference = ColorContrastPreference.NoPreference, + AccentColor1 = accent + }; + } } - internal void OnColorValuesChanged(IntPtr handle) + internal void OnColorValuesChanged() { var oldColorValues = _lastColorValues; var colorValues = GetColorValues(); diff --git a/src/Windows/Avalonia.Win32/WinRT/NativeWinRTMethods.cs b/src/Windows/Avalonia.Win32/WinRT/NativeWinRTMethods.cs index 1794b022fe..01b45f3df2 100644 --- a/src/Windows/Avalonia.Win32/WinRT/NativeWinRTMethods.cs +++ b/src/Windows/Avalonia.Win32/WinRT/NativeWinRTMethods.cs @@ -22,6 +22,9 @@ namespace Avalonia.Win32.WinRT internal static IntPtr WindowsCreateString(string sourceString) => WindowsCreateString(sourceString, sourceString.Length); + [DllImport("api-ms-win-core-winrt-string-l1-1-0.dll", CallingConvention = CallingConvention.StdCall)] + internal static extern unsafe char* WindowsGetStringRawBuffer(IntPtr hstring, uint* length); + [DllImport("api-ms-win-core-winrt-string-l1-1-0.dll", CallingConvention = CallingConvention.StdCall, PreserveSig = false)] internal static extern unsafe void WindowsDeleteString(IntPtr hString); @@ -120,17 +123,38 @@ namespace Avalonia.Win32.WinRT class HStringInterop : IDisposable { private IntPtr _s; + private bool _owns; public HStringInterop(string s) { _s = s == null ? IntPtr.Zero : NativeWinRTMethods.WindowsCreateString(s); + _owns = true; + } + + public HStringInterop(IntPtr str, bool owns = false) + { + _s = str; + _owns = owns; } public IntPtr Handle => _s; + public unsafe string Value + { + get + { + if (_s == IntPtr.Zero) + return null; + + uint length; + var buffer = NativeWinRTMethods.WindowsGetStringRawBuffer(_s, &length); + return new string(buffer, 0, (int) length); + } + } + public void Dispose() { - if (_s != IntPtr.Zero) + if (_s != IntPtr.Zero && _owns) { NativeWinRTMethods.WindowsDeleteString(_s); _s = IntPtr.Zero; diff --git a/src/Windows/Avalonia.Win32/WinRT/winrt.idl b/src/Windows/Avalonia.Win32/WinRT/winrt.idl index 8dee275d50..beb88352dd 100644 --- a/src/Windows/Avalonia.Win32/WinRT/winrt.idl +++ b/src/Windows/Avalonia.Win32/WinRT/winrt.idl @@ -864,3 +864,10 @@ interface IUISettings3 : IInspectable { HRESULT GetColorValue([in] UIColorType desiredColor, [out][retval] Color* value); } + +[uuid(FE0E8147-C4C0-4562-B962-1327B52AD5B9)] +interface IAccessibilitySettings : IInspectable +{ + [propget] HRESULT HighContrast([out] [retval] boolean* value); + [propget] HRESULT HighContrastScheme([out] [retval] HSTRING* value); +} diff --git a/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs b/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs index af634fd760..8c362b0c29 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs @@ -732,14 +732,6 @@ namespace Avalonia.Win32 var node = AutomationNode.GetOrCreate(peer); return UiaCoreProviderApi.UiaReturnRawElementProvider(_hwnd, wParam, lParam, node); } - break; - case WindowsMessage.WM_SETTINGCHANGE: - if (Marshal.PtrToStringAuto(lParam) == "ImmersiveColorSet" - && Win32Platform.PlatformSettings is Win32PlatformSettings win32PlatformSettings) - { - win32PlatformSettings.OnColorValuesChanged(_hwnd); - } - break; } diff --git a/src/iOS/Avalonia.iOS/PlatformSettings.cs b/src/iOS/Avalonia.iOS/PlatformSettings.cs index 736e94ffc6..b8557f31bd 100644 --- a/src/iOS/Avalonia.iOS/PlatformSettings.cs +++ b/src/iOS/Avalonia.iOS/PlatformSettings.cs @@ -17,8 +17,17 @@ internal class PlatformSettings : DefaultPlatformSettings var themeVariant = UITraitCollection.CurrentTraitCollection.UserInterfaceStyle == UIUserInterfaceStyle.Dark ? PlatformThemeVariant.Dark : PlatformThemeVariant.Light; + + + var contrastPreference = UITraitCollection.CurrentTraitCollection.AccessibilityContrast == UIAccessibilityContrast.High ? + ColorContrastPreference.High : + ColorContrastPreference.NoPreference; - return _lastColorValues = new PlatformColorValues(themeVariant); + return _lastColorValues = new PlatformColorValues + { + ThemeVariant = themeVariant, + ContrastPreference = contrastPreference + }; } public void TraitCollectionDidChange() From 124c87b2cf4f15963c78511849c0c31b6398a5f0 Mon Sep 17 00:00:00 2001 From: Max Katz Date: Fri, 6 Jan 2023 23:01:13 -0500 Subject: [PATCH 15/20] Add iOS tint/accent color support --- src/iOS/Avalonia.iOS/AvaloniaView.cs | 8 ++++++ src/iOS/Avalonia.iOS/PlatformSettings.cs | 32 ++++++++++++++++++++---- 2 files changed, 35 insertions(+), 5 deletions(-) diff --git a/src/iOS/Avalonia.iOS/AvaloniaView.cs b/src/iOS/Avalonia.iOS/AvaloniaView.cs index 6d36ca7814..7442861597 100644 --- a/src/iOS/Avalonia.iOS/AvaloniaView.cs +++ b/src/iOS/Avalonia.iOS/AvaloniaView.cs @@ -60,6 +60,14 @@ namespace Avalonia.iOS settings?.TraitCollectionDidChange(); } + public override void TintColorDidChange() + { + base.TintColorDidChange(); + + var settings = AvaloniaLocator.Current.GetRequiredService() as PlatformSettings; + settings?.TraitCollectionDidChange(); + } + internal class TopLevelImpl : ITopLevelImplWithTextInputMethod, ITopLevelImplWithNativeControlHost, ITopLevelImplWithStorageProvider { diff --git a/src/iOS/Avalonia.iOS/PlatformSettings.cs b/src/iOS/Avalonia.iOS/PlatformSettings.cs index b8557f31bd..0ae290468b 100644 --- a/src/iOS/Avalonia.iOS/PlatformSettings.cs +++ b/src/iOS/Avalonia.iOS/PlatformSettings.cs @@ -22,12 +22,34 @@ internal class PlatformSettings : DefaultPlatformSettings var contrastPreference = UITraitCollection.CurrentTraitCollection.AccessibilityContrast == UIAccessibilityContrast.High ? ColorContrastPreference.High : ColorContrastPreference.NoPreference; - - return _lastColorValues = new PlatformColorValues + + UIColor? tintColor = null; + if (OperatingSystem.IsIOSVersionAtLeast(14)) + { + tintColor = UIConfigurationColorTransformer.PreferredTint(UIColor.Clear); + } + + if (tintColor is not null) { - ThemeVariant = themeVariant, - ContrastPreference = contrastPreference - }; + tintColor.GetRGBA(out var red, out var green, out var blue, out var alpha); + return _lastColorValues = new PlatformColorValues + { + ThemeVariant = themeVariant, + ContrastPreference = contrastPreference, + AccentColor1 = new Color( + (byte)(alpha * 255), + (byte)(red * 255), + (byte)(green * 255), + (byte)(blue * 255)) + }; + } + else + { + return _lastColorValues = new PlatformColorValues + { + ThemeVariant = themeVariant, ContrastPreference = contrastPreference + }; + } } public void TraitCollectionDidChange() From f7605ca22e66856a19b7c4fc465cbfe92ccc06b3 Mon Sep 17 00:00:00 2001 From: Max Katz Date: Fri, 6 Jan 2023 23:05:17 -0500 Subject: [PATCH 16/20] Fix build --- native/Avalonia.Native/src/OSX/PlatformSettings.mm | 2 +- src/Windows/Avalonia.Win32/Win32PlatformSettings.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/native/Avalonia.Native/src/OSX/PlatformSettings.mm b/native/Avalonia.Native/src/OSX/PlatformSettings.mm index fe88214c8d..34ceb93186 100644 --- a/native/Avalonia.Native/src/OSX/PlatformSettings.mm +++ b/native/Avalonia.Native/src/OSX/PlatformSettings.mm @@ -25,7 +25,7 @@ public: } else if (NSApplication.sharedApplication.effectiveAppearance.name == NSAppearanceNameAccessibilityHighContrastAqua || NSApplication.sharedApplication.effectiveAppearance.name == NSAppearanceNameAccessibilityHighContrastVibrantLight) { return AvnPlatformThemeVariant::HighContrastLight; - } else if (|| NSApplication.sharedApplication.effectiveAppearance.name == NSAppearanceNameAccessibilityHighContrastDarkAqua + } else if (NSApplication.sharedApplication.effectiveAppearance.name == NSAppearanceNameAccessibilityHighContrastDarkAqua || NSApplication.sharedApplication.effectiveAppearance.name == NSAppearanceNameAccessibilityHighContrastVibrantDark) { return AvnPlatformThemeVariant::HighContrastDark; } diff --git a/src/Windows/Avalonia.Win32/Win32PlatformSettings.cs b/src/Windows/Avalonia.Win32/Win32PlatformSettings.cs index a021a3c48c..0310c4bfad 100644 --- a/src/Windows/Avalonia.Win32/Win32PlatformSettings.cs +++ b/src/Windows/Avalonia.Win32/Win32PlatformSettings.cs @@ -54,7 +54,7 @@ internal class Win32PlatformSettings : DefaultPlatformSettings using var highContrastScheme = new HStringInterop(accessibilitySettings.HighContrastScheme); return _lastColorValues = new PlatformColorValues { - ThemeVariant = highContrastScheme.Value?.Contains("White", StringComparison.OrdinalIgnoreCase) == true ? + ThemeVariant = highContrastScheme.Value?.Contains("White") == true ? PlatformThemeVariant.Light : PlatformThemeVariant.Dark, ContrastPreference = ColorContrastPreference.High, From 289b14a48c8737d93cd383ea408f40e75f1e6a7f Mon Sep 17 00:00:00 2001 From: Max Katz Date: Sun, 8 Jan 2023 15:02:43 -0500 Subject: [PATCH 17/20] Apply suggestions from code review --- src/Browser/Avalonia.Browser/Interop/DomHelper.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Browser/Avalonia.Browser/Interop/DomHelper.cs b/src/Browser/Avalonia.Browser/Interop/DomHelper.cs index 9ee96768ec..d1133b8916 100644 --- a/src/Browser/Avalonia.Browser/Interop/DomHelper.cs +++ b/src/Browser/Avalonia.Browser/Interop/DomHelper.cs @@ -29,5 +29,5 @@ internal static partial class DomHelper [JSImport("AvaloniaDOM.observeDarkMode", AvaloniaModule.MainModuleName)] public static partial JSObject ObserveDarkMode( [JSMarshalAs>] - Action onDpiChanged); + Action observer); } From 5936f52b2c0cba31d11bf366e74dfedfda299a12 Mon Sep 17 00:00:00 2001 From: Emmanuel Hansen Date: Tue, 3 Jan 2023 13:48:36 +0000 Subject: [PATCH 18/20] Add back requested event for TopLevel --- .../Avalonia.Android/AvaloniaMainActivity.cs | 19 ++++--- .../IAndroidNavigationService.cs | 14 +++++ .../AndroidSystemNavigationManager.cs | 28 ++++++++++ .../Platform/SkiaPlatform/TopLevelImpl.cs | 7 ++- .../Platform/SystemNavigationManager.cs | 18 ++++++ src/Avalonia.Controls/TopLevel.cs | 56 +++++++++++++++++++ 6 files changed, 134 insertions(+), 8 deletions(-) create mode 100644 src/Android/Avalonia.Android/IAndroidNavigationService.cs create mode 100644 src/Android/Avalonia.Android/Platform/AndroidSystemNavigationManager.cs create mode 100644 src/Avalonia.Base/Platform/SystemNavigationManager.cs diff --git a/src/Android/Avalonia.Android/AvaloniaMainActivity.cs b/src/Android/Avalonia.Android/AvaloniaMainActivity.cs index b24581fb8b..c06f5c74ec 100644 --- a/src/Android/Avalonia.Android/AvaloniaMainActivity.cs +++ b/src/Android/Avalonia.Android/AvaloniaMainActivity.cs @@ -1,18 +1,14 @@ using System; using Android.App; using Android.Content; -using Android.Content.Res; using Android.OS; using Android.Runtime; using Android.Views; using AndroidX.AppCompat.App; -using AndroidX.Lifecycle; - -using AndroidRect = Android.Graphics.Rect; namespace Avalonia.Android { - public abstract class AvaloniaMainActivity : AppCompatActivity, IActivityResultHandler + public abstract class AvaloniaMainActivity : AppCompatActivity, IActivityResultHandler, IActivityNavigationService { internal static object ViewContent; @@ -56,9 +52,18 @@ namespace Avalonia.Android } } - public override void OnConfigurationChanged(Configuration newConfig) + public event EventHandler BackRequested; + + public override void OnBackPressed() { - base.OnConfigurationChanged(newConfig); + var eventArgs = new AndroidBackRequestedEventArgs(); + + BackRequested?.Invoke(this, eventArgs); + + if (!eventArgs.Handled) + { + base.OnBackPressed(); + } } protected override void OnDestroy() diff --git a/src/Android/Avalonia.Android/IAndroidNavigationService.cs b/src/Android/Avalonia.Android/IAndroidNavigationService.cs new file mode 100644 index 0000000000..5fa93971d9 --- /dev/null +++ b/src/Android/Avalonia.Android/IAndroidNavigationService.cs @@ -0,0 +1,14 @@ +using System; + +namespace Avalonia.Android +{ + public interface IActivityNavigationService + { + event EventHandler BackRequested; + } + + public class AndroidBackRequestedEventArgs : EventArgs + { + public bool Handled { get; set; } + } +} diff --git a/src/Android/Avalonia.Android/Platform/AndroidSystemNavigationManager.cs b/src/Android/Avalonia.Android/Platform/AndroidSystemNavigationManager.cs new file mode 100644 index 0000000000..bb9dc66f5a --- /dev/null +++ b/src/Android/Avalonia.Android/Platform/AndroidSystemNavigationManager.cs @@ -0,0 +1,28 @@ +using System; +using Avalonia.Interactivity; +using Avalonia.Platform; + +namespace Avalonia.Android.Platform +{ + internal class AndroidSystemNavigationManager : ISystemNavigationManager + { + public event EventHandler BackRequested; + + public AndroidSystemNavigationManager(IActivityNavigationService? navigationService) + { + if(navigationService != null) + { + navigationService.BackRequested += OnBackRequested; + } + } + + private void OnBackRequested(object sender, AndroidBackRequestedEventArgs e) + { + var routedEventArgs = new RoutedEventArgs(); + + BackRequested?.Invoke(this, routedEventArgs); + + e.Handled = routedEventArgs.Handled; + } + } +} diff --git a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs index 1aac567dda..c504a951e5 100644 --- a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs +++ b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs @@ -31,7 +31,8 @@ using Android.Graphics.Drawables; namespace Avalonia.Android.Platform.SkiaPlatform { class TopLevelImpl : IAndroidView, ITopLevelImpl, EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo, - ITopLevelImplWithTextInputMethod, ITopLevelImplWithNativeControlHost, ITopLevelImplWithStorageProvider + ITopLevelImplWithTextInputMethod, ITopLevelImplWithNativeControlHost, ITopLevelImplWithStorageProvider, + ITopLevelWithSystemNavigationManager { private readonly IGlPlatformSurface _gl; private readonly IFramebufferPlatformSurface _framebuffer; @@ -57,6 +58,8 @@ namespace Avalonia.Android.Platform.SkiaPlatform NativeControlHost = new AndroidNativeControlHostImpl(avaloniaView); StorageProvider = new AndroidStorageProvider((Activity)avaloniaView.Context); + + SystemNavigationManager = new AndroidSystemNavigationManager(avaloniaView.Context as IActivityNavigationService); } public virtual Point GetAvaloniaPointFromEvent(MotionEvent e, int pointerIndex) => @@ -300,6 +303,8 @@ namespace Avalonia.Android.Platform.SkiaPlatform public IStorageProvider StorageProvider { get; } + public ISystemNavigationManager SystemNavigationManager { get; } + public void SetTransparencyLevelHint(WindowTransparencyLevel transparencyLevel) { if (TransparencyLevel != transparencyLevel) diff --git a/src/Avalonia.Base/Platform/SystemNavigationManager.cs b/src/Avalonia.Base/Platform/SystemNavigationManager.cs new file mode 100644 index 0000000000..6165b2bb77 --- /dev/null +++ b/src/Avalonia.Base/Platform/SystemNavigationManager.cs @@ -0,0 +1,18 @@ +using System; +using Avalonia.Interactivity; +using Avalonia.Metadata; + +namespace Avalonia.Platform +{ + [Unstable] + public interface ITopLevelWithSystemNavigationManager + { + ISystemNavigationManager SystemNavigationManager { get; } + } + + [Unstable] + public interface ISystemNavigationManager + { + public event EventHandler? BackRequested; + } +} diff --git a/src/Avalonia.Controls/TopLevel.cs b/src/Avalonia.Controls/TopLevel.cs index 9970a0f1c7..66c108b436 100644 --- a/src/Avalonia.Controls/TopLevel.cs +++ b/src/Avalonia.Controls/TopLevel.cs @@ -7,6 +7,7 @@ using Avalonia.Controls.Primitives; using Avalonia.Input; using Avalonia.Input.Raw; using Avalonia.Input.TextInput; +using Avalonia.Interactivity; using Avalonia.Layout; using Avalonia.Logging; using Avalonia.LogicalTree; @@ -76,6 +77,12 @@ namespace Avalonia.Controls public static readonly StyledProperty TransparencyBackgroundFallbackProperty = AvaloniaProperty.Register(nameof(TransparencyBackgroundFallback), Brushes.White); + /// + /// Defines the event. + /// + public static readonly RoutedEvent BackRequestedEvent = + RoutedEvent.Register(nameof(BackRequested), RoutingStrategies.Bubble); + private static readonly WeakEvent ResourcesChangedWeakEvent = WeakEvent.Register( (s, h) => s.ResourcesChanged += h, @@ -89,6 +96,7 @@ namespace Avalonia.Controls private readonly IGlobalStyles? _globalStyles; private readonly PointerOverPreProcessor? _pointerOverPreProcessor; private readonly IDisposable? _pointerOverPreProcessorSubscription; + private readonly IDisposable? _backGestureSubscription; private Size _clientSize; private Size? _frameSize; private WindowTransparencyLevel _actualTransparencyLevel; @@ -205,6 +213,44 @@ namespace Avalonia.Controls _pointerOverPreProcessor = new PointerOverPreProcessor(this); _pointerOverPreProcessorSubscription = _inputManager?.PreProcess.Subscribe(_pointerOverPreProcessor); + + if(impl is ITopLevelWithSystemNavigationManager topLevelWithSystemNavigation) + { + topLevelWithSystemNavigation.SystemNavigationManager.BackRequested += (s, e) => + { + e.RoutedEvent = BackRequestedEvent; + RaiseEvent(e); + }; + } + + var backKeyGesture = new KeyGesture(Key.Left, KeyModifiers.Alt); + _backGestureSubscription = _inputManager?.PreProcess.OfType().Subscribe(e => + { + bool backRequested = false; + + if (e is RawKeyEventArgs rawKeyEventArgs && rawKeyEventArgs.Type == RawKeyEventType.KeyDown) + { + var keyEvent = new KeyEventArgs() + { + KeyModifiers = (KeyModifiers)rawKeyEventArgs.Modifiers, + Key = rawKeyEventArgs.Key + }; + + backRequested = backKeyGesture.Matches(keyEvent); + } + else if(e is RawPointerEventArgs pointerEventArgs) + { + backRequested = pointerEventArgs.Type == RawPointerEventType.XButton1Down; + } + + if (backRequested) + { + var backRequestedEventArgs = new RoutedEventArgs(BackRequestedEvent); + RaiseEvent(backRequestedEventArgs); + + e.Handled = backRequestedEventArgs.Handled; + } + }); } /// @@ -263,6 +309,15 @@ namespace Avalonia.Controls set => SetValue(TransparencyBackgroundFallbackProperty, value); } + /// + /// Occurs when physical Back Button is pressed or a back navigation has been requested. + /// + public event EventHandler BackRequested + { + add { AddHandler(BackRequestedEvent, value); } + remove { RemoveHandler(BackRequestedEvent, value); } + } + public ILayoutManager LayoutManager { get @@ -382,6 +437,7 @@ namespace Avalonia.Controls _pointerOverPreProcessor?.OnCompleted(); _pointerOverPreProcessorSubscription?.Dispose(); + _backGestureSubscription?.Dispose(); PlatformImpl = null; From a75753c9426c5c47b490c24511d082704e244a10 Mon Sep 17 00:00:00 2001 From: Dmitry Zhelnin Date: Fri, 6 Jan 2023 16:28:34 +0300 Subject: [PATCH 19/20] EventRoute: remove additional check if event was handled --- src/Avalonia.Base/Interactivity/EventRoute.cs | 5 +--- .../Interactivity/InteractiveTests.cs | 26 +++++++++++++++++++ 2 files changed, 27 insertions(+), 4 deletions(-) diff --git a/src/Avalonia.Base/Interactivity/EventRoute.cs b/src/Avalonia.Base/Interactivity/EventRoute.cs index 3491465a68..37539a7153 100644 --- a/src/Avalonia.Base/Interactivity/EventRoute.cs +++ b/src/Avalonia.Base/Interactivity/EventRoute.cs @@ -143,10 +143,7 @@ namespace Avalonia.Interactivity // If we've got to a new control then call any RoutedEvent.Raised listeners. if (entry.Target != lastTarget) { - if (!e.Handled) - { - _event.InvokeRaised(entry.Target, e); - } + _event.InvokeRaised(entry.Target, e); // If this is a direct event and we've already raised events then we're finished. if (e.Route == RoutingStrategies.Direct && lastTarget is object) diff --git a/tests/Avalonia.Base.UnitTests/Interactivity/InteractiveTests.cs b/tests/Avalonia.Base.UnitTests/Interactivity/InteractiveTests.cs index 1a8b72d5e5..49119c3830 100644 --- a/tests/Avalonia.Base.UnitTests/Interactivity/InteractiveTests.cs +++ b/tests/Avalonia.Base.UnitTests/Interactivity/InteractiveTests.cs @@ -337,6 +337,27 @@ namespace Avalonia.Base.UnitTests.Interactivity Assert.True(target.GetVisualParent().ClassHandlerInvoked); } + [Fact] + public void Typed_Class_Handlers_Should_Be_Called_For_Handled_Events() + { + var ev = new RoutedEvent( + "test", + RoutingStrategies.Bubble | RoutingStrategies.Tunnel, + typeof(TestInteractive)); + + var target = CreateTree(ev, null, 0); + + ev.AddClassHandler((x, e) => x.MarkEventAsHandled(e), RoutingStrategies.Bubble); + ev.AddClassHandler((x, e) => x.ClassHandler(e), RoutingStrategies.Bubble, handledEventsToo: true); + + var args = new RoutedEventArgs(ev, target); + target.RaiseEvent(args); + + Assert.True(args.Handled); + Assert.True(target.ClassHandlerInvoked); + Assert.True(target.GetVisualParent().ClassHandlerInvoked); + } + [Fact] public void GetObservable_Should_Listen_To_Event() { @@ -443,6 +464,11 @@ namespace Avalonia.Base.UnitTests.Interactivity { ClassHandlerInvoked = true; } + + public void MarkEventAsHandled(RoutedEventArgs e) + { + e.Handled = true; + } } } } From ac206db0980fd3483b3dc3c5db571ec512f965ca Mon Sep 17 00:00:00 2001 From: Emmanuel Hansen Date: Mon, 16 Jan 2023 14:14:53 +0000 Subject: [PATCH 20/20] add browser support --- .../Platform/PlatformHotkeyConfiguration.cs | 5 ++++ src/Avalonia.Controls/TopLevel.cs | 20 ++++++++++------ .../BrowserSystemNavigationManager.cs | 24 +++++++++++++++++++ .../Avalonia.Browser/BrowserTopLevelImpl.cs | 5 +++- .../Interop/NavigationHelper.cs | 10 ++++++++ .../webapp/modules/avalonia.ts | 5 +++- .../modules/avalonia/navigationHelper.ts | 14 +++++++++++ 7 files changed, 74 insertions(+), 9 deletions(-) create mode 100644 src/Browser/Avalonia.Browser/BrowserSystemNavigationManager.cs create mode 100644 src/Browser/Avalonia.Browser/Interop/NavigationHelper.cs create mode 100644 src/Browser/Avalonia.Browser/webapp/modules/avalonia/navigationHelper.ts diff --git a/src/Avalonia.Base/Input/Platform/PlatformHotkeyConfiguration.cs b/src/Avalonia.Base/Input/Platform/PlatformHotkeyConfiguration.cs index 1fc46dd396..b87ca5eded 100644 --- a/src/Avalonia.Base/Input/Platform/PlatformHotkeyConfiguration.cs +++ b/src/Avalonia.Base/Input/Platform/PlatformHotkeyConfiguration.cs @@ -81,6 +81,10 @@ namespace Avalonia.Input.Platform { new KeyGesture(Key.Apps) }; + Back = new List + { + new KeyGesture(Key.Left, KeyModifiers.Alt) + }; } public KeyModifiers CommandModifiers { get; set; } @@ -101,5 +105,6 @@ namespace Avalonia.Input.Platform public List MoveCursorToTheStartOfDocumentWithSelection { get; set; } public List MoveCursorToTheEndOfDocumentWithSelection { get; set; } public List OpenContextMenu { get; set; } + public List Back { get; set; } } } diff --git a/src/Avalonia.Controls/TopLevel.cs b/src/Avalonia.Controls/TopLevel.cs index 66c108b436..c0265e28b9 100644 --- a/src/Avalonia.Controls/TopLevel.cs +++ b/src/Avalonia.Controls/TopLevel.cs @@ -18,6 +18,8 @@ using Avalonia.Rendering; using Avalonia.Styling; using Avalonia.Utilities; using Avalonia.VisualTree; +using Avalonia.Input.Platform; +using System.Linq; namespace Avalonia.Controls { @@ -223,20 +225,24 @@ namespace Avalonia.Controls }; } - var backKeyGesture = new KeyGesture(Key.Left, KeyModifiers.Alt); - _backGestureSubscription = _inputManager?.PreProcess.OfType().Subscribe(e => + _backGestureSubscription = _inputManager?.PreProcess.Subscribe(e => { bool backRequested = false; if (e is RawKeyEventArgs rawKeyEventArgs && rawKeyEventArgs.Type == RawKeyEventType.KeyDown) { - var keyEvent = new KeyEventArgs() + var keymap = AvaloniaLocator.Current.GetService()?.Back; + + if (keymap != null) { - KeyModifiers = (KeyModifiers)rawKeyEventArgs.Modifiers, - Key = rawKeyEventArgs.Key - }; + var keyEvent = new KeyEventArgs() + { + KeyModifiers = (KeyModifiers)rawKeyEventArgs.Modifiers, + Key = rawKeyEventArgs.Key + }; - backRequested = backKeyGesture.Matches(keyEvent); + backRequested = keymap.Any( key => key.Matches(keyEvent)); + } } else if(e is RawPointerEventArgs pointerEventArgs) { diff --git a/src/Browser/Avalonia.Browser/BrowserSystemNavigationManager.cs b/src/Browser/Avalonia.Browser/BrowserSystemNavigationManager.cs new file mode 100644 index 0000000000..cc7d8c3af1 --- /dev/null +++ b/src/Browser/Avalonia.Browser/BrowserSystemNavigationManager.cs @@ -0,0 +1,24 @@ +using System; +using Avalonia.Browser.Interop; +using Avalonia.Interactivity; +using Avalonia.Platform; + +namespace Avalonia.Browser +{ + internal class BrowserSystemNavigationManager : ISystemNavigationManager + { + public event EventHandler? BackRequested; + + public BrowserSystemNavigationManager() + { + NavigationHelper.AddBackHandler(() => + { + var routedEventArgs = new RoutedEventArgs(); + + BackRequested?.Invoke(this, routedEventArgs); + + return routedEventArgs.Handled; + }); + } + } +} diff --git a/src/Browser/Avalonia.Browser/BrowserTopLevelImpl.cs b/src/Browser/Avalonia.Browser/BrowserTopLevelImpl.cs index 4e6be3268b..90310fb0e1 100644 --- a/src/Browser/Avalonia.Browser/BrowserTopLevelImpl.cs +++ b/src/Browser/Avalonia.Browser/BrowserTopLevelImpl.cs @@ -19,7 +19,8 @@ using Avalonia.Rendering.Composition; namespace Avalonia.Browser { - internal class BrowserTopLevelImpl : ITopLevelImplWithTextInputMethod, ITopLevelImplWithNativeControlHost, ITopLevelImplWithStorageProvider + internal class BrowserTopLevelImpl : ITopLevelImplWithTextInputMethod, ITopLevelImplWithNativeControlHost, ITopLevelImplWithStorageProvider, + ITopLevelWithSystemNavigationManager { private Size _clientSize; private IInputRoot? _inputRoot; @@ -234,5 +235,7 @@ namespace Avalonia.Browser public INativeControlHostImpl? NativeControlHost { get; } public IStorageProvider StorageProvider { get; } = new BrowserStorageProvider(); + + public ISystemNavigationManager SystemNavigationManager { get; } = new BrowserSystemNavigationManager(); } } diff --git a/src/Browser/Avalonia.Browser/Interop/NavigationHelper.cs b/src/Browser/Avalonia.Browser/Interop/NavigationHelper.cs new file mode 100644 index 0000000000..d49b83b5a0 --- /dev/null +++ b/src/Browser/Avalonia.Browser/Interop/NavigationHelper.cs @@ -0,0 +1,10 @@ +using System; +using System.Runtime.InteropServices.JavaScript; + +namespace Avalonia.Browser.Interop; + +internal static partial class NavigationHelper +{ + [JSImport("NavigationHelper.addBackHandler", AvaloniaModule.MainModuleName)] + public static partial void AddBackHandler([JSMarshalAs>] Func backHandlerCallback); +} diff --git a/src/Browser/Avalonia.Browser/webapp/modules/avalonia.ts b/src/Browser/Avalonia.Browser/webapp/modules/avalonia.ts index 0642bd475d..ab0c85eaa2 100644 --- a/src/Browser/Avalonia.Browser/webapp/modules/avalonia.ts +++ b/src/Browser/Avalonia.Browser/webapp/modules/avalonia.ts @@ -5,6 +5,7 @@ import { AvaloniaDOM } from "./avalonia/dom"; import { Caniuse } from "./avalonia/caniuse"; import { StreamHelper } from "./avalonia/stream"; import { NativeControlHost } from "./avalonia/nativeControlHost"; +import { NavigationHelper } from "./avalonia/navigationHelper"; async function registerAvaloniaModule(api: RuntimeAPI): Promise { api.setModuleImports("avalonia", { @@ -15,7 +16,8 @@ async function registerAvaloniaModule(api: RuntimeAPI): Promise { DpiWatcher, AvaloniaDOM, StreamHelper, - NativeControlHost + NativeControlHost, + NavigationHelper }); } export { @@ -27,6 +29,7 @@ export { AvaloniaDOM, StreamHelper, NativeControlHost, + NavigationHelper, registerAvaloniaModule }; diff --git a/src/Browser/Avalonia.Browser/webapp/modules/avalonia/navigationHelper.ts b/src/Browser/Avalonia.Browser/webapp/modules/avalonia/navigationHelper.ts new file mode 100644 index 0000000000..ee594fe85c --- /dev/null +++ b/src/Browser/Avalonia.Browser/webapp/modules/avalonia/navigationHelper.ts @@ -0,0 +1,14 @@ +export class NavigationHelper { + public static addBackHandler(backHandlerCallback: () => Boolean) { + history.pushState(null, "", window.location.href); + window.onpopstate = () => { + const handled = backHandlerCallback(); + + if (!handled) { + history.back(); + } else { + history.forward(); + } + }; + } +}