diff --git a/NOTICE.md b/NOTICE.md
index e97fc654c9..bd26b65d70 100644
--- a/NOTICE.md
+++ b/NOTICE.md
@@ -303,3 +303,34 @@ https://github.com/chromium/chromium
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+# Flutter
+
+https://github.com/flutter/flutter
+
+//Copyright 2014 The Flutter Authors. All rights reserved.
+
+//Redistribution and use in source and binary forms, with or without modification,
+//are permitted provided that the following conditions are met:
+
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following
+// disclaimer in the documentation and/or other materials provided
+// with the distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived
+// from this software without specific prior written permission.
+
+//THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+//ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+//WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+//DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+//ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+//(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+//LOSS OF USE, DATA, OR PROFITS;
+//OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+//ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+//(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+//SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidMotionEventsHelper.cs b/src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidMotionEventsHelper.cs
index 6d0e6be0ad..3d0f3b2652 100644
--- a/src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidMotionEventsHelper.cs
+++ b/src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidMotionEventsHelper.cs
@@ -38,7 +38,7 @@ namespace Avalonia.Android.Platform.Specific.Helpers
return null;
}
- var eventTime = (ulong)DateTime.Now.Millisecond;
+ var eventTime = (ulong)e.EventTime;
var inputRoot = _view.InputRoot;
var actionMasked = e.ActionMasked;
var modifiers = GetModifiers(e.MetaState, e.ButtonState);
diff --git a/src/Avalonia.Base/Input/GestureRecognizers/ScrollGestureRecognizer.cs b/src/Avalonia.Base/Input/GestureRecognizers/ScrollGestureRecognizer.cs
index 64fe275547..cec98ec66b 100644
--- a/src/Avalonia.Base/Input/GestureRecognizers/ScrollGestureRecognizer.cs
+++ b/src/Avalonia.Base/Input/GestureRecognizers/ScrollGestureRecognizer.cs
@@ -17,7 +17,9 @@ namespace Avalonia.Input.GestureRecognizers
private bool _canVerticallyScroll;
private int _gestureId;
private int _scrollStartDistance = 30;
-
+ private Point _pointerPressedPoint;
+ private VelocityTracker? _velocityTracker;
+
// Movement per second
private Vector _inertia;
private ulong? _lastMoveTimestamp;
@@ -91,7 +93,7 @@ namespace Avalonia.Input.GestureRecognizers
EndGesture();
_tracking = e.Pointer;
_gestureId = ScrollGestureEventArgs.GetNextFreeId();
- _trackedRootPoint = e.GetPosition((Visual?)_target);
+ _trackedRootPoint = _pointerPressedPoint = e.GetPosition((Visual?)_target);
}
}
@@ -111,6 +113,13 @@ namespace Avalonia.Input.GestureRecognizers
_scrolling = true;
if (_scrolling)
{
+ _velocityTracker = new VelocityTracker();
+
+ // Correct _trackedRootPoint with ScrollStartDistance, so scrolling does not start with a skip of ScrollStartDistance
+ _trackedRootPoint = new Point(
+ _trackedRootPoint.X - (_trackedRootPoint.X >= rootPoint.X ? _scrollStartDistance : -_scrollStartDistance),
+ _trackedRootPoint.Y - (_trackedRootPoint.Y >= rootPoint.Y ? _scrollStartDistance : -_scrollStartDistance));
+
_actions!.Capture(e.Pointer, this);
}
}
@@ -118,14 +127,11 @@ namespace Avalonia.Input.GestureRecognizers
if (_scrolling)
{
var vector = _trackedRootPoint - rootPoint;
- var elapsed = _lastMoveTimestamp.HasValue && _lastMoveTimestamp < e.Timestamp ?
- TimeSpan.FromMilliseconds(e.Timestamp - _lastMoveTimestamp.Value) :
- TimeSpan.Zero;
-
+
+ _velocityTracker?.AddPosition(TimeSpan.FromMilliseconds(e.Timestamp), _pointerPressedPoint - rootPoint);
+
_lastMoveTimestamp = e.Timestamp;
_trackedRootPoint = rootPoint;
- if (elapsed.TotalSeconds > 0)
- _inertia = vector / elapsed.TotalSeconds;
_target!.RaiseEvent(new ScrollGestureEventArgs(_gestureId, vector));
e.Handled = true;
}
@@ -150,12 +156,14 @@ namespace Avalonia.Input.GestureRecognizers
}
}
-
-
+
+
public void PointerReleased(PointerReleasedEventArgs e)
{
if (e.Pointer == _tracking && _scrolling)
{
+ _inertia = _velocityTracker?.GetFlingVelocity().PixelsPerSecond ?? Vector.Zero;
+
e.Handled = true;
if (_inertia == default
|| e.Timestamp == 0
@@ -183,9 +191,18 @@ namespace Avalonia.Input.GestureRecognizers
var distance = speed * elapsedSinceLastTick.TotalSeconds;
_target!.RaiseEvent(new ScrollGestureEventArgs(_gestureId, distance));
-
-
- if (Math.Abs(speed.X) < InertialScrollSpeedEnd || Math.Abs(speed.Y) <= InertialScrollSpeedEnd)
+ // EndGesture using InertialScrollSpeedEnd only in the direction of scrolling
+ if (CanVerticallyScroll && CanHorizontallyScroll && Math.Abs(speed.X) < InertialScrollSpeedEnd && Math.Abs(speed.Y) <= InertialScrollSpeedEnd)
+ {
+ EndGesture();
+ return false;
+ }
+ else if (CanVerticallyScroll && Math.Abs(speed.Y) <= InertialScrollSpeedEnd)
+ {
+ EndGesture();
+ return false;
+ }
+ else if (CanHorizontallyScroll && Math.Abs(speed.X) < InertialScrollSpeedEnd)
{
EndGesture();
return false;
diff --git a/src/Avalonia.Base/Input/GestureRecognizers/VelocityTracker.cs b/src/Avalonia.Base/Input/GestureRecognizers/VelocityTracker.cs
new file mode 100644
index 0000000000..ce41aa6308
--- /dev/null
+++ b/src/Avalonia.Base/Input/GestureRecognizers/VelocityTracker.cs
@@ -0,0 +1,375 @@
+// Code in this file is derived from
+// https://github.com/flutter/flutter/blob/master/packages/flutter/lib/src/gestures/velocity_tracker.dart
+
+using System;
+using System.Diagnostics;
+using Avalonia.Utilities;
+
+namespace Avalonia.Input.GestureRecognizers
+{
+ // Possible enhancement: add Flutter's 'IOSScrollViewFlingVelocityTracker' and 'MacOSScrollViewFlingVelocityTracker'?
+
+ internal readonly record struct Velocity(Vector PixelsPerSecond)
+ {
+ public Velocity ClampMagnitude(double minValue, double maxValue)
+ {
+ Debug.Assert(minValue >= 0.0);
+ Debug.Assert(maxValue >= 0.0 && maxValue >= minValue);
+ double valueSquared = PixelsPerSecond.SquaredLength;
+ if (valueSquared > maxValue * maxValue)
+ {
+ double length = PixelsPerSecond.Length;
+ return new Velocity(length != 0.0 ? (PixelsPerSecond / length) * maxValue : Vector.Zero);
+ // preventing double.NaN in Vector PixelsPerSecond is important -- if a NaN eventually gets into a
+ // ScrollGestureEventArgs it results in runtime errors.
+ }
+ if (valueSquared < minValue * minValue)
+ {
+ double length = PixelsPerSecond.Length;
+ return new Velocity(length != 0.0 ? (PixelsPerSecond / length) * minValue : Vector.Zero);
+ }
+ return this;
+ }
+ }
+
+ /// A two dimensional velocity estimate.
+ ///
+ /// VelocityEstimates are computed by [VelocityTracker.getVelocityEstimate]. An
+ /// estimate's [confidence] measures how well the velocity tracker's position
+ /// data fit a straight line, [duration] is the time that elapsed between the
+ /// first and last position sample used to compute the velocity, and [offset]
+ /// is similarly the difference between the first and last positions.
+ ///
+ /// See also:
+ ///
+ /// * [VelocityTracker], which computes [VelocityEstimate]s.
+ /// * [Velocity], which encapsulates (just) a velocity vector and provides some
+ /// useful velocity operations.
+ internal record VelocityEstimate(Vector PixelsPerSecond, double Confidence, TimeSpan Duration, Vector Offset);
+
+ internal record struct PointAtTime(bool Valid, Vector Point, TimeSpan Time);
+
+ /// Computes a pointer's velocity based on data from [PointerMoveEvent]s.
+ ///
+ /// The input data is provided by calling [addPosition]. Adding data is cheap.
+ ///
+ /// To obtain a velocity, call [getVelocity] or [getVelocityEstimate]. This will
+ /// compute the velocity based on the data added so far. Only call these when
+ /// you need to use the velocity, as they are comparatively expensive.
+ ///
+ /// The quality of the velocity estimation will be better if more data points
+ /// have been received.
+ internal class VelocityTracker
+ {
+ private const int AssumePointerMoveStoppedMilliseconds = 40;
+ private const int HistorySize = 20;
+ private const int HorizonMilliseconds = 100;
+ private const int MinSampleSize = 3;
+ private const double MinFlingVelocity = 50.0; // Logical pixels / second
+ private const double MaxFlingVelocity = 8000.0;
+
+ private readonly PointAtTime[] _samples = new PointAtTime[HistorySize];
+ private int _index = 0;
+
+ ///
+ /// Adds a position as the given time to the tracker.
+ ///
+ ///
+ ///
+ public void AddPosition(TimeSpan time, Vector position)
+ {
+ _index++;
+ if (_index == HistorySize)
+ {
+ _index = 0;
+ }
+ _samples[_index] = new PointAtTime(true, position, time);
+ }
+
+ /// Returns an estimate of the velocity of the object being tracked by the
+ /// tracker given the current information available to the tracker.
+ ///
+ /// Information is added using [addPosition].
+ ///
+ /// Returns null if there is no data on which to base an estimate.
+ protected virtual VelocityEstimate? GetVelocityEstimate()
+ {
+ Span x = stackalloc double[HistorySize];
+ Span y = stackalloc double[HistorySize];
+ Span w = stackalloc double[HistorySize];
+ Span time = stackalloc double[HistorySize];
+ int sampleCount = 0;
+ int index = _index;
+
+ var newestSample = _samples[index];
+ if (!newestSample.Valid)
+ {
+ return null;
+ }
+
+ var previousSample = newestSample;
+ var oldestSample = newestSample;
+
+ // Starting with the most recent PointAtTime sample, iterate backwards while
+ // the samples represent continuous motion.
+ do
+ {
+ var sample = _samples[index];
+ if (!sample.Valid)
+ {
+ break;
+ }
+
+ double age = (newestSample.Time - sample.Time).TotalMilliseconds;
+ double delta = Math.Abs((sample.Time - previousSample.Time).TotalMilliseconds);
+ previousSample = sample;
+ if (age > HorizonMilliseconds || delta > AssumePointerMoveStoppedMilliseconds)
+ {
+ break;
+ }
+
+ oldestSample = sample;
+ var position = sample.Point;
+ x[sampleCount] = position.X;
+ y[sampleCount] = position.Y;
+ w[sampleCount] = 1.0;
+ time[sampleCount] = -age;
+ index = (index == 0 ? HistorySize : index) - 1;
+
+ sampleCount++;
+ } while (sampleCount < HistorySize);
+
+ if (sampleCount >= MinSampleSize)
+ {
+ var xFit = LeastSquaresSolver.Solve(2, time.Slice(0, sampleCount), x.Slice(0, sampleCount), w.Slice(0, sampleCount));
+ if (xFit != null)
+ {
+ var yFit = LeastSquaresSolver.Solve(2, time.Slice(0, sampleCount), y.Slice(0, sampleCount), w.Slice(0, sampleCount));
+ if (yFit != null)
+ {
+ return new VelocityEstimate( // convert from pixels/ms to pixels/s
+ PixelsPerSecond: new Vector(xFit.Coefficients[1] * 1000, yFit.Coefficients[1] * 1000),
+ Confidence: xFit.Confidence * yFit.Confidence,
+ Duration: newestSample.Time - oldestSample.Time,
+ Offset: newestSample.Point - oldestSample.Point
+ );
+ }
+ }
+ }
+
+ // We're unable to make a velocity estimate but we did have at least one
+ // valid pointer position.
+ return new VelocityEstimate(
+ PixelsPerSecond: Vector.Zero,
+ Confidence: 1.0,
+ Duration: newestSample.Time - oldestSample.Time,
+ Offset: newestSample.Point - oldestSample.Point
+ );
+ }
+
+ ///
+ /// Computes the velocity of the pointer at the time of the last
+ /// provided data point.
+ ///
+ /// This can be expensive. Only call this when you need the velocity.
+ ///
+ /// Returns [Velocity.zero] if there is no data from which to compute an
+ /// estimate or if the estimated velocity is zero.///
+ ///
+ ///
+ internal Velocity GetVelocity()
+ {
+ var estimate = GetVelocityEstimate();
+ if (estimate == null || estimate.PixelsPerSecond.IsDefault)
+ {
+ return new Velocity(Vector.Zero);
+ }
+ return new Velocity(estimate.PixelsPerSecond);
+ }
+
+ internal virtual Velocity GetFlingVelocity()
+ {
+ return GetVelocity().ClampMagnitude(MinFlingVelocity, MaxFlingVelocity);
+ }
+ }
+
+ /// An nth degree polynomial fit to a dataset.
+ internal class PolynomialFit
+ {
+ /// Creates a polynomial fit of the given degree.
+ ///
+ /// There are n + 1 coefficients in a fit of degree n.
+ internal PolynomialFit(int degree)
+ {
+ Coefficients = new double[degree + 1];
+ }
+
+ /// The polynomial coefficients of the fit.
+ public double[] Coefficients { get; }
+
+ /// An indicator of the quality of the fit.
+ ///
+ /// Larger values indicate greater quality.
+ public double Confidence { get; set; }
+ }
+
+ internal class LeastSquaresSolver
+ {
+ private const double PrecisionErrorTolerance = 1e-10;
+
+ ///
+ /// Fits a polynomial of the given degree to the data points.
+ /// When there is not enough data to fit a curve null is returned.
+ ///
+ public static PolynomialFit? Solve(int degree, ReadOnlySpan x, ReadOnlySpan y, ReadOnlySpan w)
+ {
+ if (degree > x.Length)
+ {
+ // Not enough data to fit a curve.
+ return null;
+ }
+
+ PolynomialFit result = new PolynomialFit(degree);
+
+ // Shorthands for the purpose of notation equivalence to original C++ code.
+ int m = x.Length;
+ int n = degree + 1;
+
+ // Expand the X vector to a matrix A, pre-multiplied by the weights.
+ _Matrix a = new _Matrix(m, stackalloc double[n * m]);
+ for (int h = 0; h < m; h += 1)
+ {
+ a[0, h] = w[h];
+ for (int i = 1; i < n; i += 1)
+ {
+ a[i, h] = a[i - 1, h] * x[h];
+ }
+ }
+
+ // Apply the Gram-Schmidt process to A to obtain its QR decomposition.
+
+ // Orthonormal basis, column-major order Vector.
+ _Matrix q = new _Matrix(m, stackalloc double[n * m]);
+ // Upper triangular matrix, row-major order.
+ _Matrix r = new _Matrix(n, stackalloc double[n * n]);
+ for (int j = 0; j < n; j += 1)
+ {
+ for (int h = 0; h < m; h += 1)
+ {
+ q[j, h] = a[j, h];
+ }
+ for (int i = 0; i < j; i += 1)
+ {
+ double dot = Multiply(q.GetRow(j), q.GetRow(i));
+ for (int h = 0; h < m; h += 1)
+ {
+ q[j, h] = q[j, h] - dot * q[i, h];
+ }
+ }
+
+ double norm = Norm(q.GetRow(j));
+ if (norm < PrecisionErrorTolerance)
+ {
+ // Vectors are linearly dependent or zero so no solution.
+ return null;
+ }
+
+ double inverseNorm = 1.0 / norm;
+ for (int h = 0; h < m; h += 1)
+ {
+ q[j, h] = q[j, h] * inverseNorm;
+ }
+ for (int i = 0; i < n; i += 1)
+ {
+ r[j, i] = i < j ? 0.0 : Multiply(q.GetRow(j), a.GetRow(i));
+ }
+ }
+
+ // Solve R B = Qt W Y to find B. This is easy because R is upper triangular.
+ // We just work from bottom-right to top-left calculating B's coefficients.
+ // "m" isn't expected to be bigger than HistorySize=20, so allocation on stack is safe.
+ Span wy = stackalloc double[m];
+ for (int h = 0; h < m; h += 1)
+ {
+ wy[h] = y[h] * w[h];
+ }
+ for (int i = n - 1; i >= 0; i -= 1)
+ {
+ result.Coefficients[i] = Multiply(q.GetRow(i), wy);
+ for (int j = n - 1; j > i; j -= 1)
+ {
+ result.Coefficients[i] -= r[i, j] * result.Coefficients[j];
+ }
+ result.Coefficients[i] /= r[i, i];
+ }
+
+ // Calculate the coefficient of determination (confidence) as:
+ // 1 - (sumSquaredError / sumSquaredTotal)
+ // ...where sumSquaredError is the residual sum of squares (variance of the
+ // error), and sumSquaredTotal is the total sum of squares (variance of the
+ // data) where each has been weighted.
+ double yMean = 0.0;
+ for (int h = 0; h < m; h += 1)
+ {
+ yMean += y[h];
+ }
+ yMean /= m;
+
+ double sumSquaredError = 0.0;
+ double sumSquaredTotal = 0.0;
+ for (int h = 0; h < m; h += 1)
+ {
+ double term = 1.0;
+ double err = y[h] - result.Coefficients[0];
+ for (int i = 1; i < n; i += 1)
+ {
+ term *= x[h];
+ err -= term * result.Coefficients[i];
+ }
+ sumSquaredError += w[h] * w[h] * err * err;
+ double v = y[h] - yMean;
+ sumSquaredTotal += w[h] * w[h] * v * v;
+ }
+
+ result.Confidence = sumSquaredTotal <= PrecisionErrorTolerance ? 1.0 :
+ 1.0 - (sumSquaredError / sumSquaredTotal);
+
+ return result;
+ }
+
+ private static double Multiply(Span v1, Span v2)
+ {
+ double result = 0.0;
+ for (int i = 0; i < v1.Length; i += 1)
+ {
+ result += v1[i] * v2[i];
+ }
+ return result;
+ }
+
+ private static double Norm(Span v)
+ {
+ return Math.Sqrt(Multiply(v, v));
+ }
+
+ private readonly ref struct _Matrix
+ {
+ private readonly int _columns;
+ private readonly Span _elements;
+
+ internal _Matrix(int cols, Span elements)
+ {
+ _columns = cols;
+ _elements = elements;
+ }
+
+ public double this[int row, int col]
+ {
+ get => _elements[row * _columns + col];
+ set => _elements[row * _columns + col] = value;
+ }
+
+ public Span GetRow(int row) => _elements.Slice(row * _columns, _columns);
+ }
+ }
+}
diff --git a/src/Avalonia.Base/Input/Gestures.cs b/src/Avalonia.Base/Input/Gestures.cs
index b4d5feaf3b..54e61d89b2 100644
--- a/src/Avalonia.Base/Input/Gestures.cs
+++ b/src/Avalonia.Base/Input/Gestures.cs
@@ -1,6 +1,8 @@
using System;
+using System.Threading;
using Avalonia.Interactivity;
using Avalonia.Platform;
+using Avalonia.Threading;
using Avalonia.VisualTree;
namespace Avalonia.Input
@@ -8,6 +10,21 @@ namespace Avalonia.Input
public static class Gestures
{
private static bool s_isDoubleTapped = false;
+ private static bool s_isHolding;
+ private static CancellationTokenSource? s_holdCancellationToken;
+
+ ///
+ /// Defines the IsHoldingEnabled attached property.
+ ///
+ public static readonly AttachedProperty IsHoldingEnabledProperty =
+ AvaloniaProperty.RegisterAttached("IsHoldingEnabled", typeof(Gestures), true);
+
+ ///
+ /// Defines the IsHoldWithMouseEnabled attached property.
+ ///
+ public static readonly AttachedProperty IsHoldWithMouseEnabledProperty =
+ AvaloniaProperty.RegisterAttached("IsHoldWithMouseEnabled", typeof(Gestures), false);
+
public static readonly RoutedEvent TappedEvent = RoutedEvent.Register(
"Tapped",
RoutingStrategies.Bubble,
@@ -45,6 +62,7 @@ namespace Avalonia.Input
private static readonly WeakReference s_lastPress = new WeakReference(null);
private static Point s_lastPressPoint;
+ private static IPointer? s_lastPointer;
public static readonly RoutedEvent PinchEvent =
RoutedEvent.Register(
@@ -58,14 +76,40 @@ namespace Avalonia.Input
RoutedEvent.Register(
"PullGesture", RoutingStrategies.Bubble, typeof(Gestures));
+ ///
+ /// Occurs when a user performs a press and hold gesture (with a single touch, mouse, or pen/stylus contact).
+ ///
+ public static readonly RoutedEvent HoldingEvent =
+ RoutedEvent.Register(
+ "Holding", RoutingStrategies.Bubble, typeof(Gestures));
+
public static readonly RoutedEvent PullGestureEndedEvent =
RoutedEvent.Register(
"PullGestureEnded", RoutingStrategies.Bubble, typeof(Gestures));
+ public static bool GetIsHoldingEnabled(StyledElement element)
+ {
+ return element.GetValue(IsHoldingEnabledProperty);
+ }
+ public static void SetIsHoldingEnabled(StyledElement element, bool value)
+ {
+ element.SetValue(IsHoldingEnabledProperty, value);
+ }
+
+ public static bool GetIsHoldWithMouseEnabled(StyledElement element)
+ {
+ return element.GetValue(IsHoldWithMouseEnabledProperty);
+ }
+ public static void SetIsHoldWithMouseEnabled(StyledElement element, bool value)
+ {
+ element.SetValue(IsHoldWithMouseEnabledProperty, value);
+ }
+
static Gestures()
{
InputElement.PointerPressedEvent.RouteFinished.Subscribe(PointerPressed);
InputElement.PointerReleasedEvent.RouteFinished.Subscribe(PointerReleased);
+ InputElement.PointerMovedEvent.RouteFinished.Subscribe(PointerMoved);
}
public static void AddTappedHandler(Interactive element, EventHandler handler)
@@ -110,11 +154,42 @@ namespace Avalonia.Input
var e = (PointerPressedEventArgs)ev;
var visual = (Visual)ev.Source;
+ if(s_lastPointer != null)
+ {
+ if(s_isHolding && ev.Source is Interactive i)
+ {
+ i.RaiseEvent(new HoldingRoutedEventArgs(HoldingState.Cancelled, s_lastPressPoint, s_lastPointer.Type));
+ }
+ s_holdCancellationToken?.Cancel();
+ s_holdCancellationToken?.Dispose();
+ s_holdCancellationToken = null;
+
+ s_lastPointer = null;
+ }
+
+ s_isHolding = false;
+
if (e.ClickCount % 2 == 1)
{
s_isDoubleTapped = false;
s_lastPress.SetTarget(ev.Source);
+ s_lastPointer = e.Pointer;
s_lastPressPoint = e.GetPosition((Visual)ev.Source);
+ s_holdCancellationToken = new CancellationTokenSource();
+ var token = s_holdCancellationToken.Token;
+ var settings = AvaloniaLocator.Current.GetService();
+
+ if (settings != null)
+ {
+ DispatcherTimer.RunOnce(() =>
+ {
+ if (!token.IsCancellationRequested && e.Source is InputElement i && GetIsHoldingEnabled(i) && (e.Pointer.Type != PointerType.Mouse || GetIsHoldWithMouseEnabled(i)))
+ {
+ s_isHolding = true;
+ i.RaiseEvent(new HoldingRoutedEventArgs(HoldingState.Started, s_lastPressPoint, s_lastPointer.Type));
+ }
+ }, settings.HoldWaitDuration);
+ }
}
else if (e.ClickCount % 2 == 0 && e.GetCurrentPoint(visual).Properties.IsLeftButtonPressed)
{
@@ -148,7 +223,12 @@ namespace Avalonia.Input
if (tapRect.ContainsExclusive(point.Position))
{
- if (e.InitialPressMouseButton == MouseButton.Right)
+ if(s_isHolding)
+ {
+ s_isHolding = false;
+ i.RaiseEvent(new HoldingRoutedEventArgs(HoldingState.Completed, s_lastPressPoint, s_lastPointer!.Type));
+ }
+ else if (e.InitialPressMouseButton == MouseButton.Right)
{
i.RaiseEvent(new TappedEventArgs(RightTappedEvent, e));
}
@@ -160,6 +240,45 @@ namespace Avalonia.Input
}
}
}
+
+ s_holdCancellationToken?.Cancel();
+ s_holdCancellationToken?.Dispose();
+ s_holdCancellationToken = null;
+ s_lastPointer = null;
+ }
+ }
+
+ private static void PointerMoved(RoutedEventArgs ev)
+ {
+ if (ev.Route == RoutingStrategies.Bubble)
+ {
+ var e = (PointerEventArgs)ev;
+ if (s_lastPress.TryGetTarget(out var target))
+ {
+ if (e.Pointer == s_lastPointer)
+ {
+ var point = e.GetCurrentPoint((Visual)target);
+ var settings = AvaloniaLocator.Current.GetService();
+ var tapSize = settings?.GetTapSize(point.Pointer.Type) ?? new Size(4, 4);
+ var tapRect = new Rect(s_lastPressPoint, new Size())
+ .Inflate(new Thickness(tapSize.Width, tapSize.Height));
+
+ if (tapRect.ContainsExclusive(point.Position))
+ {
+ return;
+ }
+
+ if (s_isHolding && ev.Source is Interactive i)
+ {
+ i.RaiseEvent(new HoldingRoutedEventArgs(HoldingState.Cancelled, s_lastPressPoint, s_lastPointer!.Type));
+ }
+ }
+ }
+
+ s_holdCancellationToken?.Cancel();
+ s_holdCancellationToken?.Dispose();
+ s_holdCancellationToken = null;
+ s_isHolding = false;
}
}
}
diff --git a/src/Avalonia.Base/Input/HoldingRoutedEventArgs.cs b/src/Avalonia.Base/Input/HoldingRoutedEventArgs.cs
new file mode 100644
index 0000000000..b9a877b2ed
--- /dev/null
+++ b/src/Avalonia.Base/Input/HoldingRoutedEventArgs.cs
@@ -0,0 +1,51 @@
+using System;
+using Avalonia.Interactivity;
+
+namespace Avalonia.Input
+{
+ public class HoldingRoutedEventArgs : RoutedEventArgs
+ {
+ ///
+ /// Gets the state of the event.
+ ///
+ public HoldingState HoldingState { get; }
+
+ ///
+ /// Gets the location of the touch, mouse, or pen/stylus contact.
+ ///
+ public Point Position { get; }
+
+ ///
+ /// Gets the pointer type of the input source.
+ ///
+ public PointerType PointerType { get; }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public HoldingRoutedEventArgs(HoldingState holdingState, Point position, PointerType pointerType) : base(Gestures.HoldingEvent)
+ {
+ HoldingState = holdingState;
+ Position = position;
+ PointerType = pointerType;
+ }
+ }
+
+ public enum HoldingState
+ {
+ ///
+ /// A single contact has been detected and a time threshold is crossed without the contact being lifted, another contact detected, or another gesture started.
+ ///
+ Started,
+
+ ///
+ /// The single contact is lifted.
+ ///
+ Completed,
+
+ ///
+ /// An additional contact is detected or a subsequent gesture (such as a slide) is detected.
+ ///
+ Cancelled,
+ }
+}
diff --git a/src/Avalonia.Base/Input/InputElement.cs b/src/Avalonia.Base/Input/InputElement.cs
index fa755277cc..f233fdce51 100644
--- a/src/Avalonia.Base/Input/InputElement.cs
+++ b/src/Avalonia.Base/Input/InputElement.cs
@@ -188,11 +188,16 @@ namespace Avalonia.Input
///
public static readonly RoutedEvent TappedEvent = Gestures.TappedEvent;
+ ///
+ /// Defines the event.
+ ///
+ public static readonly RoutedEvent HoldingEvent = Gestures.HoldingEvent;
+
///
/// Defines the event.
///
public static readonly RoutedEvent DoubleTappedEvent = Gestures.DoubleTappedEvent;
-
+
private bool _isEffectivelyEnabled = true;
private bool _isFocused;
private bool _isKeyboardFocusWithin;
@@ -352,6 +357,15 @@ namespace Avalonia.Input
add { AddHandler(TappedEvent, value); }
remove { RemoveHandler(TappedEvent, value); }
}
+
+ ///
+ /// Occurs when a hold gesture occurs on the control.
+ ///
+ public event EventHandler? Holding
+ {
+ add { AddHandler(HoldingEvent, value); }
+ remove { RemoveHandler(HoldingEvent, value); }
+ }
///
/// Occurs when a double-tap gesture occurs on the control.
diff --git a/src/Avalonia.Base/Platform/DefaultPlatformSettings.cs b/src/Avalonia.Base/Platform/DefaultPlatformSettings.cs
index dd3e1a4cb1..d0b27b057f 100644
--- a/src/Avalonia.Base/Platform/DefaultPlatformSettings.cs
+++ b/src/Avalonia.Base/Platform/DefaultPlatformSettings.cs
@@ -26,5 +26,7 @@ namespace Avalonia.Platform
};
}
public TimeSpan GetDoubleTapTime(PointerType type) => TimeSpan.FromMilliseconds(500);
+
+ public TimeSpan HoldWaitDuration { get; set; } = TimeSpan.FromMilliseconds(300);
}
}
diff --git a/src/Avalonia.Base/Platform/IPlatformSettings.cs b/src/Avalonia.Base/Platform/IPlatformSettings.cs
index e7921883fd..7c4c1420eb 100644
--- a/src/Avalonia.Base/Platform/IPlatformSettings.cs
+++ b/src/Avalonia.Base/Platform/IPlatformSettings.cs
@@ -27,5 +27,7 @@ namespace Avalonia.Platform
/// tap gesture.
///
TimeSpan GetDoubleTapTime(PointerType type);
+
+ TimeSpan HoldWaitDuration { get; set; }
}
}
diff --git a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual.cs b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual.cs
index 0e15cbd54b..0b2df6d2b3 100644
--- a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual.cs
+++ b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual.cs
@@ -42,11 +42,18 @@ namespace Avalonia.Rendering.Composition.Server
Root!.RenderedVisuals++;
+ if (Opacity != 1)
+ canvas.PushOpacity(Opacity);
+ if (AdornedVisual != null)
+ {
+ canvas.PostTransform = Matrix.Identity;
+ canvas.Transform = Matrix.Identity;
+ canvas.PushClip(AdornedVisual._combinedTransformedClipBounds);
+ }
var transform = GlobalTransformMatrix;
canvas.PostTransform = MatrixUtils.ToMatrix(transform);
canvas.Transform = Matrix.Identity;
- if (Opacity != 1)
- canvas.PushOpacity(Opacity);
+
var boundsRect = new Rect(new Size(Size.X, Size.Y));
if (ClipToBounds && !HandlesClipToBounds)
canvas.PushClip(Root!.SnapToDevicePixels(boundsRect));
@@ -67,6 +74,8 @@ namespace Avalonia.Rendering.Composition.Server
canvas.PopGeometryClip();
if (ClipToBounds && !HandlesClipToBounds)
canvas.PopClip();
+ if (AdornedVisual != null)
+ canvas.PopClip();
if(Opacity != 1)
canvas.PopOpacity();
}
@@ -155,8 +164,12 @@ namespace Avalonia.Rendering.Composition.Server
_clipSizeDirty = false;
}
+
+ _combinedTransformedClipBounds =
+ AdornedVisual?._combinedTransformedClipBounds
+ ?? Parent?._combinedTransformedClipBounds
+ ?? new Rect(Root!.Size);
- _combinedTransformedClipBounds = Parent?._combinedTransformedClipBounds ?? new Rect(Root!.Size);
if (_transformedClipBounds != null)
_combinedTransformedClipBounds = _combinedTransformedClipBounds.Intersect(_transformedClipBounds.Value);
diff --git a/src/Avalonia.Base/Rendering/Composition/Transport/BatchStream.cs b/src/Avalonia.Base/Rendering/Composition/Transport/BatchStream.cs
index a3cad3cebd..5a88f0e91d 100644
--- a/src/Avalonia.Base/Rendering/Composition/Transport/BatchStream.cs
+++ b/src/Avalonia.Base/Rendering/Composition/Transport/BatchStream.cs
@@ -2,6 +2,7 @@ using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
using Avalonia.Rendering.Composition.Animations;
using Avalonia.Rendering.Composition.Server;
@@ -27,6 +28,37 @@ public record struct BatchStreamSegment
public int ElementCount { get; set; }
}
+
+// Unsafe.ReadUnaligned/Unsafe.WriteUnaligned are broken on arm,
+// see https://github.com/dotnet/runtime/issues/80068
+static unsafe class UnalignedMemoryHelper
+{
+ public static T ReadUnaligned(byte* src) where T : unmanaged
+ {
+#if NET6_0_OR_GREATER
+ Unsafe.SkipInit(out var rv);
+#else
+ T rv;
+#endif
+ UnalignedMemcpy((byte*)&rv, src, Unsafe.SizeOf());
+ return rv;
+ }
+
+ public static void WriteUnaligned(byte* dst, T value) where T : unmanaged
+ {
+ UnalignedMemcpy(dst, (byte*)&value, Unsafe.SizeOf());
+ }
+
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ static unsafe void UnalignedMemcpy(byte* dst, byte* src, int count)
+ {
+ for (var c = 0; c < count; c++)
+ {
+ dst[c] = src[c];
+ }
+ }
+}
+
internal class BatchStreamWriter : IDisposable
{
private readonly BatchStreamData _output;
@@ -74,7 +106,15 @@ internal class BatchStreamWriter : IDisposable
var size = Unsafe.SizeOf();
if (_currentDataSegment.Data == IntPtr.Zero || _currentDataSegment.ElementCount + size > _memoryPool.BufferSize)
NextDataSegment();
- Unsafe.WriteUnaligned((byte*)_currentDataSegment.Data + _currentDataSegment.ElementCount, item);
+ var ptr = (byte*)_currentDataSegment.Data + _currentDataSegment.ElementCount;
+
+ // Unsafe.ReadUnaligned/Unsafe.WriteUnaligned are broken on arm32,
+ // see https://github.com/dotnet/runtime/issues/80068
+ if (RuntimeInformation.ProcessArchitecture == Architecture.Arm)
+ UnalignedMemoryHelper.WriteUnaligned(ptr, item);
+ else
+ Unsafe.WriteUnaligned(ptr, item);
+
_currentDataSegment.ElementCount += size;
}
@@ -125,7 +165,16 @@ internal class BatchStreamReader : IDisposable
if (_memoryOffset + size > _currentDataSegment.ElementCount)
throw new InvalidOperationException("Attempted to read more memory then left in the current segment");
- var rv = Unsafe.ReadUnaligned((byte*)_currentDataSegment.Data + _memoryOffset);
+ var ptr = (byte*)_currentDataSegment.Data + _memoryOffset;
+ T rv;
+
+ // Unsafe.ReadUnaligned/Unsafe.WriteUnaligned are broken on arm32,
+ // see https://github.com/dotnet/runtime/issues/80068
+ if (RuntimeInformation.ProcessArchitecture == Architecture.Arm)
+ rv = UnalignedMemoryHelper.ReadUnaligned(ptr);
+ else
+ rv = Unsafe.ReadUnaligned(ptr);
+
_memoryOffset += size;
if (_memoryOffset == _currentDataSegment.ElementCount)
{
diff --git a/src/Avalonia.Controls/ContentControl.cs b/src/Avalonia.Controls/ContentControl.cs
index b8a45e102f..d47a7a7809 100644
--- a/src/Avalonia.Controls/ContentControl.cs
+++ b/src/Avalonia.Controls/ContentControl.cs
@@ -116,14 +116,19 @@ namespace Avalonia.Controls
return false;
}
- private void ContentChanged(AvaloniaPropertyChangedEventArgs e)
+ protected virtual void ContentChanged(AvaloniaPropertyChangedEventArgs e)
{
- if (e.OldValue is ILogical oldChild)
+ UpdateLogicalTree(e.OldValue, e.NewValue);
+ }
+
+ protected void UpdateLogicalTree(object? toRemove, object? toAdd)
+ {
+ if (toRemove is ILogical oldChild)
{
LogicalChildren.Remove(oldChild);
}
- if (e.NewValue is ILogical newChild)
+ if (toAdd is ILogical newChild)
{
LogicalChildren.Add(newChild);
}
diff --git a/src/Avalonia.Controls/Control.cs b/src/Avalonia.Controls/Control.cs
index 88c9823952..26db431a0e 100644
--- a/src/Avalonia.Controls/Control.cs
+++ b/src/Avalonia.Controls/Control.cs
@@ -366,16 +366,29 @@ namespace Avalonia.Controls
{
base.OnAttachedToVisualTreeCore(e);
+ AddHandler(Gestures.HoldingEvent, OnHoldEvent);
+
InitializeIfNeeded();
ScheduleOnLoadedCore();
}
+ private void OnHoldEvent(object? sender, HoldingRoutedEventArgs e)
+ {
+ if(e.HoldingState == HoldingState.Started)
+ {
+ // Trigger ContentRequest when hold has started
+ RaiseEvent(new ContextRequestedEventArgs());
+ }
+ }
+
///
protected sealed override void OnDetachedFromVisualTreeCore(VisualTreeAttachmentEventArgs e)
{
base.OnDetachedFromVisualTreeCore(e);
+ RemoveHandler(Gestures.HoldingEvent, OnHoldEvent);
+
OnUnloadedCore();
}
diff --git a/src/Avalonia.Controls/TransitioningContentControl.cs b/src/Avalonia.Controls/TransitioningContentControl.cs
index 70b21b7248..545032befb 100644
--- a/src/Avalonia.Controls/TransitioningContentControl.cs
+++ b/src/Avalonia.Controls/TransitioningContentControl.cs
@@ -71,6 +71,11 @@ public class TransitioningContentControl : ContentControl
}
}
+ protected override void ContentChanged(AvaloniaPropertyChangedEventArgs e)
+ {
+ // We do nothing becuse we should not remove old Content until the animation is over
+ }
+
///
/// Updates the content with transitions.
///
@@ -89,6 +94,8 @@ public class TransitioningContentControl : ContentControl
if (PageTransition != null)
await PageTransition.Start(this, null, true, localToken);
+ UpdateLogicalTree(CurrentContent, content);
+
if (localToken.IsCancellationRequested)
{
return;
diff --git a/src/Avalonia.Themes.Fluent/Controls/CheckBox.xaml b/src/Avalonia.Themes.Fluent/Controls/CheckBox.xaml
index 9abcf5d32b..28f8649e2d 100644
--- a/src/Avalonia.Themes.Fluent/Controls/CheckBox.xaml
+++ b/src/Avalonia.Themes.Fluent/Controls/CheckBox.xaml
@@ -50,7 +50,8 @@
Opacity="0"
Fill="{DynamicResource CheckBoxCheckGlyphForegroundUnchecked}"
Stretch="Uniform"
- VerticalAlignment="Center" />
+ VerticalAlignment="Center"
+ FlowDirection="LeftToRight" />
@@ -146,7 +147,6 @@
-
diff --git a/src/Windows/Avalonia.Win32/Win32Platform.cs b/src/Windows/Avalonia.Win32/Win32Platform.cs
index 93d16b5768..585a9cdf19 100644
--- a/src/Windows/Avalonia.Win32/Win32Platform.cs
+++ b/src/Windows/Avalonia.Win32/Win32Platform.cs
@@ -258,6 +258,8 @@ 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;
diff --git a/tests/Avalonia.Base.UnitTests/Input/GesturesTests.cs b/tests/Avalonia.Base.UnitTests/Input/GesturesTests.cs
index 59085a21ce..84ee35ba61 100644
--- a/tests/Avalonia.Base.UnitTests/Input/GesturesTests.cs
+++ b/tests/Avalonia.Base.UnitTests/Input/GesturesTests.cs
@@ -1,9 +1,13 @@
+using System;
using System.Collections.Generic;
using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.Input.GestureRecognizers;
using Avalonia.Media;
+using Avalonia.Platform;
+using Avalonia.Threading;
using Avalonia.UnitTests;
+using Moq;
using Xunit;
namespace Avalonia.Base.UnitTests.Input
@@ -169,6 +173,242 @@ namespace Avalonia.Base.UnitTests.Input
Assert.False(raised);
}
+ [Fact]
+ public void Hold_Should_Be_Raised_After_Hold_Duration()
+ {
+ using var scope = AvaloniaLocator.EnterScope();
+ var iSettingsMock = new Mock();
+ iSettingsMock.Setup(x => x.HoldWaitDuration).Returns(TimeSpan.FromMilliseconds(300));
+ iSettingsMock.Setup(x => x.GetTapSize(It.IsAny())).Returns(new Size(16, 16));
+ AvaloniaLocator.CurrentMutable.BindToSelf(this)
+ .Bind().ToConstant(iSettingsMock.Object);
+
+ var scheduledTimers = new List<(TimeSpan time, Action action)>();
+ using var app = UnitTestApplication.Start(new TestServices(
+ threadingInterface: CreatePlatformThreadingInterface(t => scheduledTimers.Add(t))));
+
+ Border border = new Border();
+ Gestures.SetIsHoldWithMouseEnabled(border, true);
+ var decorator = new Decorator
+ {
+ Child = border
+ };
+ HoldingState holding = HoldingState.Cancelled;
+
+ decorator.AddHandler(Gestures.HoldingEvent, (s, e) => holding = e.HoldingState);
+
+ _mouse.Down(border);
+ Assert.False(holding != HoldingState.Cancelled);
+
+ // Verify timer duration, but execute it immediately.
+ var timer = Assert.Single(scheduledTimers);
+ Assert.Equal(iSettingsMock.Object.HoldWaitDuration, timer.time);
+ timer.action();
+
+ Assert.True(holding == HoldingState.Started);
+
+ _mouse.Up(border);
+
+ Assert.True(holding == HoldingState.Completed);
+ }
+
+ [Fact]
+ public void Hold_Should_Not_Raised_When_Pointer_Released_Before_Timer()
+ {
+ using var scope = AvaloniaLocator.EnterScope();
+ var iSettingsMock = new Mock();
+ iSettingsMock.Setup(x => x.HoldWaitDuration).Returns(TimeSpan.FromMilliseconds(300));
+ AvaloniaLocator.CurrentMutable.BindToSelf(this)
+ .Bind().ToConstant(iSettingsMock.Object);
+
+ var scheduledTimers = new List<(TimeSpan time, Action action)>();
+ using var app = UnitTestApplication.Start(new TestServices(
+ threadingInterface: CreatePlatformThreadingInterface(t => scheduledTimers.Add(t))));
+
+ Border border = new Border();
+ Gestures.SetIsHoldWithMouseEnabled(border, true);
+ var decorator = new Decorator
+ {
+ Child = border
+ };
+ var raised = false;
+
+ decorator.AddHandler(Gestures.HoldingEvent, (s, e) => raised = e.HoldingState == HoldingState.Started);
+
+ _mouse.Down(border);
+ Assert.False(raised);
+
+ _mouse.Up(border);
+ Assert.False(raised);
+
+ // Verify timer duration, but execute it immediately.
+ var timer = Assert.Single(scheduledTimers);
+ Assert.Equal(iSettingsMock.Object.HoldWaitDuration, timer.time);
+ timer.action();
+
+ Assert.False(raised);
+ }
+
+ [Fact]
+ public void Hold_Should_Not_Raised_When_Pointer_Is_Moved_Before_Timer()
+ {
+ using var scope = AvaloniaLocator.EnterScope();
+ var iSettingsMock = new Mock();
+ iSettingsMock.Setup(x => x.HoldWaitDuration).Returns(TimeSpan.FromMilliseconds(300));
+ AvaloniaLocator.CurrentMutable.BindToSelf(this)
+ .Bind().ToConstant(iSettingsMock.Object);
+
+ var scheduledTimers = new List<(TimeSpan time, Action action)>();
+ using var app = UnitTestApplication.Start(new TestServices(
+ threadingInterface: CreatePlatformThreadingInterface(t => scheduledTimers.Add(t))));
+
+ Border border = new Border();
+ Gestures.SetIsHoldWithMouseEnabled(border, true);
+ var decorator = new Decorator
+ {
+ Child = border
+ };
+ var raised = false;
+
+ decorator.AddHandler(Gestures.HoldingEvent, (s, e) => raised = e.HoldingState == HoldingState.Completed);
+
+ _mouse.Down(border);
+ Assert.False(raised);
+
+ _mouse.Move(border, position: new Point(20, 20));
+ Assert.False(raised);
+
+ // Verify timer duration, but execute it immediately.
+ var timer = Assert.Single(scheduledTimers);
+ Assert.Equal(iSettingsMock.Object.HoldWaitDuration, timer.time);
+ timer.action();
+
+ Assert.False(raised);
+ }
+
+ [Fact]
+ public void Hold_Should_Be_Cancelled_When_Second_Contact_Is_Detected()
+ {
+ using var scope = AvaloniaLocator.EnterScope();
+ var iSettingsMock = new Mock();
+ iSettingsMock.Setup(x => x.HoldWaitDuration).Returns(TimeSpan.FromMilliseconds(300));
+ AvaloniaLocator.CurrentMutable.BindToSelf(this)
+ .Bind().ToConstant(iSettingsMock.Object);
+
+ var scheduledTimers = new List<(TimeSpan time, Action action)>();
+ using var app = UnitTestApplication.Start(new TestServices(
+ threadingInterface: CreatePlatformThreadingInterface(t => scheduledTimers.Add(t))));
+
+ Border border = new Border();
+ Gestures.SetIsHoldWithMouseEnabled(border, true);
+ var decorator = new Decorator
+ {
+ Child = border
+ };
+ var cancelled = false;
+
+ decorator.AddHandler(Gestures.HoldingEvent, (s, e) => cancelled = e.HoldingState == HoldingState.Cancelled);
+
+ _mouse.Down(border);
+ Assert.False(cancelled);
+
+ var timer = Assert.Single(scheduledTimers);
+ Assert.Equal(iSettingsMock.Object.HoldWaitDuration, timer.time);
+ timer.action();
+
+ var secondMouse = new MouseTestHelper();
+
+ secondMouse.Down(border);
+
+ Assert.True(cancelled);
+ }
+
+ [Fact]
+ public void Hold_Should_Be_Cancelled_When_Pointer_Moves_Too_Far()
+ {
+ using var scope = AvaloniaLocator.EnterScope();
+ var iSettingsMock = new Mock();
+ iSettingsMock.Setup(x => x.HoldWaitDuration).Returns(TimeSpan.FromMilliseconds(300));
+ iSettingsMock.Setup(x => x.GetTapSize(It.IsAny())).Returns(new Size(16, 16));
+ AvaloniaLocator.CurrentMutable.BindToSelf(this)
+ .Bind().ToConstant(iSettingsMock.Object);
+
+ var scheduledTimers = new List<(TimeSpan time, Action action)>();
+ using var app = UnitTestApplication.Start(new TestServices(
+ threadingInterface: CreatePlatformThreadingInterface(t => scheduledTimers.Add(t))));
+
+ Border border = new Border();
+ Gestures.SetIsHoldWithMouseEnabled(border, true);
+ var decorator = new Decorator
+ {
+ Child = border
+ };
+ var cancelled = false;
+
+ decorator.AddHandler(Gestures.HoldingEvent, (s, e) => cancelled = e.HoldingState == HoldingState.Cancelled);
+
+ _mouse.Down(border);
+
+ var timer = Assert.Single(scheduledTimers);
+ Assert.Equal(iSettingsMock.Object.HoldWaitDuration, timer.time);
+ timer.action();
+
+ _mouse.Move(border, position: new Point(3, 3));
+
+ Assert.False(cancelled);
+
+ _mouse.Move(border, position: new Point(20, 20));
+
+ Assert.True(cancelled);
+ }
+
+ [Fact]
+ public void Hold_Should_Not_Be_Raised_For_Multiple_Contacts()
+ {
+ using var scope = AvaloniaLocator.EnterScope();
+ var iSettingsMock = new Mock();
+ iSettingsMock.Setup(x => x.HoldWaitDuration).Returns(TimeSpan.FromMilliseconds(300));
+ AvaloniaLocator.CurrentMutable.BindToSelf(this)
+ .Bind().ToConstant(iSettingsMock.Object);
+
+ var scheduledTimers = new List<(TimeSpan time, Action action)>();
+ using var app = UnitTestApplication.Start(new TestServices(
+ threadingInterface: CreatePlatformThreadingInterface(t => scheduledTimers.Add(t))));
+
+ Border border = new Border();
+ Gestures.SetIsHoldWithMouseEnabled(border, true);
+ var decorator = new Decorator
+ {
+ Child = border
+ };
+ var raised = false;
+
+ decorator.AddHandler(Gestures.HoldingEvent, (s, e) => raised = e.HoldingState == HoldingState.Completed);
+
+ var secondMouse = new MouseTestHelper();
+
+ _mouse.Down(border, MouseButton.Left);
+
+ // Verify timer duration, but execute it immediately.
+ var timer = Assert.Single(scheduledTimers);
+ Assert.Equal(iSettingsMock.Object.HoldWaitDuration, timer.time);
+ timer.action();
+
+ secondMouse.Down(border, MouseButton.Left);
+
+ Assert.False(raised);
+ }
+
+ private static IPlatformThreadingInterface CreatePlatformThreadingInterface(Action<(TimeSpan, Action)> callback)
+ {
+ var threadingInterface = new Mock();
+ threadingInterface.SetupGet(p => p.CurrentThreadIsLoopThread).Returns(true);
+ threadingInterface.Setup(p => p
+ .StartTimer(It.IsAny(), It.IsAny(), It.IsAny()))
+ .Callback((_, t, a) => callback((t, a)));
+ return threadingInterface.Object;
+ }
+
private static void AddHandlers(
Decorator decorator,
Border border,
diff --git a/tests/Avalonia.Base.UnitTests/Input/TouchDeviceTests.cs b/tests/Avalonia.Base.UnitTests/Input/TouchDeviceTests.cs
index c0c0182622..36587ea222 100644
--- a/tests/Avalonia.Base.UnitTests/Input/TouchDeviceTests.cs
+++ b/tests/Avalonia.Base.UnitTests/Input/TouchDeviceTests.cs
@@ -207,7 +207,7 @@ namespace Avalonia.Input.UnitTests
private IDisposable UnitTestApp(TimeSpan doubleClickTime = new TimeSpan())
{
var unitTestApp = UnitTestApplication.Start(
- new TestServices(inputManager: new InputManager()));
+ new TestServices(inputManager: new InputManager(), threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true)));
var iSettingsMock = new Mock();
iSettingsMock.Setup(x => x.GetDoubleTapTime(It.IsAny())).Returns(doubleClickTime);
iSettingsMock.Setup(x => x.GetDoubleTapSize(It.IsAny())).Returns(new Size(16, 16));
diff --git a/tests/Avalonia.Controls.UnitTests/TransitioningContentControlTests.cs b/tests/Avalonia.Controls.UnitTests/TransitioningContentControlTests.cs
new file mode 100644
index 0000000000..aaa1de4da4
--- /dev/null
+++ b/tests/Avalonia.Controls.UnitTests/TransitioningContentControlTests.cs
@@ -0,0 +1,66 @@
+using System;
+using Avalonia.LogicalTree;
+using Avalonia.UnitTests;
+using Xunit;
+using System.Threading;
+using System.Threading.Tasks;
+using Avalonia.Animation;
+
+namespace Avalonia.Controls.UnitTests
+{
+ public class TransitioningContentControlTests
+ {
+ [Fact]
+ public void Old_Content_Shuold_Be_Removed__From_Logical_Tree_After_Out_Animation()
+ {
+ using (UnitTestApplication.Start(TestServices.StyledWindow))
+ {
+ var testTransition = new TestTransition();
+
+ var target = new TransitioningContentControl();
+ target.PageTransition = testTransition;
+
+ var root = new TestRoot() { Child = target };
+
+ var oldControl = new Control();
+ var newControl = new Control();
+
+ target.Content = oldControl;
+ Threading.Dispatcher.UIThread.RunJobs();
+
+ Assert.Equal(target, oldControl.GetLogicalParent());
+ Assert.Equal(null, newControl.GetLogicalParent());
+
+ testTransition.BeginTransition += isFrom =>
+ {
+ // Old out
+ if (isFrom)
+ {
+ Assert.Equal(target, oldControl.GetLogicalParent());
+ Assert.Equal(null, newControl.GetLogicalParent());
+ }
+ // New in
+ else
+ {
+ Assert.Equal(null, oldControl.GetLogicalParent());
+ Assert.Equal(target, newControl.GetLogicalParent());
+ }
+ };
+
+ target.Content = newControl;
+ Threading.Dispatcher.UIThread.RunJobs();
+ }
+ }
+ }
+ public class TestTransition : IPageTransition
+ {
+ public event Action BeginTransition;
+
+ public Task Start(Visual from, Visual to, bool forward, CancellationToken cancellationToken)
+ {
+ bool isFrom = from != null && to == null;
+ BeginTransition?.Invoke(isFrom);
+ return Task.CompletedTask;
+ }
+ }
+}
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 eb57cfb8da..0c0857b680 100644
--- a/tests/Avalonia.DesignerSupport.Tests/Remote/HtmlTransport/webapp/package-lock.json
+++ b/tests/Avalonia.DesignerSupport.Tests/Remote/HtmlTransport/webapp/package-lock.json
@@ -389,9 +389,9 @@
"dev": true
},
"ansi-regex": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.1.tgz",
- "integrity": "sha512-+O9Jct8wf++lXxxFc4hc8LsjaSq0HFzzL7cVsw8pRDIPdjKD2mT4ytDZlLuSBZ4cLKZFXIrMGO7DbQCtMJJMKw==",
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
"dev": true
},
"ansi-styles": {
@@ -405,9 +405,9 @@
}
},
"anymatch": {
- "version": "3.1.1",
- "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz",
- "integrity": "sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==",
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
+ "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
"dev": true,
"requires": {
"normalize-path": "^3.0.0",
@@ -444,18 +444,6 @@
"sprintf-js": "~1.0.2"
}
},
- "array.prototype.map": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/array.prototype.map/-/array.prototype.map-1.0.2.tgz",
- "integrity": "sha512-Az3OYxgsa1g7xDYp86l0nnN4bcmuEITGe1rbdEBVkrqkzMgDcbdQ2R7r41pNzti+4NMces3H8gMmuioZUilLgw==",
- "dev": true,
- "requires": {
- "define-properties": "^1.1.3",
- "es-abstract": "^1.17.0-next.1",
- "es-array-method-boxes-properly": "^1.0.0",
- "is-string": "^1.0.4"
- }
- },
"assertion-error": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz",
@@ -469,9 +457,9 @@
"dev": true
},
"binary-extensions": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.1.0.tgz",
- "integrity": "sha512-1Yj8h9Q+QDF5FzhMs/c9+6UntbD5MkRfRwac8DoEm9ZfUBZ7tZ55YcGVAzEe4bXsdQHEk+s9S5wsOKVdZrw0tQ==",
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz",
+ "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==",
"dev": true
},
"brace-expansion": {
@@ -538,9 +526,9 @@
}
},
"chalk": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz",
- "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==",
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
+ "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
"dev": true,
"requires": {
"ansi-styles": "^4.1.0",
@@ -554,19 +542,19 @@
"dev": true
},
"chokidar": {
- "version": "3.4.2",
- "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.4.2.tgz",
- "integrity": "sha512-IZHaDeBeI+sZJRX7lGcXsdzgvZqKv6sECqsbErJA4mHWfpRrD8B97kSFN4cQz6nGBGiuFia1MKR4d6c1o8Cv7A==",
+ "version": "3.5.3",
+ "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz",
+ "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==",
"dev": true,
"requires": {
- "anymatch": "~3.1.1",
+ "anymatch": "~3.1.2",
"braces": "~3.0.2",
- "fsevents": "~2.1.2",
- "glob-parent": "~5.1.0",
+ "fsevents": "~2.3.2",
+ "glob-parent": "~5.1.2",
"is-binary-path": "~2.1.0",
"is-glob": "~4.0.1",
"normalize-path": "~3.0.0",
- "readdirp": "~3.4.0"
+ "readdirp": "~3.6.0"
}
},
"clean-stack": {
@@ -576,42 +564,14 @@
"dev": true
},
"cliui": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz",
- "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==",
+ "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,
"requires": {
- "string-width": "^3.1.0",
- "strip-ansi": "^5.2.0",
- "wrap-ansi": "^5.1.0"
- },
- "dependencies": {
- "ansi-regex": {
- "version": "4.1.1",
- "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz",
- "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==",
- "dev": true
- },
- "string-width": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz",
- "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==",
- "dev": true,
- "requires": {
- "emoji-regex": "^7.0.1",
- "is-fullwidth-code-point": "^2.0.0",
- "strip-ansi": "^5.1.0"
- }
- },
- "strip-ansi": {
- "version": "5.2.0",
- "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz",
- "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==",
- "dev": true,
- "requires": {
- "ansi-regex": "^4.1.0"
- }
- }
+ "string-width": "^4.2.0",
+ "strip-ansi": "^6.0.0",
+ "wrap-ansi": "^7.0.0"
}
},
"color-convert": {
@@ -708,15 +668,6 @@
"strip-bom": "^4.0.0"
}
},
- "define-properties": {
- "version": "1.1.3",
- "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz",
- "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==",
- "dev": true,
- "requires": {
- "object-keys": "^1.0.12"
- }
- },
"diff": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz",
@@ -724,68 +675,23 @@
"dev": true
},
"emoji-regex": {
- "version": "7.0.3",
- "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz",
- "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==",
- "dev": true
- },
- "es-abstract": {
- "version": "1.17.6",
- "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.6.tgz",
- "integrity": "sha512-Fr89bON3WFyUi5EvAeI48QTWX0AyekGgLA8H+c+7fbfCkJwRWRMLd8CQedNEyJuoYYhmtEqY92pgte1FAhBlhw==",
- "dev": true,
- "requires": {
- "es-to-primitive": "^1.2.1",
- "function-bind": "^1.1.1",
- "has": "^1.0.3",
- "has-symbols": "^1.0.1",
- "is-callable": "^1.2.0",
- "is-regex": "^1.1.0",
- "object-inspect": "^1.7.0",
- "object-keys": "^1.1.1",
- "object.assign": "^4.1.0",
- "string.prototype.trimend": "^1.0.1",
- "string.prototype.trimstart": "^1.0.1"
- }
- },
- "es-array-method-boxes-properly": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/es-array-method-boxes-properly/-/es-array-method-boxes-properly-1.0.0.tgz",
- "integrity": "sha512-wd6JXUmyHmt8T5a2xreUwKcGPq6f1f+WwIJkijUqiGcJz1qqnZgP6XIK+QyIWU5lT7imeNxUll48bziG+TSYcA==",
+ "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
},
- "es-get-iterator": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/es-get-iterator/-/es-get-iterator-1.1.0.tgz",
- "integrity": "sha512-UfrmHuWQlNMTs35e1ypnvikg6jCz3SK8v8ImvmDsh36fCVUR1MqoFDiyn0/k52C8NqO3YsO8Oe0azeesNuqSsQ==",
- "dev": true,
- "requires": {
- "es-abstract": "^1.17.4",
- "has-symbols": "^1.0.1",
- "is-arguments": "^1.0.4",
- "is-map": "^2.0.1",
- "is-set": "^2.0.1",
- "is-string": "^1.0.5",
- "isarray": "^2.0.5"
- }
- },
- "es-to-primitive": {
- "version": "1.2.1",
- "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz",
- "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==",
- "dev": true,
- "requires": {
- "is-callable": "^1.1.4",
- "is-date-object": "^1.0.1",
- "is-symbol": "^1.0.2"
- }
- },
"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
},
+ "escalade": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz",
+ "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==",
+ "dev": true
+ },
"escape-string-regexp": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
@@ -829,13 +735,10 @@
}
},
"flat": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/flat/-/flat-4.1.0.tgz",
- "integrity": "sha512-Px/TiLIznH7gEDlPXcUD4KnBusa6kR6ayRUVcnEAbreRIuhkqow/mun59BuRXwoYk7ZQOLW1ZM05ilIvK38hFw==",
- "dev": true,
- "requires": {
- "is-buffer": "~2.0.3"
- }
+ "version": "5.0.2",
+ "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz",
+ "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==",
+ "dev": true
},
"foreground-child": {
"version": "2.0.0",
@@ -860,18 +763,12 @@
"dev": true
},
"fsevents": {
- "version": "2.1.3",
- "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.3.tgz",
- "integrity": "sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==",
+ "version": "2.3.2",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
+ "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
"dev": true,
"optional": true
},
- "function-bind": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
- "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",
- "dev": true
- },
"gensync": {
"version": "1.0.0-beta.1",
"resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.1.tgz",
@@ -911,9 +808,9 @@
}
},
"glob-parent": {
- "version": "5.1.1",
- "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz",
- "integrity": "sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==",
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
+ "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
"dev": true,
"requires": {
"is-glob": "^4.0.1"
@@ -931,33 +828,12 @@
"integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==",
"dev": true
},
- "growl": {
- "version": "1.10.5",
- "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz",
- "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==",
- "dev": true
- },
- "has": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
- "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
- "dev": true,
- "requires": {
- "function-bind": "^1.1.1"
- }
- },
"has-flag": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
"dev": true
},
- "has-symbols": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz",
- "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==",
- "dev": true
- },
"hasha": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/hasha/-/hasha-5.2.0.tgz",
@@ -1008,12 +884,6 @@
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
"dev": true
},
- "is-arguments": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.0.4.tgz",
- "integrity": "sha512-xPh0Rmt8NE65sNzvyUmWgI1tz3mKq74lGA0mL8LYZcoIzKOzDh6HmrYm3d18k60nHerC8A9Km8kYu87zfSFnLA==",
- "dev": true
- },
"is-binary-path": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
@@ -1023,51 +893,27 @@
"binary-extensions": "^2.0.0"
}
},
- "is-buffer": {
- "version": "2.0.4",
- "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.4.tgz",
- "integrity": "sha512-Kq1rokWXOPXWuaMAqZiJW4XxsmD9zGx9q4aePabbn3qCRGedtH7Cm+zV8WETitMfu1wdh+Rvd6w5egwSngUX2A==",
- "dev": true
- },
- "is-callable": {
- "version": "1.2.1",
- "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.1.tgz",
- "integrity": "sha512-wliAfSzx6V+6WfMOmus1xy0XvSgf/dlStkvTfq7F0g4bOIW0PSUbnyse3NhDwdyYS1ozfUtAAySqTws3z9Eqgg==",
- "dev": true
- },
- "is-date-object": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.2.tgz",
- "integrity": "sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g==",
- "dev": true
- },
"is-extglob": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
- "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=",
+ "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
"dev": true
},
"is-fullwidth-code-point": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz",
- "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=",
+ "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
},
"is-glob": {
- "version": "4.0.1",
- "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz",
- "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==",
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
+ "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
"dev": true,
"requires": {
"is-extglob": "^2.1.1"
}
},
- "is-map": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.1.tgz",
- "integrity": "sha512-T/S49scO8plUiAOA2DBTBG3JHpn1yiw0kRp6dgiZ0v2/6twi5eiB0rHtHFH9ZIrvlWc6+4O+m4zg5+Z833aXgw==",
- "dev": true
- },
"is-number": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
@@ -1075,24 +921,9 @@
"dev": true
},
"is-plain-obj": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz",
- "integrity": "sha1-caUMhCnfync8kqOQpKA7OfzVHT4=",
- "dev": true
- },
- "is-regex": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.1.tgz",
- "integrity": "sha512-1+QkEcxiLlB7VEyFtyBg94e08OAsvq7FUBgApTq/w2ymCLyKJgDPsybBENVtA7XCQEgEXxKPonG+mvYRxh/LIg==",
- "dev": true,
- "requires": {
- "has-symbols": "^1.0.1"
- }
- },
- "is-set": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.1.tgz",
- "integrity": "sha512-eJEzOtVyenDs1TMzSQ3kU3K+E0GUS9sno+F0OBT97xsgcJsF9nXMBtkT9/kut5JEpM7oL7X/0qxR17K3mcwIAA==",
+ "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
},
"is-stream": {
@@ -1101,39 +932,24 @@
"integrity": "sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==",
"dev": true
},
- "is-string": {
- "version": "1.0.5",
- "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.5.tgz",
- "integrity": "sha512-buY6VNRjhQMiF1qWDouloZlQbRhDPCebwxSjxMjxgemYT46YMd2NR0/H+fBhEfWX4A/w9TBJ+ol+okqJKFE6vQ==",
- "dev": true
- },
- "is-symbol": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz",
- "integrity": "sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==",
- "dev": true,
- "requires": {
- "has-symbols": "^1.0.1"
- }
- },
"is-typedarray": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz",
"integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=",
"dev": true
},
+ "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
+ },
"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
},
- "isarray": {
- "version": "2.0.5",
- "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz",
- "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==",
- "dev": true
- },
"isexe": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
@@ -1222,22 +1038,6 @@
"istanbul-lib-report": "^3.0.0"
}
},
- "iterate-iterator": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/iterate-iterator/-/iterate-iterator-1.0.1.tgz",
- "integrity": "sha512-3Q6tudGN05kbkDQDI4CqjaBf4qf85w6W6GnuZDtUVYwKgtC1q8yxYX7CZed7N+tLzQqS6roujWvszf13T+n9aw==",
- "dev": true
- },
- "iterate-value": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/iterate-value/-/iterate-value-1.0.2.tgz",
- "integrity": "sha512-A6fMAio4D2ot2r/TYzr4yUWrmwNdsN5xL7+HUiyACE4DXm+q8HtPcnFTp+NnW3k4N05tZ7FVYFFb2CR13NxyHQ==",
- "dev": true,
- "requires": {
- "es-get-iterator": "^1.0.2",
- "iterate-iterator": "^1.0.1"
- }
- },
"js-tokens": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
@@ -1261,13 +1061,10 @@
"dev": true
},
"json5": {
- "version": "2.1.3",
- "resolved": "https://registry.npmjs.org/json5/-/json5-2.1.3.tgz",
- "integrity": "sha512-KXPvOm8K9IJKFM0bmdn8QXh7udDh1g/giieX0NLCaMnb4hEiVFqnop2ImTXCc5e0/oHz3LTqmHGtExn5hfMkOA==",
- "dev": true,
- "requires": {
- "minimist": "^1.2.5"
- }
+ "version": "2.2.2",
+ "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.2.tgz",
+ "integrity": "sha512-46Tk9JiOL2z7ytNQWFLpj99RZkVgeHf87yGQKsIkaPz1qSH9UczKH1rO7K3wgRselo0tYMUNfecYpm/p1vC7tQ==",
+ "dev": true
},
"locate-path": {
"version": "6.0.0",
@@ -1291,12 +1088,13 @@
"dev": true
},
"log-symbols": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.0.0.tgz",
- "integrity": "sha512-FN8JBzLx6CzeMrB0tg6pqlGU1wCrXW+ZXGH481kfsBqer0hToTIiHdjH4Mq8xJUbvATujKCvaREGWpGUionraA==",
+ "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,
"requires": {
- "chalk": "^4.0.0"
+ "chalk": "^4.1.0",
+ "is-unicode-supported": "^0.1.0"
}
},
"loose-envify": {
@@ -1332,43 +1130,133 @@
"brace-expansion": "^1.1.7"
}
},
- "minimist": {
- "version": "1.2.6",
- "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz",
- "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==",
- "dev": true
- },
"mocha": {
- "version": "8.1.3",
- "resolved": "https://registry.npmjs.org/mocha/-/mocha-8.1.3.tgz",
- "integrity": "sha512-ZbaYib4hT4PpF4bdSO2DohooKXIn4lDeiYqB+vTmCdr6l2woW0b6H3pf5x4sM5nwQMru9RvjjHYWVGltR50ZBw==",
+ "version": "10.2.0",
+ "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.2.0.tgz",
+ "integrity": "sha512-IDY7fl/BecMwFHzoqF2sg/SHHANeBoMMXFlS9r0OXKDssYE1M5O43wUY/9BVPeIvfH2zmEbBfseqN9gBQZzXkg==",
"dev": true,
"requires": {
"ansi-colors": "4.1.1",
"browser-stdout": "1.3.1",
- "chokidar": "3.4.2",
- "debug": "4.1.1",
- "diff": "4.0.2",
+ "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.1.6",
- "growl": "1.10.5",
+ "glob": "7.2.0",
"he": "1.2.0",
- "js-yaml": "3.14.0",
- "log-symbols": "4.0.0",
- "minimatch": "3.0.4",
- "ms": "2.1.2",
- "object.assign": "4.1.0",
- "promise.allsettled": "1.0.2",
- "serialize-javascript": "4.0.0",
- "strip-json-comments": "3.0.1",
- "supports-color": "7.1.0",
- "which": "2.0.2",
- "wide-align": "1.1.3",
- "workerpool": "6.0.0",
- "yargs": "13.3.2",
- "yargs-parser": "13.1.2",
- "yargs-unparser": "1.6.1"
+ "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"
+ },
+ "dependencies": {
+ "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
+ },
+ "debug": {
+ "version": "4.3.4",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
+ "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
+ "dev": true,
+ "requires": {
+ "ms": "2.1.2"
+ },
+ "dependencies": {
+ "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
+ }
+ }
+ },
+ "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
+ },
+ "glob": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz",
+ "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==",
+ "dev": true,
+ "requires": {
+ "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"
+ },
+ "dependencies": {
+ "minimatch": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
+ "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+ "dev": true,
+ "requires": {
+ "brace-expansion": "^1.1.7"
+ }
+ }
+ }
+ },
+ "js-yaml": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
+ "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
+ "dev": true,
+ "requires": {
+ "argparse": "^2.0.1"
+ }
+ },
+ "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,
+ "requires": {
+ "brace-expansion": "^2.0.1"
+ },
+ "dependencies": {
+ "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,
+ "requires": {
+ "balanced-match": "^1.0.0"
+ }
+ }
+ }
+ },
+ "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
+ },
+ "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,
+ "requires": {
+ "has-flag": "^4.0.0"
+ }
+ }
}
},
"moq.ts": {
@@ -1386,6 +1274,12 @@
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
"dev": true
},
+ "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
+ },
"node-preload": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/node-preload/-/node-preload-0.2.1.tgz",
@@ -1570,30 +1464,6 @@
"integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=",
"dev": true
},
- "object-inspect": {
- "version": "1.8.0",
- "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.8.0.tgz",
- "integrity": "sha512-jLdtEOB112fORuypAyl/50VRVIBIdVQOSUUGQHzJ4xBSbit81zRarz7GThkEFZy1RceYrWYcPcBFPQwHyAc1gA==",
- "dev": true
- },
- "object-keys": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz",
- "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==",
- "dev": true
- },
- "object.assign": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz",
- "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==",
- "dev": true,
- "requires": {
- "define-properties": "^1.1.2",
- "function-bind": "^1.1.1",
- "has-symbols": "^1.0.0",
- "object-keys": "^1.0.11"
- }
- },
"once": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
@@ -1604,12 +1474,12 @@
}
},
"p-limit": {
- "version": "3.0.2",
- "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.0.2.tgz",
- "integrity": "sha512-iwqZSOoWIW+Ew4kAGUlN16J4M7OB3ysMLSZtnhmqx7njIHFPlxWBX8xo3lVTyFVq6mI/lL9qt2IsN1sHwaxJkg==",
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
+ "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
"dev": true,
"requires": {
- "p-try": "^2.0.0"
+ "yocto-queue": "^0.1.0"
}
},
"p-locate": {
@@ -1679,9 +1549,9 @@
"dev": true
},
"picomatch": {
- "version": "2.2.2",
- "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz",
- "integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==",
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
+ "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
"dev": true
},
"pkg-dir": {
@@ -1741,19 +1611,6 @@
"fromentries": "^1.2.0"
}
},
- "promise.allsettled": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/promise.allsettled/-/promise.allsettled-1.0.2.tgz",
- "integrity": "sha512-UpcYW5S1RaNKT6pd+s9jp9K9rlQge1UXKskec0j6Mmuq7UJCvlS2J2/s/yuPN8ehftf9HXMxWlKiPbGGUzpoRg==",
- "dev": true,
- "requires": {
- "array.prototype.map": "^1.0.1",
- "define-properties": "^1.1.3",
- "es-abstract": "^1.17.0-next.1",
- "function-bind": "^1.1.1",
- "iterate-value": "^1.0.0"
- }
- },
"prop-types": {
"version": "15.7.2",
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz",
@@ -1792,9 +1649,9 @@
"dev": true
},
"readdirp": {
- "version": "3.4.0",
- "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.4.0.tgz",
- "integrity": "sha512-0xe001vZBnJEK+uKcj8qOhyAKPzIT+gStxWr3LCB0DwcXR5NZJ3IaC+yGnHCYzB/S7ov3m3EEbZI2zeNvX+hGQ==",
+ "version": "3.6.0",
+ "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
+ "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
"dev": true,
"requires": {
"picomatch": "^2.2.1"
@@ -1858,9 +1715,9 @@
"dev": true
},
"serialize-javascript": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-4.0.0.tgz",
- "integrity": "sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw==",
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz",
+ "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==",
"dev": true,
"requires": {
"randombytes": "^2.1.0"
@@ -1938,42 +1795,23 @@
"dev": true
},
"string-width": {
- "version": "2.1.1",
- "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz",
- "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==",
- "dev": true,
- "requires": {
- "is-fullwidth-code-point": "^2.0.0",
- "strip-ansi": "^4.0.0"
- }
- },
- "string.prototype.trimend": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.1.tgz",
- "integrity": "sha512-LRPxFUaTtpqYsTeNKaFOw3R4bxIzWOnbQ837QfBylo8jIxtcbK/A/sMV7Q+OAV/vWo+7s25pOE10KYSjaSO06g==",
+ "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,
"requires": {
- "define-properties": "^1.1.3",
- "es-abstract": "^1.17.5"
- }
- },
- "string.prototype.trimstart": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.1.tgz",
- "integrity": "sha512-XxZn+QpvrBI1FOcg6dIpxUPgWCPuNXvMD72aaRaUQv1eD4e/Qy8i/hFTe0BUmD60p/QA6bh1avmuPTfNjqVWRw==",
- "dev": true,
- "requires": {
- "define-properties": "^1.1.3",
- "es-abstract": "^1.17.5"
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.1"
}
},
"strip-ansi": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz",
- "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=",
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
"dev": true,
"requires": {
- "ansi-regex": "^3.0.0"
+ "ansi-regex": "^5.0.1"
}
},
"strip-bom": {
@@ -1983,9 +1821,9 @@
"dev": true
},
"strip-json-comments": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.0.1.tgz",
- "integrity": "sha512-VTyMAUfdm047mwKl+u79WIdrZxtFtn+nBxHeb844XBQ9uMNTuTHdx2hc5RiAJYqwTj3wc/xe5HLSdJSkJ+WfZw==",
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
+ "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
"dev": true
},
"supports-color": {
@@ -2090,82 +1928,21 @@
"integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=",
"dev": true
},
- "wide-align": {
- "version": "1.1.3",
- "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz",
- "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==",
- "dev": true,
- "requires": {
- "string-width": "^1.0.2 || 2"
- }
- },
"workerpool": {
- "version": "6.0.0",
- "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.0.0.tgz",
- "integrity": "sha512-fU2OcNA/GVAJLLyKUoHkAgIhKb0JoCpSjLC/G2vYKxUjVmQwGbRVeoPJ1a8U4pnVofz4AQV5Y/NEw8oKqxEBtA==",
+ "version": "6.2.1",
+ "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.1.tgz",
+ "integrity": "sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw==",
"dev": true
},
"wrap-ansi": {
- "version": "5.1.0",
- "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz",
- "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==",
+ "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,
"requires": {
- "ansi-styles": "^3.2.0",
- "string-width": "^3.0.0",
- "strip-ansi": "^5.0.0"
- },
- "dependencies": {
- "ansi-regex": {
- "version": "4.1.1",
- "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz",
- "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==",
- "dev": true
- },
- "ansi-styles": {
- "version": "3.2.1",
- "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
- "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
- "dev": true,
- "requires": {
- "color-convert": "^1.9.0"
- }
- },
- "color-convert": {
- "version": "1.9.3",
- "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
- "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
- "dev": true,
- "requires": {
- "color-name": "1.1.3"
- }
- },
- "color-name": {
- "version": "1.1.3",
- "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
- "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=",
- "dev": true
- },
- "string-width": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz",
- "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==",
- "dev": true,
- "requires": {
- "emoji-regex": "^7.0.1",
- "is-fullwidth-code-point": "^2.0.0",
- "strip-ansi": "^5.1.0"
- }
- },
- "strip-ansi": {
- "version": "5.2.0",
- "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz",
- "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==",
- "dev": true,
- "requires": {
- "ansi-regex": "^4.1.0"
- }
- }
+ "ansi-styles": "^4.0.0",
+ "string-width": "^4.1.0",
+ "strip-ansi": "^6.0.0"
}
},
"wrappy": {
@@ -2193,214 +1970,57 @@
"dev": true
},
"yargs": {
- "version": "13.3.2",
- "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz",
- "integrity": "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==",
+ "version": "16.2.0",
+ "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz",
+ "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==",
"dev": true,
"requires": {
- "cliui": "^5.0.0",
- "find-up": "^3.0.0",
- "get-caller-file": "^2.0.1",
+ "cliui": "^7.0.2",
+ "escalade": "^3.1.1",
+ "get-caller-file": "^2.0.5",
"require-directory": "^2.1.1",
- "require-main-filename": "^2.0.0",
- "set-blocking": "^2.0.0",
- "string-width": "^3.0.0",
- "which-module": "^2.0.0",
- "y18n": "^4.0.0",
- "yargs-parser": "^13.1.2"
+ "string-width": "^4.2.0",
+ "y18n": "^5.0.5",
+ "yargs-parser": "^20.2.2"
},
"dependencies": {
- "ansi-regex": {
- "version": "4.1.1",
- "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz",
- "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==",
+ "y18n": {
+ "version": "5.0.8",
+ "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
+ "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==",
"dev": true
- },
- "find-up": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz",
- "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==",
- "dev": true,
- "requires": {
- "locate-path": "^3.0.0"
- }
- },
- "locate-path": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz",
- "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==",
- "dev": true,
- "requires": {
- "p-locate": "^3.0.0",
- "path-exists": "^3.0.0"
- }
- },
- "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,
- "requires": {
- "p-try": "^2.0.0"
- }
- },
- "p-locate": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz",
- "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==",
- "dev": true,
- "requires": {
- "p-limit": "^2.0.0"
- }
- },
- "path-exists": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz",
- "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=",
- "dev": true
- },
- "string-width": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz",
- "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==",
- "dev": true,
- "requires": {
- "emoji-regex": "^7.0.1",
- "is-fullwidth-code-point": "^2.0.0",
- "strip-ansi": "^5.1.0"
- }
- },
- "strip-ansi": {
- "version": "5.2.0",
- "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz",
- "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==",
- "dev": true,
- "requires": {
- "ansi-regex": "^4.1.0"
- }
}
}
},
"yargs-parser": {
- "version": "13.1.2",
- "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz",
- "integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==",
- "dev": true,
- "requires": {
- "camelcase": "^5.0.0",
- "decamelize": "^1.2.0"
- }
+ "version": "20.2.4",
+ "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz",
+ "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==",
+ "dev": true
},
"yargs-unparser": {
- "version": "1.6.1",
- "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-1.6.1.tgz",
- "integrity": "sha512-qZV14lK9MWsGCmcr7u5oXGH0dbGqZAIxTDrWXZDo5zUr6b6iUmelNKO6x6R1dQT24AH3LgRxJpr8meWy2unolA==",
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz",
+ "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==",
"dev": true,
"requires": {
- "camelcase": "^5.3.1",
- "decamelize": "^1.2.0",
- "flat": "^4.1.0",
- "is-plain-obj": "^1.1.0",
- "yargs": "^14.2.3"
+ "camelcase": "^6.0.0",
+ "decamelize": "^4.0.0",
+ "flat": "^5.0.2",
+ "is-plain-obj": "^2.1.0"
},
"dependencies": {
- "ansi-regex": {
- "version": "4.1.1",
- "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz",
- "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==",
+ "camelcase": {
+ "version": "6.3.0",
+ "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz",
+ "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==",
"dev": true
},
- "find-up": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz",
- "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==",
- "dev": true,
- "requires": {
- "locate-path": "^3.0.0"
- }
- },
- "locate-path": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz",
- "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==",
- "dev": true,
- "requires": {
- "p-locate": "^3.0.0",
- "path-exists": "^3.0.0"
- }
- },
- "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,
- "requires": {
- "p-try": "^2.0.0"
- }
- },
- "p-locate": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz",
- "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==",
- "dev": true,
- "requires": {
- "p-limit": "^2.0.0"
- }
- },
- "path-exists": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz",
- "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=",
+ "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
- },
- "string-width": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz",
- "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==",
- "dev": true,
- "requires": {
- "emoji-regex": "^7.0.1",
- "is-fullwidth-code-point": "^2.0.0",
- "strip-ansi": "^5.1.0"
- }
- },
- "strip-ansi": {
- "version": "5.2.0",
- "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz",
- "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==",
- "dev": true,
- "requires": {
- "ansi-regex": "^4.1.0"
- }
- },
- "yargs": {
- "version": "14.2.3",
- "resolved": "https://registry.npmjs.org/yargs/-/yargs-14.2.3.tgz",
- "integrity": "sha512-ZbotRWhF+lkjijC/VhmOT9wSgyBQ7+zr13+YLkhfsSiTriYsMzkTUFP18pFhWwBeMa5gUc1MzbhrO6/VB7c9Xg==",
- "dev": true,
- "requires": {
- "cliui": "^5.0.0",
- "decamelize": "^1.2.0",
- "find-up": "^3.0.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": "^3.0.0",
- "which-module": "^2.0.0",
- "y18n": "^4.0.0",
- "yargs-parser": "^15.0.1"
- }
- },
- "yargs-parser": {
- "version": "15.0.1",
- "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-15.0.1.tgz",
- "integrity": "sha512-0OAMV2mAZQrs3FkNpDQcBk1x5HXb8X4twADss4S0Iuk+2dGnLOE/fRHrsYm542GduMveyA77OF4wrNJuanRCWw==",
- "dev": true,
- "requires": {
- "camelcase": "^5.0.0",
- "decamelize": "^1.2.0"
- }
}
}
},
@@ -2409,6 +2029,12 @@
"resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz",
"integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==",
"dev": true
+ },
+ "yocto-queue": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
+ "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
+ "dev": true
}
}
}
diff --git a/tests/Avalonia.DesignerSupport.Tests/Remote/HtmlTransport/webapp/package.json b/tests/Avalonia.DesignerSupport.Tests/Remote/HtmlTransport/webapp/package.json
index dbb12e192a..580c4059ba 100644
--- a/tests/Avalonia.DesignerSupport.Tests/Remote/HtmlTransport/webapp/package.json
+++ b/tests/Avalonia.DesignerSupport.Tests/Remote/HtmlTransport/webapp/package.json
@@ -16,7 +16,7 @@
"@types/mocha": "8.0.3",
"@types/react": "^16.3.14",
"chai": "^4.2.0",
- "mocha": "^8.1.3",
+ "mocha": "^10.2.0",
"moq.ts": "^6.4.0",
"nyc": "^15.1.0",
"react": "^16.3.2",
diff --git a/tests/Avalonia.RenderTests/Controls/AdornerTests.cs b/tests/Avalonia.RenderTests/Controls/AdornerTests.cs
new file mode 100644
index 0000000000..c833017212
--- /dev/null
+++ b/tests/Avalonia.RenderTests/Controls/AdornerTests.cs
@@ -0,0 +1,73 @@
+using System.Threading.Tasks;
+using Avalonia.Controls;
+using Avalonia.Controls.Primitives;
+using Avalonia.Layout;
+using Avalonia.Media;
+using Xunit;
+
+#if AVALONIA_SKIA
+namespace Avalonia.Skia.RenderTests;
+#else
+namespace Avalonia.Direct2D1.RenderTests.Controls;
+#endif
+
+public class AdornerTests : TestBase
+{
+ public AdornerTests()
+ : base(@"Controls\Adorner")
+ {
+ }
+
+ [Fact]
+ public async Task Focus_Adorner_Is_Properly_Clipped()
+ {
+ Border adorned;
+ var tree = new Decorator
+ {
+ Child = new VisualLayerManager
+ {
+ Child = new Border
+ {
+ Background = Brushes.Red,
+ Padding = new Thickness(10, 50, 10,10),
+ Child = new Border()
+ {
+ Background = Brushes.White,
+ ClipToBounds = true,
+ Padding = new Thickness(0, -30, 0, 0),
+ Child = adorned = new Border
+ {
+ Background = Brushes.Green,
+ VerticalAlignment = VerticalAlignment.Top,
+ Height = 100,
+ Width = 50
+ }
+ }
+ }
+ },
+ Width = 200,
+ Height = 200
+ };
+ var adorner = new Border
+ {
+ BorderThickness = new Thickness(2),
+ BorderBrush = Brushes.Black
+ };
+
+ var size = new Size(tree.Width, tree.Height);
+ tree.Measure(size);
+ tree.Arrange(new Rect(size));
+
+
+ adorned.AttachedToVisualTree += delegate
+ {
+ AdornerLayer.SetAdornedElement(adorner, adorned);
+ AdornerLayer.GetAdornerLayer(adorned)!.Children.Add(adorner);
+ };
+ tree.Measure(size);
+ tree.Arrange(new Rect(size));
+
+ await RenderToFile(tree);
+ CompareImages(skipImmediate: true, skipDeferred: true);
+ }
+}
\ No newline at end of file
diff --git a/tests/Avalonia.RenderTests/TestBase.cs b/tests/Avalonia.RenderTests/TestBase.cs
index 8a127897d7..313281d6c6 100644
--- a/tests/Avalonia.RenderTests/TestBase.cs
+++ b/tests/Avalonia.RenderTests/TestBase.cs
@@ -156,7 +156,8 @@ namespace Avalonia.Direct2D1.RenderTests
public ILockedFramebuffer Lock() => _bitmap.Lock();
}
- protected void CompareImages([CallerMemberName] string testName = "")
+ protected void CompareImages([CallerMemberName] string testName = "",
+ bool skipImmediate = false, bool skipDeferred = false, bool skipCompositor = false)
{
var expectedPath = Path.Combine(OutputPath, testName + ".expected.png");
var immediatePath = Path.Combine(OutputPath, testName + ".immediate.out.png");
@@ -172,17 +173,17 @@ namespace Avalonia.Direct2D1.RenderTests
var deferredError = CompareImages(deferred, expected);
var compositedError = CompareImages(composited, expected);
- if (immediateError > 0.022)
+ if (immediateError > 0.022 && !skipImmediate)
{
Assert.True(false, immediatePath + ": Error = " + immediateError);
}
- if (deferredError > 0.022)
+ if (deferredError > 0.022 && !skipDeferred)
{
Assert.True(false, deferredPath + ": Error = " + deferredError);
}
- if (compositedError > 0.022)
+ if (compositedError > 0.022 && !skipCompositor)
{
Assert.True(false, compositedPath + ": Error = " + compositedError);
}
diff --git a/tests/TestFiles/Direct2D1/Controls/Adorner/Focus_Adorner_Is_Properly_Clipped.expected.png b/tests/TestFiles/Direct2D1/Controls/Adorner/Focus_Adorner_Is_Properly_Clipped.expected.png
new file mode 100644
index 0000000000..6a67087d41
Binary files /dev/null and b/tests/TestFiles/Direct2D1/Controls/Adorner/Focus_Adorner_Is_Properly_Clipped.expected.png differ
diff --git a/tests/TestFiles/Skia/Controls/Adorner/Focus_Adorner_Is_Properly_Clipped.expected.png b/tests/TestFiles/Skia/Controls/Adorner/Focus_Adorner_Is_Properly_Clipped.expected.png
new file mode 100644
index 0000000000..6a67087d41
Binary files /dev/null and b/tests/TestFiles/Skia/Controls/Adorner/Focus_Adorner_Is_Properly_Clipped.expected.png differ