Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: added ui5 lifecycle service and fixed ui5 select cva #107

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40,254 changes: 12,703 additions & 27,551 deletions apps/documentation/src/api-json.json

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@ import {AngularExportSpecifierType} from "../angular-export-specifier-type";
import {genericCva} from "./generic-cva";
import {ComponentWrapperCreator} from "./component-wrapper-creator";
import {format as prettierFormat} from "prettier";
import {utilsFile} from "./utils";
import {proxyUtilsFile} from "./utils-generated-files/proxy-utils-file";
import {outputTypesImportData} from "./output-types-import-data";
import { ui5LifecyclesServiceFile } from "./utils-generated-files/ui5-lifecycles-service-file";

/**
* The Angular Component file creator.
Expand All @@ -15,7 +16,7 @@ export class ComponentFile extends AngularGeneratedFile {
/** The export specifier of the component wrapper */
wrapperExportSpecifier!: ExportSpecifier<AngularExportSpecifierType>;
/** The name of the element type */
getComponentCode = () => ComponentWrapperCreator(this, this.elementTypeName, this.eventsNameMapName, this.options, this.componentsMap);
getComponentCode = () => ComponentWrapperCreator(this);
/** The name of the element's interface */
elementTypeName!: string;
/** The name of the events map */
Expand All @@ -32,7 +33,7 @@ export class ComponentFile extends AngularGeneratedFile {
return this.componentData.selector;
}

constructor(readonly componentData: ComponentData, private options: AngularGeneratorOptions, private componentsMap: Map<ComponentData, AngularGeneratedFile>) {
constructor(readonly componentData: ComponentData, private options: AngularGeneratorOptions, readonly componentsMap: Map<ComponentData, AngularGeneratedFile>) {
super();
this.componentsMap.set(componentData, this);
this.move(options.exportFileNameFactory(componentData.path));
Expand All @@ -55,12 +56,13 @@ export class ComponentFile extends AngularGeneratedFile {
this.addExport(this.wrapperExportSpecifier);
this.addImport(['Component', 'ElementRef', 'NgZone', 'ChangeDetectorRef'], '@angular/core');
this.addImport({specifiers: [], path: this.componentData.path});
this.addImport(['ProxyInputs', 'ProxyMethods', 'ProxyOutputs'], utilsFile.relativePathFrom);
this.addImport(['ProxyInputs', 'ProxyMethods', 'ProxyOutputs'], proxyUtilsFile.relativePathFrom);
if (this.componentData.formData.length > 0) {
this.addImport('forwardRef', '@angular/core');
this.addImport(['Observable', 'fromEvent', 'merge'], 'rxjs');
this.addImport('NG_VALUE_ACCESSOR', '@angular/forms');
this.addImport(genericCva.exports[0].specifiers[0].exported, genericCva.relativePathFrom);
this.addImport(ui5LifecyclesServiceFile.exportClassName, ui5LifecyclesServiceFile.relativePathFrom);
}
if (this.componentData.outputs.length > 0) {
this.addImport(outputTypesImportData(this.componentData, this.componentsMap));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {genericCva} from "./generic-cva";
import {AngularGeneratedFile} from "../angular-generated-file";
import {inputsJson, outputsJson} from "./metadata-tools";
import {outputType} from "./output-type";
import { ui5LifecyclesServiceFile } from "./utils-generated-files/ui5-lifecycles-service-file";

/**
* Returns the base class extends string for the component file.
Expand All @@ -24,11 +25,25 @@ function CvaBaseClassExtends(componentFile: ComponentFile): string {
* Returns the providers string for the component file.
* @param componentFile
*/
function providers(componentFile: ComponentFile) {
function providers(componentFile: ComponentFile): string[] {
if (componentFile.componentData.formData.length === 0) {
return '';
return [];
}
return [`{ provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => ${componentFile.wrapperExportSpecifier.local}), multi: true }`, ui5LifecyclesServiceFile.exportClassName]
}

function constructorDeps(componentFile: ComponentFile): [string, string][] {
const deps: [string, string][] = [
['private c', 'ChangeDetectorRef'],
['private elementRef', `ElementRef<${componentFile.elementTypeName}>`],
['private zone', 'NgZone']
];

if (componentFile.componentData.formData.length > 0) {
deps.push(['private ui5LifecyclesService', 'Ui5LifecyclesService'])
}
return `{ provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => ${componentFile.wrapperExportSpecifier.local}), multi: true }`

return deps;
}

/**
Expand Down Expand Up @@ -65,7 +80,9 @@ export function CvaConstructor(componentFile: ComponentFile): string {
return ${getValue}
},
set value(val) {
${setValue}
ui5LifecyclesService.onDomEnter(() => {
${setValue}
}, 'cvaSetValue');
},
valueUpdatedNotifier$: merge(
${outputEvents.map((event) => `fromEvent(elementRef.nativeElement, '${event.name}')`).join(',\n')}
Expand All @@ -89,36 +106,38 @@ export function CvaConstructor(componentFile: ComponentFile): string {
* Types are generated based on the component's inputs, outputs and slots.
*
* @param componentFile
* @param elementTypeName
* @param eventsMapName
* @param options
* @param ComponentsMap
* @constructor
*/
export function ComponentWrapperCreator(
componentFile: ComponentFile,
elementTypeName: string,
eventsMapName: string,
options: AngularGeneratorOptions,
ComponentsMap: Map<ComponentData, AngularGeneratedFile>
componentFile: ComponentFile
): string {
const {
elementTypeName,
eventsNameMapName: eventsMapName,
componentsMap,
componentData: {
inputs,
outputs,
slots,
methods
}
} = componentFile;
const getInputType = (input: InputType) => {
const t = typeof input.type === 'string' ? input.type : input.type.map((cmp: ComponentData) => {
const generatedFile = ComponentsMap.get(cmp);
const generatedFile = componentsMap.get(cmp);
const exported = generatedFile!.exports[0].specifiers[0].exported;
return (typeof exported === 'string' ? exported : exported()) + '["element"]';
}).join(' | ');
return input.isArray ? `Array<${t}>` : t;
}
const outputs = componentFile.componentData.outputs;

const eventsMap = `
interface ${eventsMapName} extends Omit<HTMLElementEventMap, ${outputs.map(output => `'${output.name}'`).join(' | ')}> {
${outputs.map((output) => `${output.name}: CustomEvent<${outputType(output, ComponentsMap)}>;`).join('\n')}
${outputs.map((output) => `${output.name}: CustomEvent<${outputType(output, componentsMap)}>;`).join('\n')}
}
`;

const inputsTypeStr = componentFile.componentData.inputs.map((input) => {
const inputsTypeStr = inputs.map((input) => {
return `${input.name}: ${getInputType(input)};`;
}).join('\n');

Expand All @@ -129,9 +148,9 @@ export function ComponentWrapperCreator(
removeEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | EventListenerOptions): void;
`;

const analyzedSlots = componentFile.componentData.slots.filter(sl => sl.name !== 'default').map((slot) => {
const analyzedSlots = slots.filter(sl => sl.name !== 'default').map((slot) => {
const supportedElements = slot.supportedElements.map((element) => {
const componentGeneratedFile = ComponentsMap.get(element);
const componentGeneratedFile = componentsMap.get(element);
const exportedWrapperClassName = componentGeneratedFile!.exports[0].specifiers[0].exported;
if (!exportedWrapperClassName) {
throw new Error(`Component ${element.baseName} is not exported`);
Expand All @@ -147,7 +166,7 @@ export function ComponentWrapperCreator(
}`;
}).join('\n');
const htmlElementType = (() => {
const componentInputsOutputsAndSlots = [...componentFile.componentData.inputs, ...componentFile.componentData.outputs, ...componentFile.componentData.slots];
const componentInputsOutputsAndSlots = [...inputs, ...outputs, ...slots];
if (componentInputsOutputsAndSlots.length > 0) {
return `Omit<HTMLElement, ${componentInputsOutputsAndSlots.map(output => `'${output.name}'`).join(' | ')}>`
}
Expand All @@ -168,23 +187,25 @@ export function ComponentWrapperCreator(
return `
${elementType}
// JS source file
@ProxyInputs([${componentFile.componentData.inputs.map((input) => `'${input.name}'`)}])
@ProxyOutputs([${componentFile.componentData.outputs.map((output) => `'${output.name}'`)}])
@ProxyMethods([${componentFile.componentData.methods.map((method) => `'${method.name}'`)}])
@ProxyInputs([${inputs.map((input) => `'${input.name}'`)}])
@ProxyOutputs([${outputs.map((output) => `'${output.name}'`)}])
@ProxyMethods([${methods.map((method) => `'${method.name}'`)}])
@Component({
template: \`<ng-content></ng-content>\`,
selector: '${componentFile.selector}',
exportAs: '${camelCase(componentFile.selector)}',
standalone: true,
providers: [
${providers(componentFile)}
${providers(componentFile).join(',\n')}
],
inputs: ${inputsJson(componentFile.componentData.inputs)},
inputs: ${inputsJson(inputs)},
outputs: ${outputsJson(outputs)},
})
export class ${componentFile.wrapperExportSpecifier.local} ${CvaBaseClassExtends(componentFile)} {
${componentFile.componentData.inputs.filter(({type}) => typeof type === 'string' && type.indexOf('any') === -1).map(({name}) => `${name}?: ${elementTypeName}['${name}'];`).join('\n')}
constructor(private c: ChangeDetectorRef, private elementRef: ElementRef<${elementTypeName}>, private zone: NgZone) {
${inputs.filter(({type}) => typeof type === 'string' && type.indexOf('any') === -1).map(({name}) => `${name}?: ${elementTypeName}['${name}'];`).join('\n')}
constructor(
${constructorDeps(componentFile).map(([name, type]) => `${name}: ${type}`).join(',\n')}
) {
c.detach();
${CvaConstructor(componentFile)}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,11 @@ import {format as prettierFormat} from "prettier";
import {camelCase} from "lodash";
import {AngularExportSpecifierType} from "../angular-export-specifier-type";
import {genericCva} from "./generic-cva";
import {utilsFile} from "./utils";
import { proxyUtilsFile } from "./utils-generated-files/proxy-utils-file";
import {inputsJson, outputsJson} from "./metadata-tools";
import {outputType} from "./output-type";
import {outputTypesImportData} from "./output-types-import-data";
import { ui5LifecyclesServiceFile } from "./utils-generated-files/ui5-lifecycles-service-file";

/**
* Class is used when the source component is a typescript file.
Expand Down Expand Up @@ -43,7 +44,7 @@ export class TsComponentFile extends AngularGeneratedFile {
types: [ExportSpecifierType.Class, AngularExportSpecifierType.NgModule]
})
this.addImport(['Component', 'ElementRef', 'NgZone', 'ChangeDetectorRef'], '@angular/core');
this.addImport(['ProxyInputs', 'ProxyMethods', 'ProxyOutputs'], utilsFile.relativePathFrom);
this.addImport(['ProxyInputs', 'ProxyMethods', 'ProxyOutputs'], proxyUtilsFile.relativePathFrom);
if (this.componentData.outputs.length) {
this.addImport(['EventEmitter'], '@angular/core');
this.addImport(outputTypesImportData(this.componentData, this.componentsMap));
Expand All @@ -53,6 +54,7 @@ export class TsComponentFile extends AngularGeneratedFile {
this.addImport(['forwardRef'], '@angular/core');
this.addImport(['merge', 'fromEvent'], 'rxjs');
this.addImport(genericCva.exports[0].specifiers[0].exported, genericCva.relativePathFrom);
this.addImport(ui5LifecyclesServiceFile.exportClassName, ui5LifecyclesServiceFile.relativePathFrom);
}
this.addImport({specifiers: [], path: this.componentData.path})

Expand Down Expand Up @@ -129,7 +131,9 @@ export class TsComponentFile extends AngularGeneratedFile {
return ${getValue}
},
set value(val) {
${setValue}
ui5LifecyclesService.onDomEnter(() => {
${setValue}
}, 'cvaSetValue');
},
valueUpdatedNotifier$: merge(
${outputEvents.map((event) => `fromEvent(elementRef.nativeElement, '${event.name}')`).join(',\n')}
Expand All @@ -154,16 +158,14 @@ export class TsComponentFile extends AngularGeneratedFile {
inputs: ${inputsJson(this.componentData.inputs)},
outputs: ${outputsJson(this.componentData.outputs)},
providers: [
${this.componentData.formData.length > 0 ? `{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => ${this.componentClassName}),
multi: true
}` : ''}
${this.providers()}
]
})
export class ${this.componentClassName} ${baseClass()}{
${this.componentData.inputs.filter(({type}) => typeof type === 'string' && type.indexOf('any') === -1).map(({name}) => `${name}?: ${this.componentData.baseName}Element['${name}'];`).join('\n')}
constructor(private c: ChangeDetectorRef, private elementRef: ElementRef<${this.componentData.baseName}Element>, private zone: NgZone) {
constructor(
${this.constructorDeps()}
) {
c.detach();
${cvaConstructor()}
}
Expand All @@ -187,4 +189,32 @@ export class TsComponentFile extends AngularGeneratedFile {
}`
].join('\n'), {parser: 'typescript'});
}

private constructorDeps(): string {
/**
* private c: ChangeDetectorRef, private elementRef: ElementRef<${this.componentData.baseName}Element>, private zone: NgZone
*/
const deps: string[] = [
'c: ChangeDetectorRef',
`private elementRef: ElementRef<${this.componentData.baseName}Element>`,
`
/** Used in proxy decorators */
private zone: NgZone
`
];
if (this.componentData.formData.length > 0) {
deps.push(`ui5LifecyclesService: ${ui5LifecyclesServiceFile.exportClassName}`);
}
return deps.join(',\n');
}

private providers(): string {
if (this.componentData.formData.length > 0) {
return [
`{ provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => ${this.componentClassName}), multi: true }`,
ui5LifecyclesServiceFile.exportClassName
].join(',\n');
}
return '';
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {join} from "path";
import {AngularModuleFile} from "../angular-module-file";
import {TsComponentFile} from "./ts-component-file";
import {ComponentFile} from "./component-file";
import { utilsFile } from "./utils";
import { generateUtilsEntry } from "./utils-generated-files/generate-utils-entry";

function getComponentFile(componentData: ComponentData, options: AngularGeneratorOptions, cache: Map<ComponentData, AngularGeneratedFile>): AngularGeneratedFile {
const cached = cache.get(componentData);
Expand Down Expand Up @@ -55,12 +55,7 @@ export const ui5componentsWrapper = (components: ComponentData[], options: Angul
genericCva.apfPath = options.apfPathFactory(genericCva.path);
}

(() => {
files[utilsFile.path] = utilsFile;
const ngPackageFile = new NgPackageFile(utilsFile, utilsFile.parsedPath.dir);
files[ngPackageFile.path] = utilsFile;
utilsFile.apfPath = options.apfPathFactory(utilsFile.path);
})();
generateUtilsEntry(files, options);

options.modules.forEach(moduleDescription => {
const moduleFile = new AngularModuleFile(Object.values(files), moduleDescription);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { IndexFile } from "../../index-file";
import { proxyUtilsFile } from "./proxy-utils-file";
import { ui5LifecyclesServiceFile } from "./ui5-lifecycles-service-file";
import { AngularGeneratorOptions } from "../../angular-generator-options";

export class UtilsEntryFile extends IndexFile {
constructor(options: AngularGeneratorOptions) {
super(
[proxyUtilsFile, ui5LifecyclesServiceFile],
options,
'utils/index.ts'
);
this.apfPath = options.apfPathFactory(this.path);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { AngularGeneratorOptions } from "../../angular-generator-options";
import { AngularGeneratedFile } from '../../angular-generated-file';
import { proxyUtilsFile } from "./proxy-utils-file";
import { UtilsEntryFile } from "./entry-file";
import { NgPackageFile } from '../../ng-package-file';
import { ui5LifecyclesServiceFile } from "./ui5-lifecycles-service-file";

export function generateUtilsEntry(files: Record<string, AngularGeneratedFile>, options: AngularGeneratorOptions): void {
files[proxyUtilsFile.path] = proxyUtilsFile;
proxyUtilsFile.apfPath = options.apfPathFactory(proxyUtilsFile.path);

files[ui5LifecyclesServiceFile.path] = ui5LifecyclesServiceFile;
ui5LifecyclesServiceFile.apfPath = options.apfPathFactory(ui5LifecyclesServiceFile.path);

const utilsEntryFile = new UtilsEntryFile(options);
files[utilsEntryFile.path] = utilsEntryFile;

const ngPackageFile = new NgPackageFile(utilsEntryFile, utilsEntryFile.parsedPath.dir);
files[ngPackageFile.path] = ngPackageFile;
}
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
import {ExportSpecifierType} from "@ui5/webcomponents-wrapper";
import {format} from "prettier";
import {AngularGeneratedFile} from "../angular-generated-file";
import {AngularGeneratedFile} from "../../angular-generated-file";

/**
* The utils file generator.
* The output code is responsible for creating the ProxyInputs, ProxyMethods and ProxyOutputs decorators,
* which are used to proxy the inputs, outputs and methods of the web component to the Angular component.
*/
export class UtilsFile extends AngularGeneratedFile {
export class ProxyUtilsFile extends AngularGeneratedFile {
constructor() {
super();
this.move('utils/index.ts');
this.move('utils/proxy-utils.ts');
this.addImport(['fromEvent', 'map'], 'rxjs');
this.addExport([
{
Expand Down Expand Up @@ -80,4 +80,4 @@ export class UtilsFile extends AngularGeneratedFile {
}
}

export const utilsFile = new UtilsFile();
export const proxyUtilsFile = new ProxyUtilsFile();
Loading
Loading