@ng-doc/app 22.1.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 +195 -12
- package/fesm2022/ng-doc-app-components-demo-frame.mjs.map +1 -1
- 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 +49 -12
- 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 +743 -108
- 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-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-constants.mjs +11 -1
- package/fesm2022/ng-doc-app-constants.mjs.map +1 -1
- package/fesm2022/ng-doc-app-demo-app.mjs +618 -15
- package/fesm2022/ng-doc-app-demo-app.mjs.map +1 -1
- package/fesm2022/ng-doc-app-helpers.mjs +125 -6
- package/fesm2022/ng-doc-app-helpers.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.mjs +1 -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 +8 -5
- package/fesm2022/ng-doc-app-services-highlighter.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 +5 -3
- 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 +59 -3
- package/styles/global.css +1 -0
- package/styles/main.css +1 -0
- package/types/ng-doc-app-classes-default-search-engine.d.ts +11 -0
- package/types/ng-doc-app-classes-root-page.d.ts +5 -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 +62 -3
- package/types/ng-doc-app-components-demo.d.ts +27 -8
- 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 +241 -44
- 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-theme-toggle.d.ts +4 -1
- package/types/ng-doc-app-components-toc.d.ts +2 -1
- package/types/ng-doc-app-constants.d.ts +11 -1
- package/types/ng-doc-app-demo-app.d.ts +211 -3
- package/types/ng-doc-app-helpers.d.ts +56 -3
- package/types/ng-doc-app-interfaces.d.ts +621 -3
- package/types/ng-doc-app-providers-ng-doc-app.d.ts +6 -0
- package/types/ng-doc-app-providers-texts.d.ts +28 -0
- package/types/ng-doc-app-providers.d.ts +1 -0
- package/types/ng-doc-app-services-content-scroll-intent.d.ts +11 -1
- package/types/ng-doc-app-services-highlighter.d.ts +3 -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 +2 -1
- package/types/ng-doc-app-type-controls.d.ts +2 -0
|
@@ -1,29 +1,124 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { input, viewChild, ViewContainerRef, signal,
|
|
2
|
+
import { InjectionToken, input, inject, ChangeDetectorRef, Directive, output, model, viewChild, computed, ViewContainerRef, signal, Injector, DestroyRef, PendingTasks, ChangeDetectionStrategy, Component, effect, untracked, reflectComponentType, isDevMode, linkedSignal, PLATFORM_ID, DOCUMENT, afterNextRender } from '@angular/core';
|
|
3
3
|
import { extractFunctionDefaults } from '@ng-doc/core/helpers/extract-function-defaults';
|
|
4
4
|
import { Subject, take } from 'rxjs';
|
|
5
|
+
import { NgTemplateOutlet, KeyValuePipe, PlatformLocation, APP_BASE_HREF, isPlatformBrowser } from '@angular/common';
|
|
6
|
+
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
|
5
7
|
import * as i1 from '@angular/forms';
|
|
6
8
|
import { FormsModule, FormBuilder } from '@angular/forms';
|
|
7
9
|
import { NgDocRootPage } from '@ng-doc/app/classes/root-page';
|
|
10
|
+
import { ɵwithDemoTheme as _withDemoTheme, ɵplaygroundStateToFragment as _playgroundStateToFragment, ɵdecodePlaygroundValue as _decodePlaygroundValue, ɵencodePlaygroundValue as _encodePlaygroundValue, ɵplaygroundStateFromFragment as _playgroundStateFromFragment, ɵNG_DOC_DEMO_MESSAGE_SOURCE as _NG_DOC_DEMO_MESSAGE_SOURCE } from '@ng-doc/app/demo-app';
|
|
11
|
+
import { getTokenForType } from '@ng-doc/app/providers/type-control';
|
|
12
|
+
import { NG_DOC_TEXTS } from '@ng-doc/app/tokens';
|
|
8
13
|
import { isSameObject } from '@ng-doc/core/helpers/is-same-object';
|
|
9
14
|
import { objectKeys } from '@ng-doc/core/helpers/object-keys';
|
|
15
|
+
import { ɵplaygroundTargetSlugs as _playgroundTargetSlugs } from '@ng-doc/core/helpers/playground-target-slugs';
|
|
10
16
|
import { NgDocLabelComponent, NgDocTooltipDirective, NgDocCheckboxComponent, NgDocIconComponent, NgDocBindPipe, NgDocExecutePipe, NgDocAsArrayPipe } from '@ng-doc/ui-kit';
|
|
11
|
-
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
|
12
17
|
import { NgDocDemoDisplayerComponent } from '@ng-doc/app/components/demo-displayer';
|
|
18
|
+
import { NgDocDemoFrameComponent, NgDocDemoThemeButtonComponent } from '@ng-doc/app/components/demo-frame';
|
|
13
19
|
import { formatHtml, isPlaygroundProperty, resolvePlaygroundOption } from '@ng-doc/app/helpers';
|
|
14
20
|
import { getPlaygroundDemoToken } from '@ng-doc/app/providers/playground-demo';
|
|
15
21
|
import { buildPlaygroundDemoPipeTemplate, buildPlaygroundDemoTemplate } from '@ng-doc/core/helpers/build-playground-demo-template';
|
|
16
22
|
import { stringify } from '@ng-doc/core/helpers/stringify';
|
|
17
23
|
import { startWith, takeUntil } from 'rxjs/operators';
|
|
18
|
-
import { NgTemplateOutlet, KeyValuePipe } from '@angular/common';
|
|
19
|
-
import { getTokenForType } from '@ng-doc/app/providers/type-control';
|
|
20
24
|
import { isPresent } from '@ng-doc/core/helpers/is-present';
|
|
21
25
|
import { NgDocKindIconComponent } from '@ng-doc/app/components/kind-icon';
|
|
22
26
|
import { NgDocSanitizeHtmlPipe } from '@ng-doc/app/pipes';
|
|
27
|
+
import { ActivatedRoute } from '@angular/router';
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* The playground that shows a generated playground class.
|
|
31
|
+
* @internal
|
|
32
|
+
*/
|
|
33
|
+
const ɵNG_DOC_PLAYGROUND_CONTAINER = new InjectionToken('NG_DOC_PLAYGROUND_CONTAINER');
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Base class of the playground classes the builder generates, one per playground and selector.
|
|
37
|
+
* A generated class renders the playground template with the target's inputs bound to
|
|
38
|
+
* `properties()`, and queries the target with `viewChild()`.
|
|
39
|
+
*/
|
|
40
|
+
class NgDocBasePlayground {
|
|
41
|
+
static { this.selector = 'unknown'; }
|
|
42
|
+
constructor() {
|
|
43
|
+
/** Values of the target's inputs, by property name. */
|
|
44
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
45
|
+
this.properties = input({}, /* @ts-ignore */
|
|
46
|
+
...(ngDevMode ? [{ debugName: "properties" }] : /* istanbul ignore next */ []));
|
|
47
|
+
/** The `data` given to the playground action; it extends the configuration's `data`. */
|
|
48
|
+
this.actionData = input({}, /* @ts-ignore */
|
|
49
|
+
...(ngDevMode ? [{ debugName: "actionData" }] : /* istanbul ignore next */ []));
|
|
50
|
+
/** Whether each content slot is shown, by slot name. */
|
|
51
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
52
|
+
this.content = input({}, /* @ts-ignore */
|
|
53
|
+
...(ngDevMode ? [{ debugName: "content" }] : /* istanbul ignore next */ []));
|
|
54
|
+
/** The default values of the target's inputs, read before the template binds them. */
|
|
55
|
+
this.defaultValues = {};
|
|
56
|
+
this.reattached = new Subject();
|
|
57
|
+
this.playgroundContainer = inject(ɵNG_DOC_PLAYGROUND_CONTAINER);
|
|
58
|
+
this.changeDetectorRef = inject(ChangeDetectorRef);
|
|
59
|
+
// The view is not checked until the default values of the target are read, so the
|
|
60
|
+
// template's bindings cannot overwrite them first.
|
|
61
|
+
this.changeDetectorRef.detach();
|
|
62
|
+
}
|
|
63
|
+
/** Emits once, after the view is attached again and checks the template's bindings. */
|
|
64
|
+
get onReattached() {
|
|
65
|
+
return this.reattached.pipe(take(1));
|
|
66
|
+
}
|
|
67
|
+
/** The playground `data`: the configuration's data extended by the action's data. */
|
|
68
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
69
|
+
get data() {
|
|
70
|
+
return Object.assign({}, this.configData, this.actionData());
|
|
71
|
+
}
|
|
72
|
+
ngOnInit() {
|
|
73
|
+
// The target is created with the view, but the view has not been checked yet, so its inputs
|
|
74
|
+
// still hold their defaults. Signal inputs are read by calling them.
|
|
75
|
+
const playground = this.playground();
|
|
76
|
+
if (playground) {
|
|
77
|
+
this.defaultValues = Object.keys(playground).reduce((values, key) => {
|
|
78
|
+
try {
|
|
79
|
+
const value = playground[key];
|
|
80
|
+
values[key] = typeof value === 'function' ? value.call(playground) : value;
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
// Some properties are getters or functions that throw without their context.
|
|
84
|
+
}
|
|
85
|
+
return values;
|
|
86
|
+
}, {});
|
|
87
|
+
}
|
|
88
|
+
else if (this.target) {
|
|
89
|
+
// A pipe: the defaults of its `transform` parameters after the value itself.
|
|
90
|
+
const defaults = extractFunctionDefaults(this.target.prototype.transform);
|
|
91
|
+
this.defaultValues = Object.keys(this.playgroundContainer.properties() ?? {}).reduce((values, key, i) => {
|
|
92
|
+
values[key] = defaults[i + 1];
|
|
93
|
+
return values;
|
|
94
|
+
}, {});
|
|
95
|
+
}
|
|
96
|
+
else {
|
|
97
|
+
throw new Error('Playground is not defined or initialized');
|
|
98
|
+
}
|
|
99
|
+
if (!this.playgroundContainer.defaultValues()) {
|
|
100
|
+
this.playgroundContainer.defaultValues.set(this.defaultValues);
|
|
101
|
+
}
|
|
102
|
+
// The demo sets the playground's inputs right after it creates this view; the view is
|
|
103
|
+
// attached again once they are set, so its first check renders the playground's values.
|
|
104
|
+
Promise.resolve().then(() => {
|
|
105
|
+
this.changeDetectorRef.reattach();
|
|
106
|
+
this.changeDetectorRef.markForCheck();
|
|
107
|
+
this.reattached.next();
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.2.1", ngImport: i0, type: NgDocBasePlayground, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
111
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.2.1", type: NgDocBasePlayground, isStandalone: true, inputs: { properties: { classPropertyName: "properties", publicName: "properties", isSignal: true, isRequired: false, transformFunction: null }, actionData: { classPropertyName: "actionData", publicName: "actionData", isSignal: true, isRequired: false, transformFunction: null }, content: { classPropertyName: "content", publicName: "content", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0 }); }
|
|
112
|
+
}
|
|
113
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.2.1", ngImport: i0, type: NgDocBasePlayground, decorators: [{
|
|
114
|
+
type: Directive
|
|
115
|
+
}], ctorParameters: () => [], propDecorators: { properties: [{ type: i0.Input, args: [{ isSignal: true, alias: "properties", required: false }] }], actionData: [{ type: i0.Input, args: [{ isSignal: true, alias: "actionData", required: false }] }], content: [{ type: i0.Input, args: [{ isSignal: true, alias: "content", required: false }] }] } });
|
|
23
116
|
|
|
24
117
|
/**
|
|
25
118
|
* One demo of a playground: it creates the generated playground class of its selector (or pipe),
|
|
26
|
-
* passes it the form's values and shows the code of the current state.
|
|
119
|
+
* passes it the form's values and shows the code of the current state. In an isolated playground
|
|
120
|
+
* the class runs on a page of the demo application in an iframe, which gets the values from its
|
|
121
|
+
* URL fragment, and reports the target's defaults back for the code view.
|
|
27
122
|
*/
|
|
28
123
|
class NgDocPlaygroundDemoComponent {
|
|
29
124
|
constructor() {
|
|
@@ -51,6 +146,42 @@ class NgDocPlaygroundDemoComponent {
|
|
|
51
146
|
/** Whether the code of the demo is shown. */
|
|
52
147
|
this.expanded = input(false, /* @ts-ignore */
|
|
53
148
|
...(ngDevMode ? [{ debugName: "expanded" }] : /* istanbul ignore next */ []));
|
|
149
|
+
/** The iframe of an isolated playground; without it, the class is created in the page. */
|
|
150
|
+
this.frame = input(undefined, /* @ts-ignore */
|
|
151
|
+
...(ngDevMode ? [{ debugName: "frame" }] : /* istanbul ignore next */ []));
|
|
152
|
+
/** The defaults of the target's inputs that the iframe reported, for the code view. */
|
|
153
|
+
this.frameDefaults = input(undefined, /* @ts-ignore */
|
|
154
|
+
...(ngDevMode ? [{ debugName: "frameDefaults" }] : /* istanbul ignore next */ []));
|
|
155
|
+
/** The messages of the iframe of an isolated playground. */
|
|
156
|
+
this.frameMessage = output();
|
|
157
|
+
/** The iframe's page loaded but did not report the target in time. */
|
|
158
|
+
this.frameSilent = output();
|
|
159
|
+
/** The theme of the iframe, which the theme button beside the demo changes. */
|
|
160
|
+
this.frameTheme = model(undefined, /* @ts-ignore */
|
|
161
|
+
...(ngDevMode ? [{ debugName: "frameTheme" }] : /* istanbul ignore next */ []));
|
|
162
|
+
/** The `theme` option of the playground, which the theme button offers too. */
|
|
163
|
+
this.configuredTheme = input(undefined, /* @ts-ignore */
|
|
164
|
+
...(ngDevMode ? [{ debugName: "configuredTheme" }] : /* istanbul ignore next */ []));
|
|
165
|
+
/** The iframe of an isolated playground, while it is shown. */
|
|
166
|
+
this.frameComponent = viewChild(NgDocDemoFrameComponent, /* @ts-ignore */
|
|
167
|
+
...(ngDevMode ? [{ debugName: "frameComponent" }] : /* istanbul ignore next */ []));
|
|
168
|
+
/**
|
|
169
|
+
* The background of the canvas while the iframe shows a theme of its own: the background its
|
|
170
|
+
* page reported, so the whole canvas reads in that theme.
|
|
171
|
+
*/
|
|
172
|
+
this.canvasBackground = computed(() => this.frame() && this.frameTheme() !== undefined
|
|
173
|
+
? this.frameComponent()?.background()
|
|
174
|
+
: undefined, /* @ts-ignore */
|
|
175
|
+
...(ngDevMode ? [{ debugName: "canvasBackground" }] : /* istanbul ignore next */ []));
|
|
176
|
+
/** The dots of the canvas while the iframe shows a theme of its own, in that theme's color. */
|
|
177
|
+
this.canvasDots = computed(() => this.canvasBackground() ? this.frameComponent()?.dots() : undefined, /* @ts-ignore */
|
|
178
|
+
...(ngDevMode ? [{ debugName: "canvasDots" }] : /* istanbul ignore next */ []));
|
|
179
|
+
/** The iframe's page with the current state, which the toolbar opens in a new tab. */
|
|
180
|
+
this.frameUrl = computed(() => {
|
|
181
|
+
const frame = this.frame();
|
|
182
|
+
return frame ? `${frame.url}#${frame.fragment}` : undefined;
|
|
183
|
+
}, /* @ts-ignore */
|
|
184
|
+
...(ngDevMode ? [{ debugName: "frameUrl" }] : /* istanbul ignore next */ []));
|
|
54
185
|
/** Where the playground class is created. */
|
|
55
186
|
this.demoOutlet = viewChild('demoOutlet', { ...(ngDevMode ? { debugName: "demoOutlet" } : /* istanbul ignore next */ {}), read: ViewContainerRef });
|
|
56
187
|
this.code = signal('', /* @ts-ignore */
|
|
@@ -60,7 +191,11 @@ class NgDocPlaygroundDemoComponent {
|
|
|
60
191
|
this.pendingTasks = inject(PendingTasks);
|
|
61
192
|
this.unsubscribe$ = new Subject();
|
|
62
193
|
}
|
|
63
|
-
async ngOnChanges({ form, id }) {
|
|
194
|
+
async ngOnChanges({ form, id, frame }) {
|
|
195
|
+
// A playground that falls back from its iframe to the page creates its class in the page.
|
|
196
|
+
if (frame && !frame.firstChange && !this.frame()) {
|
|
197
|
+
await this.updateDemo(this.form()?.value);
|
|
198
|
+
}
|
|
64
199
|
if (form || id) {
|
|
65
200
|
this.unsubscribe$.next();
|
|
66
201
|
const demoInjector = getPlaygroundDemoToken(this.id());
|
|
@@ -68,14 +203,25 @@ class NgDocPlaygroundDemoComponent {
|
|
|
68
203
|
const demos = this.injector.get(demoInjector, []);
|
|
69
204
|
this.playgroundDemo = demos.find((demo) => demo.selector === this.selector() || demo.selector === this.pipeName());
|
|
70
205
|
}
|
|
71
|
-
await this.updateDemo();
|
|
72
206
|
const form = this.form();
|
|
73
|
-
form
|
|
207
|
+
if (!form) {
|
|
208
|
+
// No values yet: the class is created so that it reports the target's defaults.
|
|
209
|
+
await this.updateDemo();
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
// The form's values reach the class synchronously, in this check: the class may already be
|
|
213
|
+
// attached again, and checked with no values its target would get `undefined` for every
|
|
214
|
+
// input instead of its default.
|
|
215
|
+
form.valueChanges
|
|
74
216
|
.pipe(startWith(form.value), takeUntil(this.unsubscribe$), takeUntilDestroyed(this.destroyRef))
|
|
75
217
|
.subscribe((data) => this.updateDemo(data));
|
|
76
218
|
}
|
|
77
219
|
}
|
|
78
220
|
async updateDemo(data) {
|
|
221
|
+
if (this.frame()) {
|
|
222
|
+
await this.updateCodeView();
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
79
225
|
if (this.recreateDemo() || !this.demoRef) {
|
|
80
226
|
this.createDemo();
|
|
81
227
|
}
|
|
@@ -124,7 +270,7 @@ class NgDocPlaygroundDemoComponent {
|
|
|
124
270
|
return objectKeys(formData).reduce((result, key) => {
|
|
125
271
|
const inputName = this.properties()?.[key]?.inputName ?? key;
|
|
126
272
|
const value = formData[key];
|
|
127
|
-
const property = this.
|
|
273
|
+
const property = this.defaultValue(key);
|
|
128
274
|
if (property !== value) {
|
|
129
275
|
result[inputName] = stringify(value).replace(/"/g, `'`);
|
|
130
276
|
}
|
|
@@ -137,7 +283,7 @@ class NgDocPlaygroundDemoComponent {
|
|
|
137
283
|
return objectKeys(formData)
|
|
138
284
|
.map((key, i) => {
|
|
139
285
|
const value = formData[key];
|
|
140
|
-
const defaultValue = this.
|
|
286
|
+
const defaultValue = this.defaultValue(key);
|
|
141
287
|
if (defaultValue !== value) {
|
|
142
288
|
changedInputIndex = i;
|
|
143
289
|
}
|
|
@@ -153,16 +299,175 @@ class NgDocPlaygroundDemoComponent {
|
|
|
153
299
|
this.unsubscribe$.next();
|
|
154
300
|
this.unsubscribe$.complete();
|
|
155
301
|
}
|
|
302
|
+
/**
|
|
303
|
+
* The default of an input: from the class in the page, or as the iframe reported it.
|
|
304
|
+
* @param key
|
|
305
|
+
*/
|
|
306
|
+
defaultValue(key) {
|
|
307
|
+
return this.frame() ? this.frameDefaults()?.[key] : this.demoRef?.instance?.defaultValues[key];
|
|
308
|
+
}
|
|
156
309
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.2.1", ngImport: i0, type: NgDocPlaygroundDemoComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
157
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.
|
|
310
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.2.1", type: NgDocPlaygroundDemoComponent, isStandalone: true, selector: "ng-doc-playground-demo", inputs: { id: { classPropertyName: "id", publicName: "id", isSignal: true, isRequired: false, transformFunction: null }, pipeName: { classPropertyName: "pipeName", publicName: "pipeName", isSignal: true, isRequired: false, transformFunction: null }, selector: { classPropertyName: "selector", publicName: "selector", isSignal: true, isRequired: false, transformFunction: null }, configuration: { classPropertyName: "configuration", publicName: "configuration", isSignal: true, isRequired: false, transformFunction: null }, properties: { classPropertyName: "properties", publicName: "properties", isSignal: true, isRequired: false, transformFunction: null }, recreateDemo: { classPropertyName: "recreateDemo", publicName: "recreateDemo", isSignal: true, isRequired: false, transformFunction: null }, form: { classPropertyName: "form", publicName: "form", isSignal: true, isRequired: false, transformFunction: null }, expanded: { classPropertyName: "expanded", publicName: "expanded", isSignal: true, isRequired: false, transformFunction: null }, frame: { classPropertyName: "frame", publicName: "frame", isSignal: true, isRequired: false, transformFunction: null }, frameDefaults: { classPropertyName: "frameDefaults", publicName: "frameDefaults", isSignal: true, isRequired: false, transformFunction: null }, frameTheme: { classPropertyName: "frameTheme", publicName: "frameTheme", isSignal: true, isRequired: false, transformFunction: null }, configuredTheme: { classPropertyName: "configuredTheme", publicName: "configuredTheme", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { frameMessage: "frameMessage", frameSilent: "frameSilent", frameTheme: "frameThemeChange" }, viewQueries: [{ propertyName: "frameComponent", first: true, predicate: NgDocDemoFrameComponent, descendants: true, isSignal: true }, { propertyName: "demoOutlet", first: true, predicate: ["demoOutlet"], descendants: true, read: ViewContainerRef, isSignal: true }], usesOnChanges: true, ngImport: i0, template: "<ng-doc-demo-displayer\n [code]=\"code()\"\n [border]=\"false\"\n [expanded]=\"expanded()\"\n [newTabUrl]=\"frameUrl()\"\n [canvasBackground]=\"canvasBackground()\"\n [canvasDots]=\"canvasDots()\"\n language=\"angular-html\">\n @if (frame()) {\n <ng-doc-demo-theme-button\n ngDocDemoControl\n [(theme)]=\"frameTheme\"\n [configured]=\"configuredTheme()\" />\n }\n @if (frame(); as frame) {\n <ng-doc-demo-frame\n [src]=\"frame.url\"\n [fragment]=\"frame.fragment\"\n [playground]=\"true\"\n [frameTitle]=\"frame.title\"\n (playgroundMessage)=\"frameMessage.emit($event)\"\n (playgroundSilent)=\"frameSilent.emit()\" />\n }\n <!-- Static, so the class can be created before the first check (in ngOnChanges). An isolated\n playground leaves it empty. -->\n <ng-container #demoOutlet></ng-container>\n</ng-doc-demo-displayer>\n", styles: [":host{display:flex;flex-direction:column}ng-doc-demo-displayer{flex:1 1 auto}\n"], dependencies: [{ kind: "component", type: NgDocDemoDisplayerComponent, selector: "ng-doc-demo-displayer", inputs: ["codeContent", "code", "language", "container", "canvasBackground", "canvasDots", "border", "expanded", "newTabUrl"], outputs: ["expandedChange"] }, { kind: "component", type: NgDocDemoFrameComponent, selector: "ng-doc-demo-frame", inputs: ["src", "frameTitle", "fill", "fragment", "playground"], outputs: ["playgroundMessage", "playgroundSilent"] }, { kind: "component", type: NgDocDemoThemeButtonComponent, selector: "ng-doc-demo-theme-button", inputs: ["theme", "configured"], outputs: ["themeChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
158
311
|
}
|
|
159
312
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.2.1", ngImport: i0, type: NgDocPlaygroundDemoComponent, decorators: [{
|
|
160
313
|
type: Component,
|
|
161
|
-
args: [{ selector: 'ng-doc-playground-demo', changeDetection: ChangeDetectionStrategy.OnPush, imports: [NgDocDemoDisplayerComponent], template: "<ng-doc-demo-displayer\n [code]=\"code()\"\n [border]=\"false\"\n [expanded]=\"expanded()\"\n language=\"angular-html\">\n <ng-container #demoOutlet></ng-container>\n</ng-doc-demo-displayer>\n", styles: [":host{display:flex;flex-direction:column}ng-doc-demo-displayer{flex:1 1 auto}\n"] }]
|
|
162
|
-
}], propDecorators: { id: [{ type: i0.Input, args: [{ isSignal: true, alias: "id", required: false }] }], pipeName: [{ type: i0.Input, args: [{ isSignal: true, alias: "pipeName", required: false }] }], selector: [{ type: i0.Input, args: [{ isSignal: true, alias: "selector", required: false }] }], configuration: [{ type: i0.Input, args: [{ isSignal: true, alias: "configuration", required: false }] }], properties: [{ type: i0.Input, args: [{ isSignal: true, alias: "properties", required: false }] }], recreateDemo: [{ type: i0.Input, args: [{ isSignal: true, alias: "recreateDemo", required: false }] }], form: [{ type: i0.Input, args: [{ isSignal: true, alias: "form", required: false }] }], expanded: [{ type: i0.Input, args: [{ isSignal: true, alias: "expanded", required: false }] }], demoOutlet: [{ type: i0.ViewChild, args: ['demoOutlet', { ...{
|
|
314
|
+
args: [{ selector: 'ng-doc-playground-demo', changeDetection: ChangeDetectionStrategy.OnPush, imports: [NgDocDemoDisplayerComponent, NgDocDemoFrameComponent, NgDocDemoThemeButtonComponent], template: "<ng-doc-demo-displayer\n [code]=\"code()\"\n [border]=\"false\"\n [expanded]=\"expanded()\"\n [newTabUrl]=\"frameUrl()\"\n [canvasBackground]=\"canvasBackground()\"\n [canvasDots]=\"canvasDots()\"\n language=\"angular-html\">\n @if (frame()) {\n <ng-doc-demo-theme-button\n ngDocDemoControl\n [(theme)]=\"frameTheme\"\n [configured]=\"configuredTheme()\" />\n }\n @if (frame(); as frame) {\n <ng-doc-demo-frame\n [src]=\"frame.url\"\n [fragment]=\"frame.fragment\"\n [playground]=\"true\"\n [frameTitle]=\"frame.title\"\n (playgroundMessage)=\"frameMessage.emit($event)\"\n (playgroundSilent)=\"frameSilent.emit()\" />\n }\n <!-- Static, so the class can be created before the first check (in ngOnChanges). An isolated\n playground leaves it empty. -->\n <ng-container #demoOutlet></ng-container>\n</ng-doc-demo-displayer>\n", styles: [":host{display:flex;flex-direction:column}ng-doc-demo-displayer{flex:1 1 auto}\n"] }]
|
|
315
|
+
}], propDecorators: { id: [{ type: i0.Input, args: [{ isSignal: true, alias: "id", required: false }] }], pipeName: [{ type: i0.Input, args: [{ isSignal: true, alias: "pipeName", required: false }] }], selector: [{ type: i0.Input, args: [{ isSignal: true, alias: "selector", required: false }] }], configuration: [{ type: i0.Input, args: [{ isSignal: true, alias: "configuration", required: false }] }], properties: [{ type: i0.Input, args: [{ isSignal: true, alias: "properties", required: false }] }], recreateDemo: [{ type: i0.Input, args: [{ isSignal: true, alias: "recreateDemo", required: false }] }], form: [{ type: i0.Input, args: [{ isSignal: true, alias: "form", required: false }] }], expanded: [{ type: i0.Input, args: [{ isSignal: true, alias: "expanded", required: false }] }], frame: [{ type: i0.Input, args: [{ isSignal: true, alias: "frame", required: false }] }], frameDefaults: [{ type: i0.Input, args: [{ isSignal: true, alias: "frameDefaults", required: false }] }], frameMessage: [{ type: i0.Output, args: ["frameMessage"] }], frameSilent: [{ type: i0.Output, args: ["frameSilent"] }], frameTheme: [{ type: i0.Input, args: [{ isSignal: true, alias: "frameTheme", required: false }] }, { type: i0.Output, args: ["frameThemeChange"] }], configuredTheme: [{ type: i0.Input, args: [{ isSignal: true, alias: "configuredTheme", required: false }] }], frameComponent: [{ type: i0.ViewChild, args: [i0.forwardRef(() => NgDocDemoFrameComponent), { isSignal: true }] }], demoOutlet: [{ type: i0.ViewChild, args: ['demoOutlet', { ...{
|
|
163
316
|
read: ViewContainerRef,
|
|
164
317
|
}, isSignal: true }] }] } });
|
|
165
318
|
|
|
319
|
+
/**
|
|
320
|
+
* Reading an input type as an array type, which the inspector uses to choose the control of an
|
|
321
|
+
* array input and the property row to leave out the type chip of one. Shared inside the playground
|
|
322
|
+
* entry point only; not public API.
|
|
323
|
+
*/
|
|
324
|
+
/**
|
|
325
|
+
* The type that the control of array types without a control of their own is registered for.
|
|
326
|
+
* NgDoc registers its JSON field for it, and `controls` can name it for an input.
|
|
327
|
+
* @internal
|
|
328
|
+
*/
|
|
329
|
+
const ARRAY_CONTROL_TYPE = 'NgDocArray';
|
|
330
|
+
/**
|
|
331
|
+
* The type that the control of arrays of a union of literals (arrays whose `options` the builder
|
|
332
|
+
* lists) is registered for. NgDoc registers its multi-select for it, as it registers the select
|
|
333
|
+
* of a union for `NgDocTypeAlias`, and `controls` can name it for an input.
|
|
334
|
+
* @internal
|
|
335
|
+
*/
|
|
336
|
+
const UNION_ARRAY_CONTROL_TYPE = 'NgDocTypeAlias[]';
|
|
337
|
+
/** The item types whose arrays get the control registered for `<item>[]`, a chip field by default. */
|
|
338
|
+
const LIST_ITEM_TYPES = new Set(['string', 'number']);
|
|
339
|
+
/** Keywords that make a type ending in `[]` another type than an array, as in `keyof Item[]`. */
|
|
340
|
+
const TYPE_OPERATOR = /^(?:keyof|typeof|unique|infer|asserts|new)\s/;
|
|
341
|
+
/**
|
|
342
|
+
* Reads an input type as an array type: `T[]` (as both engines print an array), `Array<T>`,
|
|
343
|
+
* `readonly T[]`, `ReadonlyArray<T>` or a tuple such as `[string, number]`, alone or in a union
|
|
344
|
+
* with `undefined` or `null` (an optional or nullable input). An array of strings or numbers gets
|
|
345
|
+
* the control of `string[]` or `number[]`, so a control registered for that type replaces the
|
|
346
|
+
* built-in list for every spelling; any other array gets the control of `NgDocArray`, the JSON
|
|
347
|
+
* field. A type alias of an array (`type Tags = string[]`) is printed by its name and is not
|
|
348
|
+
* recognized: register a control for the name.
|
|
349
|
+
* @param type - The input type as the builder printed it.
|
|
350
|
+
* @returns What the array type is, or `undefined` when the type is not one.
|
|
351
|
+
* @internal
|
|
352
|
+
*/
|
|
353
|
+
function arrayType(type) {
|
|
354
|
+
const members = splitTopLevel(type);
|
|
355
|
+
const rest = members.filter((member) => member !== 'undefined' && member !== 'null');
|
|
356
|
+
if (rest.length !== 1) {
|
|
357
|
+
return undefined;
|
|
358
|
+
}
|
|
359
|
+
const written = unwrapParentheses(rest[0]).replace(/^readonly\s+/, '');
|
|
360
|
+
const item = arrayItem(written);
|
|
361
|
+
if (item !== undefined) {
|
|
362
|
+
return LIST_ITEM_TYPES.has(item) ? { list: `${item}[]` } : {};
|
|
363
|
+
}
|
|
364
|
+
return written.startsWith('[') && written.endsWith(']') && isBalanced(written.slice(1, -1))
|
|
365
|
+
? {}
|
|
366
|
+
: undefined;
|
|
367
|
+
}
|
|
368
|
+
/**
|
|
369
|
+
* The item type of `T[]`, `Array<T>` or `ReadonlyArray<T>`.
|
|
370
|
+
* @param type - A type that is not a union.
|
|
371
|
+
*/
|
|
372
|
+
function arrayItem(type) {
|
|
373
|
+
const generic = /^(?:Readonly)?Array<([\s\S]*)>$/.exec(type);
|
|
374
|
+
if (generic) {
|
|
375
|
+
return isBalanced(generic[1]) ? unwrapParentheses(generic[1].trim()) : undefined;
|
|
376
|
+
}
|
|
377
|
+
if (!type.endsWith('[]') || TYPE_OPERATOR.test(type)) {
|
|
378
|
+
return undefined;
|
|
379
|
+
}
|
|
380
|
+
const item = type.slice(0, -2).trim();
|
|
381
|
+
// `() => string[]` is a function, and `A extends B ? C[] : D[]` a conditional type.
|
|
382
|
+
const text = item ? topLevelText(item) : undefined;
|
|
383
|
+
return text !== undefined && !/=>|\?/.test(text) ? unwrapParentheses(item) : undefined;
|
|
384
|
+
}
|
|
385
|
+
/**
|
|
386
|
+
* Splits a type at the `|` of its top-level union, outside brackets and string literals.
|
|
387
|
+
* @param type - The type.
|
|
388
|
+
*/
|
|
389
|
+
function splitTopLevel(type) {
|
|
390
|
+
const text = topLevelText(type);
|
|
391
|
+
if (text === undefined) {
|
|
392
|
+
return [type.trim()];
|
|
393
|
+
}
|
|
394
|
+
const members = [];
|
|
395
|
+
let start = 0;
|
|
396
|
+
for (let index = 0; index < text.length; index++) {
|
|
397
|
+
if (text[index] === '|') {
|
|
398
|
+
members.push(type.slice(start, index).trim());
|
|
399
|
+
start = index + 1;
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
return [...members, type.slice(start).trim()];
|
|
403
|
+
}
|
|
404
|
+
/**
|
|
405
|
+
* Removes the parentheses around a whole type, such as those of `(string | number)`.
|
|
406
|
+
* @param type - The type.
|
|
407
|
+
*/
|
|
408
|
+
function unwrapParentheses(type) {
|
|
409
|
+
let result = type.trim();
|
|
410
|
+
while (result.startsWith('(') && result.endsWith(')') && isBalanced(result.slice(1, -1))) {
|
|
411
|
+
result = result.slice(1, -1).trim();
|
|
412
|
+
}
|
|
413
|
+
return result;
|
|
414
|
+
}
|
|
415
|
+
/**
|
|
416
|
+
* Whether every bracket and string literal of a type is closed in it.
|
|
417
|
+
* @param type - The type.
|
|
418
|
+
*/
|
|
419
|
+
function isBalanced(type) {
|
|
420
|
+
return topLevelText(type) !== undefined;
|
|
421
|
+
}
|
|
422
|
+
/**
|
|
423
|
+
* The type with every character inside brackets or a string literal replaced by a space, so that
|
|
424
|
+
* the operators of the type itself can be found by position; `undefined` when a bracket or a string
|
|
425
|
+
* is not closed. The `>` of an arrow (`=>`) closes no bracket.
|
|
426
|
+
* @param type - The type.
|
|
427
|
+
*/
|
|
428
|
+
function topLevelText(type) {
|
|
429
|
+
let depth = 0;
|
|
430
|
+
let quote;
|
|
431
|
+
let text = '';
|
|
432
|
+
for (let index = 0; index < type.length; index++) {
|
|
433
|
+
const char = type[index];
|
|
434
|
+
if (quote) {
|
|
435
|
+
if (char === '\\') {
|
|
436
|
+
index++;
|
|
437
|
+
text += ' ';
|
|
438
|
+
continue;
|
|
439
|
+
}
|
|
440
|
+
if (char === quote) {
|
|
441
|
+
quote = undefined;
|
|
442
|
+
}
|
|
443
|
+
text += ' ';
|
|
444
|
+
continue;
|
|
445
|
+
}
|
|
446
|
+
if (char === "'" || char === '"' || char === '`') {
|
|
447
|
+
quote = char;
|
|
448
|
+
text += ' ';
|
|
449
|
+
}
|
|
450
|
+
else if ('([{<'.includes(char)) {
|
|
451
|
+
depth++;
|
|
452
|
+
text += ' ';
|
|
453
|
+
}
|
|
454
|
+
else if (char === '>' && type[index - 1] === '=') {
|
|
455
|
+
text += depth ? ' ' : char;
|
|
456
|
+
}
|
|
457
|
+
else if (')]}>'.includes(char)) {
|
|
458
|
+
depth--;
|
|
459
|
+
text += ' ';
|
|
460
|
+
if (depth < 0) {
|
|
461
|
+
return undefined;
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
else {
|
|
465
|
+
text += depth ? ' ' : char;
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
return depth === 0 && !quote ? text : undefined;
|
|
469
|
+
}
|
|
470
|
+
|
|
166
471
|
let nextId = 0;
|
|
167
472
|
/** Value types that have a chip colour. */
|
|
168
473
|
const CHIP_TYPES = new Set([
|
|
@@ -269,7 +574,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.2.1", ngImpor
|
|
|
269
574
|
}], ctorParameters: () => [], propDecorators: { name: [{ type: i0.Input, args: [{ isSignal: true, alias: "name", required: false }] }], property: [{ type: i0.Input, args: [{ isSignal: true, alias: "property", required: false }] }], typeControl: [{ type: i0.Input, args: [{ isSignal: true, alias: "typeControl", required: false }] }], control: [{ type: i0.Input, args: [{ isSignal: true, alias: "control", required: false }] }], defaultValue: [{ type: i0.Input, args: [{ isSignal: true, alias: "defaultValue", required: false }] }], propertyOutlet: [{ type: i0.ViewChild, args: ['propertyOutlet', { ...{ read: ViewContainerRef }, isSignal: true }] }] } });
|
|
270
575
|
/**
|
|
271
576
|
* The value type of an input: its declared type when it is a primitive, else the type of the first
|
|
272
|
-
* value of its type alias. Content slots are booleans.
|
|
577
|
+
* value of its type alias; none for an array. Content slots are booleans.
|
|
273
578
|
* @param property - The input or the content slot.
|
|
274
579
|
*/
|
|
275
580
|
function valueType(property) {
|
|
@@ -282,6 +587,11 @@ function valueType(property) {
|
|
|
282
587
|
if (CHIP_TYPES.has(property.type)) {
|
|
283
588
|
return property.type;
|
|
284
589
|
}
|
|
590
|
+
// The options of an array are the values of its items, or the members of an optional array's
|
|
591
|
+
// union (`undefined` first): neither is the type of the input.
|
|
592
|
+
if (arrayType(property.type)) {
|
|
593
|
+
return '';
|
|
594
|
+
}
|
|
285
595
|
const option = property.options?.[0];
|
|
286
596
|
if (option !== undefined) {
|
|
287
597
|
try {
|
|
@@ -322,6 +632,7 @@ const INPUT_ORDER = new Intl.Collator('en');
|
|
|
322
632
|
*/
|
|
323
633
|
class NgDocPlaygroundPropertiesComponent {
|
|
324
634
|
constructor() {
|
|
635
|
+
this.texts = inject(NG_DOC_TEXTS).playground;
|
|
325
636
|
this.injector = inject(Injector);
|
|
326
637
|
/** The form of the playground. */
|
|
327
638
|
this.form = input.required(/* @ts-ignore */
|
|
@@ -338,6 +649,12 @@ class NgDocPlaygroundPropertiesComponent {
|
|
|
338
649
|
/** Default values of the inputs; the inspector renders once they are known. */
|
|
339
650
|
this.defaultValues = input(undefined, /* @ts-ignore */
|
|
340
651
|
...(ngDevMode ? [{ debugName: "defaultValues" }] : /* istanbul ignore next */ []));
|
|
652
|
+
/**
|
|
653
|
+
* Whether the demos render before the defaults are known: an isolated playground's iframes
|
|
654
|
+
* report them, so they must render first. The controls render once the defaults are known.
|
|
655
|
+
*/
|
|
656
|
+
this.waitingForDefaults = input(false, /* @ts-ignore */
|
|
657
|
+
...(ngDevMode ? [{ debugName: "waitingForDefaults" }] : /* istanbul ignore next */ []));
|
|
341
658
|
/** Whether the inspector is hidden, leaving the demos only. */
|
|
342
659
|
this.hideSidePanel = input(false, /* @ts-ignore */
|
|
343
660
|
...(ngDevMode ? [{ debugName: "hideSidePanel" }] : /* istanbul ignore next */ []));
|
|
@@ -386,7 +703,7 @@ class NgDocPlaygroundPropertiesComponent {
|
|
|
386
703
|
sections.set(group, [...(sections.get(group) ?? []), control]);
|
|
387
704
|
}
|
|
388
705
|
return Array.from(sections, ([group, controls]) => ({
|
|
389
|
-
title: group ??
|
|
706
|
+
title: group ?? this.texts.settings,
|
|
390
707
|
controls,
|
|
391
708
|
})).filter((section) => section.controls.length > 0);
|
|
392
709
|
}, /* @ts-ignore */
|
|
@@ -405,9 +722,19 @@ class NgDocPlaygroundPropertiesComponent {
|
|
|
405
722
|
getTypeControl(property) {
|
|
406
723
|
const type = property.type;
|
|
407
724
|
const primitive = optionalPrimitive(type);
|
|
725
|
+
const array = arrayType(type);
|
|
726
|
+
const options = isPlaygroundProperty(property)
|
|
727
|
+
? property.options
|
|
728
|
+
: undefined;
|
|
729
|
+
// The options of an array are the values of its items, when they are a union of literals.
|
|
408
730
|
const typeControl = this.getControlForType(type) ??
|
|
409
731
|
(primitive ? this.getControlForType(primitive) : undefined) ??
|
|
410
|
-
|
|
732
|
+
(array?.list ? this.getControlForType(array.list) : undefined) ??
|
|
733
|
+
(array
|
|
734
|
+
? this.getControlForTypeAlias(options, property.isManual, UNION_ARRAY_CONTROL_TYPE)
|
|
735
|
+
: undefined) ??
|
|
736
|
+
(array ? this.getControlForType(ARRAY_CONTROL_TYPE) : undefined) ??
|
|
737
|
+
this.getControlForTypeAlias(options, property.isManual, 'NgDocTypeAlias');
|
|
411
738
|
if (!typeControl && isDevMode()) {
|
|
412
739
|
console.warn(`NgDocPlayground didn't find the control for the @Input "${property.inputName}", the type "${type}" was not recognized'`);
|
|
413
740
|
}
|
|
@@ -417,25 +744,38 @@ class NgDocPlaygroundPropertiesComponent {
|
|
|
417
744
|
const token = getTokenForType(type);
|
|
418
745
|
return token ? this.injector.get(token) : undefined;
|
|
419
746
|
}
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
return token ? this.injector.get(token) : undefined;
|
|
433
|
-
}
|
|
747
|
+
/**
|
|
748
|
+
* The control of the values of a union, when every option is a value: the select of a union
|
|
749
|
+
* (`NgDocTypeAlias`) or the multi-select of an array of one (`NgDocTypeAlias[]`). An optional
|
|
750
|
+
* array input without such options has the members of its own union (`undefined` and the array
|
|
751
|
+
* type), and the text of an array type is not a value of an item.
|
|
752
|
+
* @param options - The options of the input.
|
|
753
|
+
* @param isManual - Whether the options come from the playground's `controls`.
|
|
754
|
+
* @param controlType - The type the control is registered for.
|
|
755
|
+
*/
|
|
756
|
+
getControlForTypeAlias(options, isManual, controlType) {
|
|
757
|
+
if (!options?.length) {
|
|
758
|
+
return undefined;
|
|
434
759
|
}
|
|
435
|
-
|
|
760
|
+
const values = [];
|
|
761
|
+
try {
|
|
762
|
+
// checking that all values are extractable
|
|
763
|
+
options.forEach((item) => values.push(resolvePlaygroundOption(item, isManual).value));
|
|
764
|
+
}
|
|
765
|
+
catch {
|
|
766
|
+
return undefined;
|
|
767
|
+
}
|
|
768
|
+
// The items of an array are values: an option that evaluates to an object, such as the text
|
|
769
|
+
// of a tuple type, means the options are not those of the items.
|
|
770
|
+
if (controlType === UNION_ARRAY_CONTROL_TYPE &&
|
|
771
|
+
!(values.every((value) => value === null || ITEM_TYPES.has(typeof value)) &&
|
|
772
|
+
values.some((value) => value !== null && value !== undefined))) {
|
|
773
|
+
return undefined;
|
|
774
|
+
}
|
|
775
|
+
return this.getControlForType(controlType);
|
|
436
776
|
}
|
|
437
777
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.2.1", ngImport: i0, type: NgDocPlaygroundPropertiesComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
438
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.2.1", type: NgDocPlaygroundPropertiesComponent, isStandalone: true, selector: "ng-doc-playground-properties", inputs: { form: { classPropertyName: "form", publicName: "form", isSignal: true, isRequired: true, transformFunction: null }, properties: { classPropertyName: "properties", publicName: "properties", isSignal: true, isRequired: false, transformFunction: null }, ignoreInputs: { classPropertyName: "ignoreInputs", publicName: "ignoreInputs", isSignal: true, isRequired: false, transformFunction: null }, dynamicContent: { classPropertyName: "dynamicContent", publicName: "dynamicContent", isSignal: true, isRequired: false, transformFunction: null }, defaultValues: { classPropertyName: "defaultValues", publicName: "defaultValues", isSignal: true, isRequired: false, transformFunction: null }, hideSidePanel: { classPropertyName: "hideSidePanel", publicName: "hideSidePanel", isSignal: true, isRequired: false, transformFunction: null }, inspectorPosition: { classPropertyName: "inspectorPosition", publicName: "inspectorPosition", isSignal: true, isRequired: false, transformFunction: null }, recreateDemo: { classPropertyName: "recreateDemo", publicName: "recreateDemo", isSignal: true, isRequired: false, transformFunction: null }, recreateLocked: { classPropertyName: "recreateLocked", publicName: "recreateLocked", isSignal: true, isRequired: false, transformFunction: null }, showResetButton: { classPropertyName: "showResetButton", publicName: "showResetButton", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { recreateDemo: "recreateDemoChange", resetForm: "resetForm" }, ngImport: i0, template: "@if (defaultValues(); as defaultValues) {\n <div\n class=\"ng-doc-playground-properties-wrapper\"\n [class.no-side-panel]=\"hideSidePanel()\"\n [class.inspector-bottom]=\"inspectorPosition() === 'bottom'\">\n <div class=\"ng-doc-playground-demos\">\n <ng-content></ng-content>\n </div>\n @if (!hideSidePanel()) {\n <div class=\"ng-doc-playground-properties\">\n <div class=\"ng-doc-playground-header\">\n <h4>Playground</h4>\n @if (showResetButton()) {\n <button type=\"button\" class=\"ng-doc-playground-reset\" (click)=\"resetForm.emit()\">\n Reset\n </button>\n }\n </div>\n @if (!recreateLocked()) {\n <div class=\"ng-doc-playground-setting\">\n <ng-doc-checkbox [ngModel]=\"recreateDemo()\" (ngModelChange)=\"recreateDemo.set($event)\">\n Recreate\n </ng-doc-checkbox>\n <ng-doc-icon\n class=\"ng-doc-playground-info\"\n icon=\"info\"\n role=\"img\"\n aria-label=\"Recreates the demo every time one of the inputs changes\"\n [ngDocTooltip]=\"'Recreates the demo every time one of the inputs changes'\"\n [positions]=\"['bottom-right', 'left-center']\"></ng-doc-icon>\n </div>\n }\n <div class=\"ng-doc-playground-divider\"></div>\n @for (section of propertySections(); track $index) {\n <h5 class=\"ng-doc-title\">{{ section.title }}</h5>\n <div class=\"ng-doc-playground-property-list\">\n @for (propertyControl of section.controls; track propertyControl.propertyName) {\n <ng-doc-playground-property\n [name]=\"propertyControl.property.label ?? propertyControl.property.inputName\"\n [property]=\"propertyControl.property\"\n [typeControl]=\"propertyControl.typeControl\"\n [defaultValue]=\"defaultValues[propertyControl.propertyName]\"\n [control]=\"\n getFormControl | bind: this | execute: 'properties' : propertyControl.propertyName\n \">\n </ng-doc-playground-property>\n }\n </div>\n }\n @if ((dynamicContent() | keyvalue)?.length) {\n <h5 class=\"ng-doc-title\">Content</h5>\n <div class=\"ng-doc-playground-property-list\">\n @for (content of dynamicContent() | keyvalue; track content.key) {\n <ng-doc-playground-property\n [name]=\"content.value.label\"\n [property]=\"content.value\"\n [typeControl]=\"contentTypeControl\"\n [control]=\"getFormControl | bind: this | execute: 'content' : content.key\">\n </ng-doc-playground-property>\n }\n </div>\n }\n </div>\n }\n </div>\n}\n", styles: [":host{display:block;container:ng-doc-playground/inline-size}.ng-doc-playground-properties-wrapper{display:grid;grid-template-columns:minmax(0,1fr) var(--ng-doc-playground-inspector-width);align-items:stretch;border:var(--ng-doc-demo-displayer-border);border-radius:var(--ng-doc-demo-displayer-border-radius);overflow:clip;background-color:var(--ng-doc-demo-displayer-background, var(--ng-doc-background))}.ng-doc-playground-properties-wrapper.no-side-panel{grid-template-columns:minmax(0,1fr)}.ng-doc-playground-properties-wrapper:not(.no-side-panel):not(.inspector-bottom) .ng-doc-playground-demos{position:sticky;top:calc(var(--ng-doc-page-sticky-offset) + var(--ng-doc-base-gutter));align-self:start;z-index:4}.ng-doc-playground-demos{display:flex;flex-direction:column;min-width:0}.ng-doc-playground-properties-wrapper:not(.no-side-panel):not(.inspector-bottom) .ng-doc-playground-demos{min-height:min(100%,100dvh - var(--ng-doc-page-sticky-offset) - 2 * var(--ng-doc-base-gutter))}.ng-doc-playground-properties{min-width:0;background:var(--ng-doc-base-1);border-left:1px solid var(--ng-doc-border-color);font-family:var(--ng-doc-font-family);font-size:14px;line-height:20px;color:var(--ng-doc-text);--ng-doc-input-height: 36px;--ng-doc-input-border: var(--ng-doc-playground-input-border);--ng-doc-checkbox-border: var(--ng-doc-playground-input-border)}.ng-doc-playground-header{display:flex;align-items:center;gap:var(--ng-doc-space-2);height:52px;padding:0 var(--ng-doc-space-3) 0 var(--ng-doc-space-4)}.ng-doc-playground-header h4{margin:0;font-family:var(--ng-doc-heading-font-family);font-size:15px;line-height:22px;font-weight:600;letter-spacing:0;color:var(--ng-doc-heading-color)}.ng-doc-playground-reset{display:inline-flex;align-items:center;height:28px;margin-left:auto;padding:0 10px;border:1px solid var(--ng-doc-border-color);border-radius:var(--ng-doc-radius-md);background:var(--ng-doc-background);font:inherit;font-size:12px;font-weight:500;color:var(--ng-doc-heading-color);cursor:pointer}.ng-doc-playground-reset:hover{border-color:var(--ng-doc-border-hover-color)}.ng-doc-playground-reset:focus-visible{outline:none;box-shadow:var(--ng-doc-focus-ring)}.ng-doc-playground-setting{display:flex;align-items:center;gap:6px;padding:0 var(--ng-doc-space-4) var(--ng-doc-space-3)}.ng-doc-playground-info{--ng-doc-icon-color: var(--ng-doc-text-muted)}.ng-doc-playground-divider{height:1px;background:var(--ng-doc-border-color)}h5.ng-doc-title{margin:0;padding:var(--ng-doc-space-3) var(--ng-doc-space-4) var(--ng-doc-space-1);font-family:var(--ng-doc-font-family);font-size:11px;line-height:16px;font-weight:600;letter-spacing:.05em;text-transform:uppercase;color:var(--ng-doc-text-muted)}ng-doc-playground-property{padding:var(--ng-doc-space-2) var(--ng-doc-space-4)}ng-doc-playground-property+ng-doc-playground-property{border-top:1px solid var(--ng-doc-base-2)}.ng-doc-playground-property-list:last-child{padding-bottom:var(--ng-doc-space-2)}.ng-doc-playground-properties-wrapper.inspector-bottom{grid-template-columns:minmax(0,1fr)}.ng-doc-playground-properties-wrapper.inspector-bottom .ng-doc-playground-properties{border-left:0;border-top:1px solid var(--ng-doc-border-color)}.ng-doc-playground-properties-wrapper.inspector-bottom .ng-doc-playground-property-list{display:grid;grid-template-columns:repeat(auto-fill,minmax(min(100%,320px),1fr));column-gap:var(--ng-doc-space-4)}.ng-doc-playground-properties-wrapper.inspector-bottom ng-doc-playground-property+ng-doc-playground-property{border-top:0}@container ng-doc-playground (max-width: 640px){.ng-doc-playground-properties-wrapper{grid-template-columns:minmax(0,1fr)}.ng-doc-playground-properties-wrapper:not(.no-side-panel) .ng-doc-playground-demos{position:static}.ng-doc-playground-properties{border-left:0;border-top:1px solid var(--ng-doc-border-color)}}\n"], dependencies: [{ kind: "component", type: NgDocCheckboxComponent, selector: "ng-doc-checkbox", inputs: ["color"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: NgDocTooltipDirective, selector: "[ngDocTooltip]", inputs: ["ngDocTooltip", "delay", "displayOrigin", "pointerOrigin", "positions", "canOpen", "panelClass", "minHeight", "maxHeight", "height", "minWidth", "maxWidth", "width"], outputs: ["beforeOpen", "afterOpen", "beforeClose", "afterClose"], exportAs: ["ngDocTooltip"] }, { kind: "component", type: NgDocIconComponent, selector: "ng-doc-icon", inputs: ["icon", "customIcon", "size"] }, { kind: "component", type: NgDocPlaygroundPropertyComponent, selector: "ng-doc-playground-property", inputs: ["name", "property", "typeControl", "control", "defaultValue"] }, { kind: "pipe", type: KeyValuePipe, name: "keyvalue" }, { kind: "pipe", type: NgDocBindPipe, name: "bind" }, { kind: "pipe", type: NgDocExecutePipe, name: "execute" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
778
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.2.1", type: NgDocPlaygroundPropertiesComponent, isStandalone: true, selector: "ng-doc-playground-properties", inputs: { form: { classPropertyName: "form", publicName: "form", isSignal: true, isRequired: true, transformFunction: null }, properties: { classPropertyName: "properties", publicName: "properties", isSignal: true, isRequired: false, transformFunction: null }, ignoreInputs: { classPropertyName: "ignoreInputs", publicName: "ignoreInputs", isSignal: true, isRequired: false, transformFunction: null }, dynamicContent: { classPropertyName: "dynamicContent", publicName: "dynamicContent", isSignal: true, isRequired: false, transformFunction: null }, defaultValues: { classPropertyName: "defaultValues", publicName: "defaultValues", isSignal: true, isRequired: false, transformFunction: null }, waitingForDefaults: { classPropertyName: "waitingForDefaults", publicName: "waitingForDefaults", isSignal: true, isRequired: false, transformFunction: null }, hideSidePanel: { classPropertyName: "hideSidePanel", publicName: "hideSidePanel", isSignal: true, isRequired: false, transformFunction: null }, inspectorPosition: { classPropertyName: "inspectorPosition", publicName: "inspectorPosition", isSignal: true, isRequired: false, transformFunction: null }, recreateDemo: { classPropertyName: "recreateDemo", publicName: "recreateDemo", isSignal: true, isRequired: false, transformFunction: null }, recreateLocked: { classPropertyName: "recreateLocked", publicName: "recreateLocked", isSignal: true, isRequired: false, transformFunction: null }, showResetButton: { classPropertyName: "showResetButton", publicName: "showResetButton", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { recreateDemo: "recreateDemoChange", resetForm: "resetForm" }, ngImport: i0, template: "@if (defaultValues() || waitingForDefaults()) {\n <div\n class=\"ng-doc-playground-properties-wrapper\"\n [class.no-side-panel]=\"hideSidePanel()\"\n [class.inspector-bottom]=\"inspectorPosition() === 'bottom'\">\n <div class=\"ng-doc-playground-demos\">\n <ng-content></ng-content>\n </div>\n @if (!hideSidePanel() && defaultValues(); as defaultValues) {\n <div class=\"ng-doc-playground-properties\">\n <div class=\"ng-doc-playground-header\">\n <h4>{{ texts.title }}</h4>\n @if (showResetButton()) {\n <button type=\"button\" class=\"ng-doc-playground-reset\" (click)=\"resetForm.emit()\">\n {{ texts.reset }}\n </button>\n }\n </div>\n @if (!recreateLocked()) {\n <div class=\"ng-doc-playground-setting\">\n <ng-doc-checkbox [ngModel]=\"recreateDemo()\" (ngModelChange)=\"recreateDemo.set($event)\">\n {{ texts.recreate }}\n </ng-doc-checkbox>\n <ng-doc-icon\n class=\"ng-doc-playground-info\"\n icon=\"info\"\n role=\"img\"\n [attr.aria-label]=\"texts.recreateHint\"\n [ngDocTooltip]=\"texts.recreateHint\"\n [positions]=\"['bottom-right', 'left-center']\"></ng-doc-icon>\n </div>\n }\n <div class=\"ng-doc-playground-divider\"></div>\n @for (section of propertySections(); track $index) {\n <h5 class=\"ng-doc-title\">{{ section.title }}</h5>\n <div class=\"ng-doc-playground-property-list\">\n @for (propertyControl of section.controls; track propertyControl.propertyName) {\n <ng-doc-playground-property\n [name]=\"propertyControl.property.label ?? propertyControl.property.inputName\"\n [property]=\"propertyControl.property\"\n [typeControl]=\"propertyControl.typeControl\"\n [defaultValue]=\"defaultValues[propertyControl.propertyName]\"\n [control]=\"\n getFormControl | bind: this | execute: 'properties' : propertyControl.propertyName\n \">\n </ng-doc-playground-property>\n }\n </div>\n }\n @if ((dynamicContent() | keyvalue)?.length) {\n <h5 class=\"ng-doc-title\">{{ texts.content }}</h5>\n <div class=\"ng-doc-playground-property-list\">\n @for (content of dynamicContent() | keyvalue; track content.key) {\n <ng-doc-playground-property\n [name]=\"content.value.label\"\n [property]=\"content.value\"\n [typeControl]=\"contentTypeControl\"\n [control]=\"getFormControl | bind: this | execute: 'content' : content.key\">\n </ng-doc-playground-property>\n }\n </div>\n }\n </div>\n }\n </div>\n}\n", styles: [":host{display:block;container:ng-doc-playground/inline-size}.ng-doc-playground-properties-wrapper{display:grid;grid-template-columns:minmax(0,1fr) var(--ng-doc-playground-inspector-width);align-items:stretch;border:var(--ng-doc-demo-displayer-border);border-radius:var(--ng-doc-demo-displayer-border-radius);overflow:clip;background-color:var(--ng-doc-demo-displayer-background, var(--ng-doc-background))}.ng-doc-playground-properties-wrapper.no-side-panel{grid-template-columns:minmax(0,1fr)}.ng-doc-playground-properties-wrapper:not(.no-side-panel):not(.inspector-bottom) .ng-doc-playground-demos{position:sticky;top:calc(var(--ng-doc-page-sticky-offset) + var(--ng-doc-base-gutter));align-self:start;z-index:4}.ng-doc-playground-demos{display:flex;flex-direction:column;min-width:0}.ng-doc-playground-properties-wrapper:not(.no-side-panel):not(.inspector-bottom) .ng-doc-playground-demos{min-height:min(100%,100dvh - var(--ng-doc-page-sticky-offset) - 2 * var(--ng-doc-base-gutter))}.ng-doc-playground-properties{min-width:0;background:var(--ng-doc-base-1);border-left:1px solid var(--ng-doc-border-color);font-family:var(--ng-doc-font-family);font-size:14px;line-height:20px;color:var(--ng-doc-text);--ng-doc-input-height: 36px;--ng-doc-input-border: var(--ng-doc-playground-input-border);--ng-doc-checkbox-border: var(--ng-doc-playground-input-border)}.ng-doc-playground-header{display:flex;align-items:center;gap:var(--ng-doc-space-2);height:52px;padding:0 var(--ng-doc-space-3) 0 var(--ng-doc-space-4)}.ng-doc-playground-header h4{margin:0;font-family:var(--ng-doc-heading-font-family);font-size:15px;line-height:22px;font-weight:600;letter-spacing:0;color:var(--ng-doc-heading-color)}.ng-doc-playground-reset{display:inline-flex;align-items:center;height:28px;margin-left:auto;padding:0 10px;border:1px solid var(--ng-doc-border-color);border-radius:var(--ng-doc-radius-md);background:var(--ng-doc-background);font:inherit;font-size:12px;font-weight:500;color:var(--ng-doc-heading-color);cursor:pointer}.ng-doc-playground-reset:hover{border-color:var(--ng-doc-border-hover-color)}.ng-doc-playground-reset:focus-visible{outline:none;box-shadow:var(--ng-doc-focus-ring)}.ng-doc-playground-setting{display:flex;align-items:center;gap:6px;padding:0 var(--ng-doc-space-4) var(--ng-doc-space-3)}.ng-doc-playground-info{--ng-doc-icon-color: var(--ng-doc-text-muted)}.ng-doc-playground-divider{height:1px;background:var(--ng-doc-border-color)}h5.ng-doc-title{margin:0;padding:var(--ng-doc-space-3) var(--ng-doc-space-4) var(--ng-doc-space-1);font-family:var(--ng-doc-font-family);font-size:11px;line-height:16px;font-weight:600;letter-spacing:.05em;text-transform:uppercase;color:var(--ng-doc-text-muted)}ng-doc-playground-property{padding:var(--ng-doc-space-2) var(--ng-doc-space-4)}ng-doc-playground-property+ng-doc-playground-property{border-top:1px solid var(--ng-doc-base-2)}.ng-doc-playground-property-list:last-child{padding-bottom:var(--ng-doc-space-2)}.ng-doc-playground-properties-wrapper.inspector-bottom{grid-template-columns:minmax(0,1fr)}.ng-doc-playground-properties-wrapper.inspector-bottom .ng-doc-playground-properties{border-left:0;border-top:1px solid var(--ng-doc-border-color)}.ng-doc-playground-properties-wrapper.inspector-bottom .ng-doc-playground-property-list{display:grid;grid-template-columns:repeat(auto-fill,minmax(min(100%,320px),1fr));column-gap:var(--ng-doc-space-4)}.ng-doc-playground-properties-wrapper.inspector-bottom ng-doc-playground-property+ng-doc-playground-property{border-top:0}@container ng-doc-playground (max-width: 640px){.ng-doc-playground-properties-wrapper{grid-template-columns:minmax(0,1fr)}.ng-doc-playground-properties-wrapper:not(.no-side-panel) .ng-doc-playground-demos{position:static}.ng-doc-playground-properties{border-left:0;border-top:1px solid var(--ng-doc-border-color)}}\n"], dependencies: [{ kind: "component", type: NgDocCheckboxComponent, selector: "ng-doc-checkbox", inputs: ["color"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: NgDocTooltipDirective, selector: "[ngDocTooltip]", inputs: ["ngDocTooltip", "delay", "displayOrigin", "pointerOrigin", "positions", "canOpen", "panelClass", "minHeight", "maxHeight", "height", "minWidth", "maxWidth", "width"], outputs: ["beforeOpen", "afterOpen", "beforeClose", "afterClose"], exportAs: ["ngDocTooltip"] }, { kind: "component", type: NgDocIconComponent, selector: "ng-doc-icon", inputs: ["icon", "customIcon", "size"] }, { kind: "component", type: NgDocPlaygroundPropertyComponent, selector: "ng-doc-playground-property", inputs: ["name", "property", "typeControl", "control", "defaultValue"] }, { kind: "pipe", type: KeyValuePipe, name: "keyvalue" }, { kind: "pipe", type: NgDocBindPipe, name: "bind" }, { kind: "pipe", type: NgDocExecutePipe, name: "execute" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
439
779
|
}
|
|
440
780
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.2.1", ngImport: i0, type: NgDocPlaygroundPropertiesComponent, decorators: [{
|
|
441
781
|
type: Component,
|
|
@@ -448,8 +788,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.2.1", ngImpor
|
|
|
448
788
|
KeyValuePipe,
|
|
449
789
|
NgDocBindPipe,
|
|
450
790
|
NgDocExecutePipe,
|
|
451
|
-
], template: "@if (defaultValues()
|
|
452
|
-
}], propDecorators: { form: [{ type: i0.Input, args: [{ isSignal: true, alias: "form", required: true }] }], properties: [{ type: i0.Input, args: [{ isSignal: true, alias: "properties", required: false }] }], ignoreInputs: [{ type: i0.Input, args: [{ isSignal: true, alias: "ignoreInputs", required: false }] }], dynamicContent: [{ type: i0.Input, args: [{ isSignal: true, alias: "dynamicContent", required: false }] }], defaultValues: [{ type: i0.Input, args: [{ isSignal: true, alias: "defaultValues", required: false }] }], hideSidePanel: [{ type: i0.Input, args: [{ isSignal: true, alias: "hideSidePanel", required: false }] }], inspectorPosition: [{ type: i0.Input, args: [{ isSignal: true, alias: "inspectorPosition", required: false }] }], recreateDemo: [{ type: i0.Input, args: [{ isSignal: true, alias: "recreateDemo", required: false }] }, { type: i0.Output, args: ["recreateDemoChange"] }], recreateLocked: [{ type: i0.Input, args: [{ isSignal: true, alias: "recreateLocked", required: false }] }], showResetButton: [{ type: i0.Input, args: [{ isSignal: true, alias: "showResetButton", required: false }] }], resetForm: [{ type: i0.Output, args: ["resetForm"] }] } });
|
|
791
|
+
], template: "@if (defaultValues() || waitingForDefaults()) {\n <div\n class=\"ng-doc-playground-properties-wrapper\"\n [class.no-side-panel]=\"hideSidePanel()\"\n [class.inspector-bottom]=\"inspectorPosition() === 'bottom'\">\n <div class=\"ng-doc-playground-demos\">\n <ng-content></ng-content>\n </div>\n @if (!hideSidePanel() && defaultValues(); as defaultValues) {\n <div class=\"ng-doc-playground-properties\">\n <div class=\"ng-doc-playground-header\">\n <h4>{{ texts.title }}</h4>\n @if (showResetButton()) {\n <button type=\"button\" class=\"ng-doc-playground-reset\" (click)=\"resetForm.emit()\">\n {{ texts.reset }}\n </button>\n }\n </div>\n @if (!recreateLocked()) {\n <div class=\"ng-doc-playground-setting\">\n <ng-doc-checkbox [ngModel]=\"recreateDemo()\" (ngModelChange)=\"recreateDemo.set($event)\">\n {{ texts.recreate }}\n </ng-doc-checkbox>\n <ng-doc-icon\n class=\"ng-doc-playground-info\"\n icon=\"info\"\n role=\"img\"\n [attr.aria-label]=\"texts.recreateHint\"\n [ngDocTooltip]=\"texts.recreateHint\"\n [positions]=\"['bottom-right', 'left-center']\"></ng-doc-icon>\n </div>\n }\n <div class=\"ng-doc-playground-divider\"></div>\n @for (section of propertySections(); track $index) {\n <h5 class=\"ng-doc-title\">{{ section.title }}</h5>\n <div class=\"ng-doc-playground-property-list\">\n @for (propertyControl of section.controls; track propertyControl.propertyName) {\n <ng-doc-playground-property\n [name]=\"propertyControl.property.label ?? propertyControl.property.inputName\"\n [property]=\"propertyControl.property\"\n [typeControl]=\"propertyControl.typeControl\"\n [defaultValue]=\"defaultValues[propertyControl.propertyName]\"\n [control]=\"\n getFormControl | bind: this | execute: 'properties' : propertyControl.propertyName\n \">\n </ng-doc-playground-property>\n }\n </div>\n }\n @if ((dynamicContent() | keyvalue)?.length) {\n <h5 class=\"ng-doc-title\">{{ texts.content }}</h5>\n <div class=\"ng-doc-playground-property-list\">\n @for (content of dynamicContent() | keyvalue; track content.key) {\n <ng-doc-playground-property\n [name]=\"content.value.label\"\n [property]=\"content.value\"\n [typeControl]=\"contentTypeControl\"\n [control]=\"getFormControl | bind: this | execute: 'content' : content.key\">\n </ng-doc-playground-property>\n }\n </div>\n }\n </div>\n }\n </div>\n}\n", styles: [":host{display:block;container:ng-doc-playground/inline-size}.ng-doc-playground-properties-wrapper{display:grid;grid-template-columns:minmax(0,1fr) var(--ng-doc-playground-inspector-width);align-items:stretch;border:var(--ng-doc-demo-displayer-border);border-radius:var(--ng-doc-demo-displayer-border-radius);overflow:clip;background-color:var(--ng-doc-demo-displayer-background, var(--ng-doc-background))}.ng-doc-playground-properties-wrapper.no-side-panel{grid-template-columns:minmax(0,1fr)}.ng-doc-playground-properties-wrapper:not(.no-side-panel):not(.inspector-bottom) .ng-doc-playground-demos{position:sticky;top:calc(var(--ng-doc-page-sticky-offset) + var(--ng-doc-base-gutter));align-self:start;z-index:4}.ng-doc-playground-demos{display:flex;flex-direction:column;min-width:0}.ng-doc-playground-properties-wrapper:not(.no-side-panel):not(.inspector-bottom) .ng-doc-playground-demos{min-height:min(100%,100dvh - var(--ng-doc-page-sticky-offset) - 2 * var(--ng-doc-base-gutter))}.ng-doc-playground-properties{min-width:0;background:var(--ng-doc-base-1);border-left:1px solid var(--ng-doc-border-color);font-family:var(--ng-doc-font-family);font-size:14px;line-height:20px;color:var(--ng-doc-text);--ng-doc-input-height: 36px;--ng-doc-input-border: var(--ng-doc-playground-input-border);--ng-doc-checkbox-border: var(--ng-doc-playground-input-border)}.ng-doc-playground-header{display:flex;align-items:center;gap:var(--ng-doc-space-2);height:52px;padding:0 var(--ng-doc-space-3) 0 var(--ng-doc-space-4)}.ng-doc-playground-header h4{margin:0;font-family:var(--ng-doc-heading-font-family);font-size:15px;line-height:22px;font-weight:600;letter-spacing:0;color:var(--ng-doc-heading-color)}.ng-doc-playground-reset{display:inline-flex;align-items:center;height:28px;margin-left:auto;padding:0 10px;border:1px solid var(--ng-doc-border-color);border-radius:var(--ng-doc-radius-md);background:var(--ng-doc-background);font:inherit;font-size:12px;font-weight:500;color:var(--ng-doc-heading-color);cursor:pointer}.ng-doc-playground-reset:hover{border-color:var(--ng-doc-border-hover-color)}.ng-doc-playground-reset:focus-visible{outline:none;box-shadow:var(--ng-doc-focus-ring)}.ng-doc-playground-setting{display:flex;align-items:center;gap:6px;padding:0 var(--ng-doc-space-4) var(--ng-doc-space-3)}.ng-doc-playground-info{--ng-doc-icon-color: var(--ng-doc-text-muted)}.ng-doc-playground-divider{height:1px;background:var(--ng-doc-border-color)}h5.ng-doc-title{margin:0;padding:var(--ng-doc-space-3) var(--ng-doc-space-4) var(--ng-doc-space-1);font-family:var(--ng-doc-font-family);font-size:11px;line-height:16px;font-weight:600;letter-spacing:.05em;text-transform:uppercase;color:var(--ng-doc-text-muted)}ng-doc-playground-property{padding:var(--ng-doc-space-2) var(--ng-doc-space-4)}ng-doc-playground-property+ng-doc-playground-property{border-top:1px solid var(--ng-doc-base-2)}.ng-doc-playground-property-list:last-child{padding-bottom:var(--ng-doc-space-2)}.ng-doc-playground-properties-wrapper.inspector-bottom{grid-template-columns:minmax(0,1fr)}.ng-doc-playground-properties-wrapper.inspector-bottom .ng-doc-playground-properties{border-left:0;border-top:1px solid var(--ng-doc-border-color)}.ng-doc-playground-properties-wrapper.inspector-bottom .ng-doc-playground-property-list{display:grid;grid-template-columns:repeat(auto-fill,minmax(min(100%,320px),1fr));column-gap:var(--ng-doc-space-4)}.ng-doc-playground-properties-wrapper.inspector-bottom ng-doc-playground-property+ng-doc-playground-property{border-top:0}@container ng-doc-playground (max-width: 640px){.ng-doc-playground-properties-wrapper{grid-template-columns:minmax(0,1fr)}.ng-doc-playground-properties-wrapper:not(.no-side-panel) .ng-doc-playground-demos{position:static}.ng-doc-playground-properties{border-left:0;border-top:1px solid var(--ng-doc-border-color)}}\n"] }]
|
|
792
|
+
}], propDecorators: { form: [{ type: i0.Input, args: [{ isSignal: true, alias: "form", required: true }] }], properties: [{ type: i0.Input, args: [{ isSignal: true, alias: "properties", required: false }] }], ignoreInputs: [{ type: i0.Input, args: [{ isSignal: true, alias: "ignoreInputs", required: false }] }], dynamicContent: [{ type: i0.Input, args: [{ isSignal: true, alias: "dynamicContent", required: false }] }], defaultValues: [{ type: i0.Input, args: [{ isSignal: true, alias: "defaultValues", required: false }] }], waitingForDefaults: [{ type: i0.Input, args: [{ isSignal: true, alias: "waitingForDefaults", required: false }] }], hideSidePanel: [{ type: i0.Input, args: [{ isSignal: true, alias: "hideSidePanel", required: false }] }], inspectorPosition: [{ type: i0.Input, args: [{ isSignal: true, alias: "inspectorPosition", required: false }] }], recreateDemo: [{ type: i0.Input, args: [{ isSignal: true, alias: "recreateDemo", required: false }] }, { type: i0.Output, args: ["recreateDemoChange"] }], recreateLocked: [{ type: i0.Input, args: [{ isSignal: true, alias: "recreateLocked", required: false }] }], showResetButton: [{ type: i0.Input, args: [{ isSignal: true, alias: "showResetButton", required: false }] }], resetForm: [{ type: i0.Output, args: ["resetForm"] }] } });
|
|
453
793
|
/**
|
|
454
794
|
* Compares the `order` of two inputs from the playground's `controls`: inputs with one come first,
|
|
455
795
|
* lowest first.
|
|
@@ -489,15 +829,17 @@ function compareByTypeControl(a, b) {
|
|
|
489
829
|
}
|
|
490
830
|
return INPUT_ORDER.compare(a.property.inputName, b.property.inputName);
|
|
491
831
|
}
|
|
832
|
+
/** The types of the values that an item of the multi-select of a union can have. */
|
|
833
|
+
const ITEM_TYPES = new Set(['string', 'number', 'boolean', 'undefined']);
|
|
492
834
|
/** The types whose control also edits an optional or nullable input of the type. */
|
|
493
835
|
const PRIMITIVE_TYPES = new Set(['string', 'number', 'boolean']);
|
|
494
836
|
/**
|
|
495
837
|
* The primitive of an optional or nullable primitive input type, such as `string` of
|
|
496
838
|
* `string | undefined` (an optional `@Input() label?: string`, `input<string>()` or
|
|
497
839
|
* `model<string>()`) or of `number | null`, so that the input gets the primitive's control.
|
|
498
|
-
* Other types are matched by their exact name only
|
|
499
|
-
* does not receive the `undefined` of a
|
|
500
|
-
* that type too.
|
|
840
|
+
* Other types are matched by their exact name only (arrays of strings and numbers aside, see
|
|
841
|
+
* `arrayType`): a custom control registered for `Position` does not receive the `undefined` of a
|
|
842
|
+
* `Position | undefined` input unless it is registered for that type too.
|
|
501
843
|
* @param type - The input type as the builder printed it.
|
|
502
844
|
* @returns The primitive, or `undefined` when the type is not one.
|
|
503
845
|
*/
|
|
@@ -512,11 +854,18 @@ function optionalPrimitive(type) {
|
|
|
512
854
|
/**
|
|
513
855
|
* A playground: an inspector with a control per input and content slot of its target, and a demo
|
|
514
856
|
* per selector (or the pipe) rendered with the chosen values.
|
|
857
|
+
*
|
|
858
|
+
* An isolated playground (the `isolated` option, or `isolatedPlaygrounds`) shows each demo in an
|
|
859
|
+
* iframe of its page in the demo application, which the Vite engine builds. The inspector stays
|
|
860
|
+
* here and passes the values in the iframe's URL fragment; the page reports the target's defaults
|
|
861
|
+
* back. When a value can't be put in the URL, the playground renders its demos in the page
|
|
862
|
+
* instead, with a note that names the input.
|
|
515
863
|
*/
|
|
516
864
|
class NgDocPlaygroundComponent {
|
|
517
865
|
constructor() {
|
|
518
866
|
this.rootPage = inject(NgDocRootPage);
|
|
519
867
|
this.formBuilder = inject(FormBuilder);
|
|
868
|
+
this.texts = inject(NG_DOC_TEXTS).playground;
|
|
520
869
|
/** The name of the playground in the page configuration. */
|
|
521
870
|
this.id = input.required(/* @ts-ignore */
|
|
522
871
|
...(ngDevMode ? [{ debugName: "id" }] : /* istanbul ignore next */ []));
|
|
@@ -550,10 +899,169 @@ class NgDocPlaygroundComponent {
|
|
|
550
899
|
/** Whether the Recreate setting is fixed on, and hidden, by the `'always'` option. */
|
|
551
900
|
this.recreateLocked = computed(() => this.configuration().recreate === 'always', /* @ts-ignore */
|
|
552
901
|
...(ngDevMode ? [{ debugName: "recreateLocked" }] : /* istanbul ignore next */ []));
|
|
902
|
+
/**
|
|
903
|
+
* The note that says why the playground renders in the page although it is isolated: the input
|
|
904
|
+
* whose value can't be sent to its iframes, or what failed. Once set, it stays for the rest of
|
|
905
|
+
* the visit.
|
|
906
|
+
*/
|
|
907
|
+
this.fallback = signal(undefined, /* @ts-ignore */
|
|
908
|
+
...(ngDevMode ? [{ debugName: "fallback" }] : /* istanbul ignore next */ []));
|
|
909
|
+
/** Whether the demos are shown in iframes of their pages in the demo application. */
|
|
910
|
+
this.isolated = computed(() => !!this.rootPage.demoRoute &&
|
|
911
|
+
!this.fallback() &&
|
|
912
|
+
!this.uncloneableControl() &&
|
|
913
|
+
!this.pagelessSelector() &&
|
|
914
|
+
(this.configuration().isolated ?? this.rootPage.isolatedPlaygrounds ?? false), /* @ts-ignore */
|
|
915
|
+
...(ngDevMode ? [{ debugName: "isolated" }] : /* istanbul ignore next */ []));
|
|
916
|
+
/** The note that says why an isolated playground renders in the page. */
|
|
917
|
+
this.fallbackNote = computed(() => {
|
|
918
|
+
const note = this.uncloneableControl() ?? this.pagelessSelector() ?? this.fallback();
|
|
919
|
+
return note && (this.configuration().isolated ?? this.rootPage.isolatedPlaygrounds)
|
|
920
|
+
? note
|
|
921
|
+
: undefined;
|
|
922
|
+
}, /* @ts-ignore */
|
|
923
|
+
...(ngDevMode ? [{ debugName: "fallbackNote" }] : /* istanbul ignore next */ []));
|
|
924
|
+
/**
|
|
925
|
+
* The theme of the iframes: the `theme` option, until the reader picks another with the theme
|
|
926
|
+
* button beside a demo; `undefined` follows the page.
|
|
927
|
+
*/
|
|
928
|
+
this.frameTheme = linkedSignal(() => this.configuration().theme, /* @ts-ignore */
|
|
929
|
+
...(ngDevMode ? [{ debugName: "frameTheme" }] : /* istanbul ignore next */ []));
|
|
930
|
+
/** The iframe of each target, by selector or pipe name, while the playground is isolated. */
|
|
931
|
+
this.frames = computed(() => {
|
|
932
|
+
const encoded = this.fragment();
|
|
933
|
+
if (!this.isolated() || !encoded.ok)
|
|
934
|
+
return undefined;
|
|
935
|
+
const targets = this.targets();
|
|
936
|
+
const slugs = _playgroundTargetSlugs(targets);
|
|
937
|
+
return new Map(targets.map((target, index) => [
|
|
938
|
+
target,
|
|
939
|
+
{
|
|
940
|
+
url: this.pageUrl(slugs[index]),
|
|
941
|
+
fragment: _withDemoTheme(encoded.fragment, this.frameTheme()),
|
|
942
|
+
title: this.texts.frameTitle({ id: this.id(), target }),
|
|
943
|
+
},
|
|
944
|
+
]));
|
|
945
|
+
}, /* @ts-ignore */
|
|
946
|
+
...(ngDevMode ? [{ debugName: "frames" }] : /* istanbul ignore next */ []));
|
|
947
|
+
this.injector = inject(Injector);
|
|
948
|
+
this.platformLocation = inject(PlatformLocation);
|
|
949
|
+
this.appBaseHref = inject(APP_BASE_HREF, { optional: true });
|
|
950
|
+
this.formValue = signal({}, /* @ts-ignore */
|
|
951
|
+
...(ngDevMode ? [{ debugName: "formValue" }] : /* istanbul ignore next */ []));
|
|
553
952
|
this.defaultProperties = {};
|
|
554
953
|
this.defaultContent = {};
|
|
954
|
+
this.viewInitialized = false;
|
|
955
|
+
/** Every target of the playground, in the order of its page's routes: its selectors, or the pipe. */
|
|
956
|
+
this.targets = computed(() => this.pipeName() ? [this.pipeName()] : this.selectors(), /* @ts-ignore */
|
|
957
|
+
...(ngDevMode ? [{ debugName: "targets" }] : /* istanbul ignore next */ []));
|
|
958
|
+
/** The state of the iframes as a URL fragment. */
|
|
959
|
+
this.fragment = computed(() => {
|
|
960
|
+
const names = objectKeys(this.properties() ?? {}).map(String);
|
|
961
|
+
const value = this.formValue();
|
|
962
|
+
const configuration = this.configuration();
|
|
963
|
+
// Before the iframe reported the defaults, it gets the playground's own values only.
|
|
964
|
+
const properties = value.properties ??
|
|
965
|
+
Object.fromEntries(Object.entries(Object.assign({}, configuration.defaults, configuration.inputs)).filter(([name]) => names.includes(name)));
|
|
966
|
+
return _playgroundStateToFragment({
|
|
967
|
+
inputs: names,
|
|
968
|
+
properties,
|
|
969
|
+
content: Object.entries(value.content ?? {})
|
|
970
|
+
.filter(([, shown]) => shown)
|
|
971
|
+
.map(([slot]) => slot),
|
|
972
|
+
recreate: this.recreateDemo(),
|
|
973
|
+
data: this.options().data,
|
|
974
|
+
});
|
|
975
|
+
}, /* @ts-ignore */
|
|
976
|
+
...(ngDevMode ? [{ debugName: "fragment" }] : /* istanbul ignore next */ []));
|
|
977
|
+
/**
|
|
978
|
+
* An input whose control is declared not cloneable (`cloneable: false` of `provideTypeControl`):
|
|
979
|
+
* the playground renders in the page from the start.
|
|
980
|
+
*/
|
|
981
|
+
this.uncloneableControl = computed(() => {
|
|
982
|
+
const controls = this.configuration().controls ?? {};
|
|
983
|
+
for (const [name, property] of Object.entries(this.properties() ?? {})) {
|
|
984
|
+
const control = controls[name];
|
|
985
|
+
const type = typeof control === 'string' ? control : control?.type ?? property.type;
|
|
986
|
+
const token = getTokenForType(type);
|
|
987
|
+
if (token && this.injector.get(token, null)?.options?.cloneable === false) {
|
|
988
|
+
return this.texts.inPageControl({ input: name });
|
|
989
|
+
}
|
|
990
|
+
}
|
|
991
|
+
return undefined;
|
|
992
|
+
}, /* @ts-ignore */
|
|
993
|
+
...(ngDevMode ? [{ debugName: "uncloneableControl" }] : /* istanbul ignore next */ []));
|
|
994
|
+
/**
|
|
995
|
+
* A selector of the `selectors` option that is not one of the target's: it has no page, so the
|
|
996
|
+
* playground renders in the page from the start.
|
|
997
|
+
*/
|
|
998
|
+
this.pagelessSelector = computed(() => {
|
|
999
|
+
if (this.pipeName())
|
|
1000
|
+
return undefined;
|
|
1001
|
+
const option = this.configuration().selectors;
|
|
1002
|
+
const rendered = option === undefined ? [] : Array.isArray(option) ? option : [option];
|
|
1003
|
+
const selector = rendered.find((item) => !this.selectors().includes(item));
|
|
1004
|
+
return selector === undefined ? undefined : this.texts.inPageSelector({ selector });
|
|
1005
|
+
}, /* @ts-ignore */
|
|
1006
|
+
...(ngDevMode ? [{ debugName: "pagelessSelector" }] : /* istanbul ignore next */ []));
|
|
1007
|
+
// A value that can't be put in the URL makes the playground render in the page.
|
|
1008
|
+
effect(() => {
|
|
1009
|
+
const encoded = this.fragment();
|
|
1010
|
+
if (!encoded.ok && this.isolated()) {
|
|
1011
|
+
untracked(() => this.fallback.set(encoded.path
|
|
1012
|
+
? this.texts.inPageValue({ input: encoded.path, reason: encoded.reason })
|
|
1013
|
+
: this.texts.inPageTooLong));
|
|
1014
|
+
}
|
|
1015
|
+
});
|
|
1016
|
+
// A playground that fell back before its form existed creates it once a demo in the page has
|
|
1017
|
+
// reported the defaults. A playground in the page from the start creates it in
|
|
1018
|
+
// `ngAfterViewInit`, as before.
|
|
1019
|
+
effect(() => {
|
|
1020
|
+
if (this.fallback() && this.defaultValues() && !this.formGroup() && this.viewInitialized) {
|
|
1021
|
+
untracked(() => this.createForm());
|
|
1022
|
+
}
|
|
1023
|
+
});
|
|
1024
|
+
}
|
|
1025
|
+
/**
|
|
1026
|
+
* Takes a message of a demo's iframe: the target's defaults, which create the form, or a
|
|
1027
|
+
* failure, which makes the playground render in the page.
|
|
1028
|
+
* @param message - The message.
|
|
1029
|
+
* @internal
|
|
1030
|
+
*/
|
|
1031
|
+
onFrameMessage(message) {
|
|
1032
|
+
if (message.type === 'playground-error') {
|
|
1033
|
+
this.fallback.set(this.texts.inPageFrameError({ message: message.message }));
|
|
1034
|
+
return;
|
|
1035
|
+
}
|
|
1036
|
+
if (message.type !== 'playground-ready' || this.formGroup())
|
|
1037
|
+
return;
|
|
1038
|
+
if (message.uncloneable.length) {
|
|
1039
|
+
this.fallback.set(this.texts.inPageDefault({ input: message.uncloneable[0] }));
|
|
1040
|
+
return;
|
|
1041
|
+
}
|
|
1042
|
+
this.defaultValues.set(Object.fromEntries(Object.entries(message.defaults).map(([name, value]) => [
|
|
1043
|
+
name,
|
|
1044
|
+
_decodePlaygroundValue(value),
|
|
1045
|
+
])));
|
|
1046
|
+
this.createForm();
|
|
1047
|
+
}
|
|
1048
|
+
/**
|
|
1049
|
+
* Makes the playground render in the page when the page in an iframe loaded but did not report
|
|
1050
|
+
* the target, such as a page the build didn't make.
|
|
1051
|
+
* @internal
|
|
1052
|
+
*/
|
|
1053
|
+
onFrameSilent() {
|
|
1054
|
+
if (!this.fallback())
|
|
1055
|
+
this.fallback.set(this.texts.inPageFrameSilent);
|
|
555
1056
|
}
|
|
556
1057
|
ngAfterViewInit() {
|
|
1058
|
+
this.viewInitialized = true;
|
|
1059
|
+
// An isolated playground creates its form once its iframe reported the defaults.
|
|
1060
|
+
if (this.isolated())
|
|
1061
|
+
return;
|
|
1062
|
+
this.createForm();
|
|
1063
|
+
}
|
|
1064
|
+
createForm() {
|
|
557
1065
|
this.defaultProperties = this.getPropertiesFormValues();
|
|
558
1066
|
this.defaultContent = this.getContentFormValues();
|
|
559
1067
|
// `FormBuilder` reads an array as `[value, validators]`, so each value is wrapped: the default
|
|
@@ -570,6 +1078,30 @@ class NgDocPlaygroundComponent {
|
|
|
570
1078
|
content: this.defaultContent,
|
|
571
1079
|
});
|
|
572
1080
|
this.formGroup.set(formGroup);
|
|
1081
|
+
formGroup.valueChanges
|
|
1082
|
+
.pipe(takeUntilDestroyed(this.injector.get(DestroyRef)))
|
|
1083
|
+
.subscribe(() => this.formValue.set(formGroup.getRawValue()));
|
|
1084
|
+
this.formValue.set(formGroup.getRawValue());
|
|
1085
|
+
}
|
|
1086
|
+
/**
|
|
1087
|
+
* The URL of a target's page in the demo application, under the document's base href.
|
|
1088
|
+
* @param slug
|
|
1089
|
+
*/
|
|
1090
|
+
pageUrl(slug) {
|
|
1091
|
+
const baseHref = (this.appBaseHref ??
|
|
1092
|
+
this.platformLocation.getBaseHrefFromDOM() ??
|
|
1093
|
+
'/').replace(/^\/+|\/+$/g, '');
|
|
1094
|
+
const path = [
|
|
1095
|
+
...baseHref.split('/'),
|
|
1096
|
+
...(this.rootPage.demoRoute ?? '').split('/'),
|
|
1097
|
+
'playground',
|
|
1098
|
+
this.id(),
|
|
1099
|
+
slug,
|
|
1100
|
+
]
|
|
1101
|
+
.filter(Boolean)
|
|
1102
|
+
.map((segment) => encodeURIComponent(segment))
|
|
1103
|
+
.join('/');
|
|
1104
|
+
return `/${path}`;
|
|
573
1105
|
}
|
|
574
1106
|
isDefaultState() {
|
|
575
1107
|
const formGroup = this.formGroup();
|
|
@@ -603,98 +1135,201 @@ class NgDocPlaygroundComponent {
|
|
|
603
1135
|
});
|
|
604
1136
|
}
|
|
605
1137
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.2.1", ngImport: i0, type: NgDocPlaygroundComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
606
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.2.1", type: NgDocPlaygroundComponent, isStandalone: true, selector: "ng-doc-playground", inputs: { id: { classPropertyName: "id", publicName: "id", isSignal: true, isRequired: true, transformFunction: null }, pipeName: { classPropertyName: "pipeName", publicName: "pipeName", isSignal: true, isRequired: false, transformFunction: null }, selectors: { classPropertyName: "selectors", publicName: "selectors", isSignal: true, isRequired: false, transformFunction: null }, properties: { classPropertyName: "properties", publicName: "properties", isSignal: true, isRequired: false, transformFunction: null }, options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "@if (configuration(); as configuration) {\n <ng-doc-playground-properties\n [form]=\"formGroup()!\"\n [properties]=\"properties()\"\n [ignoreInputs]=\"configuration.hiddenInputs\"\n [dynamicContent]=\"configuration.content\"\n [hideSidePanel]=\"configuration.hideSidePanel ?? false\"\n [inspectorPosition]=\"configuration.inspectorPosition ?? 'right'\"\n [defaultValues]=\"defaultValues()\"\n [showResetButton]=\"!isDefaultState()\"\n [(recreateDemo)]=\"recreateDemo\"\n [recreateLocked]=\"recreateLocked()\"\n (resetForm)=\"resetForm()\">\n @for (selector of configuration.selectors ?? selectors() | asArray; track selector) {\n <ng-doc-playground-demo\n [id]=\"id()\"\n [selector]=\"selector\"\n [properties]=\"properties()\"\n [configuration]=\"configuration\"\n [recreateDemo]=\"recreateDemo()\"\n [form]=\"formGroup()\"\n [expanded]=\"configuration.expanded ?? false\">\n </ng-doc-playground-demo>\n }\n @if (pipeName()) {\n <ng-doc-playground-demo\n [id]=\"id()\"\n [pipeName]=\"pipeName()\"\n [properties]=\"properties()\"\n [configuration]=\"configuration\"\n [recreateDemo]=\"recreateDemo()\"\n [form]=\"formGroup()\">\n </ng-doc-playground-demo>\n }\n </ng-doc-playground-properties>\n}\n", styles: [":host{display:block;margin:var(--ng-doc-playground-margin)}ng-doc-playground-demo:last-child{flex:1 1 auto}ng-doc-playground-demo+ng-doc-playground-demo{border-top:1px solid color-mix(in srgb,var(--ng-doc-border-color) 70%,transparent)}\n"], dependencies: [{ kind: "component", type: NgDocPlaygroundPropertiesComponent, selector: "ng-doc-playground-properties", inputs: ["form", "properties", "ignoreInputs", "dynamicContent", "defaultValues", "hideSidePanel", "inspectorPosition", "recreateDemo", "recreateLocked", "showResetButton"], outputs: ["recreateDemoChange", "resetForm"] }, { kind: "component", type: NgDocPlaygroundDemoComponent, selector: "ng-doc-playground-demo", inputs: ["id", "pipeName", "selector", "configuration", "properties", "recreateDemo", "form", "expanded"] }, { kind: "pipe", type: NgDocAsArrayPipe, name: "asArray" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
1138
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.2.1", type: NgDocPlaygroundComponent, isStandalone: true, selector: "ng-doc-playground", inputs: { id: { classPropertyName: "id", publicName: "id", isSignal: true, isRequired: true, transformFunction: null }, pipeName: { classPropertyName: "pipeName", publicName: "pipeName", isSignal: true, isRequired: false, transformFunction: null }, selectors: { classPropertyName: "selectors", publicName: "selectors", isSignal: true, isRequired: false, transformFunction: null }, properties: { classPropertyName: "properties", publicName: "properties", isSignal: true, isRequired: false, transformFunction: null }, options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: false, transformFunction: null } }, providers: [{ provide: ɵNG_DOC_PLAYGROUND_CONTAINER, useExisting: NgDocPlaygroundComponent }], ngImport: i0, template: "@if (configuration(); as configuration) {\n @if (fallbackNote(); as note) {\n <p class=\"ng-doc-playground-note\" role=\"note\">{{ note }}</p>\n }\n <ng-doc-playground-properties\n [form]=\"formGroup()!\"\n [properties]=\"properties()\"\n [ignoreInputs]=\"configuration.hiddenInputs\"\n [dynamicContent]=\"configuration.content\"\n [hideSidePanel]=\"configuration.hideSidePanel ?? false\"\n [inspectorPosition]=\"configuration.inspectorPosition ?? 'right'\"\n [defaultValues]=\"defaultValues()\"\n [waitingForDefaults]=\"isolated()\"\n [showResetButton]=\"!isDefaultState()\"\n [(recreateDemo)]=\"recreateDemo\"\n [recreateLocked]=\"recreateLocked()\"\n (resetForm)=\"resetForm()\">\n @for (selector of configuration.selectors ?? selectors() | asArray; track selector) {\n <ng-doc-playground-demo\n [id]=\"id()\"\n [selector]=\"selector\"\n [properties]=\"properties()\"\n [configuration]=\"configuration\"\n [recreateDemo]=\"recreateDemo()\"\n [form]=\"formGroup()\"\n [expanded]=\"configuration.expanded ?? false\"\n [frame]=\"frames()?.get(selector)\"\n [frameDefaults]=\"defaultValues()\"\n [(frameTheme)]=\"frameTheme\"\n [configuredTheme]=\"configuration.theme\"\n (frameMessage)=\"onFrameMessage($event)\"\n (frameSilent)=\"onFrameSilent()\">\n </ng-doc-playground-demo>\n }\n @if (pipeName()) {\n <ng-doc-playground-demo\n [id]=\"id()\"\n [pipeName]=\"pipeName()\"\n [properties]=\"properties()\"\n [configuration]=\"configuration\"\n [recreateDemo]=\"recreateDemo()\"\n [form]=\"formGroup()\"\n [frame]=\"frames()?.get(pipeName())\"\n [frameDefaults]=\"defaultValues()\"\n [(frameTheme)]=\"frameTheme\"\n [configuredTheme]=\"configuration.theme\"\n (frameMessage)=\"onFrameMessage($event)\"\n (frameSilent)=\"onFrameSilent()\">\n </ng-doc-playground-demo>\n }\n </ng-doc-playground-properties>\n}\n", styles: [":host{display:block;margin:var(--ng-doc-playground-margin)}ng-doc-playground-demo:last-child{flex:1 1 auto}ng-doc-playground-demo+ng-doc-playground-demo{border-top:1px solid color-mix(in srgb,var(--ng-doc-border-color) 70%,transparent)}.ng-doc-playground-note{margin:0;padding:var(--ng-doc-space-2) var(--ng-doc-space-3);color:var(--ng-doc-text-muted)}\n"], dependencies: [{ kind: "component", type: NgDocPlaygroundPropertiesComponent, selector: "ng-doc-playground-properties", inputs: ["form", "properties", "ignoreInputs", "dynamicContent", "defaultValues", "waitingForDefaults", "hideSidePanel", "inspectorPosition", "recreateDemo", "recreateLocked", "showResetButton"], outputs: ["recreateDemoChange", "resetForm"] }, { kind: "component", type: NgDocPlaygroundDemoComponent, selector: "ng-doc-playground-demo", inputs: ["id", "pipeName", "selector", "configuration", "properties", "recreateDemo", "form", "expanded", "frame", "frameDefaults", "frameTheme", "configuredTheme"], outputs: ["frameMessage", "frameSilent", "frameThemeChange"] }, { kind: "pipe", type: NgDocAsArrayPipe, name: "asArray" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
607
1139
|
}
|
|
608
1140
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.2.1", ngImport: i0, type: NgDocPlaygroundComponent, decorators: [{
|
|
609
1141
|
type: Component,
|
|
610
|
-
args: [{ selector: 'ng-doc-playground', changeDetection: ChangeDetectionStrategy.OnPush, imports: [NgDocPlaygroundPropertiesComponent, NgDocPlaygroundDemoComponent, NgDocAsArrayPipe], template: "@if (configuration(); as configuration) {\n <ng-doc-playground-properties\n [form]=\"formGroup()!\"\n [properties]=\"properties()\"\n [ignoreInputs]=\"configuration.hiddenInputs\"\n [dynamicContent]=\"configuration.content\"\n [hideSidePanel]=\"configuration.hideSidePanel ?? false\"\n [inspectorPosition]=\"configuration.inspectorPosition ?? 'right'\"\n [defaultValues]=\"defaultValues()\"\n [showResetButton]=\"!isDefaultState()\"\n [(recreateDemo)]=\"recreateDemo\"\n [recreateLocked]=\"recreateLocked()\"\n (resetForm)=\"resetForm()\">\n @for (selector of configuration.selectors ?? selectors() | asArray; track selector) {\n <ng-doc-playground-demo\n [id]=\"id()\"\n [selector]=\"selector\"\n [properties]=\"properties()\"\n [configuration]=\"configuration\"\n [recreateDemo]=\"recreateDemo()\"\n [form]=\"formGroup()\"\n [expanded]=\"configuration.expanded ?? false\">\n </ng-doc-playground-demo>\n }\n @if (pipeName()) {\n <ng-doc-playground-demo\n [id]=\"id()\"\n [pipeName]=\"pipeName()\"\n [properties]=\"properties()\"\n [configuration]=\"configuration\"\n [recreateDemo]=\"recreateDemo()\"\n [form]=\"formGroup()\">\n </ng-doc-playground-demo>\n }\n </ng-doc-playground-properties>\n}\n", styles: [":host{display:block;margin:var(--ng-doc-playground-margin)}ng-doc-playground-demo:last-child{flex:1 1 auto}ng-doc-playground-demo+ng-doc-playground-demo{border-top:1px solid color-mix(in srgb,var(--ng-doc-border-color) 70%,transparent)}\n"] }]
|
|
611
|
-
}], propDecorators: { id: [{ type: i0.Input, args: [{ isSignal: true, alias: "id", required: true }] }], pipeName: [{ type: i0.Input, args: [{ isSignal: true, alias: "pipeName", required: false }] }], selectors: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectors", required: false }] }], properties: [{ type: i0.Input, args: [{ isSignal: true, alias: "properties", required: false }] }], options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: false }] }] } });
|
|
1142
|
+
args: [{ selector: 'ng-doc-playground', changeDetection: ChangeDetectionStrategy.OnPush, imports: [NgDocPlaygroundPropertiesComponent, NgDocPlaygroundDemoComponent, NgDocAsArrayPipe], providers: [{ provide: ɵNG_DOC_PLAYGROUND_CONTAINER, useExisting: NgDocPlaygroundComponent }], template: "@if (configuration(); as configuration) {\n @if (fallbackNote(); as note) {\n <p class=\"ng-doc-playground-note\" role=\"note\">{{ note }}</p>\n }\n <ng-doc-playground-properties\n [form]=\"formGroup()!\"\n [properties]=\"properties()\"\n [ignoreInputs]=\"configuration.hiddenInputs\"\n [dynamicContent]=\"configuration.content\"\n [hideSidePanel]=\"configuration.hideSidePanel ?? false\"\n [inspectorPosition]=\"configuration.inspectorPosition ?? 'right'\"\n [defaultValues]=\"defaultValues()\"\n [waitingForDefaults]=\"isolated()\"\n [showResetButton]=\"!isDefaultState()\"\n [(recreateDemo)]=\"recreateDemo\"\n [recreateLocked]=\"recreateLocked()\"\n (resetForm)=\"resetForm()\">\n @for (selector of configuration.selectors ?? selectors() | asArray; track selector) {\n <ng-doc-playground-demo\n [id]=\"id()\"\n [selector]=\"selector\"\n [properties]=\"properties()\"\n [configuration]=\"configuration\"\n [recreateDemo]=\"recreateDemo()\"\n [form]=\"formGroup()\"\n [expanded]=\"configuration.expanded ?? false\"\n [frame]=\"frames()?.get(selector)\"\n [frameDefaults]=\"defaultValues()\"\n [(frameTheme)]=\"frameTheme\"\n [configuredTheme]=\"configuration.theme\"\n (frameMessage)=\"onFrameMessage($event)\"\n (frameSilent)=\"onFrameSilent()\">\n </ng-doc-playground-demo>\n }\n @if (pipeName()) {\n <ng-doc-playground-demo\n [id]=\"id()\"\n [pipeName]=\"pipeName()\"\n [properties]=\"properties()\"\n [configuration]=\"configuration\"\n [recreateDemo]=\"recreateDemo()\"\n [form]=\"formGroup()\"\n [frame]=\"frames()?.get(pipeName())\"\n [frameDefaults]=\"defaultValues()\"\n [(frameTheme)]=\"frameTheme\"\n [configuredTheme]=\"configuration.theme\"\n (frameMessage)=\"onFrameMessage($event)\"\n (frameSilent)=\"onFrameSilent()\">\n </ng-doc-playground-demo>\n }\n </ng-doc-playground-properties>\n}\n", styles: [":host{display:block;margin:var(--ng-doc-playground-margin)}ng-doc-playground-demo:last-child{flex:1 1 auto}ng-doc-playground-demo+ng-doc-playground-demo{border-top:1px solid color-mix(in srgb,var(--ng-doc-border-color) 70%,transparent)}.ng-doc-playground-note{margin:0;padding:var(--ng-doc-space-2) var(--ng-doc-space-3);color:var(--ng-doc-text-muted)}\n"] }]
|
|
1143
|
+
}], ctorParameters: () => [], propDecorators: { id: [{ type: i0.Input, args: [{ isSignal: true, alias: "id", required: true }] }], pipeName: [{ type: i0.Input, args: [{ isSignal: true, alias: "pipeName", required: false }] }], selectors: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectors", required: false }] }], properties: [{ type: i0.Input, args: [{ isSignal: true, alias: "properties", required: false }] }], options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: false }] }] } });
|
|
612
1144
|
|
|
1145
|
+
const EMPTY_STATE = {
|
|
1146
|
+
inputs: [],
|
|
1147
|
+
properties: {},
|
|
1148
|
+
content: [],
|
|
1149
|
+
recreate: false,
|
|
1150
|
+
};
|
|
613
1151
|
/**
|
|
614
|
-
*
|
|
615
|
-
*
|
|
616
|
-
*
|
|
1152
|
+
* Shows one target of a playground on a page of the demo application, for an isolated playground:
|
|
1153
|
+
* the generated playground class of the route's playground and target, without the inspector,
|
|
1154
|
+
* which stays on the documentation page. The state comes from the URL fragment, and a new
|
|
1155
|
+
* fragment applies a new state without a reload (the documentation page replaces it with
|
|
1156
|
+
* `location.replace`). The page tells the documentation page, with `postMessage`, the target's
|
|
1157
|
+
* default input values once it exists, each state it applied, and what failed.
|
|
1158
|
+
*
|
|
1159
|
+
* Like the demo host, it skips hydration: the server renders the target with its defaults, and
|
|
1160
|
+
* the browser renders it again with the fragment's state, which only the browser has.
|
|
617
1161
|
*/
|
|
618
|
-
class
|
|
619
|
-
static { this.selector = 'unknown'; }
|
|
1162
|
+
class NgDocPlaygroundHostComponent {
|
|
620
1163
|
constructor() {
|
|
621
|
-
/**
|
|
622
|
-
|
|
623
|
-
this.properties = input({}, /* @ts-ignore */
|
|
1164
|
+
/** The target's inputs, by name, from the state: a pipe's parameters are positional. */
|
|
1165
|
+
this.properties = signal(undefined, /* @ts-ignore */
|
|
624
1166
|
...(ngDevMode ? [{ debugName: "properties" }] : /* istanbul ignore next */ []));
|
|
625
|
-
/** The
|
|
626
|
-
this.
|
|
627
|
-
...(ngDevMode ? [{ debugName: "
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
this.
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
this.
|
|
1167
|
+
/** The default values of the target's inputs, reported by the generated class. */
|
|
1168
|
+
this.defaultValues = signal(undefined, /* @ts-ignore */
|
|
1169
|
+
...(ngDevMode ? [{ debugName: "defaultValues" }] : /* istanbul ignore next */ []));
|
|
1170
|
+
this.texts = inject(NG_DOC_TEXTS).playground;
|
|
1171
|
+
this.injector = inject(Injector);
|
|
1172
|
+
this.outlet = viewChild.required('outlet', { ...(ngDevMode ? { debugName: "outlet" } : /* istanbul ignore next */ {}), read: ViewContainerRef });
|
|
1173
|
+
this.window = isPlatformBrowser(inject(PLATFORM_ID))
|
|
1174
|
+
? inject(DOCUMENT).defaultView
|
|
1175
|
+
: null;
|
|
1176
|
+
const data = inject(ActivatedRoute).snapshot.data['ngDocPlayground'];
|
|
1177
|
+
const target = data?.selector ?? data?.pipeName;
|
|
1178
|
+
const token = data ? getPlaygroundDemoToken(data.id) : undefined;
|
|
1179
|
+
const classes = token ? this.injector.get(token, []) : [];
|
|
1180
|
+
this.playground = classes.find((type) => type.selector === target);
|
|
1181
|
+
this.missing = !this.playground;
|
|
1182
|
+
// The router updates the URL after it activates the route, so the fragment is read after
|
|
1183
|
+
// the first render.
|
|
1184
|
+
afterNextRender(() => this.start(this.readState() ?? EMPTY_STATE));
|
|
1185
|
+
const window = this.window;
|
|
1186
|
+
if (window) {
|
|
1187
|
+
const onHashChange = () => {
|
|
1188
|
+
const state = this.readState();
|
|
1189
|
+
// A malformed fragment is reported and leaves the target as it is.
|
|
1190
|
+
if (state)
|
|
1191
|
+
this.apply(state);
|
|
1192
|
+
};
|
|
1193
|
+
window.addEventListener('hashchange', onHashChange);
|
|
1194
|
+
inject(DestroyRef).onDestroy(() => window.removeEventListener('hashchange', onHashChange));
|
|
1195
|
+
}
|
|
640
1196
|
}
|
|
641
|
-
/**
|
|
642
|
-
|
|
643
|
-
|
|
1197
|
+
/**
|
|
1198
|
+
* Creates the target with the first state, and reports its defaults once it has rendered.
|
|
1199
|
+
* @param state
|
|
1200
|
+
*/
|
|
1201
|
+
start(state) {
|
|
1202
|
+
this.properties.set(Object.fromEntries(state.inputs.map((name) => [name, true])));
|
|
1203
|
+
if (!this.playground) {
|
|
1204
|
+
this.post({
|
|
1205
|
+
type: 'playground-error',
|
|
1206
|
+
phase: 'create',
|
|
1207
|
+
message: this.texts.missing,
|
|
1208
|
+
});
|
|
1209
|
+
return;
|
|
1210
|
+
}
|
|
1211
|
+
if (!this.create(state))
|
|
1212
|
+
return;
|
|
1213
|
+
// The generated class reads the target's defaults when it is first checked, which the next
|
|
1214
|
+
// render has done.
|
|
1215
|
+
afterNextRender(() => {
|
|
1216
|
+
const defaults = {};
|
|
1217
|
+
const uncloneable = [];
|
|
1218
|
+
for (const [name, value] of Object.entries(this.defaultValues() ?? {})) {
|
|
1219
|
+
const encoded = _encodePlaygroundValue(value);
|
|
1220
|
+
if (encoded.ok)
|
|
1221
|
+
defaults[name] = encoded.value;
|
|
1222
|
+
else
|
|
1223
|
+
uncloneable.push(name);
|
|
1224
|
+
}
|
|
1225
|
+
this.post({ type: 'playground-ready', defaults, uncloneable });
|
|
1226
|
+
this.applied();
|
|
1227
|
+
}, { injector: this.injector });
|
|
644
1228
|
}
|
|
645
|
-
/**
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
1229
|
+
/**
|
|
1230
|
+
* Creates the target and sets the state; reports a failure instead of throwing.
|
|
1231
|
+
* @param state
|
|
1232
|
+
*/
|
|
1233
|
+
create(state) {
|
|
1234
|
+
if (!this.playground)
|
|
1235
|
+
return false;
|
|
1236
|
+
try {
|
|
1237
|
+
this.reference?.destroy();
|
|
1238
|
+
this.reference = this.outlet().createComponent(this.playground);
|
|
1239
|
+
this.setState(state);
|
|
1240
|
+
return true;
|
|
1241
|
+
}
|
|
1242
|
+
catch (error) {
|
|
1243
|
+
this.post({ type: 'playground-error', phase: 'create', message: describe(error) });
|
|
1244
|
+
return false;
|
|
1245
|
+
}
|
|
649
1246
|
}
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
// Some properties are getters or functions that throw without their context.
|
|
662
|
-
}
|
|
663
|
-
return values;
|
|
664
|
-
}, {});
|
|
1247
|
+
/**
|
|
1248
|
+
* Applies a state to the existing target, or creates it again when the state asks to.
|
|
1249
|
+
* @param state
|
|
1250
|
+
*/
|
|
1251
|
+
apply(state) {
|
|
1252
|
+
if (!this.reference)
|
|
1253
|
+
return;
|
|
1254
|
+
if (state.recreate) {
|
|
1255
|
+
if (this.create(state))
|
|
1256
|
+
this.applied();
|
|
1257
|
+
return;
|
|
665
1258
|
}
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
this.defaultValues = Object.keys(this.playgroundContainer.properties() ?? {}).reduce((values, key, i) => {
|
|
670
|
-
values[key] = defaults[i + 1];
|
|
671
|
-
return values;
|
|
672
|
-
}, {});
|
|
1259
|
+
try {
|
|
1260
|
+
this.setState(state);
|
|
1261
|
+
this.applied();
|
|
673
1262
|
}
|
|
674
|
-
|
|
675
|
-
|
|
1263
|
+
catch (error) {
|
|
1264
|
+
this.post({ type: 'playground-error', phase: 'apply', message: describe(error) });
|
|
676
1265
|
}
|
|
677
|
-
|
|
678
|
-
|
|
1266
|
+
}
|
|
1267
|
+
setState(state) {
|
|
1268
|
+
this.reference?.setInput('properties', state.properties);
|
|
1269
|
+
this.reference?.setInput('actionData', state.data ?? {});
|
|
1270
|
+
this.reference?.setInput('content', Object.fromEntries(state.content.map((slot) => [slot, true])));
|
|
1271
|
+
this.reference?.changeDetectorRef.markForCheck();
|
|
1272
|
+
}
|
|
1273
|
+
/** The state of the URL fragment; a malformed one is reported and reads as `undefined`. */
|
|
1274
|
+
readState() {
|
|
1275
|
+
const fragment = this.window?.location.hash ?? '';
|
|
1276
|
+
if (!fragment || fragment === '#')
|
|
1277
|
+
return EMPTY_STATE;
|
|
1278
|
+
try {
|
|
1279
|
+
return _playgroundStateFromFragment(fragment);
|
|
679
1280
|
}
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
1281
|
+
catch (error) {
|
|
1282
|
+
this.post({ type: 'playground-error', phase: 'apply', message: describe(error) });
|
|
1283
|
+
return undefined;
|
|
1284
|
+
}
|
|
1285
|
+
}
|
|
1286
|
+
applied() {
|
|
1287
|
+
this.post({
|
|
1288
|
+
type: 'playground-applied',
|
|
1289
|
+
fragment: (this.window?.location.hash ?? '').replace(/^#/, ''),
|
|
686
1290
|
});
|
|
687
1291
|
}
|
|
688
|
-
|
|
689
|
-
|
|
1292
|
+
post(message) {
|
|
1293
|
+
const window = this.window;
|
|
1294
|
+
// Only a page of the same origin may receive it: the demo pages are part of the site.
|
|
1295
|
+
if (window && window.parent !== window) {
|
|
1296
|
+
window.parent.postMessage({ source: _NG_DOC_DEMO_MESSAGE_SOURCE, version: 1, ...message }, window.location.origin);
|
|
1297
|
+
}
|
|
1298
|
+
}
|
|
1299
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.2.1", ngImport: i0, type: NgDocPlaygroundHostComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
1300
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.2.1", type: NgDocPlaygroundHostComponent, isStandalone: true, selector: "ng-doc-playground-host", host: { attributes: { "ngSkipHydration": "true" } }, providers: [{ provide: ɵNG_DOC_PLAYGROUND_CONTAINER, useExisting: NgDocPlaygroundHostComponent }], viewQueries: [{ propertyName: "outlet", first: true, predicate: ["outlet"], descendants: true, read: ViewContainerRef, isSignal: true }], ngImport: i0, template: `
|
|
1301
|
+
<ng-container #outlet />
|
|
1302
|
+
@if (missing) {
|
|
1303
|
+
<p role="alert">{{ texts.missing }}</p>
|
|
1304
|
+
}
|
|
1305
|
+
`, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
1306
|
+
}
|
|
1307
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.2.1", ngImport: i0, type: NgDocPlaygroundHostComponent, decorators: [{
|
|
1308
|
+
type: Component,
|
|
1309
|
+
args: [{
|
|
1310
|
+
selector: 'ng-doc-playground-host',
|
|
1311
|
+
template: `
|
|
1312
|
+
<ng-container #outlet />
|
|
1313
|
+
@if (missing) {
|
|
1314
|
+
<p role="alert">{{ texts.missing }}</p>
|
|
1315
|
+
}
|
|
1316
|
+
`,
|
|
1317
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
1318
|
+
host: { ngSkipHydration: 'true' },
|
|
1319
|
+
providers: [{ provide: ɵNG_DOC_PLAYGROUND_CONTAINER, useExisting: NgDocPlaygroundHostComponent }],
|
|
1320
|
+
}]
|
|
1321
|
+
}], ctorParameters: () => [], propDecorators: { outlet: [{ type: i0.ViewChild, args: ['outlet', { ...{ read: ViewContainerRef }, isSignal: true }] }] } });
|
|
1322
|
+
/**
|
|
1323
|
+
*
|
|
1324
|
+
* @param error
|
|
1325
|
+
*/
|
|
1326
|
+
function describe(error) {
|
|
1327
|
+
return error instanceof Error ? error.message : String(error);
|
|
690
1328
|
}
|
|
691
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.2.1", ngImport: i0, type: NgDocBasePlayground, decorators: [{
|
|
692
|
-
type: Directive
|
|
693
|
-
}], ctorParameters: () => [], propDecorators: { properties: [{ type: i0.Input, args: [{ isSignal: true, alias: "properties", required: false }] }], actionData: [{ type: i0.Input, args: [{ isSignal: true, alias: "actionData", required: false }] }], content: [{ type: i0.Input, args: [{ isSignal: true, alias: "content", required: false }] }] } });
|
|
694
1329
|
|
|
695
1330
|
/**
|
|
696
1331
|
* Generated bundle index. Do not edit.
|
|
697
1332
|
*/
|
|
698
1333
|
|
|
699
|
-
export { NgDocBasePlayground, NgDocPlaygroundComponent, NgDocPlaygroundDemoComponent, NgDocPlaygroundPropertiesComponent, NgDocPlaygroundPropertyComponent };
|
|
1334
|
+
export { NgDocBasePlayground, NgDocPlaygroundComponent, NgDocPlaygroundDemoComponent, NgDocPlaygroundHostComponent, NgDocPlaygroundPropertiesComponent, NgDocPlaygroundPropertyComponent, ɵNG_DOC_PLAYGROUND_CONTAINER };
|
|
700
1335
|
//# sourceMappingURL=ng-doc-app-components-playground.mjs.map
|