Commit Graph

466 Commits

Author SHA1 Message Date
Andrew Scott 66ffeca2de fix(router): Scroller should scroll as soon as change detection completes (#55105)
Using `setTimeout` to delay scrolling can result in scrolling in the
next frame and cause noticeable flicker. This commit scrolls as soon as
the next render happens (or in `setTimeout` if a render does not happen
before then).

fixes #53985

PR Close #55105
2024-04-30 09:19:06 -07:00
Andrew Scott 4a42961393 feat(router): withNavigationErrorHandler can convert errors to redirects (#55370)
This commit adds the ability to return `RedirectCommand` from the error
handler provided by `withNavigationErrorHandler`. This will prevent the
error from being surfaced in the `events` observable of the Router and
instead convert the error to a redirect. This allows developers to
have more control over how the Router handles navigation errors. There
are some cases when the application _does not_ want the URL to be reset
when an error occurs.

resolves #42915

PR Close #55370
2024-04-17 15:53:02 -07:00
Andrew Scott 940a3587b7 refactor(router): prevent navigationErrorHandler from breaking transition subscription (#55152)
Following up on #55068, this change prevents errors in the handler from
breaking navigations.

PR Close #55152
2024-04-01 09:47:54 -07:00
Andrew Scott 3839cfbb18 fix(router): Routed components never inherit RouterOutlet EnvironmentInjector (#54265)
This commit ensures components in the route config predictably always
get their providers from the hierarchy available to routes rather than
sometimes being dependent on where they are inserted.

fixes #53369

BREAKING CHANGE: Providers available to the routed components always
come from the injector heirarchy of the routes and never inherit from
the `RouterOutlet`. This means that providers available only to the
component that defines the `RouterOutlet` will no longer be available to
route components in any circumstances. This was already the case
whenever routes defined providers, either through lazy loading an
`NgModule` or through explicit `providers` on the route config.

PR Close #54265
2024-04-01 09:19:13 -07:00
Andrew Scott 87f3f27f90 feat(router): Allow resolvers to return RedirectCommand (#54556)
Returning a `RedirectCommand` from a resolver can be interpreted as
distinctly different from regular resolved data. When a resolver returns
`RedirectCommand` we can interperet this as an intention to redirect in
the same way as other guards.

resolves #29089

PR Close #54556
2024-03-30 10:52:44 -07:00
Andrew Scott 17369ed1e9 refactor(router): Correct resolve value for error handler case (#55068)
This commit fixes the types on the error handler resolve and reject
functions and also ensures that the resolve value matches the API type
(`boolean`, not "any random value returned from `errorHandler`"). This
could be considered a breaking change but I would argue instead that
relying on the return value of the error handler is API misuse because
the promise returned by the router navigation _does not_ include `any`
in its type.

PR Close #55068
2024-03-29 09:16:46 -07:00
David Calawa 60f1d681e0 fix(router): preserve replaceUrl when returning a urlTree from CanActivate (#54042)
This commit will fix the issue of the setting of NavigationExtras.replaceUrl being lost when
returning a urlTree from a CanActivateFn.

Fixes #53503

BREAKING CHANGE: When a a guard returns a `UrlTree` as a redirect, the
redirecting navigation will now use `replaceUrl` if the initial
navigation was also using the `replaceUrl` option. If this is not
desirable, the redirect can configure new `NavigationBehaviorOptions` by
returning a `RedirectCommand` with the desired options instead of `UrlTree`.

PR Close #54042
2024-03-27 10:57:52 -07:00
Andrew Scott eae75ff3f9 fix(router): RouterLinkActive will always remove active classes when links are not active (#54982)
Previously, `RouterLinkActive` would only add or remove the active classes when
its active state changed. This means that if you accidentally add one of
the active classes to the static class attribute, it won't get removed
until the link becomes active and then deactives (because the class is
added at creation time and never removed until the `RouterLinkActive`
state changes from active to inactive).

fixes #54978

PR Close #54982
2024-03-27 10:16:16 -07:00
Alex Rickabaugh 5901b0ca80 Revert "fix(router): Routed components never inherit RouterOutlet EnvironmentInjector (#54265)" (#54895)
This reverts commit da906fdafc.

This change causes some test failures in google3.

PR Close #54895
2024-03-15 15:08:45 -07:00
Andrew Scott da906fdafc fix(router): Routed components never inherit RouterOutlet EnvironmentInjector (#54265)
This commit ensures components in the route config predictably always
get their providers from the hierarchy available to routes rather than
sometimes being dependent on where they are inserted.

fixes #53369

BREAKING CHANGE: Providers available to the routed components always
come from the injector heirarchy of the routes and never inherit from
the `RouterOutlet`. This means that providers available only to the
component that defines the `RouterOutlet` will no longer be available to
route components in any circumstances. This was already the case
whenever routes defined providers, either through lazy loading an
`NgModule` or through explicit `providers` on the route config.

PR Close #54265
2024-03-14 12:33:13 -07:00
Andrew Scott 2b802587f2 feat(router): Allow Route.redirectTo to be a function which returns a string or UrlTree (#52606)
This commit updates the logic around `Route.redirectTo` to enable using a
function to create the redirect. This function can return a string,
ands acts the same as previous string redirects, or a `UrlTree`, which
will act as an absolute redirect.

To be useful, the redirect function needs access to the params and data.
Today, developers can access these in their redirect strings, for
example `{path: ':id', redirectTo: '/user/:id'}`. Unfortunately,
developers only have access to params and data on the _current route_
today in the redirect strings. The params and data in the `RedirectFn`
give developers access to anything from the matched parent routes as
well. This is done as the same way as param and data aggregation later
on (https://github.com/angular/angular/blob/897f014785578d87bc655ea6ae9e113653960f50/packages/router/src/router_state.ts#L236-L278).
In order to accomplish this, we inherit params and data while
matching, after the `ActivatedRouteSnapshot` is created for the matched
route rather than waiting until the end.

The `RedirectFunction` does not return the full
`ActivatedRouteSnapshot` interface. Some things are not accurately known
at the route matching phase. For example, resolvers are not run until
later, so any resolved title would not be populated. The same goes for lazy
loaded components. The is also true for all the snapshots up to the
root, so properties that include parents (root, parent, pathFromRoot)
are also excluded. And naturally, the full route matching hasn't yet
happened so firstChild and children are not available either.

fixes #13373
resolves #28661 (though not for the redirect string - you would return a
`UrlTree` from the function)

BREAKING CHANGE: This change allows `Route.redirectTo` to be a function
in addition to the previous string. Code which expects `redirectTo` to
only be a string on `Route` objects will need to be adjusted.

PR Close #52606
2024-03-14 11:19:01 -07:00
Andrew Scott 8735af08b9 feat(router): Add ability to return UrlTree with NavigationBehaviorOptions from guards (#45023)
Returning `UrlTree` from a guard was a convenient new feature added to
the `Router`. However, it does not have feature-parity with the old
`router.navigate(...); return false;` pattern. The most common use-case
for this feature is to redirect to a new page _without_ updating the URL
from the initially attempted navigation. For example, rendering a 404
page when the user does not have access privelages to a route.

Fixes #17004
Fixes #27148

BREAKING CHANGE: Guards can now return `RedirectCommand` for redirects
in addition to `UrlTree`. Code which expects only `boolean` or `UrlTree`
values in `Route` types will need to be adjusted.

PR Close #45023
2024-03-12 09:19:14 -07:00
Andrew Scott 115ee88ba9 fix(router): Navigations triggered by cancellation events should cancel previous navigation (#54710)
There is an edge case where synchronous navigations caused in
response to navigation events can result in a previous navigation not
being unsubscribed from. b/328219996

PR Close #54710
2024-03-07 13:07:08 -08:00
Joey Perrott b857aafcb9 refactor: migrate router to prettier formatting (#54318)
Migrate formatting to prettier for router from clang-format

PR Close #54318
2024-02-08 19:17:14 +00:00
Andrew Scott 15c48113c2 refactor(router): Update integration tests to cover navigation and history API (#53799)
This commit updates the router integration tests to cover both the
classic History and the new Navigation API. There is more work to be
done here, but this commit works to prove the efficacy of the
`FakeNavigation` implementation.

PR Close #53799
2024-01-30 15:04:02 +00:00
Matthieu Riegler f80fabbf27 refactor(router): Use Nullish coalescing assignment when possible. (#53930)
Inspired by #53923, lets simplify code when possible.

PR Close #53930
2024-01-17 11:36:11 -08:00
Andrew Scott 5996d3fa6b Revert "refactor(router): Update integration tests to cover navigation and history API (#53799)" (#53860)
This reverts commit eb2e879a00.

PR Close #53860
2024-01-09 14:30:50 -08:00
Andrew Scott eb2e879a00 refactor(router): Update integration tests to cover navigation and history API (#53799)
This commit updates the router integration tests to cover both the
classic History and the new Navigation API. There is more work to be
done here, but this commit works to prove the efficacy of the
`FakeNavigation` implementation.

PR Close #53799
2024-01-09 12:56:47 -08:00
Andrew Scott a5a9b408e2 feat(router): Add transient info to RouterLink input (#53784)
This is a follow up to https://github.com/angular/angular/commit/5c1d4410298e20cb03d7a1ddf7931538b6a181b4
which added the `info` property to navigation requests. `RouterLink` now
supports passing that transient navigation info to the navigation
request.

This info object can be anything and doesn't have to be serializable.
One use-case might be for passing the element that was clicked. This
might be useful for something like view transitions. In the "animating
with javascript" example from the blog (https://stackblitz.com/edit/stackblitz-starters-cklnkm)
those links could have done this instead of needing to create a separate
directive that tracks clicks.

PR Close #53784
2024-01-05 11:28:31 -08:00
Matthieu Riegler 1c63eddb7c refactor(router): remouve unused (#53750)
minor optimisation, removing unused paramters.

PR Close #53750
2024-01-03 13:29:05 -08:00
Andrew Scott 6586b99daa refactor(router): use MockPlatformLocation for integration tests (#53640)
Use of the `SpyLocation` is problematic because it prevents location
APIs from reaching the platform level (`PlatformLocation`) and
`PathLocationStrategy`. This makes it difficult to test interactions
with those providers, including the ability to use the `Navigation` API,
which will live at the platform level.

PR Close #53640
2024-01-03 10:15:54 -08:00
Andrew Scott 502f300757 fix(router): Should not freeze original object used for route data (#53635)
This was broken in 3278966068 where the
new code fails to copy the data object when not inheriting data.

fixes #53632

PR Close #53635
2023-12-19 09:20:56 -08:00
Charles Lyding e149ebf228 build: update rxjs build version to v7 (#53500)
The version of rxjs used to build the repository has been updated to v7.
This required only minimal changes to the code. Most of which were type
related only due to more strict types in v7. The behavior in those cases
was left intact. The most common type related change was to handle the
possibility of `undefined` with `toPromise` which was always possible with
v6 but the types did not reflect the runtime behavior. The one change that
was not type related was to provide a parameter value to the `defaultIfEmpty`
operator. It no longer defaults to a value of `null` if no default is provided.
To provide the same behavior the value of `null` is now passed to the operator.

PR Close #53500
2023-12-18 16:25:37 +00:00
Andrew Scott 5c1d441029 feat(router): Add info property to NavigationExtras (#53303)
This commit adds a property to the navigation options to allow
developers to provide transient navigation info that is available for
the duration of the navigation. This information can be retrieved at any
time with `Router.getCurrentNavigation()!.extras.info`. Previously,
developers were forced to either create a service to hold information
like this or put it on the `state` object, which gets persisted to the
session history.

This feature was partially motivated by the [Navigation API](https://github.com/WICG/navigation-api#example-using-info)
and would be something we would want/need to have feature parity if/when the
Router supports managing navigations with that instead of `History`.

PR Close #53303
2023-12-06 09:44:43 -08:00
Andrew Scott a9872ccbb9 Revert "Revert "fix(router): Ensure canMatch guards run on wildcard routes (#53239)" (#53339)" (#53342)
This reverts commit ac8eb5b943.

PR Close #53342
2023-12-06 09:44:19 -08:00
Andrew Scott 50d7916278 feat(router): Add router configuration to resolve navigation promise on error (#48910)
With the deprecation of the configurable errorHandler in the Router, there is a missing
use-case to prevent the navigation promise from rejecting on an error. This rejection
results in unhandled promise rejections. This commit allows developers to instruct
the router to instead resolve the navigation promise with 'false', which matches
the behavior of other failed navigations.

Resolving the Promise would be the ideal default behavior. It is rare
that any code handles the navigation Promise at all and even more rare
that the Promise rejection is caught. Updating the default value for
this option should be considered for an upcoming major version.

fixes #48902

PR Close #48910
2023-12-04 21:49:35 -08:00
Dylan Hunn d4b4236902 Revert "fix(router): Ensure canMatch guards run on wildcard routes (#53239)" (#53339)
This reverts commit 1940280d27.

PR Close #53339
2023-12-03 14:57:54 -08:00
Andrew Scott 1940280d27 fix(router): Ensure canMatch guards run on wildcard routes (#53239)
This commit makes sure that wildcard routes still run the `canMatch`
guards.

Fixes #49949

PR Close #53239
2023-11-30 09:45:40 -08:00
Leonel Franchelli 29e0834c4d fix(router): Resolvers in different parts of the route tree should be able to execute together (#52934)
The following commit accidentally broken execution of resolvers when
two resolvers appear in different parts of the tree and do not share a
https://github.com/angular/angular/commit/327896606832bf6fbfc8f23989755123028136a8

This happens when there are secondary routes. This test ensures that all
routes with resolves are run.

fixes #52892

PR Close #52934
2023-11-20 12:07:10 -08:00
Andrew Scott 726530a9af feat(router): Allow onSameUrlNavigation: 'ignore' in navigateByUrl (#52265)
There are cases where the application's default behavior is 'reload' and
a certain navigation might want to override this to be `ignore` instead.
This commit allows `onSameUrlNavigation` in the `router.navigateByUrl`
to be `ignore` where it was previously restricted to only `reload`.

PR Close #52265
2023-11-13 18:29:39 +00:00
Andrew Scott 3278966068 fix(router): Ensure newly resolved data is inherited by child routes (#52167)
The current way of computing a route's params and data recomputes
inherited data from the inheritance root every time. When the
inheritance strategy is "emptyOnly", this isn't necessarily the root of
the tree, but some point along the way (it stops once it reaches an
ancestor route with a component).

Instead, this commit updates parameter inheritance to only inherit data
directly from the parent route (again, instead of recomputing all
inherited data back to the inheritance root). The only requirement for
making this work is that the parent route data has already calculated
and updated its own inherited data. This was really already a
requirement -- parents need to be processed before children.

In addition, the update to the inheritance algorithm in this commit
requires more of an understanding that a resolver running higher up in
the tree has to propagate inherited data downwards. The previous
algorithm hid this knowledge because resolvers would recompute inherited
data from the root when run. However, routes that did not have resolvers
rerun or never had resolvers at all would not get the updated resolved data.

fixes #51934

PR Close #52167
2023-10-19 10:26:27 -07:00
Andrew Scott 1da28f4825 feat(router): Add callback to execute when a view transition is created (#52002)
This commit adds the ability to provide a function that will get called
immediately after the view transition is created. This will allow
developers to do things like add/remove classes from the DOM when the
transition animation is finished, skip the transition based on
application conditions, etc. Having access to the transition unlocks
just about every example outlined in https://developer.chrome.com/docs/web-platform/view-transitions.

Note that the timing of the `updateCallback` execution is in the spec as
being called asyncronously (https://drafts.csswg.org/css-view-transitions-1/#callbackdef-updatecallback).
This means the `onViewTransitionCreated` callback is guaranteed to
execute before the update callback which in turn means it is guaranteed
to execute before the view transition `ready`/animation. As a result, it
is safe/effictive to add classes to the document in the
`onViewTransitionCreated` function in order to control animations of
that transition and then remove those classes in the transition's `finished`
`Promise`. The animation also doesn't start until the `Promise` returned
by `updateCallback` resolves, so this would also guarantee that the
animation starts asynchronously.

resolves #51827

PR Close #52002
2023-10-10 11:16:22 -07:00
Andrew Scott 37df395be0 fix(router): children of routes with loadComponent should not inherit parent data by default (#52114)
When a route has loadComponent, its children should not inherit params and
data unless paramsInheritanceStrategy is 'always'.

fixes #52106

BREAKING CHANGE: Routes with `loadComponent` would incorrectly cause
child routes to inherit their data by default. The default
`paramsInheritanceStrategy` is `emptyOnly`. If parent data should be
inherited in child routes, this should be manually set to `always`.

PR Close #52114
2023-10-10 09:36:37 -07:00
Thomas Wilkinson 91c4da6709 refactor(router): move location methods into state_manager.ts (#52032)
This is a preparatory refactor to enable a new version of
state_manager.ts that uses the Navigation API rather than
History API

PR Close #52032
2023-10-05 11:02:07 -07:00
Andrew Scott ce1b915868 fix(router): Allow redirects after an absolute redirect (#51731)
The router currently restricts all further redirects after an absolute
redirect. Because there's no documented reason for _why_ this
restriction is in place, I'm now deeming this unnecessary. Developers
should not be restricted in this manner. Instead, configs that may
have caused infinite redirects in the past should be updated to not be
infinite. It is confusing to ignore configs with redirects after an
absolute redirect occurred because it creates different matching rules
depending on the whether an absolute redirect has happened or not.

For additional context on why I believe removing this restriction is
necessary, #13373 asks for allowing `redirectTo` to be a function. It
would make sense to allow this function to return a `UrlTree` like other
guards in the Router. When guards in the `Router` return `UrlTree`, they
cancel the current navigation and start a new one to re-do the route
matching. Since we're already in the router matching part, we don't need
to cancel the navigation. However, the restriction on absolute redirects
here then creates a weird situation where developers wouldn't see any
other redirects if they returned a `UrlTree` as an absolute redirect
from `redirectTo`.

resolves #39770

BREAKING CHANGE: Absolute redirects no longer prevent further redirects.
Route configurations may need to be adjusted to prevent infinite
redirects where additional redirects were previously ignored after an
absolute redirect occurred.

PR Close #51731
2023-09-26 10:59:20 -07:00
Andrew Scott 86e91463af feat(router): Add option to skip the first view transition (#51825)
This commit adds an option to the view transition feature to skip the first transition.
This option is not available in RouterModule.forRoot.

resolves #51815

PR Close #51825
2023-09-22 09:48:09 -07:00
Andrew Scott ad61bf6184 refactor(router): Remove internal state tracking for browserUrlTree (#48065)
The browserUrlTree is only used to support the onSameUrlNavigation: 'ignore' logic. We can achieve this functionality without having this state tracked inside the Router. Instead, we can re-examine what ignore means: We don't want to rerun the matching logic, guards, or resolvers when we already know that nothing is changing.
Outside of the "navigated", there are two things that constitute a "change":

1. The browser URL might change. Because of skipLocationChange, the browser URL might not always match the internal state of the Router (we can navigate to a path but skip updating the browser URL). If we're navigating to a place that would change the browser URL, we should process the navigation. Theoretically, all we need to really do is update the browser URL instead of processing the whole navigation w/ guards, redirects, and resolvers. But this doesn't matter that much because the default value for runGuardsAndResolvers will skip all of this anyways.
2. The internal state of the Router might change. That is, we're navigating to a new path and may or may not be updating the updating the browser URL.

If either of the above are true, we process the navigation. If both are false, we aren't changing anything so we can safely ignore the navigation request (as long as onSameUrlNavigation === 'ignore').

Why is this change important?

* Simplification of Router internals. The Router has a lot of special case handling and one-offs to handle a limited set of scenarios. Removing these when possible makes the code easier to follow

PR Close #48065
2023-09-19 16:50:56 +02:00
Kristiyan Kostadinov 52cc7f839b build: align with internal tsconfig options (#51728)
Currently internally Angular has some customized tsconfig files, because we don't align with the tsconfig of the rest of g3. These changes enable `noImplicitReturns` and `noPropertyAccessFromIndexSignature` to align better with the internal config.

PR Close #51728
2023-09-12 11:39:42 -07:00
Andrew Scott 73e4bf2ed2 feat(router): Add feature to support the View Transitions API (#51314)
The View Transitions API enables easy animations when transitioning between different DOM states. This commit adds an opt-in feature to the Router which runs the component activation and deactivation logic in the document.startViewTransition callback. If the browser does not support this API, route activation and deactivation will happen synchronously.

resolves #49401

PR Close #51314
2023-09-11 10:36:10 -07:00
Andrew Scott b2aff43621 fix(router): Remove urlHandlingStrategy from public Router properties (#51631)
This commit removes the `urlHandlingStrategy` from the public Router's API

BREAKING CHANGE:
`urlHandlingStrategy` has been removed from the Router public API.
This should instead be configured through the provideRouter or RouterModule.forRoot APIs.

PR Close #51631
2023-09-05 17:09:13 +00:00
Andrew Scott f464e39364 fix(router): Ensure title observable gets latest values (#51561)
The data `Observable` is not updated unless there have been changes to
the object. The current diffing does not look at `symbol` keys of the
object but the `title` property is stored as a private `symbol`. This
commit updates the object diffing to include symbols.

fixes #51401

PR Close #51561
2023-08-31 16:42:44 +00:00
Andrew Scott c62e680098 fix(router): Remove deprecated Router properties (#51502)
This commit removes deprecated properties on the Router. These are meant
to be configured through DI and not meant to be changed during runtime.

BREAKING CHANGE: The following Router properties have been removed from
the public API:

- canceledNavigationResolution
- paramsInheritanceStrategy
- titleStrategy
- urlUpdateStrategy
- malformedUriErrorHandler

These should instead be configured through the `provideRouter` or
`RouterModule.forRoot` APIs.

PR Close #51502
2023-08-29 19:47:58 +00:00
Andrew Scott 2aae92b05f refactor(router): Have router subscribe to transition events to update state (#48427)
Rather than the navigation transitions managing Router state, this
commit updates the Router to manage its own state based on ongoing
transition events. In the future, this can be abstracted even further to
have a totally separate class that manages the Router state. This would
allow the potential for swapping state manager implementations rather
than having to implement all types of state management in a single place.

One finding during tests was that unexpected errors thrown by the state management code moved
to the Router here will no longer be caught by the transition pipe's `catchError`.
This only includes calls to the following public Api methods:

* `go`, `replaceState`, `historyGo`, `isCurrentPathEqualTo` on `Location`
* `UrlSerializer.serialize`
* `UrlHandlingStrategy.merge`.

None of these methods should throw if the router is expected to function.
These might throw when tests include incomplete mocks, which is not
supported, or in cases where the actual browser methods like
`replaceState` would throw. This will already result in unexpected/unsupported
behavior. The failure case here is now arguably better - the navigation
itself still completes but the state update (either updating Router
internal state or updating the browser URL) fails separately and is
unhandled.

PR Close #48427
2023-08-22 08:04:00 -07:00
Andrew Scott 96d94ad130 fix(router): Ensure canceledNavigationResolution: 'computed' works on first page (#51441)
Setting the page ID is currently broken for the first page because the
helper method's second parameter is optional, which allowed the initally
`undefined` page ID to be used again when the router performs its
initial navigation.

fixes #50983

PR Close #51441
2023-08-21 13:01:58 -07:00
Andrew Scott 4e22a39e77 fix(router): Apply named outlets to children empty paths not appearing in the URL (#51292)
Empty path routes are effectively 'passthrough' routes that do not
appear in the URL. When these exist in the route tree, we do not want to
apply named outlet commands to that tree location. Instead, we skip past
this location in the tree, effectively squashing/removing this
passthrough route from the tree.

fixes #50356

PR Close #51292
2023-08-10 09:18:30 -07:00
Matthieu Riegler ad28cddd41 refactor(platform-browser): replace our own toBeAnInstanceOf with toBeInstanceOf (#50661)
There is no need to maintain that matcher since jasmine provides its own !

PR Close #50661
2023-06-14 10:58:04 +02:00
Kristiyan Kostadinov d55276aab0 refactor(router): use transform to coerce input values (#50589)
Uses the new `transform` option for inputs instead of getters and setters to coerce the incoming values.

PR Close #50589
2023-06-06 14:15:50 -07:00
BrianDGLS 2d411430e3 refactor(router): run spell check on router package (#50445)
Fix typos in router package.

PR Close #50445
2023-05-24 13:56:56 +00:00
Alan Agius 9e9b424332 test(router): enable a subset of bootstrapping test to ran on node (#49843)
Some of these test can run on Node.js

PR Close #49843
2023-04-14 14:14:55 -04:00
Andrew Scott cbca5817d8 fix(router): canceledNavigationResolution: 'computed' with redirects to the current URL (#49793)
The `canceledNavigationResolution: 'computed'` option does not correctly
assign page IDs or restore them when redirects result in navigating to
the current URL. This change ensures that the page IDs are still
incremented and restored correctly in this scenario.

PR Close #49793
2023-04-14 14:55:06 +00:00