@mmstack/router-core 20.5.0 → 20.5.2
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.
- package/README.md +352 -206
- package/fesm2022/mmstack-router-core.mjs +309 -31
- package/fesm2022/mmstack-router-core.mjs.map +1 -1
- package/index.d.ts +218 -34
- package/package.json +1 -1
|
@@ -11,7 +11,7 @@ import { Title } from '@angular/platform-browser';
|
|
|
11
11
|
/**
|
|
12
12
|
* @internal
|
|
13
13
|
*/
|
|
14
|
-
const token$
|
|
14
|
+
const token$2 = new InjectionToken('@mmstack/router-core:breadcrumb-config');
|
|
15
15
|
/**
|
|
16
16
|
* Provides configuration for the breadcrumb system.
|
|
17
17
|
* @param config - A partial `BreadcrumbConfig` object with the desired settings. *
|
|
@@ -43,7 +43,7 @@ const token$1 = new InjectionToken('MMSTACK_BREADCRUMB_CONFIG');
|
|
|
43
43
|
*/
|
|
44
44
|
function provideBreadcrumbConfig(config) {
|
|
45
45
|
return {
|
|
46
|
-
provide: token$
|
|
46
|
+
provide: token$2,
|
|
47
47
|
useValue: {
|
|
48
48
|
...config,
|
|
49
49
|
},
|
|
@@ -53,7 +53,7 @@ function provideBreadcrumbConfig(config) {
|
|
|
53
53
|
* @internal
|
|
54
54
|
*/
|
|
55
55
|
function injectBreadcrumbConfig() {
|
|
56
|
-
return (inject(token$
|
|
56
|
+
return (inject(token$2, {
|
|
57
57
|
optional: true,
|
|
58
58
|
}) ?? {});
|
|
59
59
|
}
|
|
@@ -95,8 +95,9 @@ function isNavigationEnd(e) {
|
|
|
95
95
|
* }
|
|
96
96
|
* ```
|
|
97
97
|
*/
|
|
98
|
-
function url() {
|
|
99
|
-
|
|
98
|
+
function url(router) {
|
|
99
|
+
if (!router)
|
|
100
|
+
router = inject(Router);
|
|
100
101
|
return toSignal(router.events.pipe(filter(isNavigationEnd), map((e) => e.urlAfterRedirects)), {
|
|
101
102
|
initialValue: router.url,
|
|
102
103
|
});
|
|
@@ -515,10 +516,11 @@ function injectSnapshotPathResolver() {
|
|
|
515
516
|
/**
|
|
516
517
|
* Creates and registers a breadcrumb for a specific route.
|
|
517
518
|
* This function is designed to be used as an Angular Route `ResolveFn`.
|
|
518
|
-
* It handles the registration of the breadcrumb with the `BreadcrumbStore`
|
|
519
|
-
* and ensures automatic deregistration when the route is destroyed.
|
|
520
519
|
*
|
|
521
|
-
*
|
|
520
|
+
* Accepts a static label (`string`), a static options object, or a factory returning either —
|
|
521
|
+
* use a factory when you need `inject()` for dynamic data.
|
|
522
|
+
*
|
|
523
|
+
* @param factoryOrValue A static label, a static `CreateBreadcrumbOptions`, or a factory returning either.
|
|
522
524
|
* @see CreateBreadcrumbOptions
|
|
523
525
|
*
|
|
524
526
|
* @example
|
|
@@ -528,25 +530,34 @@ function injectSnapshotPathResolver() {
|
|
|
528
530
|
* path: 'home',
|
|
529
531
|
* component: HomeComponent,
|
|
530
532
|
* resolve: {
|
|
531
|
-
*
|
|
532
|
-
*
|
|
533
|
-
* });
|
|
533
|
+
* // shorthand for { label: 'Home' }
|
|
534
|
+
* breadcrumb: createBreadcrumb('Home'),
|
|
534
535
|
* },
|
|
536
|
+
* },
|
|
537
|
+
* {
|
|
535
538
|
* path: 'users/:userId',
|
|
536
539
|
* component: UserProfileComponent,
|
|
537
540
|
* resolve: {
|
|
538
541
|
* breadcrumb: createBreadcrumb(() => {
|
|
539
542
|
* const userStore = inject(UserStore);
|
|
540
543
|
* return {
|
|
541
|
-
*
|
|
542
|
-
*
|
|
543
|
-
*
|
|
544
|
+
* label: () => userStore.user().name ?? 'Loading...',
|
|
545
|
+
* };
|
|
546
|
+
* }),
|
|
544
547
|
* },
|
|
545
|
-
* }
|
|
548
|
+
* },
|
|
546
549
|
* ];
|
|
547
550
|
* ```
|
|
548
551
|
*/
|
|
549
|
-
function createBreadcrumb(
|
|
552
|
+
function createBreadcrumb(factoryOrValue) {
|
|
553
|
+
const factory = typeof factoryOrValue === 'string'
|
|
554
|
+
? () => ({ label: factoryOrValue })
|
|
555
|
+
: typeof factoryOrValue === 'function'
|
|
556
|
+
? () => {
|
|
557
|
+
const result = factoryOrValue();
|
|
558
|
+
return typeof result === 'string' ? { label: result } : result;
|
|
559
|
+
}
|
|
560
|
+
: () => factoryOrValue;
|
|
550
561
|
return async (route) => {
|
|
551
562
|
const router = inject(Router);
|
|
552
563
|
const store = inject(BreadcrumbStore);
|
|
@@ -650,6 +661,38 @@ function treeToSerializedUrl(router, urlTree) {
|
|
|
650
661
|
return null;
|
|
651
662
|
return router.serializeUrl(urlTree);
|
|
652
663
|
}
|
|
664
|
+
/**
|
|
665
|
+
* Returns an imperative function that triggers preloading for an arbitrary link, using
|
|
666
|
+
* the same path resolution and {@link PreloadStrategy} pipeline as the {@link Link}
|
|
667
|
+
* (`mmLink`) directive.
|
|
668
|
+
*
|
|
669
|
+
* Use this when the `Link` directive isn't a fit — for example, preloading a route from
|
|
670
|
+
* an effect when a user opens a menu, hovers a non-link element, or reacts to a signal
|
|
671
|
+
* change — and you don't want to render an `<a [mmLink]>` just to request the preload.
|
|
672
|
+
*
|
|
673
|
+
* Requires {@link PreloadStrategy} to be wired up via `provideRouter(routes, withPreloading(PreloadStrategy))`,
|
|
674
|
+
* just like the directive.
|
|
675
|
+
*
|
|
676
|
+
* @returns A function accepting the same link descriptor shape as `mmLink` (`string`,
|
|
677
|
+
* commands array, `UrlTree`, or `null`). Passing `null` or an unresolvable link is a no-op.
|
|
678
|
+
*
|
|
679
|
+
* @example
|
|
680
|
+
* ```typescript
|
|
681
|
+
* @Component({ ... })
|
|
682
|
+
* export class CommandPaletteComponent {
|
|
683
|
+
* private readonly triggerPreload = injectTriggerPreload();
|
|
684
|
+
*
|
|
685
|
+
* protected readonly highlighted = signal<string | null>(null);
|
|
686
|
+
*
|
|
687
|
+
* constructor() {
|
|
688
|
+
* effect(() => {
|
|
689
|
+
* const target = this.highlighted();
|
|
690
|
+
* if (target) this.triggerPreload(target);
|
|
691
|
+
* });
|
|
692
|
+
* }
|
|
693
|
+
* }
|
|
694
|
+
* ```
|
|
695
|
+
*/
|
|
653
696
|
function injectTriggerPreload() {
|
|
654
697
|
const req = inject(PreloadRequester);
|
|
655
698
|
const router = inject(Router);
|
|
@@ -793,6 +836,229 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
|
|
|
793
836
|
]]
|
|
794
837
|
}] } });
|
|
795
838
|
|
|
839
|
+
/** @internal */
|
|
840
|
+
const token$1 = new InjectionToken('@mmstack/router-core:nav-config');
|
|
841
|
+
/**
|
|
842
|
+
* Provides global configuration for the nav system.
|
|
843
|
+
*
|
|
844
|
+
* @example
|
|
845
|
+
* ```typescript
|
|
846
|
+
* provideNavConfig({
|
|
847
|
+
* activeMatch: { queryParams: 'ignored' },
|
|
848
|
+
* }),
|
|
849
|
+
* ```
|
|
850
|
+
*/
|
|
851
|
+
function provideNavConfig(config) {
|
|
852
|
+
return {
|
|
853
|
+
provide: token$1,
|
|
854
|
+
useValue: { ...config },
|
|
855
|
+
};
|
|
856
|
+
}
|
|
857
|
+
/** @internal */
|
|
858
|
+
function injectNavConfig() {
|
|
859
|
+
return inject(token$1, { optional: true }) ?? {};
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
/** @internal */
|
|
863
|
+
const NEVER_TRUE = computed(() => false, ...(ngDevMode ? [{ debugName: "NEVER_TRUE" }] : []));
|
|
864
|
+
function wrap(value, fallback) {
|
|
865
|
+
if (value === undefined)
|
|
866
|
+
return fallback ? fallback : computed(() => undefined);
|
|
867
|
+
if (typeof value === 'function')
|
|
868
|
+
return isSignal(value) ? value : computed(value);
|
|
869
|
+
return computed(() => value);
|
|
870
|
+
}
|
|
871
|
+
function isAbsoluteCommandArray(commands) {
|
|
872
|
+
return (commands.length > 0 &&
|
|
873
|
+
typeof commands[0] === 'string' &&
|
|
874
|
+
commands[0].startsWith('/'));
|
|
875
|
+
}
|
|
876
|
+
function resolveLinkTree(input, router, relativeTo) {
|
|
877
|
+
const raw = typeof input === 'function' ? input() : input;
|
|
878
|
+
if (raw === undefined || raw === null)
|
|
879
|
+
return null;
|
|
880
|
+
if (raw instanceof UrlTree)
|
|
881
|
+
return raw;
|
|
882
|
+
if (typeof raw === 'string') {
|
|
883
|
+
if (raw.startsWith('/'))
|
|
884
|
+
return router.parseUrl(raw);
|
|
885
|
+
const parsed = router.parseUrl('/' + raw);
|
|
886
|
+
const primary = parsed.root.children['primary'];
|
|
887
|
+
const segments = primary ? primary.segments.map((s) => s.path) : [];
|
|
888
|
+
return createUrlTreeFromSnapshot(relativeTo, segments, parsed.queryParams, parsed.fragment);
|
|
889
|
+
}
|
|
890
|
+
if (isAbsoluteCommandArray(raw))
|
|
891
|
+
return router.createUrlTree(raw);
|
|
892
|
+
return createUrlTreeFromSnapshot(relativeTo, raw);
|
|
893
|
+
}
|
|
894
|
+
function resolveMeta(input) {
|
|
895
|
+
if (input === undefined)
|
|
896
|
+
return {};
|
|
897
|
+
return typeof input === 'function' ? input() : input;
|
|
898
|
+
}
|
|
899
|
+
/**
|
|
900
|
+
* @internal
|
|
901
|
+
* Recursively builds an {@link InternalNavItem} tree from {@link CreateNavItem} input.
|
|
902
|
+
* Cascades parent `disabled`/`hidden` to descendants and computes `active` against the
|
|
903
|
+
* current router URL using `Router.isActive`.
|
|
904
|
+
*/
|
|
905
|
+
function createInternalNavItem(input, router, relativeTo, configActiveMatch, parentDisabled, parentHidden, fallbackId) {
|
|
906
|
+
const label = wrap(input.label);
|
|
907
|
+
const ariaLabel = input.ariaLabel ? wrap(input.ariaLabel) : label;
|
|
908
|
+
const linkTree = computed(() => resolveLinkTree(input.link, router, relativeTo), ...(ngDevMode ? [{ debugName: "linkTree" }] : []));
|
|
909
|
+
const link = computed(() => {
|
|
910
|
+
const tree = linkTree();
|
|
911
|
+
return tree ? router.serializeUrl(tree) : null;
|
|
912
|
+
}, ...(ngDevMode ? [{ debugName: "link" }] : []));
|
|
913
|
+
const ownDisabled = wrap(input.disabled, NEVER_TRUE);
|
|
914
|
+
const ownHidden = wrap(input.hidden, NEVER_TRUE);
|
|
915
|
+
const disabled = computed(() => parentDisabled() || ownDisabled(), ...(ngDevMode ? [{ debugName: "disabled" }] : []));
|
|
916
|
+
const hidden = computed(() => parentHidden() || ownHidden(), ...(ngDevMode ? [{ debugName: "hidden" }] : []));
|
|
917
|
+
const metaInput = input.meta;
|
|
918
|
+
const meta = computed(() => resolveMeta(metaInput), ...(ngDevMode ? [{ debugName: "meta" }] : []));
|
|
919
|
+
const id = input.id !== undefined
|
|
920
|
+
? wrap(input.id)
|
|
921
|
+
: computed(() => link() ?? fallbackId);
|
|
922
|
+
const childItems = (input.children ?? []).map((childInput, i) => createInternalNavItem(childInput, router, relativeTo, configActiveMatch, disabled, hidden, `${fallbackId}.${i}`));
|
|
923
|
+
const children = computed(() => childItems.filter((c) => !c.hidden()), ...(ngDevMode ? [{ debugName: "children" }] : []));
|
|
924
|
+
const mergedActiveMatch = {
|
|
925
|
+
...configActiveMatch,
|
|
926
|
+
...input.activeMatch,
|
|
927
|
+
};
|
|
928
|
+
const trackNavigation = url(router);
|
|
929
|
+
const finalOptions = {
|
|
930
|
+
paths: 'subset',
|
|
931
|
+
fragment: 'ignored',
|
|
932
|
+
matrixParams: 'ignored',
|
|
933
|
+
queryParams: 'subset',
|
|
934
|
+
...mergedActiveMatch,
|
|
935
|
+
};
|
|
936
|
+
const ownActive = computed(() => {
|
|
937
|
+
trackNavigation();
|
|
938
|
+
const tree = linkTree();
|
|
939
|
+
return tree ? router.isActive(tree, finalOptions) : false;
|
|
940
|
+
}, ...(ngDevMode ? [{ debugName: "ownActive" }] : []));
|
|
941
|
+
const orWithChildren = input.matchesWhenChildActive ?? input.activeMatch === undefined;
|
|
942
|
+
const active = computed(() => ownActive() ||
|
|
943
|
+
(orWithChildren &&
|
|
944
|
+
!!childItems.length &&
|
|
945
|
+
childItems.some((c) => c.active())), ...(ngDevMode ? [{ debugName: "active" }] : []));
|
|
946
|
+
return {
|
|
947
|
+
id,
|
|
948
|
+
label,
|
|
949
|
+
ariaLabel,
|
|
950
|
+
link,
|
|
951
|
+
active,
|
|
952
|
+
disabled,
|
|
953
|
+
hidden,
|
|
954
|
+
meta,
|
|
955
|
+
children,
|
|
956
|
+
};
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
/** @internal */
|
|
960
|
+
const DEFAULT_NAV_SCOPE = Symbol('mmstack.nav.default');
|
|
961
|
+
class NavStore {
|
|
962
|
+
map = mutable(new Map());
|
|
963
|
+
leafRoutes = injectLeafRoutes();
|
|
964
|
+
/** @internal */
|
|
965
|
+
register(scope, routePath, items) {
|
|
966
|
+
this.map.inline((m) => {
|
|
967
|
+
let scopeMap = m.get(scope);
|
|
968
|
+
if (!scopeMap) {
|
|
969
|
+
scopeMap = new Map();
|
|
970
|
+
m.set(scope, scopeMap);
|
|
971
|
+
}
|
|
972
|
+
scopeMap.set(routePath, items);
|
|
973
|
+
});
|
|
974
|
+
}
|
|
975
|
+
/** @internal */
|
|
976
|
+
scope(name) {
|
|
977
|
+
return computed(() => {
|
|
978
|
+
const scopeMap = this.map().get(name);
|
|
979
|
+
if (!scopeMap)
|
|
980
|
+
return [];
|
|
981
|
+
const leaves = this.leafRoutes();
|
|
982
|
+
for (let i = leaves.length - 1; i >= 0; i--) {
|
|
983
|
+
const items = scopeMap.get(leaves[i].path);
|
|
984
|
+
if (items) {
|
|
985
|
+
return items.filter((it) => !it.hidden());
|
|
986
|
+
}
|
|
987
|
+
}
|
|
988
|
+
return [];
|
|
989
|
+
});
|
|
990
|
+
}
|
|
991
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: NavStore, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
992
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: NavStore, providedIn: 'root' });
|
|
993
|
+
}
|
|
994
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: NavStore, decorators: [{
|
|
995
|
+
type: Injectable,
|
|
996
|
+
args: [{ providedIn: 'root' }]
|
|
997
|
+
}] });
|
|
998
|
+
/**
|
|
999
|
+
* Returns a reactive list of nav items for the requested scope.
|
|
1000
|
+
*
|
|
1001
|
+
* The returned signal reflects the nearest active ancestor route that registered items
|
|
1002
|
+
* for `name` via `createNavItems`. Hidden items are filtered out.
|
|
1003
|
+
*
|
|
1004
|
+
* @typeParam TMeta The shape of `NavItem.meta` for the consuming code. Untyped at the
|
|
1005
|
+
* registration site — this is a consumer-side assertion.
|
|
1006
|
+
*
|
|
1007
|
+
* @example
|
|
1008
|
+
* ```typescript
|
|
1009
|
+
* @Component({
|
|
1010
|
+
* template: `
|
|
1011
|
+
* @for (item of items(); track item) {
|
|
1012
|
+
* <a [href]="item.link()" [class.active]="item.active()">{{ item.label() }}</a>
|
|
1013
|
+
* }
|
|
1014
|
+
* `,
|
|
1015
|
+
* })
|
|
1016
|
+
* export class TopBar {
|
|
1017
|
+
* protected readonly items = injectNavItems();
|
|
1018
|
+
* }
|
|
1019
|
+
* ```
|
|
1020
|
+
*/
|
|
1021
|
+
function injectNavItems(name) {
|
|
1022
|
+
const store = inject(NavStore);
|
|
1023
|
+
return store.scope(name ?? DEFAULT_NAV_SCOPE);
|
|
1024
|
+
}
|
|
1025
|
+
|
|
1026
|
+
/**
|
|
1027
|
+
* Registers a set of nav items for the activating route under the given scope.
|
|
1028
|
+
* Mirrors `createBreadcrumb` / `createTitle` — designed to be used in a route's
|
|
1029
|
+
* `resolve` map.
|
|
1030
|
+
*
|
|
1031
|
+
* Multiple scopes can be registered on a single route by giving each its own `name`
|
|
1032
|
+
* (and a unique key in the `resolve` map):
|
|
1033
|
+
*
|
|
1034
|
+
* ```typescript
|
|
1035
|
+
* resolve: {
|
|
1036
|
+
* mainNav: createNavItems([...], { name: 'main' }),
|
|
1037
|
+
* sideNav: createNavItems([...], { name: 'side' }),
|
|
1038
|
+
* }
|
|
1039
|
+
* ```
|
|
1040
|
+
*
|
|
1041
|
+
* Scope override semantics: when multiple routes in the active chain register items
|
|
1042
|
+
* under the same scope, the deepest active registration wins. Navigating away restores
|
|
1043
|
+
* the shallower registration.
|
|
1044
|
+
*/
|
|
1045
|
+
function createNavItems(itemsOrFactory, options) {
|
|
1046
|
+
const factory = typeof itemsOrFactory === 'function'
|
|
1047
|
+
? itemsOrFactory
|
|
1048
|
+
: () => itemsOrFactory;
|
|
1049
|
+
return async (route) => {
|
|
1050
|
+
const router = inject(Router);
|
|
1051
|
+
const store = inject(NavStore);
|
|
1052
|
+
const resolveRoutePath = injectSnapshotPathResolver();
|
|
1053
|
+
const config = injectNavConfig();
|
|
1054
|
+
const routePath = resolveRoutePath(route);
|
|
1055
|
+
const scope = options?.name ?? DEFAULT_NAV_SCOPE;
|
|
1056
|
+
const items = factory().map((input, i) => createInternalNavItem(input, router, route, config.activeMatch, NEVER_TRUE, NEVER_TRUE, `${routePath}#${i}`));
|
|
1057
|
+
store.register(scope, routePath, items);
|
|
1058
|
+
return Promise.resolve();
|
|
1059
|
+
};
|
|
1060
|
+
}
|
|
1061
|
+
|
|
796
1062
|
/**
|
|
797
1063
|
* Creates a WritableSignal that synchronizes with a specific URL query parameter,
|
|
798
1064
|
* enabling two-way binding between the signal's state and the URL.
|
|
@@ -824,7 +1090,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
|
|
|
824
1090
|
*
|
|
825
1091
|
* @Component({
|
|
826
1092
|
* selector: 'app-product-list',
|
|
827
|
-
* standalone: true,
|
|
828
1093
|
* // imports: [FormsModule], // If using ngModel
|
|
829
1094
|
* template: `
|
|
830
1095
|
* <div>
|
|
@@ -903,7 +1168,7 @@ function queryParam(key, route = inject(ActivatedRoute)) {
|
|
|
903
1168
|
return toWritable(queryParam, set);
|
|
904
1169
|
}
|
|
905
1170
|
|
|
906
|
-
const token = new InjectionToken('
|
|
1171
|
+
const token = new InjectionToken('@mmstack/router-core:title-config');
|
|
907
1172
|
/**
|
|
908
1173
|
* used to provide the title configuration, will not be applied unless a `createTitle` resolver is used
|
|
909
1174
|
*/
|
|
@@ -915,29 +1180,41 @@ function provideTitleConfig(config) {
|
|
|
915
1180
|
return {
|
|
916
1181
|
provide: token,
|
|
917
1182
|
useValue: {
|
|
1183
|
+
initialTitle: config?.initialTitle ?? '',
|
|
918
1184
|
parser: prefixFn,
|
|
919
1185
|
keepLastKnown: config?.keepLastKnownTitle ?? true,
|
|
920
1186
|
},
|
|
921
1187
|
};
|
|
922
1188
|
}
|
|
1189
|
+
function identity(str) {
|
|
1190
|
+
return str;
|
|
1191
|
+
}
|
|
923
1192
|
function injectTitleConfig() {
|
|
924
|
-
return inject(token
|
|
1193
|
+
return (inject(token, {
|
|
1194
|
+
optional: true,
|
|
1195
|
+
}) ?? {
|
|
1196
|
+
initialTitle: '',
|
|
1197
|
+
parser: identity,
|
|
1198
|
+
keepLastKnown: true,
|
|
1199
|
+
});
|
|
925
1200
|
}
|
|
926
1201
|
|
|
927
1202
|
class TitleStore {
|
|
928
|
-
title = inject(Title);
|
|
929
1203
|
map = mutable(new Map());
|
|
930
|
-
leafRoutes = injectLeafRoutes();
|
|
931
1204
|
constructor() {
|
|
932
|
-
const
|
|
1205
|
+
const { keepLastKnown, initialTitle } = injectTitleConfig();
|
|
1206
|
+
const leafRoutes = injectLeafRoutes();
|
|
1207
|
+
const title = inject(Title);
|
|
1208
|
+
const fallbackTitle = initialTitle || untracked(() => title.getTitle());
|
|
1209
|
+
const reverseLeaves = computed(() => leafRoutes().toReversed(), ...(ngDevMode ? [{ debugName: "reverseLeaves" }] : []));
|
|
933
1210
|
const currentResolvedTitles = computed(() => {
|
|
934
1211
|
const map = this.map();
|
|
935
1212
|
return reverseLeaves()
|
|
936
|
-
.map((leaf) => map.get(leaf.path)?.() ?? leaf.route.title)
|
|
937
|
-
.filter((v) =>
|
|
1213
|
+
.map((leaf) => map.get(leaf.path)?.() ?? leaf.route.title ?? null)
|
|
1214
|
+
.filter((v) => v !== null);
|
|
938
1215
|
}, ...(ngDevMode ? [{ debugName: "currentResolvedTitles" }] : []));
|
|
939
1216
|
const currentTitle = computed(() => currentResolvedTitles().at(0) ?? '', ...(ngDevMode ? [{ debugName: "currentTitle" }] : []));
|
|
940
|
-
const heldTitle =
|
|
1217
|
+
const heldTitle = keepLastKnown
|
|
941
1218
|
? linkedSignal({
|
|
942
1219
|
source: () => currentTitle(),
|
|
943
1220
|
computation: (value, prev) => {
|
|
@@ -948,7 +1225,7 @@ class TitleStore {
|
|
|
948
1225
|
})
|
|
949
1226
|
: currentTitle;
|
|
950
1227
|
effect(() => {
|
|
951
|
-
|
|
1228
|
+
title.setTitle(heldTitle() || fallbackTitle);
|
|
952
1229
|
});
|
|
953
1230
|
}
|
|
954
1231
|
register(id, titleFn) {
|
|
@@ -967,19 +1244,20 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
|
|
|
967
1244
|
*
|
|
968
1245
|
* Creates a title resolver function that can be used in Angular's router.
|
|
969
1246
|
*
|
|
970
|
-
* @param
|
|
971
|
-
* A function that returns a string or a Signal<string> representing the title.
|
|
1247
|
+
* @param factoryOrValue
|
|
1248
|
+
* A function that returns a string or a Signal<string> representing the title or just the string directly.
|
|
972
1249
|
* @param awaitValue
|
|
973
1250
|
* If `true`, the resolver will wait until the title signal has a value before resolving.
|
|
974
1251
|
* Defaults to `false`.
|
|
975
1252
|
*/
|
|
976
|
-
function createTitle(
|
|
1253
|
+
function createTitle(factoryOrValue, awaitValue = false) {
|
|
1254
|
+
const factory = typeof factoryOrValue === 'string' ? () => factoryOrValue : factoryOrValue;
|
|
977
1255
|
return async (route) => {
|
|
978
1256
|
const store = inject(TitleStore);
|
|
979
1257
|
const resolver = injectSnapshotPathResolver();
|
|
980
1258
|
const fp = resolver(route);
|
|
981
1259
|
const { parser } = injectTitleConfig();
|
|
982
|
-
const resolved =
|
|
1260
|
+
const resolved = factory();
|
|
983
1261
|
const titleSignal = typeof resolved === 'string'
|
|
984
1262
|
? computed(() => resolved)
|
|
985
1263
|
: computed(resolved);
|
|
@@ -995,5 +1273,5 @@ function createTitle(fn, awaitValue = false) {
|
|
|
995
1273
|
* Generated bundle index. Do not edit.
|
|
996
1274
|
*/
|
|
997
1275
|
|
|
998
|
-
export { Link, PreloadStrategy, createBreadcrumb, createTitle, injectBreadcrumbs, injectTriggerPreload, provideBreadcrumbConfig, provideMMLinkDefaultConfig, provideTitleConfig, queryParam, url };
|
|
1276
|
+
export { Link, PreloadStrategy, createBreadcrumb, createNavItems, createTitle, injectBreadcrumbs, injectNavItems, injectTriggerPreload, provideBreadcrumbConfig, provideMMLinkDefaultConfig, provideNavConfig, provideTitleConfig, queryParam, url };
|
|
999
1277
|
//# sourceMappingURL=mmstack-router-core.mjs.map
|