release(tach-typography): v0.2.0

This commit is contained in:
2026-03-25 14:01:45 +03:00
parent 9eaca089e5
commit a8c2eaa2fd
38 changed files with 1319 additions and 501 deletions

View File

@@ -1,6 +1,19 @@
import { NgModule } from "@angular/core";
import { Directive, ElementRef, Input, OnChanges, Renderer2, SimpleChanges } from "@angular/core";
import { NzTypographyModule } from "ng-zorro-antd/typography";
import { NgStyle, NgSwitch, NgSwitchCase, NgSwitchDefault } from "@angular/common";
import {
ChangeDetectionStrategy,
Component,
Directive,
ElementRef,
EventEmitter,
inject,
Input,
NgModule,
OnChanges,
Output,
Renderer2,
SimpleChanges,
} from "@angular/core";
import { NzTypographyComponent, NzTypographyModule } from "ng-zorro-antd/typography";
import {
tachTypographyClassList,
@@ -20,6 +33,18 @@ export interface AngularTypographyRenderOptions extends TypographyRenderOptions
preserveStyle?: Record<string, string | number>;
}
export type TachTypographyHostTag = "span" | "p" | "a" | "h1" | "h2" | "h3" | "h4";
type NonFunctionNonEmitterKeys<T> = {
[K in keyof T]-?: T[K] extends (...args: never[]) => unknown
? never
: T[K] extends EventEmitter<unknown>
? never
: K;
}[keyof T];
type NzTypographyInputKey = Extract<NonFunctionNonEmitterKeys<NzTypographyComponent>, `nz${string}`>;
export type TachTypographyNzProps = Partial<Pick<NzTypographyComponent, NzTypographyInputKey>>;
export type TachTypographyHostProps = Record<string, unknown>;
const camelToKebab = (value: string): string =>
value.replace(/[A-Z]/g, match => `-${match.toLowerCase()}`);
@@ -133,8 +158,292 @@ export class TachTypographyDirective implements OnChanges {
}
}
@Directive({
selector: "[tachTypographyNzProps]",
standalone: true,
})
export class TachTypographyNzPropsDirective implements OnChanges {
@Input() tachTypographyNzProps: TachTypographyNzProps | null | undefined;
private readonly appliedNzKeys = new Set<string>();
private readonly nzTypography = inject(NzTypographyComponent, { self: true, optional: true });
ngOnChanges(): void {
if (!this.nzTypography) {
return;
}
const nzTypography = this.nzTypography as unknown as Record<string, unknown>;
const nextProps = this.tachTypographyNzProps ?? {};
const nextKeys = new Set<string>();
for (const [key, value] of Object.entries(nextProps)) {
if (!key.startsWith("nz")) {
continue;
}
nzTypography[key] = value;
nextKeys.add(key);
}
for (const key of this.appliedNzKeys) {
if (!nextKeys.has(key)) {
nzTypography[key] = undefined;
}
}
this.appliedNzKeys.clear();
for (const key of nextKeys) {
this.appliedNzKeys.add(key);
}
}
}
@Directive({
selector: "[tachTypographyHostProps]",
standalone: true,
})
export class TachTypographyHostPropsDirective implements OnChanges {
@Input() tachTypographyHostProps: TachTypographyHostProps | null | undefined;
private readonly appliedHostProps = new Map<string, "attr" | "prop">();
constructor(
private readonly elementRef: ElementRef<HTMLElement>,
private readonly renderer: Renderer2,
) {}
ngOnChanges(): void {
const nextProps = this.tachTypographyHostProps ?? {};
const nextAppliedProps = new Map<string, "attr" | "prop">();
for (const [key, value] of Object.entries(nextProps)) {
if (key === "class" || key === "className" || key === "style") {
continue;
}
if (value === undefined || value === null) {
continue;
}
const applyAsAttr = this.shouldApplyAsAttribute(key, value);
if (applyAsAttr) {
if (typeof value === "boolean") {
if (value) {
this.renderer.setAttribute(this.elementRef.nativeElement, key, "");
} else {
this.renderer.removeAttribute(this.elementRef.nativeElement, key);
}
} else {
this.renderer.setAttribute(this.elementRef.nativeElement, key, String(value));
}
nextAppliedProps.set(key, "attr");
continue;
}
this.renderer.setProperty(this.elementRef.nativeElement, key, value);
nextAppliedProps.set(key, "prop");
}
for (const [key, kind] of this.appliedHostProps.entries()) {
if (nextAppliedProps.has(key)) {
continue;
}
if (kind === "attr") {
this.renderer.removeAttribute(this.elementRef.nativeElement, key);
} else {
this.renderer.setProperty(this.elementRef.nativeElement, key, undefined);
}
}
this.appliedHostProps.clear();
for (const [key, kind] of nextAppliedProps.entries()) {
this.appliedHostProps.set(key, kind);
}
}
private shouldApplyAsAttribute(key: string, value: unknown): boolean {
if (key.startsWith("data-") || key.startsWith("aria-")) {
return true;
}
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
return !this.hasPropertyOnElement(key);
}
return false;
}
private hasPropertyOnElement(key: string): boolean {
return key in this.elementRef.nativeElement;
}
}
@Component({
selector: "tach-typography",
standalone: true,
imports: [
NzTypographyModule,
TachTypographyDirective,
TachTypographyNzPropsDirective,
TachTypographyHostPropsDirective,
NgSwitch,
NgSwitchCase,
NgSwitchDefault,
NgStyle,
],
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<ng-container [ngSwitch]="hostTag">
<p
*ngSwitchCase="'p'"
nz-typography
[tachTypography]="variant"
[tachTypographyColor]="color"
[tachTypographyWeight]="weight"
[tachTypographyClickable]="clickable"
[tachTypographyClassName]="className"
[tachTypographyEllipsis]="ellipsis"
[tachTypographyNzProps]="nzProps"
[tachTypographyHostProps]="hostProps"
[ngStyle]="preserveStyle"
(click)="handleClick($event)"
>
<ng-content />
</p>
<a
*ngSwitchCase="'a'"
nz-typography
[tachTypography]="variant"
[tachTypographyColor]="color"
[tachTypographyWeight]="weight"
[tachTypographyClickable]="clickable"
[tachTypographyClassName]="className"
[tachTypographyEllipsis]="ellipsis"
[tachTypographyNzProps]="nzProps"
[tachTypographyHostProps]="hostProps"
[ngStyle]="preserveStyle"
(click)="handleClick($event)"
>
<ng-content />
</a>
<h1
*ngSwitchCase="'h1'"
nz-typography
[tachTypography]="variant"
[tachTypographyColor]="color"
[tachTypographyWeight]="weight"
[tachTypographyClickable]="clickable"
[tachTypographyClassName]="className"
[tachTypographyEllipsis]="ellipsis"
[tachTypographyNzProps]="nzProps"
[tachTypographyHostProps]="hostProps"
[ngStyle]="preserveStyle"
(click)="handleClick($event)"
>
<ng-content />
</h1>
<h2
*ngSwitchCase="'h2'"
nz-typography
[tachTypography]="variant"
[tachTypographyColor]="color"
[tachTypographyWeight]="weight"
[tachTypographyClickable]="clickable"
[tachTypographyClassName]="className"
[tachTypographyEllipsis]="ellipsis"
[tachTypographyNzProps]="nzProps"
[tachTypographyHostProps]="hostProps"
[ngStyle]="preserveStyle"
(click)="handleClick($event)"
>
<ng-content />
</h2>
<h3
*ngSwitchCase="'h3'"
nz-typography
[tachTypography]="variant"
[tachTypographyColor]="color"
[tachTypographyWeight]="weight"
[tachTypographyClickable]="clickable"
[tachTypographyClassName]="className"
[tachTypographyEllipsis]="ellipsis"
[tachTypographyNzProps]="nzProps"
[tachTypographyHostProps]="hostProps"
[ngStyle]="preserveStyle"
(click)="handleClick($event)"
>
<ng-content />
</h3>
<h4
*ngSwitchCase="'h4'"
nz-typography
[tachTypography]="variant"
[tachTypographyColor]="color"
[tachTypographyWeight]="weight"
[tachTypographyClickable]="clickable"
[tachTypographyClassName]="className"
[tachTypographyEllipsis]="ellipsis"
[tachTypographyNzProps]="nzProps"
[tachTypographyHostProps]="hostProps"
[ngStyle]="preserveStyle"
(click)="handleClick($event)"
>
<ng-content />
</h4>
<span
*ngSwitchDefault
nz-typography
[tachTypography]="variant"
[tachTypographyColor]="color"
[tachTypographyWeight]="weight"
[tachTypographyClickable]="clickable"
[tachTypographyClassName]="className"
[tachTypographyEllipsis]="ellipsis"
[tachTypographyNzProps]="nzProps"
[tachTypographyHostProps]="hostProps"
[ngStyle]="preserveStyle"
(click)="handleClick($event)"
>
<ng-content />
</span>
</ng-container>
`,
})
export class TachTypographyComponent {
@Input("as") hostTag: TachTypographyHostTag = "span";
@Input() variant: TypographyVariant = "Body";
@Input() color: TypographyColor = "primary";
@Input() weight: TypographyWeight = "normal";
@Input() clickable = false;
@Input() className: string | undefined;
@Input() ellipsis: EllipsisOptions | undefined;
@Input() nzProps: TachTypographyNzProps | undefined;
@Input() hostProps: TachTypographyHostProps | undefined;
@Input() preserveStyle: Record<string, string | number> | undefined;
@Output() readonly tachClick = new EventEmitter<MouseEvent>();
handleClick(event: MouseEvent): void {
this.tachClick.emit(event);
}
}
@NgModule({
imports: [NzTypographyModule, TachTypographyDirective],
exports: [NzTypographyModule, TachTypographyDirective],
imports: [
NzTypographyModule,
TachTypographyDirective,
TachTypographyNzPropsDirective,
TachTypographyHostPropsDirective,
TachTypographyComponent,
],
exports: [
NzTypographyModule,
TachTypographyDirective,
TachTypographyNzPropsDirective,
TachTypographyHostPropsDirective,
TachTypographyComponent,
],
})
export class TachTypographyNzModule {}

View File

@@ -0,0 +1 @@
export * from "./index";