npm package: @signosoft/signpad-js
Description: ## 📍 Table of Contents
Latest version: 0.4.0

# @signosoft/signpad-js

## 📍 Table of Contents

| Core Concepts & Overview                  | Getting Started & Usage                             | Advanced & Support                 |
| :---------------------------------------- | :-------------------------------------------------- | :--------------------------------- |
| 📜 [Description](#-description)           | 📦 [Installation](#-installation)                   | 💬 [Feedback](#-feedback--support) |
| 🎬 [Demo](#-demo)                         | 🔐 [Licensing](#-lease-setup)                       | 🛠️ [Contributing](#-contributing)  |
| ⚙️ [Tech stack](#-tech-stack--built-with) | 🚀 [Quick Start](#-quick-start)                     | 📄 [License](#-license)            |
|                                           | 📋 [Properties](#-properties)                       |                                    |
|                                           | 🧩 [Methods](#-methods)                             |                                    |
|                                           | 🎨 [Styling](#-styling--theming)                    |                                    |
|                                           | 🌐 [Localization](#-localization--translation-keys) |
|                                           |                                                     |

## 📜 Description

`signosoft-signpad` is a powerful and flexible web component built with LitElement, designed for seamless digital signature capture integration into modern web applications. It provides a **unified interface** for working with various signature devices like **Wacom or Ugee**, supporting both stylus and mouse-based signing.

This component **works effortlessly** with any modern stack:

<p align="center">
  <img src="https://cdn.jsdelivr.net/gh/devicons/devicon/icons/javascript/javascript-original.svg" alt="JS" width="35" height="35">
  <img src="https://cdn.jsdelivr.net/gh/devicons/devicon/icons/react/react-original.svg" alt="React" width="35" height="35">
  <img src="https://cdn.jsdelivr.net/gh/devicons/devicon/icons/angularjs/angularjs-original.svg" alt="Angular" width="35" height="35">
  <img src="https://cdn.jsdelivr.net/gh/devicons/devicon/icons/vuejs/vuejs-original.svg" alt="Vue" width="35" height="35">
</p>

It expertly handles the complexities of **WebHID device connections**, signature session lifecycles, and high-fidelity stroke rendering.
<br/>

## 🎬 Demo

<img src="https://unpkg.com/@signosoft/signpad-js@latest/docsVideo.gif" alt="Signosoft Demo" width="500" />

## ⚙️ Tech Stack / Built With

- [![TypeScript](https://img.shields.io/badge/TypeScript-3178C6?logo=typescript&logoColor=fff)](https://www.typescriptlang.org/)
- [![Lit](https://img.shields.io/badge/Lit-4C64FF?logo=Lit&logoColor=white)](https://lit.dev/)
- [![Vite](https://img.shields.io/badge/Vite-646CFF?logo=vite&logoColor=fff)](https://vitejs.dev/)
  <br/>

## 🔐 Lease Setup

To use the Signosoft Signpad component with physical signature hardware, a valid `lease` is **required**.

#### 🛑 CRITICAL: Mandatory Initialization

Without a valid lease, the component can still render, but driver initialization and hardware communication features will be <b>disabled</b>.

### 1. Lease flow

1. Generate/get your license key in Signosoft portal.
2. Request a lease from the Signosoft lease API.
3. Put the returned lease into `config.lease`.

> Security note: In production, call the lease API from your backend and return only the lease to frontend.

### 2. Frontend implementation (plain JavaScript reference)

```JS
async function fetchLease() {
  try {
    const response = await fetch(
      "https://test.signosoft.com/api/v1/driver/leases",
      {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({
          licenceKey: "YOUR-LICENSE-KEY",
          validityHours: 24,
          port: 4204,
        }),
      },
    );

    if (!response.ok) {
      const errorText = await response.text();
      throw new Error(
        `License server error: ${response.statusText} - ${errorText}`,
      );
    }

    return await response.json();
  } catch (error) {
    console.error("Signosoft Signpad: License initialization failed", error);
    throw error;
  }
}

async function applyLeaseToConfig(currentConfig) {
  const lease = await fetchLease();
  return {
    ...currentConfig,
    lease,
  };
}
```

<br/>

## 📦 Installation

Install the library via npm:

```bash
npm install @signosoft/signpad-js
```

Or using your preferred manager:

```bash
yarn add @signosoft/signpad-js
```

```bash
pnpm add @signosoft/signpad-js
```

### 🌍 Environment Requirements

To ensure proper communication via the **WebHID API**:

- **HTTPS:** Required for all production environments (WebHID security policy).
- **Supported Browsers:** Chrome 89+, Edge 89+, Opera (Chromium-based).
- **TypeScript:** Built-in support; no extra `@types` needed.
  <br/>

## 🚀 Quick Start

First, install the core package:

```bash
npm install @signosoft/signpad-js
```

### 🧰 Framework Integration Guide

<details>
<summary>
  <img src="https://cdn.jsdelivr.net/gh/devicons/devicon/icons/angularjs/angularjs-original.svg" alt="Angular" width="15" height="15">
  <b>Angular</b> <font color="green"><b>(Live)</b></font>
</summary>
<br>

# <img src="https://cdn.jsdelivr.net/gh/devicons/devicon/icons/angular/angular-original.svg" alt="Angular" width="30" height="30"> Angular Integration Guide

This guide describes how to integrate the `@signosoft/signpad-js` web component into an Angular application.

## 1. Installation

Install the core package using npm:

```bash
npm install @signosoft/signpad-js
```

## 2. Create the Bridge Directive

Since `signosoft-signpad` is a Web Component, Angular requires a Directive to properly sync configuration changes and provide typed access to the component instance.

Create `signosoft-signpad.directive.ts`:

```typescript
import {
  Directive,
  ElementRef,
  Input,
  OnChanges,
  SimpleChanges,
} from "@angular/core";
import type { SignosoftSignpad, SignpadConfig } from "@signosoft/signpad-js";

@Directive({
  selector: "signosoft-signpad",
  standalone: true,
})
export class SignosoftSignpadDirective implements OnChanges {
  @Input() config?: SignpadConfig;

  constructor(private host: ElementRef<SignosoftSignpad>) {}

  // Access the native Web Component instance
  get signpadRef(): SignosoftSignpad {
    return this.host.nativeElement;
  }

  ngOnChanges(changes: SimpleChanges) {
    const el = this.host.nativeElement as any;
    for (const key of Object.keys(changes)) {
      el[key] = (this as any)[key];
    }
  }
}
```

## 3. Implementation in Component

Import the library, the directive, and add the **`CUSTOM_ELEMENTS_SCHEMA`**.

```typescript
import {
  Component,
  ViewChild,
  CUSTOM_ELEMENTS_SCHEMA,
  AfterViewInit,
  signal,
} from "@angular/core";
import "@signosoft/signpad-js"; // Import the Web Component registration
import {
  type SignosoftSignpad,
  type SignpadConfig,
} from "@signosoft/signpad-js";
import { SignosoftSignpadDirective } from "./directives/signosoft-signpad.directive";

@Component({
  selector: "app-root",
  standalone: true,
  imports: [SignosoftSignpadDirective],
  templateUrl: "./app.component.html",
  schemas: [CUSTOM_ELEMENTS_SCHEMA], // Mandatory for custom HTML tags
})
export class AppComponent implements AfterViewInit {
  @ViewChild(SignosoftSignpadDirective)
  signpadDirective!: SignosoftSignpadDirective;

  public config = signal<SignpadConfig>({
    lease: undefined,
  });

  async ngAfterViewInit() {
    await this.applyLeaseToConfig();
  }

  public async fetchLease(): Promise<any> {
    try {
      const response = await fetch(
        "https://test.signosoft.com/api/v1/driver/leases",
        {
          method: "POST",
          headers: { "Content-Type": "application/json" },
          body: JSON.stringify({
            licenceKey: "YOUR-LICENSE-KEY",
            validityHours: 24,
            port: 4204,
          }),
        },
      );

      if (!response.ok) {
        const errorText = await response.text();
        throw new Error(
          `License server error: ${response.statusText} - ${errorText}`,
        );
      }

      return await response.json();
    } catch (error) {
      console.error("Signosoft Signpad: License initialization failed", error);
      throw error;
    }
  }

  public async applyLeaseToConfig() {
    const lease = await this.fetchLease();
    this.config.update((currentConfig) => ({
      ...currentConfig,
      lease,
    }));
  }

  // Helper to access methods easily (ok, clear, cancel, etc.)
  public get signpad(): SignosoftSignpad | undefined {
    return this.signpadDirective?.signpadRef;
  }
}
```

## 4. Component Template

Use the custom tag in your HTML and bind the configuration object.

```html
<div>
  <div>
    <signosoft-signpad [config]="config()"></signosoft-signpad>
  </div>
</div>
```

</details>

<details>
<summary>
  <img src="https://cdn.jsdelivr.net/gh/devicons/devicon/icons/react/react-original.svg" alt="React" width="15" height="15">
  <b>React</b> <font color="green"><b>(Live)</b></font>
</summary>
<br>

# <img src="https://cdn.jsdelivr.net/gh/devicons/devicon/icons/react/react-original.svg" alt="React" width="30" height="30"> React - Quick start

This guide describes how to integrate the `@signosoft/signpad-js` web component into a React application (Functional Components with Hooks).

## 1. Installation

Install the core package using npm:

```bash
npm install @signosoft/signpad-js
```

## 2. Implementation

In React, we use `useRef` for methods and `useEffect` to fetch a lease before updating config.

```tsx
import { useEffect, useRef, useState } from "react";
import "@signosoft/signpad-js"; // Registers the web component
import type { SignpadConfig } from "@signosoft/signpad-js";

async function fetchLease() {
  try {
    const response = await fetch(
      "https://test.signosoft.com/api/v1/driver/leases",
      {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({
          licenceKey: "YOUR-LICENSE-KEY",
          validityHours: 24,
          port: 4204,
        }),
      },
    );

    if (!response.ok) {
      const errorText = await response.text();
      throw new Error(
        `License server error: ${response.statusText} - ${errorText}`,
      );
    }

    return await response.json();
  } catch (error) {
    console.error("Signosoft Signpad: License initialization failed", error);
    throw error;
  }
}

function App() {
  // Use ref to access component methods (ok, clear, connect, etc. by signpadRef.current)
  const signpadRef = useRef<any>(null);
  const [config, setConfig] = useState<SignpadConfig>({ lease: undefined });

  useEffect(() => {
    fetchLease().then((lease) => setConfig((prev) => ({ ...prev, lease })));
  }, []);

  return (
    <div>
      <h1>Signosoft Signpad React Example</h1>
      <signosoft-signpad ref={signpadRef} config={config} />
    </div>
  );
}

export default App;
```

</details>

<details>
<summary>
  <img src="https://cdn.jsdelivr.net/gh/devicons/devicon/icons/vuejs/vuejs-original.svg" alt="Vue" width="15" height="15">
  <b>Vue.js</b> <font color="green"><b>(Live)</b></font>
</summary>
<br>

# <img src="https://cdn.jsdelivr.net/gh/devicons/devicon/icons/vuejs/vuejs-original.svg" alt="Vue" width="30" height="30"> Vue.js Integration Guide

This guide describes how to integrate the `@signosoft/signpad-js` web component into a Vue 3 application using the Composition API (`<script setup>`).

## 1. Installation

Install the core package using npm:

```bash
npm install @signosoft/signpad-js
```

## 2. Configure Vue to recognize Custom Elements

By default, Vue will try to resolve `signosoft-signpad` as a Vue component and will throw a warning. You need to tell Vue to ignore this tag.

**If you are using Vite (`vite.config.ts`):**

```typescript
import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";

export default defineConfig({
  plugins: [
    vue({
      template: {
        compilerOptions: {
          // Treat all tags starting with 'signosoft-' as custom elements
          isCustomElement: (tag) => tag.startsWith("signosoft-"),
        },
      },
    }),
  ],
});
```

## 3. Implementation

In Vue 3, we use `ref` for both the element and config, then fetch the lease and assign it to config.

```typescript
<script setup lang="ts">
import { ref } from "vue";
import "@signosoft/signpad-js"; // Registers the Web Component
import type { SignpadConfig, SignosoftSignpad, IPenData } from "@signosoft/signpad-js";

// Use ref to access component methods (ok, clear, connect, etc. by signpadRef.value)
const signpadRef = ref<SignosoftSignpad | null>(null);

const signpadConfig = ref<SignpadConfig>({ lease: undefined });

const fetchLease = async () => {
  try {
    const response = await fetch("https://test.signosoft.com/api/v1/driver/leases", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({
        licenceKey: "YOUR-LICENSE-KEY",
        validityHours: 24,
        port: 4204,
      }),
    });

    if (!response.ok) {
      const errorText = await response.text();
      throw new Error(`License server error: ${response.statusText} - ${errorText}`);
    }

    return await response.json();
  } catch (error) {
    console.error("Signosoft Signpad: License initialization failed", error);
    throw error;
  }
};

fetchLease()
  .then((lease) => {
    signpadConfig.value = {
      ...signpadConfig.value,
      lease,
    };
  });

</script>

<template>
  <div>
    <signosoft-signpad
      ref="signpadRef"
      :config="signpadConfig"
    ></signosoft-signpad>
  </div>
</template>
```

</details>

<details>
<summary>
  <img src="https://cdn.jsdelivr.net/gh/devicons/devicon/icons/javascript/javascript-original.svg" alt="JS" width="15" height="15">
  <b>Vanilla JS</b> <font color="green"><b>(Live)</b></font>
</summary>
<br>

# 🍦 Vanilla JS Integration Guide

This guide describes how to integrate the `@signosoft/signpad-js` web component into a plain JavaScript project without any frameworks.

## 1. Installation

Install the package via npm:

```bash
npm install @signosoft/signpad-js
```

Or, if you are not using a bundler, you can include the script directly in your HTML (ensure the path points to the compiled bundle in `node_modules` or a CDN).

## 2. JavaScript Implementation

In Vanilla JS, you interact with the component by selecting it from the DOM and assigning the configuration directly to its `config` property.

```javascript
import "@signosoft/signpad-js";

/**
 * INTELLISENSE SUPPORT (Optional)
 * Helps VS Code provide autocomplete for methods and properties.
 * @type {import('@signosoft/signpad-js').SignosoftSignpad}
 */
const pad = document.querySelector("signosoft-signpad");

// 1. Initial Configuration
pad.config = {
  lease: undefined,
};

const fetchLease = async () => {
  try {
    const response = await fetch(
      "https://test.signosoft.com/api/v1/driver/leases",
      {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({
          licenceKey: "YOUR-LICENSE-KEY",
          validityHours: 24,
          port: 4204,
        }),
      },
    );

    if (!response.ok) {
      const errorText = await response.text();
      throw new Error(
        `License server error: ${response.statusText} - ${errorText}`,
      );
    }

    return await response.json();
  } catch (error) {
    console.error("Signosoft Signpad: License initialization failed", error);
    throw error;
  }
};

fetchLease().then((lease) => {
  pad.config = { ...pad.config, lease };
});
```

## 3. HTML Structure

Add the custom element tag to your HTML and create the necessary control buttons.

```html
<!-- index.html -->
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>Signosoft Signpad - Vanilla JS</title>
  </head>
  <body>
    <h1>Signosoft Signpad</h1>
    <!-- The Web Component -->
    <signosoft-signpad id="my-signpad"></signosoft-signpad>
    <!-- Main logic script -->
    <script type="module" src="main.js"></script>
  </body>
</html>
```

## 4. Key Concepts

- **Direct Property Assignment:** Unlike some frameworks that use attributes, in Plain JS you should assign the configuration directly: `element.config = { ... }`.
- **Module System:** Ensure your `<script>` tag has `type="module"` to use the `import` statement.
- **Methods:** All methods like `.connect()`, `.ok()`, and `.clear()` are available directly on the DOM element object.
- **DOM Events:** The component dispatches standard DOM events (like `sign-ok`, `sign-clear`) which you can listen to using `addEventListener`.

</details>

## 📋 Properties

The component is primarily configured through a single `config` property. This object controls everything from licensing and hardware behavior to UI visibility and localization.

| Property | Type            | Default     | Description                                      |
| :------- | :-------------- | :---------- | :----------------------------------------------- |
| `config` | `SignpadConfig` | `undefined` | The main configuration object for the component. |

### 🔑 Core Options

| Option      | Type     | Required | Description                                                                   |
| :---------- | :------- | :------- | :---------------------------------------------------------------------------- |
| **`lease`** | `object` | **Yes**  | Lease payload returned from the lease API call. Mandatory for initialization. |

### 🔄 autoconnectOptions

Controls how the component handles hardware connections and session flow.

| Field                           | Type      | Default | Description                                                                                           |
| :------------------------------ | :-------- | :------ | :---------------------------------------------------------------------------------------------------- |
| `autoConnect`                   | `boolean` | `false` | Attempts to connect to an authorized device on startup. Defaults to mouse mode if no device is found. |
| `autoConnectOnPlugIn`           | `boolean` | `false` | Automatically initiates connection when a compatible and authorized device is plugged into USB.       |
| `autoRestartSigningAfterAction` | `boolean` | `false` | Automatically calls `startSigning()` after a user completes an action (OK/Cancel).                    |

### 👁️ uiVisibilityOptions

Toggles the visibility of specific User Interface elements.

| Field                        | Type      | Description                                                       |
| :--------------------------- | :-------- | :---------------------------------------------------------------- |
| `topBarVisible`              | `boolean` | Shows/hides the entire top navigation bar.                        |
| `topBarClearButtonVisible`   | `boolean` | Shows a "Clear" button in the top-left corner.                    |
| `topBarConnectButtonVisible` | `boolean` | Shows the connect/disconnect button area in the top-right corner. |
| `bottomBarVisible`           | `boolean` | Shows/hides the entire bottom action bar.                         |
| `okButtonVisible`            | `boolean` | Shows the **OK** button in the bottom bar.                        |
| `clearButtonVisible`         | `boolean` | Shows the **Clear** button in the bottom bar.                     |
| `cancelButtonVisible`        | `boolean` | Shows the **Cancel** button in the bottom bar.                    |
| `canvasLineVisible`          | `boolean` | Shows the horizontal signature guide line on the canvas.          |
| `deviceStatusTextVisible`    | `boolean` | Shows the name of the connected device (e.g., "Wacom STU-540").   |
| `additionalTextVisible`      | `boolean` | Shows helper status messages (e.g., "Ready to sign").             |

### 🖋️ canvasAndDrawingOptions

Adjusts the visual appearance of the digital ink.

| Field      | Type     | Default   | Description                                 |
| :--------- | :------- | :-------- | :------------------------------------------ |
| `color`    | `string` | `#000000` | The HEX color of the signature stroke.      |
| `minWidth` | `number` | `1`       | The line thickness at minimum pen pressure. |
| `maxWidth` | `number` | `3`       | The line thickness at maximum pen pressure. |

> Note: Since standard mice do not support physical pressure, the system emulates a normalized pressure of 0.5, ensuring consistent line rendering during fallback sessions.

### 🌐 languageOptions

Handles component localization. It can load external files or use inline definitions.

| Field          | Type     | Description                                                                                                                                          |
| :------------- | :------- | :--------------------------------------------------------------------------------------------------------------------------------------------------- |
| `lang`         | `string` | The active language code. Built-in translations are bundled for `'en'` and `'cs'`; other languages can be supplied via `langPath` or `translations`. |
| `langPath`     | `string` | URL path to fetch translation files. Expects files in `[path]/[lang].json` format.                                                                   |
| `translations` | `object` | An inline object containing key-value pairs (e.g., `{ "OK": "Confirm signature" }`). Overrides files.                                                |

## 🌐 Localization & Translation Keys

Localization is driven from `languageOptions`, but you can also provide overrides at runtime:

1. **Set the language code** in `config.languageOptions.lang`. English (`en`) is the fallback and Czech (`cs`) is shipped alongside it.
2. **Load a hosted file** by pointing `languageOptions.langPath` to the directory that contains `[lang].json`. The loader will fetch `${langPath}/${lang}.json` (or the exact file if you include the `.json` suffix).
3. **Override only specific phrases** by passing a `translations` object—either a flat key map or a per-language map (`{ en: {...}, cs: {...} }`). Inline values take precedence over both built-in and fetched files.
4. **Dynamic placeholders** (for example, `{device}` in `TEXT_SIGNING_WITH_DEVICE`) are replaced using the `LocalizationManager.t` helper. Keep the braces intact if you expect runtime interpolation.

The complete list of keys that can be translated is maintained in `src/i18n/en.json`. You can copy the structure when creating new language files or inline overrides. The defaults are:

| Key                          | Default (English)                                   |
| :--------------------------- | :-------------------------------------------------- |
| `OK`                         | OK                                                  |
| `CLEAR`                      | Clear                                               |
| `CANCEL`                     | Cancel                                              |
| `CLEAR_SIGNATURE`            | Clear signature                                     |
| `DISCONNECT`                 | Disconnect                                          |
| `CONNECT_SIGNPAD`            | Connect Signpad                                     |
| `MOUSE`                      | Mouse                                               |
| `CONNECTED_UNKNOWN_DEVICE`   | Connected (Unknown Device)                          |
| `AUTO_CONNECTING`            | Auto-connecting to device...                        |
| `CONNECTING_TO_DEVICE`       | Connecting to your device...                        |
| `REVERTING_TO_DEFAULT`       | Reverting to default input...                       |
| `DISCONNECTING_DEVICE`       | Disconnecting device...                             |
| `COMPONENT_INITIALIZED`      | Component initialized.                              |
| `TEXT_START_SIGNING`         | Connect your device.                                |
| `TEXT_SIGN_WITH_PHYSICAL`    | Please sign using the device pen.                   |
| `TEXT_SIGN_WITH_MOUSE`       | Please sign using your mouse.                       |
| `TEXT_SIGN_GENERIC`          | Sign in the area above using mouse or pen.          |
| `TEXT_SIGNING_WITH_DEVICE`   | Signing with {device}                               |
| `NO_DEVICE_FOUND`            | No device found.                                    |
| `CONNECTION_ERROR`           | Connection error.                                   |
| `ERROR_DISCONNECTION`        | Error during disconnection.                         |
| `ERROR_INSTANTIATING_DRIVER` | Error instantiating driver.                         |
| `SIGNPAD_DISCONNECTED`       | Signpad disconnected.                               |
| `INVALID_LICENSE_KEY`        | Invalid license key.                                |
| `READY_TO_CONNECT`           | Ready to connect.                                   |
| `SIGNPAD_DETECTED`           | Signpad detected. Click 'Connect Signpad' to begin. |
| `READY_TO_SIGN_MOUSE`        | Ready to sign. Use your mouse.                      |
| `READY_TO_SIGN_PHYSICAL`     | Ready to sign. Use the device pen.                  |

If you add any new keys, mirror them in every language file you provide to guarantee they fall back gracefully to English.

### ⚙️ Logic & Events

The component provides two ways to interact with internal processes.

#### 🛠️ Action Handlers (`actionHandlers`)

These allow you to **inject custom logic** directly into the internal button flows. Your code runs alongside the component's internal logic.

| Handler        | Arguments                           | Description                                                             |
| :------------- | :---------------------------------- | :---------------------------------------------------------------------- |
| `handleOk`     | `data?: ISignatureConfirmationData` | Extends the internal OK button logic. Receives captured signature data. |
| `handleClear`  | —                                   | Extends the internal Clear button logic.                                |
| `handleCancel` | —                                   | Extends the internal Cancel button logic.                               |

#### 🔔 Event Callbacks (`eventCallbacks`)

Standard event listeners triggered _after_ specific actions. Useful for observing the component from your application state.

| Callback       | Payload                                          | Description                                                                                        |
| :------------- | :----------------------------------------------- | :------------------------------------------------------------------------------------------------- |
| `onPen`        | `event: CustomEvent<IPenData>`                   | Dispatched when a device or mouse fallback are in contact with component or signature pad display. |
| `onConnecting` | `event: CustomEvent<void>`                       | Dispatched when a device is in loading state.                                                      |
| `onConnect`    | `event: CustomEvent<{ deviceInfo: any }>`        | Dispatched when a device or mouse fallback connects.                                               |
| `onDisconnect` | `event: CustomEvent<void>`                       | Dispatched when the hardware connection is closed.                                                 |
| `onOk`         | `event: CustomEvent<ISignatureConfirmationData>` | Dispatched when signature is confirmed.                                                            |
| `onClear`      | `event: CustomEvent<void>`                       | Dispatched when the canvas has been cleared.                                                       |
| `onCancel`     | `event: CustomEvent<void>`                       | Dispatched when the user aborts the session.                                                       |
| `onError`      | `event: CustomEvent<Error>`                      | Dispatched on critical failures (lease, driver, connection, etc.).                                 |

> `actionHandlers` and `eventCallbacks` are separate hooks and can both be configured.

---

### 💡 Example Implementation

```typescript
this.config = {
  lease: leasePayloadFromApi,
  autoconnectOptions: {
    autoConnect: true,
  },
  canvasAndDrawingOptions: {
    color: "#1a1a1a",
  },
  actionHandlers: {
    handleOk: async (data) => {
      console.log("Saving signature data...", data);
    },
  },
  eventCallbacks: {
    onConnect: (e) =>
      console.log("Pad connected:", e.detail.deviceInfo.deviceName),
    onError: (event) => console.error("Signpad Error:", event.detail.message),
  },
};
```

## 🧩 Methods

The following methods are available on the component instance to control the signing process programmatically.

| Method           | Returns                                            | Description                                                                      |
| :--------------- | :------------------------------------------------- | :------------------------------------------------------------------------------- |
| `connect()`      | `Promise<void>`                                    | Connects device to component                                                     |
| `disconnect()`   | `Promise<void>`                                    | Disconnects device from component                                                |
| `startSigning()` | `Promise<void>`                                    | Initializes a new signing session on the canvas and hardware.                    |
| `stopSigning()`  | `Promise<any>`                                     | Low-level session stop that returns the raw driver payload.                      |
| `ok()`           | `Promise<ISignatureConfirmationData \| undefined>` | Finalizes the session, cleans the device screen, and returns the signature data. |
| `clear()`        | `Promise<void>`                                    | Wipes the signature from the UI and hardware without ending the session.         |
| `cancel()`       | `Promise<void>`                                    | Aborts the current session and resets the component state.                       |

---

#### 📜 `connect()`

This is the primary method to start using the Signpad. Because it uses the **WebHID API**, this must be triggered by a **user gesture** (like a button click) to satisfy browser security requirements.

The method first attempts to silently reconnect to a previously authorized device. If that fails, it opens the browser's device selection dialog and, if no physical device is selected, falls back to mouse/touch mode automatically.

**Returns:**

- A `Promise<void>` that resolves once the connection attempt (or fallback) is complete.

#### 📜 `disconnect()`

Safely terminates the communication channel with the signature tablet.

**What happens during disconnect:**

1. The hardware driver is stopped and released.
2. The internal cache of device information is cleared.
3. The component UI transitions back to the `DISCONNECTED` state.
4. Any active mouse listeners are removed.
5. A `SIGN_DISCONNECT` event is dispatched.

#### 💡 Best Practices

- **Initial Connection:** Always call `connect()` from a click handler so the user can select their device from the browser list.
- **Auto-Reconnect:** Use `autoconnectOptions.autoConnect: true` in the config to silently reconnect to a previously authorized device on startup without a user gesture.
- **Cleanup:** It is good practice to call `disconnect()` when the component is being destroyed or the user navigates away from the signing page to free up the USB port.

#### 📜 `startSigning(options?: IDrawingOptions)`

Prepares the component for a new signature. It transitions the state to `CONNECTING`, resets any previous drawing data, and activates the pen input for either a physical device or mouse fallback.

```typescript
// Example: Start a session with custom ink
await signpad.startSigning({
  color: "#0000FF",
  maxWidth: 4,
  minWidth: 2,
});
```

If you have `autoRestartSigningAfterAction` enabled in your config, the component will automatically call `startSigning()` again after an `ok` or `cancel` action is completed.

#### 📜 `ok()`

This is the standard way to confirm a signature. It:

1. Stops the signing process.
2. Clears the physical device's screen (if connected).
3. Dispatches the `SIGN_OK` event.
4. Returns the final signature payload (coordinates, pressure, metadata).

**Returned payload shape (`ISignatureConfirmationData`)**

```js
[
  points, // index 0: Data about each point
  imageBase64, // index 1: Image in base64
  metadata, // index 2: Information about tablet and canvas
];
```

```js
// [0] Points structure
{
  absoluteX: number,
  absoluteY: number,
  inContact: boolean,
  pressure: number,
  ready: boolean,
  relativeX: number, // normalized 0..1
  relativeY: number, // normalized 0..1
  sequence: number,
  timestamp: number | bigint
}
```

```js
// [1] Image structure
{
  data: image / png;
  (base64, Some - long - string);
}
```

```js
// [2] Metadata structure
{
  canvas: {
    top: number,
    left: number,
    right: number,
    bottom: number,
    width: number,
    height: number,
    cssWidth: number,
    cssHeight: number,
    dpr: number
  },
  tablet: {
    name: string,
    aspectRatio: number,
    isCanvasStyleTablet: boolean,
    screenWidth: number,
    screenHeight: number,
    drawingArea: { x: number, y: number, width: number, height: number },
    serialNumber: Promise<string> | string,
    vendorId: number | null,
    productId: number | null
  }
}
```

`handleOk(data)` receives the same structure. `onOk` receives it in `event.detail`.

#### 📜 `onPen` payload

`onPen` is emitted continuously while writing and provides a single `IPenData` object in `event.detail`.

```js
{
  absoluteX: number,
  absoluteY: number,
  inContact: boolean,
  pressure: number,
  ready: boolean,
  relativeX: number, // normalized 0..1
  relativeY: number, // normalized 0..1
  sequence: number,
  timestamp: number | bigint
}
```

#### 📜 `stopSigning()`

A low-level method that forces the driver to stop capturing data and returns the raw driver payload. Unlike `ok()`, it does not trigger the full "Finalization" flow (UI transitions or device screen clearing).

> Note: `stopSigning()` intentionally exposes the unnormalized raw result, which is why it is documented as `Promise<any>`.

#### 📜 `clear()`

Resets the drawing state on both the web canvas and the physical tablet's display. This allows the user to start their signature over without leaving the current session. It dispatches the `SIGN_CLEAR` event.

#### 📜 `cancel()`

Stops the session immediately. It does not collect any signature data and resets the component to its ready state. This method dispatches the `SIGN_CANCEL` event.

## 🎨 Styling & Theming

The component uses a hybrid theming model:

- It ships with complete default styles (works out of the box).
- You can override the visual design through CSS variables.

## ✍️ Override via CSS

You can override variables in your stylesheet

### 📎 Copy-Paste Starter

Paste this directly into your app stylesheet and adjust values:

```css
signosoft-signpad {
  /* Color Variables */
  --primary-color-0: #4e56ea;
  --primary-color-10: #7178ee;
  --background-color-0: #f1f2fd;
  --background-color-10: #e3e4fc;
  --text-color-0: #333e4a;
  --white-color: #ffffff;
  --grey-color: #b5b9be;

  /* Constraints */
  --min-height: 48px;
  --spacing-constraints: 16px 24px;

  /* CSS Variables */
  --sign-font-family: Arial, Helvetica, sans-serif;
  --sign-common-border-radius: 8px;

  /* Top Bar */
  --sign-top-bar-bg-base: var(--background-color-10);
  --sign-top-bar-text-base: var(--primary-color-0);
  --sign-top-bar-padding: var(--spacing-constraints);
  --sign-top-bar-min-height: var(--min-height);
  --sign-top-bar-border-top-left-radius: var(--sign-common-border-radius);
  --sign-top-bar-border-top-right-radius: var(--sign-common-border-radius);
  --sign-top-bar-border-bottom-left-radius: 0;
  --sign-top-bar-border-bottom-right-radius: 0;

  /* Canvas Area */
  --sign-canvas-bg-base: var(--background-color-0);
  --sign-canvas-wrapper-border-top-left-radius: 0;
  --sign-canvas-wrapper-border-top-right-radius: 0;
  --sign-canvas-wrapper-border-bottom-left-radius: 0;
  --sign-canvas-wrapper-border-bottom-right-radius: 0;

  /* Line */
  --sign-line-height: 22px;
  --sign-line-margin-bottom: 16px;
  --sign-line-border-base: var(--primary-color-10);
  --sign-line-additional-text-color: var(--text-color-0);
  --sign-line-margin: 0px 24px var(--sign-line-margin-bottom) 24px;
  --sign-canvas-line-text-font-size: 12px;
  --sign-canvas-height-offset: var(--sign-line-height);

  /* Bottom Bar */
  --sign-bottom-bar-bg-base: var(--background-color-10);
  --sign-bottom-bar-padding: var(--spacing-constraints);
  --sign-bottom-bar-min-height: var(--min-height);
  --sign-bottom-bar-gap: 12px;
  --sign-bottom-bar-border-top-left-radius: 0;
  --sign-bottom-bar-border-top-right-radius: 0;
  --sign-bottom-bar-border-bottom-left-radius: var(--sign-common-border-radius);
  --sign-bottom-bar-border-bottom-right-radius: var(
    --sign-common-border-radius
  );

  /* Button general settings */
  --sign-button-font-size: 16px;
  --sign-button-padding: 14px 16px;
  --sign-button-min-height: var(--min-height);
  --sign-button-border-radius: 8px;
  --sign-button-font-weight: 500;

  /* Primary Buttons (OK, Clear, Cancel) */
  --sign-button-primary-bg-base: var(--primary-color-0);
  --sign-button-primary-bg-hover: var(--primary-color-10);
  --sign-button-primary-bg-disabled: var(--grey-color);
  --sign-button-primary-text-base: var(--white-color);
  --sign-button-primary-text-hover: var(--white-color);
  --sign-button-primary-text-disabled: var(--white-color);

  /* Link Buttons (Connect signpad) */
  --sign-button-link-bg-base: transparent;
  --sign-button-link-bg-hover: var(--background-color-10);
  --sign-button-link-bg-disabled: transparent;
  --sign-button-link-text-base: var(--primary-color-0);
  --sign-button-link-text-hover: var(--primary-color-10);
  --sign-button-link-text-disabled: var(--grey-color);
  --sign-button-link-border-base: 1px solid var(--primary-color-0);
  --sign-button-link-border-hover: 1px solid var(--primary-color-10);
  --sign-button-link-border-disabled: 1px solid var(--grey-color);

  /* Device State Text Colors */
  --sign-device-state-text-color-connected: green;
  --sign-device-state-text-color-disconnected: red;

  /* Loading Overlay */
  --sign-loading-overlay-bg-color: rgba(255, 255, 255, 0.8);
  --sign-loading-overlay-text-color: var(--text-color-0);
  --sign-loading-overlay-spinner-color: var(--primary-color-0);
  --sign-loading-overlay-spinner-border-color: rgba(78, 86, 234, 0.1);
  --sign-loading-overlay-spinner-size: 30px;
}
```

### 📋 Full CSS Variable Reference

#### Base Colors & General

| Variable                      | Default             | Description                                     |
| :---------------------------- | :------------------ | :---------------------------------------------- |
| `--primary-color-0`           | `#4e56ea`           | Primary brand color.                            |
| `--primary-color-10`          | `#7178ee`           | Primary hover/accent color.                     |
| `--background-color-0`        | `#f1f2fd`           | Secondary background color.                     |
| `--background-color-10`       | `#e3e4fc`           | Main bar background color.                      |
| `--text-color-0`              | `#333e4a`           | Main text color.                                |
| `--white-color`               | `#ffffff`           | Pure white color.                               |
| `--grey-color`                | `#b5b9be`           | Disabled state color.                           |
| `--sign-font-family`          | `Arial, sans-serif` | Global font family.                             |
| `--sign-common-border-radius` | `8px`               | Shared radius token reused by bars and buttons. |

#### Layout & Constraints

| Variable                | Default     | Description                            |
| :---------------------- | :---------- | :------------------------------------- |
| `--min-height`          | `48px`      | Minimum height for bars and buttons.   |
| `--spacing-constraints` | `16px 24px` | Default padding/margin for containers. |

#### Top Bar

| Variable                                    | Default                            | Description                         |
| :------------------------------------------ | :--------------------------------- | :---------------------------------- |
| `--sign-top-bar-bg-base`                    | `var(--background-color-10)`       | Background color of the top bar.    |
| `--sign-top-bar-text-base`                  | `var(--primary-color-0)`           | Text color in the top bar.          |
| `--sign-top-bar-padding`                    | `var(--spacing-constraints)`       | Inner padding of the top bar.       |
| `--sign-top-bar-min-height`                 | `var(--min-height)`                | Minimum height of the top bar.      |
| `--sign-top-bar-border-top-left-radius`     | `var(--sign-common-border-radius)` | Top-left radius of the top bar.     |
| `--sign-top-bar-border-top-right-radius`    | `var(--sign-common-border-radius)` | Top-right radius of the top bar.    |
| `--sign-top-bar-border-bottom-left-radius`  | `0`                                | Bottom-left radius of the top bar.  |
| `--sign-top-bar-border-bottom-right-radius` | `0`                                | Bottom-right radius of the top bar. |

#### Canvas & Signature Line

| Variable                                           | Default                                        | Description                                                                  |
| :------------------------------------------------- | :--------------------------------------------- | :--------------------------------------------------------------------------- |
| `--sign-canvas-bg-base`                            | `var(--background-color-0)`                    | Background color of the drawing area.                                        |
| `--sign-canvas-wrapper-border-top-left-radius`     | `0`                                            | Top-left canvas radius, typically rounded when the top bar is hidden.        |
| `--sign-canvas-wrapper-border-top-right-radius`    | `0`                                            | Top-right canvas radius, typically rounded when the top bar is hidden.       |
| `--sign-canvas-wrapper-border-bottom-left-radius`  | `0`                                            | Bottom-left canvas radius, typically rounded when the bottom bar is hidden.  |
| `--sign-canvas-wrapper-border-bottom-right-radius` | `0`                                            | Bottom-right canvas radius, typically rounded when the bottom bar is hidden. |
| `--sign-line-height`                               | `22px`                                         | Vertical offset/height for the guide line.                                   |
| `--sign-line-margin-bottom`                        | `16px`                                         | Bottom spacing used in canvas line layout.                                   |
| `--sign-line-border-base`                          | `var(--primary-color-10)`                      | Color of the signature guide line.                                           |
| `--sign-line-additional-text-color`                | `var(--text-color-0)`                          | Color of the helper text below the line.                                     |
| `--sign-line-margin`                               | `0px 24px var(--sign-line-margin-bottom) 24px` | Spacing around the guide line.                                               |
| `--sign-canvas-line-text-font-size`                | `12px`                                         | Font size for guide line labels.                                             |
| `--sign-canvas-height-offset`                      | `var(--sign-line-height)`                      | Canvas offset calculation for the guide line.                                |

#### Bottom Bar

| Variable                                       | Default                            | Description                              |
| :--------------------------------------------- | :--------------------------------- | :--------------------------------------- |
| `--sign-bottom-bar-bg-base`                    | `var(--background-color-10)`       | Background color of the bottom bar.      |
| `--sign-bottom-bar-padding`                    | `var(--spacing-constraints)`       | Inner padding of the bottom bar.         |
| `--sign-bottom-bar-min-height`                 | `var(--min-height)`                | Minimum height of the bottom bar.        |
| `--sign-bottom-bar-gap`                        | `12px`                             | Space between buttons in the bottom bar. |
| `--sign-bottom-bar-border-top-left-radius`     | `0`                                | Top-left radius of the bottom bar.       |
| `--sign-bottom-bar-border-top-right-radius`    | `0`                                | Top-right radius of the bottom bar.      |
| `--sign-bottom-bar-border-bottom-left-radius`  | `var(--sign-common-border-radius)` | Bottom-left radius of the bottom bar.    |
| `--sign-bottom-bar-border-bottom-right-radius` | `var(--sign-common-border-radius)` | Bottom-right radius of the bottom bar.   |

#### Buttons (General)

| Variable                      | Default             | Description                    |
| :---------------------------- | :------------------ | :----------------------------- |
| `--sign-button-font-size`     | `16px`              | Font size for all buttons.     |
| `--sign-button-padding`       | `14px 16px`         | Inner padding for all buttons. |
| `--sign-button-min-height`    | `var(--min-height)` | Minimum height for buttons.    |
| `--sign-button-border-radius` | `8px`               | Border radius for all buttons. |
| `--sign-button-font-weight`   | `500`               | Font weight for all buttons.   |

#### Primary Buttons (OK, Clear, Cancel)

| Variable                              | Description                          |
| :------------------------------------ | :----------------------------------- |
| `--sign-button-primary-bg-base`       | Background color of primary buttons. |
| `--sign-button-primary-bg-hover`      | Hover background color.              |
| `--sign-button-primary-bg-disabled`   | Disabled background color.           |
| `--sign-button-primary-text-base`     | Text color for primary buttons.      |
| `--sign-button-primary-text-hover`    | Hover text color.                    |
| `--sign-button-primary-text-disabled` | Disabled text color.                 |

#### Link/Connect Buttons

| Variable                             | Default                             | Description                                  |
| :----------------------------------- | :---------------------------------- | :------------------------------------------- |
| `--sign-button-link-bg-base`         | `transparent`                       | Background color for secondary/link buttons. |
| `--sign-button-link-bg-hover`        | `var(--background-color-10)`        | Hover background color.                      |
| `--sign-button-link-bg-disabled`     | `transparent`                       | Disabled background color.                   |
| `--sign-button-link-text-base`       | `var(--primary-color-0)`            | Text color for secondary/link buttons.       |
| `--sign-button-link-text-hover`      | `var(--primary-color-10)`           | Hover text color.                            |
| `--sign-button-link-text-disabled`   | `var(--grey-color)`                 | Disabled text color.                         |
| `--sign-button-link-border-base`     | `1px solid var(--primary-color-0)`  | Border for secondary/link buttons.           |
| `--sign-button-link-border-hover`    | `1px solid var(--primary-color-10)` | Hover border color.                          |
| `--sign-button-link-border-disabled` | `1px solid var(--grey-color)`       | Disabled border color.                       |

#### Status & Overlays

| Variable                                      | Default                    | Description                        |
| :-------------------------------------------- | :------------------------- | :--------------------------------- |
| `--sign-device-state-text-color-connected`    | `green`                    | Text color when device is ready.   |
| `--sign-device-state-text-color-disconnected` | `red`                      | Text color when disconnected.      |
| `--sign-loading-overlay-bg-color`             | `rgba(255, 255, 255, 0.8)` | Background of the loading spinner. |
| `--sign-loading-overlay-text-color`           | `var(--text-color-0)`      | Loading overlay text color.        |
| `--sign-loading-overlay-spinner-color`        | `var(--primary-color-0)`   | Color of the animated spinner.     |
| `--sign-loading-overlay-spinner-border-color` | `rgba(78, 86, 234, 0.1)`   | Base border color of spinner ring. |
| `--sign-loading-overlay-spinner-size`         | `30px`                     | Size of the spinner icon.          |

## 💬 Feedback & Support

We are constantly working to improve our components and drivers. Your feedback is essential to us. Whether you found a bug, have a feature request, or need technical assistance, please reach out to us:

- 📧 **Support Email:** [esign@signosoft.com](mailto:esign@signosoft.com)
- 🌐 **Official Website:** [www.signosoft.com](https://www.signosoft.com)

## 🛠️ Contributing

### Commit naming convention

- Every commit message must start with Jira task id and category:
  - `MAIN-000/<category>: <summary>`
- For work in progress:
  - `MAIN-000/<category>: WIP - <summary>`

Allowed categories:

- `feat`: adding a new feature
- `fix`: fixing a bug
- `refactor`: performance/readability/maintainability change
- `chore`: docs, formatting, tests, cleanup, tooling
- `merge`: branch merge into `main`

Use infinitive verb form in commit summaries:

- <span style="color: #16a34a;"><strong>RIGHT:</strong></span> `add`, `fix`, `remove`, `update`, `refactor`
- <span style="color: #dc2626;"><strong>WRONG:</strong></span> Continuous tense: `adding`, `removing` Past tense: `added`, `fixed`

Examples:

```bash
git commit -m "MAIN-000/feat: add new button component; add button to templates"
```

```bash
git commit -m "MAIN-000/fix: add stop directive to button component to prevent propagation"
```

```bash
git commit -m "MAIN-000/refactor: rewrite button component in TypeScript"
```

```bash
git commit -m "MAIN-000/chore: write button documentation"
```

```bash
git commit -m "MAIN-000/merge: new button"
```

```bash
git commit -m "MAIN-000/feat: WIP - basic signing functionality"
```

### Versioning and release commits

Follow Semantic Versioning:

- `MAJOR` (`x.0.0`): breaking changes
- `MINOR` (`x.y.0`): backward-compatible feature set
- `PATCH` (`x.y.z`): backward-compatible bug fixes

Commit naming for version bumps:

- Version-only change: use `chore`
- Version bump with actual code/features in same commit: use `feat`/`fix`/`refactor` based on dominant change
- For release commits, include both versions in message: `release from <old> to <new>`

Example (version bump with feature changes):

```bash
git commit -m "MAIN-000/feat: release from 0.2.4 to 0.3.0 - add signature method"
```

Example (version-only commit):

```bash
git commit -m "MAIN-000/chore: release from 0.2.4 to 0.3.0"
```

How version increment works:

- `PATCH` (`x.y.Z`): increase patch only (`1.4.2` -> `1.4.3`)
- `MINOR` (`x.Y.0`): increase minor, reset patch (`1.4.9` -> `1.5.0`)
- `MAJOR` (`X.0.0`): increase major, reset minor and patch (`1.9.7` -> `2.0.0`)

Examples:

```bash
# patch
git commit -m "MAIN-000/fix: release from 1.0.0 to 1.0.1 - fix onPen throttle edge case"

# minor
git commit -m "MAIN-000/feat: release from 1.1.1 to 1.2.0 - add lease refresh helper"

# major
git commit -m "MAIN-000/refactor: release from 1.1.1 to 2.0.0 - remove legacy API"
```

## 📄 License

**Proprietary Commercial License**

Copyright © 2026, Signosoft. All rights reserved.

This software is proprietary to Signosoft and is protected by copyright laws. It is **not open source**.

### 🔐 Commercial Usage

A valid, paid, and active commercial license agreement with Signosoft is **strictly required** for the use, operation, reproduction, distribution, or deployment of this software in any environment (including development, testing, staging, and production).

### 📜 Key Terms

- **Unauthorized Use:** Any use without a current, fully paid license is expressly prohibited and constitutes a violation of our intellectual property rights.
- **Legal Action:** Signosoft reserves the right to pursue legal action to enforce its rights and seek damages for unauthorized use.
- **Restrictions:** You may not reverse-engineer, decompile, or disassemble this software except as permitted by applicable law.

For information on how to obtain a valid commercial license, pricing, and support, please contact us at [esign@signosoft.com](mailto:esign@signosoft.com).

See the [LICENSE](LICENSE) file for the full legal text.