@mmstack/router-core 19.3.16 → 19.3.17

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.
@@ -21,17 +21,24 @@ type CreateBreadcrumbOptions = {
21
21
  awaitValue?: boolean;
22
22
  };
23
23
  /**
24
- * Creates and registers a breadcrumb for a specific route.
25
- * This function is designed to be used as an Angular Route `ResolveFn`.
24
+ * Creates and registers a breadcrumb for a specific route. Designed to be used
25
+ * as an Angular Route `ResolveFn` in the route's `resolve` map.
26
26
  *
27
- * Accepts a static label (`string`), a static options object, or a factory returning either —
28
- * use a factory when you need `inject()` for dynamic data.
27
+ * Accepts a static label, a static options object, or a factory returning
28
+ * either — use a factory when you need `inject()` to read dynamic data.
29
+ *
30
+ * @param factoryOrValue One of: a literal label string (shorthand for
31
+ * `{ label: <string> }`), a static {@link CreateBreadcrumbOptions} object,
32
+ * or a factory `() => string | CreateBreadcrumbOptions` invoked inside an
33
+ * injection context (so it can use `inject()`).
34
+ * @returns An Angular `ResolveFn<void>` to wire into a route's `resolve` map.
35
+ * The resolver registers the breadcrumb as a side effect; the resolved value
36
+ * itself is unused.
29
37
  *
30
- * @param factoryOrValue A static label, a static `CreateBreadcrumbOptions`, or a factory returning either.
31
38
  * @see CreateBreadcrumbOptions
32
39
  *
33
40
  * @example
34
- * ```typescript
41
+ * ```ts
35
42
  * export const appRoutes: Routes = [
36
43
  * {
37
44
  * path: 'home',
@@ -48,7 +55,7 @@ type CreateBreadcrumbOptions = {
48
55
  * breadcrumb: createBreadcrumb(() => {
49
56
  * const userStore = inject(UserStore);
50
57
  * return {
51
- * label: () => userStore.user().name ?? 'Loading...',
58
+ * label: () => userStore.user().name ?? 'Loading',
52
59
  * };
53
60
  * }),
54
61
  * },
package/lib/link.d.ts CHANGED
@@ -51,7 +51,56 @@ type MMLinkConfig = {
51
51
  */
52
52
  useMouseDown: boolean;
53
53
  };
54
+ /**
55
+ * Provide application-wide defaults for the `mmLink` directive. Each `[mmLink]`
56
+ * instance can still override per-link via its own `preloadOn` / `useMouseDown`
57
+ * inputs; this just shifts the default.
58
+ *
59
+ * @param config Partial override of `MMLinkConfig`. Unset keys fall back to:
60
+ * - `preloadOn: 'hover'` — preload triggered when the user hovers a link
61
+ * - `useMouseDown: false` — navigation triggered on click (not mousedown)
62
+ * @returns A `Provider` to add to your app's providers array.
63
+ *
64
+ * @example
65
+ * ```ts
66
+ * bootstrapApplication(AppComponent, {
67
+ * providers: [
68
+ * provideMMLinkDefaultConfig({ preloadOn: 'visible', useMouseDown: true }),
69
+ * ],
70
+ * });
71
+ * ```
72
+ */
54
73
  export declare function provideMMLinkDefaultConfig(config: Partial<MMLinkConfig>): Provider;
74
+ /**
75
+ * Drop-in replacement for `[routerLink]` that adds preloading on hover or
76
+ * visibility, optional mousedown-triggered navigation, and a `beforeNavigate`
77
+ * hook. Composes with Angular's `RouterLink` via `hostDirectives`, so every
78
+ * `RouterLink` input (`target`, `queryParams`, `fragment`, etc.) is forwarded.
79
+ *
80
+ * Preload behavior:
81
+ * - `preloadOn: 'hover'` (default) — preload when the user hovers the link
82
+ * - `preloadOn: 'visible'` — preload when the link scrolls into view
83
+ * - `preloadOn: null` — disable preloading on this link
84
+ *
85
+ * Navigation timing:
86
+ * - `useMouseDown: false` (default) — navigate on click
87
+ * - `useMouseDown: true` — navigate on mousedown (shaves ~50ms but breaks if the user
88
+ * moves off the link before mouseup)
89
+ *
90
+ * Requires {@link PreloadStrategy} to be wired via `provideRouter(routes, withComponentInputBinding(), withPreloading(PreloadStrategy))`.
91
+ * Set app-wide defaults with {@link provideMMLinkDefaultConfig}.
92
+ *
93
+ * @example
94
+ * ```html
95
+ * <a [mmLink]="['/users', userId()]">View profile</a>
96
+ *
97
+ * <!-- Override per-link -->
98
+ * <a [mmLink]="'/heavy-page'" preloadOn="visible" useMouseDown>Heavy page</a>
99
+ *
100
+ * <!-- React to the preload starting -->
101
+ * <a [mmLink]="'/checkout'" (preloading)="onPreload()">Checkout</a>
102
+ * ```
103
+ */
55
104
  export declare class Link {
56
105
  private readonly routerLink;
57
106
  private readonly req;
@@ -30,17 +30,43 @@ export type NavConfig<TMeta = Record<string, unknown>> = {
30
30
  defaults?: NavDefaultsForScope<TMeta> | Record<string, NavDefaultsForScope<TMeta>>;
31
31
  };
32
32
  /**
33
- * Provides global configuration for the nav system.
33
+ * Provides global configuration for the nav system. The factory form runs in
34
+ * an injection context, so it can use `inject()` to build defaults from app
35
+ * state.
36
+ *
37
+ * @param config Either a literal {@link NavConfig} object or a factory
38
+ * `() => NavConfig`. Optional — without it, the nav system uses Angular's
39
+ * default `activeMatch` options and renders nothing in scopes that have no
40
+ * registered items.
41
+ * @returns A `Provider` to add to your app's providers array.
34
42
  *
35
43
  * @example
36
- * ```typescript
37
- * provideNavConfig({
38
- * activeMatch: { queryParams: 'ignored' },
39
- * defaults: [
40
- * { label: 'Home', link: '/' },
41
- * { label: 'Docs', link: '/docs' },
44
+ * ```ts
45
+ * bootstrapApplication(AppComponent, {
46
+ * providers: [
47
+ * provideRouter(routes),
48
+ * provideNavConfig({
49
+ * activeMatch: { queryParams: 'ignored' },
50
+ * defaults: [
51
+ * { label: 'Home', link: '/' },
52
+ * { label: 'Docs', link: '/docs' },
53
+ * ],
54
+ * }),
42
55
  * ],
43
- * }),
56
+ * });
57
+ * ```
58
+ *
59
+ * @example
60
+ * ```ts
61
+ * // Factory form — read defaults from a service
62
+ * provideNavConfig(() => {
63
+ * const role = inject(AuthStore).role();
64
+ * return {
65
+ * defaults: {
66
+ * main: role === 'admin' ? adminNav : guestNav,
67
+ * },
68
+ * };
69
+ * });
44
70
  * ```
45
71
  */
46
72
  export declare function provideNavConfig(config?: NavConfig | (() => NavConfig)): Provider;
@@ -2,22 +2,54 @@ import { type ResolveFn } from '@angular/router';
2
2
  import { type CreateNavItem } from './nav';
3
3
  /**
4
4
  * Registers a set of nav items for the activating route under the given scope.
5
- * Mirrors `createBreadcrumb` / `createTitle` — designed to be used in a route's
6
- * `resolve` map.
5
+ * Mirrors {@link createBreadcrumb} / {@link createTitle} — designed to be used
6
+ * in a route's `resolve` map.
7
7
  *
8
- * Multiple scopes can be registered on a single route by giving each its own `name`
9
- * (and a unique key in the `resolve` map):
8
+ * Scope override semantics: when multiple routes in the active chain register
9
+ * items under the same scope, the deepest active registration wins. Navigating
10
+ * away restores the shallower registration. To explicitly render an empty nav
11
+ * (shadowing a default), pass `[]`.
10
12
  *
11
- * ```typescript
12
- * resolve: {
13
- * mainNav: createNavItems([...], { name: 'main' }),
14
- * sideNav: createNavItems([...], { name: 'side' }),
13
+ * @typeParam TMeta Optional per-item metadata type — flows through the
14
+ * registered items so consumers reading via {@link injectNavItems} get
15
+ * typed access to `item.meta`.
16
+ * @param itemsOrFactory Either a static array of {@link CreateNavItem} or a
17
+ * factory `() => CreateNavItem<TMeta>[]` invoked inside an injection
18
+ * context (so it can use `inject()` for dynamic items).
19
+ * @param options Optional `{ name }` for registering multiple scopes on a
20
+ * single route. Omit to target the default (unnamed) scope.
21
+ * @returns An Angular `ResolveFn<void>` to wire into a route's `resolve` map.
22
+ * The resolver registers items as a side effect; the resolved value itself
23
+ * is unused.
24
+ *
25
+ * @example
26
+ * ```ts
27
+ * // Single default-scope nav
28
+ * {
29
+ * path: 'app',
30
+ * resolve: {
31
+ * _nav: createNavItems([
32
+ * { label: 'Dashboard', link: 'dashboard' },
33
+ * { label: 'Reports', link: 'reports' },
34
+ * ]),
35
+ * },
15
36
  * }
16
- * ```
17
37
  *
18
- * Scope override semantics: when multiple routes in the active chain register items
19
- * under the same scope, the deepest active registration wins. Navigating away restores
20
- * the shallower registration.
38
+ * // Multiple scopes
39
+ * {
40
+ * path: 'app',
41
+ * resolve: {
42
+ * mainNav: createNavItems([...], { name: 'main' }),
43
+ * sideNav: createNavItems([...], { name: 'side' }),
44
+ * },
45
+ * }
46
+ *
47
+ * // Factory using inject()
48
+ * createNavItems(() => {
49
+ * const auth = inject(AuthStore);
50
+ * return auth.canAdmin() ? adminItems : userItems;
51
+ * });
52
+ * ```
21
53
  */
22
54
  export declare function createNavItems<TMeta = Record<string, unknown>>(itemsOrFactory: CreateNavItem<TMeta>[] | (() => CreateNavItem<TMeta>[]), options?: {
23
55
  name?: string;
@@ -1,6 +1,44 @@
1
1
  import { PreloadingStrategy, type Route } from '@angular/router';
2
2
  import { Observable } from 'rxjs';
3
3
  import * as i0 from "@angular/core";
4
+ /**
5
+ * Demand-driven preloading strategy for Angular's router. Unlike Angular's
6
+ * built-in `PreloadAllModules`, this strategy preloads a lazy route only
7
+ * when something explicitly requests it via {@link PreloadRequester} (e.g.
8
+ * the `mmLink` directive on hover or visibility, or {@link injectTriggerPreload}
9
+ * called imperatively).
10
+ *
11
+ * Skips preloading when:
12
+ * - the route has `data.preload === false`
13
+ * - the network is on `2g` or in `saveData` mode (cheap-data-mode users)
14
+ * - a load for the same path is already in flight
15
+ *
16
+ * Wire this into `provideRouter` to enable the `mmLink` preload pipeline:
17
+ *
18
+ * @example
19
+ * ```ts
20
+ * import { PreloadStrategy } from '@mmstack/router-core';
21
+ * import { provideRouter, withPreloading } from '@angular/router';
22
+ *
23
+ * bootstrapApplication(AppComponent, {
24
+ * providers: [
25
+ * provideRouter(routes, withPreloading(PreloadStrategy)),
26
+ * ],
27
+ * });
28
+ *
29
+ * // Then in templates, `mmLink` (or `injectTriggerPreload`) requests
30
+ * // preloads that this strategy executes:
31
+ * // <a [mmLink]="'/users'">Users</a>
32
+ * ```
33
+ *
34
+ * @example
35
+ * ```ts
36
+ * // Opt a route out of preloading:
37
+ * export const routes: Routes = [
38
+ * { path: 'admin', loadChildren: () => import('./admin'), data: { preload: false } },
39
+ * ];
40
+ * ```
41
+ */
4
42
  export declare class PreloadStrategy implements PreloadingStrategy {
5
43
  private readonly loading;
6
44
  private readonly router;
@@ -32,7 +32,29 @@ export type InternalTitleConfig = {
32
32
  keepLastKnown: boolean;
33
33
  };
34
34
  /**
35
- * used to provide the title configuration, will not be applied unless a `createTitle` resolver is used
35
+ * Provide application-wide configuration for the title subsystem. The config
36
+ * is only consumed when at least one route uses a {@link createTitle} resolver;
37
+ * routes without `createTitle` are unaffected.
38
+ *
39
+ * @param config Optional {@link TitleConfig}. All fields are optional — pass
40
+ * `prefix` to namespace titles (e.g. `"My App – "`), `initialTitle` to
41
+ * override the fallback (defaults to the `<title>` from `index.html`), and
42
+ * `keepLastKnownTitle: false` to clear the title on navigations to routes
43
+ * without a title (the default keeps the previous one).
44
+ * @returns A `Provider` to add to your app's providers array.
45
+ *
46
+ * @example
47
+ * ```ts
48
+ * bootstrapApplication(AppComponent, {
49
+ * providers: [
50
+ * provideRouter(routes),
51
+ * provideTitleConfig({
52
+ * prefix: (title) => `${title} • My App`,
53
+ * keepLastKnownTitle: true,
54
+ * }),
55
+ * ],
56
+ * });
57
+ * ```
36
58
  */
37
59
  export declare function provideTitleConfig(config?: TitleConfig): Provider;
38
60
  export declare function injectTitleConfig(): InternalTitleConfig;
@@ -9,13 +9,47 @@ export declare class TitleStore {
9
9
  static ɵprov: i0.ɵɵInjectableDeclaration<TitleStore>;
10
10
  }
11
11
  /**
12
+ * Creates an Angular router `ResolveFn<string>` that registers a title for the
13
+ * route it's attached to. Titles can be static strings, factory functions
14
+ * (called in an injection context, so they can use `inject()`), or signal
15
+ * factories (for reactive titles that change when underlying data does).
12
16
  *
13
- * Creates a title resolver function that can be used in Angular's router.
17
+ * The resolved title flows through any `prefix` configured via
18
+ * {@link provideTitleConfig}, and is wired into Angular's `Title` service
19
+ * via an effect. Nested routes pick the most-specific leaf's title; if a
20
+ * deeper route has no title and `keepLastKnownTitle` is `true` (default),
21
+ * the previous title is preserved.
14
22
  *
15
- * @param factoryOrValue
16
- * A function that returns a string or a Signal<string> representing the title or just the string directly.
17
- * @param awaitValue
18
- * If `true`, the resolver will wait until the title signal has a value before resolving.
19
- * Defaults to `false`.
23
+ * @param factoryOrValue Either a literal string title, a `() => string`
24
+ * factory, or a `() => Signal<string>` factory for reactive titles. Factory
25
+ * callbacks run inside an injection context, so they can use `inject()`.
26
+ * @param awaitValue When `true`, the resolver waits until the title signal
27
+ * emits a truthy value before resolving — useful for SSR/SEO where the
28
+ * resolved title should not be empty. Defaults to `false`.
29
+ * @returns An Angular `ResolveFn<string>` to wire into a route's `title` field
30
+ * (or any other `resolve` slot — the return value isn't usually consumed).
31
+ *
32
+ * @example
33
+ * ```ts
34
+ * // Static title
35
+ * { path: 'about', component: AboutComponent, title: createTitle('About us') }
36
+ *
37
+ * // Factory using inject()
38
+ * {
39
+ * path: 'users/:id',
40
+ * component: UserComponent,
41
+ * title: createTitle(() => inject(ActivatedRoute).snapshot.params['id']),
42
+ * }
43
+ *
44
+ * // Reactive title from a signal store
45
+ * {
46
+ * path: 'dashboard',
47
+ * component: DashboardComponent,
48
+ * title: createTitle(() => {
49
+ * const user = inject(UserStore).current;
50
+ * return computed(() => `Dashboard – ${user()?.name ?? 'Guest'}`);
51
+ * }),
52
+ * }
53
+ * ```
20
54
  */
21
55
  export declare function createTitle(factoryOrValue: (() => string | (() => string)) | string, awaitValue?: boolean): ResolveFn<string>;
@@ -2,7 +2,19 @@ import { Signal } from '@angular/core';
2
2
  import { ActivatedRouteSnapshot } from '@angular/router';
3
3
  import * as i0 from "@angular/core";
4
4
  /**
5
- * @internal
5
+ * A flattened view of one route in the active router chain, used by the
6
+ * breadcrumb and title subsystems. Each `ResolvedLeafRoute` describes one
7
+ * "step" in the chain from root to current leaf.
8
+ *
9
+ * Exposed publicly because custom breadcrumb generators (see
10
+ * {@link BreadcrumbConfig}'s `generation` callback) receive instances of
11
+ * this type and need to read its fields.
12
+ *
13
+ * - `route` — the underlying `ActivatedRouteSnapshot`.
14
+ * - `segment.path` — the route config segment (e.g. `:userId`).
15
+ * - `segment.resolved` — the resolved value of that segment (e.g. `'42'`).
16
+ * - `path` — the full route-config path from root (with raw segments like `:userId`).
17
+ * - `link` — the full resolved URL from root (with substituted values).
6
18
  */
7
19
  export type ResolvedLeafRoute = {
8
20
  route: ActivatedRouteSnapshot;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mmstack/router-core",
3
- "version": "19.3.16",
3
+ "version": "19.3.17",
4
4
  "keywords": [
5
5
  "angular",
6
6
  "signals",