Trademark Patent Portfolio
blocklegalUSPTO and WIPO intellectual property asset portfolio tracker, patent grant status, jurisdiction coverage, and maintenance fee timeline with docket action workflow.
Installation
$pnpm dlx uipkge-ng@latest add trademark-patent-portfolio$npx uipkge-ng@latest add trademark-patent-portfolio$yarn dlx uipkge-ng@latest add trademark-patent-portfolio$bunx uipkge-ng@latest add trademark-patent-portfolioManifest:
https://uipkge.dev/r/angular/trademark-patent-portfolio.jsonInstalls to:src/app/components/blocks/trademark-patent-portfolio/Variants
Loading interactive previews…
Loading interactive previews…
Schema
Type aliases exported from this item's source. Use these to shape the data you pass in.
IpAssetinterface IpAsset {
id: string
identifier: string
officialNumber: string
title: string
type: AssetType
jurisdictionCode: string
jurisdictionName: string
jurisdictionFlag: string
patentOffice: string
applicationDate: string
grantDate: string
status: FilingStatus
nextDeadline: string
deadlineNumber: string
deadlineNote: string
ipcClass: string
attorney: string
gazetteUrl: string
gazetteLabel: string
}npm dependencies
Files installed (2)
src/app/components/blocks/trademark-patent-portfolio/trademark-patent-portfolio.component.ts29.4 kB
// USPTO and WIPO intellectual property asset portfolio tracker: patent grant status, // jurisdiction coverage, and maintenance fee timeline with docket action workflow. // Port of the React block 1:1. import { ChangeDetectionStrategy, Component, Input, computed, signal } from '@angular/core' import { Award, BadgeCheck, Building2, Calendar, Check, CheckCircle2, ChevronDown, Clock, Copy, Download, ExternalLink, FileText, Filter, LucideAngularModule, MoreHorizontal, Plus, Scale, Search, UserCheck, } from 'lucide-angular' import { cn } from '@/lib/utils' import { UiBadgeComponent } from '@/ui/badge/badge.component' import { UiButtonComponent } from '@/ui/button/button.component' import { UiCardComponent, UiCardContentComponent, UiCardDescriptionComponent, UiCardHeaderComponent, UiCardTitleComponent, } from '@/ui/card/card.component' import { UiDropdownMenuComponent, UiDropdownMenuContentComponent, UiDropdownMenuItemComponent, UiDropdownMenuLabelComponent, UiDropdownMenuSeparatorComponent, UiDropdownMenuTriggerComponent, } from '@/ui/dropdown-menu/dropdown-menu.component' import { UiInputComponent } from '@/ui/input/input.component' import { UiTableBodyComponent, UiTableCellComponent, UiTableComponent, UiTableHeadComponent, UiTableHeaderComponent, UiTableRowComponent, } from '@/ui/table/table.component' export type AssetType = 'Utility Patent' | 'Trademark' | 'PCT International' export type FilingStatus = 'Granted / Registered' | 'Published / Under Exam' export interface IpAsset { id: string identifier: string officialNumber: string title: string type: AssetType jurisdictionCode: string jurisdictionName: string jurisdictionFlag: string patentOffice: string applicationDate: string grantDate: string status: FilingStatus nextDeadline: string deadlineNumber: string deadlineNote: string ipcClass: string attorney: string gazetteUrl: string gazetteLabel: string } const ipAssets: IpAsset[] = [ { id: 'ip-1', identifier: 'US Patent #11,849,201', officialNumber: 'US 11,849,201 B2', title: 'Zero-Dependency Dual-Framework AST Generation', type: 'Utility Patent', jurisdictionCode: 'US', jurisdictionName: 'United States', jurisdictionFlag: '🇺🇸', patentOffice: 'USPTO', applicationDate: '2023-04-12', grantDate: '2024-06-18', status: 'Granted / Registered', nextDeadline: 'Dec 2026', deadlineNumber: '2026-12-15', deadlineNote: 'USPTO 3.5-Year Maintenance Window', ipcClass: 'CPC G06F 8/41 · Abstract Syntax Trees', attorney: 'Vance & Morrison LLP', gazetteUrl: 'https://patents.google.com/patent/US11849201B2/en', gazetteLabel: 'View USPTO Gazette', }, { id: 'ip-2', identifier: 'US Patent #11,420,184', officialNumber: 'US 11,420,184 B1', title: 'Perceptually Uniform OKLCH Dark Mode Algorithm', type: 'Utility Patent', jurisdictionCode: 'US', jurisdictionName: 'United States', jurisdictionFlag: '🇺🇸', patentOffice: 'USPTO', applicationDate: '2022-09-14', grantDate: '2023-08-22', status: 'Granted / Registered', nextDeadline: 'Feb 2027', deadlineNumber: '2027-02-22', deadlineNote: 'USPTO 3.5-Year Maintenance Window', ipcClass: 'CPC G06T 11/00 · Perceptual Colorimetry', attorney: 'Vance & Morrison LLP', gazetteUrl: 'https://patents.google.com/patent/US11420184B1/en', gazetteLabel: 'View USPTO Gazette', }, { id: 'ip-3', identifier: 'US Trademark #98421042', officialNumber: 'Reg. #7,294,810 (SN 98421042)', title: 'UIPKGE™ (Class 09, 42)', type: 'Trademark', jurisdictionCode: 'US', jurisdictionName: 'United States', jurisdictionFlag: '🇺🇸', patentOffice: 'USPTO', applicationDate: '2023-02-10', grantDate: '2023-11-14', status: 'Granted / Registered', nextDeadline: 'Nov 2029', deadlineNumber: '2029-11-14', deadlineNote: 'Section 8 & 15 Affidavit of Use', ipcClass: 'IC 009, 042 · Computer Software & SaaS', attorney: 'Kilpatrick Townsend & Stockton', gazetteUrl: 'https://tsdr.uspto.gov/', gazetteLabel: 'View USPTO TSDR', }, { id: 'ip-4', identifier: 'WIPO PCT/US2026/018249', officialNumber: 'WO 2026/148201 A1', title: 'Headless Component State Serialization', type: 'PCT International', jurisdictionCode: 'WIPO', jurisdictionName: 'WIPO International', jurisdictionFlag: '🌍', patentOffice: 'WIPO IB', applicationDate: '2026-01-15', grantDate: 'Pending', status: 'Published / Under Exam', nextDeadline: 'Jul 2027', deadlineNumber: '2027-07-15', deadlineNote: '30-Mo National Phase Entry Deadline', ipcClass: 'IPC G06F 9/44 · Component State Transfer', attorney: 'Vance & Morrison LLP', gazetteUrl: 'https://patentscope.wipo.int/', gazetteLabel: 'View WIPO Patentscope', }, { id: 'ip-5', identifier: 'EP Patent #3,948,102', officialNumber: 'EP 3,948,102 A1', title: 'Reactive Virtual DOM Diffing Engine for Web Components', type: 'Utility Patent', jurisdictionCode: 'EU', jurisdictionName: 'European Union', jurisdictionFlag: '🇪🇺', patentOffice: 'EPO', applicationDate: '2024-03-02', grantDate: 'Pending', status: 'Published / Under Exam', nextDeadline: 'Mar 2027', deadlineNumber: '2027-03-31', deadlineNote: 'EPO 3rd Year Annuity Maintenance Fee', ipcClass: 'CPC G06F 9/455 · Virtualized DOM Execution', attorney: 'Bardehle Pagenberg Partnerschaft', gazetteUrl: 'https://worldwide.espacenet.com/', gazetteLabel: 'View EPO Espacenet', }, { id: 'ip-6', identifier: 'EU Trademark #018942001', officialNumber: 'Reg. #018942001', title: 'UIPKGE DESIGN SYSTEM™ (Class 09, 35, 42)', type: 'Trademark', jurisdictionCode: 'EU', jurisdictionName: 'European Union', jurisdictionFlag: '🇪🇺', patentOffice: 'EUIPO', applicationDate: '2023-08-05', grantDate: '2024-01-19', status: 'Granted / Registered', nextDeadline: 'May 2030', deadlineNumber: '2030-05-08', deadlineNote: 'EUIPO 10-Year Trademark Renewal', ipcClass: 'IC 009, 035, 042 · Design Systems & Tooling', attorney: 'Bardehle Pagenberg Partnerschaft', gazetteUrl: 'https://euipo.europa.eu/eSearch/', gazetteLabel: 'View EUIPO eSearch', }, ] @Component({ changeDetection: ChangeDetectionStrategy.Eager, selector: 'ui-trademark-patent-portfolio, [ui-trademark-patent-portfolio]', standalone: true, // React renders the root <div> itself: the host stays out of layout and `class` goes to the root. host: { '[attr.class]': '"contents"' }, imports: [ LucideAngularModule, UiBadgeComponent, UiButtonComponent, UiCardComponent, UiCardContentComponent, UiCardDescriptionComponent, UiCardHeaderComponent, UiCardTitleComponent, UiDropdownMenuComponent, UiDropdownMenuContentComponent, UiDropdownMenuItemComponent, UiDropdownMenuLabelComponent, UiDropdownMenuSeparatorComponent, UiDropdownMenuTriggerComponent, UiInputComponent, UiTableComponent, UiTableBodyComponent, UiTableCellComponent, UiTableHeadComponent, UiTableHeaderComponent, UiTableRowComponent, ], template: ` <div data-slot="trademark-patent-portfolio" [class]="rootClass"> <!-- Header Section --> <div ui-card class="border-border shadow-xs"> <div ui-card-header class="flex flex-col gap-4 pb-6 lg:flex-row lg:items-center lg:justify-between"> <div class="space-y-1.5"> <div class="flex flex-wrap items-center gap-2"> <span ui-badge variant="outline" class="gap-1.5 font-medium"> <lucide-icon [img]="Building2" class="text-muted-foreground size-3.5" /> <span>UIPKGE Technologies Inc.</span> </span> <span ui-badge wrap variant="secondary" class="gap-1 font-mono text-xs"> <lucide-icon [img]="Scale" class="text-primary size-3" /> <span>12 Patents · 6 Registered Trademarks</span> </span> </div> <h2 ui-card-title class="text-xl font-bold tracking-tight sm:text-2xl"> Intellectual Property & Patent Portfolio </h2> <p ui-card-description class="text-xs sm:text-sm"> USPTO, EPO, and WIPO asset docket, grant prosecution tracker, and statutory maintenance timeline. </p> </div> <div class="flex flex-wrap items-center gap-2.5"> <button ui-button aria-label="Download attachment" variant="outline" class="gap-2 shadow-xs" (click)="handleExport()" > <lucide-icon [img]="Download" class="size-4" /> <span>Export Portfolio Report</span> </button> <button ui-button variant="default" class="gap-2 shadow-xs" (click)="handleFileNewAsset()"> <lucide-icon [img]="Plus" class="size-4" /> <span>File New IP Asset</span> </button> </div> </div> </div> <!-- Feedback Notification Toast/Banner if active --> @if (feedbackMessage()) { <div class="border-primary/30 bg-primary/10 text-primary flex items-center justify-between rounded-lg border px-4 py-2.5 text-xs font-medium shadow-xs transition-colors" > <div class="flex items-center gap-2"> <lucide-icon [img]="CheckCircle2" class="size-4 shrink-0" /> <span>{{ feedbackMessage() }}</span> </div> <button type="button" (click)="feedbackMessage.set(null)" class="text-primary/70 hover:text-primary rounded text-xs underline underline-offset-2" > Dismiss </button> </div> } <!-- 4 IP Portfolio KPI Cards Grid --> <div class="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4"> <div ui-card class="border-border shadow-xs"> <div ui-card-content class="p-5"> <div class="flex items-center justify-between"> <span class="text-muted-foreground text-xs font-medium">Granted Patents</span> <div class="bg-success/10 text-success flex size-8 items-center justify-center rounded-md"> <lucide-icon [img]="Award" class="size-4" /> </div> </div> <div class="mt-3"> <div class="text-success text-2xl font-bold tracking-tight tabular-nums">8 Patents Granted</div> <p class="text-muted-foreground mt-1 text-xs">6 USPTO · 2 EPO · 100% Enforceable</p> </div> </div> </div> <div ui-card class="border-border shadow-xs"> <div ui-card-content class="p-5"> <div class="flex items-center justify-between"> <span class="text-muted-foreground text-xs font-medium">Pending Examination</span> <div class="bg-warning/10 text-warning flex size-8 items-center justify-center rounded-md"> <lucide-icon [img]="Clock" class="size-4" /> </div> </div> <div class="mt-3"> <div class="text-warning text-2xl font-bold tracking-tight tabular-nums">4 Under Examination · USPTO</div> <p class="text-muted-foreground mt-1 text-xs">2 PCT International · 1 USPTO · 1 EPO</p> </div> </div> </div> <div ui-card class="border-border shadow-xs"> <div ui-card-content class="p-5"> <div class="flex items-center justify-between"> <span class="text-muted-foreground text-xs font-medium">Registered Trademarks</span> <div class="bg-info/10 text-info flex size-8 items-center justify-center rounded-md"> <lucide-icon [img]="BadgeCheck" class="size-4" /> </div> </div> <div class="mt-3"> <div class="text-info text-2xl font-bold tracking-tight tabular-nums">6 Trademarks Active</div> <p class="text-muted-foreground mt-1 text-xs">Classes 09, 35, 42 · Global Protection</p> </div> </div> </div> <div ui-card class="border-border shadow-xs"> <div ui-card-content class="p-5"> <div class="flex items-center justify-between"> <span class="text-muted-foreground text-xs font-medium">Next Maintenance Fee</span> <div class="bg-destructive/10 text-destructive flex size-8 items-center justify-center rounded-md"> <lucide-icon [img]="Calendar" class="size-4" /> </div> </div> <div class="mt-3"> <div class="text-destructive text-2xl font-bold tracking-tight tabular-nums"> Due Dec 15, 2026 · USPTO 3.5-Year Window </div> <p class="text-muted-foreground mt-1 text-xs">Est. fee $2,000 · Surcharge window opens</p> </div> </div> </div> </div> <!-- Filters & Table Container Card --> <div ui-card class="border-border shadow-xs"> <div ui-card-header class="border-border border-b pb-4"> <div class="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between"> <div> <h3 ui-card-title class="text-base font-semibold">Registered Intellectual Property Assets</h3> <p ui-card-description class="text-xs"> Showing {{ filteredAssets().length }} of {{ allAssets.length }} assets across USPTO, EUIPO, and WIPO </p> </div> <div class="flex flex-wrap items-center gap-2"> <div class="relative min-w-[200px] flex-1 sm:w-64"> <lucide-icon [img]="Search" class="text-muted-foreground pointer-events-none absolute top-1/2 left-2.5 size-4 -translate-y-1/2" /> <ui-input [value]="searchQuery()" (valueChange)="searchQuery.set($event)" type="text" placeholder="Search patent #, title, class..." class="h-8 pl-8 text-xs" /> </div> <ui-dropdown-menu> <button ui-button ui-dropdown-menu-trigger variant="outline" size="sm" class="h-8 gap-1.5 text-xs"> <lucide-icon [img]="Filter" class="size-3.5 opacity-70" /> <span>{{ selectedType() === 'all' ? 'Type: All' : selectedType() }}</span> <lucide-icon [img]="ChevronDown" class="size-3 opacity-50" /> </button> <ui-dropdown-menu-content align="end" class="w-48"> <ui-dropdown-menu-label class="text-xs">Filter by Type</ui-dropdown-menu-label> <ui-dropdown-menu-separator /> <ui-dropdown-menu-item (select)="selectedType.set('all')">All Types</ui-dropdown-menu-item> <ui-dropdown-menu-item (select)="selectedType.set('Utility Patent')" >Utility Patent</ui-dropdown-menu-item > <ui-dropdown-menu-item (select)="selectedType.set('Trademark')">Trademark</ui-dropdown-menu-item> <ui-dropdown-menu-item (select)="selectedType.set('PCT International')" >PCT International</ui-dropdown-menu-item > </ui-dropdown-menu-content> </ui-dropdown-menu> <ui-dropdown-menu> <button ui-button ui-dropdown-menu-trigger variant="outline" size="sm" class="h-8 gap-1.5 text-xs"> <span>{{ selectedStatus() === 'all' ? 'Status: All' : selectedStatus() }}</span> <lucide-icon [img]="ChevronDown" class="size-3 opacity-50" /> </button> <ui-dropdown-menu-content align="end" class="w-52"> <ui-dropdown-menu-label class="text-xs">Filter by Status</ui-dropdown-menu-label> <ui-dropdown-menu-separator /> <ui-dropdown-menu-item (select)="selectedStatus.set('all')">All Statuses</ui-dropdown-menu-item> <ui-dropdown-menu-item (select)="selectedStatus.set('Granted / Registered')"> Granted / Registered </ui-dropdown-menu-item> <ui-dropdown-menu-item (select)="selectedStatus.set('Published / Under Exam')"> Published / Under Exam </ui-dropdown-menu-item> </ui-dropdown-menu-content> </ui-dropdown-menu> </div> </div> </div> <div ui-card-content class="p-0"> <div class="overflow-x-auto"> <table ui-table> <thead ui-table-header class="bg-muted/40"> <tr ui-table-row> <th ui-table-head class="min-w-[280px]">Asset Identifier & Title</th> <th ui-table-head class="w-[140px]">Type</th> <th ui-table-head class="w-[160px]">Jurisdiction</th> <th ui-table-head class="w-[160px]">App / Grant Date</th> <th ui-table-head class="w-[170px]">Filing Status</th> <th ui-table-head class="w-[180px]">Next Renewal / Deadline</th> <th ui-table-head class="w-[70px] text-right"><span class="sr-only">Actions</span></th> </tr> </thead> <tbody ui-table-body> @for (asset of filteredAssets(); track asset.id) { <tr ui-table-row class="hover:bg-muted/40 transition-colors"> <td ui-table-cell class="py-3.5 align-top"> <div class="space-y-1"> <div class="flex flex-wrap items-center gap-1.5"> <span class="text-foreground font-mono text-xs font-bold tracking-tight"> {{ asset.identifier }} </span> <button type="button" (click)="copyText(asset.id, asset.officialNumber)" class="text-muted-foreground hover:text-foreground focus-visible:ring-ring inline-flex items-center gap-1 rounded p-0.5 transition-colors focus-visible:ring-2 focus-visible:outline-hidden" [attr.title]="'Copy ' + asset.officialNumber" > @if (copiedId() === asset.id) { <lucide-icon [img]="Check" class="text-success size-3" /> } @else { <lucide-icon [img]="Copy" class="size-3" /> } </button> </div> <div class="text-foreground text-xs font-medium">{{ asset.title }}</div> <div class="text-muted-foreground flex flex-wrap items-center gap-2 text-xs"> <span>{{ asset.ipcClass }}</span> <span>•</span> <span>Counsel: {{ asset.attorney }}</span> </div> </div> </td> <td ui-table-cell class="align-top"> <span ui-badge variant="outline" [class]="typeBadgeClass(asset.type)"> {{ asset.type }} </span> </td> <td ui-table-cell class="align-top"> <div class="flex items-center gap-2"> <span class="text-base leading-none" [attr.aria-label]="asset.jurisdictionName"> {{ asset.jurisdictionFlag }} </span> <div> <div class="text-foreground text-xs font-medium">{{ asset.jurisdictionName }}</div> <div class="text-muted-foreground font-mono text-xs">{{ asset.patentOffice }}</div> </div> </div> </td> <td ui-table-cell class="align-top"> <div class="space-y-0.5 font-mono text-xs tabular-nums"> <div class="text-muted-foreground flex items-center gap-1.5"> <span class="text-muted-foreground/70">Filing:</span> <span class="text-foreground font-medium">{{ asset.applicationDate }}</span> </div> <div class="text-muted-foreground flex items-center gap-1.5"> <span class="text-muted-foreground/70">Grant:</span> <span [class]=" asset.grantDate === 'Pending' ? 'text-warning font-medium' : 'text-foreground font-medium' " > {{ asset.grantDate }} </span> </div> </div> </td> <td ui-table-cell class="align-top"> @if (asset.status === 'Granted / Registered') { <div class="border-success/30 bg-success/10 text-success inline-flex items-center gap-1.5 rounded-full border px-2.5 py-0.5 text-xs font-medium" > <span class="relative flex size-1.5"> <span class="bg-success absolute inline-flex h-full w-full rounded-full opacity-75"></span> <span class="bg-success relative inline-flex size-1.5 rounded-full"></span> </span> <span>Granted / Registered</span> </div> } @else { <div class="border-warning/30 bg-warning/10 text-warning inline-flex items-center gap-1.5 rounded-full border px-2.5 py-0.5 text-xs font-medium" > <lucide-icon [img]="Clock" class="text-warning size-3" /> <span>Published / Under Exam</span> </div> } </td> <td ui-table-cell class="align-top"> <div class="space-y-0.5"> <div class="text-foreground flex items-center gap-1.5 font-mono text-xs font-semibold tabular-nums" > <lucide-icon [img]="Calendar" class="text-muted-foreground size-3.5" /> <span>{{ asset.nextDeadline }}</span> </div> <p class="text-muted-foreground text-xs leading-tight">{{ asset.deadlineNote }}</p> </div> </td> <td ui-table-cell class="py-3.5 text-right align-top"> <ui-dropdown-menu> <button ui-button ui-dropdown-menu-trigger variant="ghost" size="icon-sm" class="text-muted-foreground hover:text-foreground focus-visible:ring-ring" > <lucide-icon [img]="MoreHorizontal" class="size-4" /> <span class="sr-only">Open actions for {{ asset.identifier }}</span> </button> <ui-dropdown-menu-content align="end" class="w-56"> <ui-dropdown-menu-label class="text-xs">Asset Operations</ui-dropdown-menu-label> <ui-dropdown-menu-separator /> <ui-dropdown-menu-item (select)="handleAction(asset.gazetteLabel, asset)"> <lucide-icon [img]="ExternalLink" class="mr-2 size-4" /> <span>{{ asset.gazetteLabel }}</span> </ui-dropdown-menu-item> <ui-dropdown-menu-item (select)="handleAction('Download Grant Certificate', asset)"> <lucide-icon [img]="Download" class="mr-2 size-4" /> <span>Download Grant Certificate</span> </ui-dropdown-menu-item> <ui-dropdown-menu-item (select)="handleAction('Assign Counsel', asset)"> <lucide-icon [img]="UserCheck" class="mr-2 size-4" /> <span>Assign Counsel</span> </ui-dropdown-menu-item> <ui-dropdown-menu-separator /> <ui-dropdown-menu-item (select)="copyText(asset.id, asset.officialNumber)"> <lucide-icon [img]="Copy" class="mr-2 size-4" /> <span>Copy Docket Number</span> </ui-dropdown-menu-item> </ui-dropdown-menu-content> </ui-dropdown-menu> </td> </tr> } @if (filteredAssets().length === 0) { <tr ui-table-row> <td ui-table-cell [attr.colspan]="7" class="text-muted-foreground py-10 text-center text-xs"> <div class="flex flex-col items-center justify-center gap-2"> <lucide-icon [img]="FileText" class="text-muted-foreground/60 size-8" /> <p class="font-medium">No intellectual property assets match your search filters.</p> <button ui-button variant="outline" size="sm" class="mt-1 text-xs" (click)="resetFilters()"> Reset Filters </button> </div> </td> </tr> } </tbody> </table> </div> </div> </div> </div> `, }) export class UiTrademarkPatentPortfolioComponent { protected readonly Award = Award protected readonly BadgeCheck = BadgeCheck protected readonly Building2 = Building2 protected readonly Calendar = Calendar protected readonly Check = Check protected readonly CheckCircle2 = CheckCircle2 protected readonly ChevronDown = ChevronDown protected readonly Clock = Clock protected readonly Copy = Copy protected readonly Download = Download protected readonly ExternalLink = ExternalLink protected readonly FileText = FileText protected readonly Filter = Filter protected readonly MoreHorizontal = MoreHorizontal protected readonly Plus = Plus protected readonly Scale = Scale protected readonly Search = Search protected readonly UserCheck = UserCheck protected readonly allAssets = ipAssets @Input('class') className?: string readonly searchQuery = signal('') readonly selectedType = signal<'all' | AssetType>('all') readonly selectedStatus = signal<'all' | FilingStatus>('all') readonly copiedId = signal<string | null>(null) readonly feedbackMessage = signal<string | null>(null) private copyTimer: ReturnType<typeof setTimeout> | undefined private feedbackTimer: ReturnType<typeof setTimeout> | undefined readonly filteredAssets = computed(() => { const query = this.searchQuery().trim().toLowerCase() return ipAssets.filter((asset) => { const matchesType = this.selectedType() === 'all' || asset.type === this.selectedType() const matchesStatus = this.selectedStatus() === 'all' || asset.status === this.selectedStatus() const matchesSearch = !query || asset.identifier.toLowerCase().includes(query) || asset.officialNumber.toLowerCase().includes(query) || asset.title.toLowerCase().includes(query) || asset.jurisdictionName.toLowerCase().includes(query) || asset.patentOffice.toLowerCase().includes(query) || asset.ipcClass.toLowerCase().includes(query) return matchesType && matchesStatus && matchesSearch }) }) get rootClass(): string { return cn('w-full space-y-6', this.className) } typeBadgeClass(type: AssetType): string { return cn('font-mono text-xs font-medium', { 'border-chart-1/30 bg-chart-1/10 text-chart-1': type === 'Utility Patent', 'border-info/30 bg-info/10 text-info': type === 'Trademark', 'border-warning/30 bg-warning/10 text-warning': type === 'PCT International', }) } resetFilters(): void { this.searchQuery.set('') this.selectedType.set('all') this.selectedStatus.set('all') } copyText(id: string, text: string): void { try { void navigator.clipboard?.writeText(text) } catch { // Clipboard unavailable (e.g. jsdom without permissions) — still show feedback. } this.copiedId.set(id) clearTimeout(this.copyTimer) this.copyTimer = setTimeout(() => this.copiedId.set(null), 2000) } private flashFeedback(message: string): void { this.feedbackMessage.set(message) clearTimeout(this.feedbackTimer) this.feedbackTimer = setTimeout(() => this.feedbackMessage.set(null), 3000) } handleExport(): void { this.flashFeedback('Exporting IP Portfolio summary (CSV / PDF)...') } handleFileNewAsset(): void { this.flashFeedback('Opening USPTO / WIPO EFS-Web filing wizard...') } handleAction(action: string, asset: IpAsset): void { this.flashFeedback(`${action}: ${asset.identifier}`) } }src/app/components/blocks/trademark-patent-portfolio/index.ts0.1 kB
export { UiTrademarkPatentPortfolioComponent, type AssetType, type FilingStatus, type IpAsset, } from './trademark-patent-portfolio.component'
Raw manifest:https://uipkge.dev/r/angular/trademark-patent-portfolio.json