@storybook/angular 9.0.0-beta.1 → 9.0.0-beta.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. package/dist/builders/build-storybook/index.d.ts +2 -16
  2. package/dist/builders/build-storybook/index.mjs +78 -0
  3. package/dist/builders/build-storybook/index.spec.mjs +187 -0
  4. package/dist/builders/build-storybook/schema.json +2 -4
  5. package/dist/builders/start-storybook/index.d.ts +2 -14
  6. package/dist/builders/start-storybook/index.mjs +99 -0
  7. package/dist/builders/start-storybook/index.spec.mjs +186 -0
  8. package/dist/builders/start-storybook/schema.json +3 -6
  9. package/dist/builders/utils/error-handler.mjs +33 -0
  10. package/dist/builders/utils/run-compodoc.mjs +31 -0
  11. package/dist/builders/utils/run-compodoc.spec.mjs +74 -0
  12. package/dist/builders/utils/standalone-options.mjs +1 -0
  13. package/dist/client/angular-beta/AbstractRenderer.mjs +164 -0
  14. package/dist/client/angular-beta/CanvasRenderer.mjs +9 -0
  15. package/dist/client/angular-beta/ComputesTemplateFromComponent.mjs +154 -0
  16. package/dist/client/angular-beta/ComputesTemplateFromComponent.test.mjs +728 -0
  17. package/dist/client/angular-beta/DocsRenderer.mjs +35 -0
  18. package/dist/client/angular-beta/RendererFactory.mjs +50 -0
  19. package/dist/client/angular-beta/RendererFactory.test.mjs +233 -0
  20. package/dist/client/angular-beta/StorybookModule.mjs +23 -0
  21. package/dist/client/angular-beta/StorybookModule.test.mjs +319 -0
  22. package/dist/client/angular-beta/StorybookProvider.mjs +22 -0
  23. package/dist/client/angular-beta/StorybookWrapperComponent.mjs +123 -0
  24. package/dist/client/angular-beta/__testfixtures__/input.component.mjs +73 -0
  25. package/dist/client/angular-beta/__testfixtures__/test.module.mjs +17 -0
  26. package/dist/client/angular-beta/utils/BootstrapQueue.mjs +49 -0
  27. package/dist/client/angular-beta/utils/BootstrapQueue.test.mjs +162 -0
  28. package/dist/client/angular-beta/utils/NgComponentAnalyzer.mjs +84 -0
  29. package/dist/client/angular-beta/utils/NgComponentAnalyzer.test.mjs +386 -0
  30. package/dist/client/angular-beta/utils/NgModulesAnalyzer.mjs +37 -0
  31. package/dist/client/angular-beta/utils/NgModulesAnalyzer.test.mjs +22 -0
  32. package/dist/client/angular-beta/utils/PropertyExtractor.mjs +158 -0
  33. package/dist/client/angular-beta/utils/PropertyExtractor.test.mjs +175 -0
  34. package/dist/client/angular-beta/utils/StoryUID.mjs +38 -0
  35. package/dist/client/argsToTemplate.mjs +55 -0
  36. package/dist/client/argsToTemplate.test.mjs +100 -0
  37. package/dist/client/config.mjs +4 -0
  38. package/dist/client/decorateStory.mjs +45 -0
  39. package/dist/client/decorateStory.test.mjs +301 -0
  40. package/dist/client/decorators.mjs +63 -0
  41. package/dist/client/decorators.test.mjs +157 -0
  42. package/dist/client/docs/__testfixtures__/doc-button/input.mjs +201 -0
  43. package/dist/client/docs/angular-properties.test.mjs +34 -0
  44. package/dist/client/docs/compodoc.mjs +244 -0
  45. package/dist/client/docs/compodoc.test.mjs +130 -0
  46. package/dist/client/docs/config.mjs +16 -0
  47. package/dist/client/docs/index.mjs +1 -0
  48. package/dist/client/docs/sourceDecorator.mjs +48 -0
  49. package/dist/client/docs/types.mjs +1 -0
  50. package/dist/client/globals.mjs +31 -0
  51. package/dist/client/index.mjs +9 -0
  52. package/dist/client/portable-stories.mjs +26 -0
  53. package/dist/client/preview-prod.mjs +2 -0
  54. package/dist/client/public-types.mjs +1 -0
  55. package/dist/client/render.mjs +14 -0
  56. package/dist/client/types.mjs +1 -0
  57. package/dist/node/index.mjs +3 -0
  58. package/dist/server/__mocks-ng-workspace__/minimal-config/src/main.mjs +2 -0
  59. package/dist/server/__mocks-ng-workspace__/some-config/src/main.mjs +2 -0
  60. package/dist/server/__mocks-ng-workspace__/with-angularBrowserTarget/src/main.mjs +2 -0
  61. package/dist/server/__mocks-ng-workspace__/with-lib/projects/pattern-lib/src/main.mjs +2 -0
  62. package/dist/server/__mocks-ng-workspace__/with-nx/src/main.mjs +2 -0
  63. package/dist/server/__mocks-ng-workspace__/with-nx-workspace/src/main.mjs +2 -0
  64. package/dist/server/__mocks-ng-workspace__/with-options-styles/src/main.mjs +2 -0
  65. package/dist/server/__mocks-ng-workspace__/without-projects-entry/projects/pattern-lib/src/main.mjs +2 -0
  66. package/dist/server/__mocks-ng-workspace__/without-tsConfig/src/main.mjs +2 -0
  67. package/dist/server/angular-cli-webpack.mjs +80 -0
  68. package/dist/server/framework-preset-angular-cli.mjs +81 -0
  69. package/dist/server/framework-preset-angular-docs.mjs +6 -0
  70. package/dist/server/framework-preset-angular-ivy.mjs +56 -0
  71. package/dist/server/plugins/storybook-normalize-angular-entry-plugin.mjs +52 -0
  72. package/dist/server/preset-options.mjs +1 -0
  73. package/dist/server/utils/filter-out-styling-rules.mjs +13 -0
  74. package/dist/server/utils/module-is-available.mjs +9 -0
  75. package/package.json +5 -15
@@ -0,0 +1,33 @@
1
+ import { logger, instance as npmLog } from 'storybook/internal/node-logger';
2
+ import { dedent } from 'ts-dedent';
3
+ export const printErrorDetails = (error) => {
4
+ // Duplicate code for Standalone error handling
5
+ // Source: https://github.com/storybookjs/storybook/blob/39c7ba09ad84fbd466f9c25d5b92791a5450b9f6/lib/core-server/src/build-dev.ts#L136
6
+ npmLog.heading = '';
7
+ if (error instanceof Error) {
8
+ if (error.error) {
9
+ logger.error(error.error);
10
+ }
11
+ else if (error.stats && error.stats.compilation.errors) {
12
+ error.stats.compilation.errors.forEach((e) => logger.plain(e));
13
+ }
14
+ else {
15
+ logger.error(error);
16
+ }
17
+ }
18
+ else if (error.compilation?.errors) {
19
+ error.compilation.errors.forEach((e) => logger.plain(e));
20
+ }
21
+ logger.line();
22
+ };
23
+ export const errorSummary = (error) => {
24
+ return error.close
25
+ ? dedent `
26
+ FATAL broken build!, will close the process,
27
+ Fix the error below and restart storybook.
28
+ `
29
+ : dedent `
30
+ Broken build, fix the error above.
31
+ You may need to refresh the browser.
32
+ `;
33
+ };
@@ -0,0 +1,31 @@
1
+ import { isAbsolute, relative } from 'node:path';
2
+ import { JsPackageManagerFactory } from 'storybook/internal/common';
3
+ import { Observable } from 'rxjs';
4
+ const hasTsConfigArg = (args) => args.indexOf('-p') !== -1;
5
+ const hasOutputArg = (args) => args.indexOf('-d') !== -1 || args.indexOf('--output') !== -1;
6
+ // relative is necessary to workaround a compodoc issue with
7
+ // absolute paths on windows machines
8
+ const toRelativePath = (pathToTsConfig) => {
9
+ return isAbsolute(pathToTsConfig) ? relative('.', pathToTsConfig) : pathToTsConfig;
10
+ };
11
+ export const runCompodoc = ({ compodocArgs, tsconfig }, context) => {
12
+ return new Observable((observer) => {
13
+ const tsConfigPath = toRelativePath(tsconfig);
14
+ const finalCompodocArgs = [
15
+ ...(hasTsConfigArg(compodocArgs) ? [] : ['-p', tsConfigPath]),
16
+ ...(hasOutputArg(compodocArgs) ? [] : ['-d', `${context.workspaceRoot || '.'}`]),
17
+ ...compodocArgs,
18
+ ];
19
+ const packageManager = JsPackageManagerFactory.getPackageManager();
20
+ try {
21
+ const stdout = packageManager.runPackageCommandSync('compodoc', finalCompodocArgs, context.workspaceRoot, 'inherit');
22
+ context.logger.info(stdout);
23
+ observer.next();
24
+ observer.complete();
25
+ }
26
+ catch (e) {
27
+ context.logger.error(e);
28
+ observer.error();
29
+ }
30
+ });
31
+ };
@@ -0,0 +1,74 @@
1
+ import { take } from 'rxjs/operators';
2
+ import { afterEach, describe, expect, it, vi } from 'vitest';
3
+ import { runCompodoc } from './run-compodoc';
4
+ const mockRunScript = vi.fn();
5
+ vi.mock('storybook/internal/common', () => ({
6
+ JsPackageManagerFactory: {
7
+ getPackageManager: () => ({
8
+ runPackageCommandSync: mockRunScript,
9
+ }),
10
+ },
11
+ }));
12
+ const builderContextLoggerMock = {
13
+ createChild: vi.fn(),
14
+ log: vi.fn(),
15
+ debug: vi.fn(),
16
+ info: vi.fn(),
17
+ warn: vi.fn(),
18
+ error: vi.fn(),
19
+ fatal: vi.fn(),
20
+ };
21
+ describe('runCompodoc', () => {
22
+ afterEach(() => {
23
+ mockRunScript.mockClear();
24
+ });
25
+ const builderContextMock = {
26
+ workspaceRoot: 'path/to/project',
27
+ logger: builderContextLoggerMock,
28
+ };
29
+ it('should run compodoc with tsconfig from context', async () => {
30
+ runCompodoc({
31
+ compodocArgs: [],
32
+ tsconfig: 'path/to/tsconfig.json',
33
+ }, builderContextMock)
34
+ .pipe(take(1))
35
+ .subscribe();
36
+ expect(mockRunScript).toHaveBeenCalledWith('compodoc', ['-p', 'path/to/tsconfig.json', '-d', 'path/to/project'], 'path/to/project', 'inherit');
37
+ });
38
+ it('should run compodoc with tsconfig from compodocArgs', async () => {
39
+ runCompodoc({
40
+ compodocArgs: ['-p', 'path/to/tsconfig.stories.json'],
41
+ tsconfig: 'path/to/tsconfig.json',
42
+ }, builderContextMock)
43
+ .pipe(take(1))
44
+ .subscribe();
45
+ expect(mockRunScript).toHaveBeenCalledWith('compodoc', ['-d', 'path/to/project', '-p', 'path/to/tsconfig.stories.json'], 'path/to/project', 'inherit');
46
+ });
47
+ it('should run compodoc with default output folder.', async () => {
48
+ runCompodoc({
49
+ compodocArgs: [],
50
+ tsconfig: 'path/to/tsconfig.json',
51
+ }, builderContextMock)
52
+ .pipe(take(1))
53
+ .subscribe();
54
+ expect(mockRunScript).toHaveBeenCalledWith('compodoc', ['-p', 'path/to/tsconfig.json', '-d', 'path/to/project'], 'path/to/project', 'inherit');
55
+ });
56
+ it('should run with custom output folder specified with --output compodocArgs', async () => {
57
+ runCompodoc({
58
+ compodocArgs: ['--output', 'path/to/customFolder'],
59
+ tsconfig: 'path/to/tsconfig.json',
60
+ }, builderContextMock)
61
+ .pipe(take(1))
62
+ .subscribe();
63
+ expect(mockRunScript).toHaveBeenCalledWith('compodoc', ['-p', 'path/to/tsconfig.json', '--output', 'path/to/customFolder'], 'path/to/project', 'inherit');
64
+ });
65
+ it('should run with custom output folder specified with -d compodocArgs', async () => {
66
+ runCompodoc({
67
+ compodocArgs: ['-d', 'path/to/customFolder'],
68
+ tsconfig: 'path/to/tsconfig.json',
69
+ }, builderContextMock)
70
+ .pipe(take(1))
71
+ .subscribe();
72
+ expect(mockRunScript).toHaveBeenCalledWith('compodoc', ['-p', 'path/to/tsconfig.json', '-d', 'path/to/customFolder'], 'path/to/project', 'inherit');
73
+ });
74
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,164 @@
1
+ import { bootstrapApplication } from '@angular/platform-browser';
2
+ import { BehaviorSubject } from 'rxjs';
3
+ import { stringify } from 'telejson';
4
+ import { getApplication } from './StorybookModule';
5
+ import { storyPropsProvider } from './StorybookProvider';
6
+ import { queueBootstrapping } from './utils/BootstrapQueue';
7
+ import { PropertyExtractor } from './utils/PropertyExtractor';
8
+ const applicationRefs = new Map();
9
+ /**
10
+ * Attribute name for the story UID that may be written to the targetDOMNode.
11
+ *
12
+ * If a target DOM node has a story UID attribute, it will be used as part of the selector for the
13
+ * Angular component.
14
+ */
15
+ export const STORY_UID_ATTRIBUTE = 'data-sb-story-uid';
16
+ export class AbstractRenderer {
17
+ constructor() {
18
+ this.previousStoryRenderInfo = new Map();
19
+ }
20
+ /** Wait and destroy the platform */
21
+ static resetApplications(domNode) {
22
+ applicationRefs.forEach((appRef, appDOMNode) => {
23
+ if (!appRef.destroyed && (!domNode || appDOMNode === domNode)) {
24
+ appRef.destroy();
25
+ }
26
+ });
27
+ }
28
+ /**
29
+ * Bootstrap main angular module with main component or send only new `props` with storyProps$
30
+ *
31
+ * @param storyFnAngular {StoryFnAngularReturnType}
32
+ * @param forced {boolean} If :
33
+ *
34
+ * - True render will only use the StoryFn `props' in storyProps observable that will update sotry's
35
+ * component/template properties. Improves performance without reloading the whole
36
+ * module&component if props changes
37
+ * - False fully recharges or initializes angular module & component
38
+ *
39
+ * @param component {Component}
40
+ */
41
+ async render({ storyFnAngular, forced, component, targetDOMNode, }) {
42
+ const targetSelector = this.generateTargetSelectorFromStoryId(targetDOMNode.id);
43
+ const newStoryProps$ = new BehaviorSubject(storyFnAngular.props);
44
+ if (!this.fullRendererRequired({
45
+ targetDOMNode,
46
+ storyFnAngular,
47
+ moduleMetadata: {
48
+ ...storyFnAngular.moduleMetadata,
49
+ },
50
+ forced,
51
+ })) {
52
+ this.storyProps$.next(storyFnAngular.props);
53
+ return;
54
+ }
55
+ await this.beforeFullRender(targetDOMNode);
56
+ // Complete last BehaviorSubject and set a new one for the current module
57
+ if (this.storyProps$) {
58
+ this.storyProps$.complete();
59
+ }
60
+ this.storyProps$ = newStoryProps$;
61
+ this.initAngularRootElement(targetDOMNode, targetSelector);
62
+ const analyzedMetadata = new PropertyExtractor(storyFnAngular.moduleMetadata, component);
63
+ await analyzedMetadata.init();
64
+ const storyUid = this.generateStoryUIdFromRawStoryUid(targetDOMNode.getAttribute(STORY_UID_ATTRIBUTE));
65
+ const componentSelector = storyUid !== null ? `${targetSelector}[${storyUid}]` : targetSelector;
66
+ if (storyUid !== null) {
67
+ const element = targetDOMNode.querySelector(targetSelector);
68
+ element.toggleAttribute(storyUid, true);
69
+ }
70
+ const application = getApplication({
71
+ storyFnAngular,
72
+ component,
73
+ targetSelector: componentSelector,
74
+ analyzedMetadata,
75
+ });
76
+ const providers = [
77
+ storyPropsProvider(newStoryProps$),
78
+ ...analyzedMetadata.applicationProviders,
79
+ ...(storyFnAngular.applicationConfig?.providers ?? []),
80
+ ];
81
+ if (STORYBOOK_ANGULAR_OPTIONS?.experimentalZoneless) {
82
+ const { provideExperimentalZonelessChangeDetection } = await import('@angular/core');
83
+ if (!provideExperimentalZonelessChangeDetection) {
84
+ throw new Error('Experimental zoneless change detection requires Angular 18 or higher');
85
+ }
86
+ else {
87
+ providers.unshift(provideExperimentalZonelessChangeDetection());
88
+ }
89
+ }
90
+ const applicationRef = await queueBootstrapping(() => {
91
+ return bootstrapApplication(application, {
92
+ ...storyFnAngular.applicationConfig,
93
+ providers,
94
+ });
95
+ });
96
+ applicationRefs.set(targetDOMNode, applicationRef);
97
+ }
98
+ /**
99
+ * Only ASCII alphanumerics can be used as HTML tag name. https://html.spec.whatwg.org/#elements-2
100
+ *
101
+ * Therefore, stories break when non-ASCII alphanumerics are included in target selector.
102
+ * https://github.com/storybookjs/storybook/issues/15147
103
+ *
104
+ * This method returns storyId when it doesn't contain any non-ASCII alphanumerics. Otherwise, it
105
+ * generates a valid HTML tag name from storyId by removing non-ASCII alphanumerics from storyId,
106
+ * prefixing "sb-", and suffixing "-component"
107
+ *
108
+ * @memberof AbstractRenderer
109
+ * @protected
110
+ */
111
+ generateTargetSelectorFromStoryId(id) {
112
+ const invalidHtmlTag = /[^A-Za-z0-9-]/g;
113
+ const storyIdIsInvalidHtmlTagName = invalidHtmlTag.test(id);
114
+ return storyIdIsInvalidHtmlTagName ? `sb-${id.replace(invalidHtmlTag, '')}-component` : id;
115
+ }
116
+ /**
117
+ * Angular is unable to handle components that have selectors with accented attributes.
118
+ *
119
+ * Therefore, stories break when meta's title contains accents.
120
+ * https://github.com/storybookjs/storybook/issues/29132
121
+ *
122
+ * This method filters accents from a given raw id. For example, this method converts
123
+ * 'Example/Button with an "é" accent' into 'Example/Button with an "e" accent'.
124
+ *
125
+ * @memberof AbstractRenderer
126
+ * @protected
127
+ */
128
+ generateStoryUIdFromRawStoryUid(rawStoryUid) {
129
+ if (rawStoryUid === null) {
130
+ return rawStoryUid;
131
+ }
132
+ const accentCharacters = /[\u0300-\u036f]/g;
133
+ return rawStoryUid.normalize('NFD').replace(accentCharacters, '');
134
+ }
135
+ /** Adds DOM element that angular will use as bootstrap component. */
136
+ initAngularRootElement(targetDOMNode, targetSelector) {
137
+ targetDOMNode.innerHTML = '';
138
+ targetDOMNode.appendChild(document.createElement(targetSelector));
139
+ }
140
+ fullRendererRequired({ targetDOMNode, storyFnAngular, moduleMetadata, forced, }) {
141
+ const previousStoryRenderInfo = this.previousStoryRenderInfo.get(targetDOMNode);
142
+ const currentStoryRender = {
143
+ storyFnAngular,
144
+ moduleMetadataSnapshot: stringify(moduleMetadata, { maxDepth: 50 }),
145
+ };
146
+ this.previousStoryRenderInfo.set(targetDOMNode, currentStoryRender);
147
+ if (
148
+ // check `forceRender` of story RenderContext
149
+ !forced ||
150
+ // if it's the first rendering and storyProps$ is not init
151
+ !this.storyProps$) {
152
+ return true;
153
+ }
154
+ // force the rendering if the template has changed
155
+ const hasChangedTemplate = !!storyFnAngular?.template &&
156
+ previousStoryRenderInfo?.storyFnAngular?.template !== storyFnAngular.template;
157
+ if (hasChangedTemplate) {
158
+ return true;
159
+ }
160
+ // force the rendering if the metadata structure has changed
161
+ const hasChangedModuleMetadata = currentStoryRender.moduleMetadataSnapshot !== previousStoryRenderInfo?.moduleMetadataSnapshot;
162
+ return hasChangedModuleMetadata;
163
+ }
164
+ }
@@ -0,0 +1,9 @@
1
+ import { AbstractRenderer } from './AbstractRenderer';
2
+ export class CanvasRenderer extends AbstractRenderer {
3
+ async render(options) {
4
+ await super.render(options);
5
+ }
6
+ async beforeFullRender() {
7
+ CanvasRenderer.resetApplications();
8
+ }
9
+ }
@@ -0,0 +1,154 @@
1
+ import { getComponentDecoratorMetadata, getComponentInputsOutputs, } from './utils/NgComponentAnalyzer';
2
+ /**
3
+ * Check if the name matches the criteria for a valid identifier. A valid identifier can only
4
+ * contain letters, digits, underscores, or dollar signs. It cannot start with a digit.
5
+ */
6
+ const isValidIdentifier = (name) => /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(name);
7
+ /**
8
+ * Returns the property name, if it can be accessed with dot notation. If not, it returns
9
+ * `this['propertyName']`.
10
+ */
11
+ export const formatPropInTemplate = (propertyName) => isValidIdentifier(propertyName) ? propertyName : `this['${propertyName}']`;
12
+ const separateInputsOutputsAttributes = (ngComponentInputsOutputs, props = {}) => {
13
+ const inputs = ngComponentInputsOutputs.inputs
14
+ .filter((i) => i.templateName in props)
15
+ .map((i) => i.templateName);
16
+ const outputs = ngComponentInputsOutputs.outputs
17
+ .filter((o) => o.templateName in props)
18
+ .map((o) => o.templateName);
19
+ return {
20
+ inputs,
21
+ outputs,
22
+ otherProps: Object.keys(props).filter((k) => ![...inputs, ...outputs].includes(k)),
23
+ };
24
+ };
25
+ /**
26
+ * Converts a component into a template with inputs/outputs present in initial props
27
+ *
28
+ * @param component
29
+ * @param initialProps
30
+ * @param innerTemplate
31
+ */
32
+ export const computesTemplateFromComponent = (component, initialProps, innerTemplate = '') => {
33
+ const ngComponentMetadata = getComponentDecoratorMetadata(component);
34
+ const ngComponentInputsOutputs = getComponentInputsOutputs(component);
35
+ if (!ngComponentMetadata.selector) {
36
+ // Allow to add renderer component when NgComponent selector is undefined
37
+ return `<ng-container *ngComponentOutlet="storyComponent"></ng-container>`;
38
+ }
39
+ const { inputs: initialInputs, outputs: initialOutputs } = separateInputsOutputsAttributes(ngComponentInputsOutputs, initialProps);
40
+ const templateInputs = initialInputs.length > 0
41
+ ? ` ${initialInputs.map((i) => `[${i}]="${formatPropInTemplate(i)}"`).join(' ')}`
42
+ : '';
43
+ const templateOutputs = initialOutputs.length > 0
44
+ ? ` ${initialOutputs.map((i) => `(${i})="${formatPropInTemplate(i)}($event)"`).join(' ')}`
45
+ : '';
46
+ return buildTemplate(ngComponentMetadata.selector, innerTemplate, templateInputs, templateOutputs);
47
+ };
48
+ /** Stringify an object with a placholder in the circular references. */
49
+ function stringifyCircular(obj) {
50
+ const seen = new Set();
51
+ return JSON.stringify(obj, (key, value) => {
52
+ if (typeof value === 'object' && value !== null) {
53
+ if (seen.has(value)) {
54
+ return '[Circular]';
55
+ }
56
+ seen.add(value);
57
+ }
58
+ return value;
59
+ });
60
+ }
61
+ const createAngularInputProperty = ({ propertyName, value, argType, }) => {
62
+ let templateValue;
63
+ switch (typeof value) {
64
+ case 'string':
65
+ templateValue = `'${value}'`;
66
+ break;
67
+ case 'object':
68
+ templateValue = stringifyCircular(value)
69
+ .replace(/'/g, '\u2019')
70
+ .replace(/\\"/g, '\u201D')
71
+ .replace(/"([^-"]+)":/g, '$1: ')
72
+ .replace(/"/g, "'")
73
+ .replace(/\u2019/g, "\\'")
74
+ .replace(/\u201D/g, "\\'")
75
+ .split(',')
76
+ .join(', ');
77
+ break;
78
+ default:
79
+ templateValue = value;
80
+ }
81
+ return `[${propertyName}]="${templateValue}"`;
82
+ };
83
+ /**
84
+ * Converts a component into a template with inputs/outputs present in initial props
85
+ *
86
+ * @param component
87
+ * @param initialProps
88
+ * @param innerTemplate
89
+ */
90
+ export const computesTemplateSourceFromComponent = (component, initialProps, argTypes) => {
91
+ const ngComponentMetadata = getComponentDecoratorMetadata(component);
92
+ if (!ngComponentMetadata) {
93
+ return null;
94
+ }
95
+ if (!ngComponentMetadata.selector) {
96
+ // Allow to add renderer component when NgComponent selector is undefined
97
+ return `<ng-container *ngComponentOutlet="${component.name}"></ng-container>`;
98
+ }
99
+ const ngComponentInputsOutputs = getComponentInputsOutputs(component);
100
+ const { inputs: initialInputs, outputs: initialOutputs } = separateInputsOutputsAttributes(ngComponentInputsOutputs, initialProps);
101
+ const templateInputs = initialInputs.length > 0
102
+ ? ` ${initialInputs
103
+ .map((propertyName) => createAngularInputProperty({
104
+ propertyName,
105
+ value: initialProps[propertyName],
106
+ argType: argTypes?.[propertyName],
107
+ }))
108
+ .join(' ')}`
109
+ : '';
110
+ const templateOutputs = initialOutputs.length > 0
111
+ ? ` ${initialOutputs.map((i) => `(${i})="${formatPropInTemplate(i)}($event)"`).join(' ')}`
112
+ : '';
113
+ return buildTemplate(ngComponentMetadata.selector, '', templateInputs, templateOutputs);
114
+ };
115
+ const buildTemplate = (selector, innerTemplate, inputs, outputs) => {
116
+ // https://www.w3.org/TR/2011/WD-html-markup-20110113/syntax.html#syntax-elements
117
+ const voidElements = [
118
+ 'area',
119
+ 'base',
120
+ 'br',
121
+ 'col',
122
+ 'command',
123
+ 'embed',
124
+ 'hr',
125
+ 'img',
126
+ 'input',
127
+ 'keygen',
128
+ 'link',
129
+ 'meta',
130
+ 'param',
131
+ 'source',
132
+ 'track',
133
+ 'wbr',
134
+ ];
135
+ const firstSelector = selector.split(',')[0];
136
+ const templateReplacers = [
137
+ [/(^.*?)(?=[,])/, '$1'],
138
+ [/(^\..+)/, 'div$1'],
139
+ [/(^\[.+?])/, 'div$1'],
140
+ [/([\w[\]]+)(\s*,[\w\s-[\],]+)+/, `$1`],
141
+ [/#([\w-]+)/, ` id="$1"`],
142
+ [/((\.[\w-]+)+)/, (_, c) => ` class="${c.split `.`.join ` `.trim()}"`],
143
+ [/(\[.+?])/g, (_, a) => ` ${a.slice(1, -1)}`],
144
+ [
145
+ /([\S]+)(.*)/,
146
+ (template, elementSelector) => {
147
+ return voidElements.some((element) => elementSelector === element)
148
+ ? template.replace(/([\S]+)(.*)/, `<$1$2${inputs}${outputs} />`)
149
+ : template.replace(/([\S]+)(.*)/, `<$1$2${inputs}${outputs}>${innerTemplate}</$1>`);
150
+ },
151
+ ],
152
+ ];
153
+ return templateReplacers.reduce((prevSelector, [searchValue, replacer]) => prevSelector.replace(searchValue, replacer), firstSelector);
154
+ };