@ng-doc/app 22.0.0-beta.1 → 22.0.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 (205) hide show
  1. package/README.md +20 -29
  2. package/fesm2022/ng-doc-app-classes-content-anchor-controller.mjs +363 -0
  3. package/fesm2022/ng-doc-app-classes-content-anchor-controller.mjs.map +1 -0
  4. package/fesm2022/ng-doc-app-classes-content-controller.mjs +299 -0
  5. package/fesm2022/ng-doc-app-classes-content-controller.mjs.map +1 -0
  6. package/fesm2022/ng-doc-app-classes-default-search-engine.mjs +51 -35
  7. package/fesm2022/ng-doc-app-classes-default-search-engine.mjs.map +1 -1
  8. package/fesm2022/ng-doc-app-classes-root-page.mjs.map +1 -1
  9. package/fesm2022/ng-doc-app-classes-search-engine.mjs.map +1 -1
  10. package/fesm2022/ng-doc-app-classes.mjs +2 -0
  11. package/fesm2022/ng-doc-app-classes.mjs.map +1 -1
  12. package/fesm2022/ng-doc-app-components-api-list.mjs +178 -68
  13. package/fesm2022/ng-doc-app-components-api-list.mjs.map +1 -1
  14. package/fesm2022/ng-doc-app-components-breadcrumb.mjs +13 -10
  15. package/fesm2022/ng-doc-app-components-breadcrumb.mjs.map +1 -1
  16. package/fesm2022/ng-doc-app-components-code.mjs +152 -25
  17. package/fesm2022/ng-doc-app-components-code.mjs.map +1 -1
  18. package/fesm2022/ng-doc-app-components-command-palette.mjs +498 -0
  19. package/fesm2022/ng-doc-app-components-command-palette.mjs.map +1 -0
  20. package/fesm2022/ng-doc-app-components-copy-button.mjs +38 -21
  21. package/fesm2022/ng-doc-app-components-copy-button.mjs.map +1 -1
  22. package/fesm2022/ng-doc-app-components-demo-displayer.mjs +36 -29
  23. package/fesm2022/ng-doc-app-components-demo-displayer.mjs.map +1 -1
  24. package/fesm2022/ng-doc-app-components-demo-pane.mjs +39 -36
  25. package/fesm2022/ng-doc-app-components-demo-pane.mjs.map +1 -1
  26. package/fesm2022/ng-doc-app-components-demo.mjs +129 -44
  27. package/fesm2022/ng-doc-app-components-demo.mjs.map +1 -1
  28. package/fesm2022/ng-doc-app-components-fullscreen-button.mjs +12 -9
  29. package/fesm2022/ng-doc-app-components-fullscreen-button.mjs.map +1 -1
  30. package/fesm2022/ng-doc-app-components-fullscreen-toggle.mjs +79 -0
  31. package/fesm2022/ng-doc-app-components-fullscreen-toggle.mjs.map +1 -0
  32. package/fesm2022/ng-doc-app-components-heading-anchor.mjs +54 -21
  33. package/fesm2022/ng-doc-app-components-heading-anchor.mjs.map +1 -1
  34. package/fesm2022/ng-doc-app-components-image-viewer.mjs +187 -91
  35. package/fesm2022/ng-doc-app-components-image-viewer.mjs.map +1 -1
  36. package/fesm2022/ng-doc-app-components-kind-icon.mjs +42 -25
  37. package/fesm2022/ng-doc-app-components-kind-icon.mjs.map +1 -1
  38. package/fesm2022/ng-doc-app-components-link.mjs +12 -9
  39. package/fesm2022/ng-doc-app-components-link.mjs.map +1 -1
  40. package/fesm2022/ng-doc-app-components-members.mjs +30 -0
  41. package/fesm2022/ng-doc-app-components-members.mjs.map +1 -0
  42. package/fesm2022/ng-doc-app-components-mermaid-viewer.mjs +69 -24
  43. package/fesm2022/ng-doc-app-components-mermaid-viewer.mjs.map +1 -1
  44. package/fesm2022/ng-doc-app-components-navbar.mjs +39 -49
  45. package/fesm2022/ng-doc-app-components-navbar.mjs.map +1 -1
  46. package/fesm2022/ng-doc-app-components-page-header.mjs +73 -10
  47. package/fesm2022/ng-doc-app-components-page-header.mjs.map +1 -1
  48. package/fesm2022/ng-doc-app-components-page-link.mjs +123 -31
  49. package/fesm2022/ng-doc-app-components-page-link.mjs.map +1 -1
  50. package/fesm2022/ng-doc-app-components-page-navigation.mjs +17 -19
  51. package/fesm2022/ng-doc-app-components-page-navigation.mjs.map +1 -1
  52. package/fesm2022/ng-doc-app-components-page-wrapper.mjs +56 -35
  53. package/fesm2022/ng-doc-app-components-page-wrapper.mjs.map +1 -1
  54. package/fesm2022/ng-doc-app-components-page.mjs +168 -24
  55. package/fesm2022/ng-doc-app-components-page.mjs.map +1 -1
  56. package/fesm2022/ng-doc-app-components-playground.mjs +392 -263
  57. package/fesm2022/ng-doc-app-components-playground.mjs.map +1 -1
  58. package/fesm2022/ng-doc-app-components-root.mjs +48 -31
  59. package/fesm2022/ng-doc-app-components-root.mjs.map +1 -1
  60. package/fesm2022/ng-doc-app-components-search-result.mjs +22 -21
  61. package/fesm2022/ng-doc-app-components-search-result.mjs.map +1 -1
  62. package/fesm2022/ng-doc-app-components-search.mjs +64 -33
  63. package/fesm2022/ng-doc-app-components-search.mjs.map +1 -1
  64. package/fesm2022/ng-doc-app-components-sidebar.mjs +90 -63
  65. package/fesm2022/ng-doc-app-components-sidebar.mjs.map +1 -1
  66. package/fesm2022/ng-doc-app-components-tabs.mjs +18 -12
  67. package/fesm2022/ng-doc-app-components-tabs.mjs.map +1 -1
  68. package/fesm2022/ng-doc-app-components-theme-toggle.mjs +33 -21
  69. package/fesm2022/ng-doc-app-components-theme-toggle.mjs.map +1 -1
  70. package/fesm2022/ng-doc-app-components-toc.mjs +268 -90
  71. package/fesm2022/ng-doc-app-components-toc.mjs.map +1 -1
  72. package/fesm2022/ng-doc-app-components.mjs +3 -1
  73. package/fesm2022/ng-doc-app-components.mjs.map +1 -1
  74. package/fesm2022/ng-doc-app-defaults-default-processors.mjs +2 -1
  75. package/fesm2022/ng-doc-app-defaults-default-processors.mjs.map +1 -1
  76. package/fesm2022/ng-doc-app-directives-code-highlighter.mjs +14 -7
  77. package/fesm2022/ng-doc-app-directives-code-highlighter.mjs.map +1 -1
  78. package/fesm2022/ng-doc-app-directives-members-filter.mjs +276 -0
  79. package/fesm2022/ng-doc-app-directives-members-filter.mjs.map +1 -0
  80. package/fesm2022/ng-doc-app-directives-route-active.mjs +34 -30
  81. package/fesm2022/ng-doc-app-directives-route-active.mjs.map +1 -1
  82. package/fesm2022/ng-doc-app-directives.mjs +1 -0
  83. package/fesm2022/ng-doc-app-directives.mjs.map +1 -1
  84. package/fesm2022/ng-doc-app-helpers.mjs +285 -1
  85. package/fesm2022/ng-doc-app-helpers.mjs.map +1 -1
  86. package/fesm2022/ng-doc-app-pipes-decode-uri-component.mjs +6 -4
  87. package/fesm2022/ng-doc-app-pipes-decode-uri-component.mjs.map +1 -1
  88. package/fesm2022/ng-doc-app-pipes-extract-value.mjs +6 -4
  89. package/fesm2022/ng-doc-app-pipes-extract-value.mjs.map +1 -1
  90. package/fesm2022/ng-doc-app-pipes-filter-by-text.mjs +7 -4
  91. package/fesm2022/ng-doc-app-pipes-filter-by-text.mjs.map +1 -1
  92. package/fesm2022/ng-doc-app-pipes-sanitize-html.mjs +8 -5
  93. package/fesm2022/ng-doc-app-pipes-sanitize-html.mjs.map +1 -1
  94. package/fesm2022/ng-doc-app-processors-page-processor.mjs +86 -23
  95. package/fesm2022/ng-doc-app-processors-page-processor.mjs.map +1 -1
  96. package/fesm2022/ng-doc-app-processors-processors-tooltip.mjs +26 -25
  97. package/fesm2022/ng-doc-app-processors-processors-tooltip.mjs.map +1 -1
  98. package/fesm2022/ng-doc-app-processors-processors.mjs +12 -1
  99. package/fesm2022/ng-doc-app-processors-processors.mjs.map +1 -1
  100. package/fesm2022/ng-doc-app-providers-ng-doc-app.mjs +31 -2
  101. package/fesm2022/ng-doc-app-providers-ng-doc-app.mjs.map +1 -1
  102. package/fesm2022/ng-doc-app-services-content-scroll-intent.mjs +122 -0
  103. package/fesm2022/ng-doc-app-services-content-scroll-intent.mjs.map +1 -0
  104. package/fesm2022/ng-doc-app-services-content-state.mjs +57 -0
  105. package/fesm2022/ng-doc-app-services-content-state.mjs.map +1 -0
  106. package/fesm2022/ng-doc-app-services-fullscreen-route.mjs +74 -0
  107. package/fesm2022/ng-doc-app-services-fullscreen-route.mjs.map +1 -0
  108. package/fesm2022/ng-doc-app-services-highlighter.mjs +83 -14
  109. package/fesm2022/ng-doc-app-services-highlighter.mjs.map +1 -1
  110. package/fesm2022/ng-doc-app-services-route-preloader.mjs +360 -0
  111. package/fesm2022/ng-doc-app-services-route-preloader.mjs.map +1 -0
  112. package/fesm2022/ng-doc-app-services-shortcuts.mjs +214 -0
  113. package/fesm2022/ng-doc-app-services-shortcuts.mjs.map +1 -0
  114. package/fesm2022/ng-doc-app-services-sidebar.mjs +59 -25
  115. package/fesm2022/ng-doc-app-services-sidebar.mjs.map +1 -1
  116. package/fesm2022/ng-doc-app-services-store.mjs +8 -8
  117. package/fesm2022/ng-doc-app-services-store.mjs.map +1 -1
  118. package/fesm2022/ng-doc-app-services-theme.mjs +32 -12
  119. package/fesm2022/ng-doc-app-services-theme.mjs.map +1 -1
  120. package/fesm2022/ng-doc-app-services.mjs +143 -0
  121. package/fesm2022/ng-doc-app-services.mjs.map +1 -1
  122. package/fesm2022/ng-doc-app-tokens.mjs +7 -28
  123. package/fesm2022/ng-doc-app-tokens.mjs.map +1 -1
  124. package/fesm2022/ng-doc-app-type-controls-boolean-control.mjs +18 -9
  125. package/fesm2022/ng-doc-app-type-controls-boolean-control.mjs.map +1 -1
  126. package/fesm2022/ng-doc-app-type-controls-number-control.mjs +12 -9
  127. package/fesm2022/ng-doc-app-type-controls-number-control.mjs.map +1 -1
  128. package/fesm2022/ng-doc-app-type-controls-string-control.mjs +12 -9
  129. package/fesm2022/ng-doc-app-type-controls-string-control.mjs.map +1 -1
  130. package/fesm2022/ng-doc-app-type-controls-type-alias-control.mjs +22 -16
  131. package/fesm2022/ng-doc-app-type-controls-type-alias-control.mjs.map +1 -1
  132. package/package.json +74 -32
  133. package/styles/global.css +1129 -205
  134. package/styles/main.css +1129 -205
  135. package/styles/themes/dark.css +78 -56
  136. package/types/ng-doc-app-classes-content-anchor-controller.d.ts +54 -0
  137. package/types/ng-doc-app-classes-content-controller.d.ts +94 -0
  138. package/types/ng-doc-app-classes-default-search-engine.d.ts +9 -1
  139. package/types/ng-doc-app-classes-root-page.d.ts +6 -1
  140. package/types/ng-doc-app-classes-search-engine.d.ts +5 -0
  141. package/types/ng-doc-app-classes.d.ts +2 -0
  142. package/types/ng-doc-app-components-api-list.d.ts +69 -20
  143. package/types/ng-doc-app-components-breadcrumb.d.ts +8 -3
  144. package/types/ng-doc-app-components-code.d.ts +46 -12
  145. package/types/ng-doc-app-components-command-palette.d.ts +147 -0
  146. package/types/ng-doc-app-components-copy-button.d.ts +18 -6
  147. package/types/ng-doc-app-components-demo-displayer.d.ts +23 -10
  148. package/types/ng-doc-app-components-demo-pane.d.ts +19 -15
  149. package/types/ng-doc-app-components-demo.d.ts +62 -15
  150. package/types/ng-doc-app-components-fullscreen-button.d.ts +4 -2
  151. package/types/ng-doc-app-components-fullscreen-toggle.d.ts +24 -0
  152. package/types/ng-doc-app-components-heading-anchor.d.ts +26 -7
  153. package/types/ng-doc-app-components-image-viewer.d.ts +71 -7
  154. package/types/ng-doc-app-components-kind-icon.d.ts +28 -7
  155. package/types/ng-doc-app-components-link.d.ts +6 -2
  156. package/types/ng-doc-app-components-members.d.ts +13 -0
  157. package/types/ng-doc-app-components-mermaid-viewer.d.ts +21 -6
  158. package/types/ng-doc-app-components-navbar.d.ts +25 -15
  159. package/types/ng-doc-app-components-page-header.d.ts +28 -4
  160. package/types/ng-doc-app-components-page-link.d.ts +34 -15
  161. package/types/ng-doc-app-components-page-navigation.d.ts +10 -5
  162. package/types/ng-doc-app-components-page-wrapper.d.ts +35 -11
  163. package/types/ng-doc-app-components-page.d.ts +57 -6
  164. package/types/ng-doc-app-components-playground.d.ts +146 -78
  165. package/types/ng-doc-app-components-root.d.ts +28 -7
  166. package/types/ng-doc-app-components-search-result.d.ts +14 -6
  167. package/types/ng-doc-app-components-search.d.ts +19 -7
  168. package/types/ng-doc-app-components-tabs.d.ts +13 -4
  169. package/types/ng-doc-app-components-theme-toggle.d.ts +18 -5
  170. package/types/ng-doc-app-components-toc.d.ts +76 -29
  171. package/types/ng-doc-app-components.d.ts +3 -1
  172. package/types/ng-doc-app-directives-code-highlighter.d.ts +8 -4
  173. package/types/ng-doc-app-directives-members-filter.d.ts +61 -0
  174. package/types/ng-doc-app-directives-route-active.d.ts +21 -7
  175. package/types/ng-doc-app-directives.d.ts +1 -0
  176. package/types/ng-doc-app-helpers.d.ts +124 -3
  177. package/types/ng-doc-app-interfaces.d.ts +48 -28
  178. package/types/ng-doc-app-pipes-decode-uri-component.d.ts +3 -0
  179. package/types/ng-doc-app-pipes-extract-value.d.ts +3 -0
  180. package/types/ng-doc-app-pipes-filter-by-text.d.ts +4 -0
  181. package/types/ng-doc-app-pipes-sanitize-html.d.ts +4 -2
  182. package/types/ng-doc-app-processors-page-processor.d.ts +39 -10
  183. package/types/ng-doc-app-processors-processors-tooltip.d.ts +11 -10
  184. package/types/ng-doc-app-processors-processors.d.ts +5 -1
  185. package/types/ng-doc-app-providers-ng-doc-app.d.ts +17 -0
  186. package/types/ng-doc-app-providers-playground-demo.d.ts +1 -1
  187. package/types/ng-doc-app-providers-type-control.d.ts +2 -2
  188. package/types/ng-doc-app-services-content-scroll-intent.d.ts +52 -0
  189. package/types/ng-doc-app-services-content-state.d.ts +40 -0
  190. package/types/ng-doc-app-services-fullscreen-route.d.ts +35 -0
  191. package/types/ng-doc-app-services-highlighter.d.ts +40 -5
  192. package/types/ng-doc-app-services-route-preloader.d.ts +109 -0
  193. package/types/ng-doc-app-services-shortcuts.d.ts +96 -0
  194. package/types/ng-doc-app-services-sidebar.d.ts +24 -4
  195. package/types/ng-doc-app-services-store.d.ts +24 -1
  196. package/types/ng-doc-app-services-theme.d.ts +19 -3
  197. package/types/ng-doc-app-services.d.ts +77 -0
  198. package/types/ng-doc-app-tokens.d.ts +10 -30
  199. package/types/ng-doc-app-type-controls-boolean-control.d.ts +11 -6
  200. package/types/ng-doc-app-type-controls-number-control.d.ts +5 -3
  201. package/types/ng-doc-app-type-controls-string-control.d.ts +5 -3
  202. package/types/ng-doc-app-type-controls-type-alias-control.d.ts +13 -6
  203. package/fesm2022/ng-doc-app-components-search-dialog.mjs +0 -78
  204. package/fesm2022/ng-doc-app-components-search-dialog.mjs.map +0 -1
  205. package/types/ng-doc-app-components-search-dialog.d.ts +0 -28
@@ -0,0 +1,360 @@
1
+ import * as i0 from '@angular/core';
2
+ import { inject, DOCUMENT, Injector, NgZone, PLATFORM_ID, DestroyRef, Service } from '@angular/core';
3
+ import { of, catchError } from 'rxjs';
4
+ import { LocationStrategy, isPlatformBrowser } from '@angular/common';
5
+ import { Router, RouterPreloader, PRIMARY_OUTLET } from '@angular/router';
6
+ import { ɵngDocRouteContentSources as _ngDocRouteContentSources, ɵpreloadNgDocContent as _preloadNgDocContent } from '@ng-doc/app/helpers';
7
+ import { NG_DOC_ROUTE_PREFIX } from '@ng-doc/app/tokens';
8
+
9
+ /** The events that tell that the reader is about to open a link. */
10
+ const INTENT_EVENTS = ['pointerover', 'mouseover', 'focusin', 'touchstart'];
11
+ /** How long the browser may stay busy before an idle preload runs anyway, in milliseconds. */
12
+ const IDLE_TIMEOUT = 2000;
13
+ /** The delay of an idle preload where the browser has no `requestIdleCallback`. */
14
+ const IDLE_FALLBACK_DELAY = 200;
15
+ /**
16
+ * Preloads the pages that the reader is about to open, so that opening them shows them at once.
17
+ *
18
+ * It preloads a page when the reader points at, focuses or touches a link to it, anywhere in the
19
+ * document: the sidebar, the previous and next page links, links in the page content, search
20
+ * results and the table of contents. It also preloads the previous and next pages of a guide once
21
+ * the browser is idle after the guide has rendered. A preload loads the code of the page's route
22
+ * and its content, once per page. Nothing is preloaded on the server, when the reader asked the
23
+ * browser to save data, or on a 2G connection.
24
+ *
25
+ * The router loads the code through its preloading strategy, so preloading works only when the
26
+ * application sets `NgDocPreloadingStrategy` as that strategy:
27
+ *
28
+ * ```ts
29
+ * provideRouter(routes, withPreloading(NgDocPreloadingStrategy));
30
+ * ```
31
+ */
32
+ class NgDocRoutePreloader {
33
+ constructor() {
34
+ this.document = inject(DOCUMENT);
35
+ this.injector = inject(Injector);
36
+ this.ngZone = inject(NgZone);
37
+ this.router = inject(Router);
38
+ this.locationStrategy = inject(LocationStrategy);
39
+ this.browser = isPlatformBrowser(inject(PLATFORM_ID));
40
+ /** The path segments under which the documentation's routes live (`routePrefix`). */
41
+ this.prefix = (inject(NG_DOC_ROUTE_PREFIX, { optional: true }) ?? '')
42
+ .split('/')
43
+ .filter(Boolean);
44
+ /** The pages that were asked for, by path, so each page is preloaded once. */
45
+ this.requested = new Set();
46
+ /** The paths, as URL segments, whose routes are being loaded. */
47
+ this.loading = new Map();
48
+ this.enabled = false;
49
+ this.destroyed = false;
50
+ this.intent = (event) => {
51
+ const target = event.target;
52
+ const anchor = typeof Element !== 'undefined' && target instanceof Element
53
+ ? target.closest('a[href]')
54
+ : null;
55
+ // Pointer events fire for every element under the pointer, so a link is checked only when
56
+ // the pointer reaches it.
57
+ if (!anchor || (anchor === this.lastAnchor && event.type !== 'focusin'))
58
+ return;
59
+ this.lastAnchor = anchor;
60
+ const url = this.linkUrl(anchor);
61
+ if (url !== undefined)
62
+ this.preload(url);
63
+ };
64
+ inject(DestroyRef).onDestroy(() => this.destroy());
65
+ }
66
+ /**
67
+ * Turns preloading on and starts listening for links the reader is about to open. Called by
68
+ * `NgDocPreloadingStrategy` when the router creates it; does nothing on the server.
69
+ * @internal
70
+ */
71
+ enable() {
72
+ if (this.enabled || this.destroyed || !this.browser)
73
+ return;
74
+ this.enabled = true;
75
+ const view = this.document.defaultView;
76
+ if (!view)
77
+ return;
78
+ // A link the reader points at must not run change detection in a zone application.
79
+ this.ngZone.runOutsideAngular(() => {
80
+ INTENT_EVENTS.forEach((type) => this.document.addEventListener(type, this.intent, { capture: true, passive: true }));
81
+ });
82
+ }
83
+ /**
84
+ * Preloads the code and the content of the page at a URL of the application. A page is
85
+ * preloaded once; the current page is not preloaded.
86
+ * @param url - The URL, relative to the application's base, for example `/docs/get-started`.
87
+ * @returns Whether a preload started.
88
+ */
89
+ preload(url) {
90
+ if (!this.active())
91
+ return false;
92
+ let segments;
93
+ try {
94
+ segments = urlSegments(typeof url === 'string' ? this.router.parseUrl(url) : url);
95
+ }
96
+ catch {
97
+ return false;
98
+ }
99
+ // Only the documentation's own pages are preloaded, never the rest of the application.
100
+ if (this.prefix.some((part, index) => segments[index] !== part))
101
+ return false;
102
+ const key = segments.join('/');
103
+ if (this.requested.has(key) || key === urlSegments(this.currentUrl()).join('/'))
104
+ return false;
105
+ this.requested.add(key);
106
+ this.loading.set(key, segments);
107
+ this.ngZone.runOutsideAngular(() => {
108
+ this.injector
109
+ .get(RouterPreloader)
110
+ .preload()
111
+ .subscribe({ complete: () => this.loaded(key, segments) });
112
+ });
113
+ return true;
114
+ }
115
+ /**
116
+ * Preloads pages once the browser is idle. A later call replaces the pages of an earlier one
117
+ * that has not run yet.
118
+ * @param urls - The URLs of the pages; missing ones are skipped.
119
+ */
120
+ preloadWhenIdle(urls) {
121
+ this.cancelIdle?.();
122
+ this.cancelIdle = undefined;
123
+ const view = this.document.defaultView;
124
+ const pages = urls.filter((url) => !!url);
125
+ if (!this.active() || !view || !pages.length)
126
+ return;
127
+ const run = () => {
128
+ this.cancelIdle = undefined;
129
+ pages.forEach((url) => this.preload(url));
130
+ };
131
+ this.ngZone.runOutsideAngular(() => {
132
+ if (typeof view.requestIdleCallback === 'function') {
133
+ const handle = view.requestIdleCallback(run, { timeout: IDLE_TIMEOUT });
134
+ this.cancelIdle = () => view.cancelIdleCallback(handle);
135
+ }
136
+ else {
137
+ const handle = view.setTimeout(run, IDLE_FALLBACK_DELAY);
138
+ this.cancelIdle = () => view.clearTimeout(handle);
139
+ }
140
+ });
141
+ }
142
+ /**
143
+ * Whether the router should load a lazy route now: the route leads to a page that is being
144
+ * preloaded.
145
+ * @param route - A lazy route that the router has not loaded yet.
146
+ * @returns Whether to load it.
147
+ * @internal
148
+ */
149
+ shouldPreload(route) {
150
+ if (!this.enabled || this.destroyed)
151
+ return false;
152
+ for (const segments of this.loading.values()) {
153
+ if (routesOnPath(this.router.config, segments).has(route))
154
+ return true;
155
+ }
156
+ return false;
157
+ }
158
+ loaded(key, segments) {
159
+ this.loading.delete(key);
160
+ if (this.destroyed)
161
+ return;
162
+ const routes = routesOnPath(this.router.config, segments);
163
+ // The strategy turns a failed load into no load, so the router's own preloading survives
164
+ // it. A route that is still not loaded failed: a later intent may try again.
165
+ if ([...routes].some((route) => route.loadChildren && !loadedRoutes(route))) {
166
+ this.requested.delete(key);
167
+ return;
168
+ }
169
+ // The routes of the page have loaded, so the content sources of their components are known.
170
+ for (const route of routes) {
171
+ for (const source of _ngDocRouteContentSources(route)) {
172
+ _preloadNgDocContent(source).catch(() => undefined);
173
+ }
174
+ }
175
+ }
176
+ active() {
177
+ return this.enabled && !this.destroyed && !constrainedConnection(this.document);
178
+ }
179
+ currentUrl() {
180
+ return this.router.parseUrl(this.router.url);
181
+ }
182
+ /**
183
+ * Returns the application URL of a link, or `undefined` for a link that leaves the
184
+ * application, opens elsewhere, downloads a file or points into the current page.
185
+ * @param anchor - The link.
186
+ * @returns The URL relative to the application's base, with its query.
187
+ */
188
+ linkUrl(anchor) {
189
+ const view = this.document.defaultView;
190
+ const href = anchor.getAttribute('href');
191
+ const target = anchor.getAttribute('target');
192
+ if (!view ||
193
+ !href ||
194
+ anchor.hasAttribute('download') ||
195
+ (target && target !== '_self') ||
196
+ href.startsWith('#')) {
197
+ return undefined;
198
+ }
199
+ let url;
200
+ try {
201
+ url = new URL(href, this.document.baseURI);
202
+ }
203
+ catch {
204
+ return undefined;
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) {
208
+ return undefined;
209
+ }
210
+ const base = this.basePath();
211
+ if (!`${url.pathname}/`.startsWith(base))
212
+ return undefined;
213
+ return `/${url.pathname.slice(base.length)}${url.search}`;
214
+ }
215
+ /** The base path of the application, ending with a slash. */
216
+ basePath() {
217
+ const base = this.locationStrategy.getBaseHref() || '/';
218
+ const path = new URL(base, this.document.baseURI).pathname;
219
+ return path.endsWith('/') ? path : `${path}/`;
220
+ }
221
+ destroy() {
222
+ this.destroyed = true;
223
+ this.cancelIdle?.();
224
+ this.cancelIdle = undefined;
225
+ this.loading.clear();
226
+ INTENT_EVENTS.forEach((type) => this.document.removeEventListener(type, this.intent, { capture: true }));
227
+ }
228
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.2.1", ngImport: i0, type: NgDocRoutePreloader, deps: [], target: i0.ɵɵFactoryTarget.Service }); }
229
+ static { this.ɵprov = i0.ɵɵngDeclareService({ minVersion: "22.0.0", version: "22.2.1", ngImport: i0, type: NgDocRoutePreloader }); }
230
+ }
231
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.2.1", ngImport: i0, type: NgDocRoutePreloader, decorators: [{
232
+ type: Service
233
+ }], ctorParameters: () => [] });
234
+ /**
235
+ * Whether the reader asked the browser to save data or the connection is 2G, where preloading
236
+ * would compete with what the reader opens.
237
+ * @param document - The document of the application.
238
+ * @returns Whether to skip preloading.
239
+ */
240
+ function constrainedConnection(document) {
241
+ const navigator = document.defaultView?.navigator;
242
+ const connection = navigator?.connection;
243
+ return (!!connection &&
244
+ (connection.saveData === true || /(^|-)2g$/.test(connection.effectiveType ?? '')));
245
+ }
246
+ /**
247
+ * Returns the path segments of the primary outlet of a URL.
248
+ * @param url - The URL.
249
+ * @returns The segments.
250
+ */
251
+ function urlSegments(url) {
252
+ return url.root.children[PRIMARY_OUTLET]?.segments.map(({ path }) => path) ?? [];
253
+ }
254
+ /**
255
+ * Returns the routes that can match a path: those whose own path matches its start, through
256
+ * the children the router has loaded so far. A lazy route whose children have not loaded yet
257
+ * may lead anywhere below its path.
258
+ * @param routes - The routes to search.
259
+ * @param segments - The path segments.
260
+ * @param found - Collects the routes.
261
+ * @returns The routes.
262
+ */
263
+ function routesOnPath(routes, segments, found = new Set()) {
264
+ for (const route of routes) {
265
+ if ((route.outlet ?? PRIMARY_OUTLET) !== PRIMARY_OUTLET ||
266
+ route.matcher ||
267
+ route.redirectTo !== undefined ||
268
+ // A guarded route may not match for this reader; only opening the page may decide.
269
+ route.canMatch?.length) {
270
+ continue;
271
+ }
272
+ const consumed = matchedSegments(route.path ?? '', segments);
273
+ if (consumed === undefined)
274
+ continue;
275
+ const rest = segments.slice(consumed);
276
+ const children = route.children ?? loadedRoutes(route);
277
+ // A route without children matches only the whole remaining path.
278
+ const leaf = !route.children && !route.loadChildren;
279
+ if ((route.pathMatch === 'full' || leaf) && rest.length)
280
+ continue;
281
+ found.add(route);
282
+ if (children)
283
+ routesOnPath(children, rest, found);
284
+ }
285
+ return found;
286
+ }
287
+ /**
288
+ * Returns how many segments a route path matches at the start of a path.
289
+ * @param path - The route path.
290
+ * @param segments - The path segments.
291
+ * @returns The number of matched segments, or `undefined` when the route does not match.
292
+ */
293
+ function matchedSegments(path, segments) {
294
+ if (!path)
295
+ return 0;
296
+ const parts = path.split('/');
297
+ for (let index = 0; index < parts.length; index++) {
298
+ const part = parts[index];
299
+ if (part === '**')
300
+ return segments.length;
301
+ if (index >= segments.length)
302
+ return undefined;
303
+ if (!part.startsWith(':') && part !== segments[index])
304
+ return undefined;
305
+ }
306
+ return parts.length;
307
+ }
308
+ /**
309
+ * Returns the children that the router loaded for a lazy route. The router keeps them on the
310
+ * route itself and has no public API for them; its own preloader reads the same property.
311
+ * Without it, preloading stops at the first lazy route.
312
+ * @param route - The route.
313
+ * @returns The loaded children, if any.
314
+ */
315
+ function loadedRoutes(route) {
316
+ return route._loadedRoutes;
317
+ }
318
+
319
+ /**
320
+ * The router preloading strategy of NgDoc: it loads a lazy route only when the reader is about
321
+ * to open one of its pages, as `NgDocRoutePreloader` decides, instead of every route at once.
322
+ *
323
+ * Set it as the router's preloading strategy to preload pages on intent:
324
+ *
325
+ * ```ts
326
+ * provideRouter(routes, withPreloading(NgDocPreloadingStrategy));
327
+ * ```
328
+ */
329
+ class NgDocPreloadingStrategy {
330
+ constructor() {
331
+ this.preloader = inject(NgDocRoutePreloader);
332
+ // The router creates its preloading strategy at start-up, so this is where preloading on
333
+ // intent starts.
334
+ this.preloader.enable();
335
+ }
336
+ /**
337
+ * Loads a lazy route when it leads to a page that is being preloaded.
338
+ * @param route - A lazy route that the router has not loaded yet.
339
+ * @param load - Loads the route.
340
+ * @returns The load, or an observable of `null` when the route is not loaded now.
341
+ */
342
+ preload(route, load) {
343
+ if (!this.preloader.shouldPreload(route))
344
+ return of(null);
345
+ // A failed preload is not an error: opening the page loads the route again and reports it.
346
+ return load().pipe(catchError(() => of(null)));
347
+ }
348
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.2.1", ngImport: i0, type: NgDocPreloadingStrategy, deps: [], target: i0.ɵɵFactoryTarget.Service }); }
349
+ static { this.ɵprov = i0.ɵɵngDeclareService({ minVersion: "22.0.0", version: "22.2.1", ngImport: i0, type: NgDocPreloadingStrategy }); }
350
+ }
351
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.2.1", ngImport: i0, type: NgDocPreloadingStrategy, decorators: [{
352
+ type: Service
353
+ }], ctorParameters: () => [] });
354
+
355
+ /**
356
+ * Generated bundle index. Do not edit.
357
+ */
358
+
359
+ export { NgDocPreloadingStrategy, NgDocRoutePreloader };
360
+ //# sourceMappingURL=ng-doc-app-services-route-preloader.mjs.map
@@ -0,0 +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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"}
@@ -0,0 +1,214 @@
1
+ import * as i0 from '@angular/core';
2
+ import { InjectionToken, inject, DOCUMENT, Injector, NgZone, PLATFORM_ID, signal, computed, afterNextRender, DestroyRef, untracked, Service } from '@angular/core';
3
+ import { isPlatformBrowser } from '@angular/common';
4
+ import { NgDocStoreService } from '@ng-doc/app/services/store';
5
+ import { NgDocThemeService } from '@ng-doc/app/services/theme';
6
+
7
+ /**
8
+ * Whether single-key shortcuts are on for readers who have not turned them on or off themselves.
9
+ * The default is true. Set it with the shortcuts option of provideNgDocApp.
10
+ */
11
+ const NG_DOC_SHORTCUTS = new InjectionToken('NG_DOC_SHORTCUTS', { factory: () => true });
12
+ /**
13
+ * The local storage key that keeps a reader's choice to turn single-key shortcuts on ("1") or off
14
+ * ("0").
15
+ */
16
+ const NG_DOC_STORE_SHORTCUTS_KEY = 'ng-doc-shortcuts';
17
+
18
+ /**
19
+ * Elements where a single key types text or drives the widget instead of running a shortcut:
20
+ * fields, widgets with their own key handling or typeahead, and demos and playgrounds, which
21
+ * host the reader's components.
22
+ */
23
+ const OWN_KEYS = [
24
+ 'input',
25
+ 'textarea',
26
+ 'select',
27
+ '[contenteditable]:not([contenteditable="false"])',
28
+ ...[
29
+ 'combobox',
30
+ 'listbox',
31
+ 'menu',
32
+ 'menubar',
33
+ 'grid',
34
+ 'tree',
35
+ 'treegrid',
36
+ 'slider',
37
+ 'spinbutton',
38
+ 'application',
39
+ ].map((role) => `[role="${role}"]`),
40
+ 'ng-doc-demo',
41
+ 'ng-doc-demo-pane',
42
+ 'ng-doc-playground',
43
+ ].join(', ');
44
+ /**
45
+ * Keyboard shortcuts of the documentation.
46
+ *
47
+ * Single-key shortcuts (slash opens the search, the square brackets open the previous and next
48
+ * page, F focuses the filter of the page, L copies the link to the page and T switches between
49
+ * the light and the dark theme) are on by default. Readers turn them all off with one switch, and
50
+ * the choice is saved in the browser. A site turns them off by default with the shortcuts option
51
+ * of provideNgDocApp. They never run while the reader types in a field or holds Command, Control
52
+ * or Alt. Chords such as Command+K (Control+K) always work.
53
+ */
54
+ class NgDocShortcutsService {
55
+ constructor() {
56
+ this.document = inject(DOCUMENT);
57
+ this.injector = inject(Injector);
58
+ this.ngZone = inject(NgZone);
59
+ this.browser = isPlatformBrowser(inject(PLATFORM_ID));
60
+ this.appDefault = inject(NG_DOC_SHORTCUTS);
61
+ this.userChoice = signal(undefined, /* @ts-ignore */
62
+ ...(ngDevMode ? [{ debugName: "userChoice" }] : /* istanbul ignore next */ []));
63
+ this.shortcuts = [];
64
+ /**
65
+ * Whether single-key shortcuts are on: the reader's saved choice, or the site's default.
66
+ * Components hide the hints of single-key shortcuts while it is false.
67
+ */
68
+ this.enabled = computed(() => this.userChoice() ?? this.appDefault, /* @ts-ignore */
69
+ ...(ngDevMode ? [{ debugName: "enabled" }] : /* istanbul ignore next */ []));
70
+ this.registerDefaults();
71
+ if (!this.browser) {
72
+ return;
73
+ }
74
+ // The saved choice is read after the first render, so a hydrated page first renders exactly
75
+ // what the server rendered (the site's default) and then switches.
76
+ afterNextRender(() => this.userChoice.set(this.readChoice()), { injector: this.injector });
77
+ const listener = (event) => this.onKeydown(event);
78
+ // One document listener for every shortcut; change detection runs only when one matches.
79
+ this.ngZone.runOutsideAngular(() => this.document.addEventListener('keydown', listener));
80
+ inject(DestroyRef).onDestroy(() => this.document.removeEventListener('keydown', listener));
81
+ }
82
+ /**
83
+ * Turns single-key shortcuts on or off for this reader and saves the choice in the browser.
84
+ * @param enabled - Whether single-key shortcuts should run.
85
+ */
86
+ setEnabled(enabled) {
87
+ this.userChoice.set(enabled);
88
+ if (this.browser) {
89
+ try {
90
+ this.injector.get(NgDocStoreService).set(NG_DOC_STORE_SHORTCUTS_KEY, enabled ? '1' : '0');
91
+ }
92
+ catch {
93
+ // Storage can be unavailable (private mode, blocked cookies); the choice lasts the visit.
94
+ }
95
+ }
96
+ }
97
+ /** Turns single-key shortcuts off when they are on, and on when they are off. */
98
+ toggle() {
99
+ this.setEnabled(!untracked(this.enabled));
100
+ }
101
+ /**
102
+ * Registers a shortcut. When several shortcuts use the same key, the one registered last runs,
103
+ * so a page can take over a key while it is shown and give it back when it is destroyed.
104
+ * @param shortcut - The shortcut to register.
105
+ * @returns A function that removes the shortcut.
106
+ */
107
+ register(shortcut) {
108
+ this.shortcuts.push(shortcut);
109
+ return () => {
110
+ const index = this.shortcuts.lastIndexOf(shortcut);
111
+ if (index !== -1) {
112
+ this.shortcuts.splice(index, 1);
113
+ }
114
+ };
115
+ }
116
+ /**
117
+ * Runs the single-key shortcut registered for a key, as if the reader pressed it, even while
118
+ * single-key shortcuts are off. The search palette uses it for its actions.
119
+ * @param key - The key of the shortcut, for example 't'.
120
+ * @returns Whether a shortcut is registered for the key.
121
+ */
122
+ run(key) {
123
+ const shortcut = this.find(key.toLowerCase(), false);
124
+ untracked(() => shortcut?.handler());
125
+ return !!shortcut;
126
+ }
127
+ onKeydown(event) {
128
+ if (event.defaultPrevented || event.isComposing || event.repeat || !event.key) {
129
+ return;
130
+ }
131
+ // AltGr (Control+Alt on Windows) types characters such as [ and ] on many layouts: those
132
+ // keys are plain characters, not chords.
133
+ const altGraph = !!event.getModifierState?.('AltGraph');
134
+ const chord = !altGraph && (event.metaKey || event.ctrlKey) && !event.altKey;
135
+ const shortcut = this.find(event.key.toLowerCase(), chord);
136
+ if (!shortcut || (!chord && !this.acceptsSingleKey(event, altGraph))) {
137
+ return;
138
+ }
139
+ event.preventDefault();
140
+ this.ngZone.run(() => untracked(() => shortcut.handler(event)));
141
+ }
142
+ find(key, chord) {
143
+ for (let index = this.shortcuts.length - 1; index >= 0; index--) {
144
+ const shortcut = this.shortcuts[index];
145
+ if (shortcut.key.toLowerCase() === key && !!shortcut.chord === chord) {
146
+ return shortcut;
147
+ }
148
+ }
149
+ return undefined;
150
+ }
151
+ acceptsSingleKey(event, altGraph) {
152
+ if (!untracked(this.enabled) ||
153
+ event.metaKey ||
154
+ (!altGraph && (event.ctrlKey || event.altKey))) {
155
+ return false;
156
+ }
157
+ const target = event.composedPath?.()[0] ?? event.target;
158
+ return !(target instanceof Element && target.closest(OWN_KEYS));
159
+ }
160
+ readChoice() {
161
+ try {
162
+ const saved = this.injector.get(NgDocStoreService).get(NG_DOC_STORE_SHORTCUTS_KEY);
163
+ return saved === '1' ? true : saved === '0' ? false : undefined;
164
+ }
165
+ catch {
166
+ return undefined;
167
+ }
168
+ }
169
+ registerDefaults() {
170
+ this.register({ key: 't', handler: () => this.toggleDarkTheme() });
171
+ this.register({ key: 'l', handler: () => this.copyLink() });
172
+ // Pager links: rel="prev"/"next" is the standard marker; the classes are the NgDoc pager's.
173
+ this.register({ key: '[', handler: () => this.click('a[rel~="prev"], a.ng-doc-prev-page') });
174
+ this.register({ key: ']', handler: () => this.click('a[rel~="next"], a.ng-doc-next-page') });
175
+ }
176
+ toggleDarkTheme() {
177
+ const themeService = this.injector.get(NgDocThemeService);
178
+ const theme = themeService.theme();
179
+ const toggle = this.themeToggle;
180
+ // A second T restores the theme the first one replaced (auto or a custom theme), unless the
181
+ // reader changed the theme in between.
182
+ if (toggle && toggle.set === theme) {
183
+ this.themeToggle = undefined;
184
+ themeService.set(toggle.previous ?? undefined);
185
+ return;
186
+ }
187
+ const dark = theme === 'dark' ||
188
+ (theme === 'auto' &&
189
+ !!this.document.defaultView?.matchMedia?.('(prefers-color-scheme: dark)').matches);
190
+ const next = dark ? null : 'dark';
191
+ this.themeToggle = { previous: theme, set: next };
192
+ themeService.set(next ?? undefined);
193
+ }
194
+ copyLink() {
195
+ void this.document.defaultView?.navigator.clipboard
196
+ ?.writeText(this.document.location.href)
197
+ .catch(() => undefined);
198
+ }
199
+ click(selector) {
200
+ this.document.querySelector(selector)?.click();
201
+ }
202
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.2.1", ngImport: i0, type: NgDocShortcutsService, deps: [], target: i0.ɵɵFactoryTarget.Service }); }
203
+ static { this.ɵprov = i0.ɵɵngDeclareService({ minVersion: "22.0.0", version: "22.2.1", ngImport: i0, type: NgDocShortcutsService }); }
204
+ }
205
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.2.1", ngImport: i0, type: NgDocShortcutsService, decorators: [{
206
+ type: Service
207
+ }], ctorParameters: () => [] });
208
+
209
+ /**
210
+ * Generated bundle index. Do not edit.
211
+ */
212
+
213
+ export { NG_DOC_SHORTCUTS, NG_DOC_STORE_SHORTCUTS_KEY, NgDocShortcutsService };
214
+ //# sourceMappingURL=ng-doc-app-services-shortcuts.mjs.map