@ngrdt/router 0.0.97 → 0.0.99

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,13 +1,12 @@
1
1
  import * as i0 from '@angular/core';
2
- import { InjectionToken, inject, Injectable, EnvironmentInjector, runInInjectionContext, DestroyRef, Renderer2, ElementRef, Input, Directive, afterNextRender, input, linkedSignal, booleanAttribute, computed, effect } from '@angular/core';
3
- import { defer, of, filter, take, fromEvent } from 'rxjs';
4
- import { PlatformLocation, Location } from '@angular/common';
2
+ import { InjectionToken, inject, EnvironmentInjector, runInInjectionContext, makeEnvironmentProviders, Injectable, DestroyRef, Renderer2, ElementRef, Input, Directive, afterNextRender, input, linkedSignal, booleanAttribute, computed, effect } from '@angular/core';
3
+ import { Location, PlatformLocation } from '@angular/common';
5
4
  import * as i1 from '@angular/router';
6
5
  import { Router, NavigationEnd, RouterModule, RouterLink, GuardsCheckStart } from '@angular/router';
7
6
  import { RdtStringUtils } from '@ngrdt/utils';
7
+ import { filter, take, fromEvent } from 'rxjs';
8
8
  import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
9
9
  import { RDT_BUTTON_BASE_PROVIDER } from '@ngrdt/button';
10
- import { RdtComponentGuardStoreService } from '@ngrdt/core';
11
10
 
12
11
  /**
13
12
  * Injection token that defines behavior when route cannot be entered (canBeEntered returns false).
@@ -15,15 +14,12 @@ import { RdtComponentGuardStoreService } from '@ngrdt/core';
15
14
  */
16
15
  const RDT_CANNOT_BE_ENTERED_PROVIDER = new InjectionToken('RDT_CANNOT_BE_ENTERED', { factory: () => undefined, providedIn: 'root' });
17
16
 
18
- const RDT_CONFIRM_DATA_LOSS_SERVICE = new InjectionToken('RDT_CONFIRM_DATA_LOSS_SERVICE', { factory: () => new RdtConfirmDataLossServiceAlert() });
19
- class RdtConfirmDataLossService {
20
- }
21
- class RdtConfirmDataLossServiceAlert extends RdtConfirmDataLossService {
22
- confirmDataLoss() {
23
- return defer(() => of(confirm('Are you sure you want to leave this page?')));
24
- }
25
- }
26
-
17
+ /**
18
+ * Type-safe container for route parameters keyed by route instance.
19
+ * Stores params for multiple routes in a hierarchy (e.g. parent + child params from a single URL).
20
+ * Parameters are keyed internally by absolute path, so two different `RdtRoute` instances
21
+ * with the same path share the same slot.
22
+ */
27
23
  class RdtParameters {
28
24
  params;
29
25
  constructor(params = {}) {
@@ -57,177 +53,23 @@ function ALWAYS_TRUE() {
57
53
  return true;
58
54
  }
59
55
 
60
- const RDT_ROUTES_PROVIDER = new InjectionToken('RDT_ROUTES_PROVIDER');
61
-
62
- const DEFAULT_TARGET = '_self';
63
- const RDT_STATE_PARAMS_KEY = 'RdtStateParams';
64
- class RdtRouterService {
65
- allRoutes = inject(RDT_ROUTES_PROVIDER, { optional: true });
66
- baseHref = inject(PlatformLocation).getBaseHrefFromDOM();
67
- location = inject(Location);
68
- router = inject(Router);
69
- _previousUrl = null;
70
- _currentUrl = null;
71
- get previousUrl() {
72
- return this._previousUrl;
73
- }
74
- get currentUrl() {
75
- return this._currentUrl;
76
- }
77
- parsePreviousUrl() {
78
- return this._previousUrl ? this.parseAbsoluteUrl(this._previousUrl) : null;
79
- }
80
- parseCurrentUrl() {
81
- return this._currentUrl ? this.parseAbsoluteUrl(this._currentUrl) : null;
82
- }
83
- navigationEnd$ = this.router.events.pipe(filter((e) => e instanceof NavigationEnd));
84
- nextNavigationEnd$ = this.navigationEnd$.pipe(take(1));
85
- constructor() {
86
- if (this.allRoutes === null) {
87
- console.warn('All routes not provided. Make sure to provide RDT_ROUTES_PROVIDER with RdtRoute[].');
88
- this.allRoutes = [];
89
- }
90
- this.navigationEnd$.subscribe((e) => {
91
- this._previousUrl = this._currentUrl;
92
- this._currentUrl = e.url;
93
- });
94
- }
95
- /**
96
- * @returns window.history.state extended by state parameters passed from parent window.
97
- */
98
- getHistoryState() {
99
- if ('RdtStateParams' in window &&
100
- typeof window[RDT_STATE_PARAMS_KEY] === 'object') {
101
- return { ...window[RDT_STATE_PARAMS_KEY], ...(history.state ?? {}) };
102
- }
103
- else {
104
- return history.state ?? {};
105
- }
106
- }
107
- /**
108
- * Navigates to the specified route.
109
- * @param link Route to navigate to.
110
- * @param params Parameter of route passed as first argument or RdtParameters object.
111
- * @param extras Allows you to specify additional parameters for navigation. These extras have higher priority than those defined in the route.
112
- * @returns Promise that resolves when navigation is complete.
113
- */
114
- navigate(link, params, extras) {
115
- let url;
116
- if (params instanceof RdtParameters) {
117
- let linkCpy = link;
118
- for (let r = linkCpy; r !== null; r = r.parent) {
119
- const p = params.get(r);
120
- if (p) {
121
- linkCpy = linkCpy.withStaticParams(r, p);
122
- }
123
- }
124
- url = linkCpy.createAbsoluteUrl();
125
- }
126
- else {
127
- url = params ? link.createAbsoluteUrl(params) : link.createAbsoluteUrl();
128
- }
129
- const target = extras?.target ?? DEFAULT_TARGET;
130
- const queryParams = extras?.query ?? link.queryParams;
131
- const stateParams = extras?.state ?? link.stateParams;
132
- if (target === '_self') {
133
- return this.router.navigate([url], {
134
- queryParams: queryParams,
135
- state: stateParams,
136
- replaceUrl: extras?.replaceUrl,
137
- });
138
- }
139
- const absolutePath = RdtStringUtils.createAbsoluteUrl(url, this.baseHref);
140
- const pathWithParams = RdtStringUtils.appendQueryParams(absolutePath, queryParams);
141
- const win = window.open(pathWithParams, target);
142
- if (win) {
143
- win[RDT_STATE_PARAMS_KEY] = stateParams;
144
- }
145
- return Promise.resolve(true);
146
- }
147
- navigateHome(extras) {
148
- return this.router.navigateByUrl(RdtStringUtils.appendQueryParams('/', extras?.query ?? {}), {
149
- state: extras?.state,
150
- replaceUrl: extras?.replaceUrl,
151
- });
152
- }
153
- navigateBack(extras) {
154
- const parsed = this.parseAbsoluteUrl();
155
- if (parsed) {
156
- let route = parsed.route.withStaticParams(parsed.params);
157
- do {
158
- route = route.parent;
159
- } while (route && route.entryDisabled);
160
- // In case route has no ancestor with allowed entry,
161
- if (!route) {
162
- return this.navigateHome(extras);
163
- }
164
- if (extras) {
165
- if (extras.query) {
166
- route = route.withQueryParams(extras.query);
167
- }
168
- if (extras.state) {
169
- route = route.withStateParams(extras.state);
170
- }
171
- }
172
- return this.navigate(route, extras);
173
- }
174
- else {
175
- console.warn(`Cannot go back from ${this.location.path()} because no route matches current url`);
176
- return null;
177
- }
178
- }
179
- /**
180
- * This method will try to find matching route for given absolute URL, extract its parameters and return them.
181
- * If no route matches the URL, it returns null. Parameters are extracted only for the last matching route, not ancestors.
182
- * @param url Absolute URL to parse (e.g. '/home/details/123?query=abc').
183
- * @returns Object containing the matching route and its parameters, or null if no route is matching.
184
- */
185
- parseAbsoluteUrl(url = this.location.path()) {
186
- const stripped = RdtStringUtils.stripQueryParams(url);
187
- if (this.allRoutes) {
188
- for (const route of this.allRoutes) {
189
- const params = route.parseAbsoluteUrl(stripped);
190
- if (params) {
191
- return { route, params };
192
- }
193
- }
194
- }
195
- return null;
196
- }
197
- extractAllParams(currentRoute) {
198
- if (!currentRoute) {
199
- const parsed = this.parseAbsoluteUrl();
200
- if (!parsed) {
201
- console.warn('No route matches current url.');
202
- return null;
203
- }
204
- currentRoute = parsed.route;
205
- }
206
- const url = this.location.path();
207
- return currentRoute.parseAbsoluteUrl(url);
208
- }
209
- isParentOfCurrentLocation(route) {
210
- return true;
211
- }
212
- removeQueryParams(...paramNames) {
213
- const regex = new RegExp(`[?&](${paramNames.join('|')})=[^&]*`, 'g');
214
- return history.replaceState(history.state, '', location.pathname + location.search.replace(regex, '').replace(/^&/, '?'));
215
- }
216
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.0", ngImport: i0, type: RdtRouterService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
217
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.0.0", ngImport: i0, type: RdtRouterService, providedIn: 'root' });
218
- }
219
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.0", ngImport: i0, type: RdtRouterService, decorators: [{
220
- type: Injectable,
221
- args: [{
222
- providedIn: 'root',
223
- }]
224
- }], ctorParameters: () => [] });
225
-
226
56
  var RdtNavigationSource;
227
57
  (function (RdtNavigationSource) {
228
58
  RdtNavigationSource["BREADCRUMB"] = "breadcrumb";
229
59
  })(RdtNavigationSource || (RdtNavigationSource = {}));
230
60
 
61
+ /**
62
+ * Bridges an `RdtRoute` to Angular's `Route` config.
63
+ * Obtained via `RdtRoute.toAngularRoute()`. Use the fluent API to attach
64
+ * components, guards, resolvers, and providers, then call `build()` to produce
65
+ * the Angular `Route` object.
66
+ *
67
+ * `build()` validates that children defined on the `RdtRoute` match the children
68
+ * passed via `withChildren()` — a mismatch throws at startup, catching misconfiguration early.
69
+ *
70
+ * When a route has both a component and children, `build()` automatically restructures
71
+ * the output into Angular's required wrapper format (parent with empty-path child for the component).
72
+ */
231
73
  class RdtAngularRoute {
232
74
  route;
233
75
  children = [];
@@ -427,8 +269,8 @@ class RdtAngularRoute {
427
269
  }
428
270
  getCanMatch() {
429
271
  const paramMap = this.route.paramMap;
430
- const hasNumberParams = Object.keys(paramMap).some((p) => paramMap[p] === 'number');
431
- if (!hasNumberParams) {
272
+ const hasConstrainedParams = Object.keys(paramMap).some((p) => paramMap[p] === 'number' || Array.isArray(paramMap[p]));
273
+ if (!hasConstrainedParams) {
432
274
  return undefined;
433
275
  }
434
276
  const routeSegmentLength = this.route.path.split('/').length;
@@ -564,6 +406,19 @@ class RdtRouteBase {
564
406
  }
565
407
  }
566
408
 
409
+ /**
410
+ * Immutable, type-safe route definition. Created via `RdtRouteBuilder.build()`.
411
+ *
412
+ * Each instance represents a single route in the hierarchy and holds its path pattern,
413
+ * parameter types, parent reference, and access guard. Use `withStaticParams()`,
414
+ * `withQueryParams()`, or `withStateParams()` to create derived instances with
415
+ * pre-filled values (the original is never mutated).
416
+ *
417
+ * To bridge to Angular's router, call `toAngularRoute()` to get an `RdtAngularRoute`
418
+ * builder that produces an Angular `Route` config object.
419
+ *
420
+ * @typeParam T - Shape of this route's own parameters.
421
+ */
567
422
  class RdtRoute extends RdtRouteBase {
568
423
  _absoluteRegex;
569
424
  _staticParams = {};
@@ -712,7 +567,7 @@ class RdtRoute extends RdtRouteBase {
712
567
  for (let i = 0; i < reversedParams.length && i < values.length; i++) {
713
568
  const key = reversedParams[i];
714
569
  const mappedKey = this._paramMappings.find((m) => m.urlName === key)?.tableName ?? key;
715
- let val = values[i];
570
+ let val = decodeURIComponent(values[i]);
716
571
  if (this._paramMap[key] === 'number') {
717
572
  val = parseInt(val);
718
573
  }
@@ -755,7 +610,11 @@ class RdtRoute extends RdtRouteBase {
755
610
  throw new Error(`Param ${param} is missing for route ${this.absolutePath}. Please pass object with required parameters.`);
756
611
  }
757
612
  const value = extendedParamMap[param];
758
- path = path.replace(`:${param}`, `${value}`);
613
+ const type = this._paramMap[param];
614
+ if (Array.isArray(type) && !type.includes(`${value}`)) {
615
+ throw new Error(`Value "${value}" is not allowed for enum param "${param}" in route ${this.absolutePath}. Allowed values: ${type.join(', ')}`);
616
+ }
617
+ path = path.replace(`:${param}`, encodeURIComponent(`${value}`));
759
618
  });
760
619
  return path;
761
620
  }
@@ -846,8 +705,18 @@ class RdtRoute extends RdtRouteBase {
846
705
  clone._paramMappings = this._paramMappings;
847
706
  clone._staticParams = { ...this._staticParams };
848
707
  clone._stateParams = { ...this._stateParams };
708
+ clone._canBeEntered = this._canBeEntered;
709
+ clone._queryParams = { ...this._queryParams };
849
710
  return clone;
850
711
  }
712
+ static getGroup(type) {
713
+ if (Array.isArray(type)) {
714
+ return ('(' +
715
+ type.map((v) => v.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')).join('|') +
716
+ ')');
717
+ }
718
+ return RdtRoute.groups[type];
719
+ }
851
720
  setRegex() {
852
721
  const params = Object.keys(this._paramMap);
853
722
  params.sort((a, b) => b.length - a.length);
@@ -857,7 +726,7 @@ class RdtRoute extends RdtRouteBase {
857
726
  if (!type) {
858
727
  throw new Error('Params is not set i');
859
728
  }
860
- substituted = substituted.replace(`:${p}`, RdtRoute.groups[type]);
729
+ substituted = substituted.replace(`:${p}`, RdtRoute.getGroup(type));
861
730
  });
862
731
  if (substituted === '') {
863
732
  this._regex = null;
@@ -883,12 +752,228 @@ class RdtRoute extends RdtRouteBase {
883
752
  return route;
884
753
  }
885
754
  static groups = {
886
- string: '([\\w_\\-]+)',
887
- number: '(\\d+)',
755
+ string: '([^/]+)',
756
+ number: '(-?\\d+)',
888
757
  };
889
758
  }
890
759
 
760
+ /**
761
+ * Injection token holding all application routes as a flat array.
762
+ * Used internally by `RdtRouterService` to match URLs and enable type-safe navigation.
763
+ * Prefer using `provideRdtRoutes()` instead of providing this token directly.
764
+ */
765
+ const RDT_ROUTES_PROVIDER = new InjectionToken('RDT_ROUTES_PROVIDER');
766
+ /**
767
+ * Provides all application routes to `RdtRouterService`.
768
+ * Accepts either a route module object (`import * as routes from './routes'`)
769
+ * or a plain array of `RdtRoute` instances.
770
+ *
771
+ * @example
772
+ * ```ts
773
+ * import * as ALL_ROUTES from './rdt-routes';
774
+ *
775
+ * export const appConfig: ApplicationConfig = {
776
+ * providers: [
777
+ * provideRdtRoutes(ALL_ROUTES),
778
+ * ],
779
+ * };
780
+ * ```
781
+ */
782
+ function provideRdtRoutes(routes) {
783
+ const routeArray = Array.isArray(routes)
784
+ ? routes
785
+ : Object.values(routes).filter((v) => v instanceof RdtRoute);
786
+ return makeEnvironmentProviders([
787
+ { provide: RDT_ROUTES_PROVIDER, useValue: routeArray },
788
+ ]);
789
+ }
790
+
791
+ const DEFAULT_TARGET = '_self';
792
+ const RDT_STATE_PARAMS_KEY = 'RdtStateParams';
793
+ /**
794
+ * Central navigation service for type-safe routing.
795
+ * Wraps Angular's `Router` with `RdtRoute`-based navigation, URL parsing, and history tracking.
796
+ * Requires `RDT_ROUTES_PROVIDER` to be configured with all application routes.
797
+ */
798
+ class RdtRouterService {
799
+ allRoutes = inject(RDT_ROUTES_PROVIDER, { optional: true });
800
+ baseHref = inject(PlatformLocation).getBaseHrefFromDOM();
801
+ location = inject(Location);
802
+ router = inject(Router);
803
+ _previousUrl = null;
804
+ _currentUrl = null;
805
+ get previousUrl() {
806
+ return this._previousUrl;
807
+ }
808
+ get currentUrl() {
809
+ return this._currentUrl;
810
+ }
811
+ parsePreviousUrl() {
812
+ return this._previousUrl ? this.parseAbsoluteUrl(this._previousUrl) : null;
813
+ }
814
+ parseCurrentUrl() {
815
+ return this._currentUrl ? this.parseAbsoluteUrl(this._currentUrl) : null;
816
+ }
817
+ navigationEnd$ = this.router.events.pipe(filter((e) => e instanceof NavigationEnd));
818
+ nextNavigationEnd$ = this.navigationEnd$.pipe(take(1));
819
+ constructor() {
820
+ if (this.allRoutes === null) {
821
+ console.warn('All routes not provided. Make sure to provide RDT_ROUTES_PROVIDER with RdtRoute[].');
822
+ this.allRoutes = [];
823
+ }
824
+ this.navigationEnd$.subscribe((e) => {
825
+ this._previousUrl = this._currentUrl;
826
+ this._currentUrl = e.url;
827
+ });
828
+ }
829
+ /**
830
+ * @returns window.history.state extended by state parameters passed from parent window.
831
+ */
832
+ getHistoryState() {
833
+ if ('RdtStateParams' in window &&
834
+ typeof window[RDT_STATE_PARAMS_KEY] === 'object') {
835
+ return { ...window[RDT_STATE_PARAMS_KEY], ...(history.state ?? {}) };
836
+ }
837
+ else {
838
+ return history.state ?? {};
839
+ }
840
+ }
841
+ /**
842
+ * Navigates to the specified route.
843
+ * @param link Route to navigate to.
844
+ * @param params Parameter of route passed as first argument or RdtParameters object.
845
+ * @param extras Allows you to specify additional parameters for navigation. These extras have higher priority than those defined in the route.
846
+ * @returns Promise that resolves when navigation is complete.
847
+ */
848
+ navigate(link, params, extras) {
849
+ let url;
850
+ if (params instanceof RdtParameters) {
851
+ let linkCpy = link;
852
+ for (let r = linkCpy; r !== null; r = r.parent) {
853
+ const p = params.get(r);
854
+ if (p) {
855
+ linkCpy = linkCpy.withStaticParams(r, p);
856
+ }
857
+ }
858
+ url = linkCpy.createAbsoluteUrl();
859
+ }
860
+ else {
861
+ url = params ? link.createAbsoluteUrl(params) : link.createAbsoluteUrl();
862
+ }
863
+ const target = extras?.target ?? DEFAULT_TARGET;
864
+ const queryParams = extras?.query ?? link.queryParams;
865
+ const stateParams = extras?.state ?? link.stateParams;
866
+ if (target === '_self') {
867
+ return this.router.navigate([url], {
868
+ queryParams: queryParams,
869
+ state: stateParams,
870
+ replaceUrl: extras?.replaceUrl,
871
+ });
872
+ }
873
+ const absolutePath = RdtStringUtils.createAbsoluteUrl(url, this.baseHref);
874
+ const pathWithParams = RdtStringUtils.appendQueryParams(absolutePath, queryParams);
875
+ const win = window.open(pathWithParams, target);
876
+ if (win) {
877
+ win[RDT_STATE_PARAMS_KEY] = stateParams;
878
+ }
879
+ return Promise.resolve(true);
880
+ }
881
+ navigateHome(extras) {
882
+ return this.router.navigateByUrl(RdtStringUtils.appendQueryParams('/', extras?.query ?? {}), {
883
+ state: extras?.state,
884
+ replaceUrl: extras?.replaceUrl,
885
+ });
886
+ }
887
+ navigateBack(extras) {
888
+ const parsed = this.parseAbsoluteUrl();
889
+ if (parsed) {
890
+ let route = parsed.route.withStaticParams(parsed.params);
891
+ do {
892
+ route = route.parent;
893
+ } while (route && route.entryDisabled);
894
+ // In case route has no ancestor with allowed entry,
895
+ if (!route) {
896
+ return this.navigateHome(extras);
897
+ }
898
+ if (extras) {
899
+ if (extras.query) {
900
+ route = route.withQueryParams(extras.query);
901
+ }
902
+ if (extras.state) {
903
+ route = route.withStateParams(extras.state);
904
+ }
905
+ }
906
+ return this.navigate(route, extras);
907
+ }
908
+ else {
909
+ console.warn(`Cannot go back from ${this.location.path()} because no route matches current url`);
910
+ return null;
911
+ }
912
+ }
913
+ /**
914
+ * This method will try to find matching route for given absolute URL, extract its parameters and return them.
915
+ * If no route matches the URL, it returns null. Parameters are extracted only for the last matching route, not ancestors.
916
+ * @param url Absolute URL to parse (e.g. '/home/details/123?query=abc').
917
+ * @returns Object containing the matching route and its parameters, or null if no route is matching.
918
+ */
919
+ parseAbsoluteUrl(url = this.location.path()) {
920
+ const stripped = RdtStringUtils.stripQueryParams(url);
921
+ if (this.allRoutes) {
922
+ for (const route of this.allRoutes) {
923
+ const params = route.parseAbsoluteUrl(stripped);
924
+ if (params) {
925
+ return { route, params };
926
+ }
927
+ }
928
+ }
929
+ return null;
930
+ }
931
+ extractAllParams(currentRoute) {
932
+ if (!currentRoute) {
933
+ const parsed = this.parseAbsoluteUrl();
934
+ if (!parsed) {
935
+ console.warn('No route matches current url.');
936
+ return null;
937
+ }
938
+ currentRoute = parsed.route;
939
+ }
940
+ const url = this.location.path();
941
+ return currentRoute.parseAbsoluteUrl(url);
942
+ }
943
+ isParentOfCurrentLocation(route) {
944
+ return true;
945
+ }
946
+ removeQueryParams(...paramNames) {
947
+ const regex = new RegExp(`[?&](${paramNames.join('|')})=[^&]*`, 'g');
948
+ return history.replaceState(history.state, '', location.pathname + location.search.replace(regex, '').replace(/^&/, '?'));
949
+ }
950
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.2", ngImport: i0, type: RdtRouterService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
951
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.1.2", ngImport: i0, type: RdtRouterService, providedIn: 'root' });
952
+ }
953
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.2", ngImport: i0, type: RdtRouterService, decorators: [{
954
+ type: Injectable,
955
+ args: [{
956
+ providedIn: 'root',
957
+ }]
958
+ }], ctorParameters: () => [] });
959
+
891
960
  const DEFAULT_PARAM_TYPE = 'number';
961
+ /**
962
+ * Fluent builder for creating `RdtRoute` instances.
963
+ * Routes are defined once in a central file and referenced everywhere by object, eliminating string-based path typos.
964
+ *
965
+ * @example
966
+ * ```ts
967
+ * const USER_DETAIL = new RdtRouteBuilder<{ userId: number }>()
968
+ * .withName('User Detail')
969
+ * .withPath(':userId')
970
+ * .withParam('userId', 'number')
971
+ * .withCanBeEntered((route, params) => params.params.userId !== 0)
972
+ * .build();
973
+ * ```
974
+ *
975
+ * @typeParam T - Shape of the route's parameters. Enforced at compile time when navigating or creating URLs.
976
+ */
892
977
  class RdtRouteBuilder extends RdtRouteBase {
893
978
  get canBeEntered() {
894
979
  return this._canBeEntered;
@@ -976,8 +1061,8 @@ class RdtRouteBuilder extends RdtRouteBase {
976
1061
  }
977
1062
  /**
978
1063
  * Defines parameter type and lets framework parse it.
979
- * @param paramName
980
- * @param type
1064
+ * @param paramName Name of the parameter in the path.
1065
+ * @param type 'string', 'number', or an array of allowed string values (enum).
981
1066
  */
982
1067
  withParam(paramName, type) {
983
1068
  this._paramMap[paramName] = type;
@@ -1122,10 +1207,10 @@ class RdtAnyRouteActiveDirective {
1122
1207
  return input;
1123
1208
  }
1124
1209
  }
1125
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.0", ngImport: i0, type: RdtAnyRouteActiveDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
1126
- static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "20.0.0", type: RdtAnyRouteActiveDirective, isStandalone: true, selector: "[rdtAnyRouteActive]", inputs: { anyRouteActive: ["rdtAnyRouteActive", "anyRouteActive"], watchedRoutes: "watchedRoutes", anyRouteActiveOptions: "anyRouteActiveOptions", ariaCurrentWhenActive: "ariaCurrentWhenActive" }, providers: [RouterModule], usesOnChanges: true, ngImport: i0 });
1210
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.2", ngImport: i0, type: RdtAnyRouteActiveDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
1211
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.1.2", type: RdtAnyRouteActiveDirective, isStandalone: true, selector: "[rdtAnyRouteActive]", inputs: { anyRouteActive: ["rdtAnyRouteActive", "anyRouteActive"], watchedRoutes: "watchedRoutes", anyRouteActiveOptions: "anyRouteActiveOptions", ariaCurrentWhenActive: "ariaCurrentWhenActive" }, providers: [RouterModule], usesOnChanges: true, ngImport: i0 });
1127
1212
  }
1128
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.0", ngImport: i0, type: RdtAnyRouteActiveDirective, decorators: [{
1213
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.2", ngImport: i0, type: RdtAnyRouteActiveDirective, decorators: [{
1129
1214
  type: Directive,
1130
1215
  args: [{
1131
1216
  selector: '[rdtAnyRouteActive]',
@@ -1165,10 +1250,10 @@ class RdtBackLinkDirective {
1165
1250
  }
1166
1251
  });
1167
1252
  }
1168
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.0", ngImport: i0, type: RdtBackLinkDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
1169
- static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "20.0.0", type: RdtBackLinkDirective, isStandalone: true, selector: "[rdtBackLink]", ngImport: i0 });
1253
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.2", ngImport: i0, type: RdtBackLinkDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
1254
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.1.2", type: RdtBackLinkDirective, isStandalone: true, selector: "[rdtBackLink]", ngImport: i0 });
1170
1255
  }
1171
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.0", ngImport: i0, type: RdtBackLinkDirective, decorators: [{
1256
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.2", ngImport: i0, type: RdtBackLinkDirective, decorators: [{
1172
1257
  type: Directive,
1173
1258
  args: [{
1174
1259
  selector: '[rdtBackLink]',
@@ -1184,16 +1269,14 @@ class RdtRouterLinkDirective {
1184
1269
  });
1185
1270
  routerLinkRef = inject(RouterLink, { self: true });
1186
1271
  envInjector = inject(EnvironmentInjector);
1187
- routeInput = input.required({ alias: 'rdtRouterLink' });
1188
- route = linkedSignal(() => this.routeInput());
1189
- target = input('_self');
1190
- params = input();
1191
- queryParams = input();
1192
- stateParams = input();
1193
- disabled = input(false, {
1194
- alias: 'rdtDisabled',
1195
- transform: booleanAttribute,
1196
- });
1272
+ routeInput = input.required({ ...(ngDevMode ? { debugName: "routeInput" } : {}), alias: 'rdtRouterLink' });
1273
+ route = linkedSignal(() => this.routeInput(), ...(ngDevMode ? [{ debugName: "route" }] : []));
1274
+ target = input('_self', ...(ngDevMode ? [{ debugName: "target" }] : []));
1275
+ params = input(...(ngDevMode ? [undefined, { debugName: "params" }] : []));
1276
+ queryParams = input(...(ngDevMode ? [undefined, { debugName: "queryParams" }] : []));
1277
+ stateParams = input(...(ngDevMode ? [undefined, { debugName: "stateParams" }] : []));
1278
+ disabled = input(false, { ...(ngDevMode ? { debugName: "disabled" } : {}), alias: 'rdtDisabled',
1279
+ transform: booleanAttribute });
1197
1280
  canBeEntered = computed(() => {
1198
1281
  const route = this.route();
1199
1282
  const disabled = this.disabled();
@@ -1206,7 +1289,7 @@ class RdtRouterLinkDirective {
1206
1289
  state: this.stateParams(),
1207
1290
  };
1208
1291
  return route.canBeEntered(this.envInjector, combinedParams);
1209
- });
1292
+ }, ...(ngDevMode ? [{ debugName: "canBeEntered" }] : []));
1210
1293
  updateEffect = effect(() => {
1211
1294
  if (this.buttonRef) {
1212
1295
  this.updateButton();
@@ -1214,7 +1297,7 @@ class RdtRouterLinkDirective {
1214
1297
  else {
1215
1298
  this.updateRouterLink();
1216
1299
  }
1217
- });
1300
+ }, ...(ngDevMode ? [{ debugName: "updateEffect" }] : []));
1218
1301
  updateButton() {
1219
1302
  const route = this.route();
1220
1303
  if (!route) {
@@ -1292,10 +1375,10 @@ class RdtRouterLinkDirective {
1292
1375
  getNgHref(route) {
1293
1376
  return route.createAbsoluteUrl(this.params());
1294
1377
  }
1295
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.0", ngImport: i0, type: RdtRouterLinkDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
1296
- static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "20.0.0", type: RdtRouterLinkDirective, isStandalone: true, selector: "[rdtRouterLink]", inputs: { routeInput: { classPropertyName: "routeInput", publicName: "rdtRouterLink", isSignal: true, isRequired: true, transformFunction: null }, target: { classPropertyName: "target", publicName: "target", isSignal: true, isRequired: false, transformFunction: null }, params: { classPropertyName: "params", publicName: "params", isSignal: true, isRequired: false, transformFunction: null }, queryParams: { classPropertyName: "queryParams", publicName: "queryParams", isSignal: true, isRequired: false, transformFunction: null }, stateParams: { classPropertyName: "stateParams", publicName: "stateParams", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "rdtDisabled", isSignal: true, isRequired: false, transformFunction: null } }, hostDirectives: [{ directive: i1.RouterLink, inputs: ["target", "target", "replaceUrl", "replaceUrl"] }], ngImport: i0 });
1378
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.2", ngImport: i0, type: RdtRouterLinkDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
1379
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.1.2", type: RdtRouterLinkDirective, isStandalone: true, selector: "[rdtRouterLink]", inputs: { routeInput: { classPropertyName: "routeInput", publicName: "rdtRouterLink", isSignal: true, isRequired: true, transformFunction: null }, target: { classPropertyName: "target", publicName: "target", isSignal: true, isRequired: false, transformFunction: null }, params: { classPropertyName: "params", publicName: "params", isSignal: true, isRequired: false, transformFunction: null }, queryParams: { classPropertyName: "queryParams", publicName: "queryParams", isSignal: true, isRequired: false, transformFunction: null }, stateParams: { classPropertyName: "stateParams", publicName: "stateParams", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "rdtDisabled", isSignal: true, isRequired: false, transformFunction: null } }, hostDirectives: [{ directive: i1.RouterLink, inputs: ["target", "target", "replaceUrl", "replaceUrl"] }], ngImport: i0 });
1297
1380
  }
1298
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.0", ngImport: i0, type: RdtRouterLinkDirective, decorators: [{
1381
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.2", ngImport: i0, type: RdtRouterLinkDirective, decorators: [{
1299
1382
  type: Directive,
1300
1383
  args: [{
1301
1384
  selector: '[rdtRouterLink]',
@@ -1307,19 +1390,33 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.0", ngImpor
1307
1390
  },
1308
1391
  ],
1309
1392
  }]
1310
- }] });
1393
+ }], propDecorators: { routeInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "rdtRouterLink", required: true }] }], target: [{ type: i0.Input, args: [{ isSignal: true, alias: "target", required: false }] }], params: [{ type: i0.Input, args: [{ isSignal: true, alias: "params", required: false }] }], queryParams: [{ type: i0.Input, args: [{ isSignal: true, alias: "queryParams", required: false }] }], stateParams: [{ type: i0.Input, args: [{ isSignal: true, alias: "stateParams", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "rdtDisabled", required: false }] }] } });
1311
1394
 
1312
- const preventDataLossGuardFn = () => {
1313
- const guardStore = inject(RdtComponentGuardStoreService);
1314
- return guardStore.checkCanLeaveGlobal$();
1395
+ /**
1396
+ * `CanDeactivate` guard that blocks navigation when there are unsaved changes.
1397
+ * The routed component must implement `RdtGuardedContainer` from `@ngrdt/core`.
1398
+ * Add to routes via `RdtAngularRoute.addCanDeactivate(preventDataLossGuardFn)`.
1399
+ */
1400
+ const preventDataLossGuardFn = (component) => {
1401
+ if (!component.guardRegistry.hasLeaveBlockers())
1402
+ return true;
1403
+ return component.guardRegistry.canLeaveAll();
1315
1404
  };
1316
1405
 
1406
+ /**
1407
+ * Ensures that `preventDataLossGuardFn` is applied to every route at runtime,
1408
+ * even if it was not explicitly added in the route definition.
1409
+ * Call `ensureGlobalGuards()` once during app initialization.
1410
+ *
1411
+ * Also patches the browser back button behavior: replaces the URL back to the
1412
+ * previous value during guard checks so the address bar doesn't flash the new URL
1413
+ * before the guard potentially rejects it.
1414
+ */
1317
1415
  class GlobalRouteGuardService {
1318
1416
  router;
1319
1417
  constructor(router) {
1320
1418
  this.router = router;
1321
1419
  }
1322
- // This will help ensure that routes are automatically protected by ensuring that global guards are applied to every route
1323
1420
  ensureGlobalGuards() {
1324
1421
  let lastUrl = '';
1325
1422
  this.router.events.subscribe((e) => {
@@ -1352,10 +1449,10 @@ class GlobalRouteGuardService {
1352
1449
  canDeactivateGuards.push(preventDataLossGuardFn);
1353
1450
  }
1354
1451
  }
1355
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.0", ngImport: i0, type: GlobalRouteGuardService, deps: [{ token: i1.Router }], target: i0.ɵɵFactoryTarget.Injectable });
1356
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.0.0", ngImport: i0, type: GlobalRouteGuardService, providedIn: 'root' });
1452
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.2", ngImport: i0, type: GlobalRouteGuardService, deps: [{ token: i1.Router }], target: i0.ɵɵFactoryTarget.Injectable });
1453
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.1.2", ngImport: i0, type: GlobalRouteGuardService, providedIn: 'root' });
1357
1454
  }
1358
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.0", ngImport: i0, type: GlobalRouteGuardService, decorators: [{
1455
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.2", ngImport: i0, type: GlobalRouteGuardService, decorators: [{
1359
1456
  type: Injectable,
1360
1457
  args: [{ providedIn: 'root' }]
1361
1458
  }], ctorParameters: () => [{ type: i1.Router }] });
@@ -1364,5 +1461,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.0", ngImpor
1364
1461
  * Generated bundle index. Do not edit.
1365
1462
  */
1366
1463
 
1367
- export { GlobalRouteGuardService, PERMISSION_DISABLED_KEY, RDT_CANNOT_BE_ENTERED_PROVIDER, RDT_CONFIRM_DATA_LOSS_SERVICE, RDT_ROUTES_PROVIDER, RdtAngularRoute, RdtAnyRouteActiveDirective, RdtBackLinkDirective, RdtConfirmDataLossService, RdtConfirmDataLossServiceAlert, RdtNavigationSource, RdtParameters, RdtRoute, RdtRouteBuilder, RdtRouterLinkDirective, RdtRouterService, preventDataLossGuardFn };
1464
+ export { GlobalRouteGuardService, PERMISSION_DISABLED_KEY, RDT_CANNOT_BE_ENTERED_PROVIDER, RDT_ROUTES_PROVIDER, RdtAngularRoute, RdtAnyRouteActiveDirective, RdtBackLinkDirective, RdtNavigationSource, RdtParameters, RdtRoute, RdtRouteBuilder, RdtRouterLinkDirective, RdtRouterService, preventDataLossGuardFn, provideRdtRoutes };
1368
1465
  //# sourceMappingURL=ngrdt-router.mjs.map