Skip to content

Commit

Permalink
feat(doc): add support for get inputs and outputs from hostDirectives #…
Browse files Browse the repository at this point in the history
  • Loading branch information
ZurabDev committed May 20, 2024
1 parent 1927d75 commit 5d93b8a
Show file tree
Hide file tree
Showing 5 changed files with 182 additions and 38 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,17 @@
<prizm-tooltip-base-example></prizm-tooltip-base-example>
</prizm-doc-example>

<!-- <prizm-doc-example id="with-template" [content]="exampleWithTemplate" heading="With Template">-->
<!-- <prizm-tooltip-with-template-example></prizm-tooltip-with-template-example>-->
<!-- </prizm-doc-example>-->
<prizm-doc-example id="with-template" [content]="exampleWithTemplate" heading="With Template">
<prizm-tooltip-with-template-example></prizm-tooltip-with-template-example>
</prizm-doc-example>

<!-- <prizm-doc-example id="with-custom-context" [content]="exampleWithContext" heading="With Context">-->
<!-- <prizm-tooltip-with-custom-context-example></prizm-tooltip-with-custom-context-example>-->
<!-- </prizm-doc-example>-->
<prizm-doc-example id="with-custom-context" [content]="exampleWithContext" heading="With Context">
<prizm-tooltip-with-custom-context-example></prizm-tooltip-with-custom-context-example>
</prizm-doc-example>

<!-- <prizm-doc-example id="with-component" [content]="exampleWithComponent" heading="With Component">-->
<!-- <prizm-tooltip-with-component-example></prizm-tooltip-with-component-example>-->
<!-- </prizm-doc-example>-->
<prizm-doc-example id="with-component" [content]="exampleWithComponent" heading="With Component">
<prizm-tooltip-with-component-example></prizm-tooltip-with-component-example>
</prizm-doc-example>
</ng-template>

<ng-template prizmDocPageTab prizmDocHost>
Expand Down
114 changes: 85 additions & 29 deletions libs/doc/base/src/lib/components/host/host-element.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { PrizmDocumentationPropertyType } from '../../types/pages';
import { debounceTime, takeUntil, tap } from 'rxjs/operators';
import { PrizmPageService } from '../page/page.service';
import { PrizmDocHostElementListenerService } from './host-element-listener.service';
import { prizmInvertObject } from '@prizm-ui/helpers';

export type PrizmDocHosSet = {
type: string;
Expand Down Expand Up @@ -49,40 +50,51 @@ export class PrizmDocHostElementService implements OnDestroy {
.subscribe();
}

/**
* Retrieves the input and output properties of an Angular component or directive.
*
* @template T - The type of the component class.
* @param {Type<T>} componentClass - The class of the Angular component or directive.
* @returns {object} An object containing input and output properties, their keys and values,
* the original metadata, and the component's selector.
* @throws {Error} If the provided class is not an Angular component or directive.
*/
private getListComponentInputsOutputs<T>(componentClass: Type<T>) {
const inputs = new Map<string, string>();
const outputs = new Map<string, string>();
const inputKeys = new Set<string>();
const inputValues = new Set<string>();
const outputKeys = new Set<string>();
const outputValues = new Set<string>();
let selector: string | null = null;
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
const componentMetadata = componentClass['ɵcmp'] || componentClass['ɵdir'];
if (componentMetadata) {
selector = componentMetadata.selectors?.[0]?.[0] as string;
const inputProperties = componentMetadata.inputs;
const outputProperties = componentMetadata.outputs;

for (const inputName in inputProperties) {
const classPropertyName = inputProperties[inputName];
const inputFromSet = inputs.get(classPropertyName);
if (inputFromSet && inputFromSet !== classPropertyName) continue;
inputs.set(classPropertyName, inputName);
}

for (const outputKey in outputProperties) {
const classPropertyName = outputProperties[outputKey];
const nameFromSet = outputs.get(classPropertyName);
if (nameFromSet && nameFromSet !== classPropertyName) continue;
outputs.set(classPropertyName, outputKey);
}
} else {
console.error('The provided class is not an Angular component.');
// Retrieve component metadata
const componentMetadata = (componentClass as any)['ɵcmp'] || (componentClass as any)['ɵdir'];
if (!componentMetadata) {
throw new Error('The provided class is not an Angular component or directive.');
}

// Extract the component's selector
selector = componentMetadata.selectors?.[0]?.[0] as string;

// Process collected properties and fill the sets
this.processProperties(inputKeys, inputValues, componentMetadata.inputs);
this.processProperties(outputKeys, outputValues, componentMetadata.outputs);
this.processProperties(
inputKeys,
inputValues,
this.collectProperties(componentMetadata.hostDirectives, 'inputs')
);
this.processProperties(
outputKeys,
outputValues,
this.collectProperties(componentMetadata.hostDirectives, 'outputs')
);

// Return the collected information
return {
inputs: [...inputs.values()],
inputProperties: [...inputs.keys()],
outputs: [...outputs.values()],
outputProperties: [...outputs.keys()],
inputs: [...inputValues],
inputProperties: [...inputKeys],
outputs: [...outputValues],
outputProperties: [...outputKeys],
origin: {
inputs: componentMetadata.inputs,
outputs: componentMetadata.outputs,
Expand All @@ -91,10 +103,54 @@ export class PrizmDocHostElementService implements OnDestroy {
};
}

/**
* Processes the properties and fills the provided sets with keys and values.
*
* @param {Set<string>} keysSet - The set to store the property keys.
* @param {Set<string>} valuesSet - The set to store the property values.
* @param {{ [key: string]: string }} properties - The properties to process.
*/
private processProperties(
keysSet: Set<string>,
valuesSet: Set<string>,
properties: { [key: string]: string }
) {
for (const key in properties) {
if (key in properties) {
keysSet.add(key);
valuesSet.add(properties[key]);
}
}
}

/**
* Collects properties of a specific type (inputs or outputs) from the given directives.
*
* @param {any[]} directives - The array of host directives.
* @param {'inputs' | 'outputs'} propertyType - The type of properties to collect ('inputs' or 'outputs').
* @returns {{ [key: string]: string }} An object containing the collected properties.
*/
private collectProperties(
directives: any[],
propertyType: 'inputs' | 'outputs'
): { [key: string]: string } {
const properties: { [key: string]: string } = {};

if (directives) {
for (const directive of directives) {
const directiveProperties = directive[propertyType];
if (directiveProperties) {
Object.assign(properties, directiveProperties);
}
}
}

return properties;
}

private updateComponentInfo(listenerElementKey: string, el: ElementRef): void {
const currentOutputMap = this.outputMap.get(listenerElementKey) || new Map();
const metaComponentData = this.getListComponentInputsOutputs(el.nativeElement.constructor);

this.outputs.set(
listenerElementKey,
metaComponentData.outputs.map(i => ({
Expand Down
1 change: 1 addition & 0 deletions libs/helpers/src/lib/util/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,4 @@ export * from './merge';
export * from './order-by';
export * from './difference';
export * from './directive';
export * from './invert-object';
71 changes: 71 additions & 0 deletions libs/helpers/src/lib/util/invert-object.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import { prizmInvertObject } from './invert-object';

describe('prizmInvertObject', () => {
it('should invert keys and values of a simple object', () => {
const originalObject = {
a: '1',
b: '2',
c: '3',
};
const expectedInvertedObject = {
'1': 'a',
'2': 'b',
'3': 'c',
};

const result = prizmInvertObject(originalObject);
expect(result).toEqual(expectedInvertedObject);
});

it('should handle objects with number keys and string values', () => {
const originalObject = {
1: 'a',
2: 'b',
3: 'c',
};
const expectedInvertedObject = {
a: '1',
b: '2',
c: '3',
};

const result = prizmInvertObject(originalObject);
expect(result).toEqual(expectedInvertedObject);
});

it('should handle objects with mixed keys and values', () => {
const originalObject = {
a: 1,
b: 2,
c: 3,
};
const expectedInvertedObject = {
1: 'a',
2: 'b',
3: 'c',
};

const result = prizmInvertObject(originalObject);
expect(result).toEqual(expectedInvertedObject);
});

it('should throw an error if the values are not primitive types', () => {
const originalObject = {
a: { nested: 'object' },
b: '2',
c: '3',
};

// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
expect(() => prizmInvertObject(originalObject)).toThrow();
});

it('should handle empty objects', () => {
const originalObject = {};
const expectedInvertedObject = {};

const result = prizmInvertObject(originalObject);
expect(result).toEqual(expectedInvertedObject);
});
});
16 changes: 16 additions & 0 deletions libs/helpers/src/lib/util/invert-object.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
export function prizmInvertObject<K extends string | number | symbol, V extends string | number | symbol>(
obj: Record<K, V>
): Record<V, K> {
const invertedObj = {} as Record<V, K>;

for (const key in obj) {
if (obj[key]) {
const value = obj[key];
if ((value && typeof value === 'object') || typeof value === 'function')
throw new Error('Passed to prizmInvertObject values that has not primitive value');
invertedObj[value] = key as K;
}
}

return invertedObj;
}

0 comments on commit 5d93b8a

Please sign in to comment.