@ng-doc/app 22.0.0 → 22.2.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 (173) hide show
  1. package/LICENSE +21 -0
  2. package/fesm2022/ng-doc-app-classes-content-controller.mjs +22 -4
  3. package/fesm2022/ng-doc-app-classes-content-controller.mjs.map +1 -1
  4. package/fesm2022/ng-doc-app-classes-default-search-engine.mjs +36 -12
  5. package/fesm2022/ng-doc-app-classes-default-search-engine.mjs.map +1 -1
  6. package/fesm2022/ng-doc-app-classes-root-page.mjs.map +1 -1
  7. package/fesm2022/ng-doc-app-components-api-list.mjs +7 -5
  8. package/fesm2022/ng-doc-app-components-api-list.mjs.map +1 -1
  9. package/fesm2022/ng-doc-app-components-breadcrumb.mjs +5 -3
  10. package/fesm2022/ng-doc-app-components-breadcrumb.mjs.map +1 -1
  11. package/fesm2022/ng-doc-app-components-code.mjs +6 -4
  12. package/fesm2022/ng-doc-app-components-code.mjs.map +1 -1
  13. package/fesm2022/ng-doc-app-components-command-palette.mjs +35 -27
  14. package/fesm2022/ng-doc-app-components-command-palette.mjs.map +1 -1
  15. package/fesm2022/ng-doc-app-components-copy-button.mjs +6 -4
  16. package/fesm2022/ng-doc-app-components-copy-button.mjs.map +1 -1
  17. package/fesm2022/ng-doc-app-components-demo-displayer.mjs +19 -4
  18. package/fesm2022/ng-doc-app-components-demo-displayer.mjs.map +1 -1
  19. package/fesm2022/ng-doc-app-components-demo-frame.mjs +279 -0
  20. package/fesm2022/ng-doc-app-components-demo-frame.mjs.map +1 -0
  21. package/fesm2022/ng-doc-app-components-demo-pane.mjs +2 -2
  22. package/fesm2022/ng-doc-app-components-demo-pane.mjs.map +1 -1
  23. package/fesm2022/ng-doc-app-components-demo.mjs +97 -14
  24. package/fesm2022/ng-doc-app-components-demo.mjs.map +1 -1
  25. package/fesm2022/ng-doc-app-components-fullscreen-button.mjs +5 -3
  26. package/fesm2022/ng-doc-app-components-fullscreen-button.mjs.map +1 -1
  27. package/fesm2022/ng-doc-app-components-fullscreen-toggle.mjs +4 -2
  28. package/fesm2022/ng-doc-app-components-fullscreen-toggle.mjs.map +1 -1
  29. package/fesm2022/ng-doc-app-components-heading-anchor.mjs +5 -3
  30. package/fesm2022/ng-doc-app-components-heading-anchor.mjs.map +1 -1
  31. package/fesm2022/ng-doc-app-components-image-viewer.mjs +5 -3
  32. package/fesm2022/ng-doc-app-components-image-viewer.mjs.map +1 -1
  33. package/fesm2022/ng-doc-app-components-navbar.mjs +4 -2
  34. package/fesm2022/ng-doc-app-components-navbar.mjs.map +1 -1
  35. package/fesm2022/ng-doc-app-components-page-navigation.mjs +5 -3
  36. package/fesm2022/ng-doc-app-components-page-navigation.mjs.map +1 -1
  37. package/fesm2022/ng-doc-app-components-page-wrapper.mjs +2 -2
  38. package/fesm2022/ng-doc-app-components-page-wrapper.mjs.map +1 -1
  39. package/fesm2022/ng-doc-app-components-page.mjs +83 -16
  40. package/fesm2022/ng-doc-app-components-page.mjs.map +1 -1
  41. package/fesm2022/ng-doc-app-components-playground.mjs +838 -141
  42. package/fesm2022/ng-doc-app-components-playground.mjs.map +1 -1
  43. package/fesm2022/ng-doc-app-components-root.mjs +51 -4
  44. package/fesm2022/ng-doc-app-components-root.mjs.map +1 -1
  45. package/fesm2022/ng-doc-app-components-search-result.mjs +8 -6
  46. package/fesm2022/ng-doc-app-components-search-result.mjs.map +1 -1
  47. package/fesm2022/ng-doc-app-components-search.mjs +7 -5
  48. package/fesm2022/ng-doc-app-components-search.mjs.map +1 -1
  49. package/fesm2022/ng-doc-app-components-sidebar.mjs +4 -3
  50. package/fesm2022/ng-doc-app-components-sidebar.mjs.map +1 -1
  51. package/fesm2022/ng-doc-app-components-tabs.mjs +21 -3
  52. package/fesm2022/ng-doc-app-components-tabs.mjs.map +1 -1
  53. package/fesm2022/ng-doc-app-components-theme-toggle.mjs +7 -5
  54. package/fesm2022/ng-doc-app-components-theme-toggle.mjs.map +1 -1
  55. package/fesm2022/ng-doc-app-components-toc.mjs +4 -2
  56. package/fesm2022/ng-doc-app-components-toc.mjs.map +1 -1
  57. package/fesm2022/ng-doc-app-components.mjs +1 -0
  58. package/fesm2022/ng-doc-app-components.mjs.map +1 -1
  59. package/fesm2022/ng-doc-app-constants.mjs +11 -1
  60. package/fesm2022/ng-doc-app-constants.mjs.map +1 -1
  61. package/fesm2022/ng-doc-app-demo-app.mjs +779 -0
  62. package/fesm2022/ng-doc-app-demo-app.mjs.map +1 -0
  63. package/fesm2022/ng-doc-app-helpers.mjs +147 -7
  64. package/fesm2022/ng-doc-app-helpers.mjs.map +1 -1
  65. package/fesm2022/ng-doc-app-processors-processors.mjs +30 -9
  66. package/fesm2022/ng-doc-app-processors-processors.mjs.map +1 -1
  67. package/fesm2022/ng-doc-app-providers-ng-doc-app.mjs +29 -18
  68. package/fesm2022/ng-doc-app-providers-ng-doc-app.mjs.map +1 -1
  69. package/fesm2022/ng-doc-app-providers-texts.mjs +53 -0
  70. package/fesm2022/ng-doc-app-providers-texts.mjs.map +1 -0
  71. package/fesm2022/ng-doc-app-providers-title.mjs +129 -0
  72. package/fesm2022/ng-doc-app-providers-title.mjs.map +1 -0
  73. package/fesm2022/ng-doc-app-providers.mjs +2 -0
  74. package/fesm2022/ng-doc-app-providers.mjs.map +1 -1
  75. package/fesm2022/ng-doc-app-services-content-scroll-intent.mjs +27 -2
  76. package/fesm2022/ng-doc-app-services-content-scroll-intent.mjs.map +1 -1
  77. package/fesm2022/ng-doc-app-services-highlighter.mjs +23 -16
  78. package/fesm2022/ng-doc-app-services-highlighter.mjs.map +1 -1
  79. package/fesm2022/ng-doc-app-services-route-preloader.mjs +34 -10
  80. package/fesm2022/ng-doc-app-services-route-preloader.mjs.map +1 -1
  81. package/fesm2022/ng-doc-app-services-shortcuts.mjs +38 -4
  82. package/fesm2022/ng-doc-app-services-shortcuts.mjs.map +1 -1
  83. package/fesm2022/ng-doc-app-texts-ar.mjs +238 -0
  84. package/fesm2022/ng-doc-app-texts-ar.mjs.map +1 -0
  85. package/fesm2022/ng-doc-app-texts-de.mjs +205 -0
  86. package/fesm2022/ng-doc-app-texts-de.mjs.map +1 -0
  87. package/fesm2022/ng-doc-app-texts-es.mjs +205 -0
  88. package/fesm2022/ng-doc-app-texts-es.mjs.map +1 -0
  89. package/fesm2022/ng-doc-app-texts-fr.mjs +205 -0
  90. package/fesm2022/ng-doc-app-texts-fr.mjs.map +1 -0
  91. package/fesm2022/ng-doc-app-texts-hi.mjs +219 -0
  92. package/fesm2022/ng-doc-app-texts-hi.mjs.map +1 -0
  93. package/fesm2022/ng-doc-app-texts-ja.mjs +205 -0
  94. package/fesm2022/ng-doc-app-texts-ja.mjs.map +1 -0
  95. package/fesm2022/ng-doc-app-texts-ko.mjs +205 -0
  96. package/fesm2022/ng-doc-app-texts-ko.mjs.map +1 -0
  97. package/fesm2022/ng-doc-app-texts-pt-br.mjs +205 -0
  98. package/fesm2022/ng-doc-app-texts-pt-br.mjs.map +1 -0
  99. package/fesm2022/ng-doc-app-texts-ru.mjs +231 -0
  100. package/fesm2022/ng-doc-app-texts-ru.mjs.map +1 -0
  101. package/fesm2022/ng-doc-app-texts-uk.mjs +231 -0
  102. package/fesm2022/ng-doc-app-texts-uk.mjs.map +1 -0
  103. package/fesm2022/ng-doc-app-texts-zh.mjs +205 -0
  104. package/fesm2022/ng-doc-app-texts-zh.mjs.map +1 -0
  105. package/fesm2022/ng-doc-app-tokens.mjs +214 -1
  106. package/fesm2022/ng-doc-app-tokens.mjs.map +1 -1
  107. package/fesm2022/ng-doc-app-type-controls-json-control.mjs +90 -0
  108. package/fesm2022/ng-doc-app-type-controls-json-control.mjs.map +1 -0
  109. package/fesm2022/ng-doc-app-type-controls-list-control.mjs +792 -0
  110. package/fesm2022/ng-doc-app-type-controls-list-control.mjs.map +1 -0
  111. package/fesm2022/ng-doc-app-type-controls-type-alias-control.mjs +45 -8
  112. package/fesm2022/ng-doc-app-type-controls-type-alias-control.mjs.map +1 -1
  113. package/fesm2022/ng-doc-app-type-controls.mjs +2 -0
  114. package/fesm2022/ng-doc-app-type-controls.mjs.map +1 -1
  115. package/package.json +73 -4
  116. package/styles/global.css +4 -0
  117. package/styles/main.css +4 -0
  118. package/styles/themes/dark.css +10 -0
  119. package/types/ng-doc-app-classes-default-search-engine.d.ts +11 -0
  120. package/types/ng-doc-app-classes-root-page.d.ts +13 -0
  121. package/types/ng-doc-app-components-api-list.d.ts +3 -1
  122. package/types/ng-doc-app-components-breadcrumb.d.ts +2 -1
  123. package/types/ng-doc-app-components-code.d.ts +2 -0
  124. package/types/ng-doc-app-components-command-palette.d.ts +3 -1
  125. package/types/ng-doc-app-components-copy-button.d.ts +3 -1
  126. package/types/ng-doc-app-components-demo-displayer.d.ts +13 -1
  127. package/types/ng-doc-app-components-demo-frame.d.ts +107 -0
  128. package/types/ng-doc-app-components-demo.d.ts +47 -10
  129. package/types/ng-doc-app-components-fullscreen-button.d.ts +2 -0
  130. package/types/ng-doc-app-components-fullscreen-toggle.d.ts +2 -0
  131. package/types/ng-doc-app-components-heading-anchor.d.ts +2 -0
  132. package/types/ng-doc-app-components-image-viewer.d.ts +2 -0
  133. package/types/ng-doc-app-components-navbar.d.ts +2 -0
  134. package/types/ng-doc-app-components-page-navigation.d.ts +2 -1
  135. package/types/ng-doc-app-components-page.d.ts +10 -1
  136. package/types/ng-doc-app-components-playground.d.ts +260 -42
  137. package/types/ng-doc-app-components-root.d.ts +18 -0
  138. package/types/ng-doc-app-components-search-result.d.ts +2 -0
  139. package/types/ng-doc-app-components-search.d.ts +2 -0
  140. package/types/ng-doc-app-components-sidebar.d.ts +2 -1
  141. package/types/ng-doc-app-components-tabs.d.ts +13 -3
  142. package/types/ng-doc-app-components-theme-toggle.d.ts +4 -1
  143. package/types/ng-doc-app-components-toc.d.ts +2 -1
  144. package/types/ng-doc-app-components.d.ts +1 -0
  145. package/types/ng-doc-app-constants.d.ts +11 -1
  146. package/types/ng-doc-app-demo-app.d.ts +297 -0
  147. package/types/ng-doc-app-helpers.d.ts +75 -4
  148. package/types/ng-doc-app-interfaces.d.ts +635 -6
  149. package/types/ng-doc-app-processors-processors.d.ts +5 -0
  150. package/types/ng-doc-app-providers-ng-doc-app.d.ts +7 -1
  151. package/types/ng-doc-app-providers-texts.d.ts +28 -0
  152. package/types/ng-doc-app-providers-title.d.ts +59 -0
  153. package/types/ng-doc-app-providers.d.ts +2 -0
  154. package/types/ng-doc-app-services-content-scroll-intent.d.ts +11 -1
  155. package/types/ng-doc-app-services-highlighter.d.ts +15 -6
  156. package/types/ng-doc-app-services-route-preloader.d.ts +13 -1
  157. package/types/ng-doc-app-services-shortcuts.d.ts +16 -0
  158. package/types/ng-doc-app-texts-ar.d.ts +13 -0
  159. package/types/ng-doc-app-texts-de.d.ts +12 -0
  160. package/types/ng-doc-app-texts-es.d.ts +12 -0
  161. package/types/ng-doc-app-texts-fr.d.ts +12 -0
  162. package/types/ng-doc-app-texts-hi.d.ts +12 -0
  163. package/types/ng-doc-app-texts-ja.d.ts +12 -0
  164. package/types/ng-doc-app-texts-ko.d.ts +12 -0
  165. package/types/ng-doc-app-texts-pt-br.d.ts +12 -0
  166. package/types/ng-doc-app-texts-ru.d.ts +12 -0
  167. package/types/ng-doc-app-texts-uk.d.ts +12 -0
  168. package/types/ng-doc-app-texts-zh.d.ts +12 -0
  169. package/types/ng-doc-app-tokens.d.ts +21 -2
  170. package/types/ng-doc-app-type-controls-json-control.d.ts +38 -0
  171. package/types/ng-doc-app-type-controls-list-control.d.ts +313 -0
  172. package/types/ng-doc-app-type-controls-type-alias-control.d.ts +28 -5
  173. package/types/ng-doc-app-type-controls.d.ts +2 -0
@@ -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":"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"}
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';\n// Types only: Shiki, its Oniguruma engine and the WebAssembly binary load with the first\n// `initialize()`, so an application that imports NgDoc only in a lazy route keeps them out of its\n// initial bundle.\nimport type { HighlighterCore, LanguageInput, ThemeInput } from 'shiki/core';\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` starts its initialization where it is provided (the\n * application, or the route that holds the documentation), and the server render waits for it.\n * Shiki is loaded on demand: `initialize()` imports it.\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 const [{ createHighlighterCore }, { createOnigurumaEngine }] = await Promise.all([\n import('shiki/core'),\n import('shiki/engine/oniguruma'),\n ]);\n\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(import('shiki/wasm')),\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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"}
@@ -50,6 +50,10 @@ const OWN_KEYS = [
50
50
  * the choice is saved in the browser. A site turns them off by default with the shortcuts option
51
51
  * of provideNgDocApp. They never run while the reader types in a field or holds Command, Control
52
52
  * or Alt. Chords such as Command+K (Control+K) always work.
53
+ *
54
+ * Shortcuts run only while the documentation layout is shown: `NgDocRootComponent` turns them on
55
+ * with `listen()`, so the other pages of an application that shows its documentation under a
56
+ * route keep their own keys.
53
57
  */
54
58
  class NgDocShortcutsService {
55
59
  constructor() {
@@ -61,6 +65,10 @@ class NgDocShortcutsService {
61
65
  this.userChoice = signal(undefined, /* @ts-ignore */
62
66
  ...(ngDevMode ? [{ debugName: "userChoice" }] : /* istanbul ignore next */ []));
63
67
  this.shortcuts = [];
68
+ this.listener = (event) => this.onKeydown(event);
69
+ /** The layouts that listen for the shortcuts; the document listener is on while there are any. */
70
+ this.listeners = 0;
71
+ this.destroyed = false;
64
72
  /**
65
73
  * Whether single-key shortcuts are on: the reader's saved choice, or the site's default.
66
74
  * Components hide the hints of single-key shortcuts while it is false.
@@ -74,10 +82,36 @@ class NgDocShortcutsService {
74
82
  // The saved choice is read after the first render, so a hydrated page first renders exactly
75
83
  // what the server rendered (the site's default) and then switches.
76
84
  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));
85
+ inject(DestroyRef).onDestroy(() => {
86
+ this.destroyed = true;
87
+ this.document.removeEventListener('keydown', this.listener);
88
+ });
89
+ }
90
+ /**
91
+ * Runs the shortcuts on key presses until the returned function is called. `NgDocRootComponent`
92
+ * calls it while the documentation layout is shown; a site with a layout of its own calls it
93
+ * the same way. Several callers may listen at once: the shortcuts run until each has stopped.
94
+ * Does nothing on the server.
95
+ * @returns A function that stops listening; later calls do nothing.
96
+ */
97
+ listen() {
98
+ if (!this.browser || this.destroyed) {
99
+ return () => undefined;
100
+ }
101
+ if (this.listeners++ === 0) {
102
+ // One document listener for every shortcut; change detection runs only when one matches.
103
+ this.ngZone.runOutsideAngular(() => this.document.addEventListener('keydown', this.listener));
104
+ }
105
+ let listening = true;
106
+ return () => {
107
+ if (!listening) {
108
+ return;
109
+ }
110
+ listening = false;
111
+ if (--this.listeners === 0) {
112
+ this.document.removeEventListener('keydown', this.listener);
113
+ }
114
+ };
81
115
  }
82
116
  /**
83
117
  * Turns single-key shortcuts on or off for this reader and saves the choice in the browser.
@@ -1 +1 @@
1
- {"version":3,"file":"ng-doc-app-services-shortcuts.mjs","sources":["../../../../libs/app/services/shortcuts/shortcut.ts","../../../../libs/app/services/shortcuts/shortcuts.service.ts","../../../../libs/app/services/shortcuts/ng-doc-app-services-shortcuts.ts"],"sourcesContent":["import { InjectionToken } from '@angular/core';\n\n/**\n * A keyboard shortcut registered with the NgDocShortcutsService.\n */\nexport interface NgDocShortcut {\n /**\n * The key that runs the shortcut, compared with the key of the keyboard event without regard to\n * case, for example '/', '[' or 'f'.\n */\n key: string;\n /**\n * Whether the shortcut is a chord with the Command key (macOS) or the Control key (elsewhere).\n * Chords always work: in text fields too, and when single-key shortcuts are turned off. Other\n * shortcuts are single keys: they run only while single-key shortcuts are on, never while the\n * reader types in a field, and never with Command, Control or Alt held.\n */\n chord?: boolean;\n /**\n * Runs the shortcut. The default action of the keyboard event is prevented before it runs. The\n * event is missing when the shortcut runs from the search palette.\n */\n handler: (event?: KeyboardEvent) => void;\n}\n\n/**\n * Whether single-key shortcuts are on for readers who have not turned them on or off themselves.\n * The default is true. Set it with the shortcuts option of provideNgDocApp.\n */\nexport const NG_DOC_SHORTCUTS: InjectionToken<boolean> = new InjectionToken<boolean>(\n 'NG_DOC_SHORTCUTS',\n { factory: () => true },\n);\n\n/**\n * The local storage key that keeps a reader's choice to turn single-key shortcuts on (\"1\") or off\n * (\"0\").\n */\nexport const NG_DOC_STORE_SHORTCUTS_KEY: string = 'ng-doc-shortcuts';\n","import { isPlatformBrowser } from '@angular/common';\nimport {\n afterNextRender,\n computed,\n DestroyRef,\n DOCUMENT,\n inject,\n Injector,\n NgZone,\n PLATFORM_ID,\n Service,\n Signal,\n signal,\n untracked,\n} from '@angular/core';\nimport { NgDocStoreService } from '@ng-doc/app/services/store';\nimport { NgDocThemeService } from '@ng-doc/app/services/theme';\n\nimport { NG_DOC_SHORTCUTS, NG_DOC_STORE_SHORTCUTS_KEY, NgDocShortcut } from './shortcut';\n\n/**\n * Elements where a single key types text or drives the widget instead of running a shortcut:\n * fields, widgets with their own key handling or typeahead, and demos and playgrounds, which\n * host the reader's components.\n */\nconst OWN_KEYS = [\n 'input',\n 'textarea',\n 'select',\n '[contenteditable]:not([contenteditable=\"false\"])',\n ...[\n 'combobox',\n 'listbox',\n 'menu',\n 'menubar',\n 'grid',\n 'tree',\n 'treegrid',\n 'slider',\n 'spinbutton',\n 'application',\n ].map((role: string) => `[role=\"${role}\"]`),\n 'ng-doc-demo',\n 'ng-doc-demo-pane',\n 'ng-doc-playground',\n].join(', ');\n\n/**\n * Keyboard shortcuts of the documentation.\n *\n * Single-key shortcuts (slash opens the search, the square brackets open the previous and next\n * page, F focuses the filter of the page, L copies the link to the page and T switches between\n * the light and the dark theme) are on by default. Readers turn them all off with one switch, and\n * the choice is saved in the browser. A site turns them off by default with the shortcuts option\n * of provideNgDocApp. They never run while the reader types in a field or holds Command, Control\n * or Alt. Chords such as Command+K (Control+K) always work.\n */\n@Service()\nexport class NgDocShortcutsService {\n private readonly document = inject(DOCUMENT);\n private readonly injector = inject(Injector);\n private readonly ngZone = inject(NgZone);\n private readonly browser = isPlatformBrowser(inject(PLATFORM_ID));\n private readonly appDefault = inject(NG_DOC_SHORTCUTS);\n private readonly userChoice = signal<boolean | undefined>(undefined);\n private readonly shortcuts: NgDocShortcut[] = [];\n // The theme T switched away from, and the theme it set, so a second T restores the first.\n private themeToggle?: { previous: string | null; set: string | null };\n\n /**\n * Whether single-key shortcuts are on: the reader's saved choice, or the site's default.\n * Components hide the hints of single-key shortcuts while it is false.\n */\n readonly enabled: Signal<boolean> = computed(() => this.userChoice() ?? this.appDefault);\n\n constructor() {\n this.registerDefaults();\n\n if (!this.browser) {\n return;\n }\n\n // The saved choice is read after the first render, so a hydrated page first renders exactly\n // what the server rendered (the site's default) and then switches.\n afterNextRender(() => this.userChoice.set(this.readChoice()), { injector: this.injector });\n\n const listener = (event: KeyboardEvent) => this.onKeydown(event);\n // One document listener for every shortcut; change detection runs only when one matches.\n this.ngZone.runOutsideAngular(() => this.document.addEventListener('keydown', listener));\n inject(DestroyRef).onDestroy(() => this.document.removeEventListener('keydown', listener));\n }\n\n /**\n * Turns single-key shortcuts on or off for this reader and saves the choice in the browser.\n * @param enabled - Whether single-key shortcuts should run.\n */\n setEnabled(enabled: boolean): void {\n this.userChoice.set(enabled);\n\n if (this.browser) {\n try {\n this.injector.get(NgDocStoreService).set(NG_DOC_STORE_SHORTCUTS_KEY, enabled ? '1' : '0');\n } catch {\n // Storage can be unavailable (private mode, blocked cookies); the choice lasts the visit.\n }\n }\n }\n\n /** Turns single-key shortcuts off when they are on, and on when they are off. */\n toggle(): void {\n this.setEnabled(!untracked(this.enabled));\n }\n\n /**\n * Registers a shortcut. When several shortcuts use the same key, the one registered last runs,\n * so a page can take over a key while it is shown and give it back when it is destroyed.\n * @param shortcut - The shortcut to register.\n * @returns A function that removes the shortcut.\n */\n register(shortcut: NgDocShortcut): () => void {\n this.shortcuts.push(shortcut);\n\n return () => {\n const index = this.shortcuts.lastIndexOf(shortcut);\n\n if (index !== -1) {\n this.shortcuts.splice(index, 1);\n }\n };\n }\n\n /**\n * Runs the single-key shortcut registered for a key, as if the reader pressed it, even while\n * single-key shortcuts are off. The search palette uses it for its actions.\n * @param key - The key of the shortcut, for example 't'.\n * @returns Whether a shortcut is registered for the key.\n */\n run(key: string): boolean {\n const shortcut = this.find(key.toLowerCase(), false);\n\n untracked(() => shortcut?.handler());\n\n return !!shortcut;\n }\n\n private onKeydown(event: KeyboardEvent): void {\n if (event.defaultPrevented || event.isComposing || event.repeat || !event.key) {\n return;\n }\n\n // AltGr (Control+Alt on Windows) types characters such as [ and ] on many layouts: those\n // keys are plain characters, not chords.\n const altGraph = !!event.getModifierState?.('AltGraph');\n const chord = !altGraph && (event.metaKey || event.ctrlKey) && !event.altKey;\n const shortcut = this.find(event.key.toLowerCase(), chord);\n\n if (!shortcut || (!chord && !this.acceptsSingleKey(event, altGraph))) {\n return;\n }\n\n event.preventDefault();\n this.ngZone.run(() => untracked(() => shortcut.handler(event)));\n }\n\n private find(key: string, chord: boolean): NgDocShortcut | undefined {\n for (let index = this.shortcuts.length - 1; index >= 0; index--) {\n const shortcut = this.shortcuts[index];\n\n if (shortcut.key.toLowerCase() === key && !!shortcut.chord === chord) {\n return shortcut;\n }\n }\n\n return undefined;\n }\n\n private acceptsSingleKey(event: KeyboardEvent, altGraph: boolean): boolean {\n if (\n !untracked(this.enabled) ||\n event.metaKey ||\n (!altGraph && (event.ctrlKey || event.altKey))\n ) {\n return false;\n }\n\n const target = event.composedPath?.()[0] ?? event.target;\n\n return !(target instanceof Element && target.closest(OWN_KEYS));\n }\n\n private readChoice(): boolean | undefined {\n try {\n const saved = this.injector.get(NgDocStoreService).get(NG_DOC_STORE_SHORTCUTS_KEY);\n\n return saved === '1' ? true : saved === '0' ? false : undefined;\n } catch {\n return undefined;\n }\n }\n\n private registerDefaults(): void {\n this.register({ key: 't', handler: () => this.toggleDarkTheme() });\n this.register({ key: 'l', handler: () => this.copyLink() });\n // Pager links: rel=\"prev\"/\"next\" is the standard marker; the classes are the NgDoc pager's.\n this.register({ key: '[', handler: () => this.click('a[rel~=\"prev\"], a.ng-doc-prev-page') });\n this.register({ key: ']', handler: () => this.click('a[rel~=\"next\"], a.ng-doc-next-page') });\n }\n\n private toggleDarkTheme(): void {\n const themeService = this.injector.get(NgDocThemeService);\n const theme = themeService.theme();\n const toggle = this.themeToggle;\n\n // A second T restores the theme the first one replaced (auto or a custom theme), unless the\n // reader changed the theme in between.\n if (toggle && toggle.set === theme) {\n this.themeToggle = undefined;\n themeService.set(toggle.previous ?? undefined);\n\n return;\n }\n\n const dark =\n theme === 'dark' ||\n (theme === 'auto' &&\n !!this.document.defaultView?.matchMedia?.('(prefers-color-scheme: dark)').matches);\n const next = dark ? null : 'dark';\n\n this.themeToggle = { previous: theme, set: next };\n themeService.set(next ?? undefined);\n }\n\n private copyLink(): void {\n void this.document.defaultView?.navigator.clipboard\n ?.writeText(this.document.location.href)\n .catch(() => undefined);\n }\n\n private click(selector: string): void {\n this.document.querySelector<HTMLElement>(selector)?.click();\n }\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"ng-doc-app-services-shortcuts.mjs","sources":["../../../../libs/app/services/shortcuts/shortcut.ts","../../../../libs/app/services/shortcuts/shortcuts.service.ts","../../../../libs/app/services/shortcuts/ng-doc-app-services-shortcuts.ts"],"sourcesContent":["import { InjectionToken } from '@angular/core';\n\n/**\n * A keyboard shortcut registered with the NgDocShortcutsService.\n */\nexport interface NgDocShortcut {\n /**\n * The key that runs the shortcut, compared with the key of the keyboard event without regard to\n * case, for example '/', '[' or 'f'.\n */\n key: string;\n /**\n * Whether the shortcut is a chord with the Command key (macOS) or the Control key (elsewhere).\n * Chords always work: in text fields too, and when single-key shortcuts are turned off. Other\n * shortcuts are single keys: they run only while single-key shortcuts are on, never while the\n * reader types in a field, and never with Command, Control or Alt held.\n */\n chord?: boolean;\n /**\n * Runs the shortcut. The default action of the keyboard event is prevented before it runs. The\n * event is missing when the shortcut runs from the search palette.\n */\n handler: (event?: KeyboardEvent) => void;\n}\n\n/**\n * Whether single-key shortcuts are on for readers who have not turned them on or off themselves.\n * The default is true. Set it with the shortcuts option of provideNgDocApp.\n */\nexport const NG_DOC_SHORTCUTS: InjectionToken<boolean> = new InjectionToken<boolean>(\n 'NG_DOC_SHORTCUTS',\n { factory: () => true },\n);\n\n/**\n * The local storage key that keeps a reader's choice to turn single-key shortcuts on (\"1\") or off\n * (\"0\").\n */\nexport const NG_DOC_STORE_SHORTCUTS_KEY: string = 'ng-doc-shortcuts';\n","import { isPlatformBrowser } from '@angular/common';\nimport {\n afterNextRender,\n computed,\n DestroyRef,\n DOCUMENT,\n inject,\n Injector,\n NgZone,\n PLATFORM_ID,\n Service,\n Signal,\n signal,\n untracked,\n} from '@angular/core';\nimport { NgDocStoreService } from '@ng-doc/app/services/store';\nimport { NgDocThemeService } from '@ng-doc/app/services/theme';\n\nimport { NG_DOC_SHORTCUTS, NG_DOC_STORE_SHORTCUTS_KEY, NgDocShortcut } from './shortcut';\n\n/**\n * Elements where a single key types text or drives the widget instead of running a shortcut:\n * fields, widgets with their own key handling or typeahead, and demos and playgrounds, which\n * host the reader's components.\n */\nconst OWN_KEYS = [\n 'input',\n 'textarea',\n 'select',\n '[contenteditable]:not([contenteditable=\"false\"])',\n ...[\n 'combobox',\n 'listbox',\n 'menu',\n 'menubar',\n 'grid',\n 'tree',\n 'treegrid',\n 'slider',\n 'spinbutton',\n 'application',\n ].map((role: string) => `[role=\"${role}\"]`),\n 'ng-doc-demo',\n 'ng-doc-demo-pane',\n 'ng-doc-playground',\n].join(', ');\n\n/**\n * Keyboard shortcuts of the documentation.\n *\n * Single-key shortcuts (slash opens the search, the square brackets open the previous and next\n * page, F focuses the filter of the page, L copies the link to the page and T switches between\n * the light and the dark theme) are on by default. Readers turn them all off with one switch, and\n * the choice is saved in the browser. A site turns them off by default with the shortcuts option\n * of provideNgDocApp. They never run while the reader types in a field or holds Command, Control\n * or Alt. Chords such as Command+K (Control+K) always work.\n *\n * Shortcuts run only while the documentation layout is shown: `NgDocRootComponent` turns them on\n * with `listen()`, so the other pages of an application that shows its documentation under a\n * route keep their own keys.\n */\n@Service()\nexport class NgDocShortcutsService {\n private readonly document = inject(DOCUMENT);\n private readonly injector = inject(Injector);\n private readonly ngZone = inject(NgZone);\n private readonly browser = isPlatformBrowser(inject(PLATFORM_ID));\n private readonly appDefault = inject(NG_DOC_SHORTCUTS);\n private readonly userChoice = signal<boolean | undefined>(undefined);\n private readonly shortcuts: NgDocShortcut[] = [];\n private readonly listener = (event: KeyboardEvent): void => this.onKeydown(event);\n /** The layouts that listen for the shortcuts; the document listener is on while there are any. */\n private listeners = 0;\n private destroyed = false;\n // The theme T switched away from, and the theme it set, so a second T restores the first.\n private themeToggle?: { previous: string | null; set: string | null };\n\n /**\n * Whether single-key shortcuts are on: the reader's saved choice, or the site's default.\n * Components hide the hints of single-key shortcuts while it is false.\n */\n readonly enabled: Signal<boolean> = computed(() => this.userChoice() ?? this.appDefault);\n\n constructor() {\n this.registerDefaults();\n\n if (!this.browser) {\n return;\n }\n\n // The saved choice is read after the first render, so a hydrated page first renders exactly\n // what the server rendered (the site's default) and then switches.\n afterNextRender(() => this.userChoice.set(this.readChoice()), { injector: this.injector });\n\n inject(DestroyRef).onDestroy(() => {\n this.destroyed = true;\n this.document.removeEventListener('keydown', this.listener);\n });\n }\n\n /**\n * Runs the shortcuts on key presses until the returned function is called. `NgDocRootComponent`\n * calls it while the documentation layout is shown; a site with a layout of its own calls it\n * the same way. Several callers may listen at once: the shortcuts run until each has stopped.\n * Does nothing on the server.\n * @returns A function that stops listening; later calls do nothing.\n */\n listen(): () => void {\n if (!this.browser || this.destroyed) {\n return () => undefined;\n }\n\n if (this.listeners++ === 0) {\n // One document listener for every shortcut; change detection runs only when one matches.\n this.ngZone.runOutsideAngular(() => this.document.addEventListener('keydown', this.listener));\n }\n\n let listening = true;\n\n return () => {\n if (!listening) {\n return;\n }\n\n listening = false;\n\n if (--this.listeners === 0) {\n this.document.removeEventListener('keydown', this.listener);\n }\n };\n }\n\n /**\n * Turns single-key shortcuts on or off for this reader and saves the choice in the browser.\n * @param enabled - Whether single-key shortcuts should run.\n */\n setEnabled(enabled: boolean): void {\n this.userChoice.set(enabled);\n\n if (this.browser) {\n try {\n this.injector.get(NgDocStoreService).set(NG_DOC_STORE_SHORTCUTS_KEY, enabled ? '1' : '0');\n } catch {\n // Storage can be unavailable (private mode, blocked cookies); the choice lasts the visit.\n }\n }\n }\n\n /** Turns single-key shortcuts off when they are on, and on when they are off. */\n toggle(): void {\n this.setEnabled(!untracked(this.enabled));\n }\n\n /**\n * Registers a shortcut. When several shortcuts use the same key, the one registered last runs,\n * so a page can take over a key while it is shown and give it back when it is destroyed.\n * @param shortcut - The shortcut to register.\n * @returns A function that removes the shortcut.\n */\n register(shortcut: NgDocShortcut): () => void {\n this.shortcuts.push(shortcut);\n\n return () => {\n const index = this.shortcuts.lastIndexOf(shortcut);\n\n if (index !== -1) {\n this.shortcuts.splice(index, 1);\n }\n };\n }\n\n /**\n * Runs the single-key shortcut registered for a key, as if the reader pressed it, even while\n * single-key shortcuts are off. The search palette uses it for its actions.\n * @param key - The key of the shortcut, for example 't'.\n * @returns Whether a shortcut is registered for the key.\n */\n run(key: string): boolean {\n const shortcut = this.find(key.toLowerCase(), false);\n\n untracked(() => shortcut?.handler());\n\n return !!shortcut;\n }\n\n private onKeydown(event: KeyboardEvent): void {\n if (event.defaultPrevented || event.isComposing || event.repeat || !event.key) {\n return;\n }\n\n // AltGr (Control+Alt on Windows) types characters such as [ and ] on many layouts: those\n // keys are plain characters, not chords.\n const altGraph = !!event.getModifierState?.('AltGraph');\n const chord = !altGraph && (event.metaKey || event.ctrlKey) && !event.altKey;\n const shortcut = this.find(event.key.toLowerCase(), chord);\n\n if (!shortcut || (!chord && !this.acceptsSingleKey(event, altGraph))) {\n return;\n }\n\n event.preventDefault();\n this.ngZone.run(() => untracked(() => shortcut.handler(event)));\n }\n\n private find(key: string, chord: boolean): NgDocShortcut | undefined {\n for (let index = this.shortcuts.length - 1; index >= 0; index--) {\n const shortcut = this.shortcuts[index];\n\n if (shortcut.key.toLowerCase() === key && !!shortcut.chord === chord) {\n return shortcut;\n }\n }\n\n return undefined;\n }\n\n private acceptsSingleKey(event: KeyboardEvent, altGraph: boolean): boolean {\n if (\n !untracked(this.enabled) ||\n event.metaKey ||\n (!altGraph && (event.ctrlKey || event.altKey))\n ) {\n return false;\n }\n\n const target = event.composedPath?.()[0] ?? event.target;\n\n return !(target instanceof Element && target.closest(OWN_KEYS));\n }\n\n private readChoice(): boolean | undefined {\n try {\n const saved = this.injector.get(NgDocStoreService).get(NG_DOC_STORE_SHORTCUTS_KEY);\n\n return saved === '1' ? true : saved === '0' ? false : undefined;\n } catch {\n return undefined;\n }\n }\n\n private registerDefaults(): void {\n this.register({ key: 't', handler: () => this.toggleDarkTheme() });\n this.register({ key: 'l', handler: () => this.copyLink() });\n // Pager links: rel=\"prev\"/\"next\" is the standard marker; the classes are the NgDoc pager's.\n this.register({ key: '[', handler: () => this.click('a[rel~=\"prev\"], a.ng-doc-prev-page') });\n this.register({ key: ']', handler: () => this.click('a[rel~=\"next\"], a.ng-doc-next-page') });\n }\n\n private toggleDarkTheme(): void {\n const themeService = this.injector.get(NgDocThemeService);\n const theme = themeService.theme();\n const toggle = this.themeToggle;\n\n // A second T restores the theme the first one replaced (auto or a custom theme), unless the\n // reader changed the theme in between.\n if (toggle && toggle.set === theme) {\n this.themeToggle = undefined;\n themeService.set(toggle.previous ?? undefined);\n\n return;\n }\n\n const dark =\n theme === 'dark' ||\n (theme === 'auto' &&\n !!this.document.defaultView?.matchMedia?.('(prefers-color-scheme: dark)').matches);\n const next = dark ? null : 'dark';\n\n this.themeToggle = { previous: theme, set: next };\n themeService.set(next ?? undefined);\n }\n\n private copyLink(): void {\n void this.document.defaultView?.navigator.clipboard\n ?.writeText(this.document.location.href)\n .catch(() => undefined);\n }\n\n private click(selector: string): void {\n this.document.querySelector<HTMLElement>(selector)?.click();\n }\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}