npm package: @sea-dev/widget-ng
Description: Native Angular widget for Sea.dev document processing
Latest version: 0.1.25

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

```bash
npm install @sea-dev/widget-ng
```

Or with pnpm:

```bash
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:

```typescript
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:

```typescript
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

```typescript
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

```typescript
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

```typescript
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

```typescript
// 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

```typescript
// Read-only signals
readonly apiKey: Signal<string | null>;
readonly apiBaseUrl: Signal<string>;
readonly theme: Signal<'light' | 'dark'>;
```

#### Example

```typescript
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

```typescript
readonly submissions: Signal<Submission[]>;
readonly loading: Signal<boolean>;
readonly error: Signal<ApiError | null>;
readonly hasSubmissions: Signal<boolean>;
readonly hasError: Signal<boolean>;
```

#### Methods

```typescript
// 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

```typescript
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

```css
/* Add to your global styles */
sea-widget {
  --sea-primary-color: #3b82f6;
  --sea-border-radius: 8px;
  --sea-spacing: 1rem;
}
```

### Custom Styling

```css
/* 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

```typescript
// 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

```typescript
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:

```typescript
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:

```typescript
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](./MIGRATION.md) for detailed migration guide from `@sea-dev/widget` (React) to `@sea-dev/widget-ng` (Angular).

Quick comparison:

```typescript
// 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

```typescript
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:

```typescript
// 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:

```typescript
@Component({
  // Don't use ShadowDom
  encapsulation: ViewEncapsulation.None // or Emulated
})
```

## 📄 License

MIT

## 🔗 Links

- [Documentation](https://docs.sea.dev)
- [API Reference](https://api.sea.dev/docs)
- [GitHub Repository](https://github.com/seadotdev/app)
- [Support](https://support.sea.dev)

## 🙋 Support

For questions and support:
- Documentation: https://docs.sea.dev
- Email: support@sea.dev
- GitHub Issues: https://github.com/seadotdev/app/issues