npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@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-ng

Or with pnpm:

pnpm add @sea-dev/widget-ng

Peer Dependencies

This package requires:

  • @angular/core >= 17.0.0
  • @angular/common >= 17.0.0
  • @angular/cdk >= 17.0.0
  • rxjs ^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(): void

Example

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