Skip to content

Commit

Permalink
feat(core): add dialog-factory
Browse files Browse the repository at this point in the history
  • Loading branch information
MillerSvt committed Oct 11, 2024
1 parent e064549 commit 5d15122
Show file tree
Hide file tree
Showing 8 changed files with 235 additions and 50 deletions.
43 changes: 43 additions & 0 deletions projects/core/components/dialog/dialog.factory.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import {inject, INJECTOR} from '@angular/core';
import type {TuiDialogContext, TuiDialogOptions} from '@taiga-ui/core/components/dialog';
import {TuiDialogService} from '@taiga-ui/core/components/dialog';
import {PolymorpheusComponent} from '@taiga-ui/polymorpheus';
import type {Observable} from 'rxjs';

type ReplaceNever<T> = [T] extends [never] ? void : T;

type ReplaceAny<T> = 0 extends T & 1 ? void : T;

type ExtractDialogData<T> = ReplaceNever<
{
[K in keyof T]: ReplaceAny<T[K]> extends TuiDialogContext<any, infer D>
? D
: never;
}[keyof T]
>;

type ExtractDialogResult<T> = ReplaceNever<
{
[K in keyof T]: ReplaceAny<T[K]> extends TuiDialogContext<infer R, any>
? R
: never;
}[keyof T]
>;

export function tuiDialog<
T,
D extends ExtractDialogData<T>,
R extends ExtractDialogResult<T>,
>(
component: new () => T,
options?: Partial<Omit<TuiDialogOptions<D>, 'data'>>,
): (data: D) => Observable<R> {
const dialogService = inject(TuiDialogService);
const injector = inject(INJECTOR);

return (data) =>
dialogService.open(new PolymorpheusComponent(component, injector), {
...options,
data,
});
}
1 change: 1 addition & 0 deletions projects/core/components/dialog/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export * from './dialog.component';
export * from './dialog.directive';
export * from './dialog.factory';
export * from './dialog.interfaces';
export * from './dialog.service';
export * from './dialog.tokens';
Expand Down
155 changes: 155 additions & 0 deletions projects/core/components/dialog/test/dialog.factory.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
import * as fs from 'node:fs';
import * as path from 'node:path';

import {TestBed} from '@angular/core/testing';
import {TuiDialogService} from '@taiga-ui/core';
import ts from 'typescript';

import tsconfig from '../../../../../tsconfig.json';

describe('tuiDialog', () => {
beforeEach(() => {
TestBed.configureTestingModule({
providers: [
{
provide: TuiDialogService,
useValue: {
open: jest.fn(),
},
},
],
});
});

describe('typing tests', () => {
let checker: ts.TypeChecker;
let sourceFile: ts.SourceFile;

function findVariable(variableName: string): ts.VariableDeclaration | undefined {
function findVariableCb(node: ts.Node): ts.VariableDeclaration | undefined {
if (
ts.isVariableDeclaration(node) &&
ts.isIdentifier(node.name) &&
node.name.text === variableName
) {
return node;
}

return ts.forEachChild(node, findVariableCb);
}

return findVariableCb(sourceFile);
}

function getVariableType(variableName: string): string {
const variableNode = findVariable(variableName);

if (!variableNode) {
throw new Error(`Cannot find variable: ${variableName}`);
}

// Получаем тип переменной через TypeChecker
const type = checker.getTypeAtLocation(variableNode);

return checker.typeToString(type);
}

beforeAll(() => {
// language=TypeScript
const sourceCode = `;
import {tuiDialog} from "../dialog.factory";
import {TuiDialogContext} from "../dialog.interfaces";
class Test1Component {
}
const dialog1 = tuiDialog(Test1Component);
class Test2Component {
context!: TuiDialogContext<string, number>;
}
const dialog2 = tuiDialog(Test2Component);
class Test3Component {
context!: TuiDialogContext<string, number>;
otherMember: string;
}
const dialog3 = tuiDialog(Test3Component);
class Test4Component {
context!: TuiDialogContext<string, number>;
otherMember: any;
}
const dialog4 = tuiDialog(Test4Component);
`;

const sourceFilePath = path.join(__dirname, 'input.ts');
const options: ts.CompilerOptions = {
noEmit: true,
target: ts.ScriptTarget.ESNext,
module: ts.ModuleKind.ES2020,
lib: ['lib.esnext.d.ts', 'lib.dom.d.ts'],
typeRoots: ['node_modules/@types'],
types: ['node'],
moduleResolution: ts.ModuleResolutionKind.Node10,
paths: tsconfig.compilerOptions.paths,
baseUrl: './',
strict: true,
};
const host = ts.createCompilerHost(options);
const program = ts.createProgram([sourceFilePath], options, {
...host,
getSourceFile: (filePath, languageVersion) => {
if (filePath === sourceFilePath) {
return ts.createSourceFile(
filePath,
sourceCode,
languageVersion,
true,
);
}

const fileContent = fs.readFileSync(filePath, 'utf8');

return ts.createSourceFile(
filePath,
fileContent,
languageVersion,
true,
);
},
getCurrentDirectory: () => process.cwd(),
});

checker = program.getTypeChecker();
sourceFile = program.getSourceFile(sourceFilePath)!;
});

it('should have void data and void result for empty component', () => {
expect(getVariableType('dialog1')).toBe('(data: void) => Observable<void>');
});

it('should have number data and string result for component with TuiDialogContext<string, number>', () => {
expect(getVariableType('dialog2')).toBe(
'(data: number) => Observable<string>',
);
});

it('should have number data and string result for component with TuiDialogContext<string, number> and other member', () => {
expect(getVariableType('dialog3')).toBe(
'(data: number) => Observable<string>',
);
});

it('should have number data and string result for component with TuiDialogContext<string, number> and other member that any', () => {
expect(getVariableType('dialog4')).toBe(
'(data: number) => Observable<string>',
);
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,13 @@ import {injectContext} from '@taiga-ui/polymorpheus';
})
export class DialogExample {
private readonly dialogs = inject(TuiDialogService);
private readonly context = injectContext<TuiDialogContext<number, number>>();

protected value: number | null = null;
protected name = '';
protected items = [10, 50, 100];

public readonly context = injectContext<TuiDialogContext<number, number>>();

protected get hasValue(): boolean {
return this.value !== null;
}
Expand Down
22 changes: 7 additions & 15 deletions projects/demo/src/modules/components/dialog/examples/2/index.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import {Component, inject, INJECTOR} from '@angular/core';
import {Component} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiButton, TuiDialogService} from '@taiga-ui/core';
import {PolymorpheusComponent} from '@taiga-ui/polymorpheus';
import {TuiButton, tuiDialog} from '@taiga-ui/core';

import {DialogExample} from './dialog-example/dialog-example.component';

Expand All @@ -14,20 +13,13 @@ import {DialogExample} from './dialog-example/dialog-example.component';
changeDetection,
})
export default class Example {
private readonly dialogs = inject(TuiDialogService);
private readonly injector = inject(INJECTOR);

private readonly dialog = this.dialogs.open<number>(
new PolymorpheusComponent(DialogExample, this.injector),
{
data: 237,
dismissible: true,
label: 'Heading',
},
);
private readonly dialog = tuiDialog(DialogExample, {
dismissible: true,
label: 'Heading',
});

protected showDialog(): void {
this.dialog.subscribe({
this.dialog(237).subscribe({
next: (data) => {
console.info(`Dialog emitted data = ${data}`);
},
Expand Down
20 changes: 8 additions & 12 deletions projects/demo/src/modules/components/dialog/examples/7/index.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import {Component, inject, INJECTOR} from '@angular/core';
import {Component} from '@angular/core';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {TuiButton, TuiDialogService} from '@taiga-ui/core';
import {PolymorpheusComponent} from '@taiga-ui/polymorpheus';
import {TuiButton, tuiDialog} from '@taiga-ui/core';

import {SearchDialogExample} from './search-example/search-dialog-example.component';

Expand All @@ -14,16 +13,13 @@ import {SearchDialogExample} from './search-example/search-dialog-example.compon
changeDetection,
})
export default class Example {
private readonly dialogs = inject(TuiDialogService);
private readonly injector = inject(INJECTOR);
private readonly dialog = tuiDialog(SearchDialogExample, {
size: 'page',
closeable: true,
dismissible: true,
});

protected showDialog(): void {
this.dialogs
.open(new PolymorpheusComponent(SearchDialogExample, this.injector), {
size: 'page',
closeable: true,
dismissible: true,
})
.subscribe();
this.dialog().subscribe();
}
}
29 changes: 11 additions & 18 deletions projects/demo/src/modules/components/dialog/examples/9/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,8 @@ import {takeUntilDestroyed} from '@angular/core/rxjs-interop';
import {FormControl, ReactiveFormsModule} from '@angular/forms';
import {changeDetection} from '@demo/emulate/change-detection';
import {encapsulation} from '@demo/emulate/encapsulation';
import {
TuiButton,
TuiDialogService,
TuiHint,
TuiIconPipe,
TuiTextfield,
} from '@taiga-ui/core';
import {TuiButton, tuiDialog, TuiHint, TuiIconPipe, TuiTextfield} from '@taiga-ui/core';
import {TuiInputNumberModule, TuiTextfieldControllerModule} from '@taiga-ui/legacy';
import {PolymorpheusComponent} from '@taiga-ui/polymorpheus';

import {PayModal} from './pay-modal/pay-modal.component';

Expand All @@ -32,20 +25,20 @@ import {PayModal} from './pay-modal/pay-modal.component';
changeDetection,
})
export default class Example {
private readonly dialogs = inject(TuiDialogService);
private readonly destroyRef = inject(DestroyRef);
private readonly payModal = tuiDialog(PayModal, {
size: 'auto',
closeable: true,
});

protected readonly amountControl = new FormControl(100);
protected readonly amountControl = new FormControl(100, {
nonNullable: true,
});

protected payByCard(): void {
this.dialogs
.open(new PolymorpheusComponent(PayModal), {
size: 'auto',
closeable: true,
data: {
amount: this.amountControl.value,
},
})
this.payModal({
amount: this.amountControl.value,
})
.pipe(takeUntilDestroyed(this.destroyRef))
.subscribe();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,18 +74,22 @@ export class PayModal implements OnInit {
saveCard: new FormControl(true),
});

protected readonly context =
injectContext<TuiDialogContext<void, DataForPayCardModal>>();

protected readonly iOS = inject(TUI_IS_IOS);

protected cards: AccountCard[] = [];
protected paymentMode: TuiValuesOf<typeof PaymentMode> = PaymentMode.ByNewCard;
protected loading$ = new BehaviorSubject(false);
protected payProcessing$ = new BehaviorSubject(false);
protected amount: number = this.context?.data?.amount ?? 0;
protected amount = 0;
protected readonly PAYMENT_MODE = PaymentMode;

public readonly context =
injectContext<TuiDialogContext<void, DataForPayCardModal>>();

constructor() {
this.amount = this.context?.data?.amount ?? 0;
}

public ngOnInit(): void {
this.fetchCardsAndSetPrimaryCard();
}
Expand Down

0 comments on commit 5d15122

Please sign in to comment.