@ngrdt/router 0.0.98 → 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
  }
@@ -850,6 +709,14 @@ class RdtRoute extends RdtRouteBase {
850
709
  clone._queryParams = { ...this._queryParams };
851
710
  return clone;
852
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
+ }
853
720
  setRegex() {
854
721
  const params = Object.keys(this._paramMap);
855
722
  params.sort((a, b) => b.length - a.length);
@@ -859,7 +726,7 @@ class RdtRoute extends RdtRouteBase {
859
726
  if (!type) {
860
727
  throw new Error('Params is not set i');
861
728
  }
862
- substituted = substituted.replace(`:${p}`, RdtRoute.groups[type]);
729
+ substituted = substituted.replace(`:${p}`, RdtRoute.getGroup(type));
863
730
  });
864
731
  if (substituted === '') {
865
732
  this._regex = null;
@@ -885,12 +752,228 @@ class RdtRoute extends RdtRouteBase {
885
752
  return route;
886
753
  }
887
754
  static groups = {
888
- string: '([\\w_\\-]+)',
889
- number: '(\\d+)',
755
+ string: '([^/]+)',
756
+ number: '(-?\\d+)',
890
757
  };
891
758
  }
892
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
+
893
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
+ */
894
977
  class RdtRouteBuilder extends RdtRouteBase {
895
978
  get canBeEntered() {
896
979
  return this._canBeEntered;
@@ -978,8 +1061,8 @@ class RdtRouteBuilder extends RdtRouteBase {
978
1061
  }
979
1062
  /**
980
1063
  * Defines parameter type and lets framework parse it.
981
- * @param paramName
982
- * @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).
983
1066
  */
984
1067
  withParam(paramName, type) {
985
1068
  this._paramMap[paramName] = type;
@@ -1124,10 +1207,10 @@ class RdtAnyRouteActiveDirective {
1124
1207
  return input;
1125
1208
  }
1126
1209
  }
1127
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.0", ngImport: i0, type: RdtAnyRouteActiveDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
1128
- 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 });
1129
1212
  }
1130
- 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: [{
1131
1214
  type: Directive,
1132
1215
  args: [{
1133
1216
  selector: '[rdtAnyRouteActive]',
@@ -1167,10 +1250,10 @@ class RdtBackLinkDirective {
1167
1250
  }
1168
1251
  });
1169
1252
  }
1170
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.0", ngImport: i0, type: RdtBackLinkDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
1171
- 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 });
1172
1255
  }
1173
- 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: [{
1174
1257
  type: Directive,
1175
1258
  args: [{
1176
1259
  selector: '[rdtBackLink]',
@@ -1186,16 +1269,14 @@ class RdtRouterLinkDirective {
1186
1269
  });
1187
1270
  routerLinkRef = inject(RouterLink, { self: true });
1188
1271
  envInjector = inject(EnvironmentInjector);
1189
- routeInput = input.required({ alias: 'rdtRouterLink' });
1190
- route = linkedSignal(() => this.routeInput());
1191
- target = input('_self');
1192
- params = input();
1193
- queryParams = input();
1194
- stateParams = input();
1195
- disabled = input(false, {
1196
- alias: 'rdtDisabled',
1197
- transform: booleanAttribute,
1198
- });
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 });
1199
1280
  canBeEntered = computed(() => {
1200
1281
  const route = this.route();
1201
1282
  const disabled = this.disabled();
@@ -1208,7 +1289,7 @@ class RdtRouterLinkDirective {
1208
1289
  state: this.stateParams(),
1209
1290
  };
1210
1291
  return route.canBeEntered(this.envInjector, combinedParams);
1211
- });
1292
+ }, ...(ngDevMode ? [{ debugName: "canBeEntered" }] : []));
1212
1293
  updateEffect = effect(() => {
1213
1294
  if (this.buttonRef) {
1214
1295
  this.updateButton();
@@ -1216,7 +1297,7 @@ class RdtRouterLinkDirective {
1216
1297
  else {
1217
1298
  this.updateRouterLink();
1218
1299
  }
1219
- });
1300
+ }, ...(ngDevMode ? [{ debugName: "updateEffect" }] : []));
1220
1301
  updateButton() {
1221
1302
  const route = this.route();
1222
1303
  if (!route) {
@@ -1294,10 +1375,10 @@ class RdtRouterLinkDirective {
1294
1375
  getNgHref(route) {
1295
1376
  return route.createAbsoluteUrl(this.params());
1296
1377
  }
1297
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.0", ngImport: i0, type: RdtRouterLinkDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
1298
- 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 });
1299
1380
  }
1300
- 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: [{
1301
1382
  type: Directive,
1302
1383
  args: [{
1303
1384
  selector: '[rdtRouterLink]',
@@ -1309,19 +1390,33 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.0", ngImpor
1309
1390
  },
1310
1391
  ],
1311
1392
  }]
1312
- }] });
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 }] }] } });
1313
1394
 
1314
- const preventDataLossGuardFn = () => {
1315
- const guardStore = inject(RdtComponentGuardStoreService);
1316
- 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();
1317
1404
  };
1318
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
+ */
1319
1415
  class GlobalRouteGuardService {
1320
1416
  router;
1321
1417
  constructor(router) {
1322
1418
  this.router = router;
1323
1419
  }
1324
- // This will help ensure that routes are automatically protected by ensuring that global guards are applied to every route
1325
1420
  ensureGlobalGuards() {
1326
1421
  let lastUrl = '';
1327
1422
  this.router.events.subscribe((e) => {
@@ -1354,10 +1449,10 @@ class GlobalRouteGuardService {
1354
1449
  canDeactivateGuards.push(preventDataLossGuardFn);
1355
1450
  }
1356
1451
  }
1357
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.0", ngImport: i0, type: GlobalRouteGuardService, deps: [{ token: i1.Router }], target: i0.ɵɵFactoryTarget.Injectable });
1358
- 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' });
1359
1454
  }
1360
- 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: [{
1361
1456
  type: Injectable,
1362
1457
  args: [{ providedIn: 'root' }]
1363
1458
  }], ctorParameters: () => [{ type: i1.Router }] });
@@ -1366,5 +1461,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.0", ngImpor
1366
1461
  * Generated bundle index. Do not edit.
1367
1462
  */
1368
1463
 
1369
- 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 };
1370
1465
  //# sourceMappingURL=ngrdt-router.mjs.map