@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.
- package/LICENSE +21 -0
- package/fesm2022/ng-doc-app-classes-content-controller.mjs +22 -4
- package/fesm2022/ng-doc-app-classes-content-controller.mjs.map +1 -1
- package/fesm2022/ng-doc-app-classes-default-search-engine.mjs +36 -12
- package/fesm2022/ng-doc-app-classes-default-search-engine.mjs.map +1 -1
- package/fesm2022/ng-doc-app-classes-root-page.mjs.map +1 -1
- package/fesm2022/ng-doc-app-components-api-list.mjs +7 -5
- package/fesm2022/ng-doc-app-components-api-list.mjs.map +1 -1
- package/fesm2022/ng-doc-app-components-breadcrumb.mjs +5 -3
- package/fesm2022/ng-doc-app-components-breadcrumb.mjs.map +1 -1
- package/fesm2022/ng-doc-app-components-code.mjs +6 -4
- package/fesm2022/ng-doc-app-components-code.mjs.map +1 -1
- package/fesm2022/ng-doc-app-components-command-palette.mjs +35 -27
- package/fesm2022/ng-doc-app-components-command-palette.mjs.map +1 -1
- package/fesm2022/ng-doc-app-components-copy-button.mjs +6 -4
- package/fesm2022/ng-doc-app-components-copy-button.mjs.map +1 -1
- package/fesm2022/ng-doc-app-components-demo-displayer.mjs +19 -4
- package/fesm2022/ng-doc-app-components-demo-displayer.mjs.map +1 -1
- package/fesm2022/ng-doc-app-components-demo-frame.mjs +279 -0
- package/fesm2022/ng-doc-app-components-demo-frame.mjs.map +1 -0
- package/fesm2022/ng-doc-app-components-demo-pane.mjs +2 -2
- package/fesm2022/ng-doc-app-components-demo-pane.mjs.map +1 -1
- package/fesm2022/ng-doc-app-components-demo.mjs +97 -14
- package/fesm2022/ng-doc-app-components-demo.mjs.map +1 -1
- package/fesm2022/ng-doc-app-components-fullscreen-button.mjs +5 -3
- package/fesm2022/ng-doc-app-components-fullscreen-button.mjs.map +1 -1
- package/fesm2022/ng-doc-app-components-fullscreen-toggle.mjs +4 -2
- package/fesm2022/ng-doc-app-components-fullscreen-toggle.mjs.map +1 -1
- package/fesm2022/ng-doc-app-components-heading-anchor.mjs +5 -3
- package/fesm2022/ng-doc-app-components-heading-anchor.mjs.map +1 -1
- package/fesm2022/ng-doc-app-components-image-viewer.mjs +5 -3
- package/fesm2022/ng-doc-app-components-image-viewer.mjs.map +1 -1
- package/fesm2022/ng-doc-app-components-navbar.mjs +4 -2
- package/fesm2022/ng-doc-app-components-navbar.mjs.map +1 -1
- package/fesm2022/ng-doc-app-components-page-navigation.mjs +5 -3
- package/fesm2022/ng-doc-app-components-page-navigation.mjs.map +1 -1
- package/fesm2022/ng-doc-app-components-page-wrapper.mjs +2 -2
- package/fesm2022/ng-doc-app-components-page-wrapper.mjs.map +1 -1
- package/fesm2022/ng-doc-app-components-page.mjs +83 -16
- package/fesm2022/ng-doc-app-components-page.mjs.map +1 -1
- package/fesm2022/ng-doc-app-components-playground.mjs +838 -141
- package/fesm2022/ng-doc-app-components-playground.mjs.map +1 -1
- package/fesm2022/ng-doc-app-components-root.mjs +51 -4
- package/fesm2022/ng-doc-app-components-root.mjs.map +1 -1
- package/fesm2022/ng-doc-app-components-search-result.mjs +8 -6
- package/fesm2022/ng-doc-app-components-search-result.mjs.map +1 -1
- package/fesm2022/ng-doc-app-components-search.mjs +7 -5
- package/fesm2022/ng-doc-app-components-search.mjs.map +1 -1
- package/fesm2022/ng-doc-app-components-sidebar.mjs +4 -3
- package/fesm2022/ng-doc-app-components-sidebar.mjs.map +1 -1
- package/fesm2022/ng-doc-app-components-tabs.mjs +21 -3
- package/fesm2022/ng-doc-app-components-tabs.mjs.map +1 -1
- package/fesm2022/ng-doc-app-components-theme-toggle.mjs +7 -5
- package/fesm2022/ng-doc-app-components-theme-toggle.mjs.map +1 -1
- package/fesm2022/ng-doc-app-components-toc.mjs +4 -2
- package/fesm2022/ng-doc-app-components-toc.mjs.map +1 -1
- package/fesm2022/ng-doc-app-components.mjs +1 -0
- package/fesm2022/ng-doc-app-components.mjs.map +1 -1
- package/fesm2022/ng-doc-app-constants.mjs +11 -1
- package/fesm2022/ng-doc-app-constants.mjs.map +1 -1
- package/fesm2022/ng-doc-app-demo-app.mjs +779 -0
- package/fesm2022/ng-doc-app-demo-app.mjs.map +1 -0
- package/fesm2022/ng-doc-app-helpers.mjs +147 -7
- package/fesm2022/ng-doc-app-helpers.mjs.map +1 -1
- package/fesm2022/ng-doc-app-processors-processors.mjs +30 -9
- package/fesm2022/ng-doc-app-processors-processors.mjs.map +1 -1
- package/fesm2022/ng-doc-app-providers-ng-doc-app.mjs +29 -18
- package/fesm2022/ng-doc-app-providers-ng-doc-app.mjs.map +1 -1
- package/fesm2022/ng-doc-app-providers-texts.mjs +53 -0
- package/fesm2022/ng-doc-app-providers-texts.mjs.map +1 -0
- package/fesm2022/ng-doc-app-providers-title.mjs +129 -0
- package/fesm2022/ng-doc-app-providers-title.mjs.map +1 -0
- package/fesm2022/ng-doc-app-providers.mjs +2 -0
- package/fesm2022/ng-doc-app-providers.mjs.map +1 -1
- package/fesm2022/ng-doc-app-services-content-scroll-intent.mjs +27 -2
- package/fesm2022/ng-doc-app-services-content-scroll-intent.mjs.map +1 -1
- package/fesm2022/ng-doc-app-services-highlighter.mjs +23 -16
- package/fesm2022/ng-doc-app-services-highlighter.mjs.map +1 -1
- package/fesm2022/ng-doc-app-services-route-preloader.mjs +34 -10
- package/fesm2022/ng-doc-app-services-route-preloader.mjs.map +1 -1
- package/fesm2022/ng-doc-app-services-shortcuts.mjs +38 -4
- package/fesm2022/ng-doc-app-services-shortcuts.mjs.map +1 -1
- package/fesm2022/ng-doc-app-texts-ar.mjs +238 -0
- package/fesm2022/ng-doc-app-texts-ar.mjs.map +1 -0
- package/fesm2022/ng-doc-app-texts-de.mjs +205 -0
- package/fesm2022/ng-doc-app-texts-de.mjs.map +1 -0
- package/fesm2022/ng-doc-app-texts-es.mjs +205 -0
- package/fesm2022/ng-doc-app-texts-es.mjs.map +1 -0
- package/fesm2022/ng-doc-app-texts-fr.mjs +205 -0
- package/fesm2022/ng-doc-app-texts-fr.mjs.map +1 -0
- package/fesm2022/ng-doc-app-texts-hi.mjs +219 -0
- package/fesm2022/ng-doc-app-texts-hi.mjs.map +1 -0
- package/fesm2022/ng-doc-app-texts-ja.mjs +205 -0
- package/fesm2022/ng-doc-app-texts-ja.mjs.map +1 -0
- package/fesm2022/ng-doc-app-texts-ko.mjs +205 -0
- package/fesm2022/ng-doc-app-texts-ko.mjs.map +1 -0
- package/fesm2022/ng-doc-app-texts-pt-br.mjs +205 -0
- package/fesm2022/ng-doc-app-texts-pt-br.mjs.map +1 -0
- package/fesm2022/ng-doc-app-texts-ru.mjs +231 -0
- package/fesm2022/ng-doc-app-texts-ru.mjs.map +1 -0
- package/fesm2022/ng-doc-app-texts-uk.mjs +231 -0
- package/fesm2022/ng-doc-app-texts-uk.mjs.map +1 -0
- package/fesm2022/ng-doc-app-texts-zh.mjs +205 -0
- package/fesm2022/ng-doc-app-texts-zh.mjs.map +1 -0
- package/fesm2022/ng-doc-app-tokens.mjs +214 -1
- package/fesm2022/ng-doc-app-tokens.mjs.map +1 -1
- package/fesm2022/ng-doc-app-type-controls-json-control.mjs +90 -0
- package/fesm2022/ng-doc-app-type-controls-json-control.mjs.map +1 -0
- package/fesm2022/ng-doc-app-type-controls-list-control.mjs +792 -0
- package/fesm2022/ng-doc-app-type-controls-list-control.mjs.map +1 -0
- package/fesm2022/ng-doc-app-type-controls-type-alias-control.mjs +45 -8
- package/fesm2022/ng-doc-app-type-controls-type-alias-control.mjs.map +1 -1
- package/fesm2022/ng-doc-app-type-controls.mjs +2 -0
- package/fesm2022/ng-doc-app-type-controls.mjs.map +1 -1
- package/package.json +73 -4
- package/styles/global.css +4 -0
- package/styles/main.css +4 -0
- package/styles/themes/dark.css +10 -0
- package/types/ng-doc-app-classes-default-search-engine.d.ts +11 -0
- package/types/ng-doc-app-classes-root-page.d.ts +13 -0
- package/types/ng-doc-app-components-api-list.d.ts +3 -1
- package/types/ng-doc-app-components-breadcrumb.d.ts +2 -1
- package/types/ng-doc-app-components-code.d.ts +2 -0
- package/types/ng-doc-app-components-command-palette.d.ts +3 -1
- package/types/ng-doc-app-components-copy-button.d.ts +3 -1
- package/types/ng-doc-app-components-demo-displayer.d.ts +13 -1
- package/types/ng-doc-app-components-demo-frame.d.ts +107 -0
- package/types/ng-doc-app-components-demo.d.ts +47 -10
- package/types/ng-doc-app-components-fullscreen-button.d.ts +2 -0
- package/types/ng-doc-app-components-fullscreen-toggle.d.ts +2 -0
- package/types/ng-doc-app-components-heading-anchor.d.ts +2 -0
- package/types/ng-doc-app-components-image-viewer.d.ts +2 -0
- package/types/ng-doc-app-components-navbar.d.ts +2 -0
- package/types/ng-doc-app-components-page-navigation.d.ts +2 -1
- package/types/ng-doc-app-components-page.d.ts +10 -1
- package/types/ng-doc-app-components-playground.d.ts +260 -42
- package/types/ng-doc-app-components-root.d.ts +18 -0
- package/types/ng-doc-app-components-search-result.d.ts +2 -0
- package/types/ng-doc-app-components-search.d.ts +2 -0
- package/types/ng-doc-app-components-sidebar.d.ts +2 -1
- package/types/ng-doc-app-components-tabs.d.ts +13 -3
- package/types/ng-doc-app-components-theme-toggle.d.ts +4 -1
- package/types/ng-doc-app-components-toc.d.ts +2 -1
- package/types/ng-doc-app-components.d.ts +1 -0
- package/types/ng-doc-app-constants.d.ts +11 -1
- package/types/ng-doc-app-demo-app.d.ts +297 -0
- package/types/ng-doc-app-helpers.d.ts +75 -4
- package/types/ng-doc-app-interfaces.d.ts +635 -6
- package/types/ng-doc-app-processors-processors.d.ts +5 -0
- package/types/ng-doc-app-providers-ng-doc-app.d.ts +7 -1
- package/types/ng-doc-app-providers-texts.d.ts +28 -0
- package/types/ng-doc-app-providers-title.d.ts +59 -0
- package/types/ng-doc-app-providers.d.ts +2 -0
- package/types/ng-doc-app-services-content-scroll-intent.d.ts +11 -1
- package/types/ng-doc-app-services-highlighter.d.ts +15 -6
- package/types/ng-doc-app-services-route-preloader.d.ts +13 -1
- package/types/ng-doc-app-services-shortcuts.d.ts +16 -0
- package/types/ng-doc-app-texts-ar.d.ts +13 -0
- package/types/ng-doc-app-texts-de.d.ts +12 -0
- package/types/ng-doc-app-texts-es.d.ts +12 -0
- package/types/ng-doc-app-texts-fr.d.ts +12 -0
- package/types/ng-doc-app-texts-hi.d.ts +12 -0
- package/types/ng-doc-app-texts-ja.d.ts +12 -0
- package/types/ng-doc-app-texts-ko.d.ts +12 -0
- package/types/ng-doc-app-texts-pt-br.d.ts +12 -0
- package/types/ng-doc-app-texts-ru.d.ts +12 -0
- package/types/ng-doc-app-texts-uk.d.ts +12 -0
- package/types/ng-doc-app-texts-zh.d.ts +12 -0
- package/types/ng-doc-app-tokens.d.ts +21 -2
- package/types/ng-doc-app-type-controls-json-control.d.ts +38 -0
- package/types/ng-doc-app-type-controls-list-control.d.ts +313 -0
- package/types/ng-doc-app-type-controls-type-alias-control.d.ts +28 -5
- package/types/ng-doc-app-type-controls.d.ts +2 -0
|
@@ -0,0 +1,779 @@
|
|
|
1
|
+
import { isPlatformBrowser, NgComponentOutlet } from '@angular/common';
|
|
2
|
+
import * as i0 from '@angular/core';
|
|
3
|
+
import { inject, PLATFORM_ID, DOCUMENT, DestroyRef, ElementRef, ChangeDetectionStrategy, ViewEncapsulation, Component, provideZoneChangeDetection, provideZonelessChangeDetection } from '@angular/core';
|
|
4
|
+
import { RouterOutlet, ActivatedRoute, provideRouter } from '@angular/router';
|
|
5
|
+
import { NG_DOC_STORE_THEME_KEY } from '@ng-doc/app/constants';
|
|
6
|
+
import { provideClientHydration, withNoIncrementalHydration } from '@angular/platform-browser';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* The `source` of every message the demo application sends to the page that embeds it.
|
|
10
|
+
* @internal
|
|
11
|
+
*/
|
|
12
|
+
const ɵNG_DOC_DEMO_MESSAGE_SOURCE = 'ng-doc-demo';
|
|
13
|
+
/**
|
|
14
|
+
* Whether a message is a size message of the demo application.
|
|
15
|
+
* @param value - The data of a `message` event.
|
|
16
|
+
* @internal
|
|
17
|
+
*/
|
|
18
|
+
function ɵisNgDocDemoSizeMessage(value) {
|
|
19
|
+
const message = value;
|
|
20
|
+
return (typeof message === 'object' &&
|
|
21
|
+
message !== null &&
|
|
22
|
+
message.source === ɵNG_DOC_DEMO_MESSAGE_SOURCE &&
|
|
23
|
+
message.version === 1 &&
|
|
24
|
+
message.type === 'size' &&
|
|
25
|
+
typeof message.height === 'number' &&
|
|
26
|
+
Number.isFinite(message.height) &&
|
|
27
|
+
message.height >= 0);
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Whether a message is a background message of the demo application.
|
|
31
|
+
* @param value - The data of a `message` event.
|
|
32
|
+
* @internal
|
|
33
|
+
*/
|
|
34
|
+
function ɵisNgDocDemoBackgroundMessage(value) {
|
|
35
|
+
const message = value;
|
|
36
|
+
return (typeof message === 'object' &&
|
|
37
|
+
message !== null &&
|
|
38
|
+
message.source === ɵNG_DOC_DEMO_MESSAGE_SOURCE &&
|
|
39
|
+
message.version === 1 &&
|
|
40
|
+
message.type === 'background' &&
|
|
41
|
+
typeof message.color === 'string' &&
|
|
42
|
+
message.color.length > 0 &&
|
|
43
|
+
message.color.length <= 200 &&
|
|
44
|
+
(message.dots === undefined ||
|
|
45
|
+
(typeof message.dots === 'string' && message.dots.length <= 200)) &&
|
|
46
|
+
(message.scheme === undefined ||
|
|
47
|
+
(typeof message.scheme === 'string' && message.scheme.length <= 50)));
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** The fragment parameter that sets the theme of a demo page; an input name never starts with `~`. */
|
|
51
|
+
const THEME = '~theme';
|
|
52
|
+
/**
|
|
53
|
+
* The theme that the URL fragment of a demo page sets, or `undefined` when it sets none and the
|
|
54
|
+
* page follows the theme of the site.
|
|
55
|
+
* @param fragment - The fragment, with or without the leading `#`.
|
|
56
|
+
* @internal
|
|
57
|
+
*/
|
|
58
|
+
function ɵdemoThemeFromFragment(fragment) {
|
|
59
|
+
for (const parameter of fragment.replace(/^#/, '').split('&')) {
|
|
60
|
+
if (!parameter.startsWith(`${THEME}=`))
|
|
61
|
+
continue;
|
|
62
|
+
try {
|
|
63
|
+
return decodeURIComponent(parameter.slice(THEME.length + 1)) || undefined;
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
return undefined;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return undefined;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* A fragment (without `#`) with the theme parameter set, after the other parameters, or without it
|
|
73
|
+
* when there is no theme. `undefined` when nothing is left.
|
|
74
|
+
* @param fragment - The fragment without the theme, or none.
|
|
75
|
+
* @param theme - The theme of the demo page, or none to follow the site.
|
|
76
|
+
* @internal
|
|
77
|
+
*/
|
|
78
|
+
function ɵwithDemoTheme(fragment, theme) {
|
|
79
|
+
const parameters = (fragment ?? '')
|
|
80
|
+
.split('&')
|
|
81
|
+
.filter((parameter) => parameter && !parameter.startsWith(`${THEME}=`));
|
|
82
|
+
if (theme)
|
|
83
|
+
parameters.push(`${THEME}=${encodeURIComponent(theme)}`);
|
|
84
|
+
return parameters.length ? parameters.join('&') : undefined;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Whether a parameter of a fragment is the theme of the demo page, which the state of a
|
|
88
|
+
* playground leaves to the demo application.
|
|
89
|
+
* @param name - The decoded name of the parameter.
|
|
90
|
+
* @internal
|
|
91
|
+
*/
|
|
92
|
+
function ɵisDemoThemeParameter(name) {
|
|
93
|
+
return name === THEME;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* The root of the demo application, which shows one demo per page with only Angular and that
|
|
98
|
+
* demo loaded. In an iframe it reports the height of its content to the page that embeds it (a
|
|
99
|
+
* `ResizeObserver` on its border box), and it follows the theme the reader picks on the
|
|
100
|
+
* documentation site, which stores it in `localStorage`, unless its URL fragment sets a theme
|
|
101
|
+
* (`~theme=dark`, the `theme` option of a demo or a playground, or the theme button beside it).
|
|
102
|
+
* In an iframe its page is transparent, so the canvas of the documentation page shows through, and
|
|
103
|
+
* it reports its theme's background, dots and color scheme whenever they change, so that the
|
|
104
|
+
* embedding page can paint the canvas in the demo's own theme.
|
|
105
|
+
*/
|
|
106
|
+
class NgDocDemoAppComponent {
|
|
107
|
+
constructor() {
|
|
108
|
+
if (!isPlatformBrowser(inject(PLATFORM_ID)))
|
|
109
|
+
return;
|
|
110
|
+
const document = inject(DOCUMENT);
|
|
111
|
+
const window = document.defaultView;
|
|
112
|
+
const destroyRef = inject(DestroyRef);
|
|
113
|
+
if (!window)
|
|
114
|
+
return;
|
|
115
|
+
const embedded = window.parent !== window;
|
|
116
|
+
let reportedBackground = '';
|
|
117
|
+
// The page's background: the body's, or the document's when the body has none. Reported only
|
|
118
|
+
// when it changes, and once the page has one (its styles may still be loading).
|
|
119
|
+
const reportBackground = () => {
|
|
120
|
+
if (!embedded)
|
|
121
|
+
return;
|
|
122
|
+
// The theme's background and dots, from its variables: the page itself is transparent in
|
|
123
|
+
// an iframe, so its computed background is not the theme's.
|
|
124
|
+
const style = window.getComputedStyle(document.documentElement);
|
|
125
|
+
const color = style.getPropertyValue('--ng-doc-background').trim();
|
|
126
|
+
const dots = style.getPropertyValue('--ng-doc-base-5').trim();
|
|
127
|
+
// A transparent iframe shows its page only while the iframe element has the page's color
|
|
128
|
+
// scheme; otherwise the browser paints an opaque backdrop behind it.
|
|
129
|
+
const scheme = style.colorScheme && style.colorScheme !== 'normal' ? style.colorScheme : '';
|
|
130
|
+
const key = `${color}|${dots}|${scheme}`;
|
|
131
|
+
if (!color || key === reportedBackground)
|
|
132
|
+
return;
|
|
133
|
+
reportedBackground = key;
|
|
134
|
+
const message = {
|
|
135
|
+
source: ɵNG_DOC_DEMO_MESSAGE_SOURCE,
|
|
136
|
+
version: 1,
|
|
137
|
+
type: 'background',
|
|
138
|
+
color,
|
|
139
|
+
...(dots ? { dots } : {}),
|
|
140
|
+
...(scheme ? { scheme } : {}),
|
|
141
|
+
};
|
|
142
|
+
window.parent.postMessage(message, window.location.origin);
|
|
143
|
+
};
|
|
144
|
+
if (embedded) {
|
|
145
|
+
// The page is transparent in an iframe, so the canvas of the documentation page, dots and
|
|
146
|
+
// all, shows through around and behind the demo.
|
|
147
|
+
document.documentElement.classList.add('ng-doc-demo-embedded');
|
|
148
|
+
const host = inject(ElementRef).nativeElement;
|
|
149
|
+
let reported = -1;
|
|
150
|
+
const observer = new ResizeObserver(() => {
|
|
151
|
+
const height = Math.ceil(host.getBoundingClientRect().height);
|
|
152
|
+
// Styles that load after the first report change the background too.
|
|
153
|
+
reportBackground();
|
|
154
|
+
if (height === reported)
|
|
155
|
+
return;
|
|
156
|
+
reported = height;
|
|
157
|
+
const message = {
|
|
158
|
+
source: ɵNG_DOC_DEMO_MESSAGE_SOURCE,
|
|
159
|
+
version: 1,
|
|
160
|
+
type: 'size',
|
|
161
|
+
height,
|
|
162
|
+
};
|
|
163
|
+
// Only a page of the same origin may receive it: the demo pages are part of the site.
|
|
164
|
+
window.parent.postMessage(message, window.location.origin);
|
|
165
|
+
});
|
|
166
|
+
observer.observe(host, { box: 'border-box' });
|
|
167
|
+
destroyRef.onDestroy(() => observer.disconnect());
|
|
168
|
+
}
|
|
169
|
+
// An empty id is the light theme, as in the restore script that applies the stored theme
|
|
170
|
+
// before the application starts; so is `light`, which the `theme` option uses.
|
|
171
|
+
const apply = (theme) => {
|
|
172
|
+
if (theme && theme !== 'light') {
|
|
173
|
+
document.documentElement.setAttribute('data-theme', theme);
|
|
174
|
+
}
|
|
175
|
+
else {
|
|
176
|
+
document.documentElement.removeAttribute('data-theme');
|
|
177
|
+
}
|
|
178
|
+
reportBackground();
|
|
179
|
+
};
|
|
180
|
+
const pinned = () => ɵdemoThemeFromFragment(window.location.hash);
|
|
181
|
+
const site = () => {
|
|
182
|
+
try {
|
|
183
|
+
return window.localStorage.getItem(NG_DOC_STORE_THEME_KEY);
|
|
184
|
+
}
|
|
185
|
+
catch {
|
|
186
|
+
return null;
|
|
187
|
+
}
|
|
188
|
+
};
|
|
189
|
+
// The documentation page and the demo page share the origin, so a theme the reader picks
|
|
190
|
+
// reaches this page as a storage event; a theme of the fragment wins over it.
|
|
191
|
+
const onStorage = (event) => {
|
|
192
|
+
if (event.key !== NG_DOC_STORE_THEME_KEY || event.newValue === null || pinned())
|
|
193
|
+
return;
|
|
194
|
+
apply(event.newValue);
|
|
195
|
+
};
|
|
196
|
+
// The documentation page changes the fragment with `location.replace`, without a reload.
|
|
197
|
+
const onHashChange = () => apply(pinned() ?? site());
|
|
198
|
+
// The `auto` theme follows the system's color scheme.
|
|
199
|
+
const scheme = window.matchMedia?.('(prefers-color-scheme: dark)');
|
|
200
|
+
if (pinned())
|
|
201
|
+
apply(pinned());
|
|
202
|
+
reportBackground();
|
|
203
|
+
window.addEventListener('storage', onStorage);
|
|
204
|
+
window.addEventListener('hashchange', onHashChange);
|
|
205
|
+
scheme?.addEventListener?.('change', reportBackground);
|
|
206
|
+
destroyRef.onDestroy(() => {
|
|
207
|
+
window.removeEventListener('storage', onStorage);
|
|
208
|
+
window.removeEventListener('hashchange', onHashChange);
|
|
209
|
+
scheme?.removeEventListener?.('change', reportBackground);
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.2.1", ngImport: i0, type: NgDocDemoAppComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
213
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "22.2.1", type: NgDocDemoAppComponent, isStandalone: true, selector: "ng-doc-demo-app", ngImport: i0, template: '<router-outlet />', isInline: true, styles: ["ng-doc-demo-app{display:flow-root}ng-doc-demo-host,ng-doc-playground-host{display:flex;justify-content:center;min-width:0}html.ng-doc-demo-embedded,html.ng-doc-demo-embedded body{background-color:transparent}\n"], dependencies: [{ kind: "directive", type: RouterOutlet, selector: "router-outlet", inputs: ["name", "routerOutletData"], outputs: ["activate", "deactivate", "attach", "detach"], exportAs: ["outlet"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None }); }
|
|
214
|
+
}
|
|
215
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.2.1", ngImport: i0, type: NgDocDemoAppComponent, decorators: [{
|
|
216
|
+
type: Component,
|
|
217
|
+
args: [{ selector: 'ng-doc-demo-app', imports: [RouterOutlet], template: '<router-outlet />', encapsulation: ViewEncapsulation.None, changeDetection: ChangeDetectionStrategy.OnPush, styles: ["ng-doc-demo-app{display:flow-root}ng-doc-demo-host,ng-doc-playground-host{display:flex;justify-content:center;min-width:0}html.ng-doc-demo-embedded,html.ng-doc-demo-embedded body{background-color:transparent}\n"] }]
|
|
218
|
+
}], ctorParameters: () => [] });
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Shows the demo of a demo page: the component in the route's `ngDocDemo` data, with the inputs
|
|
222
|
+
* of the `inputs` query parameter (the JSON of the `inputs` option of the `demo` action).
|
|
223
|
+
*
|
|
224
|
+
* The demo application hydrates its server-rendered page, but not the demo: a demo is the user's
|
|
225
|
+
* component and need not be hydration-safe, and the server renders it without the inputs of the
|
|
226
|
+
* query, which only the browser has. So the host skips hydration, as documentation pages do for
|
|
227
|
+
* their content, and the browser renders the demo again.
|
|
228
|
+
*/
|
|
229
|
+
class NgDocDemoHostComponent {
|
|
230
|
+
constructor() {
|
|
231
|
+
this.route = inject(ActivatedRoute);
|
|
232
|
+
/** The demo component, or undefined on the page of a demo that does not exist. */
|
|
233
|
+
this.demo = this.route.snapshot.data['ngDocDemo'];
|
|
234
|
+
/** The inputs of the demo. */
|
|
235
|
+
this.inputs = demoInputs(this.route.snapshot.queryParamMap.get('inputs'));
|
|
236
|
+
}
|
|
237
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.2.1", ngImport: i0, type: NgDocDemoHostComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
238
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.2.1", type: NgDocDemoHostComponent, isStandalone: true, selector: "ng-doc-demo-host", host: { attributes: { "ngSkipHydration": "true" } }, ngImport: i0, template: `
|
|
239
|
+
@if (demo) {
|
|
240
|
+
<ng-container *ngComponentOutlet="demo; inputs: inputs" />
|
|
241
|
+
} @else {
|
|
242
|
+
<p role="alert">This demo does not exist.</p>
|
|
243
|
+
}
|
|
244
|
+
`, isInline: true, dependencies: [{ kind: "directive", type: NgComponentOutlet, selector: "[ngComponentOutlet]", inputs: ["ngComponentOutlet", "ngComponentOutletInputs", "ngComponentOutletInjector", "ngComponentOutletEnvironmentInjector", "ngComponentOutletContent", "ngComponentOutletNgModule"], exportAs: ["ngComponentOutlet"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
245
|
+
}
|
|
246
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.2.1", ngImport: i0, type: NgDocDemoHostComponent, decorators: [{
|
|
247
|
+
type: Component,
|
|
248
|
+
args: [{
|
|
249
|
+
selector: 'ng-doc-demo-host',
|
|
250
|
+
imports: [NgComponentOutlet],
|
|
251
|
+
template: `
|
|
252
|
+
@if (demo) {
|
|
253
|
+
<ng-container *ngComponentOutlet="demo; inputs: inputs" />
|
|
254
|
+
} @else {
|
|
255
|
+
<p role="alert">This demo does not exist.</p>
|
|
256
|
+
}
|
|
257
|
+
`,
|
|
258
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
259
|
+
host: { ngSkipHydration: 'true' },
|
|
260
|
+
}]
|
|
261
|
+
}] });
|
|
262
|
+
/**
|
|
263
|
+
* The inputs of the `inputs` query parameter: a JSON object, or none.
|
|
264
|
+
* @param value - The parameter.
|
|
265
|
+
*/
|
|
266
|
+
function demoInputs(value) {
|
|
267
|
+
if (!value)
|
|
268
|
+
return {};
|
|
269
|
+
try {
|
|
270
|
+
const parsed = JSON.parse(value);
|
|
271
|
+
return parsed && typeof parsed === 'object' && !Array.isArray(parsed)
|
|
272
|
+
? parsed
|
|
273
|
+
: {};
|
|
274
|
+
}
|
|
275
|
+
catch {
|
|
276
|
+
return {};
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* The configuration of the demo application: change detection, the router with the demo pages
|
|
282
|
+
* (and a page for unknown demos), hydration, and the providers of `demoProviders`. The generated entries of
|
|
283
|
+
* the demo application bootstrap `NgDocDemoAppComponent` with it, in the browser and on the server.
|
|
284
|
+
* @param routes - The generated demo routes.
|
|
285
|
+
* @param demoProviders - The generated import of the `demoProviders` module, if any.
|
|
286
|
+
* @param options - How the application starts.
|
|
287
|
+
* @internal
|
|
288
|
+
*/
|
|
289
|
+
async function ɵngDocDemoApplicationConfig(routes, demoProviders, options = {}) {
|
|
290
|
+
const providers = demoProviders ? (await demoProviders()).default : [];
|
|
291
|
+
return {
|
|
292
|
+
providers: [
|
|
293
|
+
options.zone ? provideZoneChangeDetection() : provideZonelessChangeDetection(),
|
|
294
|
+
...(options.hydration ? [provideClientHydration(withNoIncrementalHydration())] : []),
|
|
295
|
+
provideRouter([...routes, { path: '**', component: NgDocDemoHostComponent }]),
|
|
296
|
+
...providers,
|
|
297
|
+
...(options.providers ?? []),
|
|
298
|
+
],
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/** The key of a tagged value: a value JSON has no form for (`undefined`, `NaN`, a `Date`, …). */
|
|
303
|
+
const TAG = '$ngdoc';
|
|
304
|
+
/** Deeper values are refused: a playground input never needs them, and a cycle would recurse. */
|
|
305
|
+
const MAX_DEPTH = 32;
|
|
306
|
+
/**
|
|
307
|
+
* The longest encoded state an "Open in a new tab" URL carries; a longer one is left out.
|
|
308
|
+
* @internal
|
|
309
|
+
*/
|
|
310
|
+
const ɵNG_DOC_PLAYGROUND_STATE_URL_LIMIT = 8192;
|
|
311
|
+
/**
|
|
312
|
+
* Encodes a value for the frame of an isolated playground. Only plain data is accepted:
|
|
313
|
+
* strings, numbers (`NaN`, the infinities and `-0` included), booleans, `null`, `undefined`, `Date`s,
|
|
314
|
+
* arrays and plain objects (prototype `Object.prototype` or `null`, own enumerable data
|
|
315
|
+
* properties). Anything else (a function, a symbol, a `bigint`, a class instance such as a
|
|
316
|
+
* `TemplateRef`, a `Map` or a signal, a getter, a cycle) is refused rather than sent changed: a
|
|
317
|
+
* structured clone would turn a class instance into a plain object without its behaviour, and
|
|
318
|
+
* the demo would then run with a value that isn't the one the inspector shows.
|
|
319
|
+
* @param value - The value of a playground input.
|
|
320
|
+
* @internal
|
|
321
|
+
*/
|
|
322
|
+
function ɵencodePlaygroundValue(value) {
|
|
323
|
+
try {
|
|
324
|
+
return { ok: true, value: encode(value, '', 0, new Set()) };
|
|
325
|
+
}
|
|
326
|
+
catch (error) {
|
|
327
|
+
if (error instanceof Unencodable)
|
|
328
|
+
return { ok: false, path: error.path, reason: error.reason };
|
|
329
|
+
throw error;
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
/**
|
|
333
|
+
* Decodes a value that {@link ɵencodePlaygroundValue} encoded. Throws on anything it can't have
|
|
334
|
+
* produced, so a malformed message or URL never reaches the demo half-decoded.
|
|
335
|
+
* @param value - An encoded value.
|
|
336
|
+
* @internal
|
|
337
|
+
*/
|
|
338
|
+
function ɵdecodePlaygroundValue(value) {
|
|
339
|
+
return decode(value, '', 0);
|
|
340
|
+
}
|
|
341
|
+
/**
|
|
342
|
+
* The encoded values of an "Open in a new tab" URL: base64url of their JSON, or `undefined`
|
|
343
|
+
* when it would be longer than {@link ɵNG_DOC_PLAYGROUND_STATE_URL_LIMIT}.
|
|
344
|
+
* @param values - Encoded values, by input name.
|
|
345
|
+
* @internal
|
|
346
|
+
*/
|
|
347
|
+
function ɵplaygroundStateToUrl(values) {
|
|
348
|
+
const bytes = new TextEncoder().encode(JSON.stringify(values));
|
|
349
|
+
let binary = '';
|
|
350
|
+
for (const byte of bytes)
|
|
351
|
+
binary += String.fromCharCode(byte);
|
|
352
|
+
const text = btoa(binary).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
|
|
353
|
+
return text.length > ɵNG_DOC_PLAYGROUND_STATE_URL_LIMIT ? undefined : text;
|
|
354
|
+
}
|
|
355
|
+
/**
|
|
356
|
+
* The encoded values of an "Open in a new tab" URL, or `undefined` when the parameter isn't one
|
|
357
|
+
* that {@link ɵplaygroundStateToUrl} wrote.
|
|
358
|
+
* @param text - The `state` query parameter.
|
|
359
|
+
* @internal
|
|
360
|
+
*/
|
|
361
|
+
function ɵplaygroundStateFromUrl(text) {
|
|
362
|
+
if (!/^[A-Za-z0-9_-]*$/.test(text) || text.length > ɵNG_DOC_PLAYGROUND_STATE_URL_LIMIT) {
|
|
363
|
+
return undefined;
|
|
364
|
+
}
|
|
365
|
+
try {
|
|
366
|
+
const binary = atob(text.replace(/-/g, '+').replace(/_/g, '/'));
|
|
367
|
+
const bytes = Uint8Array.from(binary, (character) => character.charCodeAt(0));
|
|
368
|
+
const parsed = JSON.parse(new TextDecoder('utf-8', { fatal: true }).decode(bytes));
|
|
369
|
+
if (!isPlainObject(parsed))
|
|
370
|
+
return undefined;
|
|
371
|
+
// Every value must decode; the decoded form is computed again where it is used.
|
|
372
|
+
for (const value of Object.values(parsed))
|
|
373
|
+
decode(value, '', 0);
|
|
374
|
+
return parsed;
|
|
375
|
+
}
|
|
376
|
+
catch {
|
|
377
|
+
return undefined;
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
class Unencodable extends Error {
|
|
381
|
+
constructor(path, reason) {
|
|
382
|
+
super(`${path || 'value'}: ${reason}`);
|
|
383
|
+
this.path = path;
|
|
384
|
+
this.reason = reason;
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
/**
|
|
388
|
+
*
|
|
389
|
+
* @param value
|
|
390
|
+
*/
|
|
391
|
+
function isPlainObject(value) {
|
|
392
|
+
if (typeof value !== 'object' || value === null || Array.isArray(value))
|
|
393
|
+
return false;
|
|
394
|
+
const prototype = Object.getPrototypeOf(value);
|
|
395
|
+
return prototype === Object.prototype || prototype === null;
|
|
396
|
+
}
|
|
397
|
+
/**
|
|
398
|
+
*
|
|
399
|
+
* @param path
|
|
400
|
+
* @param key
|
|
401
|
+
*/
|
|
402
|
+
function child(path, key) {
|
|
403
|
+
if (typeof key === 'number')
|
|
404
|
+
return `${path}[${key}]`;
|
|
405
|
+
return /^[A-Za-z_$][\w$]*$/.test(key)
|
|
406
|
+
? path
|
|
407
|
+
? `${path}.${key}`
|
|
408
|
+
: key
|
|
409
|
+
: `${path}[${JSON.stringify(key)}]`;
|
|
410
|
+
}
|
|
411
|
+
/**
|
|
412
|
+
*
|
|
413
|
+
* @param tag
|
|
414
|
+
* @param value
|
|
415
|
+
*/
|
|
416
|
+
function tagged(tag, value) {
|
|
417
|
+
return value === undefined ? { [TAG]: tag } : { [TAG]: tag, value };
|
|
418
|
+
}
|
|
419
|
+
/**
|
|
420
|
+
*
|
|
421
|
+
* @param value
|
|
422
|
+
* @param path
|
|
423
|
+
* @param depth
|
|
424
|
+
* @param ancestors
|
|
425
|
+
*/
|
|
426
|
+
function encode(value, path, depth, ancestors) {
|
|
427
|
+
switch (typeof value) {
|
|
428
|
+
case 'string':
|
|
429
|
+
case 'boolean':
|
|
430
|
+
return value;
|
|
431
|
+
case 'number':
|
|
432
|
+
// JSON writes -0 as 0, so -0 is tagged like the numbers JSON has no form for.
|
|
433
|
+
return Number.isFinite(value) && !Object.is(value, -0)
|
|
434
|
+
? value
|
|
435
|
+
: tagged('number', Object.is(value, -0) ? '-0' : String(value));
|
|
436
|
+
case 'undefined':
|
|
437
|
+
return tagged('undefined');
|
|
438
|
+
case 'function':
|
|
439
|
+
throw new Unencodable(path, 'a function');
|
|
440
|
+
case 'symbol':
|
|
441
|
+
throw new Unencodable(path, 'a symbol');
|
|
442
|
+
case 'bigint':
|
|
443
|
+
throw new Unencodable(path, 'a bigint');
|
|
444
|
+
}
|
|
445
|
+
if (value === null)
|
|
446
|
+
return null;
|
|
447
|
+
if (depth >= MAX_DEPTH)
|
|
448
|
+
throw new Unencodable(path, `nested deeper than ${MAX_DEPTH} levels`);
|
|
449
|
+
if (value instanceof Date) {
|
|
450
|
+
const time = value.getTime();
|
|
451
|
+
return tagged('date', Number.isNaN(time) ? 'Invalid Date' : value.toISOString());
|
|
452
|
+
}
|
|
453
|
+
const object = value;
|
|
454
|
+
if (ancestors.has(object))
|
|
455
|
+
throw new Unencodable(path, 'a cyclic reference');
|
|
456
|
+
ancestors.add(object);
|
|
457
|
+
try {
|
|
458
|
+
if (Array.isArray(object)) {
|
|
459
|
+
// A hole reads as `undefined`, as the code view prints it.
|
|
460
|
+
return Array.from(object, (item, index) => encode(item, child(path, index), depth + 1, ancestors));
|
|
461
|
+
}
|
|
462
|
+
if (!isPlainObject(object)) {
|
|
463
|
+
const name = object.constructor?.name;
|
|
464
|
+
throw new Unencodable(path, typeof name === 'string' && name ? `an instance of ${name}` : 'not a plain object');
|
|
465
|
+
}
|
|
466
|
+
if (Object.getOwnPropertySymbols(object).length) {
|
|
467
|
+
throw new Unencodable(path, 'an object with symbol keys');
|
|
468
|
+
}
|
|
469
|
+
const entries = [];
|
|
470
|
+
for (const key of Object.keys(object)) {
|
|
471
|
+
const descriptor = Object.getOwnPropertyDescriptor(object, key);
|
|
472
|
+
if (descriptor && !('value' in descriptor)) {
|
|
473
|
+
throw new Unencodable(child(path, key), 'a getter');
|
|
474
|
+
}
|
|
475
|
+
entries.push([key, encode(descriptor?.value, child(path, key), depth + 1, ancestors)]);
|
|
476
|
+
}
|
|
477
|
+
const encoded = Object.fromEntries(entries);
|
|
478
|
+
// An object of the user's that has the tag's key is wrapped, so it never reads as a tag.
|
|
479
|
+
return Object.prototype.hasOwnProperty.call(object, TAG) ? tagged('object', encoded) : encoded;
|
|
480
|
+
}
|
|
481
|
+
finally {
|
|
482
|
+
ancestors.delete(object);
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
/**
|
|
486
|
+
*
|
|
487
|
+
* @param value
|
|
488
|
+
* @param path
|
|
489
|
+
* @param depth
|
|
490
|
+
*/
|
|
491
|
+
function decode(value, path, depth) {
|
|
492
|
+
if (value === null || typeof value === 'string' || typeof value === 'boolean')
|
|
493
|
+
return value;
|
|
494
|
+
if (typeof value === 'number') {
|
|
495
|
+
if (!Number.isFinite(value))
|
|
496
|
+
throw new Error(`${path || 'value'}: not a finite number`);
|
|
497
|
+
return value;
|
|
498
|
+
}
|
|
499
|
+
if (depth >= MAX_DEPTH)
|
|
500
|
+
throw new Error(`${path || 'value'}: nested too deep`);
|
|
501
|
+
if (Array.isArray(value)) {
|
|
502
|
+
return value.map((item, index) => decode(item, child(path, index), depth + 1));
|
|
503
|
+
}
|
|
504
|
+
if (!isPlainObject(value))
|
|
505
|
+
throw new Error(`${path || 'value'}: not an encoded value`);
|
|
506
|
+
if (Object.prototype.hasOwnProperty.call(value, TAG)) {
|
|
507
|
+
const keys = Object.keys(value).sort().join(',');
|
|
508
|
+
const tag = value[TAG];
|
|
509
|
+
const content = value['value'];
|
|
510
|
+
if (tag === 'undefined' && keys === TAG)
|
|
511
|
+
return undefined;
|
|
512
|
+
if (tag === 'number' && keys === `${TAG},value`) {
|
|
513
|
+
if (content === 'NaN')
|
|
514
|
+
return Number.NaN;
|
|
515
|
+
if (content === 'Infinity')
|
|
516
|
+
return Number.POSITIVE_INFINITY;
|
|
517
|
+
if (content === '-Infinity')
|
|
518
|
+
return Number.NEGATIVE_INFINITY;
|
|
519
|
+
if (content === '-0')
|
|
520
|
+
return -0;
|
|
521
|
+
}
|
|
522
|
+
if (tag === 'date' && keys === `${TAG},value` && typeof content === 'string') {
|
|
523
|
+
const date = new Date(content === 'Invalid Date' ? Number.NaN : content);
|
|
524
|
+
if (content === 'Invalid Date' || !Number.isNaN(date.getTime()))
|
|
525
|
+
return date;
|
|
526
|
+
}
|
|
527
|
+
if (tag === 'object' && keys === `${TAG},value` && isPlainObject(content)) {
|
|
528
|
+
return decodeObject(content, path, depth);
|
|
529
|
+
}
|
|
530
|
+
throw new Error(`${path || 'value'}: not an encoded value`);
|
|
531
|
+
}
|
|
532
|
+
return decodeObject(value, path, depth);
|
|
533
|
+
}
|
|
534
|
+
/**
|
|
535
|
+
*
|
|
536
|
+
* @param value
|
|
537
|
+
* @param path
|
|
538
|
+
* @param depth
|
|
539
|
+
*/
|
|
540
|
+
function decodeObject(value, path, depth) {
|
|
541
|
+
return Object.fromEntries(Object.keys(value).map((key) => [key, decode(value[key], child(path, key), depth + 1)]));
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
/** Fragment parameters of the playground itself; an input name never starts with `~`. */
|
|
545
|
+
const INPUTS = '~inputs';
|
|
546
|
+
const CONTENT = '~content';
|
|
547
|
+
const RECREATE = '~recreate';
|
|
548
|
+
const VALUES = '~v';
|
|
549
|
+
const DATA = '~data';
|
|
550
|
+
/** The text JSON gives a number, which a readable string value must not look like. */
|
|
551
|
+
const NUMBER = /^-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?$/;
|
|
552
|
+
/**
|
|
553
|
+
* Writes a state as a URL fragment (without `#`). A string, a finite number or a boolean is its
|
|
554
|
+
* own readable parameter (`label=Save&size=2&disabled=true`; a string that would read as another
|
|
555
|
+
* type is quoted as JSON). Every other value goes into one `~v` parameter, encoded by
|
|
556
|
+
* {@link ɵencodePlaygroundValue}. The frame reads the fragment with
|
|
557
|
+
* {@link ɵplaygroundStateFromFragment}, and the same fragment opens the page in a new tab.
|
|
558
|
+
* @param state - The state of the playground.
|
|
559
|
+
* @internal
|
|
560
|
+
*/
|
|
561
|
+
function ɵplaygroundStateToFragment(state) {
|
|
562
|
+
const parameters = [`${INPUTS}=${state.inputs.map(encodeURIComponent).join(',')}`];
|
|
563
|
+
const encoded = {};
|
|
564
|
+
for (const [name, value] of Object.entries(state.properties)) {
|
|
565
|
+
const readable = readableText(value);
|
|
566
|
+
if (readable !== undefined) {
|
|
567
|
+
parameters.push(`${encodeURIComponent(name)}=${encodeURIComponent(readable)}`);
|
|
568
|
+
continue;
|
|
569
|
+
}
|
|
570
|
+
const result = ɵencodePlaygroundValue(value);
|
|
571
|
+
if (!result.ok) {
|
|
572
|
+
return {
|
|
573
|
+
ok: false,
|
|
574
|
+
path: result.path ? `${name}.${result.path}` : name,
|
|
575
|
+
reason: result.reason,
|
|
576
|
+
};
|
|
577
|
+
}
|
|
578
|
+
encoded[name] = result.value;
|
|
579
|
+
}
|
|
580
|
+
if (Object.keys(encoded).length) {
|
|
581
|
+
const text = ɵplaygroundStateToUrl(encoded);
|
|
582
|
+
if (text === undefined)
|
|
583
|
+
return tooLong();
|
|
584
|
+
parameters.push(`${VALUES}=${text}`);
|
|
585
|
+
}
|
|
586
|
+
if (state.content.length) {
|
|
587
|
+
parameters.push(`${CONTENT}=${state.content.map(encodeURIComponent).join(',')}`);
|
|
588
|
+
}
|
|
589
|
+
if (state.recreate)
|
|
590
|
+
parameters.push(`${RECREATE}=1`);
|
|
591
|
+
if (state.data && Object.keys(state.data).length) {
|
|
592
|
+
const data = {};
|
|
593
|
+
for (const [name, value] of Object.entries(state.data)) {
|
|
594
|
+
const result = ɵencodePlaygroundValue(value);
|
|
595
|
+
if (!result.ok) {
|
|
596
|
+
return {
|
|
597
|
+
ok: false,
|
|
598
|
+
path: ['data', name, result.path].filter(Boolean).join('.'),
|
|
599
|
+
reason: result.reason,
|
|
600
|
+
};
|
|
601
|
+
}
|
|
602
|
+
data[name] = result.value;
|
|
603
|
+
}
|
|
604
|
+
const text = ɵplaygroundStateToUrl(data);
|
|
605
|
+
if (text === undefined)
|
|
606
|
+
return tooLong();
|
|
607
|
+
parameters.push(`${DATA}=${text}`);
|
|
608
|
+
}
|
|
609
|
+
const fragment = parameters.join('&');
|
|
610
|
+
return fragment.length > ɵNG_DOC_PLAYGROUND_STATE_URL_LIMIT ? tooLong() : { ok: true, fragment };
|
|
611
|
+
}
|
|
612
|
+
/**
|
|
613
|
+
* Reads a state that {@link ɵplaygroundStateToFragment} wrote. Throws on a fragment it can't
|
|
614
|
+
* have written, so a malformed URL never reaches the target half-read.
|
|
615
|
+
* @param fragment - The fragment, with or without the leading `#`.
|
|
616
|
+
* @internal
|
|
617
|
+
*/
|
|
618
|
+
function ɵplaygroundStateFromFragment(fragment) {
|
|
619
|
+
const text = fragment.replace(/^#/, '');
|
|
620
|
+
const properties = {};
|
|
621
|
+
let inputs;
|
|
622
|
+
let content = [];
|
|
623
|
+
let recreate = false;
|
|
624
|
+
let data;
|
|
625
|
+
if (text.length > ɵNG_DOC_PLAYGROUND_STATE_URL_LIMIT)
|
|
626
|
+
throw new Error('The state is too long.');
|
|
627
|
+
for (const parameter of text ? text.split('&') : []) {
|
|
628
|
+
const separator = parameter.indexOf('=');
|
|
629
|
+
if (separator < 1)
|
|
630
|
+
throw new Error(`Malformed parameter: ${parameter}`);
|
|
631
|
+
const name = decodeURIComponent(parameter.slice(0, separator));
|
|
632
|
+
const value = parameter.slice(separator + 1);
|
|
633
|
+
if (name === INPUTS)
|
|
634
|
+
inputs = list(value);
|
|
635
|
+
else if (name === CONTENT)
|
|
636
|
+
content = list(value);
|
|
637
|
+
else if (name === RECREATE)
|
|
638
|
+
recreate = value === '1';
|
|
639
|
+
else if (name === VALUES) {
|
|
640
|
+
const values = ɵplaygroundStateFromUrl(value);
|
|
641
|
+
if (!values)
|
|
642
|
+
throw new Error('Malformed encoded values.');
|
|
643
|
+
for (const [key, item] of Object.entries(values))
|
|
644
|
+
properties[key] = ɵdecodePlaygroundValue(item);
|
|
645
|
+
}
|
|
646
|
+
else if (name === DATA) {
|
|
647
|
+
const values = ɵplaygroundStateFromUrl(value);
|
|
648
|
+
if (!values)
|
|
649
|
+
throw new Error('Malformed data.');
|
|
650
|
+
data = {};
|
|
651
|
+
for (const [key, item] of Object.entries(values))
|
|
652
|
+
data[key] = ɵdecodePlaygroundValue(item);
|
|
653
|
+
}
|
|
654
|
+
else if (ɵisDemoThemeParameter(name)) {
|
|
655
|
+
// The demo application applies the theme; it is not part of the playground's state.
|
|
656
|
+
}
|
|
657
|
+
else if (name.startsWith('~')) {
|
|
658
|
+
throw new Error(`Unknown parameter: ${name}`);
|
|
659
|
+
}
|
|
660
|
+
else {
|
|
661
|
+
properties[name] = readValue(decodeURIComponent(value));
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
if (!inputs)
|
|
665
|
+
throw new Error('The state names no inputs.');
|
|
666
|
+
return { inputs, properties, content, recreate, ...(data ? { data } : {}) };
|
|
667
|
+
}
|
|
668
|
+
/**
|
|
669
|
+
*
|
|
670
|
+
*/
|
|
671
|
+
function tooLong() {
|
|
672
|
+
return { ok: false, path: '', reason: 'the values are too long for the URL of the frame' };
|
|
673
|
+
}
|
|
674
|
+
/**
|
|
675
|
+
*
|
|
676
|
+
* @param value
|
|
677
|
+
*/
|
|
678
|
+
function list(value) {
|
|
679
|
+
return value ? value.split(',').map(decodeURIComponent) : [];
|
|
680
|
+
}
|
|
681
|
+
/**
|
|
682
|
+
* The readable text of a value, or `undefined` when it goes into the encoded parameter.
|
|
683
|
+
* @param value
|
|
684
|
+
*/
|
|
685
|
+
function readableText(value) {
|
|
686
|
+
if (typeof value === 'boolean')
|
|
687
|
+
return String(value);
|
|
688
|
+
if (typeof value === 'number') {
|
|
689
|
+
return Number.isFinite(value) && !Object.is(value, -0) ? JSON.stringify(value) : undefined;
|
|
690
|
+
}
|
|
691
|
+
if (typeof value !== 'string')
|
|
692
|
+
return undefined;
|
|
693
|
+
return value === 'true' || value === 'false' || NUMBER.test(value) || value.startsWith('"')
|
|
694
|
+
? JSON.stringify(value)
|
|
695
|
+
: value;
|
|
696
|
+
}
|
|
697
|
+
/**
|
|
698
|
+
*
|
|
699
|
+
* @param text
|
|
700
|
+
*/
|
|
701
|
+
function readValue(text) {
|
|
702
|
+
if (text === 'true')
|
|
703
|
+
return true;
|
|
704
|
+
if (text === 'false')
|
|
705
|
+
return false;
|
|
706
|
+
if (NUMBER.test(text))
|
|
707
|
+
return Number(text);
|
|
708
|
+
if (text.startsWith('"')) {
|
|
709
|
+
const parsed = JSON.parse(text);
|
|
710
|
+
if (typeof parsed !== 'string')
|
|
711
|
+
throw new Error(`Malformed string: ${text}`);
|
|
712
|
+
return parsed;
|
|
713
|
+
}
|
|
714
|
+
return text;
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
/**
|
|
718
|
+
*
|
|
719
|
+
* @param value
|
|
720
|
+
* @param type
|
|
721
|
+
*/
|
|
722
|
+
function header(value, type) {
|
|
723
|
+
const message = value;
|
|
724
|
+
return (typeof message === 'object' &&
|
|
725
|
+
message !== null &&
|
|
726
|
+
message['source'] === ɵNG_DOC_DEMO_MESSAGE_SOURCE &&
|
|
727
|
+
message['version'] === 1 &&
|
|
728
|
+
message['type'] === type);
|
|
729
|
+
}
|
|
730
|
+
/**
|
|
731
|
+
*
|
|
732
|
+
* @param value
|
|
733
|
+
*/
|
|
734
|
+
function isRecord(value) {
|
|
735
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
736
|
+
}
|
|
737
|
+
/**
|
|
738
|
+
* Whether every value of a record decodes: a malformed value is a malformed message.
|
|
739
|
+
* @param value
|
|
740
|
+
*/
|
|
741
|
+
function isEncodedRecord(value) {
|
|
742
|
+
if (!isRecord(value))
|
|
743
|
+
return false;
|
|
744
|
+
try {
|
|
745
|
+
for (const item of Object.values(value))
|
|
746
|
+
ɵdecodePlaygroundValue(item);
|
|
747
|
+
return true;
|
|
748
|
+
}
|
|
749
|
+
catch {
|
|
750
|
+
return false;
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
/**
|
|
754
|
+
* Whether a message comes from the frame of an isolated playground, and is one it sends.
|
|
755
|
+
* @param value - The data of a `message` event.
|
|
756
|
+
* @internal
|
|
757
|
+
*/
|
|
758
|
+
function ɵisNgDocPlaygroundFrameMessage(value) {
|
|
759
|
+
if (header(value, 'playground-ready')) {
|
|
760
|
+
const uncloneable = value['uncloneable'];
|
|
761
|
+
return (isEncodedRecord(value['defaults']) &&
|
|
762
|
+
Array.isArray(uncloneable) &&
|
|
763
|
+
uncloneable.every((name) => typeof name === 'string'));
|
|
764
|
+
}
|
|
765
|
+
if (header(value, 'playground-applied'))
|
|
766
|
+
return typeof value['fragment'] === 'string';
|
|
767
|
+
if (header(value, 'playground-error')) {
|
|
768
|
+
return ((value['phase'] === 'create' || value['phase'] === 'apply') &&
|
|
769
|
+
typeof value['message'] === 'string');
|
|
770
|
+
}
|
|
771
|
+
return false;
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
/**
|
|
775
|
+
* Generated bundle index. Do not edit.
|
|
776
|
+
*/
|
|
777
|
+
|
|
778
|
+
export { NgDocDemoAppComponent, NgDocDemoHostComponent, ɵNG_DOC_DEMO_MESSAGE_SOURCE, ɵNG_DOC_PLAYGROUND_STATE_URL_LIMIT, ɵdecodePlaygroundValue, ɵdemoThemeFromFragment, ɵencodePlaygroundValue, ɵisDemoThemeParameter, ɵisNgDocDemoBackgroundMessage, ɵisNgDocDemoSizeMessage, ɵisNgDocPlaygroundFrameMessage, ɵngDocDemoApplicationConfig, ɵplaygroundStateFromFragment, ɵplaygroundStateFromUrl, ɵplaygroundStateToFragment, ɵplaygroundStateToUrl, ɵwithDemoTheme };
|
|
779
|
+
//# sourceMappingURL=ng-doc-app-demo-app.mjs.map
|