@signosoft/signpad-js 0.5.3 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,102 +1,56 @@
1
1
  # @signosoft/signpad-js
2
2
 
3
+ <a id="table-of-contents"></a>
4
+
3
5
  ## 📍 Table of Contents
4
6
 
5
- | Core Concepts & Overview | Getting Started & Usage | Advanced & Support |
6
- | :---------------------------------------- | :-------------------------------------------------- | :--------------------------------- |
7
- | 📜 [Description](#-description) | 📦 [Installation](#-installation) | 💬 [Feedback](#-feedback--support) |
8
- | 🎬 [Demo](#-demo) | 🔐 [Licensing](#-lease-setup) | 🛠️ [Contributing](#-contributing) |
9
- | ⚙️ [Tech stack](#-tech-stack--built-with) | 🚀 [Quick Start](#-quick-start) | 📄 [License](#-license) |
10
- | 🖊️ [Devices](#️-device-compatibility) | 📋 [Properties](#-properties) | |
11
- | | 🧩 [Methods](#-methods) | |
12
- | | 🔄 [States](#-component-states) | |
13
- | | 🎨 [Styling](#-styling--theming) | |
14
- | | 🌐 [Localization](#-localization--translation-keys) |
15
- | | |
7
+ | Overview | Getting Started & API | Customization & Support |
8
+ | :--------------------------------------- | :------------------------------------------- | :------------------------------------------------- |
9
+ | 📜 [Description](#description) | 📦 [Installation](#installation) | 🌐 [Localization](#localization--translation-keys) |
10
+ | 🎬 [Demo](#demo) | 🌍 [Requirements](#environment-requirements) | 🎨 [Styling](#styling--theming) |
11
+ | 🧰 [Tech stack](#tech-stack--built-with) | 🔐 [Licensing](#lease-setup) | ♿ [Accessibility](#accessibility) |
12
+ | 🔌 [Devices](#device-compatibility) | 🚀 [Quick Start](#quick-start) | |
13
+ | | 📋 [Properties](#properties) | 💬 [Feedback](#feedback--support) |
14
+ | | 📤 [Results & errors](#results--errors) | 📄 [License](#license) |
15
+ | | 🧩 [Methods](#methods) | |
16
+ | | 🔄 [States](#component-states) | |
17
+ | | ⚠️ [Error codes](#error-codes) | |
18
+ | | 🧱 [Types](#type-definitions) | |
19
+
20
+ <a id="description"></a>
16
21
 
17
22
  ## 📜 Description
18
23
 
19
- `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.
24
+ `signosoft-signpad` is a web component built with Lit that captures handwritten signatures in the browser. It reads signature tablets (Wacom STU, Ugee UG05, XP-Pen) over WebHID and uses mouse or touch input when no tablet is connected.
20
25
 
21
- This component **works effortlessly** with any modern stack:
26
+ It works in plain JavaScript, React, Angular and Vue:
22
27
  | **JavaScript** | **React** | **Angular** | **Vue** |
23
28
  |:-:|:-:|:-:|:-:|
24
29
  | <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"> ||
25
30
 
26
- It expertly handles the complexities of **WebHID device connections**, signature session lifecycles, and high-fidelity stroke rendering.
27
- <br/>
31
+ The component manages the WebHID connection, the signing session (connect, sign, OK / Clear / Cancel) and pressure-sensitive stroke rendering.
32
+
33
+ > **Before you start:** you need Chrome or Edge 89+, HTTPS (`localhost` is fine for development), a [lease](#lease-setup), and a click or tap before the browser can open the device picker. Details: [Environment Requirements](#environment-requirements).
34
+ > <br/>
35
+
36
+ <a id="demo"></a>
28
37
 
29
38
  ## 🎬 Demo
30
39
 
31
- <img src="https://unpkg.com/@signosoft/signpad-js@latest/docsVideo.gif" alt="Signosoft Demo" width="500" />
40
+ <img src="https://unpkg.com/@signosoft/signpad-js@latest/presentation.gif" alt="Signosoft Signpad presentation" width="400" />
41
+
42
+ <a id="tech-stack--built-with"></a>
32
43
 
33
- ## ⚙️ Tech Stack / Built With
44
+ ## 🧰 Tech Stack / Built With
34
45
 
35
46
  - [![TypeScript](https://img.shields.io/badge/TypeScript-3178C6?logo=typescript&logoColor=fff)](https://www.typescriptlang.org/)
36
47
  - [![Lit](https://img.shields.io/badge/Lit-4C64FF?logo=Lit&logoColor=white)](https://lit.dev/)
37
48
  - [![Vite](https://img.shields.io/badge/Vite-646CFF?logo=vite&logoColor=fff)](https://vitejs.dev/)
38
49
  <br/>
39
50
 
40
- ## 🔐 Lease Setup
41
-
42
- To use the Signosoft Signpad component with physical signature hardware, a valid `lease` is **required**.
51
+ <a id="device-compatibility"></a>
43
52
 
44
- #### 🛑 CRITICAL: Mandatory Initialization
45
-
46
- Without a valid lease, the component can still render, but driver initialization and hardware communication features will be <b>disabled</b>.
47
-
48
- ### 1. Lease flow
49
-
50
- 1. Generate/get your license key in Signosoft portal.
51
- 2. Request a lease from the Signosoft lease API.
52
- 3. Put the returned lease into `config.lease`.
53
-
54
- > Security note: In production, call the lease API from your backend and return only the lease to frontend.
55
-
56
- ### 2. Frontend implementation (plain JavaScript reference)
57
-
58
- ```JS
59
- async function fetchLease() {
60
- try {
61
- const response = await fetch(
62
- "https://test.signosoft.com/api/v1/driver/leases",
63
- {
64
- method: "POST",
65
- headers: { "Content-Type": "application/json" },
66
- body: JSON.stringify({
67
- licenceKey: "YOUR-LICENSE-KEY",
68
- validityHours: 24,
69
- port: 4204,
70
- }),
71
- },
72
- );
73
-
74
- if (!response.ok) {
75
- const errorText = await response.text();
76
- throw new Error(
77
- `License server error: ${response.statusText} - ${errorText}`,
78
- );
79
- }
80
-
81
- return await response.json();
82
- } catch (error) {
83
- console.error("Signosoft Signpad: License initialization failed", error);
84
- throw error;
85
- }
86
- }
87
-
88
- async function applyLeaseToConfig(currentConfig) {
89
- const lease = await fetchLease();
90
- return {
91
- ...currentConfig,
92
- lease,
93
- };
94
- }
95
- ```
96
-
97
- <br/>
98
-
99
- ## 🖊️ Device Compatibility
53
+ ## 🔌 Device Compatibility
100
54
 
101
55
  The component communicates with signature tablets via the **WebHID API**. Devices are matched against a built-in configuration database; unrecognized devices fall back to a generic driver.
102
56
 
@@ -109,6 +63,8 @@ The component communicates with signature tablets via the **WebHID API**. Device
109
63
 
110
64
  > **Mouse fallback:** If no supported device is connected (or the user dismisses the HID picker), the component automatically activates mouse/touch input so signing can still proceed in the browser.
111
65
 
66
+ <a id="installation"></a>
67
+
112
68
  ## 📦 Installation
113
69
 
114
70
  Install the library via npm:
@@ -127,132 +83,119 @@ yarn add @signosoft/signpad-js
127
83
  pnpm add @signosoft/signpad-js
128
84
  ```
129
85
 
130
- ### 🌍 Environment Requirements
86
+ <a id="environment-requirements"></a>
87
+
88
+ ## 🌍 Environment Requirements
131
89
 
132
90
  To ensure proper communication via the **WebHID API**:
133
91
 
134
- - **HTTPS:** Required for all production environments (WebHID security policy).
92
+ - **HTTPS:** Required for all production environments (WebHID security policy). `http://localhost` is allowed for development.
93
+ - **User gesture:** The browser only opens the HID device picker in response to a user action (click/tap). The built-in Connect button already satisfies this; if you call `connect()` yourself, do it from an event handler (click/tap). The browser rejects the picker otherwise and the component falls back to mouse mode if enabled. Devices the user already authorized are reconnected without the picker.
135
94
  - **Supported Browsers:** Chrome 89+, Edge 89+, Opera (Chromium-based).
95
+ - **Server-side rendering:** the package can be imported on the server, but the component only works in the browser (WebHID, canvas). Create it on the client, for example after the first render.
136
96
  - **TypeScript:** Built-in support; no extra `@types` needed.
137
- <br/>
138
97
 
139
- ## 🚀 Quick Start
98
+ <a id="lease-setup"></a>
140
99
 
141
- First, install the core package:
100
+ ## 🔐 Lease Setup
142
101
 
143
- ```bash
144
- npm install @signosoft/signpad-js
145
- ```
102
+ To use the Signosoft Signpad component with physical signature hardware, a valid `lease` is **required**.
146
103
 
147
- ### 🧰 Framework Integration Guide
104
+ ### CRITICAL: Mandatory Initialization
148
105
 
149
- <details>
150
- <summary>
151
- <img src="https://cdn.jsdelivr.net/gh/devicons/devicon/icons/angularjs/angularjs-original.svg" alt="Angular" width="15" height="15">
152
- <b>Angular</b> <font color="green"><b>(Live)</b></font>
153
- </summary>
154
- <br>
106
+ Without a valid lease, the component can still render, but driver initialization and hardware communication features will be <b>disabled</b>.
155
107
 
156
- # <img src="https://cdn.jsdelivr.net/gh/devicons/devicon/icons/angular/angular-original.svg" alt="Angular" width="30" height="30"> Angular Integration Guide
108
+ ### 1. Lease flow
157
109
 
158
- This guide describes how to integrate the `@signosoft/signpad-js` web component into an Angular application.
110
+ 1. Go to https://apps.signosoft.com/
111
+ 2. Create account
112
+ 3. In dashboard go to "manage account" -> "licence"
113
+ 4. Create licence with your desired origins
114
+ 5. Done
159
115
 
160
- ## 1. Installation
116
+ > Security note: In production, call the lease API from your backend and return only the lease to frontend.
161
117
 
162
- Install the core package using npm:
118
+ ### 2. Pricing
163
119
 
164
- ```bash
165
- npm install @signosoft/signpad-js
166
- ```
120
+ Every signature completed on the signpad is billed against the credits of your lease: **0.0050 USD per signature** (JavaScript driver), excluding VAT. Current prices are on [signosoft.com/pricing](https://www.signosoft.com/pricing). When the credits run out, the component stops signing and reports `BIL-01` (`BILLING_DENIED`, see [error codes](#signpaderrorcode)).
167
121
 
168
- ## 2. Create the Bridge Directive
122
+ <a id="quick-start"></a>
169
123
 
170
- Since `signosoft-signpad` is a Web Component, Angular requires a Directive to properly sync configuration changes and provide typed access to the component instance.
124
+ ## 🚀 Quick Start
171
125
 
172
- Create `signosoft-signpad.directive.ts`:
126
+ Every example below logs the finished signature in `onOk`; what that data contains is described in [Signature result](#signature-result-isignatureconfirmationdata). Before the framework guides, install the package ([Installation](#installation)), check the [requirements](#environment-requirements) and get a [lease](#lease-setup).
173
127
 
174
- ```typescript
175
- import {
176
- Directive,
177
- ElementRef,
178
- Input,
179
- OnChanges,
180
- SimpleChanges,
181
- } from "@angular/core";
182
- import type { SignosoftSignpad, SignpadConfig } from "@signosoft/signpad-js";
128
+ > **Plain JS snippets:** in this document `signosoftPad` is the element, obtained once like this. Framework guides (Angular, React, Vue) get it through their own refs.
129
+ >
130
+ > ```js
131
+ > const signosoftPad = document.querySelector("signosoft-signpad");
132
+ > ```
183
133
 
184
- @Directive({
185
- selector: "signosoft-signpad",
186
- standalone: true,
187
- })
188
- export class SignosoftSignpadDirective implements OnChanges {
189
- @Input() config?: SignpadConfig;
134
+ > **Size:** the component renders at a fixed `25rem × 31.25rem` (400×500 px) by itself; no wrapper element is needed. To resize or make it fluid, set `--signpad-width` / `--signpad-height` (see [Layout & Constraints](#layout--constraints)).
190
135
 
191
- constructor(private host: ElementRef<SignosoftSignpad>) {}
136
+ ### Framework Integration Guide
192
137
 
193
- // Access the native Web Component instance
194
- get signpadRef(): SignosoftSignpad {
195
- return this.host.nativeElement;
196
- }
138
+ <details>
139
+ <summary>
140
+ <img src="https://cdn.jsdelivr.net/gh/devicons/devicon/icons/angularjs/angularjs-original.svg" alt="Angular" width="15" height="15">
141
+ <b>Angular</b> <font color="green"><b>(Live)</b></font>
142
+ </summary>
143
+ <br>
197
144
 
198
- ngOnChanges(changes: SimpleChanges) {
199
- const el = this.host.nativeElement as any;
200
- for (const key of Object.keys(changes)) {
201
- el[key] = (this as any)[key];
202
- }
203
- }
204
- }
205
- ```
145
+ #### <img src="https://cdn.jsdelivr.net/gh/devicons/devicon/icons/angular/angular-original.svg" alt="Angular" width="30" height="30"> Angular Integration Guide
146
+
147
+ This guide describes how to integrate the `@signosoft/signpad-js` web component into an Angular application. It uses the current signal-based Angular APIs (`viewChild()`, `afterNextRender()`), so it needs **Angular 20 or newer** (standalone components are the default, so there is no `standalone: true`).
206
148
 
207
- ## 3. Implementation in Component
149
+ ##### 1. Implementation in Component
208
150
 
209
- Import the library, the directive, and add the **`CUSTOM_ELEMENTS_SCHEMA`**.
151
+ Import the library and add the **`CUSTOM_ELEMENTS_SCHEMA`**. Angular passes `[config]` to the element as a property, so no extra wrapper is needed.
210
152
 
211
153
  ```typescript
212
154
  import {
213
155
  Component,
214
- ViewChild,
215
156
  CUSTOM_ELEMENTS_SCHEMA,
216
- AfterViewInit,
157
+ ElementRef,
158
+ afterNextRender,
217
159
  signal,
160
+ viewChild,
218
161
  } from "@angular/core";
219
- import "@signosoft/signpad-js"; // Import the Web Component registration
220
- import {
221
- type SignosoftSignpad,
222
- type SignpadConfig,
223
- } from "@signosoft/signpad-js";
224
- import { SignosoftSignpadDirective } from "./directives/signosoft-signpad.directive";
162
+ import "@signosoft/signpad-js"; // Registers the Web Component
163
+ import type { SignosoftSignpad, SignpadConfig } from "@signosoft/signpad-js";
225
164
 
226
165
  @Component({
227
166
  selector: "app-root",
228
- standalone: true,
229
- imports: [SignosoftSignpadDirective],
230
- templateUrl: "./app.component.html",
167
+ templateUrl: "./app.html",
231
168
  schemas: [CUSTOM_ELEMENTS_SCHEMA], // Mandatory for custom HTML tags
232
169
  })
233
- export class AppComponent implements AfterViewInit {
234
- @ViewChild(SignosoftSignpadDirective)
235
- signpadDirective!: SignosoftSignpadDirective;
170
+ export class App {
171
+ // The element behind #pad in the template: call ok(), clear(), startSigning() on it
172
+ readonly pad = viewChild.required<ElementRef<SignosoftSignpad>>("pad");
236
173
 
237
- public config = signal<SignpadConfig>({
174
+ readonly config = signal<SignpadConfig>({
238
175
  lease: undefined,
176
+ eventCallbacks: {
177
+ onOk: (event) => {
178
+ // The signature: [points, imageBase64, metadata]
179
+ const [points, imageBase64, metadata] = event.detail;
180
+ console.log(points, imageBase64, metadata);
181
+ },
182
+ },
239
183
  });
240
184
 
241
- async ngAfterViewInit() {
242
- await this.applyLeaseToConfig();
185
+ constructor() {
186
+ // Runs in the browser after the first render
187
+ afterNextRender(() => this.applyLeaseToConfig());
243
188
  }
244
189
 
245
- public async fetchLease(): Promise<any> {
190
+ async fetchLease(): Promise<any> {
246
191
  try {
247
192
  const response = await fetch(
248
- "https://test.signosoft.com/api/v1/driver/leases",
193
+ "https://api-test.signosoft.com/v1/public/driver-leases",
249
194
  {
250
195
  method: "POST",
251
196
  headers: { "Content-Type": "application/json" },
252
197
  body: JSON.stringify({
253
- licenceKey: "YOUR-LICENSE-KEY",
254
- validityHours: 24,
255
- port: 4204,
198
+ key: "YOUR-LICENSE-TOKEN",
256
199
  }),
257
200
  },
258
201
  );
@@ -271,7 +214,7 @@ export class AppComponent implements AfterViewInit {
271
214
  }
272
215
  }
273
216
 
274
- public async applyLeaseToConfig() {
217
+ async applyLeaseToConfig() {
275
218
  const lease = await this.fetchLease();
276
219
  this.config.update((currentConfig) => ({
277
220
  ...currentConfig,
@@ -279,23 +222,78 @@ export class AppComponent implements AfterViewInit {
279
222
  }));
280
223
  }
281
224
 
282
- // Helper to access methods easily (ok, clear, cancel, etc.)
283
- public get signpad(): SignosoftSignpad | undefined {
284
- return this.signpadDirective?.signpadRef;
225
+ // Example: start a new session with blue ink from your own button
226
+ startBlue() {
227
+ return this.pad().nativeElement.startSigning({ color: "#0000ff" });
285
228
  }
286
229
  }
287
230
  ```
288
231
 
289
- ## 4. Component Template
232
+ ##### 2. Component Template
290
233
 
291
- Use the custom tag in your HTML and bind the configuration object.
234
+ Use the custom tag in your HTML, name it with `#pad` and bind the configuration object.
292
235
 
293
236
  ```html
294
- <div>
295
- <div>
296
- <signosoft-signpad [config]="config()"></signosoft-signpad>
297
- </div>
298
- </div>
237
+ <signosoft-signpad #pad [config]="config()"></signosoft-signpad>
238
+ <button (click)="startBlue()">Sign with blue ink</button>
239
+ ```
240
+
241
+ The `config` signal is the only link between your code and the component: assign a new object to it and the component updates. `this.pad().nativeElement` is the element itself, so every method from [Methods](#methods) is available on it.
242
+
243
+ ##### 3. Optional: a typed directive
244
+
245
+ Not required. Add it if you want Angular to type-check `[config]` in templates and a typed handle to the element (`instance`) instead of `viewChild<ElementRef>`.
246
+
247
+ ```typescript
248
+ // signosoft.directive.ts
249
+ import { Directive, ElementRef, effect, inject, input } from "@angular/core";
250
+ import type { SignosoftSignpad, SignpadConfig } from "@signosoft/signpad-js";
251
+
252
+ @Directive({ selector: "signosoft-signpad" })
253
+ export class SignosoftSignpadDirective {
254
+ readonly config = input<SignpadConfig>();
255
+ private readonly host = inject<ElementRef<SignosoftSignpad>>(ElementRef);
256
+
257
+ get instance(): SignosoftSignpad {
258
+ return this.host.nativeElement;
259
+ }
260
+
261
+ constructor() {
262
+ // Push every config change into the Web Component
263
+ effect(() => {
264
+ const config = this.config();
265
+ if (config) this.host.nativeElement.config = config;
266
+ });
267
+ }
268
+ }
269
+ ```
270
+
271
+ To use it, change three things in the component from steps 1 and 2:
272
+
273
+ ```typescript
274
+ import { SignosoftSignpadDirective } from "./signosoft.directive";
275
+
276
+ @Component({
277
+ selector: "app-root",
278
+ templateUrl: "./app.html",
279
+ imports: [SignosoftSignpadDirective], // importing the file is not enough, list the directive here
280
+ schemas: [CUSTOM_ELEMENTS_SCHEMA],
281
+ })
282
+ export class App {
283
+ // Finds the directive by its class instead of the #pad template reference
284
+ readonly pad = viewChild.required(SignosoftSignpadDirective);
285
+
286
+ startBlue() {
287
+ // .instance is the SignosoftSignpad element
288
+ return this.pad().instance.startSigning({ color: "#0000ff" });
289
+ }
290
+ }
291
+ ```
292
+
293
+ The template no longer needs `#pad`:
294
+
295
+ ```html
296
+ <signosoft-signpad [config]="config()"></signosoft-signpad>
299
297
  ```
300
298
 
301
299
  </details>
@@ -307,19 +305,23 @@ Use the custom tag in your HTML and bind the configuration object.
307
305
  </summary>
308
306
  <br>
309
307
 
310
- # <img src="https://cdn.jsdelivr.net/gh/devicons/devicon/icons/react/react-original.svg" alt="React" width="30" height="30"> React - Quick start
308
+ #### <img src="https://cdn.jsdelivr.net/gh/devicons/devicon/icons/react/react-original.svg" alt="React" width="30" height="30"> React - Quick start
311
309
 
312
310
  This guide describes how to integrate the `@signosoft/signpad-js` web component into a React application (Functional Components with Hooks).
313
311
 
314
- ## 1. Installation
312
+ It needs **React 19 or newer**: React 19 passes the `config` object to the custom element as a property. React 18 turns unknown JSX props into string attributes, so `config` would arrive as `"[object Object]"`. On React 18 assign it yourself in an effect:
313
+
314
+ ```tsx
315
+ const signpadRef = useRef<any>(null);
315
316
 
316
- Install the core package using npm:
317
+ useEffect(() => {
318
+ if (signpadRef.current) signpadRef.current.config = config;
319
+ }, [config]);
317
320
 
318
- ```bash
319
- npm install @signosoft/signpad-js
321
+ return <signosoft-signpad ref={signpadRef} />;
320
322
  ```
321
323
 
322
- ## 2. Implementation
324
+ ##### 1. Implementation
323
325
 
324
326
  In React, we use `useRef` for methods and `useEffect` to fetch a lease before updating config.
325
327
 
@@ -331,14 +333,12 @@ import type { SignpadConfig } from "@signosoft/signpad-js";
331
333
  async function fetchLease() {
332
334
  try {
333
335
  const response = await fetch(
334
- "https://test.signosoft.com/api/v1/driver/leases",
336
+ "https://api-test.signosoft.com/v1/public/driver-leases",
335
337
  {
336
338
  method: "POST",
337
339
  headers: { "Content-Type": "application/json" },
338
340
  body: JSON.stringify({
339
- licenceKey: "YOUR-LICENSE-KEY",
340
- validityHours: 24,
341
- port: 4204,
341
+ key: "YOUR-LICENSE-TOKEN",
342
342
  }),
343
343
  },
344
344
  );
@@ -360,7 +360,16 @@ async function fetchLease() {
360
360
  function App() {
361
361
  // Use ref to access component methods (ok, clear, connect, etc. by signpadRef.current)
362
362
  const signpadRef = useRef<any>(null);
363
- const [config, setConfig] = useState<SignpadConfig>({ lease: undefined });
363
+ const [config, setConfig] = useState<SignpadConfig>({
364
+ lease: undefined,
365
+ eventCallbacks: {
366
+ onOk: (event) => {
367
+ // The signature: [points, imageBase64, metadata]
368
+ const [points, imageBase64, metadata] = event.detail;
369
+ console.log(points, imageBase64, metadata);
370
+ },
371
+ },
372
+ });
364
373
 
365
374
  useEffect(() => {
366
375
  fetchLease().then((lease) => setConfig((prev) => ({ ...prev, lease })));
@@ -386,19 +395,11 @@ export default App;
386
395
  </summary>
387
396
  <br>
388
397
 
389
- # <img src="https://cdn.jsdelivr.net/gh/devicons/devicon/icons/vuejs/vuejs-original.svg" alt="Vue" width="30" height="30"> Vue.js Integration Guide
398
+ #### <img src="https://cdn.jsdelivr.net/gh/devicons/devicon/icons/vuejs/vuejs-original.svg" alt="Vue" width="30" height="30"> Vue.js Integration Guide
390
399
 
391
400
  This guide describes how to integrate the `@signosoft/signpad-js` web component into a Vue 3 application using the Composition API (`<script setup>`).
392
401
 
393
- ## 1. Installation
394
-
395
- Install the core package using npm:
396
-
397
- ```bash
398
- npm install @signosoft/signpad-js
399
- ```
400
-
401
- ## 2. Configure Vue to recognize Custom Elements
402
+ ##### 1. Configure Vue to recognize Custom Elements
402
403
 
403
404
  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.
404
405
 
@@ -422,7 +423,7 @@ export default defineConfig({
422
423
  });
423
424
  ```
424
425
 
425
- ## 3. Implementation
426
+ ##### 2. Implementation
426
427
 
427
428
  In Vue 3, we use `ref` for both the element and config, then fetch the lease and assign it to config.
428
429
 
@@ -435,17 +436,24 @@ import type { SignpadConfig, SignosoftSignpad, IPenData } from "@signosoft/signp
435
436
  // Use ref to access component methods (ok, clear, connect, etc. by signpadRef.value)
436
437
  const signpadRef = ref<SignosoftSignpad | null>(null);
437
438
 
438
- const signpadConfig = ref<SignpadConfig>({ lease: undefined });
439
+ const signpadConfig = ref<SignpadConfig>({
440
+ lease: undefined,
441
+ eventCallbacks: {
442
+ onOk: (event) => {
443
+ // The signature: [points, imageBase64, metadata]
444
+ const [points, imageBase64, metadata] = event.detail;
445
+ console.log(points, imageBase64, metadata);
446
+ },
447
+ },
448
+ });
439
449
 
440
450
  const fetchLease = async () => {
441
451
  try {
442
- const response = await fetch("https://test.signosoft.com/api/v1/driver/leases", {
452
+ const response = await fetch("https://api-test.signosoft.com/v1/public/driver-leases", {
443
453
  method: "POST",
444
454
  headers: { "Content-Type": "application/json" },
445
455
  body: JSON.stringify({
446
- licenceKey: "YOUR-LICENSE-KEY",
447
- validityHours: 24,
448
- port: 4204,
456
+ key: "YOUR-LICENSE-TOKEN",
449
457
  }),
450
458
  });
451
459
 
@@ -490,21 +498,15 @@ fetchLease()
490
498
  </summary>
491
499
  <br>
492
500
 
493
- # 🍦 Vanilla JS Integration Guide
501
+ #### 🍦 Vanilla JS Integration Guide
494
502
 
495
503
  This guide describes how to integrate the `@signosoft/signpad-js` web component into a plain JavaScript project without any frameworks.
496
504
 
497
- ## 1. Installation
498
-
499
- Install the package via npm:
500
-
501
- ```bash
502
- npm install @signosoft/signpad-js
503
- ```
505
+ ##### 1. Load the component
504
506
 
505
- 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).
507
+ Install the package first (see [Installation](#installation)). 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).
506
508
 
507
- ## 2. JavaScript Implementation
509
+ ##### 2. JavaScript Implementation
508
510
 
509
511
  In Vanilla JS, you interact with the component by selecting it from the DOM and assigning the configuration directly to its `config` property.
510
512
 
@@ -516,24 +518,29 @@ import "@signosoft/signpad-js";
516
518
  * Helps VS Code provide autocomplete for methods and properties.
517
519
  * @type {import('@signosoft/signpad-js').SignosoftSignpad}
518
520
  */
519
- const pad = document.querySelector("signosoft-signpad");
521
+ const signosoftPad = document.querySelector("signosoft-signpad");
520
522
 
521
523
  // 1. Initial Configuration
522
- pad.config = {
524
+ signosoftPad.config = {
523
525
  lease: undefined,
526
+ eventCallbacks: {
527
+ onOk: (event) => {
528
+ // The signature: [points, imageBase64, metadata]
529
+ const [points, imageBase64, metadata] = event.detail;
530
+ console.log(points, imageBase64, metadata);
531
+ },
532
+ },
524
533
  };
525
534
 
526
535
  const fetchLease = async () => {
527
536
  try {
528
537
  const response = await fetch(
529
- "https://test.signosoft.com/api/v1/driver/leases",
538
+ "https://api-test.signosoft.com/v1/public/driver-leases",
530
539
  {
531
540
  method: "POST",
532
541
  headers: { "Content-Type": "application/json" },
533
542
  body: JSON.stringify({
534
- licenceKey: "YOUR-LICENSE-KEY",
535
- validityHours: 24,
536
- port: 4204,
543
+ key: "YOUR-LICENSE-TOKEN",
537
544
  }),
538
545
  },
539
546
  );
@@ -553,13 +560,13 @@ const fetchLease = async () => {
553
560
  };
554
561
 
555
562
  fetchLease().then((lease) => {
556
- pad.config = { ...pad.config, lease };
563
+ signosoftPad.config = { ...signosoftPad.config, lease };
557
564
  });
558
565
  ```
559
566
 
560
- ## 3. HTML Structure
567
+ ##### 3. HTML Structure
561
568
 
562
- Add the custom element tag to your HTML and create the necessary control buttons.
569
+ Add the custom element tag to your HTML. The component renders its own Connect, OK, Clear and Cancel buttons, so no extra controls are required.
563
570
 
564
571
  ```html
565
572
  <!-- index.html -->
@@ -579,15 +586,17 @@ Add the custom element tag to your HTML and create the necessary control buttons
579
586
  </html>
580
587
  ```
581
588
 
582
- ## 4. Key Concepts
589
+ ##### 4. Key Concepts
583
590
 
584
591
  - **Direct Property Assignment:** Unlike some frameworks that use attributes, in Plain JS you should assign the configuration directly: `element.config = { ... }`.
585
592
  - **Module System:** Ensure your `<script>` tag has `type="module"` to use the `import` statement.
586
593
  - **Methods:** All methods like `.connect()`, `.ok()`, and `.clear()` are available directly on the DOM element object.
587
- - **DOM Events:** The component dispatches standard DOM events (like `sign-ok`, `sign-clear`) which you can listen to using `addEventListener`.
594
+ - **DOM Events:** every callback is also dispatched as a DOM `CustomEvent` (name in the "DOM event" column of [Event Callbacks](#eventcallbacks)) that bubbles and crosses shadow boundaries, so you can use `addEventListener` on the element or a parent.
588
595
 
589
596
  </details>
590
597
 
598
+ <a id="properties"></a>
599
+
591
600
  ## 📋 Properties
592
601
 
593
602
  The component is primarily configured through a single `config` property. This object controls everything from licensing and hardware behavior to UI visibility and localization.
@@ -596,30 +605,36 @@ The component is primarily configured through a single `config` property. This o
596
605
  | :------- | :-------------- | :------ | :----------------------------------------------- |
597
606
  | `config` | `SignpadConfig` | `{}` | The main configuration object for the component. |
598
607
 
599
- ### 🔑 Core Options
608
+ ### What goes in `config`
600
609
 
601
- | Option | Type | Required | Description |
602
- | :---------- | :------- | :------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
603
- | **`lease`** | `object` | Required for hardware mode | Lease payload returned from the lease API call. The component still renders without it, but driver initialization and hardware communication are disabled (see [Lease Setup](#-lease-setup)). |
610
+ Everything below is a key of the one `config` object. Every key is optional.
604
611
 
605
- ### 👁️ uiVisibilityOptions
612
+ ```typescript
613
+ const config: SignpadConfig = {
614
+ lease: {}, // licence, required for hardware mode
615
+ theme: "light", // look: "light" | "dark" | "auto"
616
+ languageOptions: {}, // texts
617
+ uiVisibilityOptions: {}, // show/hide UI parts
618
+ canvasAndDrawingOptions: {}, // pen color and width
619
+ actionHandlers: {}, // your code in the OK/Clear/Cancel flow
620
+ eventCallbacks: {}, // observe events
621
+ };
622
+ ```
606
623
 
607
- Toggles the visibility of specific User Interface elements.
624
+ **Not part of `config`:**
625
+
626
+ - **Methods** such as `connect()`, `startSigning()`, `ok()` are called on the element, see [Methods](#methods).
627
+ - **DOM events** can be listened to with `addEventListener`, see [`eventCallbacks`](#eventcallbacks).
628
+ - **Size and colors of the component** are CSS variables, see [Styling & Theming](#styling--theming).
629
+ - **What you get back** (signature, errors) is described in [Results & Errors](#results--errors).
630
+
631
+ ### `lease`
632
+
633
+ | Option | Type | Required | Description |
634
+ | :---------- | :------- | :------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
635
+ | **`lease`** | `object` | Required for hardware mode | Lease payload returned from the lease API call. The component still renders without it, but driver initialization and hardware communication are disabled (see [Lease Setup](#lease-setup)). |
608
636
 
609
- | Field | Type | Default | Description |
610
- | :--------------------------- | :-------- | :------ | :---------------------------------------------------------------- |
611
- | `topBarVisible` | `boolean` | `true` | Shows/hides the entire top navigation bar. |
612
- | `topBarClearButtonVisible` | `boolean` | `false` | Shows a "Clear" button in the top-left corner. |
613
- | `topBarConnectButtonVisible` | `boolean` | `true` | Shows the connect/disconnect button area in the top-right corner. |
614
- | `bottomBarVisible` | `boolean` | `true` | Shows/hides the entire bottom action bar. |
615
- | `okButtonVisible` | `boolean` | `true` | Shows the **OK** button in the bottom bar. |
616
- | `clearButtonVisible` | `boolean` | `true` | Shows the **Clear** button in the bottom bar. |
617
- | `cancelButtonVisible` | `boolean` | `true` | Shows the **Cancel** button in the bottom bar. |
618
- | `canvasLineVisible` | `boolean` | `true` | Shows the horizontal signature guide line on the canvas. |
619
- | `deviceStatusTextVisible` | `boolean` | `true` | Shows the name of the connected device (e.g., "Wacom STU-540"). |
620
- | `additionalTextVisible` | `boolean` | `true` | Shows helper status messages (e.g., "Ready to sign"). |
621
-
622
- ### 🖋️ canvasAndDrawingOptions
637
+ ### `canvasAndDrawingOptions`
623
638
 
624
639
  Adjusts the visual appearance of the digital ink.
625
640
 
@@ -631,93 +646,132 @@ Adjusts the visual appearance of the digital ink.
631
646
 
632
647
  > 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.
633
648
 
634
- ### 🌐 languageOptions
649
+ ### `uiVisibilityOptions`
635
650
 
636
- Handles component localization. It can load external files or use inline definitions.
651
+ Toggles the visibility of specific User Interface elements.
637
652
 
638
- | Field | Type | Description |
639
- | :------------- | :------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------- |
640
- | `lang` | `string` | The active language code. Built-in translations are bundled for `'en'` and `'cs'`; other languages (including `'pt'`) require `langPath` or `translations`. |
641
- | `langPath` | `string` | URL path to fetch translation files. Expects files in `[path]/[lang].json` format. |
642
- | `translations` | `object` | An inline object containing key-value pairs (e.g., `{ "OK": "Confirm signature" }`). Overrides files. |
653
+ | Field | Type | Default | Description |
654
+ | :------------------------------- | :-------- | :------ | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
655
+ | `topBarVisible` | `boolean` | `true` | Shows/hides the entire top navigation bar. |
656
+ | `topBarClearButtonVisible` | `boolean` | `false` | Shows a "Clear" button in the top-left corner. |
657
+ | `topBarConnectButtonVisible` | `boolean` | `true` | Shows the connect/disconnect button area in the top-right corner. The button is hidden while an OK, Clear or Cancel action is processing. |
658
+ | `bottomBarVisible` | `boolean` | `true` | Shows/hides the entire bottom action bar. |
659
+ | `okButtonVisible` | `boolean` | `true` | Shows the **OK** button in the bottom bar. |
660
+ | `clearButtonVisible` | `boolean` | `true` | Shows the **Clear** button in the bottom bar. |
661
+ | `cancelButtonVisible` | `boolean` | `true` | Shows the **Cancel** button in the bottom bar. |
662
+ | `canvasLineVisible` | `boolean` | `true` | Shows the horizontal signature guide line on the canvas. |
663
+ | `deviceStatusTextVisible` | `boolean` | `true` | Shows the name of the connected device (e.g., "Wacom STU-540"). |
664
+ | `additionalTextVisible` | `boolean` | `true` | Shows helper status messages (e.g., "Ready to sign"). |
665
+ | `startNewSignatureButtonVisible` | `boolean` | `true` | After OK or Cancel the signing session is over and nothing is captured until `startSigning()` is called. While in that state the bottom bar shows a **Start new signature** button instead of OK/Clear/Cancel. Set `false` to hide it and call `startSigning()` yourself (it is also hidden with `bottomBarVisible: false`). |
666
+
667
+ ### `languageOptions`
643
668
 
644
- ## 🌐 Localization & Translation Keys
669
+ Handles component localization. It can load external files or use inline definitions.
645
670
 
646
- Localization is driven from `languageOptions`, but you can also provide overrides at runtime:
647
-
648
- 1. **Set the language code** in `config.languageOptions.lang`. English (`en`) is the fallback, and Czech (`cs`) is bundled alongside it. Other languages, including Portuguese (`pt`), must be supplied via `langPath` or `translations` — they are not bundled.
649
- 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).
650
- 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.
651
- 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.
652
-
653
- 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:
654
-
655
- | Key | Default (English) |
656
- | :--------------------------- | :-------------------------------------------------- |
657
- | `OK` | OK |
658
- | `CLEAR` | Clear |
659
- | `CANCEL` | Cancel |
660
- | `CLEAR_SIGNATURE` | Clear signature |
661
- | `DISCONNECT` | Disconnect |
662
- | `CONNECT_SIGNPAD` | Connect Signpad |
663
- | `MOUSE` | Mouse |
664
- | `CONNECTED_UNKNOWN_DEVICE` | Connected (Unknown Device) |
665
- | `AUTO_CONNECTING` | Auto-connecting to device... |
666
- | `CONNECTING_TO_DEVICE` | Connecting to your device... |
667
- | `REVERTING_TO_DEFAULT` | Reverting to default input... |
668
- | `DISCONNECTING_DEVICE` | Disconnecting device... |
669
- | `COMPONENT_INITIALIZED` | Component initialized. |
670
- | `TEXT_START_SIGNING` | Connect your device. |
671
- | `TEXT_SIGN_WITH_PHYSICAL` | Please sign using the device pen. |
672
- | `TEXT_SIGN_WITH_MOUSE` | Please sign using your mouse. |
673
- | `TEXT_SIGN_GENERIC` | Sign in the area above using mouse or pen. |
674
- | `TEXT_SIGNING_WITH_DEVICE` | Signing with {device} |
675
- | `NO_DEVICE_FOUND` | No device found. |
676
- | `CONNECTION_ERROR` | Connection error. |
677
- | `ERROR_DISCONNECTION` | Error during disconnection. |
678
- | `ERROR_INSTANTIATING_DRIVER` | Error instantiating driver. |
679
- | `INVALID_LICENSE_KEY` | Invalid license key. |
680
- | `READY_TO_CONNECT` | Ready to connect. |
681
- | `SIGNPAD_DETECTED` | Signpad detected. Click 'Connect Signpad' to begin. |
682
- | `READY_TO_SIGN_MOUSE` | Ready to sign. Use your mouse. |
683
- | `READY_TO_SIGN_PHYSICAL` | Ready to sign. Use the device pen. |
671
+ | Field | Type | Description |
672
+ | :------------- | :------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
673
+ | `lang` | `string` | The active language code. Built-in translations are bundled for `'en'`, `'cs'` and `'pt'`; other languages require `langPath` or `translations` (see [Applying a language](#applying-a-language)). |
674
+ | `langPath` | `string` | URL path to fetch translation files. Expects files in `[path]/[lang].json` format. |
675
+ | `translations` | `object` | An inline object containing key-value pairs (e.g., `{ "OK": "Confirm signature" }`). Overrides files. |
684
676
 
685
- If you add any new keys, mirror them in every language file you provide to guarantee they fall back gracefully to English.
677
+ ### `theme`
686
678
 
687
- ### ⚙️ Logic & Events
679
+ Color theme of the component.
688
680
 
689
- The component provides two ways to interact with internal processes.
681
+ | Option | Type | Required | Description |
682
+ | :---------- | :---------------------------- | :-------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
683
+ | **`theme`** | `'light' \| 'dark' \| 'auto'` | Optional, default `'light'` | Color theme. `'dark'` switches the bars, buttons and overlay to the dark design tokens; `'auto'` follows the OS `prefers-color-scheme` and updates live. The signature canvas stays light in every theme, because the default ink is black. Exported as type `SignpadTheme`. The component sets `data-theme` on its host element, so you can style around it. |
690
684
 
691
- #### 🛠️ Action Handlers (`actionHandlers`)
685
+ ### `actionHandlers`
692
686
 
693
687
  These allow you to **inject custom logic** directly into the internal button flows. Your code runs alongside the component's internal logic.
694
688
 
695
- | Handler | Arguments | Description |
696
- | :------------- | :---------------------------------- | :---------------------------------------------------------------------- |
697
- | `handleOk` | `data?: ISignatureConfirmationData` | Extends the internal OK button logic. Receives captured signature data. |
698
- | `handleClear` | — | Extends the internal Clear button logic. |
699
- | `handleCancel` | — | Extends the internal Cancel button logic. |
689
+ | Handler | Arguments | Description |
690
+ | :------------- | :---------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------- |
691
+ | `handleOk` | `data?: ISignatureConfirmationData` | Extends the internal OK button logic. Receives the captured signature, see [Signature result](#signature-result-isignatureconfirmationdata). |
692
+ | `handleClear` | — | Extends the internal Clear button logic. |
693
+ | `handleCancel` | — | Extends the internal Cancel button logic. |
700
694
 
701
- #### 🔔 Event Callbacks (`eventCallbacks`)
695
+ Handlers may be `async`. The component awaits them, and only then dispatches the matching `sign-ok` / `sign-clear` / `sign-cancel` event.
696
+
697
+ ```typescript
698
+ const config: SignpadConfig = {
699
+ lease,
700
+ actionHandlers: {
701
+ // Runs after the signature is captured, before `sign-ok` is dispatched
702
+ handleOk: async (data) => {
703
+ if (!data) return;
704
+ const [, imageBase64, metadata] = data;
705
+ await fetch("/api/signatures", {
706
+ method: "POST",
707
+ headers: { "Content-Type": "application/json" },
708
+ body: JSON.stringify({ imageBase64, metadata }),
709
+ });
710
+ },
711
+ handleClear: () => console.log("Signature cleared"),
712
+ handleCancel: () => console.log("Signing cancelled"),
713
+ },
714
+ };
715
+ ```
716
+
717
+ ### `eventCallbacks`
702
718
 
703
719
  Standard event listeners triggered _after_ specific actions. Useful for observing the component from your application state.
704
720
 
705
- | Callback | Payload | Description |
706
- | :------------- | :----------------------------------------------- | :------------------------------------------------------------------------------------------------- |
707
- | `onPen` | `event: CustomEvent<IPenData>` | Dispatched when a device or mouse fallback are in contact with component or signature pad display. |
708
- | `onConnecting` | `event: CustomEvent<void>` | Dispatched when a device is in loading state. |
709
- | `onConnect` | `event: CustomEvent<{ deviceInfo: any }>` | Dispatched when a device or mouse fallback connects. |
710
- | `onDisconnect` | `event: CustomEvent<void>` | Dispatched when the hardware connection is closed. |
711
- | `onOk` | `event: CustomEvent<ISignatureConfirmationData>` | Dispatched when signature is confirmed. |
712
- | `onClear` | `event: CustomEvent<void>` | Dispatched when the canvas has been cleared. |
713
- | `onCancel` | `event: CustomEvent<void>` | Dispatched when the user aborts the session. |
714
- | `onError` | `event: CustomEvent<Error>` | Dispatched on critical failures (lease, driver, connection, etc.). |
721
+ | Callback | DOM event | Payload | Description |
722
+ | :------------- | :---------------- | :----------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------ |
723
+ | `onPen` | `sign-pen` | `event: CustomEvent<IPenData>` | Dispatched when a device or mouse fallback are in contact with component or signature pad display. |
724
+ | `onConnecting` | `sign-connecting` | `event: CustomEvent<void>` | Dispatched when a device is in loading state. |
725
+ | `onConnect` | `sign-connect` | `event: CustomEvent<{ deviceInfo: any }>` | Dispatched when a device or mouse fallback connects. |
726
+ | `onDisconnect` | `sign-disconnect` | `event: CustomEvent<void>` | Dispatched when the hardware connection is closed. |
727
+ | `onOk` | `sign-ok` | `event: CustomEvent<ISignatureConfirmationData>` | Dispatched when signature is confirmed. The signature is in `event.detail`, see [Signature result](#signature-result-isignatureconfirmationdata). |
728
+ | `onClear` | `sign-clear` | `event: CustomEvent<void>` | Dispatched when the canvas has been cleared. |
729
+ | `onCancel` | `sign-cancel` | `event: CustomEvent<void>` | Dispatched when the user aborts the session. |
730
+ | `onError` | `sign-error` | `event: CustomEvent<ISignpadErrorDetail>` | Dispatched on critical failures (lease, driver, connection, billing, etc.). |
715
731
 
716
732
  > `actionHandlers` and `eventCallbacks` are separate hooks and can both be configured.
717
733
 
734
+ **Listening with the config.** Pass the callbacks in `eventCallbacks`; each receives the `CustomEvent`:
735
+
736
+ ```typescript
737
+ const config: SignpadConfig = {
738
+ lease,
739
+ eventCallbacks: {
740
+ onOk: (event) => {
741
+ const [points, imageBase64] = event.detail; // see Signature result
742
+ console.log(`${points.length} points`, imageBase64);
743
+ },
744
+ },
745
+ };
746
+ ```
747
+
748
+ **Listening without the config.** Every callback is also a DOM event (column "DOM event"), so `addEventListener` works on the element like on any other:
749
+
750
+ ```js
751
+ signosoftPad.addEventListener("sign-ok", (event) => {
752
+ const [points, imageBase64] = event.detail; // see Signature result
753
+ console.log(`${points.length} points`, imageBase64);
754
+ });
755
+ ```
756
+
757
+ #### `onPen` payload
758
+
759
+ `onPen` is emitted continuously while writing and provides a single `IPenData` object in `event.detail`.
760
+
761
+ ```typescript
762
+ interface IPenData {
763
+ relativeX: number; // normalized 0..1
764
+ relativeY: number; // normalized 0..1
765
+ pressure?: number;
766
+ timestamp?: number; // Unix ms (Date.now()) at the time of capture
767
+ inContact: boolean;
768
+ [key: string]: any; // the driver may add more fields; the component does not populate absoluteX, absoluteY, ready or sequence
769
+ }
770
+ ```
771
+
718
772
  ---
719
773
 
720
- ### 💡 Example Implementation
774
+ ### Full example
721
775
 
722
776
  ```typescript
723
777
  this.config = {
@@ -738,6 +792,131 @@ this.config = {
738
792
  };
739
793
  ```
740
794
 
795
+ <a id="results--errors"></a>
796
+
797
+ ## 📤 Results & Errors
798
+
799
+ What the component hands back to your code: the captured signature and the errors it reports.
800
+
801
+ ### Signature result (`ISignatureConfirmationData`)
802
+
803
+ The captured signature arrives in three places, always in the same shape: `ok()` returns it, `actionHandlers.handleOk(data)` receives it, and `eventCallbacks.onOk` (DOM event `sign-ok`) carries it in `event.detail`.
804
+
805
+ It is an **array of three items**, not an object, so read it by position or destructure it:
806
+
807
+ ```ts
808
+ eventCallbacks: {
809
+ onOk: (event) => {
810
+ const [points, imageBase64, metadata] = event.detail;
811
+ },
812
+ },
813
+ ```
814
+
815
+ | Index | Name | Type | Content |
816
+ | :---- | :------------ | :--------------- | :------------------------------------- |
817
+ | `0` | `points` | `IPenData[]` | Every captured pen sample. |
818
+ | `1` | `imageBase64` | `string \| null` | PNG data URL of the signature. |
819
+ | `2` | `metadata` | object | Canvas size, tablet info and duration. |
820
+
821
+ #### `[0]` points
822
+
823
+ Every captured pen sample, in order. `pressure` is `0.5` on the mouse/touch fallback.
824
+
825
+ ```typescript
826
+ interface IPenDataPoint {
827
+ relativeX: number; // normalized 0..1
828
+ relativeY: number; // normalized 0..1
829
+ absoluteX?: number; // not populated by the component
830
+ absoluteY?: number; // not populated by the component
831
+ pressure: number;
832
+ timestamp: number; // Unix ms (Date.now()) at the time of capture
833
+ inContact: boolean;
834
+ }
835
+ ```
836
+
837
+ #### `[1]` imageBase64
838
+
839
+ Base64 PNG data URL of the signature, e.g. `"data:image/png;base64,iVBORw0KGgo..."`, or `null` if no image could be captured.
840
+
841
+ #### `[2]` metadata
842
+
843
+ The fields depend on whether a physical tablet or the mouse/touch fallback produced the signature.
844
+
845
+ ```typescript
846
+ interface ISignatureResultMeta {
847
+ canvas?: ISignatureCanvasMeta;
848
+ // Physical tablet only. `undefined` on the mouse/touch fallback.
849
+ tablet?: ISignatureTabletMeta;
850
+ // Mouse/touch fallback only: seconds between the first and last pen sample.
851
+ // Not reported for a physical tablet.
852
+ duration?: number;
853
+ [key: string]: any;
854
+ }
855
+
856
+ interface ISignatureCanvasMeta {
857
+ width?: number;
858
+ height?: number;
859
+ // Mouse/touch fallback only:
860
+ cssWidth?: number;
861
+ cssHeight?: number;
862
+ dpr?: number;
863
+ // Physical tablet only:
864
+ top?: number;
865
+ left?: number;
866
+ right?: number;
867
+ bottom?: number;
868
+ [key: string]: any;
869
+ }
870
+
871
+ interface ISignatureTabletMeta {
872
+ name?: string;
873
+ aspectRatio?: number;
874
+ isCanvasStyleTablet?: boolean;
875
+ screenWidth?: number;
876
+ screenHeight?: number;
877
+ drawingArea?: { x: number; y: number; width: number; height: number };
878
+ serialNumber?: string | Promise<string>;
879
+ vendorId?: number | null;
880
+ productId?: number | null;
881
+ [key: string]: any;
882
+ }
883
+ ```
884
+
885
+ ---
886
+
887
+ ### Error handling (`onError`)
888
+
889
+ Critical failures (lease, driver, connection, billing, ...) do not throw. The component switches to the `ERROR` state, shows an overlay and reports the failure through `eventCallbacks.onError` (DOM event `sign-error`). Branch on `event.detail.code`:
890
+
891
+ ```typescript
892
+ const config: SignpadConfig = {
893
+ lease,
894
+ eventCallbacks: {
895
+ onError: (event) => {
896
+ const { code, message, error } = event.detail;
897
+ if (code === SignpadErrorCode.BILLING_DENIED) {
898
+ notifyAdmin("Out of signing credits");
899
+ }
900
+ console.error(code, message, error);
901
+ },
902
+ },
903
+ };
904
+ ```
905
+
906
+ `event.detail` is `ISignpadErrorDetail`:
907
+
908
+ ```typescript
909
+ interface ISignpadErrorDetail {
910
+ code: SignpadErrorCode; // "AREA-NN", e.g. "CON-01", see Error codes
911
+ message: string; // error.message, for convenience
912
+ error: Error; // the original error
913
+ }
914
+ ```
915
+
916
+ All values of `code`, what they mean and how the user recovers are in [Error codes](#error-codes).
917
+
918
+ <a id="methods"></a>
919
+
741
920
  ## 🧩 Methods
742
921
 
743
922
  The following methods are available on the component instance to control the signing process programmatically.
@@ -752,180 +931,322 @@ The following methods are available on the component instance to control the sig
752
931
  | `clear()` | `Promise<void>` | Wipes the signature from the UI and hardware without ending the session. |
753
932
  | `cancel()` | `Promise<void>` | Aborts the current session and resets the component state. |
754
933
 
934
+ **Example:** a full signing round. The snippets for each method below call the method on the same `signosoftPad` element.
935
+
936
+ ```js
937
+ await signosoftPad.connect(); // opens the WebHID picker, so call it from a click handler
938
+
939
+ // ... the user signs ...
940
+
941
+ const data = await signosoftPad.ok(); // ends the session and returns the signature
942
+ if (data) {
943
+ const [points, imageBase64, metadata] = data;
944
+ console.log(`${points.length} points`, imageBase64, metadata);
945
+ }
946
+
947
+ // OK and Cancel end the session, so start a new one before the next signature
948
+ await signosoftPad.startSigning();
949
+ ```
950
+
755
951
  ---
756
952
 
757
- #### 📜 `connect()`
953
+ ### `connect()`
758
954
 
759
- 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.
955
+ Connects a device to the component. Because it uses the **WebHID API**, it must be triggered by a **user gesture** (like a button click) to satisfy browser security requirements.
760
956
 
761
- 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.
957
+ ```js
958
+ await signosoftPad.connect(); // call it from a click handler
959
+ ```
960
+
961
+ It first tries 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.
962
+
963
+ **Returns:** `Promise<void>`, resolved once the connection attempt (or fallback) is complete.
762
964
 
763
- **Returns:**
965
+ **Tip:** always call it from a click handler so the user can pick their device from the browser list.
764
966
 
765
- - A `Promise<void>` that resolves once the connection attempt (or fallback) is complete.
967
+ ---
766
968
 
767
- #### 📜 `disconnect()`
969
+ ### `disconnect()`
768
970
 
769
971
  Safely terminates the communication channel with the signature tablet.
770
972
 
771
- **What happens during disconnect:**
973
+ ```js
974
+ await signosoftPad.disconnect();
975
+ ```
976
+
977
+ What happens:
772
978
 
773
979
  1. The hardware driver is stopped and released.
774
980
  2. The internal cache of device information is cleared.
775
- 3. The component UI transitions back to the `DISCONNECTED` state.
981
+ 3. The component switches to mouse input (`CONNECTED_MOUSE_FALLBACK`). Failures of the driver's own disconnect are logged, not surfaced.
776
982
  4. Any active mouse listeners are removed.
777
983
  5. A `SIGN_DISCONNECT` event is dispatched.
778
984
 
779
- #### 💡 Best Practices
985
+ **Returns:** `Promise<void>`
780
986
 
781
- - **Initial Connection:** Always call `connect()` from a click handler so the user can select their device from the browser list.
782
- - **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.
987
+ **Tip:** call it when the component is destroyed or the user leaves the signing page, to free the USB port.
783
988
 
784
- #### 📜 `startSigning(options?: IDrawingOptions)`
989
+ ---
785
990
 
786
- 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.
991
+ ### `startSigning(options?: IDrawingOptions)`
787
992
 
788
- ```typescript
789
- // Example: Start a session with custom ink
790
- await signpad.startSigning({
993
+ Prepares the component for a new signature. OK and Cancel end the signing session, so call this (or let the user press the built-in **Start new signature** button, see `startNewSignatureButtonVisible`) before the next signature.
994
+
995
+ ```js
996
+ // Start a session with custom ink
997
+ await signosoftPad.startSigning({
791
998
  color: "#0000FF",
792
999
  maxWidth: 4,
793
1000
  minWidth: 2,
794
1001
  });
795
1002
  ```
796
1003
 
797
- #### 📜 `ok()`
1004
+ It transitions the state to `CONNECTING`, resets any previous drawing data, and activates the pen input for either a physical device or mouse fallback.
798
1005
 
799
- This is the standard way to confirm a signature. It:
1006
+ **Returns:** `Promise<void>`
800
1007
 
801
- 1. Stops the signing process.
802
- 2. Clears the physical device's screen (if connected).
803
- 3. Dispatches the `SIGN_OK` event.
804
- 4. Returns the final signature payload (coordinates, pressure, metadata).
1008
+ ---
1009
+
1010
+ ### `stopSigning()`
805
1011
 
806
- **Returned payload shape (`ISignatureConfirmationData`)**
1012
+ Low-level method that forces the driver to stop capturing data and returns the signature payload. Unlike `ok()`, it does not trigger the full finalization flow (UI transitions or device screen clearing).
807
1013
 
808
1014
  ```js
809
- [
810
- points, // index 0: Data about each point
811
- imageBase64, // index 1: Image in base64
812
- metadata, // index 2: Information about tablet and canvas
813
- ];
1015
+ const [penDataBatch, imageBase64, metadata] = await signosoftPad.stopSigning();
814
1016
  ```
815
1017
 
1018
+ **Returns:** `Promise<any>`, a 3-element tuple:
1019
+
1020
+ - `penDataBatch`: array of `IPenData` points collected during the session
1021
+ - `imageBase64`: PNG data URL of the signature drawn on canvas, or `null` if unavailable
1022
+ - `metadata`: object with `canvas` (dimensions, DPR) and `tablet` (device info) fields
1023
+
1024
+ ---
1025
+
1026
+ ### `ok()`
1027
+
1028
+ The standard way to confirm a signature.
1029
+
816
1030
  ```js
817
- // [0] Points structure — same IPenData shape documented under onPen below
818
- {
819
- relativeX: number, // normalized 0..1
820
- relativeY: number, // normalized 0..1
821
- pressure: number,
822
- inContact: boolean,
823
- timestamp: number, // Unix ms (Date.now()) at the time of capture
1031
+ const data = await signosoftPad.ok();
1032
+ if (data) {
1033
+ const [points, imageBase64, metadata] = data;
824
1034
  }
825
1035
  ```
826
1036
 
1037
+ It:
1038
+
1039
+ 1. Stops the signing process.
1040
+ 2. Clears the physical device's screen (if connected).
1041
+ 3. Dispatches the `SIGN_OK` event.
1042
+ 4. Returns the final signature payload (coordinates, pressure, metadata).
1043
+
1044
+ After `ok()` the signing session is over: nothing is captured until a new one starts. The bottom bar then shows **Start new signature** (see `startNewSignatureButtonVisible`), or you call `startSigning()` yourself.
1045
+
1046
+ **Returns:** `Promise<ISignatureConfirmationData | undefined>`, the same payload `handleOk` and `onOk` receive; see [Signature result](#signature-result-isignatureconfirmationdata).
1047
+
1048
+ ---
1049
+
1050
+ ### `clear()`
1051
+
1052
+ Resets the drawing state on both the web canvas and the physical tablet's display, so the user can start over without leaving the current session.
1053
+
827
1054
  ```js
828
- // [1] imageBase64 — base64 PNG data URL, or null if no image could be captured
829
- "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUA...";
1055
+ await signosoftPad.clear();
830
1056
  ```
831
1057
 
1058
+ **Returns:** `Promise<void>`. Dispatches `SIGN_CLEAR`.
1059
+
1060
+ ---
1061
+
1062
+ ### `cancel()`
1063
+
1064
+ Stops the session immediately, without collecting any signature data.
1065
+
832
1066
  ```js
833
- // [2] Metadata structure
834
- // The exact shape depends on whether a physical tablet or the mouse/touch
835
- // fallback produced the signature — the two paths report different fields.
836
-
837
- // Mouse / touch fallback:
838
- {
839
- canvas: {
840
- width: number,
841
- height: number,
842
- cssWidth: number,
843
- cssHeight: number,
844
- dpr: number
845
- },
846
- tablet: undefined,
847
- duration: number // seconds elapsed between the first and last recorded pen sample
848
- }
1067
+ await signosoftPad.cancel();
1068
+ ```
849
1069
 
850
- // Physical tablet (driver-reported):
851
- {
852
- canvas: {
853
- top?: number,
854
- left?: number,
855
- right?: number,
856
- bottom?: number,
857
- width: number,
858
- height: number
859
- },
860
- tablet: {
861
- name: string,
862
- aspectRatio: number,
863
- isCanvasStyleTablet: boolean,
864
- screenWidth: number,
865
- screenHeight: number,
866
- drawingArea: { x: number, y: number, width: number, height: number },
867
- serialNumber: Promise<string> | string,
868
- vendorId: number | null,
869
- productId: number | null
870
- }
871
- // no `duration` field is reported for the physical-tablet path
872
- }
1070
+ Like `ok()`, it ends the signing session: the bottom bar then shows **Start new signature**, or call `startSigning()` to begin again.
1071
+
1072
+ **Returns:** `Promise<void>`. Dispatches `SIGN_CANCEL`.
1073
+
1074
+ <a id="component-states"></a>
1075
+
1076
+ ## 🔄 Component States
1077
+
1078
+ The component progresses through a finite set of states, exposed via the `SignpadState` enum (importable from `@signosoft/signpad-js`). Useful for observing the component lifecycle in your application.
1079
+
1080
+ | State | Value | Description |
1081
+ | :------------------------- | :--------------------------- | :-------------------------------------------------------------------------------------------------------- |
1082
+ | `CONNECTING` | `"connecting"` | Preparing or connecting. Also the initial state while waiting for the lease. |
1083
+ | `CONNECTED_PHYSICAL` | `"connected_physical"` | Physical tablet connected and ready. |
1084
+ | `CONNECTED_MOUSE_FALLBACK` | `"connected_mouse_fallback"` | No tablet found; mouse/touch fallback is active. |
1085
+ | `PROCESSING_OK` | `"processing_ok"` | OK action is being processed (finalizing signature). |
1086
+ | `PROCESSING_CLEAR` | `"processing_clear"` | Clear action is being processed. |
1087
+ | `PROCESSING_CANCEL` | `"processing_cancel"` | Cancel action is being processed. |
1088
+ | `DISCONNECTING` | `"disconnecting"` | Disconnect in progress. |
1089
+ | `ERROR` | `"error"` | An error occurred (see `onError` callback and [Error codes](#error-codes)). Recovery depends on the code. |
1090
+
1091
+ <a id="error-codes"></a>
1092
+
1093
+ ## ⚠️ Error Codes
1094
+
1095
+ Reported through the `onError` callback (`sign-error` event), see [Error handling](#error-handling-onerror). The `code` field uses the `SignpadErrorCode` enum, importable from `@signosoft/signpad-js`. Format `AREA-NN`; it decides how the user gets out of the error:
1096
+
1097
+ | Code | `SignpadErrorCode` | Meaning | Recovery (component) | What your app should do |
1098
+ | :----- | :--------------------- | :--------------------------------------------------------------------------------------------------------------------------------------- | :---------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------- |
1099
+ | LIC-01 | `LICENSE_MISSING` | No `lease` in config after ~6 s. Integration error. | None. Passing a valid `lease` recovers automatically. | Make sure `config.lease` is set; fix the lease fetch (check `onError` / network tab). |
1100
+ | LIC-02 | `LICENSE_INVALID` | The driver rejected the lease (malformed, or its signature/validity/origin check failed). An **expired** lease is reported the same way. | None. Pass a new `lease`. | Fetch a **fresh lease** on every mount and do not cache it across days; verify the licence allows the page's origin. |
1101
+ | DRV-01 | `DRIVER_INIT_FAILED` | Driver / WASM could not be started. | "Restart session" button | Log it; if it persists, check that WASM/`locateFile` assets load and the browser is supported. |
1102
+ | CON-01 | `CONNECT_FAILED` | Unexpected failure while connecting. | "Restart session" button | Nothing required; ask the user to retry or reconnect the device. |
1103
+ | NET-01 | `NETWORK_ERROR` | Network / billing check unreachable. | "Restart session" button | Check connectivity and any proxy/CSP blocking the licence server. |
1104
+ | BIL-01 | `BILLING_DENIED` | The lease ran out of signing credits. | "Restart session" button | Top up credits on the licence; a restart will not help until then. |
1105
+ | SES-01 | `START_SIGNING_FAILED` | The signing session could not be started. | "Restart session" button | Nothing required; ask the user to restart the session. |
1106
+ | ACT-01 | `ACTION_FAILED` | OK / Clear / Cancel failed. | "Restart session" button | Nothing required; the user can retry the action. |
1107
+
1108
+ The error overlay shows the message followed by the code, e.g. `... (Error code BIL-01)` (the suffix is not translated), and a "Restart session" button (for every code except `LIC-01`/`LIC-02`) is shown in the top bar. Nothing restarts automatically, so the user always sees the error first. The button works regardless of `topBarConnectButtonVisible`; if the top bar is hidden (`topBarVisible: false`) it is shown in the overlay instead.
1109
+
1110
+ <a id="type-definitions"></a>
1111
+
1112
+ ## 🧱 Type Definitions
1113
+
1114
+ Every type is exported from `@signosoft/signpad-js` and ships with the package, so you get autocompletion and type checking without extra setup. Each one is defined once, where it is used:
1115
+
1116
+ ```typescript
1117
+ import type {
1118
+ SignpadConfig,
1119
+ ISignatureConfirmationData,
1120
+ } from "@signosoft/signpad-js";
873
1121
  ```
874
1122
 
875
- `handleOk(data)` receives the same structure. `onOk` receives it in `event.detail`.
1123
+ | Type | Used for | Defined in |
1124
+ | :-------------------------------------------------------------------------------------------------------------------- | :------------------------------------------ | :----------------------------------------------------------------------------------- |
1125
+ | `SignpadConfig`, `SignpadTheme` | the `config` object | [Properties](#properties) |
1126
+ | `IDrawingOptions` | pen color and width | [`startSigning()`](#startsigning-options-idrawingoptions), [Properties](#properties) |
1127
+ | `IUIVisibilityOptions` | show/hide UI parts | [uiVisibilityOptions](#uivisibilityoptions) |
1128
+ | `ILanguageOptions`, `LanguageCode`, `ITranslationSet` | language and translations | [languageOptions](#languageoptions) |
1129
+ | `IActionHandlers` | `handleOk` / `handleClear` / `handleCancel` | [actionHandlers](#actionhandlers) |
1130
+ | `IEventCallbacks` | `onOk`, `onPen`, `onError`, ... | [eventCallbacks](#eventcallbacks) |
1131
+ | `IPenData` | `onPen` payload | [`onPen` payload](#onpen-payload) |
1132
+ | `ISignatureConfirmationData`, `IPenDataPoint`, `ISignatureResultMeta`, `ISignatureCanvasMeta`, `ISignatureTabletMeta` | the signature | [Signature result](#signature-result-isignatureconfirmationdata) |
1133
+ | `ISignpadErrorDetail` | `onError` payload | [Error handling](#error-handling-onerror) |
1134
+ | `SignpadState` | component state (enum) | [Component States](#component-states) |
1135
+ | `SignpadErrorCode` | error codes (enum) | [Error codes](#error-codes) |
1136
+ | `SignpadEventType` | `sign-*` DOM event names (enum) | [Event Callbacks](#eventcallbacks) |
1137
+
1138
+ The enums exist at runtime, so import them without `type`.
1139
+
1140
+ <a id="localization--translation-keys"></a>
876
1141
 
877
- #### 📜 `onPen` payload
1142
+ ## 🌐 Localization & Translation Keys
878
1143
 
879
- `onPen` is emitted continuously while writing and provides a single `IPenData` object in `event.detail`.
1144
+ All texts come from `config.languageOptions` (`lang`, `langPath`, `translations`). There is no other way to set them.
880
1145
 
881
- ```js
882
- {
883
- relativeX: number, // normalized 0..1
884
- relativeY: number, // normalized 0..1
885
- pressure: number,
886
- inContact: boolean,
887
- timestamp: number, // Unix ms (Date.now()) at the time of capture
888
- }
1146
+ | You want | Set |
1147
+ | :---------------------------- | :------------------------------------------------------------------------------------------------------ |
1148
+ | English, Czech or Portuguese | `lang: "cs"` (built in) |
1149
+ | Another language, e.g. German | `lang: "de"` and `langPath: "/i18n"`, with a `de.json` you host |
1150
+ | Your own JSON file | `import yourJson from "./de.json"`, then `translations: yourJson` (works from any file in your project) |
1151
+ | Change a few texts only | `translations: { OK: "Confirm" }` |
1152
+
1153
+ ### Applying a language
1154
+
1155
+ **Why there are two ways.** The component runs in the browser, and a browser can only load what a server hands out. So a translation file reaches the component in one of two ways:
1156
+
1157
+ - **Served as a static file (`langPath`).** Put the file in the folder your web server serves as static files and point `langPath` at its URL. The component downloads it with `fetch` when the language is needed. A file anywhere else in your project, such as `src/de.json` or `./../de.json`, is not served, so it cannot be loaded and the texts silently stay English.
1158
+ - **Bundled into your app (`translations`).** If the file lives anywhere else in your project, `import` it and pass it as `translations`. The bundler includes it in your build, so no served folder and no request are needed.
1159
+
1160
+ **Hosted file (`langPath`).** For any language that is not built in (anything except `en`, `cs`, `pt`):
1161
+
1162
+ 1. Copy `node_modules/@signosoft/signpad-js/src/i18n/en.json`, translate the values and save it as `de.json` in a folder your app serves as static files.
1163
+ 2. Set `langPath` to that folder. The component loads `${langPath}/${lang}.json`.
1164
+
1165
+ ```ts
1166
+ languageOptions: { lang: "de", langPath: "/i18n" } // loads /i18n/de.json
889
1167
  ```
890
1168
 
891
- > The `IPenData` type also allows optional `absoluteX`, `absoluteY`, `ready`, and `sequence` fields for forward compatibility with the underlying driver, but the component does not currently populate them.
1169
+ `langPath` is the URL of that folder, not a path on disk. If the server serves the folder at the site root, a file saved as `<static folder>/i18n/de.json` is reachable at `/i18n/de.json`, so `langPath` is `"/i18n"`. Which folder is served as static files depends on your project setup; check where your tooling copies files that are not bundled.
892
1170
 
893
- #### 📜 `stopSigning()`
1171
+ **Your own texts (`translations`).** Pass an object of key-value strings, flat or one object per language. Keys you leave out come from the selected language:
894
1172
 
895
- A low-level method that forces the driver to stop capturing data and returns the signature payload as a 3-element tuple `[penDataBatch, imageBase64, metadata]`. Unlike `ok()`, it does not trigger the full "Finalization" flow (UI transitions or device screen clearing).
1173
+ ```ts
1174
+ languageOptions: { lang: "cs", translations: { START_NEW_SIGNATURE: "Podepsat znovu" } }
896
1175
 
897
- - `penDataBatch` — array of `IPenData` points collected during the session
898
- - `imageBase64` — PNG data URL of the signature drawn on canvas, or `null` if unavailable
899
- - `metadata` — object with `canvas` (dimensions, DPR) and `tablet` (device info) fields
1176
+ languageOptions: { lang: "cs", translations: { cs: { OK: "Potvrdit" }, en: { OK: "Confirm" } } }
1177
+ ```
900
1178
 
901
- #### 📜 `clear()`
1179
+ A JSON file you already have works the same way, import it and pass it:
902
1180
 
903
- 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.
1181
+ ```ts
1182
+ import signpadTexts from "./i18n/signpad.json";
904
1183
 
905
- #### 📜 `cancel()`
1184
+ languageOptions: { lang: "cs", translations: signpadTexts }
1185
+ ```
906
1186
 
907
- 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.
1187
+ If the file is nested (it also holds your app's texts), pass only the part for the signpad: `translations: appTexts.signpad`.
908
1188
 
909
- ## 🔄 Component States
1189
+ **Changing the language at runtime.** Assign a new config with a different `lang`; the texts update without a reconnect:
910
1190
 
911
- The component progresses through a finite set of states, exposed via the `SignpadState` enum (importable from `@signosoft/signpad-js`). Useful for observing the component lifecycle in your application.
1191
+ ```ts
1192
+ signosoftPad.config = {
1193
+ ...signosoftPad.config,
1194
+ languageOptions: { ...signosoftPad.config.languageOptions, lang: "pt" },
1195
+ };
1196
+ ```
912
1197
 
913
- | State | Value | Description |
914
- | :----------------------------- | :------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
915
- | `INITIAL` | `"initial"` | Component just mounted, no initialization yet. |
916
- | `CONNECTING` | `"connecting"` | Connection attempt in progress. |
917
- | `WAITING_FOR_DEVICE_SELECTION` | `"waiting_for_device_selection"` | Browser HID picker is open, waiting for user selection. |
918
- | `CONNECTED_PHYSICAL` | `"connected_physical"` | Physical tablet connected and ready. |
919
- | `CONNECTED_MOUSE_FALLBACK` | `"connected_mouse_fallback"` | No tablet found; mouse/touch fallback is active. |
920
- | `SIGNING_ACTIVE` | `"signing_active"` | Defined for future use; the component does not currently transition to this state — drawing happens while still in `CONNECTED_PHYSICAL`/`CONNECTED_MOUSE_FALLBACK`. |
921
- | `PROCESSING_OK` | `"processing_ok"` | OK action is being processed (finalizing signature). |
922
- | `PROCESSING_CLEAR` | `"processing_clear"` | Clear action is being processed. |
923
- | `PROCESSING_CANCEL` | `"processing_cancel"` | Cancel action is being processed. |
924
- | `DISCONNECTING` | `"disconnecting"` | Disconnect in progress. |
925
- | `DISCONNECTED` | `"disconnected"` | Component is idle and disconnected. |
926
- | `ERROR` | `"error"` | An unrecoverable error occurred (see `onError` callback). |
1198
+ **Good to know**
1199
+
1200
+ - Missing keys fall back to English. If the file cannot be loaded, the component logs a message and shows English.
1201
+ - If `translations` has an entry for the active language, `langPath` is not fetched at all. Put the complete set into one of the two.
1202
+ - `langPath` can also be one exact file (`"/i18n/signpad.json"`). The same file is requested again when `lang` changes, so use a folder if you need a file per language.
1203
+ - A relative `langPath` (`"i18n"`) is resolved against the page URL. Start with `/` if your app is served from a sub-route.
1204
+ - Keep placeholders such as `{device}` in your texts; they are replaced at runtime.
1205
+
1206
+ The complete list of keys that can be translated ships with the package as `node_modules/@signosoft/signpad-js/src/i18n/en.json`. Copy its structure when creating new language files or inline overrides. The defaults are:
1207
+
1208
+ | Key | Default (English) |
1209
+ | :------------------------- | :--------------------------------------------------------------------------------------- |
1210
+ | `OK` | OK |
1211
+ | `CLEAR` | Clear |
1212
+ | `CANCEL` | Cancel |
1213
+ | `CLEAR_SIGNATURE` | Clear signature |
1214
+ | `DISCONNECT` | Disconnect |
1215
+ | `CONNECT_SIGNPAD` | Connect Signpad |
1216
+ | `MOUSE` | Mouse |
1217
+ | `CONNECTED_UNKNOWN_DEVICE` | Connected (Unknown Device) |
1218
+ | `AUTO_CONNECTING` | Auto-connecting to device... |
1219
+ | `CONNECTING_TO_DEVICE` | Connecting to your device... |
1220
+ | `REVERTING_TO_DEFAULT` | Reverting to default input... |
1221
+ | `DISCONNECTING_DEVICE` | Disconnecting device... |
1222
+ | `INITIALIZING` | Preparing signature pad... |
1223
+ | `RESTART_SESSION` | Restart session |
1224
+ | `START_NEW_SIGNATURE` | Start new signature |
1225
+ | `PREPARING_SESSION` | Preparing session... |
1226
+ | `TEXT_PRESS_START_SIGNING` | Start a new signing session to continue. |
1227
+ | `CANVAS_ARIA_LABEL` | Signature drawing area |
1228
+ | `PROCESSING_OK` | Processing OK... |
1229
+ | `PROCESSING_CLEAR` | Clearing... |
1230
+ | `PROCESSING_CANCEL` | Canceling... |
1231
+ | `TEXT_START_SIGNING` | Connect your device. |
1232
+ | `TEXT_SIGN_WITH_PHYSICAL` | Please sign using the device pen. |
1233
+ | `TEXT_SIGN_WITH_MOUSE` | Please sign using your mouse. |
1234
+ | `TEXT_SIGN_GENERIC` | Sign in the area above using mouse or pen. |
1235
+ | `TEXT_SIGNING_WITH_DEVICE` | Signing with {device} |
1236
+ | `LICENSE_MISSING` | License key is missing. Provide a valid lease in the component config. |
1237
+ | `CONNECTION_ERROR` | Could not connect to the signature pad. Try again. |
1238
+ | `ERROR_DRIVER_INIT` | The signature driver could not be started. Try again or reload the page. |
1239
+ | `NETWORK_ERROR` | Network error. Check your internet connection and try again. |
1240
+ | `INSUFFICIENT_TOKENS` | Not enough signing credits. |
1241
+ | `ERROR_STARTING_SIGNING` | Could not start the signing session. |
1242
+ | `ERROR_PROCESSING_ACTION` | The action could not be completed. |
1243
+ | `INVALID_LICENSE_KEY` | The license key was rejected. Check that the lease is valid and issued for this website. |
1244
+ | `READY_TO_SIGN_MOUSE` | Ready to sign. Use your mouse. |
1245
+ | `READY_TO_SIGN_PHYSICAL` | Ready to sign. Use the device pen. |
927
1246
 
928
- ---
1247
+ If you add any new keys, mirror them in every language file you provide to guarantee they fall back gracefully to English.
1248
+
1249
+ <a id="styling--theming"></a>
929
1250
 
930
1251
  ## 🎨 Styling & Theming
931
1252
 
@@ -934,36 +1255,49 @@ The component uses a hybrid theming model:
934
1255
  - It ships with complete default styles (works out of the box).
935
1256
  - You can override the visual design through CSS variables.
936
1257
 
937
- ## ✍️ Override via CSS
1258
+ ### Design tokens
938
1259
 
939
- You can override variables in your stylesheet
1260
+ Defaults come from the Signosoft design system tokens, shipped inside the component (`src/styles/primitive.css` for raw scales, `src/styles/semantic.css` for meaning, e.g. `--background-brand`, `--content-primary`, `--border-focus`). They are declared on the component's `:host`, so they do not depend on the host page and do not leak into it. `--sign-*` variables below are the public API; their defaults point at semantic tokens. Override `--sign-*` on the `signosoft-signpad` element.
940
1261
 
941
- ### 📎 Copy-Paste Starter
1262
+ <a id="override-via-css"></a>
942
1263
 
943
- Paste this directly into your app stylesheet and adjust values:
1264
+ ## 🪄 Override via CSS
1265
+
1266
+ Set the `--sign-*` variables on the element in your own stylesheet. Variables set there beat the component's defaults, in light and dark theme alike:
944
1267
 
945
1268
  ```css
946
1269
  signosoft-signpad {
947
- /* Color Variables */
948
- --primary-color-0: #4e56ea;
949
- --primary-color-10: #7178ee;
950
- --background-color-0: #f1f2fd;
951
- --background-color-10: #e3e4fc;
952
- --text-color-0: #333e4a;
953
- --white-color: #ffffff;
954
- --grey-color: #b5b9be;
1270
+ --sign-top-bar-bg-base: #f3e8f8;
1271
+ --sign-top-bar-text-base: #5d1776;
1272
+ --sign-bottom-bar-bg-base: #f3e8f8;
1273
+ --sign-button-primary-bg-base: #7a1f9a;
1274
+ --sign-button-primary-bg-hover: #5d1776;
1275
+ }
1276
+ ```
1277
+
1278
+ The full list of variables follows below.
1279
+
1280
+ ### Copy-Paste Starter
1281
+
1282
+ Paste this directly into your app stylesheet and adjust values:
955
1283
 
1284
+ ```css
1285
+ signosoft-signpad {
956
1286
  /* Constraints */
957
- --min-height: 48px;
958
- --spacing-constraints: 16px 24px;
1287
+ --min-height: var(--unit-48);
1288
+ --spacing-constraints: var(--spacing-12) var(--spacing-16);
1289
+
1290
+ /* Host Sizing (override to make the component fluid, e.g. width: 100%; height: 100vh;) */
1291
+ --signpad-width: 25rem;
1292
+ --signpad-height: 31.25rem;
959
1293
 
960
1294
  /* CSS Variables */
961
- --sign-font-family: Arial, Helvetica, sans-serif;
962
- --sign-common-border-radius: 8px;
1295
+ --sign-font-family: var(--fontFamily-primary), Arial, Helvetica, sans-serif;
1296
+ --sign-common-border-radius: var(--borderRadius-s);
963
1297
 
964
1298
  /* Top Bar */
965
- --sign-top-bar-bg-base: var(--background-color-10);
966
- --sign-top-bar-text-base: var(--primary-color-0);
1299
+ --sign-top-bar-bg-base: var(--background-brandSubtle);
1300
+ --sign-top-bar-text-base: var(--brand-650);
967
1301
  --sign-top-bar-padding: var(--spacing-constraints);
968
1302
  --sign-top-bar-min-height: var(--min-height);
969
1303
  --sign-top-bar-border-top-left-radius: var(--sign-common-border-radius);
@@ -972,26 +1306,27 @@ signosoft-signpad {
972
1306
  --sign-top-bar-border-bottom-right-radius: 0;
973
1307
 
974
1308
  /* Canvas Area */
975
- --sign-canvas-bg-base: var(--background-color-0);
1309
+ --sign-canvas-bg-base: var(--brandBeige-100);
976
1310
  --sign-canvas-wrapper-border-top-left-radius: 0;
977
1311
  --sign-canvas-wrapper-border-top-right-radius: 0;
978
1312
  --sign-canvas-wrapper-border-bottom-left-radius: 0;
979
1313
  --sign-canvas-wrapper-border-bottom-right-radius: 0;
980
1314
 
981
1315
  /* Line */
982
- --sign-line-height: 22px;
983
- --sign-line-margin-bottom: 16px;
984
- --sign-line-border-base: var(--primary-color-10);
985
- --sign-line-additional-text-color: var(--text-color-0);
986
- --sign-line-margin: 0px 24px var(--sign-line-margin-bottom) 24px;
987
- --sign-canvas-line-text-font-size: 12px;
1316
+ --sign-line-height: var(--unit-24);
1317
+ --sign-line-margin-bottom: var(--spacing-16);
1318
+ --sign-line-border-base: var(--border-secondary);
1319
+ --sign-line-additional-text-color: var(--content-tertiary);
1320
+ --sign-line-margin: 0 var(--spacing-16) var(--sign-line-margin-bottom)
1321
+ var(--spacing-16);
1322
+ --sign-canvas-line-text-font-size: var(--text-s-regular-fontSize);
988
1323
  --sign-canvas-height-offset: var(--sign-line-height);
989
1324
 
990
1325
  /* Bottom Bar */
991
- --sign-bottom-bar-bg-base: var(--background-color-10);
1326
+ --sign-bottom-bar-bg-base: var(--background-brandSubtle);
992
1327
  --sign-bottom-bar-padding: var(--spacing-constraints);
993
1328
  --sign-bottom-bar-min-height: var(--min-height);
994
- --sign-bottom-bar-gap: 12px;
1329
+ --sign-bottom-bar-gap: var(--spacing-8);
995
1330
  --sign-bottom-bar-border-top-left-radius: 0;
996
1331
  --sign-bottom-bar-border-top-right-radius: 0;
997
1332
  --sign-bottom-bar-border-bottom-left-radius: var(--sign-common-border-radius);
@@ -1000,76 +1335,66 @@ signosoft-signpad {
1000
1335
  );
1001
1336
 
1002
1337
  /* Button general settings */
1003
- --sign-button-font-size: 16px;
1004
- --sign-button-padding: 14px 16px;
1005
- --sign-button-min-height: var(--min-height);
1006
- --sign-button-border-radius: 8px;
1007
- --sign-button-font-weight: 500;
1338
+ --sign-button-font-size: var(--text-m-semibold-fontSize);
1339
+ --sign-button-padding: var(--spacing-10);
1340
+ --sign-button-min-height: var(--unit-48);
1341
+ --sign-button-border-radius: var(--borderRadius-pill);
1342
+ --sign-button-font-weight: var(--text-m-semibold-fontWeight);
1008
1343
 
1009
1344
  /* Primary Buttons (OK, Clear, Cancel) */
1010
- --sign-button-primary-bg-base: var(--primary-color-0);
1011
- --sign-button-primary-bg-hover: var(--primary-color-10);
1012
- --sign-button-primary-bg-disabled: var(--grey-color);
1013
- --sign-button-primary-text-base: var(--white-color);
1014
- --sign-button-primary-text-hover: var(--white-color);
1015
- --sign-button-primary-text-disabled: var(--white-color);
1016
- --sign-button-primary-border-base: 1px solid var(--primary-color-0);
1017
- --sign-button-primary-border-hover: 1px solid var(--primary-color-10);
1018
- --sign-button-primary-border-disabled: 1px solid var(--grey-color);
1019
-
1020
- /* Link Buttons (Connect signpad) */
1021
- --sign-button-link-bg-base: transparent;
1022
- --sign-button-link-bg-hover: var(--background-color-10);
1023
- --sign-button-link-bg-disabled: transparent;
1024
- --sign-button-link-text-base: var(--primary-color-0);
1025
- --sign-button-link-text-hover: var(--primary-color-10);
1026
- --sign-button-link-text-disabled: var(--grey-color);
1027
- --sign-button-link-border-base: 1px solid var(--primary-color-0);
1028
- --sign-button-link-border-hover: 1px solid var(--primary-color-10);
1029
- --sign-button-link-border-disabled: 1px solid var(--grey-color);
1345
+ --sign-button-primary-bg-base: var(--background-brand);
1346
+ --sign-button-primary-bg-hover: var(--background-brandHover);
1347
+ --sign-button-primary-bg-pressed: var(--background-brandPressed);
1348
+ --sign-button-primary-bg-disabled: var(--background-disabled);
1349
+ --sign-button-primary-text-base: var(--content-primaryInverse);
1350
+ --sign-button-primary-text-hover: var(--content-primaryInverse);
1351
+ --sign-button-primary-text-disabled: var(--content-disabled);
1352
+ --sign-button-primary-border-base: var(--borderWidth-xs) solid transparent;
1353
+ --sign-button-primary-border-hover: var(--borderWidth-xs) solid transparent;
1354
+ --sign-button-primary-border-disabled: var(--borderWidth-xs) solid transparent;
1030
1355
 
1031
1356
  /* Device State Text Colors */
1032
- --sign-device-state-text-color-connected: green;
1033
- --sign-device-state-text-color-disconnected: red;
1357
+ --sign-device-state-text-color-connected: var(--brand-650);
1034
1358
 
1035
1359
  /* Loading Overlay */
1036
- --sign-loading-overlay-bg-color: rgba(255, 255, 255, 0.8);
1037
- --sign-loading-overlay-text-color: var(--text-color-0);
1038
- --sign-loading-overlay-spinner-color: var(--primary-color-0);
1039
- --sign-loading-overlay-spinner-border-color: rgba(78, 86, 234, 0.1);
1040
- --sign-loading-overlay-spinner-size: 30px;
1360
+ --sign-loading-overlay-bg-color: color-mix(
1361
+ in srgb,
1362
+ var(--background-primary) 80%,
1363
+ transparent
1364
+ );
1365
+ --sign-loading-overlay-text-color: var(--content-primary);
1366
+ --sign-loading-overlay-spinner-color: var(--content-brandHover);
1367
+ --sign-loading-overlay-spinner-border-color: var(--background-brandSubtle);
1368
+ --sign-loading-overlay-spinner-size: var(--unit-32);
1041
1369
  }
1042
1370
  ```
1043
1371
 
1044
- ### 📋 Full CSS Variable Reference
1372
+ ### Full CSS Variable Reference
1045
1373
 
1046
- #### Base Colors & General
1374
+ #### General
1047
1375
 
1048
- | Variable | Default | Description |
1049
- | :---------------------------- | :------------------ | :---------------------------------------------- |
1050
- | `--primary-color-0` | `#4e56ea` | Primary brand color. |
1051
- | `--primary-color-10` | `#7178ee` | Primary hover/accent color. |
1052
- | `--background-color-0` | `#f1f2fd` | Secondary background color. |
1053
- | `--background-color-10` | `#e3e4fc` | Main bar background color. |
1054
- | `--text-color-0` | `#333e4a` | Main text color. |
1055
- | `--white-color` | `#ffffff` | Pure white color. |
1056
- | `--grey-color` | `#b5b9be` | Disabled state color. |
1057
- | `--sign-font-family` | `Arial, sans-serif` | Global font family. |
1058
- | `--sign-common-border-radius` | `8px` | Shared radius token reused by bars and buttons. |
1376
+ | Variable | Default | Description |
1377
+ | :---------------------------- | :-------------------------------------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
1378
+ | `--sign-font-family` | `var(--fontFamily-primary), Arial, Helvetica, sans-serif` | Global font family. The default starts with the design system font `Inter`, which is **not bundled** — load it on the host page, otherwise the `Arial` fallback is used. |
1379
+ | `--sign-common-border-radius` | `var(--borderRadius-s)` | Shared radius token reused by bars and buttons. |
1059
1380
 
1060
1381
  #### Layout & Constraints
1061
1382
 
1062
- | Variable | Default | Description |
1063
- | :---------------------- | :---------- | :------------------------------------- |
1064
- | `--min-height` | `48px` | Minimum height for bars and buttons. |
1065
- | `--spacing-constraints` | `16px 24px` | Default padding/margin for containers. |
1383
+ | Variable | Default | Description |
1384
+ | :---------------------- | :------------------------------------ | :--------------------------------------------------------------------------- |
1385
+ | `--signpad-width` | `25rem` | Host element width. Override to resize, e.g. `100%` for a fluid container. |
1386
+ | `--signpad-height` | `31.25rem` | Host element height. Override to resize, e.g. `100vh` for a fluid container. |
1387
+ | `--min-height` | `var(--unit-48)` | Minimum height for bars and buttons. |
1388
+ | `--spacing-constraints` | `var(--spacing-12) var(--spacing-16)` | Default padding/margin for containers. |
1389
+
1390
+ > **Sizing:** By default the component renders at a fixed `400x500` size so it doesn't stretch to fill its parent on integration. To make it fluid, override `--signpad-width` / `--signpad-height` on the host element (e.g. `signosoft-signpad { --signpad-width: 100%; --signpad-height: 100vh; }`).
1066
1391
 
1067
1392
  #### Top Bar
1068
1393
 
1069
1394
  | Variable | Default | Description |
1070
1395
  | :------------------------------------------ | :--------------------------------- | :---------------------------------- |
1071
- | `--sign-top-bar-bg-base` | `var(--background-color-10)` | Background color of the top bar. |
1072
- | `--sign-top-bar-text-base` | `var(--primary-color-0)` | Text color in the top bar. |
1396
+ | `--sign-top-bar-bg-base` | `var(--background-brandSubtle)` | Background color of the top bar. |
1397
+ | `--sign-top-bar-text-base` | `var(--brand-650)` | Text color in the top bar. |
1073
1398
  | `--sign-top-bar-padding` | `var(--spacing-constraints)` | Inner padding of the top bar. |
1074
1399
  | `--sign-top-bar-min-height` | `var(--min-height)` | Minimum height of the top bar. |
1075
1400
  | `--sign-top-bar-border-top-left-radius` | `var(--sign-common-border-radius)` | Top-left radius of the top bar. |
@@ -1079,29 +1404,29 @@ signosoft-signpad {
1079
1404
 
1080
1405
  #### Canvas & Signature Line
1081
1406
 
1082
- | Variable | Default | Description |
1083
- | :------------------------------------------------- | :--------------------------------------------- | :--------------------------------------------------------------------------- |
1084
- | `--sign-canvas-bg-base` | `var(--background-color-0)` | Background color of the drawing area. |
1085
- | `--sign-canvas-wrapper-border-top-left-radius` | `0` | Top-left canvas radius, typically rounded when the top bar is hidden. |
1086
- | `--sign-canvas-wrapper-border-top-right-radius` | `0` | Top-right canvas radius, typically rounded when the top bar is hidden. |
1087
- | `--sign-canvas-wrapper-border-bottom-left-radius` | `0` | Bottom-left canvas radius, typically rounded when the bottom bar is hidden. |
1088
- | `--sign-canvas-wrapper-border-bottom-right-radius` | `0` | Bottom-right canvas radius, typically rounded when the bottom bar is hidden. |
1089
- | `--sign-line-height` | `22px` | Vertical offset/height for the guide line. |
1090
- | `--sign-line-margin-bottom` | `16px` | Bottom spacing used in canvas line layout. |
1091
- | `--sign-line-border-base` | `var(--primary-color-10)` | Color of the signature guide line. |
1092
- | `--sign-line-additional-text-color` | `var(--text-color-0)` | Color of the helper text below the line. |
1093
- | `--sign-line-margin` | `0px 24px var(--sign-line-margin-bottom) 24px` | Spacing around the guide line. |
1094
- | `--sign-canvas-line-text-font-size` | `12px` | Font size for guide line labels. |
1095
- | `--sign-canvas-height-offset` | `var(--sign-line-height)` | Canvas offset calculation for the guide line. |
1407
+ | Variable | Default | Description |
1408
+ | :------------------------------------------------- | :--------------------------------------------------------------------- | :--------------------------------------------------------------------------- |
1409
+ | `--sign-canvas-bg-base` | `var(--brandBeige-100)` | Background color of the drawing area. |
1410
+ | `--sign-canvas-wrapper-border-top-left-radius` | `0` | Top-left canvas radius, typically rounded when the top bar is hidden. |
1411
+ | `--sign-canvas-wrapper-border-top-right-radius` | `0` | Top-right canvas radius, typically rounded when the top bar is hidden. |
1412
+ | `--sign-canvas-wrapper-border-bottom-left-radius` | `0` | Bottom-left canvas radius, typically rounded when the bottom bar is hidden. |
1413
+ | `--sign-canvas-wrapper-border-bottom-right-radius` | `0` | Bottom-right canvas radius, typically rounded when the bottom bar is hidden. |
1414
+ | `--sign-line-height` | `var(--unit-24)` | Vertical offset/height for the guide line. |
1415
+ | `--sign-line-margin-bottom` | `var(--spacing-16)` | Bottom spacing used in canvas line layout. |
1416
+ | `--sign-line-border-base` | `var(--border-secondary)` | Color of the signature guide line. |
1417
+ | `--sign-line-additional-text-color` | `var(--content-tertiary)` | Color of the helper text below the line. |
1418
+ | `--sign-line-margin` | `0 var(--spacing-16) var(--sign-line-margin-bottom) var(--spacing-16)` | Spacing around the guide line. |
1419
+ | `--sign-canvas-line-text-font-size` | `var(--text-s-regular-fontSize)` | Font size for guide line labels. |
1420
+ | `--sign-canvas-height-offset` | `var(--sign-line-height)` | Canvas offset calculation for the guide line. |
1096
1421
 
1097
1422
  #### Bottom Bar
1098
1423
 
1099
1424
  | Variable | Default | Description |
1100
1425
  | :--------------------------------------------- | :--------------------------------- | :--------------------------------------- |
1101
- | `--sign-bottom-bar-bg-base` | `var(--background-color-10)` | Background color of the bottom bar. |
1426
+ | `--sign-bottom-bar-bg-base` | `var(--background-brandSubtle)` | Background color of the bottom bar. |
1102
1427
  | `--sign-bottom-bar-padding` | `var(--spacing-constraints)` | Inner padding of the bottom bar. |
1103
1428
  | `--sign-bottom-bar-min-height` | `var(--min-height)` | Minimum height of the bottom bar. |
1104
- | `--sign-bottom-bar-gap` | `12px` | Space between buttons in the bottom bar. |
1429
+ | `--sign-bottom-bar-gap` | `var(--spacing-8)` | Space between buttons in the bottom bar. |
1105
1430
  | `--sign-bottom-bar-border-top-left-radius` | `0` | Top-left radius of the bottom bar. |
1106
1431
  | `--sign-bottom-bar-border-top-right-radius` | `0` | Top-right radius of the bottom bar. |
1107
1432
  | `--sign-bottom-bar-border-bottom-left-radius` | `var(--sign-common-border-radius)` | Bottom-left radius of the bottom bar. |
@@ -1109,167 +1434,87 @@ signosoft-signpad {
1109
1434
 
1110
1435
  #### Buttons (General)
1111
1436
 
1112
- | Variable | Default | Description |
1113
- | :---------------------------- | :------------------ | :----------------------------- |
1114
- | `--sign-button-font-size` | `16px` | Font size for all buttons. |
1115
- | `--sign-button-padding` | `14px 16px` | Inner padding for all buttons. |
1116
- | `--sign-button-min-height` | `var(--min-height)` | Minimum height for buttons. |
1117
- | `--sign-button-border-radius` | `8px` | Border radius for all buttons. |
1118
- | `--sign-button-font-weight` | `500` | Font weight for all buttons. |
1437
+ | Variable | Default | Description |
1438
+ | :---------------------------- | :---------------------------------- | :----------------------------- |
1439
+ | `--sign-button-font-size` | `var(--text-m-semibold-fontSize)` | Font size for all buttons. |
1440
+ | `--sign-button-padding` | `var(--spacing-10)` | Inner padding for all buttons. |
1441
+ | `--sign-button-min-height` | `var(--unit-48)` | Minimum height for buttons. |
1442
+ | `--sign-button-border-radius` | `var(--borderRadius-pill)` | Border radius for all buttons. |
1443
+ | `--sign-button-font-weight` | `var(--text-m-semibold-fontWeight)` | Font weight for all buttons. |
1119
1444
 
1120
1445
  #### Primary Buttons (OK, Clear, Cancel)
1121
1446
 
1122
- | Variable | Description |
1123
- | :-------------------------------------- | :----------------------------------- |
1124
- | `--sign-button-primary-bg-base` | Background color of primary buttons. |
1125
- | `--sign-button-primary-bg-hover` | Hover background color. |
1126
- | `--sign-button-primary-bg-disabled` | Disabled background color. |
1127
- | `--sign-button-primary-text-base` | Text color for primary buttons. |
1128
- | `--sign-button-primary-text-hover` | Hover text color. |
1129
- | `--sign-button-primary-text-disabled` | Disabled text color. |
1130
- | `--sign-button-primary-border-base` | Border of primary buttons. |
1131
- | `--sign-button-primary-border-hover` | Hover border color. |
1132
- | `--sign-button-primary-border-disabled` | Disabled border color. |
1133
-
1134
- #### Link/Connect Buttons
1135
-
1136
- | Variable | Default | Description |
1137
- | :----------------------------------- | :---------------------------------- | :------------------------------------------- |
1138
- | `--sign-button-link-bg-base` | `transparent` | Background color for secondary/link buttons. |
1139
- | `--sign-button-link-bg-hover` | `var(--background-color-10)` | Hover background color. |
1140
- | `--sign-button-link-bg-disabled` | `transparent` | Disabled background color. |
1141
- | `--sign-button-link-text-base` | `var(--primary-color-0)` | Text color for secondary/link buttons. |
1142
- | `--sign-button-link-text-hover` | `var(--primary-color-10)` | Hover text color. |
1143
- | `--sign-button-link-text-disabled` | `var(--grey-color)` | Disabled text color. |
1144
- | `--sign-button-link-border-base` | `1px solid var(--primary-color-0)` | Border for secondary/link buttons. |
1145
- | `--sign-button-link-border-hover` | `1px solid var(--primary-color-10)` | Hover border color. |
1146
- | `--sign-button-link-border-disabled` | `1px solid var(--grey-color)` | Disabled border color. |
1447
+ | Variable | Default | Description |
1448
+ | :-------------------------------------- | :---------------------------------------- | :-------------------------------------------------- |
1449
+ | `--sign-button-primary-bg-base` | `var(--background-brand)` | Background color of primary buttons. |
1450
+ | `--sign-button-primary-bg-hover` | `var(--background-brandHover)` | Hover background color. |
1451
+ | `--sign-button-primary-bg-pressed` | `var(--background-brandPressed)` | Background while the button is pressed (`:active`). |
1452
+ | `--sign-button-primary-bg-disabled` | `var(--background-disabled)` | Disabled background color. |
1453
+ | `--sign-button-primary-text-base` | `var(--content-primaryInverse)` | Text color for primary buttons. |
1454
+ | `--sign-button-primary-text-hover` | `var(--content-primaryInverse)` | Hover text color. |
1455
+ | `--sign-button-primary-text-disabled` | `var(--content-disabled)` | Disabled text color. |
1456
+ | `--sign-button-primary-border-base` | `var(--borderWidth-xs) solid transparent` | Border of primary buttons. |
1457
+ | `--sign-button-primary-border-hover` | `var(--borderWidth-xs) solid transparent` | Hover border color. |
1458
+ | `--sign-button-primary-border-disabled` | `var(--borderWidth-xs) solid transparent` | Disabled border color. |
1147
1459
 
1148
1460
  #### Status & Overlays
1149
1461
 
1150
- | Variable | Default | Description |
1151
- | :-------------------------------------------- | :------------------------- | :--------------------------------- |
1152
- | `--sign-device-state-text-color-connected` | `green` | Text color when device is ready. |
1153
- | `--sign-device-state-text-color-disconnected` | `red` | Text color when disconnected. |
1154
- | `--sign-loading-overlay-bg-color` | `rgba(255, 255, 255, 0.8)` | Background of the loading spinner. |
1155
- | `--sign-loading-overlay-text-color` | `var(--text-color-0)` | Loading overlay text color. |
1156
- | `--sign-loading-overlay-spinner-color` | `var(--primary-color-0)` | Color of the animated spinner. |
1157
- | `--sign-loading-overlay-spinner-border-color` | `rgba(78, 86, 234, 0.1)` | Base border color of spinner ring. |
1158
- | `--sign-loading-overlay-spinner-size` | `30px` | Size of the spinner icon. |
1159
-
1160
- ## 💬 Feedback & Support
1161
-
1162
- 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:
1163
-
1164
- - 📧 **Support Email:** [esign@signosoft.com](mailto:esign@signosoft.com)
1165
- - 🌐 **Official Website:** [www.signosoft.com](https://www.signosoft.com)
1166
-
1167
- ## 🛠️ Contributing
1168
-
1169
- ### Commit naming convention
1170
-
1171
- - Every commit message must start with Jira task id and category:
1172
- - `MAIN-000/<category>: <summary>`
1173
- - For work in progress:
1174
- - `MAIN-000/<category>: WIP - <summary>`
1175
-
1176
- Allowed categories:
1177
-
1178
- - `feat`: adding a new feature
1179
- - `fix`: fixing a bug
1180
- - `refactor`: performance/readability/maintainability change
1181
- - `chore`: docs, formatting, tests, cleanup, tooling
1182
- - `merge`: branch merge into `main`
1183
-
1184
- Use infinitive verb form in commit summaries:
1185
-
1186
- - <span style="color: #16a34a;"><strong>RIGHT:</strong></span> `add`, `fix`, `remove`, `update`, `refactor`
1187
- - <span style="color: #dc2626;"><strong>WRONG:</strong></span> Continuous tense: `adding`, `removing` Past tense: `added`, `fixed`
1188
-
1189
- Examples:
1190
-
1191
- ```bash
1192
- git commit -m "MAIN-000/feat: add new button component; add button to templates"
1193
- ```
1194
-
1195
- ```bash
1196
- git commit -m "MAIN-000/fix: add stop directive to button component to prevent propagation"
1197
- ```
1198
-
1199
- ```bash
1200
- git commit -m "MAIN-000/refactor: rewrite button component in TypeScript"
1201
- ```
1202
-
1203
- ```bash
1204
- git commit -m "MAIN-000/chore: write button documentation"
1205
- ```
1206
-
1207
- ```bash
1208
- git commit -m "MAIN-000/merge: new button"
1209
- ```
1210
-
1211
- ```bash
1212
- git commit -m "MAIN-000/feat: WIP - basic signing functionality"
1213
- ```
1214
-
1215
- ### Versioning and release commits
1216
-
1217
- Follow Semantic Versioning:
1462
+ | Variable | Default | Description |
1463
+ | :-------------------------------------------- | :--------------------------------------------------------------- | :--------------------------------- |
1464
+ | `--sign-device-state-text-color-connected` | `var(--brand-650)` | Text color when device is ready. |
1465
+ | `--sign-loading-overlay-bg-color` | `color-mix(in srgb, var(--background-primary) 80%, transparent)` | Background of the loading spinner. |
1466
+ | `--sign-loading-overlay-text-color` | `var(--content-primary)` | Loading overlay text color. |
1467
+ | `--sign-loading-overlay-spinner-color` | `var(--content-brandHover)` | Color of the animated spinner. |
1468
+ | `--sign-loading-overlay-spinner-border-color` | `var(--background-brandSubtle)` | Base border color of spinner ring. |
1469
+ | `--sign-loading-overlay-spinner-size` | `var(--unit-32)` | Size of the spinner icon. |
1218
1470
 
1219
- - `MAJOR` (`x.0.0`): breaking changes
1220
- - `MINOR` (`x.y.0`): backward-compatible feature set
1221
- - `PATCH` (`x.y.z`): backward-compatible bug fixes
1471
+ > **Accessibility (WCAG 2.1 AA):** if you override `--sign-loading-overlay-bg-color` or `--sign-loading-overlay-text-color`, keep the text/background contrast ratio at **4.5:1 or higher**. The overlay background is semi-transparent, so measure against the worst case: the overlay color blended over the canvas, including dark pen strokes. Raise the background alpha if the text is hard to read over strokes.
1222
1472
 
1223
- Commit naming for version bumps:
1473
+ <a id="accessibility"></a>
1224
1474
 
1225
- - Version-only change: use `chore`
1226
- - Version bump with actual code/features in same commit: use `feat`/`fix`/`refactor` based on dominant change
1227
- - For release commits, include both versions in message: `release from <old> to <new>`
1475
+ ## ♿ Accessibility
1228
1476
 
1229
- Example (version bump with feature changes):
1477
+ The component targets WCAG 2.1 AA for everything it renders. What is in place:
1230
1478
 
1231
- ```bash
1232
- git commit -m "MAIN-000/feat: release from 0.2.4 to 0.3.0 - add signature method"
1233
- ```
1479
+ - **Names and state:** all controls are native `<button>`s with visible, localized text; disabled buttons use the native `disabled` state. The drawing canvas has `role="img"` and a localized name (`CANVAS_ARIA_LABEL`).
1480
+ - **Announcements:** the component container sets `aria-busy` while connecting, disconnecting or processing OK/Clear/Cancel. The error/busy overlay message is announced (`role="alert"` for errors, `role="status"` while busy). Device status changes (e.g. connected / mouse) are announced through a polite live region.
1481
+ - **Keyboard and focus:** all buttons are reachable with Tab and show a `:focus-visible` outline (`--border-focus`). When an error appears and focus is already inside the component, focus moves to the "Restart session" button.
1482
+ - **Language:** the active language is set as `lang` on the component content, so screen readers pronounce `cs`/`pt` text correctly.
1483
+ - **Motion:** the spinner slows down and button transitions are disabled under `prefers-reduced-motion: reduce`.
1484
+ - **High contrast:** under `forced-colors: active` buttons, focus ring and overlay use system colors. The signing surface keeps its own light background (`forced-color-adjust: none`), because the ink is drawn on a canvas in `canvasAndDrawingOptions.color` (black by default) and would be invisible on a forced dark background.
1234
1485
 
1235
- Example (version-only commit):
1486
+ **Known limitations (plan an alternative in your app):**
1236
1487
 
1237
- ```bash
1238
- git commit -m "MAIN-000/chore: release from 0.2.4 to 0.3.0"
1239
- ```
1488
+ - **Signing itself is pointer-only** (pen, mouse or touch). The canvas cannot be operated with a keyboard or screen reader (WCAG 2.1.1). If you must support people who cannot draw, offer another way to sign or confirm in your app (e.g. a typed-name signature or an assisted flow) and treat the signpad as one option.
1489
+ - **Default color contrast:** the defaults are chosen for WCAG 2.1 AA: white on the primary button is 5.1:1 (`--background-brand` = `brand-650`; hover 6.5:1, pressed 8.3:1), top bar and device status text are at least 4.5:1, the spinner and focus ring are above 3:1. `--background-brand` was changed from `brand-500` (white on it is 2.7:1) to `brand-650` for this reason; `--background-brandHover` and `--background-brandPressed` are `brand-700` and `brand-750`. Disabled controls are exempt. If you override colors, re-check the ratios.
1490
+ - **Zoom and text size:** spacing, sizes and radii come from design tokens converted to `rem`, so they scale with the browser font size and page zoom. Border widths stay in `px`. `--signpad-width` / `--signpad-height` are also `rem`; reflow at 400% zoom depends on your page layout around the component.
1240
1491
 
1241
- How version increment works:
1492
+ **If you override styles:** keep text/background contrast at 4.5:1 (3:1 for borders and UI components), do not remove the `:focus-visible` outline, and keep controls at least 24×24 px.
1242
1493
 
1243
- - `PATCH` (`x.y.Z`): increase patch only (`1.4.2` -> `1.4.3`)
1244
- - `MINOR` (`x.Y.0`): increase minor, reset patch (`1.4.9` -> `1.5.0`)
1245
- - `MAJOR` (`X.0.0`): increase major, reset minor and patch (`1.9.7` -> `2.0.0`)
1494
+ <a id="feedback--support"></a>
1246
1495
 
1247
- Examples:
1496
+ ## 💬 Feedback & Support
1248
1497
 
1249
- ```bash
1250
- # patch
1251
- git commit -m "MAIN-000/fix: release from 1.0.0 to 1.0.1 - fix onPen throttle edge case"
1498
+ Found a bug, missing a feature, or need help with an integration? Write to us:
1252
1499
 
1253
- # minor
1254
- git commit -m "MAIN-000/feat: release from 1.1.1 to 1.2.0 - add lease refresh helper"
1500
+ - 📧 **Support Email:** [esign@signosoft.com](mailto:esign@signosoft.com)
1501
+ - 🌐 **Official Website:** [www.signosoft.com](https://www.signosoft.com)
1255
1502
 
1256
- # major
1257
- git commit -m "MAIN-000/refactor: release from 1.1.1 to 2.0.0 - remove legacy API"
1258
- ```
1503
+ <a id="license"></a>
1259
1504
 
1260
1505
  ## 📄 License
1261
1506
 
1262
- **Proprietary Commercial License**
1507
+ **Source-available, proprietary commercial license**
1263
1508
 
1264
1509
  Copyright © 2026, Signosoft. All rights reserved.
1265
1510
 
1266
- This software is proprietary to Signosoft and is protected by copyright laws. It is **not open source**.
1511
+ This software is proprietary to Signosoft and is protected by copyright laws. The source is available to inspect, but it is **not open source** (not an OSI-approved license): use requires a Signosoft account and paid tokens.
1267
1512
 
1268
- ### 🔐 Commercial Usage
1513
+ ### Commercial Usage
1269
1514
 
1270
1515
  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).
1271
1516
 
1272
- ### 📜 Key Terms
1517
+ ### Key Terms
1273
1518
 
1274
1519
  - **Unauthorized Use:** Any use without a current, fully paid license is expressly prohibited and constitutes a violation of our intellectual property rights.
1275
1520
  - **Legal Action:** Signosoft reserves the right to pursue legal action to enforce its rights and seek damages for unauthorized use.