@sea-dev/widget-ng
v0.1.25
Published
Native Angular widget for Sea.dev document processing
Readme
@sea-dev/widget-ng
Native Angular Widget for Sea.dev Document Processing
A fully native Angular implementation of the Sea.dev document processing widget. Built with Angular standalone components, Signals, and RxJS for optimal performance and developer experience.
🎯 Features
- ✅ Zero React dependency - Pure Angular implementation
- ✅ Modern Angular - Angular 17+ block templates, Standalone components, Signals, RxJS
- ✅ Type-safe - Full TypeScript support with strict mode
- ✅ Lightweight - Small bundle size (~50-80KB)
- ✅ SSR ready - Compatible with Angular Universal
- ✅ Accessible - WCAG 2.1 compliant
- ✅ OnPush - Optimized change detection
- ✅ Reactive - Declarative state management with Signals
📦 Installation
npm install @sea-dev/widget-ngOr with pnpm:
pnpm add @sea-dev/widget-ngPeer Dependencies
This package requires:
@angular/core>= 17.0.0@angular/common>= 17.0.0@angular/cdk>= 17.0.0rxjs^7.0.0
🚀 Quick Start
1. Configure the API Key
Set up your Sea.dev API key at application startup:
import { bootstrapApplication } from '@angular/platform-browser';
import { APP_INITIALIZER } from '@angular/core';
import { SeaWidgetConfigService } from '@sea-dev/widget-ng';
import { AppComponent } from './app/app.component';
bootstrapApplication(AppComponent, {
providers: [
{
provide: APP_INITIALIZER,
multi: true,
useFactory: (config: SeaWidgetConfigService) => () => {
config.setApiKey('your-sea-dev-api-key');
},
deps: [SeaWidgetConfigService],
},
],
});2. Use the Widget
Import and use the widget component:
import { Component } from '@angular/core';
import { SeaWidgetComponent } from '@sea-dev/widget-ng';
@Component({
selector: 'app-root',
standalone: true,
imports: [SeaWidgetComponent],
template: `
<div class="container">
<sea-widget
apiKey="your-api-key-here"
[formId]="formId"
[dealId]="dealId"
/>
</div>
`,
})
export class AppComponent {
formId = 'form_123';
dealId = 'deal_456';
}📚 Components
SeaWidgetComponent
The main widget component providing the full document processing workflow.
Inputs
| Input | Type | Required | Description |
|-------|------|----------|-------------|
| apiKey | string | Yes | Sea.dev API key |
| formId | string | No | Filter submissions by form ID |
| dealId | string | No | Filter submissions by deal ID |
| submissionId | string | No | Pre-select a specific submission |
Example
import { Component } from '@angular/core';
import { SeaWidgetComponent } from '@sea-dev/widget-ng';
@Component({
selector: 'app-documents',
standalone: true,
imports: [SeaWidgetComponent],
template: `
<sea-widget
[apiKey]="apiKey"
[submissionId]="submissionId"
/>
`,
})
export class DocumentsComponent {
apiKey = 'sea_live_xxx';
submissionId = 'sub_123';
}SeaFileUploadComponent
Standalone file upload component with drag-and-drop support.
Inputs
| Input | Type | Default | Description |
|-------|------|---------|-------------|
| submissionId | string | - | Submission ID to upload to |
| acceptedTypes | string | '.pdf,.png,.jpg,.jpeg,.xlsx,.xls,.csv' | Accepted file types |
| maxSize | number | 52428800 (50MB) | Maximum file size in bytes |
| multiple | boolean | false | Allow multiple file selection |
Outputs
| Output | Type | Description |
|--------|------|-------------|
| uploaded | EventEmitter<UploadResult> | Emits when file is successfully uploaded |
| uploadError | EventEmitter<ApiError> | Emits when upload fails |
Example
import { Component } from '@angular/core';
import { SeaFileUploadComponent } from '@sea-dev/widget-ng';
import type { UploadResult, ApiError } from '@sea-dev/widget-ng';
@Component({
selector: 'app-upload',
standalone: true,
imports: [SeaFileUploadComponent],
template: `
<sea-file-upload
[submissionId]="submissionId"
[multiple]="true"
[maxSize]="100 * 1024 * 1024"
(uploaded)="onUploaded($event)"
(uploadError)="onError($event)"
/>
`,
})
export class UploadComponent {
submissionId = 'sub_123';
onUploaded(result: UploadResult): void {
console.log('File uploaded:', result);
}
onError(error: ApiError): void {
console.error('Upload failed:', error);
}
}SeaDataViewerComponent
Read-only or editable data viewer for extracted submission data.
Inputs
| Input | Type | Default | Description |
|-------|------|---------|-------------|
| submissionId | string | - | Submission ID to display |
| editable | boolean | false | Enable inline field editing |
Outputs
| Output | Type | Description |
|--------|------|-------------|
| fieldChanged | EventEmitter<{field: Field, value: unknown}> | Emits when field is edited |
| citationClicked | EventEmitter<Field> | Emits when citation is clicked |
Example
import { Component } from '@angular/core';
import { SeaDataViewerComponent } from '@sea-dev/widget-ng';
import type { Field } from '@sea-dev/widget-ng';
@Component({
selector: 'app-viewer',
standalone: true,
imports: [SeaDataViewerComponent],
template: `
<sea-data-viewer
[submissionId]="submissionId"
[editable]="true"
(fieldChanged)="onFieldChanged($event)"
(citationClicked)="onCitationClicked($event)"
/>
`,
})
export class ViewerComponent {
submissionId = 'sub_123';
onFieldChanged(event: { field: Field; value: unknown }): void {
console.log('Field updated:', event.field.name, event.value);
}
onCitationClicked(field: Field): void {
console.log('Navigate to:', field.citation);
}
}🔧 Services
SeaWidgetConfigService
Global configuration service for the widget.
Methods
// Set API key
config.setApiKey('sea_live_xxx');
// Set custom API base URL
config.setApiBaseUrl('https://api.sea.dev');
// Set theme
config.setTheme('dark');
// Configure multiple values at once
config.configure({
apiKey: 'sea_live_xxx',
apiBaseUrl: 'https://api.sea.dev',
theme: 'light',
});
// Clear configuration
config.clear();Signals
// Read-only signals
readonly apiKey: Signal<string | null>;
readonly apiBaseUrl: Signal<string>;
readonly theme: Signal<'light' | 'dark'>;Example
import { Component, inject } from '@angular/core';
import { SeaWidgetConfigService } from '@sea-dev/widget-ng';
@Component({
selector: 'app-settings',
template: `
<div>
<p>API Key: {{ apiKey() || 'Not set' }}</p>
<p>Theme: {{ theme() }}</p>
<button (click)="toggleTheme()">Toggle Theme</button>
</div>
`,
})
export class SettingsComponent {
private readonly config = inject(SeaWidgetConfigService);
readonly apiKey = this.config.apiKey;
readonly theme = this.config.theme;
toggleTheme(): void {
const newTheme = this.theme() === 'light' ? 'dark' : 'light';
this.config.setTheme(newTheme);
}
}SeaApiClientService
Low-level API client for advanced usage.
Signals
readonly submissions: Signal<Submission[]>;
readonly loading: Signal<boolean>;
readonly error: Signal<ApiError | null>;
readonly hasSubmissions: Signal<boolean>;
readonly hasError: Signal<boolean>;Methods
// Get single submission
getSubmission(id: string): Observable<Submission>
// List submissions
listSubmissions(filters?: {
formId?: string;
dealId?: string;
status?: string;
limit?: number;
offset?: number;
}): Observable<Submission[]>
// Create submission
createSubmission(data: {
formId?: string;
dealId?: string;
}): Observable<Submission>
// Update field
updateSubmissionField(
submissionId: string,
fieldId: string,
value: unknown
): Observable<Submission>
// Upload document
uploadDocument(
submissionId: string,
file: File,
onProgress?: (progress: number) => void
): Observable<UploadResult>
// Delete document
deleteDocument(
submissionId: string,
documentId: string
): Observable<void>
// Get documents
getDocuments(submissionId: string): Observable<Document[]>
// Clear cache
clearCache(): voidExample
import { Component, inject, OnInit } from '@angular/core';
import { SeaApiClientService } from '@sea-dev/widget-ng';
@Component({
selector: 'app-custom-integration',
template: `
<div>
@if (loading()) {
<p>Loading...</p>
} @else {
<ul>
@for (submission of submissions(); track submission.id) {
<li>{{ submission.id }}</li>
}
</ul>
}
</div>
`,
})
export class CustomIntegrationComponent implements OnInit {
private readonly api = inject(SeaApiClientService);
readonly submissions = this.api.submissions;
readonly loading = this.api.loading;
ngOnInit(): void {
this.api.listSubmissions({ limit: 10 }).subscribe();
}
}🎨 Styling
The widget components use scoped styles with no global CSS conflicts. You can customize the appearance using CSS custom properties or by overriding component styles.
Theme Variables
/* Add to your global styles */
sea-widget {
--sea-primary-color: #3b82f6;
--sea-border-radius: 8px;
--sea-spacing: 1rem;
}Custom Styling
/* Override component styles */
sea-widget::ng-deep .sea-widget__header {
background: #your-color;
}🔒 Security
- Never expose API keys in client-side code in production
- Use environment variables for API keys
- Consider server-side API key proxying for enhanced security
// Good: Load from environment
const apiKey = environment.seaApiKey;
// Better: Fetch from server on startup
this.http.get<{apiKey: string}>('/api/sea-config')
.subscribe(config => {
this.seaConfig.setApiKey(config.apiKey);
});🧪 Testing
Unit Testing
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { SeaWidgetComponent, SeaWidgetConfigService } from '@sea-dev/widget-ng';
import { provideHttpClient } from '@angular/common/http';
describe('SeaWidgetComponent', () => {
let component: SeaWidgetComponent;
let fixture: ComponentFixture<SeaWidgetComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [SeaWidgetComponent],
providers: [provideHttpClient()],
}).compileComponents();
fixture = TestBed.createComponent(SeaWidgetComponent);
component = fixture.componentInstance;
});
it('should create', () => {
expect(component).toBeTruthy();
});
it('should require API key', () => {
fixture.detectChanges();
const element = fixture.nativeElement;
expect(element.textContent).toContain('API Key Required');
});
});📖 TypeScript Types
All components and services are fully typed. Import types from the package:
import type {
Submission,
SubmissionStatus,
Field,
FieldType,
Document,
DocumentStatus,
UploadResult,
ApiError,
Citation,
BoundingBox,
} from '@sea-dev/widget-ng';🌐 SSR Support
The widget is compatible with Angular Universal (SSR). Use platform checks for browser-only APIs:
import { Component, inject, PLATFORM_ID } from '@angular/core';
import { isPlatformBrowser } from '@angular/common';
@Component({...})
export class MyComponent {
private readonly platformId = inject(PLATFORM_ID);
ngOnInit(): void {
if (isPlatformBrowser(this.platformId)) {
// Browser-only code
}
}
}🆚 Comparison with React Widget
| Feature | Native Angular | React Wrapper | |---------|----------------|---------------| | Bundle Size | ~50-80KB | ~180-220KB | | Framework Dependencies | Angular only | Angular + React | | Developer Experience | Native Angular | React in Angular | | Type Safety | Full | Full | | Change Detection | OnPush | Zone coordination | | DevTools Support | Angular DevTools | Both | | SSR Support | Native | Complex |
📦 Bundle Size
Production build (gzipped):
- Core widget: ~50KB
- File upload: ~15KB
- Data viewer: ~15KB
- Total: ~80KB (excluding Angular framework which customers already have)
Compare to React wrapper: ~180-220KB (includes React runtime)
🤝 Migration from React Widget
See MIGRATION.md for detailed migration guide from @sea-dev/widget (React) to @sea-dev/widget-ng (Angular).
Quick comparison:
// React widget
import { SeaWidget } from '@sea-dev/widget';
<SeaWidget apiKey="xxx" formId="form_123" />
// Angular widget
import { SeaWidgetComponent } from '@sea-dev/widget-ng';
<sea-widget apiKey="xxx" [formId]="'form_123'" />📝 Examples
Complete Application Example
import { Component } from '@angular/core';
import { bootstrapApplication } from '@angular/platform-browser';
import { SeaWidgetComponent, SeaWidgetConfigService } from '@sea-dev/widget-ng';
import { APP_INITIALIZER } from '@angular/core';
import { provideHttpClient } from '@angular/common/http';
@Component({
selector: 'app-root',
standalone: true,
imports: [SeaWidgetComponent],
template: `
<main class="container">
<h1>Document Processing</h1>
<sea-widget [formId]="formId" />
</main>
`,
styles: [`
.container {
max-width: 1200px;
margin: 0 auto;
padding: 2rem;
}
`],
})
export class AppComponent {
formId = 'form_financial_statements';
}
bootstrapApplication(AppComponent, {
providers: [
provideHttpClient(),
{
provide: APP_INITIALIZER,
multi: true,
useFactory: (config: SeaWidgetConfigService) => () => {
config.setApiKey('sea_live_your_key_here');
},
deps: [SeaWidgetConfigService],
},
],
});🐛 Troubleshooting
API Key Not Working
Ensure you're setting the API key before the widget initializes:
// Use APP_INITIALIZER to set key on startup
providers: [
{
provide: APP_INITIALIZER,
multi: true,
useFactory: (config: SeaWidgetConfigService) => () => {
config.setApiKey(environment.seaApiKey);
},
deps: [SeaWidgetConfigService],
},
]CORS Errors
Make sure your domain is whitelisted in your Sea.dev dashboard settings.
Styles Not Applying
Ensure you're not using ViewEncapsulation.ShadowDom which may block styles:
@Component({
// Don't use ShadowDom
encapsulation: ViewEncapsulation.None // or Emulated
})📄 License
MIT
🔗 Links
🙋 Support
For questions and support:
- Documentation: https://docs.sea.dev
- Email: [email protected]
- GitHub Issues: https://github.com/seadotdev/app/issues
