@ng-doc/app 22.0.0 → 22.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/fesm2022/ng-doc-app-classes-content-controller.mjs +22 -4
- package/fesm2022/ng-doc-app-classes-content-controller.mjs.map +1 -1
- package/fesm2022/ng-doc-app-classes-default-search-engine.mjs +36 -12
- package/fesm2022/ng-doc-app-classes-default-search-engine.mjs.map +1 -1
- package/fesm2022/ng-doc-app-classes-root-page.mjs.map +1 -1
- package/fesm2022/ng-doc-app-components-api-list.mjs +7 -5
- package/fesm2022/ng-doc-app-components-api-list.mjs.map +1 -1
- package/fesm2022/ng-doc-app-components-breadcrumb.mjs +5 -3
- package/fesm2022/ng-doc-app-components-breadcrumb.mjs.map +1 -1
- package/fesm2022/ng-doc-app-components-code.mjs +6 -4
- package/fesm2022/ng-doc-app-components-code.mjs.map +1 -1
- package/fesm2022/ng-doc-app-components-command-palette.mjs +35 -27
- package/fesm2022/ng-doc-app-components-command-palette.mjs.map +1 -1
- package/fesm2022/ng-doc-app-components-copy-button.mjs +6 -4
- package/fesm2022/ng-doc-app-components-copy-button.mjs.map +1 -1
- package/fesm2022/ng-doc-app-components-demo-displayer.mjs +19 -4
- package/fesm2022/ng-doc-app-components-demo-displayer.mjs.map +1 -1
- package/fesm2022/ng-doc-app-components-demo-frame.mjs +279 -0
- package/fesm2022/ng-doc-app-components-demo-frame.mjs.map +1 -0
- package/fesm2022/ng-doc-app-components-demo-pane.mjs +2 -2
- package/fesm2022/ng-doc-app-components-demo-pane.mjs.map +1 -1
- package/fesm2022/ng-doc-app-components-demo.mjs +97 -14
- package/fesm2022/ng-doc-app-components-demo.mjs.map +1 -1
- package/fesm2022/ng-doc-app-components-fullscreen-button.mjs +5 -3
- package/fesm2022/ng-doc-app-components-fullscreen-button.mjs.map +1 -1
- package/fesm2022/ng-doc-app-components-fullscreen-toggle.mjs +4 -2
- package/fesm2022/ng-doc-app-components-fullscreen-toggle.mjs.map +1 -1
- package/fesm2022/ng-doc-app-components-heading-anchor.mjs +5 -3
- package/fesm2022/ng-doc-app-components-heading-anchor.mjs.map +1 -1
- package/fesm2022/ng-doc-app-components-image-viewer.mjs +5 -3
- package/fesm2022/ng-doc-app-components-image-viewer.mjs.map +1 -1
- package/fesm2022/ng-doc-app-components-navbar.mjs +4 -2
- package/fesm2022/ng-doc-app-components-navbar.mjs.map +1 -1
- package/fesm2022/ng-doc-app-components-page-navigation.mjs +5 -3
- package/fesm2022/ng-doc-app-components-page-navigation.mjs.map +1 -1
- package/fesm2022/ng-doc-app-components-page-wrapper.mjs +2 -2
- package/fesm2022/ng-doc-app-components-page-wrapper.mjs.map +1 -1
- package/fesm2022/ng-doc-app-components-page.mjs +83 -16
- package/fesm2022/ng-doc-app-components-page.mjs.map +1 -1
- package/fesm2022/ng-doc-app-components-playground.mjs +838 -141
- package/fesm2022/ng-doc-app-components-playground.mjs.map +1 -1
- package/fesm2022/ng-doc-app-components-root.mjs +51 -4
- package/fesm2022/ng-doc-app-components-root.mjs.map +1 -1
- package/fesm2022/ng-doc-app-components-search-result.mjs +8 -6
- package/fesm2022/ng-doc-app-components-search-result.mjs.map +1 -1
- package/fesm2022/ng-doc-app-components-search.mjs +7 -5
- package/fesm2022/ng-doc-app-components-search.mjs.map +1 -1
- package/fesm2022/ng-doc-app-components-sidebar.mjs +4 -3
- package/fesm2022/ng-doc-app-components-sidebar.mjs.map +1 -1
- package/fesm2022/ng-doc-app-components-tabs.mjs +21 -3
- package/fesm2022/ng-doc-app-components-tabs.mjs.map +1 -1
- package/fesm2022/ng-doc-app-components-theme-toggle.mjs +7 -5
- package/fesm2022/ng-doc-app-components-theme-toggle.mjs.map +1 -1
- package/fesm2022/ng-doc-app-components-toc.mjs +4 -2
- package/fesm2022/ng-doc-app-components-toc.mjs.map +1 -1
- package/fesm2022/ng-doc-app-components.mjs +1 -0
- package/fesm2022/ng-doc-app-components.mjs.map +1 -1
- package/fesm2022/ng-doc-app-constants.mjs +11 -1
- package/fesm2022/ng-doc-app-constants.mjs.map +1 -1
- package/fesm2022/ng-doc-app-demo-app.mjs +779 -0
- package/fesm2022/ng-doc-app-demo-app.mjs.map +1 -0
- package/fesm2022/ng-doc-app-helpers.mjs +147 -7
- package/fesm2022/ng-doc-app-helpers.mjs.map +1 -1
- package/fesm2022/ng-doc-app-processors-processors.mjs +30 -9
- package/fesm2022/ng-doc-app-processors-processors.mjs.map +1 -1
- package/fesm2022/ng-doc-app-providers-ng-doc-app.mjs +29 -18
- package/fesm2022/ng-doc-app-providers-ng-doc-app.mjs.map +1 -1
- package/fesm2022/ng-doc-app-providers-texts.mjs +53 -0
- package/fesm2022/ng-doc-app-providers-texts.mjs.map +1 -0
- package/fesm2022/ng-doc-app-providers-title.mjs +129 -0
- package/fesm2022/ng-doc-app-providers-title.mjs.map +1 -0
- package/fesm2022/ng-doc-app-providers.mjs +2 -0
- package/fesm2022/ng-doc-app-providers.mjs.map +1 -1
- package/fesm2022/ng-doc-app-services-content-scroll-intent.mjs +27 -2
- package/fesm2022/ng-doc-app-services-content-scroll-intent.mjs.map +1 -1
- package/fesm2022/ng-doc-app-services-highlighter.mjs +23 -16
- package/fesm2022/ng-doc-app-services-highlighter.mjs.map +1 -1
- package/fesm2022/ng-doc-app-services-route-preloader.mjs +34 -10
- package/fesm2022/ng-doc-app-services-route-preloader.mjs.map +1 -1
- package/fesm2022/ng-doc-app-services-shortcuts.mjs +38 -4
- package/fesm2022/ng-doc-app-services-shortcuts.mjs.map +1 -1
- package/fesm2022/ng-doc-app-texts-ar.mjs +238 -0
- package/fesm2022/ng-doc-app-texts-ar.mjs.map +1 -0
- package/fesm2022/ng-doc-app-texts-de.mjs +205 -0
- package/fesm2022/ng-doc-app-texts-de.mjs.map +1 -0
- package/fesm2022/ng-doc-app-texts-es.mjs +205 -0
- package/fesm2022/ng-doc-app-texts-es.mjs.map +1 -0
- package/fesm2022/ng-doc-app-texts-fr.mjs +205 -0
- package/fesm2022/ng-doc-app-texts-fr.mjs.map +1 -0
- package/fesm2022/ng-doc-app-texts-hi.mjs +219 -0
- package/fesm2022/ng-doc-app-texts-hi.mjs.map +1 -0
- package/fesm2022/ng-doc-app-texts-ja.mjs +205 -0
- package/fesm2022/ng-doc-app-texts-ja.mjs.map +1 -0
- package/fesm2022/ng-doc-app-texts-ko.mjs +205 -0
- package/fesm2022/ng-doc-app-texts-ko.mjs.map +1 -0
- package/fesm2022/ng-doc-app-texts-pt-br.mjs +205 -0
- package/fesm2022/ng-doc-app-texts-pt-br.mjs.map +1 -0
- package/fesm2022/ng-doc-app-texts-ru.mjs +231 -0
- package/fesm2022/ng-doc-app-texts-ru.mjs.map +1 -0
- package/fesm2022/ng-doc-app-texts-uk.mjs +231 -0
- package/fesm2022/ng-doc-app-texts-uk.mjs.map +1 -0
- package/fesm2022/ng-doc-app-texts-zh.mjs +205 -0
- package/fesm2022/ng-doc-app-texts-zh.mjs.map +1 -0
- package/fesm2022/ng-doc-app-tokens.mjs +214 -1
- package/fesm2022/ng-doc-app-tokens.mjs.map +1 -1
- package/fesm2022/ng-doc-app-type-controls-json-control.mjs +90 -0
- package/fesm2022/ng-doc-app-type-controls-json-control.mjs.map +1 -0
- package/fesm2022/ng-doc-app-type-controls-list-control.mjs +792 -0
- package/fesm2022/ng-doc-app-type-controls-list-control.mjs.map +1 -0
- package/fesm2022/ng-doc-app-type-controls-type-alias-control.mjs +45 -8
- package/fesm2022/ng-doc-app-type-controls-type-alias-control.mjs.map +1 -1
- package/fesm2022/ng-doc-app-type-controls.mjs +2 -0
- package/fesm2022/ng-doc-app-type-controls.mjs.map +1 -1
- package/package.json +73 -4
- package/styles/global.css +4 -0
- package/styles/main.css +4 -0
- package/styles/themes/dark.css +10 -0
- package/types/ng-doc-app-classes-default-search-engine.d.ts +11 -0
- package/types/ng-doc-app-classes-root-page.d.ts +13 -0
- package/types/ng-doc-app-components-api-list.d.ts +3 -1
- package/types/ng-doc-app-components-breadcrumb.d.ts +2 -1
- package/types/ng-doc-app-components-code.d.ts +2 -0
- package/types/ng-doc-app-components-command-palette.d.ts +3 -1
- package/types/ng-doc-app-components-copy-button.d.ts +3 -1
- package/types/ng-doc-app-components-demo-displayer.d.ts +13 -1
- package/types/ng-doc-app-components-demo-frame.d.ts +107 -0
- package/types/ng-doc-app-components-demo.d.ts +47 -10
- package/types/ng-doc-app-components-fullscreen-button.d.ts +2 -0
- package/types/ng-doc-app-components-fullscreen-toggle.d.ts +2 -0
- package/types/ng-doc-app-components-heading-anchor.d.ts +2 -0
- package/types/ng-doc-app-components-image-viewer.d.ts +2 -0
- package/types/ng-doc-app-components-navbar.d.ts +2 -0
- package/types/ng-doc-app-components-page-navigation.d.ts +2 -1
- package/types/ng-doc-app-components-page.d.ts +10 -1
- package/types/ng-doc-app-components-playground.d.ts +260 -42
- package/types/ng-doc-app-components-root.d.ts +18 -0
- package/types/ng-doc-app-components-search-result.d.ts +2 -0
- package/types/ng-doc-app-components-search.d.ts +2 -0
- package/types/ng-doc-app-components-sidebar.d.ts +2 -1
- package/types/ng-doc-app-components-tabs.d.ts +13 -3
- package/types/ng-doc-app-components-theme-toggle.d.ts +4 -1
- package/types/ng-doc-app-components-toc.d.ts +2 -1
- package/types/ng-doc-app-components.d.ts +1 -0
- package/types/ng-doc-app-constants.d.ts +11 -1
- package/types/ng-doc-app-demo-app.d.ts +297 -0
- package/types/ng-doc-app-helpers.d.ts +75 -4
- package/types/ng-doc-app-interfaces.d.ts +635 -6
- package/types/ng-doc-app-processors-processors.d.ts +5 -0
- package/types/ng-doc-app-providers-ng-doc-app.d.ts +7 -1
- package/types/ng-doc-app-providers-texts.d.ts +28 -0
- package/types/ng-doc-app-providers-title.d.ts +59 -0
- package/types/ng-doc-app-providers.d.ts +2 -0
- package/types/ng-doc-app-services-content-scroll-intent.d.ts +11 -1
- package/types/ng-doc-app-services-highlighter.d.ts +15 -6
- package/types/ng-doc-app-services-route-preloader.d.ts +13 -1
- package/types/ng-doc-app-services-shortcuts.d.ts +16 -0
- package/types/ng-doc-app-texts-ar.d.ts +13 -0
- package/types/ng-doc-app-texts-de.d.ts +12 -0
- package/types/ng-doc-app-texts-es.d.ts +12 -0
- package/types/ng-doc-app-texts-fr.d.ts +12 -0
- package/types/ng-doc-app-texts-hi.d.ts +12 -0
- package/types/ng-doc-app-texts-ja.d.ts +12 -0
- package/types/ng-doc-app-texts-ko.d.ts +12 -0
- package/types/ng-doc-app-texts-pt-br.d.ts +12 -0
- package/types/ng-doc-app-texts-ru.d.ts +12 -0
- package/types/ng-doc-app-texts-uk.d.ts +12 -0
- package/types/ng-doc-app-texts-zh.d.ts +12 -0
- package/types/ng-doc-app-tokens.d.ts +21 -2
- package/types/ng-doc-app-type-controls-json-control.d.ts +38 -0
- package/types/ng-doc-app-type-controls-list-control.d.ts +313 -0
- package/types/ng-doc-app-type-controls-type-alias-control.d.ts +28 -5
- package/types/ng-doc-app-type-controls.d.ts +2 -0
|
@@ -1,30 +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';
|
|
13
|
-
import {
|
|
18
|
+
import { NgDocDemoFrameComponent, NgDocDemoThemeButtonComponent } from '@ng-doc/app/components/demo-frame';
|
|
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 { KeyValuePipe } from '@angular/common';
|
|
19
|
-
import { getTokenForType } from '@ng-doc/app/providers/type-control';
|
|
20
|
-
import { extractValueOrThrow } from '@ng-doc/core/helpers/extract-value';
|
|
21
24
|
import { isPresent } from '@ng-doc/core/helpers/is-present';
|
|
22
25
|
import { NgDocKindIconComponent } from '@ng-doc/app/components/kind-icon';
|
|
23
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 }] }] } });
|
|
24
116
|
|
|
25
117
|
/**
|
|
26
118
|
* One demo of a playground: it creates the generated playground class of its selector (or pipe),
|
|
27
|
-
* 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.
|
|
28
122
|
*/
|
|
29
123
|
class NgDocPlaygroundDemoComponent {
|
|
30
124
|
constructor() {
|
|
@@ -52,6 +146,42 @@ class NgDocPlaygroundDemoComponent {
|
|
|
52
146
|
/** Whether the code of the demo is shown. */
|
|
53
147
|
this.expanded = input(false, /* @ts-ignore */
|
|
54
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 */ []));
|
|
55
185
|
/** Where the playground class is created. */
|
|
56
186
|
this.demoOutlet = viewChild('demoOutlet', { ...(ngDevMode ? { debugName: "demoOutlet" } : /* istanbul ignore next */ {}), read: ViewContainerRef });
|
|
57
187
|
this.code = signal('', /* @ts-ignore */
|
|
@@ -61,7 +191,11 @@ class NgDocPlaygroundDemoComponent {
|
|
|
61
191
|
this.pendingTasks = inject(PendingTasks);
|
|
62
192
|
this.unsubscribe$ = new Subject();
|
|
63
193
|
}
|
|
64
|
-
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
|
+
}
|
|
65
199
|
if (form || id) {
|
|
66
200
|
this.unsubscribe$.next();
|
|
67
201
|
const demoInjector = getPlaygroundDemoToken(this.id());
|
|
@@ -69,14 +203,25 @@ class NgDocPlaygroundDemoComponent {
|
|
|
69
203
|
const demos = this.injector.get(demoInjector, []);
|
|
70
204
|
this.playgroundDemo = demos.find((demo) => demo.selector === this.selector() || demo.selector === this.pipeName());
|
|
71
205
|
}
|
|
72
|
-
await this.updateDemo();
|
|
73
206
|
const form = this.form();
|
|
74
|
-
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
|
|
75
216
|
.pipe(startWith(form.value), takeUntil(this.unsubscribe$), takeUntilDestroyed(this.destroyRef))
|
|
76
217
|
.subscribe((data) => this.updateDemo(data));
|
|
77
218
|
}
|
|
78
219
|
}
|
|
79
220
|
async updateDemo(data) {
|
|
221
|
+
if (this.frame()) {
|
|
222
|
+
await this.updateCodeView();
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
80
225
|
if (this.recreateDemo() || !this.demoRef) {
|
|
81
226
|
this.createDemo();
|
|
82
227
|
}
|
|
@@ -125,7 +270,7 @@ class NgDocPlaygroundDemoComponent {
|
|
|
125
270
|
return objectKeys(formData).reduce((result, key) => {
|
|
126
271
|
const inputName = this.properties()?.[key]?.inputName ?? key;
|
|
127
272
|
const value = formData[key];
|
|
128
|
-
const property = this.
|
|
273
|
+
const property = this.defaultValue(key);
|
|
129
274
|
if (property !== value) {
|
|
130
275
|
result[inputName] = stringify(value).replace(/"/g, `'`);
|
|
131
276
|
}
|
|
@@ -138,7 +283,7 @@ class NgDocPlaygroundDemoComponent {
|
|
|
138
283
|
return objectKeys(formData)
|
|
139
284
|
.map((key, i) => {
|
|
140
285
|
const value = formData[key];
|
|
141
|
-
const defaultValue = this.
|
|
286
|
+
const defaultValue = this.defaultValue(key);
|
|
142
287
|
if (defaultValue !== value) {
|
|
143
288
|
changedInputIndex = i;
|
|
144
289
|
}
|
|
@@ -154,16 +299,176 @@ class NgDocPlaygroundDemoComponent {
|
|
|
154
299
|
this.unsubscribe$.next();
|
|
155
300
|
this.unsubscribe$.complete();
|
|
156
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
|
+
}
|
|
157
309
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.2.1", ngImport: i0, type: NgDocPlaygroundDemoComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
158
|
-
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 }); }
|
|
159
311
|
}
|
|
160
312
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.2.1", ngImport: i0, type: NgDocPlaygroundDemoComponent, decorators: [{
|
|
161
313
|
type: Component,
|
|
162
|
-
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"] }]
|
|
163
|
-
}], 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', { ...{
|
|
164
316
|
read: ViewContainerRef,
|
|
165
317
|
}, isSignal: true }] }] } });
|
|
166
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
|
+
|
|
471
|
+
let nextId = 0;
|
|
167
472
|
/** Value types that have a chip colour. */
|
|
168
473
|
const CHIP_TYPES = new Set([
|
|
169
474
|
'string',
|
|
@@ -209,25 +514,35 @@ class NgDocPlaygroundPropertyComponent {
|
|
|
209
514
|
/** The value type shown as a chip under the name, or an empty string. */
|
|
210
515
|
this.typeChip = computed(() => valueType(this.property()), /* @ts-ignore */
|
|
211
516
|
...(ngDevMode ? [{ debugName: "typeChip" }] : /* istanbul ignore next */ []));
|
|
212
|
-
|
|
517
|
+
/** Whether the row is a `<label>` around the control, unless its provider opts out. */
|
|
518
|
+
this.labelWrapper = computed(() => this.option()?.labelWrapper !== false, /* @ts-ignore */
|
|
519
|
+
...(ngDevMode ? [{ debugName: "labelWrapper" }] : /* istanbul ignore next */ []));
|
|
520
|
+
/** ID of the caption that names the control of a row without the `<label>`. */
|
|
521
|
+
this.captionId = `ng-doc-playground-property-caption-${nextId++}`;
|
|
522
|
+
// The outlet is in the `<label>` or in the `<div>` of the row, so the query follows the branch.
|
|
523
|
+
this.propertyOutlet = viewChild('propertyOutlet', { ...(ngDevMode ? { debugName: "propertyOutlet" } : /* istanbul ignore next */ {}), read: ViewContainerRef });
|
|
213
524
|
// A plain effect, not an after-render hook: it runs during change detection on the server
|
|
214
|
-
// too, so server-rendered and prerendered pages include the controls. The outlet is
|
|
215
|
-
//
|
|
525
|
+
// too, so server-rendered and prerendered pages include the controls. The outlet is inside
|
|
526
|
+
// the row's `@if` branch, so the query reports it once the branch renders, and the effect
|
|
527
|
+
// runs again then. It also runs again when an input it reads changes.
|
|
216
528
|
effect(() => {
|
|
529
|
+
const outlet = this.propertyOutlet();
|
|
217
530
|
const property = this.property();
|
|
218
531
|
const typeControl = this.typeControl();
|
|
219
532
|
const control = this.control();
|
|
220
533
|
const defaultValue = this.defaultValue();
|
|
221
|
-
|
|
534
|
+
if (outlet) {
|
|
535
|
+
untracked(() => this.createControl(outlet, property, typeControl, control, defaultValue));
|
|
536
|
+
}
|
|
222
537
|
});
|
|
223
538
|
inject(DestroyRef).onDestroy(() => this.propertyTypeControl?.destroy());
|
|
224
539
|
}
|
|
225
|
-
createControl(property, typeControl, formControl, defaultValue) {
|
|
540
|
+
createControl(outlet, property, typeControl, formControl, defaultValue) {
|
|
226
541
|
if (!property || !typeControl) {
|
|
227
542
|
return;
|
|
228
543
|
}
|
|
229
544
|
this.propertyTypeControl?.destroy();
|
|
230
|
-
const control =
|
|
545
|
+
const control = outlet.createComponent(typeControl.control);
|
|
231
546
|
this.propertyTypeControl = control;
|
|
232
547
|
setTypeControlField(control, 'name', this.name());
|
|
233
548
|
setTypeControlField(control, 'description', this.tooltipContent());
|
|
@@ -243,7 +558,7 @@ class NgDocPlaygroundPropertyComponent {
|
|
|
243
558
|
this.hasPropertyControl.set(true);
|
|
244
559
|
}
|
|
245
560
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.2.1", ngImport: i0, type: NgDocPlaygroundPropertyComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
246
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.2.1", type: NgDocPlaygroundPropertyComponent, isStandalone: true, selector: "ng-doc-playground-property", inputs: { name: { classPropertyName: "name", publicName: "name", isSignal: true, isRequired: false, transformFunction: null }, property: { classPropertyName: "property", publicName: "property", isSignal: true, isRequired: false, transformFunction: null }, typeControl: { classPropertyName: "typeControl", publicName: "typeControl", isSignal: true, isRequired: false, transformFunction: null }, control: { classPropertyName: "control", publicName: "control", isSignal: true, isRequired: false, transformFunction: null }, defaultValue: { classPropertyName: "defaultValue", publicName: "defaultValue", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "attr.data-has-property-control": "hasPropertyControl()" } }, viewQueries: [{ propertyName: "propertyOutlet", first: true, predicate: ["propertyOutlet"], descendants: true, read: ViewContainerRef, isSignal: true }], ngImport: i0, template: "<label\n
|
|
561
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.2.1", type: NgDocPlaygroundPropertyComponent, isStandalone: true, selector: "ng-doc-playground-property", inputs: { name: { classPropertyName: "name", publicName: "name", isSignal: true, isRequired: false, transformFunction: null }, property: { classPropertyName: "property", publicName: "property", isSignal: true, isRequired: false, transformFunction: null }, typeControl: { classPropertyName: "typeControl", publicName: "typeControl", isSignal: true, isRequired: false, transformFunction: null }, control: { classPropertyName: "control", publicName: "control", isSignal: true, isRequired: false, transformFunction: null }, defaultValue: { classPropertyName: "defaultValue", publicName: "defaultValue", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "attr.data-has-property-control": "hasPropertyControl()" } }, viewQueries: [{ propertyName: "propertyOutlet", first: true, predicate: ["propertyOutlet"], descendants: true, read: ViewContainerRef, isSignal: true }], ngImport: i0, template: "@if (labelWrapper()) {\n <label\n class=\"ng-doc-playground-property-label\"\n [class.with-label]=\"!option()?.hideLabel\"\n [ng-doc-label]=\"option()?.hideLabel ? '' : labelContent\">\n <ng-container #propertyOutlet></ng-container>\n </label>\n} @else {\n <!-- A control with several interactive parts: a label would forward clicks on them to the first\n of them, so the caption names the control through aria-labelledby instead. -->\n <div\n class=\"ng-doc-playground-property-label ng-doc-playground-property-group\"\n role=\"group\"\n [class.with-label]=\"!option()?.hideLabel\"\n [attr.aria-labelledby]=\"option()?.hideLabel ? null : captionId\">\n @if (!option()?.hideLabel) {\n <span class=\"ng-doc-label\" [id]=\"captionId\">\n <ng-container [ngTemplateOutlet]=\"labelContent\"></ng-container>\n </span>\n }\n <span class=\"ng-doc-content\">\n <ng-container #propertyOutlet></ng-container>\n </span>\n </div>\n}\n<ng-template #labelContent>\n <span\n class=\"ng-doc-playground-property-name\"\n [ngDocTooltip]=\"tooltipTemplate\"\n [canOpen]=\"!!tooltipContent()\"\n [positions]=\"['left-center', 'top-right', 'bottom-right']\"\n >{{ name() }}</span\n >\n @if (typeChip(); as type) {\n <ng-doc-kind-icon [kind]=\"type\" type=\"type\"></ng-doc-kind-icon>\n }\n <ng-template #tooltipTemplate>\n <div [innerHTML]=\"tooltipContent() | ngDocSanitizeHtml\"></div>\n </ng-template>\n</ng-template>\n", styles: [":host{display:block}:host[data-has-property-control=false]{display:none}.ng-doc-playground-property-group{display:flex;flex-direction:column;align-items:flex-start}.ng-doc-playground-property-group>.ng-doc-content{display:flex;flex-direction:column;align-items:flex-start;width:100%}.ng-doc-playground-property-label.with-label{display:grid;grid-template-columns:84px minmax(0,1fr);align-items:center;gap:var(--ng-doc-space-3)}.ng-doc-playground-property-label.with-label ::ng-deep>.ng-doc-label{display:grid;justify-items:start;gap:var(--ng-doc-space-1);min-width:0;margin:0}.ng-doc-playground-property-label.with-label ::ng-deep>.ng-doc-content{width:100%;min-width:0}.ng-doc-playground-property-name{max-width:100%;overflow:hidden;text-overflow:ellipsis;font-family:var(--ng-doc-code-font);font-size:13px;line-height:18px;font-weight:500;color:var(--ng-doc-heading-color)}\n"], dependencies: [{ kind: "component", type: NgDocLabelComponent, selector: "label[ng-doc-label]", inputs: ["ng-doc-label", "align"] }, { 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: NgDocKindIconComponent, selector: "ng-doc-kind-icon", inputs: ["kind", "type", "size"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "pipe", type: NgDocSanitizeHtmlPipe, name: "ngDocSanitizeHtml" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
247
562
|
}
|
|
248
563
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.2.1", ngImport: i0, type: NgDocPlaygroundPropertyComponent, decorators: [{
|
|
249
564
|
type: Component,
|
|
@@ -252,15 +567,14 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.2.1", ngImpor
|
|
|
252
567
|
NgDocTooltipDirective,
|
|
253
568
|
NgDocSanitizeHtmlPipe,
|
|
254
569
|
NgDocKindIconComponent,
|
|
570
|
+
NgTemplateOutlet,
|
|
255
571
|
], host: {
|
|
256
572
|
'[attr.data-has-property-control]': 'hasPropertyControl()',
|
|
257
|
-
}, template: "<label\n
|
|
258
|
-
}], 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', { ...{
|
|
259
|
-
read: ViewContainerRef,
|
|
260
|
-
}, isSignal: true }] }] } });
|
|
573
|
+
}, template: "@if (labelWrapper()) {\n <label\n class=\"ng-doc-playground-property-label\"\n [class.with-label]=\"!option()?.hideLabel\"\n [ng-doc-label]=\"option()?.hideLabel ? '' : labelContent\">\n <ng-container #propertyOutlet></ng-container>\n </label>\n} @else {\n <!-- A control with several interactive parts: a label would forward clicks on them to the first\n of them, so the caption names the control through aria-labelledby instead. -->\n <div\n class=\"ng-doc-playground-property-label ng-doc-playground-property-group\"\n role=\"group\"\n [class.with-label]=\"!option()?.hideLabel\"\n [attr.aria-labelledby]=\"option()?.hideLabel ? null : captionId\">\n @if (!option()?.hideLabel) {\n <span class=\"ng-doc-label\" [id]=\"captionId\">\n <ng-container [ngTemplateOutlet]=\"labelContent\"></ng-container>\n </span>\n }\n <span class=\"ng-doc-content\">\n <ng-container #propertyOutlet></ng-container>\n </span>\n </div>\n}\n<ng-template #labelContent>\n <span\n class=\"ng-doc-playground-property-name\"\n [ngDocTooltip]=\"tooltipTemplate\"\n [canOpen]=\"!!tooltipContent()\"\n [positions]=\"['left-center', 'top-right', 'bottom-right']\"\n >{{ name() }}</span\n >\n @if (typeChip(); as type) {\n <ng-doc-kind-icon [kind]=\"type\" type=\"type\"></ng-doc-kind-icon>\n }\n <ng-template #tooltipTemplate>\n <div [innerHTML]=\"tooltipContent() | ngDocSanitizeHtml\"></div>\n </ng-template>\n</ng-template>\n", styles: [":host{display:block}:host[data-has-property-control=false]{display:none}.ng-doc-playground-property-group{display:flex;flex-direction:column;align-items:flex-start}.ng-doc-playground-property-group>.ng-doc-content{display:flex;flex-direction:column;align-items:flex-start;width:100%}.ng-doc-playground-property-label.with-label{display:grid;grid-template-columns:84px minmax(0,1fr);align-items:center;gap:var(--ng-doc-space-3)}.ng-doc-playground-property-label.with-label ::ng-deep>.ng-doc-label{display:grid;justify-items:start;gap:var(--ng-doc-space-1);min-width:0;margin:0}.ng-doc-playground-property-label.with-label ::ng-deep>.ng-doc-content{width:100%;min-width:0}.ng-doc-playground-property-name{max-width:100%;overflow:hidden;text-overflow:ellipsis;font-family:var(--ng-doc-code-font);font-size:13px;line-height:18px;font-weight:500;color:var(--ng-doc-heading-color)}\n"] }]
|
|
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 }] }] } });
|
|
261
575
|
/**
|
|
262
576
|
* The value type of an input: its declared type when it is a primitive, else the type of the first
|
|
263
|
-
* value of its type alias. Content slots are booleans.
|
|
577
|
+
* value of its type alias; none for an array. Content slots are booleans.
|
|
264
578
|
* @param property - The input or the content slot.
|
|
265
579
|
*/
|
|
266
580
|
function valueType(property) {
|
|
@@ -273,10 +587,15 @@ function valueType(property) {
|
|
|
273
587
|
if (CHIP_TYPES.has(property.type)) {
|
|
274
588
|
return property.type;
|
|
275
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
|
+
}
|
|
276
595
|
const option = property.options?.[0];
|
|
277
596
|
if (option !== undefined) {
|
|
278
597
|
try {
|
|
279
|
-
const type = typeof (property.isManual
|
|
598
|
+
const type = typeof resolvePlaygroundOption(option, property.isManual).value;
|
|
280
599
|
return CHIP_TYPES.has(type) ? type : '';
|
|
281
600
|
}
|
|
282
601
|
catch {
|
|
@@ -313,6 +632,7 @@ const INPUT_ORDER = new Intl.Collator('en');
|
|
|
313
632
|
*/
|
|
314
633
|
class NgDocPlaygroundPropertiesComponent {
|
|
315
634
|
constructor() {
|
|
635
|
+
this.texts = inject(NG_DOC_TEXTS).playground;
|
|
316
636
|
this.injector = inject(Injector);
|
|
317
637
|
/** The form of the playground. */
|
|
318
638
|
this.form = input.required(/* @ts-ignore */
|
|
@@ -329,6 +649,12 @@ class NgDocPlaygroundPropertiesComponent {
|
|
|
329
649
|
/** Default values of the inputs; the inspector renders once they are known. */
|
|
330
650
|
this.defaultValues = input(undefined, /* @ts-ignore */
|
|
331
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 */ []));
|
|
332
658
|
/** Whether the inspector is hidden, leaving the demos only. */
|
|
333
659
|
this.hideSidePanel = input(false, /* @ts-ignore */
|
|
334
660
|
...(ngDevMode ? [{ debugName: "hideSidePanel" }] : /* istanbul ignore next */ []));
|
|
@@ -338,6 +664,9 @@ class NgDocPlaygroundPropertiesComponent {
|
|
|
338
664
|
/** Whether the demo is recreated each time an input changes. */
|
|
339
665
|
this.recreateDemo = model(false, /* @ts-ignore */
|
|
340
666
|
...(ngDevMode ? [{ debugName: "recreateDemo" }] : /* istanbul ignore next */ []));
|
|
667
|
+
/** Whether the Recreate setting is fixed by the playground's options and hidden. */
|
|
668
|
+
this.recreateLocked = input(false, /* @ts-ignore */
|
|
669
|
+
...(ngDevMode ? [{ debugName: "recreateLocked" }] : /* istanbul ignore next */ []));
|
|
341
670
|
/** Whether the Reset button is shown. */
|
|
342
671
|
this.showResetButton = input(false, /* @ts-ignore */
|
|
343
672
|
...(ngDevMode ? [{ debugName: "showResetButton" }] : /* istanbul ignore next */ []));
|
|
@@ -358,22 +687,27 @@ class NgDocPlaygroundPropertiesComponent {
|
|
|
358
687
|
return typeControl ? { propertyName: String(key), property, typeControl } : null;
|
|
359
688
|
})
|
|
360
689
|
.filter(isPresent)
|
|
361
|
-
.sort((a, b) =>
|
|
362
|
-
const aOrder = a.typeControl.options?.order;
|
|
363
|
-
const bOrder = b.typeControl.options?.order;
|
|
364
|
-
if (isPresent(aOrder) && isPresent(bOrder)) {
|
|
365
|
-
return aOrder - bOrder;
|
|
366
|
-
}
|
|
367
|
-
if (isPresent(aOrder)) {
|
|
368
|
-
return -1;
|
|
369
|
-
}
|
|
370
|
-
if (isPresent(bOrder)) {
|
|
371
|
-
return 1;
|
|
372
|
-
}
|
|
373
|
-
return INPUT_ORDER.compare(a.property.inputName, b.property.inputName);
|
|
374
|
-
});
|
|
690
|
+
.sort((a, b) => compareOrder(a.property.order, b.property.order) ?? compareByTypeControl(a, b));
|
|
375
691
|
}, /* @ts-ignore */
|
|
376
692
|
...(ngDevMode ? [{ debugName: "propertyControls" }] : /* istanbul ignore next */ []));
|
|
693
|
+
/**
|
|
694
|
+
* The controls in sections: the inputs without a group first, then a section per group, in the
|
|
695
|
+
* order of the group's first input.
|
|
696
|
+
*/
|
|
697
|
+
this.propertySections = computed(() => {
|
|
698
|
+
const sections = new Map([
|
|
699
|
+
[undefined, []],
|
|
700
|
+
]);
|
|
701
|
+
for (const control of this.propertyControls()) {
|
|
702
|
+
const group = control.property.group || undefined;
|
|
703
|
+
sections.set(group, [...(sections.get(group) ?? []), control]);
|
|
704
|
+
}
|
|
705
|
+
return Array.from(sections, ([group, controls]) => ({
|
|
706
|
+
title: group ?? this.texts.settings,
|
|
707
|
+
controls,
|
|
708
|
+
})).filter((section) => section.controls.length > 0);
|
|
709
|
+
}, /* @ts-ignore */
|
|
710
|
+
...(ngDevMode ? [{ debugName: "propertySections" }] : /* istanbul ignore next */ []));
|
|
377
711
|
/** The control of content slots. */
|
|
378
712
|
this.contentTypeControl = this.getControlForType('boolean');
|
|
379
713
|
}
|
|
@@ -388,9 +722,19 @@ class NgDocPlaygroundPropertiesComponent {
|
|
|
388
722
|
getTypeControl(property) {
|
|
389
723
|
const type = property.type;
|
|
390
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.
|
|
391
730
|
const typeControl = this.getControlForType(type) ??
|
|
392
731
|
(primitive ? this.getControlForType(primitive) : undefined) ??
|
|
393
|
-
|
|
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');
|
|
394
738
|
if (!typeControl && isDevMode()) {
|
|
395
739
|
console.warn(`NgDocPlayground didn't find the control for the @Input "${property.inputName}", the type "${type}" was not recognized'`);
|
|
396
740
|
}
|
|
@@ -400,27 +744,38 @@ class NgDocPlaygroundPropertiesComponent {
|
|
|
400
744
|
const token = getTokenForType(type);
|
|
401
745
|
return token ? this.injector.get(token) : undefined;
|
|
402
746
|
}
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
if (optionsIsValid) {
|
|
416
|
-
const token = getTokenForType('NgDocTypeAlias');
|
|
417
|
-
return token ? this.injector.get(token) : undefined;
|
|
418
|
-
}
|
|
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;
|
|
419
759
|
}
|
|
420
|
-
|
|
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);
|
|
421
776
|
}
|
|
422
777
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.2.1", ngImport: i0, type: NgDocPlaygroundPropertiesComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
423
|
-
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 }, 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 <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 <div class=\"ng-doc-playground-divider\"></div>\n @if (propertyControls().length) {\n <h5 class=\"ng-doc-title\">Settings</h5>\n <div class=\"ng-doc-playground-property-list\">\n @for (propertyControl of propertyControls(); track propertyControl.propertyName) {\n <ng-doc-playground-property\n [name]=\"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 }); }
|
|
424
779
|
}
|
|
425
780
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.2.1", ngImport: i0, type: NgDocPlaygroundPropertiesComponent, decorators: [{
|
|
426
781
|
type: Component,
|
|
@@ -433,17 +788,58 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.2.1", ngImpor
|
|
|
433
788
|
KeyValuePipe,
|
|
434
789
|
NgDocBindPipe,
|
|
435
790
|
NgDocExecutePipe,
|
|
436
|
-
], template: "@if (defaultValues()
|
|
437
|
-
}], 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"] }], 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"] }] } });
|
|
793
|
+
/**
|
|
794
|
+
* Compares the `order` of two inputs from the playground's `controls`: inputs with one come first,
|
|
795
|
+
* lowest first.
|
|
796
|
+
* @param a - The first input's order.
|
|
797
|
+
* @param b - The second input's order.
|
|
798
|
+
* @returns The comparison, or `undefined` when neither input has an order or both have the same.
|
|
799
|
+
*/
|
|
800
|
+
function compareOrder(a, b) {
|
|
801
|
+
if (isPresent(a) && isPresent(b)) {
|
|
802
|
+
return a - b || undefined;
|
|
803
|
+
}
|
|
804
|
+
if (isPresent(a)) {
|
|
805
|
+
return -1;
|
|
806
|
+
}
|
|
807
|
+
if (isPresent(b)) {
|
|
808
|
+
return 1;
|
|
809
|
+
}
|
|
810
|
+
return undefined;
|
|
811
|
+
}
|
|
812
|
+
/**
|
|
813
|
+
* Compares two inputs by the order of their type controls: controls with an order first, lowest
|
|
814
|
+
* first (equal orders keep the inputs' order), then the others by name.
|
|
815
|
+
* @param a - The first input.
|
|
816
|
+
* @param b - The second input.
|
|
817
|
+
*/
|
|
818
|
+
function compareByTypeControl(a, b) {
|
|
819
|
+
const aOrder = a.typeControl.options?.order;
|
|
820
|
+
const bOrder = b.typeControl.options?.order;
|
|
821
|
+
if (isPresent(aOrder) && isPresent(bOrder)) {
|
|
822
|
+
return aOrder - bOrder;
|
|
823
|
+
}
|
|
824
|
+
if (isPresent(aOrder)) {
|
|
825
|
+
return -1;
|
|
826
|
+
}
|
|
827
|
+
if (isPresent(bOrder)) {
|
|
828
|
+
return 1;
|
|
829
|
+
}
|
|
830
|
+
return INPUT_ORDER.compare(a.property.inputName, b.property.inputName);
|
|
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']);
|
|
438
834
|
/** The types whose control also edits an optional or nullable input of the type. */
|
|
439
835
|
const PRIMITIVE_TYPES = new Set(['string', 'number', 'boolean']);
|
|
440
836
|
/**
|
|
441
837
|
* The primitive of an optional or nullable primitive input type, such as `string` of
|
|
442
838
|
* `string | undefined` (an optional `@Input() label?: string`, `input<string>()` or
|
|
443
839
|
* `model<string>()`) or of `number | null`, so that the input gets the primitive's control.
|
|
444
|
-
* Other types are matched by their exact name only
|
|
445
|
-
* does not receive the `undefined` of a
|
|
446
|
-
* 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.
|
|
447
843
|
* @param type - The input type as the builder printed it.
|
|
448
844
|
* @returns The primitive, or `undefined` when the type is not one.
|
|
449
845
|
*/
|
|
@@ -458,11 +854,18 @@ function optionalPrimitive(type) {
|
|
|
458
854
|
/**
|
|
459
855
|
* A playground: an inspector with a control per input and content slot of its target, and a demo
|
|
460
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.
|
|
461
863
|
*/
|
|
462
864
|
class NgDocPlaygroundComponent {
|
|
463
865
|
constructor() {
|
|
464
866
|
this.rootPage = inject(NgDocRootPage);
|
|
465
867
|
this.formBuilder = inject(FormBuilder);
|
|
868
|
+
this.texts = inject(NG_DOC_TEXTS).playground;
|
|
466
869
|
/** The name of the playground in the page configuration. */
|
|
467
870
|
this.id = input.required(/* @ts-ignore */
|
|
468
871
|
...(ngDevMode ? [{ debugName: "id" }] : /* istanbul ignore next */ []));
|
|
@@ -478,9 +881,6 @@ class NgDocPlaygroundComponent {
|
|
|
478
881
|
/** Options of the playground action; they extend the playground configuration. */
|
|
479
882
|
this.options = input({}, /* @ts-ignore */
|
|
480
883
|
...(ngDevMode ? [{ debugName: "options" }] : /* istanbul ignore next */ []));
|
|
481
|
-
/** Whether the demo is created again each time a value changes. */
|
|
482
|
-
this.recreateDemo = signal(false, /* @ts-ignore */
|
|
483
|
-
...(ngDevMode ? [{ debugName: "recreateDemo" }] : /* istanbul ignore next */ []));
|
|
484
884
|
/** The values of the inputs and content slots. */
|
|
485
885
|
this.formGroup = signal(undefined, /* @ts-ignore */
|
|
486
886
|
...(ngDevMode ? [{ debugName: "formGroup" }] : /* istanbul ignore next */ []));
|
|
@@ -490,13 +890,183 @@ class NgDocPlaygroundComponent {
|
|
|
490
890
|
/** The playground configuration of the page, extended by the options. */
|
|
491
891
|
this.configuration = computed(() => Object.assign({}, this.rootPage.page?.playgrounds?.[this.id()], this.options()), /* @ts-ignore */
|
|
492
892
|
...(ngDevMode ? [{ debugName: "configuration" }] : /* istanbul ignore next */ []));
|
|
893
|
+
/**
|
|
894
|
+
* Whether the demo is created again each time a value changes. It starts from the `recreate`
|
|
895
|
+
* option; the reader can change it unless the option is `'always'`.
|
|
896
|
+
*/
|
|
897
|
+
this.recreateDemo = linkedSignal(() => !!this.configuration().recreate, /* @ts-ignore */
|
|
898
|
+
...(ngDevMode ? [{ debugName: "recreateDemo" }] : /* istanbul ignore next */ []));
|
|
899
|
+
/** Whether the Recreate setting is fixed on, and hidden, by the `'always'` option. */
|
|
900
|
+
this.recreateLocked = computed(() => this.configuration().recreate === 'always', /* @ts-ignore */
|
|
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 */ []));
|
|
493
952
|
this.defaultProperties = {};
|
|
494
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);
|
|
495
1056
|
}
|
|
496
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() {
|
|
497
1065
|
this.defaultProperties = this.getPropertiesFormValues();
|
|
498
1066
|
this.defaultContent = this.getContentFormValues();
|
|
499
|
-
|
|
1067
|
+
// `FormBuilder` reads an array as `[value, validators]`, so each value is wrapped: the default
|
|
1068
|
+
// of an array input is the value of its control.
|
|
1069
|
+
const propertiesForm = this.formBuilder.group(Object.fromEntries(Object.entries(this.defaultProperties).map(([key, value]) => [key, [value]])));
|
|
500
1070
|
const contentForm = this.formBuilder.group(this.defaultContent);
|
|
501
1071
|
const formGroup = this.formBuilder.group({
|
|
502
1072
|
properties: propertiesForm,
|
|
@@ -508,6 +1078,30 @@ class NgDocPlaygroundComponent {
|
|
|
508
1078
|
content: this.defaultContent,
|
|
509
1079
|
});
|
|
510
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}`;
|
|
511
1105
|
}
|
|
512
1106
|
isDefaultState() {
|
|
513
1107
|
const formGroup = this.formGroup();
|
|
@@ -541,98 +1135,201 @@ class NgDocPlaygroundComponent {
|
|
|
541
1135
|
});
|
|
542
1136
|
}
|
|
543
1137
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.2.1", ngImport: i0, type: NgDocPlaygroundComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
544
|
-
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 (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", "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 }); }
|
|
545
1139
|
}
|
|
546
1140
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.2.1", ngImport: i0, type: NgDocPlaygroundComponent, decorators: [{
|
|
547
1141
|
type: Component,
|
|
548
|
-
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 (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"] }]
|
|
549
|
-
}], 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 }] }] } });
|
|
550
1144
|
|
|
1145
|
+
const EMPTY_STATE = {
|
|
1146
|
+
inputs: [],
|
|
1147
|
+
properties: {},
|
|
1148
|
+
content: [],
|
|
1149
|
+
recreate: false,
|
|
1150
|
+
};
|
|
551
1151
|
/**
|
|
552
|
-
*
|
|
553
|
-
*
|
|
554
|
-
*
|
|
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.
|
|
555
1161
|
*/
|
|
556
|
-
class
|
|
557
|
-
static { this.selector = 'unknown'; }
|
|
1162
|
+
class NgDocPlaygroundHostComponent {
|
|
558
1163
|
constructor() {
|
|
559
|
-
/**
|
|
560
|
-
|
|
561
|
-
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 */
|
|
562
1166
|
...(ngDevMode ? [{ debugName: "properties" }] : /* istanbul ignore next */ []));
|
|
563
|
-
/** The
|
|
564
|
-
this.
|
|
565
|
-
...(ngDevMode ? [{ debugName: "
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
this.
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
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
|
+
}
|
|
578
1196
|
}
|
|
579
|
-
/**
|
|
580
|
-
|
|
581
|
-
|
|
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 });
|
|
582
1228
|
}
|
|
583
|
-
/**
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
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
|
+
}
|
|
587
1246
|
}
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
// Some properties are getters or functions that throw without their context.
|
|
600
|
-
}
|
|
601
|
-
return values;
|
|
602
|
-
}, {});
|
|
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;
|
|
603
1258
|
}
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
this.defaultValues = Object.keys(this.playgroundContainer.properties() ?? {}).reduce((values, key, i) => {
|
|
608
|
-
values[key] = defaults[i + 1];
|
|
609
|
-
return values;
|
|
610
|
-
}, {});
|
|
1259
|
+
try {
|
|
1260
|
+
this.setState(state);
|
|
1261
|
+
this.applied();
|
|
611
1262
|
}
|
|
612
|
-
|
|
613
|
-
|
|
1263
|
+
catch (error) {
|
|
1264
|
+
this.post({ type: 'playground-error', phase: 'apply', message: describe(error) });
|
|
614
1265
|
}
|
|
615
|
-
|
|
616
|
-
|
|
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);
|
|
617
1280
|
}
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
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(/^#/, ''),
|
|
624
1290
|
});
|
|
625
1291
|
}
|
|
626
|
-
|
|
627
|
-
|
|
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);
|
|
628
1328
|
}
|
|
629
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.2.1", ngImport: i0, type: NgDocBasePlayground, decorators: [{
|
|
630
|
-
type: Directive
|
|
631
|
-
}], 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 }] }] } });
|
|
632
1329
|
|
|
633
1330
|
/**
|
|
634
1331
|
* Generated bundle index. Do not edit.
|
|
635
1332
|
*/
|
|
636
1333
|
|
|
637
|
-
export { NgDocBasePlayground, NgDocPlaygroundComponent, NgDocPlaygroundDemoComponent, NgDocPlaygroundPropertiesComponent, NgDocPlaygroundPropertyComponent };
|
|
1334
|
+
export { NgDocBasePlayground, NgDocPlaygroundComponent, NgDocPlaygroundDemoComponent, NgDocPlaygroundHostComponent, NgDocPlaygroundPropertiesComponent, NgDocPlaygroundPropertyComponent, ɵNG_DOC_PLAYGROUND_CONTAINER };
|
|
638
1335
|
//# sourceMappingURL=ng-doc-app-components-playground.mjs.map
|