@ng-doc/app 22.0.0 → 22.1.0

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.
Files changed (50) hide show
  1. package/fesm2022/ng-doc-app-classes-root-page.mjs.map +1 -1
  2. package/fesm2022/ng-doc-app-components-demo-frame.mjs +96 -0
  3. package/fesm2022/ng-doc-app-components-demo-frame.mjs.map +1 -0
  4. package/fesm2022/ng-doc-app-components-demo-pane.mjs +1 -1
  5. package/fesm2022/ng-doc-app-components-demo-pane.mjs.map +1 -1
  6. package/fesm2022/ng-doc-app-components-demo.mjs +54 -8
  7. package/fesm2022/ng-doc-app-components-demo.mjs.map +1 -1
  8. package/fesm2022/ng-doc-app-components-playground.mjs +109 -47
  9. package/fesm2022/ng-doc-app-components-playground.mjs.map +1 -1
  10. package/fesm2022/ng-doc-app-components-tabs.mjs +21 -3
  11. package/fesm2022/ng-doc-app-components-tabs.mjs.map +1 -1
  12. package/fesm2022/ng-doc-app-components.mjs +1 -0
  13. package/fesm2022/ng-doc-app-components.mjs.map +1 -1
  14. package/fesm2022/ng-doc-app-demo-app.mjs +176 -0
  15. package/fesm2022/ng-doc-app-demo-app.mjs.map +1 -0
  16. package/fesm2022/ng-doc-app-helpers.mjs +23 -2
  17. package/fesm2022/ng-doc-app-helpers.mjs.map +1 -1
  18. package/fesm2022/ng-doc-app-processors-processors.mjs +30 -9
  19. package/fesm2022/ng-doc-app-processors-processors.mjs.map +1 -1
  20. package/fesm2022/ng-doc-app-providers-ng-doc-app.mjs.map +1 -1
  21. package/fesm2022/ng-doc-app-providers-title.mjs +129 -0
  22. package/fesm2022/ng-doc-app-providers-title.mjs.map +1 -0
  23. package/fesm2022/ng-doc-app-providers.mjs +1 -0
  24. package/fesm2022/ng-doc-app-providers.mjs.map +1 -1
  25. package/fesm2022/ng-doc-app-services-highlighter.mjs +17 -13
  26. package/fesm2022/ng-doc-app-services-highlighter.mjs.map +1 -1
  27. package/fesm2022/ng-doc-app-services-route-preloader.mjs +34 -10
  28. package/fesm2022/ng-doc-app-services-route-preloader.mjs.map +1 -1
  29. package/fesm2022/ng-doc-app-type-controls-type-alias-control.mjs +43 -8
  30. package/fesm2022/ng-doc-app-type-controls-type-alias-control.mjs.map +1 -1
  31. package/package.json +17 -4
  32. package/styles/global.css +3 -0
  33. package/styles/main.css +3 -0
  34. package/styles/themes/dark.css +10 -0
  35. package/types/ng-doc-app-classes-root-page.d.ts +8 -0
  36. package/types/ng-doc-app-components-demo-frame.d.ts +48 -0
  37. package/types/ng-doc-app-components-demo.d.ts +20 -2
  38. package/types/ng-doc-app-components-playground.d.ts +24 -3
  39. package/types/ng-doc-app-components-tabs.d.ts +13 -3
  40. package/types/ng-doc-app-components.d.ts +1 -0
  41. package/types/ng-doc-app-demo-app.d.ts +89 -0
  42. package/types/ng-doc-app-helpers.d.ts +21 -3
  43. package/types/ng-doc-app-interfaces.d.ts +15 -4
  44. package/types/ng-doc-app-processors-processors.d.ts +5 -0
  45. package/types/ng-doc-app-providers-ng-doc-app.d.ts +1 -1
  46. package/types/ng-doc-app-providers-title.d.ts +59 -0
  47. package/types/ng-doc-app-providers.d.ts +1 -0
  48. package/types/ng-doc-app-services-highlighter.d.ts +12 -5
  49. package/types/ng-doc-app-services-route-preloader.d.ts +13 -1
  50. package/types/ng-doc-app-type-controls-type-alias-control.d.ts +26 -4
@@ -0,0 +1,129 @@
1
+ import * as i0 from '@angular/core';
2
+ import { InjectionToken, inject, EnvironmentInjector, runInInjectionContext, Injectable, makeEnvironmentProviders } from '@angular/core';
3
+ import { Title } from '@angular/platform-browser';
4
+ import { TitleStrategy } from '@angular/router';
5
+
6
+ /** Route data key that names what a generated route's title belongs to. */
7
+ const TITLE_KIND_DATA = 'ngDocTitle';
8
+ /** Route data key of a page route that names the page type (`guide` or `api`). */
9
+ const PAGE_TYPE_DATA = 'ngDocPageType';
10
+ /** The function that `provideNgDocTitle` was given. */
11
+ const NG_DOC_TITLE_FN = new InjectionToken('NG_DOC_TITLE_FN');
12
+ /**
13
+ * Title strategy that builds the document title with the function given to `provideNgDocTitle`,
14
+ * from the categories, page, tab and API declaration of the current route. It stays internal:
15
+ * `provideNgDocTitle` is the public way to set it.
16
+ */
17
+ class NgDocTitleStrategy extends TitleStrategy {
18
+ constructor() {
19
+ super(...arguments);
20
+ this.title = inject(Title);
21
+ this.injector = inject(EnvironmentInjector);
22
+ this.titleFn = inject(NG_DOC_TITLE_FN);
23
+ }
24
+ /**
25
+ * Sets the document title for the router state.
26
+ * @param snapshot - The router state after a navigation.
27
+ */
28
+ updateTitle(snapshot) {
29
+ const title = runInInjectionContext(this.injector, () => this.titleFn(this.buildContext(snapshot)));
30
+ if (title !== undefined) {
31
+ this.title.setTitle(title);
32
+ }
33
+ }
34
+ /**
35
+ * Collects the titles of the router state by what they belong to.
36
+ * @param snapshot - The router state after a navigation.
37
+ */
38
+ buildContext(snapshot) {
39
+ const categories = [];
40
+ let page;
41
+ let tab;
42
+ let ownTitle;
43
+ let isApi = false;
44
+ for (let route = snapshot.root; route; route = route.firstChild) {
45
+ const config = route.routeConfig;
46
+ if (config?.data?.[PAGE_TYPE_DATA] === 'api') {
47
+ isApi = true;
48
+ }
49
+ // Only a route's own title counts: routes without one inherit the title of their parent,
50
+ // which would repeat it.
51
+ if (config?.title === undefined) {
52
+ continue;
53
+ }
54
+ const title = this.getResolvedTitleForRoute(route);
55
+ if (typeof title !== 'string') {
56
+ continue;
57
+ }
58
+ switch (config.data?.[TITLE_KIND_DATA]) {
59
+ case 'category':
60
+ categories.push(title);
61
+ break;
62
+ case 'page':
63
+ page = title;
64
+ break;
65
+ case 'tab':
66
+ // A page with one tab shows no tabs, and the tab's title is the page's.
67
+ if ((route.parent?.routeConfig?.children?.length ?? 0) > 1) {
68
+ tab = title;
69
+ }
70
+ break;
71
+ default:
72
+ // A title below the page comes from the page's own `route`, and wins over the
73
+ // generated one. Titles above the documentation belong to the application.
74
+ if (page !== undefined) {
75
+ ownTitle = title;
76
+ }
77
+ }
78
+ }
79
+ // The first tab of a page usually has no title of its own and shows the page's.
80
+ if (tab === page) {
81
+ tab = undefined;
82
+ }
83
+ page = ownTitle ?? page;
84
+ return {
85
+ title: this.buildTitle(snapshot),
86
+ categories,
87
+ page: isApi ? undefined : page,
88
+ tab,
89
+ api: isApi ? page : undefined,
90
+ snapshot,
91
+ };
92
+ }
93
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.2.1", ngImport: i0, type: NgDocTitleStrategy, deps: null, target: i0.ɵɵFactoryTarget.Injectable }); }
94
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.2.1", ngImport: i0, type: NgDocTitleStrategy }); }
95
+ }
96
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.2.1", ngImport: i0, type: NgDocTitleStrategy, decorators: [{
97
+ type: Injectable
98
+ }] });
99
+ /**
100
+ * Builds the browser tab title of every navigation with a function that receives the categories,
101
+ * the page, the tab and the API declaration of the route, so titles like
102
+ * "Installation · Getting started | NgDoc" need no parsing of route titles.
103
+ *
104
+ * Without it, Angular sets the title of the deepest route: the page's (or its tab's) title.
105
+ * @param titleFn - Builds the title; `undefined` keeps the current title.
106
+ * @example
107
+ * ```ts
108
+ * provideNgDocTitle(({ categories, page, tab, api, title }) => {
109
+ * const name = api ?? page;
110
+ *
111
+ * return name
112
+ * ? [tab, name, ...[...categories].reverse()].filter(Boolean).join(' · ') + ' | My library'
113
+ * : (title ?? 'My library');
114
+ * });
115
+ * ```
116
+ */
117
+ function provideNgDocTitle(titleFn) {
118
+ return makeEnvironmentProviders([
119
+ { provide: NG_DOC_TITLE_FN, useValue: titleFn },
120
+ { provide: TitleStrategy, useClass: NgDocTitleStrategy },
121
+ ]);
122
+ }
123
+
124
+ /**
125
+ * Generated bundle index. Do not edit.
126
+ */
127
+
128
+ export { provideNgDocTitle };
129
+ //# sourceMappingURL=ng-doc-app-providers-title.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ng-doc-app-providers-title.mjs","sources":["../../../../libs/app/providers/title/provide-ng-doc-title.ts","../../../../libs/app/providers/title/ng-doc-app-providers-title.ts"],"sourcesContent":["import {\n EnvironmentInjector,\n EnvironmentProviders,\n inject,\n Injectable,\n InjectionToken,\n makeEnvironmentProviders,\n runInInjectionContext,\n} from '@angular/core';\nimport { Title } from '@angular/platform-browser';\nimport { ActivatedRouteSnapshot, RouterStateSnapshot, TitleStrategy } from '@angular/router';\n\n/**\n * What the title of a route belongs to. NgDoc writes it as `ngDocTitle` into the `data` of the\n * routes it generates.\n */\ntype NgDocTitleKind = 'category' | 'page' | 'tab';\n\n/** Route data key that names what a generated route's title belongs to. */\nconst TITLE_KIND_DATA: string = 'ngDocTitle';\n\n/** Route data key of a page route that names the page type (`guide` or `api`). */\nconst PAGE_TYPE_DATA: string = 'ngDocPageType';\n\n/**\n * The parts of the current route that `provideNgDocTitle` passes to its function.\n *\n * Outside the documentation (a landing page, for example), `categories` is empty and `page`,\n * `tab` and `api` are not set.\n */\nexport interface NgDocTitleContext {\n /**\n * The title Angular sets by default: the title of the deepest route that has one. It is not set\n * when no route of the navigation has a title.\n */\n readonly title: string | undefined;\n /** Titles of the categories that contain the page, the outermost category first. */\n readonly categories: readonly string[];\n /**\n * Title of the guide or the API list. A title in the page's own route replaces it. Not set on\n * an API page, which sets `api` instead.\n */\n readonly page: string | undefined;\n /**\n * Title of the active tab, when the page has more than one tab. Not set on a tab titled like\n * the page, usually its first one.\n */\n readonly tab: string | undefined;\n /** Name of the API declaration, on an API page. */\n readonly api: string | undefined;\n /** The router state the title is for. */\n readonly snapshot: RouterStateSnapshot;\n}\n\n/**\n * Builds the browser tab title from the parts of the current route. It runs in an injection\n * context, so it can call `inject()`. When it returns `undefined`, the title stays as it is.\n */\nexport type NgDocTitleFn = (context: NgDocTitleContext) => string | undefined;\n\n/** The function that `provideNgDocTitle` was given. */\nconst NG_DOC_TITLE_FN = new InjectionToken<NgDocTitleFn>('NG_DOC_TITLE_FN');\n\n/**\n * Title strategy that builds the document title with the function given to `provideNgDocTitle`,\n * from the categories, page, tab and API declaration of the current route. It stays internal:\n * `provideNgDocTitle` is the public way to set it.\n */\n@Injectable()\nclass NgDocTitleStrategy extends TitleStrategy {\n private readonly title = inject(Title);\n private readonly injector = inject(EnvironmentInjector);\n private readonly titleFn = inject(NG_DOC_TITLE_FN);\n\n /**\n * Sets the document title for the router state.\n * @param snapshot - The router state after a navigation.\n */\n override updateTitle(snapshot: RouterStateSnapshot): void {\n const title = runInInjectionContext(this.injector, () =>\n this.titleFn(this.buildContext(snapshot)),\n );\n\n if (title !== undefined) {\n this.title.setTitle(title);\n }\n }\n\n /**\n * Collects the titles of the router state by what they belong to.\n * @param snapshot - The router state after a navigation.\n */\n buildContext(snapshot: RouterStateSnapshot): NgDocTitleContext {\n const categories: string[] = [];\n let page: string | undefined;\n let tab: string | undefined;\n let ownTitle: string | undefined;\n let isApi = false;\n\n for (\n let route: ActivatedRouteSnapshot | null = snapshot.root;\n route;\n route = route.firstChild\n ) {\n const config = route.routeConfig;\n\n if (config?.data?.[PAGE_TYPE_DATA] === 'api') {\n isApi = true;\n }\n\n // Only a route's own title counts: routes without one inherit the title of their parent,\n // which would repeat it.\n if (config?.title === undefined) {\n continue;\n }\n\n const title = this.getResolvedTitleForRoute(route);\n\n if (typeof title !== 'string') {\n continue;\n }\n\n switch (config.data?.[TITLE_KIND_DATA] as NgDocTitleKind | undefined) {\n case 'category':\n categories.push(title);\n break;\n case 'page':\n page = title;\n break;\n case 'tab':\n // A page with one tab shows no tabs, and the tab's title is the page's.\n if ((route.parent?.routeConfig?.children?.length ?? 0) > 1) {\n tab = title;\n }\n break;\n default:\n // A title below the page comes from the page's own `route`, and wins over the\n // generated one. Titles above the documentation belong to the application.\n if (page !== undefined) {\n ownTitle = title;\n }\n }\n }\n\n // The first tab of a page usually has no title of its own and shows the page's.\n if (tab === page) {\n tab = undefined;\n }\n\n page = ownTitle ?? page;\n\n return {\n title: this.buildTitle(snapshot),\n categories,\n page: isApi ? undefined : page,\n tab,\n api: isApi ? page : undefined,\n snapshot,\n };\n }\n}\n\n/**\n * Builds the browser tab title of every navigation with a function that receives the categories,\n * the page, the tab and the API declaration of the route, so titles like\n * \"Installation · Getting started | NgDoc\" need no parsing of route titles.\n *\n * Without it, Angular sets the title of the deepest route: the page's (or its tab's) title.\n * @param titleFn - Builds the title; `undefined` keeps the current title.\n * @example\n * ```ts\n * provideNgDocTitle(({ categories, page, tab, api, title }) => {\n * const name = api ?? page;\n *\n * return name\n * ? [tab, name, ...[...categories].reverse()].filter(Boolean).join(' · ') + ' | My library'\n * : (title ?? 'My library');\n * });\n * ```\n */\nexport function provideNgDocTitle(titleFn: NgDocTitleFn): EnvironmentProviders {\n return makeEnvironmentProviders([\n { provide: NG_DOC_TITLE_FN, useValue: titleFn },\n { provide: TitleStrategy, useClass: NgDocTitleStrategy },\n ]);\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
@@ -3,6 +3,7 @@ export * from '@ng-doc/app/providers/ng-doc-app';
3
3
  export * from '@ng-doc/app/providers/page-skeleton';
4
4
  export * from '@ng-doc/app/providers/playground-demo';
5
5
  export * from '@ng-doc/app/providers/search-engine';
6
+ export * from '@ng-doc/app/providers/title';
6
7
  export * from '@ng-doc/app/providers/type-control';
7
8
 
8
9
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"ng-doc-app-providers.mjs","sources":["../../../../libs/app/providers/ng-doc-app-providers.ts"],"sourcesContent":["/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":";;;;;;;AAAA;;AAEG"}
1
+ {"version":3,"file":"ng-doc-app-providers.mjs","sources":["../../../../libs/app/providers/ng-doc-app-providers.ts"],"sourcesContent":["/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":";;;;;;;;AAAA;;AAEG"}
@@ -3,6 +3,7 @@ import { signal, inject, isDevMode, Service } from '@angular/core';
3
3
  import { NG_DOC_SHIKI_THEME } from '@ng-doc/app/tokens';
4
4
  import { ngDocSyntaxTheme } from '@ng-doc/core/constants/syntax-theme';
5
5
  import { createHighlighterCore } from 'shiki/core';
6
+ import { createOnigurumaEngine } from 'shiki/engine/oniguruma';
6
7
  import getWasm from 'shiki/wasm';
7
8
 
8
9
  /**
@@ -23,9 +24,9 @@ class NgDocHighlighterService {
23
24
  }
24
25
  /**
25
26
  * Loads Shiki with the built-in themes (`github-light`, `ayu-dark` and NgDoc's `css-variables`)
26
- * and the given ones. Concurrent and repeated calls share one initialization; a failed one can
27
- * be retried.
28
- * @param config - Custom Shiki themes to load.
27
+ * and the given ones, and the given languages. Concurrent and repeated calls share one
28
+ * initialization; a failed one can be retried.
29
+ * @param config - Custom Shiki themes and languages to load.
29
30
  */
30
31
  initialize(config) {
31
32
  if (this.destroyed) {
@@ -39,11 +40,11 @@ class NgDocHighlighterService {
39
40
  '`shiki.themes` of `ng-doc.config.ts`: NgDoc uses them for the code blocks and the ' +
40
41
  'code that the browser highlights.');
41
42
  }
42
- // Only immutable built-in themes are shared across SSR applications. Custom
43
- // themes may reuse names with different colors, or be asynchronous getters.
44
- this.ownsHighlighter = Boolean(config?.themes?.length);
43
+ // Only immutable built-in themes and languages are shared across SSR applications. Custom
44
+ // ones may reuse names with other definitions, or be asynchronous getters.
45
+ this.ownsHighlighter = Boolean(config?.themes?.length || config?.langs?.length);
45
46
  const creation = this.ownsHighlighter
46
- ? NgDocHighlighterService.create(config?.themes)
47
+ ? NgDocHighlighterService.create(config?.themes, config?.langs)
47
48
  : (NgDocHighlighterService.defaultInitialization ??= NgDocHighlighterService.create().catch((error) => {
48
49
  NgDocHighlighterService.defaultInitialization = undefined;
49
50
  throw error;
@@ -65,7 +66,7 @@ class NgDocHighlighterService {
65
66
  });
66
67
  return this.initialization;
67
68
  }
68
- /** Disposes the highlighter this service created for custom themes. */
69
+ /** Disposes the highlighter this service created for custom themes or languages. */
69
70
  ngOnDestroy() {
70
71
  this.destroyed = true;
71
72
  this.readyState.set(false);
@@ -74,8 +75,8 @@ class NgDocHighlighterService {
74
75
  }
75
76
  this.highlighter = undefined;
76
77
  }
77
- static async create(themes = []) {
78
- return (await createHighlighterCore({
78
+ static async create(themes = [], langs = []) {
79
+ return createHighlighterCore({
79
80
  themes: [
80
81
  import('shiki/themes/github-light.mjs'),
81
82
  import('shiki/themes/ayu-dark.mjs'),
@@ -83,9 +84,12 @@ class NgDocHighlighterService {
83
84
  ngDocSyntaxTheme(),
84
85
  ...themes,
85
86
  ],
86
- langs: [import('shiki/langs/angular-html.mjs')],
87
- loadWasm: getWasm,
88
- }));
87
+ // Later registrations of a name replace earlier ones, so the given languages win.
88
+ langs: [import('shiki/langs/angular-html.mjs'), ...langs],
89
+ // The Oniguruma engine, as at build time, so that the browser tokenizes code as the code
90
+ // blocks were tokenized.
91
+ engine: createOnigurumaEngine(getWasm),
92
+ });
89
93
  }
90
94
  /**
91
95
  * Returns the code as highlighted HTML, or an empty string before `initialize()` has
@@ -1 +1 @@
1
- {"version":3,"file":"ng-doc-app-services-highlighter.mjs","sources":["../../../../libs/app/services/highlighter/highlighter.service.ts","../../../../libs/app/services/highlighter/ng-doc-app-services-highlighter.ts"],"sourcesContent":["import { inject, isDevMode, OnDestroy, Service, Signal, signal } from '@angular/core';\nimport { NG_DOC_SHIKI_THEME } from '@ng-doc/app/tokens';\nimport { ngDocSyntaxTheme } from '@ng-doc/core/constants/syntax-theme';\nimport { HighlighterGeneric } from '@shikijs/core/types';\nimport { ThemeInput } from 'shiki';\nimport { createHighlighterCore } from 'shiki/core';\nimport getWasm from 'shiki/wasm';\n\n/** Shiki configuration of the code highlighter. */\nexport interface NgDocHighlighterConfig {\n /**\n * Themes sources.\n */\n themes?: ThemeInput[];\n /**\n * Has no effect, and is reported in development mode.\n *\n * The theme names come from `shiki.themes` in `ng-doc.config.ts`: NgDoc highlights code blocks\n * with them when it builds the site, and passes them to the application (`NG_DOC_SHIKI_THEME`),\n * so the code that the browser highlights matches the code blocks.\n * @deprecated Set the theme names in `shiki.themes` of `ng-doc.config.ts` instead, and load\n * other themes with `themes`. This field will be removed in a future major release.\n */\n theme?: {\n light: string;\n dark: string;\n };\n}\n\n/**\n * Highlights code with Shiki. `provideNgDocApp` initializes it before the application renders.\n */\n@Service()\nexport class NgDocHighlighterService implements OnDestroy {\n private static defaultInitialization?: Promise<HighlighterGeneric<string, string>>;\n\n private highlighter?: HighlighterGeneric<string, string>;\n private initialization?: Promise<void>;\n private ownsHighlighter = false;\n private destroyed = false;\n private readonly readyState = signal(false);\n\n protected readonly theme = inject(NG_DOC_SHIKI_THEME);\n\n /**\n * Whether `highlight()` can highlight: `true` once `initialize()` has finished, and\n * `false` again after the service is destroyed.\n */\n readonly ready: Signal<boolean> = this.readyState.asReadonly();\n\n /**\n * Loads Shiki with the built-in themes (`github-light`, `ayu-dark` and NgDoc's `css-variables`)\n * and the given ones. Concurrent and repeated calls share one initialization; a failed one can\n * be retried.\n * @param config - Custom Shiki themes to load.\n */\n initialize(config?: NgDocHighlighterConfig): Promise<void> {\n if (this.destroyed) {\n return Promise.reject(new Error('NgDoc highlighter has been destroyed.'));\n }\n if (this.initialization) {\n return this.initialization;\n }\n if (config?.theme && isDevMode()) {\n console.warn(\n '[NgDoc] `shiki.theme` of `provideNgDocApp` has no effect. Set the theme names in ' +\n '`shiki.themes` of `ng-doc.config.ts`: NgDoc uses them for the code blocks and the ' +\n 'code that the browser highlights.',\n );\n }\n\n // Only immutable built-in themes are shared across SSR applications. Custom\n // themes may reuse names with different colors, or be asynchronous getters.\n this.ownsHighlighter = Boolean(config?.themes?.length);\n const creation = this.ownsHighlighter\n ? NgDocHighlighterService.create(config?.themes)\n : (NgDocHighlighterService.defaultInitialization ??= NgDocHighlighterService.create().catch(\n (error: unknown) => {\n NgDocHighlighterService.defaultInitialization = undefined;\n throw error;\n },\n ));\n this.initialization = creation\n .then((highlighter) => {\n if (this.destroyed) {\n if (this.ownsHighlighter) {\n highlighter.dispose();\n }\n return;\n }\n this.highlighter = highlighter;\n this.readyState.set(true);\n })\n .catch((error: unknown) => {\n this.initialization = undefined;\n throw error;\n });\n return this.initialization;\n }\n\n /** Disposes the highlighter this service created for custom themes. */\n ngOnDestroy(): void {\n this.destroyed = true;\n this.readyState.set(false);\n if (this.ownsHighlighter) {\n this.highlighter?.dispose();\n }\n this.highlighter = undefined;\n }\n\n private static async create(\n themes: ThemeInput[] = [],\n ): Promise<HighlighterGeneric<string, string>> {\n return (await createHighlighterCore({\n themes: [\n import('shiki/themes/github-light.mjs'),\n import('shiki/themes/ayu-dark.mjs'),\n // A new copy: Shiki changes the theme object it loads.\n ngDocSyntaxTheme(),\n ...themes,\n ],\n langs: [import('shiki/langs/angular-html.mjs')],\n loadWasm: getWasm,\n })) as HighlighterGeneric<string, string>;\n }\n\n /**\n * Returns the code as highlighted HTML, or an empty string before `initialize()` has\n * finished. It reads `ready()`, so a `computed` over it updates once Shiki is loaded.\n * @param code - Angular HTML to highlight.\n */\n highlight(code: string): string {\n if (!this.readyState()) return '';\n\n return (\n this.highlighter?.codeToHtml(code, {\n lang: 'angular-html',\n themes: {\n light: this.theme.light || 'github-light',\n dark: this.theme.dark || 'ayu-dark',\n },\n }) ?? ''\n );\n }\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":";;;;;;;AA6BA;;AAEG;MAEU,uBAAuB,CAAA;AADpC,IAAA,WAAA,GAAA;QAMU,IAAA,CAAA,eAAe,GAAG,KAAK;QACvB,IAAA,CAAA,SAAS,GAAG,KAAK;QACR,IAAA,CAAA,UAAU,GAAG,MAAM,CAAC,KAAK;uFAAC;AAExB,QAAA,IAAA,CAAA,KAAK,GAAG,MAAM,CAAC,kBAAkB,CAAC;AAErD;;;AAGG;AACM,QAAA,IAAA,CAAA,KAAK,GAAoB,IAAI,CAAC,UAAU,CAAC,UAAU,EAAE;AAgG/D,IAAA;AA9FC;;;;;AAKG;AACH,IAAA,UAAU,CAAC,MAA+B,EAAA;AACxC,QAAA,IAAI,IAAI,CAAC,SAAS,EAAE;YAClB,OAAO,OAAO,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC,uCAAuC,CAAC,CAAC;QAC3E;AACA,QAAA,IAAI,IAAI,CAAC,cAAc,EAAE;YACvB,OAAO,IAAI,CAAC,cAAc;QAC5B;AACA,QAAA,IAAI,MAAM,EAAE,KAAK,IAAI,SAAS,EAAE,EAAE;YAChC,OAAO,CAAC,IAAI,CACV,mFAAmF;gBACjF,oFAAoF;AACpF,gBAAA,mCAAmC,CACtC;QACH;;;QAIA,IAAI,CAAC,eAAe,GAAG,OAAO,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC;AACtD,QAAA,MAAM,QAAQ,GAAG,IAAI,CAAC;cAClB,uBAAuB,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM;AAC/C,eAAG,uBAAuB,CAAC,qBAAqB,KAAK,uBAAuB,CAAC,MAAM,EAAE,CAAC,KAAK,CACvF,CAAC,KAAc,KAAI;AACjB,gBAAA,uBAAuB,CAAC,qBAAqB,GAAG,SAAS;AACzD,gBAAA,MAAM,KAAK;YACb,CAAC,CACF,CAAC;QACN,IAAI,CAAC,cAAc,GAAG;AACnB,aAAA,IAAI,CAAC,CAAC,WAAW,KAAI;AACpB,YAAA,IAAI,IAAI,CAAC,SAAS,EAAE;AAClB,gBAAA,IAAI,IAAI,CAAC,eAAe,EAAE;oBACxB,WAAW,CAAC,OAAO,EAAE;gBACvB;gBACA;YACF;AACA,YAAA,IAAI,CAAC,WAAW,GAAG,WAAW;AAC9B,YAAA,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC;AAC3B,QAAA,CAAC;AACA,aAAA,KAAK,CAAC,CAAC,KAAc,KAAI;AACxB,YAAA,IAAI,CAAC,cAAc,GAAG,SAAS;AAC/B,YAAA,MAAM,KAAK;AACb,QAAA,CAAC,CAAC;QACJ,OAAO,IAAI,CAAC,cAAc;IAC5B;;IAGA,WAAW,GAAA;AACT,QAAA,IAAI,CAAC,SAAS,GAAG,IAAI;AACrB,QAAA,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,KAAK,CAAC;AAC1B,QAAA,IAAI,IAAI,CAAC,eAAe,EAAE;AACxB,YAAA,IAAI,CAAC,WAAW,EAAE,OAAO,EAAE;QAC7B;AACA,QAAA,IAAI,CAAC,WAAW,GAAG,SAAS;IAC9B;AAEQ,IAAA,aAAa,MAAM,CACzB,SAAuB,EAAE,EAAA;QAEzB,QAAQ,MAAM,qBAAqB,CAAC;AAClC,YAAA,MAAM,EAAE;gBACN,OAAO,+BAA+B,CAAC;gBACvC,OAAO,2BAA2B,CAAC;;AAEnC,gBAAA,gBAAgB,EAAE;AAClB,gBAAA,GAAG,MAAM;AACV,aAAA;AACD,YAAA,KAAK,EAAE,CAAC,OAAO,8BAA8B,CAAC,CAAC;AAC/C,YAAA,QAAQ,EAAE,OAAO;AAClB,SAAA,CAAC;IACJ;AAEA;;;;AAIG;AACH,IAAA,SAAS,CAAC,IAAY,EAAA;AACpB,QAAA,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE;AAAE,YAAA,OAAO,EAAE;QAEjC,QACE,IAAI,CAAC,WAAW,EAAE,UAAU,CAAC,IAAI,EAAE;AACjC,YAAA,IAAI,EAAE,cAAc;AACpB,YAAA,MAAM,EAAE;AACN,gBAAA,KAAK,EAAE,IAAI,CAAC,KAAK,CAAC,KAAK,IAAI,cAAc;AACzC,gBAAA,IAAI,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,IAAI,UAAU;AACpC,aAAA;SACF,CAAC,IAAI,EAAE;IAEZ;8GA9GW,uBAAuB,EAAA,IAAA,EAAA,EAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,OAAA,EAAA,CAAA,CAAA;+GAAvB,uBAAuB,EAAA,CAAA,CAAA;;2FAAvB,uBAAuB,EAAA,UAAA,EAAA,CAAA;kBADnC;;;AChCD;;AAEG;;;;"}
1
+ {"version":3,"file":"ng-doc-app-services-highlighter.mjs","sources":["../../../../libs/app/services/highlighter/highlighter.service.ts","../../../../libs/app/services/highlighter/ng-doc-app-services-highlighter.ts"],"sourcesContent":["import { inject, isDevMode, OnDestroy, Service, Signal, signal } from '@angular/core';\nimport { NG_DOC_SHIKI_THEME } from '@ng-doc/app/tokens';\nimport { ngDocSyntaxTheme } from '@ng-doc/core/constants/syntax-theme';\nimport {\n type HighlighterCore,\n type LanguageInput,\n type ThemeInput,\n createHighlighterCore,\n} from 'shiki/core';\nimport { createOnigurumaEngine } from 'shiki/engine/oniguruma';\nimport getWasm from 'shiki/wasm';\n\n/** Shiki configuration of the code highlighter. */\nexport interface NgDocHighlighterConfig {\n /**\n * Themes sources.\n */\n themes?: ThemeInput[];\n /**\n * Shiki languages to load besides `angular-html`, the language of playground code, and the\n * languages it embeds. A language named like one of them replaces it, so register here the\n * languages of `shiki.langs` in `ng-doc.config.ts` that playground code uses, for example a newer\n * Angular template grammar.\n */\n langs?: LanguageInput[];\n /**\n * Has no effect, and is reported in development mode.\n *\n * The theme names come from `shiki.themes` in `ng-doc.config.ts`: NgDoc highlights code blocks\n * with them when it builds the site, and passes them to the application (`NG_DOC_SHIKI_THEME`),\n * so the code that the browser highlights matches the code blocks.\n * @deprecated Set the theme names in `shiki.themes` of `ng-doc.config.ts` instead, and load\n * other themes with `themes`. This field will be removed in a future major release.\n */\n theme?: {\n light: string;\n dark: string;\n };\n}\n\n/**\n * Highlights code with Shiki. `provideNgDocApp` initializes it before the application renders.\n */\n@Service()\nexport class NgDocHighlighterService implements OnDestroy {\n private static defaultInitialization?: Promise<HighlighterCore>;\n\n private highlighter?: HighlighterCore;\n private initialization?: Promise<void>;\n private ownsHighlighter = false;\n private destroyed = false;\n private readonly readyState = signal(false);\n\n protected readonly theme = inject(NG_DOC_SHIKI_THEME);\n\n /**\n * Whether `highlight()` can highlight: `true` once `initialize()` has finished, and\n * `false` again after the service is destroyed.\n */\n readonly ready: Signal<boolean> = this.readyState.asReadonly();\n\n /**\n * Loads Shiki with the built-in themes (`github-light`, `ayu-dark` and NgDoc's `css-variables`)\n * and the given ones, and the given languages. Concurrent and repeated calls share one\n * initialization; a failed one can be retried.\n * @param config - Custom Shiki themes and languages to load.\n */\n initialize(config?: NgDocHighlighterConfig): Promise<void> {\n if (this.destroyed) {\n return Promise.reject(new Error('NgDoc highlighter has been destroyed.'));\n }\n if (this.initialization) {\n return this.initialization;\n }\n if (config?.theme && isDevMode()) {\n console.warn(\n '[NgDoc] `shiki.theme` of `provideNgDocApp` has no effect. Set the theme names in ' +\n '`shiki.themes` of `ng-doc.config.ts`: NgDoc uses them for the code blocks and the ' +\n 'code that the browser highlights.',\n );\n }\n\n // Only immutable built-in themes and languages are shared across SSR applications. Custom\n // ones may reuse names with other definitions, or be asynchronous getters.\n this.ownsHighlighter = Boolean(config?.themes?.length || config?.langs?.length);\n const creation = this.ownsHighlighter\n ? NgDocHighlighterService.create(config?.themes, config?.langs)\n : (NgDocHighlighterService.defaultInitialization ??= NgDocHighlighterService.create().catch(\n (error: unknown) => {\n NgDocHighlighterService.defaultInitialization = undefined;\n throw error;\n },\n ));\n this.initialization = creation\n .then((highlighter) => {\n if (this.destroyed) {\n if (this.ownsHighlighter) {\n highlighter.dispose();\n }\n return;\n }\n this.highlighter = highlighter;\n this.readyState.set(true);\n })\n .catch((error: unknown) => {\n this.initialization = undefined;\n throw error;\n });\n return this.initialization;\n }\n\n /** Disposes the highlighter this service created for custom themes or languages. */\n ngOnDestroy(): void {\n this.destroyed = true;\n this.readyState.set(false);\n if (this.ownsHighlighter) {\n this.highlighter?.dispose();\n }\n this.highlighter = undefined;\n }\n\n private static async create(\n themes: ThemeInput[] = [],\n langs: LanguageInput[] = [],\n ): Promise<HighlighterCore> {\n return createHighlighterCore({\n themes: [\n import('shiki/themes/github-light.mjs'),\n import('shiki/themes/ayu-dark.mjs'),\n // A new copy: Shiki changes the theme object it loads.\n ngDocSyntaxTheme(),\n ...themes,\n ],\n // Later registrations of a name replace earlier ones, so the given languages win.\n langs: [import('shiki/langs/angular-html.mjs'), ...langs],\n // The Oniguruma engine, as at build time, so that the browser tokenizes code as the code\n // blocks were tokenized.\n engine: createOnigurumaEngine(getWasm),\n });\n }\n\n /**\n * Returns the code as highlighted HTML, or an empty string before `initialize()` has\n * finished. It reads `ready()`, so a `computed` over it updates once Shiki is loaded.\n * @param code - Angular HTML to highlight.\n */\n highlight(code: string): string {\n if (!this.readyState()) return '';\n\n return (\n this.highlighter?.codeToHtml(code, {\n lang: 'angular-html',\n themes: {\n light: this.theme.light || 'github-light',\n dark: this.theme.dark || 'ayu-dark',\n },\n }) ?? ''\n );\n }\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
@@ -3,7 +3,7 @@ import { inject, DOCUMENT, Injector, NgZone, PLATFORM_ID, DestroyRef, Service }
3
3
  import { of, catchError } from 'rxjs';
4
4
  import { LocationStrategy, isPlatformBrowser } from '@angular/common';
5
5
  import { Router, RouterPreloader, PRIMARY_OUTLET } from '@angular/router';
6
- import { ɵngDocRouteContentSources as _ngDocRouteContentSources, ɵpreloadNgDocContent as _preloadNgDocContent } from '@ng-doc/app/helpers';
6
+ import { ɵngDocRouteContentSources as _ngDocRouteContentSources, ɵpreloadNgDocContent as _preloadNgDocContent, ɵngDocRouteUrl as _ngDocRouteUrl } from '@ng-doc/app/helpers';
7
7
  import { NG_DOC_ROUTE_PREFIX } from '@ng-doc/app/tokens';
8
8
 
9
9
  /** The events that tell that the reader is about to open a link. */
@@ -182,6 +182,10 @@ class NgDocRoutePreloader {
182
182
  /**
183
183
  * Returns the application URL of a link, or `undefined` for a link that leaves the
184
184
  * application, opens elsewhere, downloads a file or points into the current page.
185
+ *
186
+ * The link is read through the location strategy: with `PathLocationStrategy` the route is the
187
+ * link's path, and with `HashLocationStrategy` (`withHashLocation()`) it is the link's fragment
188
+ * (`#/docs/page`), so a fragment that is not a route (`#usage`) is an anchor on the page.
185
189
  * @param anchor - The link.
186
190
  * @returns The URL relative to the application's base, with its query.
187
191
  */
@@ -189,11 +193,7 @@ class NgDocRoutePreloader {
189
193
  const view = this.document.defaultView;
190
194
  const href = anchor.getAttribute('href');
191
195
  const target = anchor.getAttribute('target');
192
- if (!view ||
193
- !href ||
194
- anchor.hasAttribute('download') ||
195
- (target && target !== '_self') ||
196
- href.startsWith('#')) {
196
+ if (!view || !href || anchor.hasAttribute('download') || (target && target !== '_self')) {
197
197
  return undefined;
198
198
  }
199
199
  let url;
@@ -203,8 +203,20 @@ class NgDocRoutePreloader {
203
203
  catch {
204
204
  return undefined;
205
205
  }
206
- // Another site, or the current page (an anchor on it, or a link to itself).
207
- if (url.origin !== view.location.origin || url.pathname === view.location.pathname) {
206
+ if (url.origin !== view.location.origin)
207
+ return undefined;
208
+ if (this.hashLocation()) {
209
+ // The route lives in the fragment of this document; another document leaves the
210
+ // application.
211
+ if (url.pathname !== view.location.pathname || !url.hash.startsWith('#/'))
212
+ return undefined;
213
+ url = new URL(url.hash.slice(1).replace(/^\/*/, '/'), view.location.origin);
214
+ }
215
+ else if (href.startsWith('#')) {
216
+ return undefined;
217
+ }
218
+ // The current page: an anchor on it, or a link to itself.
219
+ if (url.pathname === _ngDocRouteUrl(this.locationStrategy, view.location.href).pathname) {
208
220
  return undefined;
209
221
  }
210
222
  const base = this.basePath();
@@ -212,10 +224,22 @@ class NgDocRoutePreloader {
212
224
  return undefined;
213
225
  return `/${url.pathname.slice(base.length)}${url.search}`;
214
226
  }
215
- /** The base path of the application, ending with a slash. */
227
+ /**
228
+ * Whether the location strategy keeps the route in the document's fragment, as
229
+ * `HashLocationStrategy` does.
230
+ */
231
+ hashLocation() {
232
+ return this.locationStrategy.prepareExternalUrl('/').startsWith('#');
233
+ }
234
+ /**
235
+ * The base path of the application's routes, ending with a slash: the base href, which a
236
+ * fragment route carries only when `APP_BASE_HREF` sets one.
237
+ */
216
238
  basePath() {
217
239
  const base = this.locationStrategy.getBaseHref() || '/';
218
- const path = new URL(base, this.document.baseURI).pathname;
240
+ const path = this.hashLocation()
241
+ ? base.replace(/^\/*/, '/')
242
+ : new URL(base, this.document.baseURI).pathname;
219
243
  return path.endsWith('/') ? path : `${path}/`;
220
244
  }
221
245
  destroy() {
@@ -1 +1 @@
1
- {"version":3,"file":"ng-doc-app-services-route-preloader.mjs","sources":["../../../../libs/app/services/route-preloader/route-preloader.service.ts","../../../../libs/app/services/route-preloader/preloading-strategy.ts","../../../../libs/app/services/route-preloader/ng-doc-app-services-route-preloader.ts"],"sourcesContent":["import { isPlatformBrowser, LocationStrategy } from '@angular/common';\nimport {\n DestroyRef,\n DOCUMENT,\n inject,\n Injector,\n NgZone,\n PLATFORM_ID,\n Service,\n} from '@angular/core';\nimport { PRIMARY_OUTLET, Route, Router, RouterPreloader, UrlTree } from '@angular/router';\nimport { ɵngDocRouteContentSources, ɵpreloadNgDocContent } from '@ng-doc/app/helpers';\nimport { NG_DOC_ROUTE_PREFIX } from '@ng-doc/app/tokens';\n\n/** The events that tell that the reader is about to open a link. */\nconst INTENT_EVENTS = ['pointerover', 'mouseover', 'focusin', 'touchstart'] as const;\n\n/** How long the browser may stay busy before an idle preload runs anyway, in milliseconds. */\nconst IDLE_TIMEOUT = 2000;\n/** The delay of an idle preload where the browser has no `requestIdleCallback`. */\nconst IDLE_FALLBACK_DELAY = 200;\n\n/** The parts of the Network Information API that preloading reads. */\ninterface NgDocNetworkInformation {\n readonly saveData?: boolean;\n readonly effectiveType?: string;\n}\n\n/**\n * Preloads the pages that the reader is about to open, so that opening them shows them at once.\n *\n * It preloads a page when the reader points at, focuses or touches a link to it, anywhere in the\n * document: the sidebar, the previous and next page links, links in the page content, search\n * results and the table of contents. It also preloads the previous and next pages of a guide once\n * the browser is idle after the guide has rendered. A preload loads the code of the page's route\n * and its content, once per page. Nothing is preloaded on the server, when the reader asked the\n * browser to save data, or on a 2G connection.\n *\n * The router loads the code through its preloading strategy, so preloading works only when the\n * application sets `NgDocPreloadingStrategy` as that strategy:\n *\n * ```ts\n * provideRouter(routes, withPreloading(NgDocPreloadingStrategy));\n * ```\n */\n@Service()\nexport class NgDocRoutePreloader {\n private readonly document = inject(DOCUMENT);\n private readonly injector = inject(Injector);\n private readonly ngZone = inject(NgZone);\n private readonly router = inject(Router);\n private readonly locationStrategy = inject(LocationStrategy);\n private readonly browser = isPlatformBrowser(inject(PLATFORM_ID));\n /** The path segments under which the documentation's routes live (`routePrefix`). */\n private readonly prefix = (inject(NG_DOC_ROUTE_PREFIX, { optional: true }) ?? '')\n .split('/')\n .filter(Boolean);\n /** The pages that were asked for, by path, so each page is preloaded once. */\n private readonly requested = new Set<string>();\n /** The paths, as URL segments, whose routes are being loaded. */\n private readonly loading = new Map<string, readonly string[]>();\n private enabled = false;\n private destroyed = false;\n private lastAnchor?: Element;\n private cancelIdle?: () => void;\n\n private readonly intent = (event: Event): void => {\n const target = event.target;\n const anchor =\n typeof Element !== 'undefined' && target instanceof Element\n ? target.closest('a[href]')\n : null;\n\n // Pointer events fire for every element under the pointer, so a link is checked only when\n // the pointer reaches it.\n if (!anchor || (anchor === this.lastAnchor && event.type !== 'focusin')) return;\n this.lastAnchor = anchor;\n\n const url = this.linkUrl(anchor);\n\n if (url !== undefined) this.preload(url);\n };\n\n constructor() {\n inject(DestroyRef).onDestroy(() => this.destroy());\n }\n\n /**\n * Turns preloading on and starts listening for links the reader is about to open. Called by\n * `NgDocPreloadingStrategy` when the router creates it; does nothing on the server.\n * @internal\n */\n enable(): void {\n if (this.enabled || this.destroyed || !this.browser) return;\n this.enabled = true;\n\n const view = this.document.defaultView;\n\n if (!view) return;\n // A link the reader points at must not run change detection in a zone application.\n this.ngZone.runOutsideAngular(() => {\n INTENT_EVENTS.forEach((type: string) =>\n this.document.addEventListener(type, this.intent, { capture: true, passive: true }),\n );\n });\n }\n\n /**\n * Preloads the code and the content of the page at a URL of the application. A page is\n * preloaded once; the current page is not preloaded.\n * @param url - The URL, relative to the application's base, for example `/docs/get-started`.\n * @returns Whether a preload started.\n */\n preload(url: string | UrlTree): boolean {\n if (!this.active()) return false;\n\n let segments: string[];\n\n try {\n segments = urlSegments(typeof url === 'string' ? this.router.parseUrl(url) : url);\n } catch {\n return false;\n }\n\n // Only the documentation's own pages are preloaded, never the rest of the application.\n if (this.prefix.some((part, index) => segments[index] !== part)) return false;\n\n const key = segments.join('/');\n\n if (this.requested.has(key) || key === urlSegments(this.currentUrl()).join('/')) return false;\n this.requested.add(key);\n this.loading.set(key, segments);\n\n this.ngZone.runOutsideAngular(() => {\n this.injector\n .get(RouterPreloader)\n .preload()\n .subscribe({ complete: () => this.loaded(key, segments) });\n });\n\n return true;\n }\n\n /**\n * Preloads pages once the browser is idle. A later call replaces the pages of an earlier one\n * that has not run yet.\n * @param urls - The URLs of the pages; missing ones are skipped.\n */\n preloadWhenIdle(urls: ReadonlyArray<string | undefined>): void {\n this.cancelIdle?.();\n this.cancelIdle = undefined;\n\n const view = this.document.defaultView;\n const pages = urls.filter((url): url is string => !!url);\n\n if (!this.active() || !view || !pages.length) return;\n\n const run = (): void => {\n this.cancelIdle = undefined;\n pages.forEach((url: string) => this.preload(url));\n };\n\n this.ngZone.runOutsideAngular(() => {\n if (typeof view.requestIdleCallback === 'function') {\n const handle = view.requestIdleCallback(run, { timeout: IDLE_TIMEOUT });\n\n this.cancelIdle = () => view.cancelIdleCallback(handle);\n } else {\n const handle = view.setTimeout(run, IDLE_FALLBACK_DELAY);\n\n this.cancelIdle = () => view.clearTimeout(handle);\n }\n });\n }\n\n /**\n * Whether the router should load a lazy route now: the route leads to a page that is being\n * preloaded.\n * @param route - A lazy route that the router has not loaded yet.\n * @returns Whether to load it.\n * @internal\n */\n shouldPreload(route: Route): boolean {\n if (!this.enabled || this.destroyed) return false;\n for (const segments of this.loading.values()) {\n if (routesOnPath(this.router.config, segments).has(route)) return true;\n }\n return false;\n }\n\n private loaded(key: string, segments: readonly string[]): void {\n this.loading.delete(key);\n if (this.destroyed) return;\n\n const routes = routesOnPath(this.router.config, segments);\n\n // The strategy turns a failed load into no load, so the router's own preloading survives\n // it. A route that is still not loaded failed: a later intent may try again.\n if ([...routes].some((route) => route.loadChildren && !loadedRoutes(route))) {\n this.requested.delete(key);\n return;\n }\n // The routes of the page have loaded, so the content sources of their components are known.\n for (const route of routes) {\n for (const source of ɵngDocRouteContentSources(route)) {\n ɵpreloadNgDocContent(source).catch(() => undefined);\n }\n }\n }\n\n private active(): boolean {\n return this.enabled && !this.destroyed && !constrainedConnection(this.document);\n }\n\n private currentUrl(): UrlTree {\n return this.router.parseUrl(this.router.url);\n }\n\n /**\n * Returns the application URL of a link, or `undefined` for a link that leaves the\n * application, opens elsewhere, downloads a file or points into the current page.\n * @param anchor - The link.\n * @returns The URL relative to the application's base, with its query.\n */\n private linkUrl(anchor: Element): string | undefined {\n const view = this.document.defaultView;\n const href = anchor.getAttribute('href');\n const target = anchor.getAttribute('target');\n\n if (\n !view ||\n !href ||\n anchor.hasAttribute('download') ||\n (target && target !== '_self') ||\n href.startsWith('#')\n ) {\n return undefined;\n }\n\n let url: URL;\n\n try {\n url = new URL(href, this.document.baseURI);\n } catch {\n return undefined;\n }\n\n // Another site, or the current page (an anchor on it, or a link to itself).\n if (url.origin !== view.location.origin || url.pathname === view.location.pathname) {\n return undefined;\n }\n\n const base = this.basePath();\n\n if (!`${url.pathname}/`.startsWith(base)) return undefined;\n\n return `/${url.pathname.slice(base.length)}${url.search}`;\n }\n\n /** The base path of the application, ending with a slash. */\n private basePath(): string {\n const base = this.locationStrategy.getBaseHref() || '/';\n const path = new URL(base, this.document.baseURI).pathname;\n\n return path.endsWith('/') ? path : `${path}/`;\n }\n\n private destroy(): void {\n this.destroyed = true;\n this.cancelIdle?.();\n this.cancelIdle = undefined;\n this.loading.clear();\n INTENT_EVENTS.forEach((type: string) =>\n this.document.removeEventListener(type, this.intent, { capture: true }),\n );\n }\n}\n\n/**\n * Whether the reader asked the browser to save data or the connection is 2G, where preloading\n * would compete with what the reader opens.\n * @param document - The document of the application.\n * @returns Whether to skip preloading.\n */\nfunction constrainedConnection(document: Document): boolean {\n const navigator = document.defaultView?.navigator as\n | (Navigator & { connection?: NgDocNetworkInformation })\n | undefined;\n const connection = navigator?.connection;\n\n return (\n !!connection &&\n (connection.saveData === true || /(^|-)2g$/.test(connection.effectiveType ?? ''))\n );\n}\n\n/**\n * Returns the path segments of the primary outlet of a URL.\n * @param url - The URL.\n * @returns The segments.\n */\nfunction urlSegments(url: UrlTree): string[] {\n return url.root.children[PRIMARY_OUTLET]?.segments.map(({ path }) => path) ?? [];\n}\n\n/**\n * Returns the routes that can match a path: those whose own path matches its start, through\n * the children the router has loaded so far. A lazy route whose children have not loaded yet\n * may lead anywhere below its path.\n * @param routes - The routes to search.\n * @param segments - The path segments.\n * @param found - Collects the routes.\n * @returns The routes.\n */\nfunction routesOnPath(\n routes: readonly Route[],\n segments: readonly string[],\n found: Set<Route> = new Set(),\n): Set<Route> {\n for (const route of routes) {\n if (\n (route.outlet ?? PRIMARY_OUTLET) !== PRIMARY_OUTLET ||\n route.matcher ||\n route.redirectTo !== undefined ||\n // A guarded route may not match for this reader; only opening the page may decide.\n route.canMatch?.length\n ) {\n continue;\n }\n\n const consumed = matchedSegments(route.path ?? '', segments);\n\n if (consumed === undefined) continue;\n\n const rest = segments.slice(consumed);\n const children = route.children ?? loadedRoutes(route);\n // A route without children matches only the whole remaining path.\n const leaf = !route.children && !route.loadChildren;\n\n if ((route.pathMatch === 'full' || leaf) && rest.length) continue;\n\n found.add(route);\n if (children) routesOnPath(children, rest, found);\n }\n\n return found;\n}\n\n/**\n * Returns how many segments a route path matches at the start of a path.\n * @param path - The route path.\n * @param segments - The path segments.\n * @returns The number of matched segments, or `undefined` when the route does not match.\n */\nfunction matchedSegments(path: string, segments: readonly string[]): number | undefined {\n if (!path) return 0;\n\n const parts = path.split('/');\n\n for (let index = 0; index < parts.length; index++) {\n const part = parts[index];\n\n if (part === '**') return segments.length;\n if (index >= segments.length) return undefined;\n if (!part.startsWith(':') && part !== segments[index]) return undefined;\n }\n\n return parts.length;\n}\n\n/**\n * Returns the children that the router loaded for a lazy route. The router keeps them on the\n * route itself and has no public API for them; its own preloader reads the same property.\n * Without it, preloading stops at the first lazy route.\n * @param route - The route.\n * @returns The loaded children, if any.\n */\nfunction loadedRoutes(route: Route): readonly Route[] | undefined {\n return (route as Route & { _loadedRoutes?: Route[] })._loadedRoutes;\n}\n","import { inject, Service } from '@angular/core';\nimport { PreloadingStrategy, Route } from '@angular/router';\nimport { catchError, Observable, of } from 'rxjs';\n\nimport { NgDocRoutePreloader } from './route-preloader.service';\n\n/**\n * The router preloading strategy of NgDoc: it loads a lazy route only when the reader is about\n * to open one of its pages, as `NgDocRoutePreloader` decides, instead of every route at once.\n *\n * Set it as the router's preloading strategy to preload pages on intent:\n *\n * ```ts\n * provideRouter(routes, withPreloading(NgDocPreloadingStrategy));\n * ```\n */\n@Service()\nexport class NgDocPreloadingStrategy implements PreloadingStrategy {\n private readonly preloader = inject(NgDocRoutePreloader);\n\n constructor() {\n // The router creates its preloading strategy at start-up, so this is where preloading on\n // intent starts.\n this.preloader.enable();\n }\n\n /**\n * Loads a lazy route when it leads to a page that is being preloaded.\n * @param route - A lazy route that the router has not loaded yet.\n * @param load - Loads the route.\n * @returns The load, or an observable of `null` when the route is not loaded now.\n */\n preload(route: Route, load: () => Observable<unknown>): Observable<unknown> {\n if (!this.preloader.shouldPreload(route)) return of(null);\n\n // A failed preload is not an error: opening the page loads the route again and reports it.\n return load().pipe(catchError(() => of(null)));\n }\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":["ɵngDocRouteContentSources","ɵpreloadNgDocContent"],"mappings":"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"}
1
+ {"version":3,"file":"ng-doc-app-services-route-preloader.mjs","sources":["../../../../libs/app/services/route-preloader/route-preloader.service.ts","../../../../libs/app/services/route-preloader/preloading-strategy.ts","../../../../libs/app/services/route-preloader/ng-doc-app-services-route-preloader.ts"],"sourcesContent":["import { isPlatformBrowser, LocationStrategy } from '@angular/common';\nimport {\n DestroyRef,\n DOCUMENT,\n inject,\n Injector,\n NgZone,\n PLATFORM_ID,\n Service,\n} from '@angular/core';\nimport { PRIMARY_OUTLET, Route, Router, RouterPreloader, UrlTree } from '@angular/router';\nimport {\n ɵngDocRouteContentSources,\n ɵngDocRouteUrl,\n ɵpreloadNgDocContent,\n} from '@ng-doc/app/helpers';\nimport { NG_DOC_ROUTE_PREFIX } from '@ng-doc/app/tokens';\n\n/** The events that tell that the reader is about to open a link. */\nconst INTENT_EVENTS = ['pointerover', 'mouseover', 'focusin', 'touchstart'] as const;\n\n/** How long the browser may stay busy before an idle preload runs anyway, in milliseconds. */\nconst IDLE_TIMEOUT = 2000;\n/** The delay of an idle preload where the browser has no `requestIdleCallback`. */\nconst IDLE_FALLBACK_DELAY = 200;\n\n/** The parts of the Network Information API that preloading reads. */\ninterface NgDocNetworkInformation {\n readonly saveData?: boolean;\n readonly effectiveType?: string;\n}\n\n/**\n * Preloads the pages that the reader is about to open, so that opening them shows them at once.\n *\n * It preloads a page when the reader points at, focuses or touches a link to it, anywhere in the\n * document: the sidebar, the previous and next page links, links in the page content, search\n * results and the table of contents. It also preloads the previous and next pages of a guide once\n * the browser is idle after the guide has rendered. A preload loads the code of the page's route\n * and its content, once per page. Nothing is preloaded on the server, when the reader asked the\n * browser to save data, or on a 2G connection.\n *\n * The router loads the code through its preloading strategy, so preloading works only when the\n * application sets `NgDocPreloadingStrategy` as that strategy:\n *\n * ```ts\n * provideRouter(routes, withPreloading(NgDocPreloadingStrategy));\n * ```\n */\n@Service()\nexport class NgDocRoutePreloader {\n private readonly document = inject(DOCUMENT);\n private readonly injector = inject(Injector);\n private readonly ngZone = inject(NgZone);\n private readonly router = inject(Router);\n private readonly locationStrategy = inject(LocationStrategy);\n private readonly browser = isPlatformBrowser(inject(PLATFORM_ID));\n /** The path segments under which the documentation's routes live (`routePrefix`). */\n private readonly prefix = (inject(NG_DOC_ROUTE_PREFIX, { optional: true }) ?? '')\n .split('/')\n .filter(Boolean);\n /** The pages that were asked for, by path, so each page is preloaded once. */\n private readonly requested = new Set<string>();\n /** The paths, as URL segments, whose routes are being loaded. */\n private readonly loading = new Map<string, readonly string[]>();\n private enabled = false;\n private destroyed = false;\n private lastAnchor?: Element;\n private cancelIdle?: () => void;\n\n private readonly intent = (event: Event): void => {\n const target = event.target;\n const anchor =\n typeof Element !== 'undefined' && target instanceof Element\n ? target.closest('a[href]')\n : null;\n\n // Pointer events fire for every element under the pointer, so a link is checked only when\n // the pointer reaches it.\n if (!anchor || (anchor === this.lastAnchor && event.type !== 'focusin')) return;\n this.lastAnchor = anchor;\n\n const url = this.linkUrl(anchor);\n\n if (url !== undefined) this.preload(url);\n };\n\n constructor() {\n inject(DestroyRef).onDestroy(() => this.destroy());\n }\n\n /**\n * Turns preloading on and starts listening for links the reader is about to open. Called by\n * `NgDocPreloadingStrategy` when the router creates it; does nothing on the server.\n * @internal\n */\n enable(): void {\n if (this.enabled || this.destroyed || !this.browser) return;\n this.enabled = true;\n\n const view = this.document.defaultView;\n\n if (!view) return;\n // A link the reader points at must not run change detection in a zone application.\n this.ngZone.runOutsideAngular(() => {\n INTENT_EVENTS.forEach((type: string) =>\n this.document.addEventListener(type, this.intent, { capture: true, passive: true }),\n );\n });\n }\n\n /**\n * Preloads the code and the content of the page at a URL of the application. A page is\n * preloaded once; the current page is not preloaded.\n * @param url - The URL, relative to the application's base, for example `/docs/get-started`.\n * @returns Whether a preload started.\n */\n preload(url: string | UrlTree): boolean {\n if (!this.active()) return false;\n\n let segments: string[];\n\n try {\n segments = urlSegments(typeof url === 'string' ? this.router.parseUrl(url) : url);\n } catch {\n return false;\n }\n\n // Only the documentation's own pages are preloaded, never the rest of the application.\n if (this.prefix.some((part, index) => segments[index] !== part)) return false;\n\n const key = segments.join('/');\n\n if (this.requested.has(key) || key === urlSegments(this.currentUrl()).join('/')) return false;\n this.requested.add(key);\n this.loading.set(key, segments);\n\n this.ngZone.runOutsideAngular(() => {\n this.injector\n .get(RouterPreloader)\n .preload()\n .subscribe({ complete: () => this.loaded(key, segments) });\n });\n\n return true;\n }\n\n /**\n * Preloads pages once the browser is idle. A later call replaces the pages of an earlier one\n * that has not run yet.\n * @param urls - The URLs of the pages; missing ones are skipped.\n */\n preloadWhenIdle(urls: ReadonlyArray<string | undefined>): void {\n this.cancelIdle?.();\n this.cancelIdle = undefined;\n\n const view = this.document.defaultView;\n const pages = urls.filter((url): url is string => !!url);\n\n if (!this.active() || !view || !pages.length) return;\n\n const run = (): void => {\n this.cancelIdle = undefined;\n pages.forEach((url: string) => this.preload(url));\n };\n\n this.ngZone.runOutsideAngular(() => {\n if (typeof view.requestIdleCallback === 'function') {\n const handle = view.requestIdleCallback(run, { timeout: IDLE_TIMEOUT });\n\n this.cancelIdle = () => view.cancelIdleCallback(handle);\n } else {\n const handle = view.setTimeout(run, IDLE_FALLBACK_DELAY);\n\n this.cancelIdle = () => view.clearTimeout(handle);\n }\n });\n }\n\n /**\n * Whether the router should load a lazy route now: the route leads to a page that is being\n * preloaded.\n * @param route - A lazy route that the router has not loaded yet.\n * @returns Whether to load it.\n * @internal\n */\n shouldPreload(route: Route): boolean {\n if (!this.enabled || this.destroyed) return false;\n for (const segments of this.loading.values()) {\n if (routesOnPath(this.router.config, segments).has(route)) return true;\n }\n return false;\n }\n\n private loaded(key: string, segments: readonly string[]): void {\n this.loading.delete(key);\n if (this.destroyed) return;\n\n const routes = routesOnPath(this.router.config, segments);\n\n // The strategy turns a failed load into no load, so the router's own preloading survives\n // it. A route that is still not loaded failed: a later intent may try again.\n if ([...routes].some((route) => route.loadChildren && !loadedRoutes(route))) {\n this.requested.delete(key);\n return;\n }\n // The routes of the page have loaded, so the content sources of their components are known.\n for (const route of routes) {\n for (const source of ɵngDocRouteContentSources(route)) {\n ɵpreloadNgDocContent(source).catch(() => undefined);\n }\n }\n }\n\n private active(): boolean {\n return this.enabled && !this.destroyed && !constrainedConnection(this.document);\n }\n\n private currentUrl(): UrlTree {\n return this.router.parseUrl(this.router.url);\n }\n\n /**\n * Returns the application URL of a link, or `undefined` for a link that leaves the\n * application, opens elsewhere, downloads a file or points into the current page.\n *\n * The link is read through the location strategy: with `PathLocationStrategy` the route is the\n * link's path, and with `HashLocationStrategy` (`withHashLocation()`) it is the link's fragment\n * (`#/docs/page`), so a fragment that is not a route (`#usage`) is an anchor on the page.\n * @param anchor - The link.\n * @returns The URL relative to the application's base, with its query.\n */\n private linkUrl(anchor: Element): string | undefined {\n const view = this.document.defaultView;\n const href = anchor.getAttribute('href');\n const target = anchor.getAttribute('target');\n\n if (!view || !href || anchor.hasAttribute('download') || (target && target !== '_self')) {\n return undefined;\n }\n\n let url: URL;\n\n try {\n url = new URL(href, this.document.baseURI);\n } catch {\n return undefined;\n }\n\n if (url.origin !== view.location.origin) return undefined;\n\n if (this.hashLocation()) {\n // The route lives in the fragment of this document; another document leaves the\n // application.\n if (url.pathname !== view.location.pathname || !url.hash.startsWith('#/')) return undefined;\n\n url = new URL(url.hash.slice(1).replace(/^\\/*/, '/'), view.location.origin);\n } else if (href.startsWith('#')) {\n return undefined;\n }\n\n // The current page: an anchor on it, or a link to itself.\n if (url.pathname === ɵngDocRouteUrl(this.locationStrategy, view.location.href).pathname) {\n return undefined;\n }\n\n const base = this.basePath();\n\n if (!`${url.pathname}/`.startsWith(base)) return undefined;\n\n return `/${url.pathname.slice(base.length)}${url.search}`;\n }\n\n /**\n * Whether the location strategy keeps the route in the document's fragment, as\n * `HashLocationStrategy` does.\n */\n private hashLocation(): boolean {\n return this.locationStrategy.prepareExternalUrl('/').startsWith('#');\n }\n\n /**\n * The base path of the application's routes, ending with a slash: the base href, which a\n * fragment route carries only when `APP_BASE_HREF` sets one.\n */\n private basePath(): string {\n const base = this.locationStrategy.getBaseHref() || '/';\n const path = this.hashLocation()\n ? base.replace(/^\\/*/, '/')\n : new URL(base, this.document.baseURI).pathname;\n\n return path.endsWith('/') ? path : `${path}/`;\n }\n\n private destroy(): void {\n this.destroyed = true;\n this.cancelIdle?.();\n this.cancelIdle = undefined;\n this.loading.clear();\n INTENT_EVENTS.forEach((type: string) =>\n this.document.removeEventListener(type, this.intent, { capture: true }),\n );\n }\n}\n\n/**\n * Whether the reader asked the browser to save data or the connection is 2G, where preloading\n * would compete with what the reader opens.\n * @param document - The document of the application.\n * @returns Whether to skip preloading.\n */\nfunction constrainedConnection(document: Document): boolean {\n const navigator = document.defaultView?.navigator as\n | (Navigator & { connection?: NgDocNetworkInformation })\n | undefined;\n const connection = navigator?.connection;\n\n return (\n !!connection &&\n (connection.saveData === true || /(^|-)2g$/.test(connection.effectiveType ?? ''))\n );\n}\n\n/**\n * Returns the path segments of the primary outlet of a URL.\n * @param url - The URL.\n * @returns The segments.\n */\nfunction urlSegments(url: UrlTree): string[] {\n return url.root.children[PRIMARY_OUTLET]?.segments.map(({ path }) => path) ?? [];\n}\n\n/**\n * Returns the routes that can match a path: those whose own path matches its start, through\n * the children the router has loaded so far. A lazy route whose children have not loaded yet\n * may lead anywhere below its path.\n * @param routes - The routes to search.\n * @param segments - The path segments.\n * @param found - Collects the routes.\n * @returns The routes.\n */\nfunction routesOnPath(\n routes: readonly Route[],\n segments: readonly string[],\n found: Set<Route> = new Set(),\n): Set<Route> {\n for (const route of routes) {\n if (\n (route.outlet ?? PRIMARY_OUTLET) !== PRIMARY_OUTLET ||\n route.matcher ||\n route.redirectTo !== undefined ||\n // A guarded route may not match for this reader; only opening the page may decide.\n route.canMatch?.length\n ) {\n continue;\n }\n\n const consumed = matchedSegments(route.path ?? '', segments);\n\n if (consumed === undefined) continue;\n\n const rest = segments.slice(consumed);\n const children = route.children ?? loadedRoutes(route);\n // A route without children matches only the whole remaining path.\n const leaf = !route.children && !route.loadChildren;\n\n if ((route.pathMatch === 'full' || leaf) && rest.length) continue;\n\n found.add(route);\n if (children) routesOnPath(children, rest, found);\n }\n\n return found;\n}\n\n/**\n * Returns how many segments a route path matches at the start of a path.\n * @param path - The route path.\n * @param segments - The path segments.\n * @returns The number of matched segments, or `undefined` when the route does not match.\n */\nfunction matchedSegments(path: string, segments: readonly string[]): number | undefined {\n if (!path) return 0;\n\n const parts = path.split('/');\n\n for (let index = 0; index < parts.length; index++) {\n const part = parts[index];\n\n if (part === '**') return segments.length;\n if (index >= segments.length) return undefined;\n if (!part.startsWith(':') && part !== segments[index]) return undefined;\n }\n\n return parts.length;\n}\n\n/**\n * Returns the children that the router loaded for a lazy route. The router keeps them on the\n * route itself and has no public API for them; its own preloader reads the same property.\n * Without it, preloading stops at the first lazy route.\n * @param route - The route.\n * @returns The loaded children, if any.\n */\nfunction loadedRoutes(route: Route): readonly Route[] | undefined {\n return (route as Route & { _loadedRoutes?: Route[] })._loadedRoutes;\n}\n","import { inject, Service } from '@angular/core';\nimport { PreloadingStrategy, Route } from '@angular/router';\nimport { catchError, Observable, of } from 'rxjs';\n\nimport { NgDocRoutePreloader } from './route-preloader.service';\n\n/**\n * The router preloading strategy of NgDoc: it loads a lazy route only when the reader is about\n * to open one of its pages, as `NgDocRoutePreloader` decides, instead of every route at once.\n *\n * Set it as the router's preloading strategy to preload pages on intent:\n *\n * ```ts\n * provideRouter(routes, withPreloading(NgDocPreloadingStrategy));\n * ```\n */\n@Service()\nexport class NgDocPreloadingStrategy implements PreloadingStrategy {\n private readonly preloader = inject(NgDocRoutePreloader);\n\n constructor() {\n // The router creates its preloading strategy at start-up, so this is where preloading on\n // intent starts.\n this.preloader.enable();\n }\n\n /**\n * Loads a lazy route when it leads to a page that is being preloaded.\n * @param route - A lazy route that the router has not loaded yet.\n * @param load - Loads the route.\n * @returns The load, or an observable of `null` when the route is not loaded now.\n */\n preload(route: Route, load: () => Observable<unknown>): Observable<unknown> {\n if (!this.preloader.shouldPreload(route)) return of(null);\n\n // A failed preload is not an error: opening the page loads the route again and reports it.\n return load().pipe(catchError(() => of(null)));\n }\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":["ɵngDocRouteContentSources","ɵpreloadNgDocContent","ɵngDocRouteUrl"],"mappings":"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"}
@@ -1,15 +1,16 @@
1
1
  import { NgTemplateOutlet } from '@angular/common';
2
2
  import * as i0 from '@angular/core';
3
- import { input, ChangeDetectionStrategy, Component } from '@angular/core';
3
+ import { input, computed, ChangeDetectionStrategy, Component } from '@angular/core';
4
4
  import * as i1 from '@angular/forms';
5
5
  import { FormsModule } from '@angular/forms';
6
- import { NgDocExtractValuePipe } from '@ng-doc/app/pipes/extract-value';
6
+ import { resolvePlaygroundOption } from '@ng-doc/app/helpers';
7
7
  import { NgDocComboboxComponent, NgDocDataDirective, NgDocListComponent, NgDocOptionComponent, NgDocButtonIconComponent, NgDocFocusableDirective, NgDocIconComponent } from '@ng-doc/ui-kit';
8
8
  import { DIControl } from 'di-controls';
9
9
 
10
10
  /**
11
- * The playground control of union type aliases: a select that shows the value and whether it is
12
- * the default. The inspector shows the value type under the input's name.
11
+ * The playground control of union type aliases and enums: a select that shows the value (or the
12
+ * name of an enum member) and whether it is the default. The inspector shows the value type under
13
+ * the input's name.
13
14
  */
14
15
  class NgDocTypeAliasControlComponent extends DIControl {
15
16
  constructor() {
@@ -17,22 +18,57 @@ class NgDocTypeAliasControlComponent extends DIControl {
17
18
  /** Default value of the input; clearing the selection returns to it. */
18
19
  this.default = input(undefined, /* @ts-ignore */
19
20
  ...(ngDevMode ? [{ debugName: "default" }] : /* istanbul ignore next */ []));
20
- /** Values the input accepts: literal source text, or plain values when `isManual` is set. */
21
+ /**
22
+ * Values the input accepts: literal source text, plain values when `isManual` is set, or the
23
+ * names and values of enum members.
24
+ */
21
25
  this.options = input(undefined, /* @ts-ignore */
22
26
  ...(ngDevMode ? [{ debugName: "options" }] : /* istanbul ignore next */ []));
23
27
  /** Whether `options` are values the user listed in the playground config. */
24
28
  this.isManual = input(undefined, /* @ts-ignore */
25
29
  ...(ngDevMode ? [{ debugName: "isManual" }] : /* istanbul ignore next */ []));
30
+ /**
31
+ * The options of the select. `undefined` and `null` get no option: clearing the selection
32
+ * returns to the default value instead.
33
+ */
34
+ this.items = computed(() => {
35
+ const isManual = this.isManual();
36
+ return (this.options() ?? []).flatMap((option) => {
37
+ try {
38
+ const item = resolvePlaygroundOption(option, isManual);
39
+ return item.value === undefined || item.value === null ? [] : [item];
40
+ }
41
+ catch {
42
+ return [];
43
+ }
44
+ });
45
+ }, /* @ts-ignore */
46
+ ...(ngDevMode ? [{ debugName: "items" }] : /* istanbul ignore next */ []));
26
47
  }
27
48
  typeOf(value) {
28
49
  return typeof value;
29
50
  }
51
+ /**
52
+ * The text of a value: the name of its option, such as the name of an enum member.
53
+ * @param value - A value of the input.
54
+ */
55
+ labelOf(value) {
56
+ return (this.items().find((item) => item.value === value)?.label ??
57
+ String(value ?? ''));
58
+ }
59
+ /**
60
+ * Whether the reset button is shown: a value other than the default is selected.
61
+ * @param value - The selected value.
62
+ */
63
+ canReset(value) {
64
+ return value !== undefined && value !== null && value !== this.default();
65
+ }
30
66
  changeModel(value) {
31
67
  const defaultValue = this.default();
32
68
  this.updateModel(value === null && defaultValue ? defaultValue : value);
33
69
  }
34
70
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.2.1", ngImport: i0, type: NgDocTypeAliasControlComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
35
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.2.1", type: NgDocTypeAliasControlComponent, isStandalone: true, selector: "ng-doc-type-alias-control", inputs: { default: { classPropertyName: "default", publicName: "default", isSignal: true, isRequired: false, transformFunction: null }, options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: false, transformFunction: null }, isManual: { classPropertyName: "isManual", publicName: "isManual", isSignal: true, isRequired: false, transformFunction: null } }, usesInheritance: true, ngImport: i0, template: "<ng-doc-combobox\n [ngModel]=\"model()\"\n [readonly]=\"true\"\n [valueContent]=\"valueTemplate\"\n [clearButton]=\"false\"\n [rightContent]=\"rightContent\"\n (ngModelChange)=\"changeModel($event)\">\n <ng-doc-list *ngDocData>\n @for (option of options(); track option) {\n @if (isManual() ? option : (option | ngDocExtractValue); as value) {\n <ng-doc-option [value]=\"value\">\n <ng-container\n *ngTemplateOutlet=\"valueTemplate; context: { $implicit: value }\"></ng-container>\n </ng-doc-option>\n }\n }\n </ng-doc-list>\n <ng-template let-value #valueTemplate>\n <div class=\"ng-doc-type-alias-value\">\n <span class=\"ng-doc-type-alias-value-text\">{{ value }}</span>\n @if (value === default()) {\n <span class=\"ng-doc-type-alias-default\">default</span>\n }\n </div>\n </ng-template>\n <ng-template #rightContent>\n @if (model() && model() !== default()) {\n <button\n ng-doc-button-icon-transparent\n size=\"large\"\n aria-label=\"Reset to the default value\"\n [ngDocFocusable]=\"false\"\n [disabled]=\"disabled\"\n [rounded]=\"false\"\n (click)=\"updateModel(default() ?? null)\"\n (focusout)=\"touch()\">\n <ng-doc-icon icon=\"x\"></ng-doc-icon>\n </button>\n }\n </ng-template>\n</ng-doc-combobox>\n", styles: [":host{display:block;width:100%}.ng-doc-type-alias-value{display:flex;align-items:center;gap:var(--ng-doc-space-2);width:100%;min-width:0;font-family:var(--ng-doc-font-family);font-size:14px;line-height:20px;color:var(--ng-doc-heading-color)}.ng-doc-type-alias-value-text{min-width:0;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.ng-doc-type-alias-default{margin-left:auto;padding-right:var(--ng-doc-space-2);font-size:12px;color:var(--ng-doc-text-muted);white-space:nowrap}\n"], dependencies: [{ kind: "component", type: NgDocComboboxComponent, selector: "ng-doc-combobox", inputs: ["readonly", "placeholder", "clearButton", "rightContent", "valueContent"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: NgDocDataDirective, selector: "[ngDocData]" }, { kind: "component", type: NgDocListComponent, selector: "ng-doc-list" }, { kind: "component", type: NgDocOptionComponent, selector: "ng-doc-option" }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: NgDocButtonIconComponent, selector: "button[ng-doc-button-icon], a[ng-doc-button-icon], button[ng-doc-button-icon-raised], a[ng-doc-button-icon-raised], button[ng-doc-button-icon-transparent], a[ng-doc-button-icon-transparent]", inputs: ["size", "rounded"] }, { kind: "directive", type: NgDocFocusableDirective, selector: "[ngDocFocusable]", inputs: ["ngDocFocusable"], exportAs: ["ngDocFocusable"] }, { kind: "component", type: NgDocIconComponent, selector: "ng-doc-icon", inputs: ["icon", "customIcon", "size"] }, { kind: "pipe", type: NgDocExtractValuePipe, name: "ngDocExtractValue" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
71
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.2.1", type: NgDocTypeAliasControlComponent, isStandalone: true, selector: "ng-doc-type-alias-control", inputs: { default: { classPropertyName: "default", publicName: "default", isSignal: true, isRequired: false, transformFunction: null }, options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: false, transformFunction: null }, isManual: { classPropertyName: "isManual", publicName: "isManual", isSignal: true, isRequired: false, transformFunction: null } }, usesInheritance: true, ngImport: i0, template: "<ng-doc-combobox\n [ngModel]=\"model()\"\n [readonly]=\"true\"\n [valueContent]=\"valueTemplate\"\n [clearButton]=\"false\"\n [rightContent]=\"rightContent\"\n (ngModelChange)=\"changeModel($event)\">\n <ng-doc-list *ngDocData>\n @for (item of items(); track $index) {\n <ng-doc-option [value]=\"item.value\">\n <ng-container\n *ngTemplateOutlet=\"valueTemplate; context: { $implicit: item.value }\"></ng-container>\n </ng-doc-option>\n }\n </ng-doc-list>\n <ng-template let-value #valueTemplate>\n <div class=\"ng-doc-type-alias-value\">\n <span class=\"ng-doc-type-alias-value-text\">{{ labelOf(value) }}</span>\n @if (value === default()) {\n <span class=\"ng-doc-type-alias-default\">default</span>\n }\n </div>\n </ng-template>\n <ng-template #rightContent>\n @if (canReset(model())) {\n <button\n ng-doc-button-icon-transparent\n size=\"large\"\n aria-label=\"Reset to the default value\"\n [ngDocFocusable]=\"false\"\n [disabled]=\"disabled\"\n [rounded]=\"false\"\n (click)=\"updateModel(default() ?? null)\"\n (focusout)=\"touch()\">\n <ng-doc-icon icon=\"x\"></ng-doc-icon>\n </button>\n }\n </ng-template>\n</ng-doc-combobox>\n", styles: [":host{display:block;width:100%}.ng-doc-type-alias-value{display:flex;align-items:center;gap:var(--ng-doc-space-2);width:100%;min-width:0;font-family:var(--ng-doc-font-family);font-size:14px;line-height:20px;color:var(--ng-doc-heading-color)}.ng-doc-type-alias-value-text{min-width:0;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.ng-doc-type-alias-default{margin-left:auto;padding-right:var(--ng-doc-space-2);font-size:12px;color:var(--ng-doc-text-muted);white-space:nowrap}\n"], dependencies: [{ kind: "component", type: NgDocComboboxComponent, selector: "ng-doc-combobox", inputs: ["readonly", "placeholder", "clearButton", "rightContent", "valueContent"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: NgDocDataDirective, selector: "[ngDocData]" }, { kind: "component", type: NgDocListComponent, selector: "ng-doc-list" }, { kind: "component", type: NgDocOptionComponent, selector: "ng-doc-option" }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: NgDocButtonIconComponent, selector: "button[ng-doc-button-icon], a[ng-doc-button-icon], button[ng-doc-button-icon-raised], a[ng-doc-button-icon-raised], button[ng-doc-button-icon-transparent], a[ng-doc-button-icon-transparent]", inputs: ["size", "rounded"] }, { kind: "directive", type: NgDocFocusableDirective, selector: "[ngDocFocusable]", inputs: ["ngDocFocusable"], exportAs: ["ngDocFocusable"] }, { kind: "component", type: NgDocIconComponent, selector: "ng-doc-icon", inputs: ["icon", "customIcon", "size"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
36
72
  }
37
73
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.2.1", ngImport: i0, type: NgDocTypeAliasControlComponent, decorators: [{
38
74
  type: Component,
@@ -46,8 +82,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.2.1", ngImpor
46
82
  NgDocButtonIconComponent,
47
83
  NgDocFocusableDirective,
48
84
  NgDocIconComponent,
49
- NgDocExtractValuePipe,
50
- ], template: "<ng-doc-combobox\n [ngModel]=\"model()\"\n [readonly]=\"true\"\n [valueContent]=\"valueTemplate\"\n [clearButton]=\"false\"\n [rightContent]=\"rightContent\"\n (ngModelChange)=\"changeModel($event)\">\n <ng-doc-list *ngDocData>\n @for (option of options(); track option) {\n @if (isManual() ? option : (option | ngDocExtractValue); as value) {\n <ng-doc-option [value]=\"value\">\n <ng-container\n *ngTemplateOutlet=\"valueTemplate; context: { $implicit: value }\"></ng-container>\n </ng-doc-option>\n }\n }\n </ng-doc-list>\n <ng-template let-value #valueTemplate>\n <div class=\"ng-doc-type-alias-value\">\n <span class=\"ng-doc-type-alias-value-text\">{{ value }}</span>\n @if (value === default()) {\n <span class=\"ng-doc-type-alias-default\">default</span>\n }\n </div>\n </ng-template>\n <ng-template #rightContent>\n @if (model() && model() !== default()) {\n <button\n ng-doc-button-icon-transparent\n size=\"large\"\n aria-label=\"Reset to the default value\"\n [ngDocFocusable]=\"false\"\n [disabled]=\"disabled\"\n [rounded]=\"false\"\n (click)=\"updateModel(default() ?? null)\"\n (focusout)=\"touch()\">\n <ng-doc-icon icon=\"x\"></ng-doc-icon>\n </button>\n }\n </ng-template>\n</ng-doc-combobox>\n", styles: [":host{display:block;width:100%}.ng-doc-type-alias-value{display:flex;align-items:center;gap:var(--ng-doc-space-2);width:100%;min-width:0;font-family:var(--ng-doc-font-family);font-size:14px;line-height:20px;color:var(--ng-doc-heading-color)}.ng-doc-type-alias-value-text{min-width:0;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.ng-doc-type-alias-default{margin-left:auto;padding-right:var(--ng-doc-space-2);font-size:12px;color:var(--ng-doc-text-muted);white-space:nowrap}\n"] }]
85
+ ], template: "<ng-doc-combobox\n [ngModel]=\"model()\"\n [readonly]=\"true\"\n [valueContent]=\"valueTemplate\"\n [clearButton]=\"false\"\n [rightContent]=\"rightContent\"\n (ngModelChange)=\"changeModel($event)\">\n <ng-doc-list *ngDocData>\n @for (item of items(); track $index) {\n <ng-doc-option [value]=\"item.value\">\n <ng-container\n *ngTemplateOutlet=\"valueTemplate; context: { $implicit: item.value }\"></ng-container>\n </ng-doc-option>\n }\n </ng-doc-list>\n <ng-template let-value #valueTemplate>\n <div class=\"ng-doc-type-alias-value\">\n <span class=\"ng-doc-type-alias-value-text\">{{ labelOf(value) }}</span>\n @if (value === default()) {\n <span class=\"ng-doc-type-alias-default\">default</span>\n }\n </div>\n </ng-template>\n <ng-template #rightContent>\n @if (canReset(model())) {\n <button\n ng-doc-button-icon-transparent\n size=\"large\"\n aria-label=\"Reset to the default value\"\n [ngDocFocusable]=\"false\"\n [disabled]=\"disabled\"\n [rounded]=\"false\"\n (click)=\"updateModel(default() ?? null)\"\n (focusout)=\"touch()\">\n <ng-doc-icon icon=\"x\"></ng-doc-icon>\n </button>\n }\n </ng-template>\n</ng-doc-combobox>\n", styles: [":host{display:block;width:100%}.ng-doc-type-alias-value{display:flex;align-items:center;gap:var(--ng-doc-space-2);width:100%;min-width:0;font-family:var(--ng-doc-font-family);font-size:14px;line-height:20px;color:var(--ng-doc-heading-color)}.ng-doc-type-alias-value-text{min-width:0;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.ng-doc-type-alias-default{margin-left:auto;padding-right:var(--ng-doc-space-2);font-size:12px;color:var(--ng-doc-text-muted);white-space:nowrap}\n"] }]
51
86
  }], ctorParameters: () => [], propDecorators: { default: [{ type: i0.Input, args: [{ isSignal: true, alias: "default", required: false }] }], options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: false }] }], isManual: [{ type: i0.Input, args: [{ isSignal: true, alias: "isManual", required: false }] }] } });
52
87
 
53
88
  /**