So, the solution was to set PointerCaptureLostEvent RoutingStrategies to Direct and capture pointer in (new) OnCaptureLost method.
Now the button releases when I release finger out of the button area. I don't need PointerMovedEvent to handle this situation.
public MomentaryButton()
{
AddHandler(PointerPressedEvent, OnPress, RoutingStrategies.Tunnel);
AddHandler(PointerReleasedEvent, OnRelease, RoutingStrategies.Tunnel);
AddHandler(PointerCaptureLostEvent, OnCaptureLost, RoutingStrategies.Direct);
}
private void OnPress(object? sender, PointerPressedEventArgs e)
{
if (Disabled) return;
PressInternal();
e.Pointer.Capture(this);
}
private void OnRelease(object? sender, PointerEventArgs e)
{
ReleaseInternal();
e.Pointer.Capture(null);
}
private void OnCaptureLost(object? sender, PointerCaptureLostEventArgs e)
{
e.Pointer.Capture(this);
}