@signosoft/signpad-js 0.6.0 → 0.7.1

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,101 +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/public/driver-leases/create",
63
- {
64
- method: "POST",
65
- headers: { "Content-Type": "application/json" },
66
- body: JSON.stringify({
67
- token: "YOUR-LICENSE-TOKEN",
68
- validityHours: 24,
69
- }),
70
- },
71
- );
72
-
73
- if (!response.ok) {
74
- const errorText = await response.text();
75
- throw new Error(
76
- `License server error: ${response.statusText} - ${errorText}`,
77
- );
78
- }
79
-
80
- return await response.json();
81
- } catch (error) {
82
- console.error("Signosoft Signpad: License initialization failed", error);
83
- throw error;
84
- }
85
- }
86
-
87
- async function applyLeaseToConfig(currentConfig) {
88
- const lease = await fetchLease();
89
- return {
90
- ...currentConfig,
91
- lease,
92
- };
93
- }
94
- ```
95
-
96
- <br/>
97
-
98
- ## 🖊️ Device Compatibility
53
+ ## 🔌 Device Compatibility
99
54
 
100
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.
101
56
 
@@ -108,6 +63,8 @@ The component communicates with signature tablets via the **WebHID API**. Device
108
63
 
109
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.
110
65
 
66
+ <a id="installation"></a>
67
+
111
68
  ## 📦 Installation
112
69
 
113
70
  Install the library via npm:
@@ -126,131 +83,119 @@ yarn add @signosoft/signpad-js
126
83
  pnpm add @signosoft/signpad-js
127
84
  ```
128
85
 
129
- ### 🌍 Environment Requirements
86
+ <a id="environment-requirements"></a>
87
+
88
+ ## 🌍 Environment Requirements
130
89
 
131
90
  To ensure proper communication via the **WebHID API**:
132
91
 
133
- - **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.
134
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.
135
96
  - **TypeScript:** Built-in support; no extra `@types` needed.
136
- <br/>
137
97
 
138
- ## 🚀 Quick Start
98
+ <a id="lease-setup"></a>
139
99
 
140
- First, install the core package:
100
+ ## 🔐 Lease Setup
141
101
 
142
- ```bash
143
- npm install @signosoft/signpad-js
144
- ```
102
+ To use the Signosoft Signpad component with physical signature hardware, a valid `lease` is **required**.
145
103
 
146
- ### 🧰 Framework Integration Guide
104
+ ### CRITICAL: Mandatory Initialization
147
105
 
148
- <details>
149
- <summary>
150
- <img src="https://cdn.jsdelivr.net/gh/devicons/devicon/icons/angularjs/angularjs-original.svg" alt="Angular" width="15" height="15">
151
- <b>Angular</b> <font color="green"><b>(Live)</b></font>
152
- </summary>
153
- <br>
106
+ Without a valid lease, the component can still render, but driver initialization and hardware communication features will be <b>disabled</b>.
154
107
 
155
- # <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
156
109
 
157
- 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
158
115
 
159
- ## 1. Installation
116
+ > Security note: In production, call the lease API from your backend and return only the lease to frontend.
160
117
 
161
- Install the core package using npm:
118
+ ### 2. Pricing
162
119
 
163
- ```bash
164
- npm install @signosoft/signpad-js
165
- ```
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)).
166
121
 
167
- ## 2. Create the Bridge Directive
122
+ <a id="quick-start"></a>
168
123
 
169
- 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
170
125
 
171
- 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).
172
127
 
173
- ```typescript
174
- import {
175
- Directive,
176
- ElementRef,
177
- Input,
178
- OnChanges,
179
- SimpleChanges,
180
- } from "@angular/core";
181
- 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
+ > ```
182
133
 
183
- @Directive({
184
- selector: "signosoft-signpad",
185
- standalone: true,
186
- })
187
- export class SignosoftSignpadDirective implements OnChanges {
188
- @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)).
189
135
 
190
- constructor(private host: ElementRef<SignosoftSignpad>) {}
136
+ ### Framework Integration Guide
191
137
 
192
- // Access the native Web Component instance
193
- get signpadRef(): SignosoftSignpad {
194
- return this.host.nativeElement;
195
- }
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>
196
144
 
197
- ngOnChanges(changes: SimpleChanges) {
198
- const el = this.host.nativeElement as any;
199
- for (const key of Object.keys(changes)) {
200
- el[key] = (this as any)[key];
201
- }
202
- }
203
- }
204
- ```
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`).
205
148
 
206
- ## 3. Implementation in Component
149
+ ##### 1. Implementation in Component
207
150
 
208
- 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.
209
152
 
210
153
  ```typescript
211
154
  import {
212
155
  Component,
213
- ViewChild,
214
156
  CUSTOM_ELEMENTS_SCHEMA,
215
- AfterViewInit,
157
+ ElementRef,
158
+ afterNextRender,
216
159
  signal,
160
+ viewChild,
217
161
  } from "@angular/core";
218
- import "@signosoft/signpad-js"; // Import the Web Component registration
219
- import {
220
- type SignosoftSignpad,
221
- type SignpadConfig,
222
- } from "@signosoft/signpad-js";
223
- 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";
224
164
 
225
165
  @Component({
226
166
  selector: "app-root",
227
- standalone: true,
228
- imports: [SignosoftSignpadDirective],
229
- templateUrl: "./app.component.html",
167
+ templateUrl: "./app.html",
230
168
  schemas: [CUSTOM_ELEMENTS_SCHEMA], // Mandatory for custom HTML tags
231
169
  })
232
- export class AppComponent implements AfterViewInit {
233
- @ViewChild(SignosoftSignpadDirective)
234
- 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");
235
173
 
236
- public config = signal<SignpadConfig>({
174
+ readonly config = signal<SignpadConfig>({
237
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
+ },
238
183
  });
239
184
 
240
- async ngAfterViewInit() {
241
- await this.applyLeaseToConfig();
185
+ constructor() {
186
+ // Runs in the browser after the first render
187
+ afterNextRender(() => this.applyLeaseToConfig());
242
188
  }
243
189
 
244
- public async fetchLease(): Promise<any> {
190
+ async fetchLease(): Promise<any> {
245
191
  try {
246
192
  const response = await fetch(
247
- "https://test.signosoft.com/api/v1/public/driver-leases/create",
193
+ "https://api-test.signosoft.com/v1/public/driver-leases",
248
194
  {
249
195
  method: "POST",
250
196
  headers: { "Content-Type": "application/json" },
251
197
  body: JSON.stringify({
252
- token: "YOUR-LICENSE-TOKEN",
253
- validityHours: 24,
198
+ key: "YOUR-LICENSE-TOKEN",
254
199
  }),
255
200
  },
256
201
  );
@@ -269,7 +214,7 @@ export class AppComponent implements AfterViewInit {
269
214
  }
270
215
  }
271
216
 
272
- public async applyLeaseToConfig() {
217
+ async applyLeaseToConfig() {
273
218
  const lease = await this.fetchLease();
274
219
  this.config.update((currentConfig) => ({
275
220
  ...currentConfig,
@@ -277,23 +222,78 @@ export class AppComponent implements AfterViewInit {
277
222
  }));
278
223
  }
279
224
 
280
- // Helper to access methods easily (ok, clear, cancel, etc.)
281
- public get signpad(): SignosoftSignpad | undefined {
282
- 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" });
283
228
  }
284
229
  }
285
230
  ```
286
231
 
287
- ## 4. Component Template
232
+ ##### 2. Component Template
288
233
 
289
- 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.
290
235
 
291
236
  ```html
292
- <div>
293
- <div>
294
- <signosoft-signpad [config]="config()"></signosoft-signpad>
295
- </div>
296
- </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>
297
297
  ```
298
298
 
299
299
  </details>
@@ -305,19 +305,23 @@ Use the custom tag in your HTML and bind the configuration object.
305
305
  </summary>
306
306
  <br>
307
307
 
308
- # <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
309
309
 
310
310
  This guide describes how to integrate the `@signosoft/signpad-js` web component into a React application (Functional Components with Hooks).
311
311
 
312
- ## 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);
313
316
 
314
- Install the core package using npm:
317
+ useEffect(() => {
318
+ if (signpadRef.current) signpadRef.current.config = config;
319
+ }, [config]);
315
320
 
316
- ```bash
317
- npm install @signosoft/signpad-js
321
+ return <signosoft-signpad ref={signpadRef} />;
318
322
  ```
319
323
 
320
- ## 2. Implementation
324
+ ##### 1. Implementation
321
325
 
322
326
  In React, we use `useRef` for methods and `useEffect` to fetch a lease before updating config.
323
327
 
@@ -329,13 +333,12 @@ import type { SignpadConfig } from "@signosoft/signpad-js";
329
333
  async function fetchLease() {
330
334
  try {
331
335
  const response = await fetch(
332
- "https://test.signosoft.com/api/v1/public/driver-leases/create",
336
+ "https://api-test.signosoft.com/v1/public/driver-leases",
333
337
  {
334
338
  method: "POST",
335
339
  headers: { "Content-Type": "application/json" },
336
340
  body: JSON.stringify({
337
- token: "YOUR-LICENSE-TOKEN",
338
- validityHours: 24,
341
+ key: "YOUR-LICENSE-TOKEN",
339
342
  }),
340
343
  },
341
344
  );
@@ -357,7 +360,16 @@ async function fetchLease() {
357
360
  function App() {
358
361
  // Use ref to access component methods (ok, clear, connect, etc. by signpadRef.current)
359
362
  const signpadRef = useRef<any>(null);
360
- 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
+ });
361
373
 
362
374
  useEffect(() => {
363
375
  fetchLease().then((lease) => setConfig((prev) => ({ ...prev, lease })));
@@ -383,19 +395,11 @@ export default App;
383
395
  </summary>
384
396
  <br>
385
397
 
386
- # <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
387
399
 
388
400
  This guide describes how to integrate the `@signosoft/signpad-js` web component into a Vue 3 application using the Composition API (`<script setup>`).
389
401
 
390
- ## 1. Installation
391
-
392
- Install the core package using npm:
393
-
394
- ```bash
395
- npm install @signosoft/signpad-js
396
- ```
397
-
398
- ## 2. Configure Vue to recognize Custom Elements
402
+ ##### 1. Configure Vue to recognize Custom Elements
399
403
 
400
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.
401
405
 
@@ -419,7 +423,7 @@ export default defineConfig({
419
423
  });
420
424
  ```
421
425
 
422
- ## 3. Implementation
426
+ ##### 2. Implementation
423
427
 
424
428
  In Vue 3, we use `ref` for both the element and config, then fetch the lease and assign it to config.
425
429
 
@@ -432,16 +436,24 @@ import type { SignpadConfig, SignosoftSignpad, IPenData } from "@signosoft/signp
432
436
  // Use ref to access component methods (ok, clear, connect, etc. by signpadRef.value)
433
437
  const signpadRef = ref<SignosoftSignpad | null>(null);
434
438
 
435
- 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
+ });
436
449
 
437
450
  const fetchLease = async () => {
438
451
  try {
439
- const response = await fetch("https://test.signosoft.com/api/v1/public/driver-leases/create", {
452
+ const response = await fetch("https://api-test.signosoft.com/v1/public/driver-leases", {
440
453
  method: "POST",
441
454
  headers: { "Content-Type": "application/json" },
442
455
  body: JSON.stringify({
443
- token: "YOUR-LICENSE-TOKEN",
444
- validityHours: 24,
456
+ key: "YOUR-LICENSE-TOKEN",
445
457
  }),
446
458
  });
447
459
 
@@ -486,21 +498,15 @@ fetchLease()
486
498
  </summary>
487
499
  <br>
488
500
 
489
- # 🍦 Vanilla JS Integration Guide
501
+ #### 🍦 Vanilla JS Integration Guide
490
502
 
491
503
  This guide describes how to integrate the `@signosoft/signpad-js` web component into a plain JavaScript project without any frameworks.
492
504
 
493
- ## 1. Installation
505
+ ##### 1. Load the component
494
506
 
495
- Install the package via npm:
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).
496
508
 
497
- ```bash
498
- npm install @signosoft/signpad-js
499
- ```
500
-
501
- 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).
502
-
503
- ## 2. JavaScript Implementation
509
+ ##### 2. JavaScript Implementation
504
510
 
505
511
  In Vanilla JS, you interact with the component by selecting it from the DOM and assigning the configuration directly to its `config` property.
506
512
 
@@ -512,23 +518,29 @@ import "@signosoft/signpad-js";
512
518
  * Helps VS Code provide autocomplete for methods and properties.
513
519
  * @type {import('@signosoft/signpad-js').SignosoftSignpad}
514
520
  */
515
- const pad = document.querySelector("signosoft-signpad");
521
+ const signosoftPad = document.querySelector("signosoft-signpad");
516
522
 
517
523
  // 1. Initial Configuration
518
- pad.config = {
524
+ signosoftPad.config = {
519
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
+ },
520
533
  };
521
534
 
522
535
  const fetchLease = async () => {
523
536
  try {
524
537
  const response = await fetch(
525
- "https://test.signosoft.com/api/v1/public/driver-leases/create",
538
+ "https://api-test.signosoft.com/v1/public/driver-leases",
526
539
  {
527
540
  method: "POST",
528
541
  headers: { "Content-Type": "application/json" },
529
542
  body: JSON.stringify({
530
- token: "YOUR-LICENSE-TOKEN",
531
- validityHours: 24,
543
+ key: "YOUR-LICENSE-TOKEN",
532
544
  }),
533
545
  },
534
546
  );
@@ -548,13 +560,13 @@ const fetchLease = async () => {
548
560
  };
549
561
 
550
562
  fetchLease().then((lease) => {
551
- pad.config = { ...pad.config, lease };
563
+ signosoftPad.config = { ...signosoftPad.config, lease };
552
564
  });
553
565
  ```
554
566
 
555
- ## 3. HTML Structure
567
+ ##### 3. HTML Structure
556
568
 
557
- 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.
558
570
 
559
571
  ```html
560
572
  <!-- index.html -->
@@ -574,15 +586,17 @@ Add the custom element tag to your HTML and create the necessary control buttons
574
586
  </html>
575
587
  ```
576
588
 
577
- ## 4. Key Concepts
589
+ ##### 4. Key Concepts
578
590
 
579
591
  - **Direct Property Assignment:** Unlike some frameworks that use attributes, in Plain JS you should assign the configuration directly: `element.config = { ... }`.
580
592
  - **Module System:** Ensure your `<script>` tag has `type="module"` to use the `import` statement.
581
593
  - **Methods:** All methods like `.connect()`, `.ok()`, and `.clear()` are available directly on the DOM element object.
582
- - **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.
583
595
 
584
596
  </details>
585
597
 
598
+ <a id="properties"></a>
599
+
586
600
  ## 📋 Properties
587
601
 
588
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.
@@ -591,30 +605,36 @@ The component is primarily configured through a single `config` property. This o
591
605
  | :------- | :-------------- | :------ | :----------------------------------------------- |
592
606
  | `config` | `SignpadConfig` | `{}` | The main configuration object for the component. |
593
607
 
594
- ### 🔑 Core Options
608
+ ### What goes in `config`
595
609
 
596
- | Option | Type | Required | Description |
597
- | :---------- | :------- | :------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
598
- | **`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.
599
611
 
600
- ### 👁️ 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
+ ```
601
623
 
602
- 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`
603
632
 
604
- | Field | Type | Default | Description |
605
- | :--------------------------- | :-------- | :------ | :---------------------------------------------------------------- |
606
- | `topBarVisible` | `boolean` | `true` | Shows/hides the entire top navigation bar. |
607
- | `topBarClearButtonVisible` | `boolean` | `false` | Shows a "Clear" button in the top-left corner. |
608
- | `topBarConnectButtonVisible` | `boolean` | `true` | Shows the connect/disconnect button area in the top-right corner. |
609
- | `bottomBarVisible` | `boolean` | `true` | Shows/hides the entire bottom action bar. |
610
- | `okButtonVisible` | `boolean` | `true` | Shows the **OK** button in the bottom bar. |
611
- | `clearButtonVisible` | `boolean` | `true` | Shows the **Clear** button in the bottom bar. |
612
- | `cancelButtonVisible` | `boolean` | `true` | Shows the **Cancel** button in the bottom bar. |
613
- | `canvasLineVisible` | `boolean` | `true` | Shows the horizontal signature guide line on the canvas. |
614
- | `deviceStatusTextVisible` | `boolean` | `true` | Shows the name of the connected device (e.g., "Wacom STU-540"). |
615
- | `additionalTextVisible` | `boolean` | `true` | Shows helper status messages (e.g., "Ready to sign"). |
616
-
617
- ### 🖋️ canvasAndDrawingOptions
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)). |
636
+
637
+ ### `canvasAndDrawingOptions`
618
638
 
619
639
  Adjusts the visual appearance of the digital ink.
620
640
 
@@ -626,112 +646,132 @@ Adjusts the visual appearance of the digital ink.
626
646
 
627
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.
628
648
 
629
- ### 🌐 languageOptions
649
+ ### `uiVisibilityOptions`
630
650
 
631
- Handles component localization. It can load external files or use inline definitions.
651
+ Toggles the visibility of specific User Interface elements.
632
652
 
633
- | Field | Type | Description |
634
- | :------------- | :------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------- |
635
- | `lang` | `string` | The active language code. Built-in translations are bundled for `'en'` and `'cs'`; other languages (including `'pt'`) require `langPath` or `translations`. |
636
- | `langPath` | `string` | URL path to fetch translation files. Expects files in `[path]/[lang].json` format. |
637
- | `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`
638
668
 
639
- ## 🌐 Localization & Translation Keys
669
+ Handles component localization. It can load external files or use inline definitions.
640
670
 
641
- Localization is driven from `languageOptions`, but you can also provide overrides at runtime:
642
-
643
- 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.
644
- 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).
645
- 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.
646
- 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.
647
-
648
- 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:
649
-
650
- | Key | Default (English) |
651
- | :--------------------------- | :-------------------------------------------------- |
652
- | `OK` | OK |
653
- | `CLEAR` | Clear |
654
- | `CANCEL` | Cancel |
655
- | `CLEAR_SIGNATURE` | Clear signature |
656
- | `DISCONNECT` | Disconnect |
657
- | `CONNECT_SIGNPAD` | Connect Signpad |
658
- | `MOUSE` | Mouse |
659
- | `CONNECTED_UNKNOWN_DEVICE` | Connected (Unknown Device) |
660
- | `AUTO_CONNECTING` | Auto-connecting to device... |
661
- | `CONNECTING_TO_DEVICE` | Connecting to your device... |
662
- | `REVERTING_TO_DEFAULT` | Reverting to default input... |
663
- | `DISCONNECTING_DEVICE` | Disconnecting device... |
664
- | `COMPONENT_INITIALIZED` | Component initialized. |
665
- | `TEXT_START_SIGNING` | Connect your device. |
666
- | `TEXT_SIGN_WITH_PHYSICAL` | Please sign using the device pen. |
667
- | `TEXT_SIGN_WITH_MOUSE` | Please sign using your mouse. |
668
- | `TEXT_SIGN_GENERIC` | Sign in the area above using mouse or pen. |
669
- | `TEXT_SIGNING_WITH_DEVICE` | Signing with {device} |
670
- | `NO_DEVICE_FOUND` | No device found. |
671
- | `CONNECTION_ERROR` | Connection error. |
672
- | `ERROR_DISCONNECTION` | Error during disconnection. |
673
- | `ERROR_INSTANTIATING_DRIVER` | Error instantiating driver. |
674
- | `INSUFFICIENT_TOKENS` | Not enough tokens to sign. Reconnecting… |
675
- | `INVALID_LICENSE_KEY` | Invalid license key. |
676
- | `READY_TO_CONNECT` | Ready to connect. |
677
- | `SIGNPAD_DETECTED` | Signpad detected. Click 'Connect Signpad' to begin. |
678
- | `READY_TO_SIGN_MOUSE` | Ready to sign. Use your mouse. |
679
- | `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. |
680
676
 
681
- If you add any new keys, mirror them in every language file you provide to guarantee they fall back gracefully to English.
677
+ ### `theme`
682
678
 
683
- ### ⚙️ Logic & Events
679
+ Color theme of the component.
684
680
 
685
- 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. |
686
684
 
687
- #### 🛠️ Action Handlers (`actionHandlers`)
685
+ ### `actionHandlers`
688
686
 
689
687
  These allow you to **inject custom logic** directly into the internal button flows. Your code runs alongside the component's internal logic.
690
688
 
691
- | Handler | Arguments | Description |
692
- | :------------- | :---------------------------------- | :---------------------------------------------------------------------- |
693
- | `handleOk` | `data?: ISignatureConfirmationData` | Extends the internal OK button logic. Receives captured signature data. |
694
- | `handleClear` | — | Extends the internal Clear button logic. |
695
- | `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. |
694
+
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
+ ```
696
716
 
697
- #### 🔔 Event Callbacks (`eventCallbacks`)
717
+ ### `eventCallbacks`
698
718
 
699
719
  Standard event listeners triggered _after_ specific actions. Useful for observing the component from your application state.
700
720
 
701
- | Callback | Payload | Description |
702
- | :------------- | :----------------------------------------------- | :------------------------------------------------------------------------------------------------- |
703
- | `onPen` | `event: CustomEvent<IPenData>` | Dispatched when a device or mouse fallback are in contact with component or signature pad display. |
704
- | `onConnecting` | `event: CustomEvent<void>` | Dispatched when a device is in loading state. |
705
- | `onConnect` | `event: CustomEvent<{ deviceInfo: any }>` | Dispatched when a device or mouse fallback connects. |
706
- | `onDisconnect` | `event: CustomEvent<void>` | Dispatched when the hardware connection is closed. |
707
- | `onOk` | `event: CustomEvent<ISignatureConfirmationData>` | Dispatched when signature is confirmed. |
708
- | `onClear` | `event: CustomEvent<void>` | Dispatched when the canvas has been cleared. |
709
- | `onCancel` | `event: CustomEvent<void>` | Dispatched when the user aborts the session. |
710
- | `onError` | `event: CustomEvent<ISignpadErrorDetail>` | Dispatched on critical failures (lease, driver, connection, billing, 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.). |
711
731
 
712
732
  > `actionHandlers` and `eventCallbacks` are separate hooks and can both be configured.
713
733
 
714
- #### 🚨 `onError` payload (`ISignpadErrorDetail`)
734
+ **Listening with the config.** Pass the callbacks in `eventCallbacks`; each receives the `CustomEvent`:
715
735
 
716
736
  ```typescript
717
- {
718
- category: SignpadErrorCategory; // BILLING_DENIED | NETWORK | OTHER
719
- message: string; // error.message, for convenience
720
- error: Error; // the original error
721
- }
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
+ };
722
746
  ```
723
747
 
724
- `category` also drives automatic recovery:
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:
725
749
 
726
- - **`BILLING_DENIED`** — the lease has run out of signing credits/tokens. Signing automatically restarts a few seconds after the error is shown.
727
- - **`OTHER`** — any other recoverable failure (e.g. a driver hiccup, a cancelled device picker). Signing automatically restarts the same way as `BILLING_DENIED`.
728
- - **`NETWORK`** — a connectivity problem (lease fetch failed, device physically disconnected, origin validation failed). The error stays shown; signing does **not** auto-restart, since retrying on a timer can't fix a network or configuration problem — the user (or your app) must retry manually, e.g. via `connect()`.
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`.
729
760
 
730
- > **Migration note (breaking payload shape change):** `event.detail` used to be the raw `Error`. It is now `ISignpadErrorDetail`; use `event.detail.error` for the original `Error` instance. `event.detail.message` still works unchanged.
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
+ ```
731
771
 
732
772
  ---
733
773
 
734
- ### 💡 Example Implementation
774
+ ### Full example
735
775
 
736
776
  ```typescript
737
777
  this.config = {
@@ -752,6 +792,131 @@ this.config = {
752
792
  };
753
793
  ```
754
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
+
755
920
  ## 🧩 Methods
756
921
 
757
922
  The following methods are available on the component instance to control the signing process programmatically.
@@ -766,190 +931,322 @@ The following methods are available on the component instance to control the sig
766
931
  | `clear()` | `Promise<void>` | Wipes the signature from the UI and hardware without ending the session. |
767
932
  | `cancel()` | `Promise<void>` | Aborts the current session and resets the component state. |
768
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
+
769
951
  ---
770
952
 
771
- #### 📜 `connect()`
953
+ ### `connect()`
954
+
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.
772
956
 
773
- 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.
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.
774
962
 
775
- 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.
963
+ **Returns:** `Promise<void>`, resolved once the connection attempt (or fallback) is complete.
776
964
 
777
- **Returns:**
965
+ **Tip:** always call it from a click handler so the user can pick their device from the browser list.
778
966
 
779
- - A `Promise<void>` that resolves once the connection attempt (or fallback) is complete.
967
+ ---
780
968
 
781
- #### 📜 `disconnect()`
969
+ ### `disconnect()`
782
970
 
783
971
  Safely terminates the communication channel with the signature tablet.
784
972
 
785
- **What happens during disconnect:**
973
+ ```js
974
+ await signosoftPad.disconnect();
975
+ ```
976
+
977
+ What happens:
786
978
 
787
979
  1. The hardware driver is stopped and released.
788
980
  2. The internal cache of device information is cleared.
789
- 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.
790
982
  4. Any active mouse listeners are removed.
791
983
  5. A `SIGN_DISCONNECT` event is dispatched.
792
984
 
793
- #### 💡 Best Practices
985
+ **Returns:** `Promise<void>`
794
986
 
795
- - **Initial Connection:** Always call `connect()` from a click handler so the user can select their device from the browser list.
796
- - **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.
797
988
 
798
- #### 📜 `startSigning(options?: IDrawingOptions)`
989
+ ---
799
990
 
800
- 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)`
801
992
 
802
- ```typescript
803
- // Example: Start a session with custom ink
804
- 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({
805
998
  color: "#0000FF",
806
999
  maxWidth: 4,
807
1000
  minWidth: 2,
808
1001
  });
809
1002
  ```
810
1003
 
811
- #### 📜 `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.
812
1005
 
813
- This is the standard way to confirm a signature. It:
1006
+ **Returns:** `Promise<void>`
814
1007
 
815
- 1. Stops the signing process.
816
- 2. Clears the physical device's screen (if connected).
817
- 3. Dispatches the `SIGN_OK` event.
818
- 4. Returns the final signature payload (coordinates, pressure, metadata).
1008
+ ---
1009
+
1010
+ ### `stopSigning()`
819
1011
 
820
- **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).
821
1013
 
822
1014
  ```js
823
- [
824
- points, // index 0: Data about each point
825
- imageBase64, // index 1: Image in base64
826
- metadata, // index 2: Information about tablet and canvas
827
- ];
1015
+ const [penDataBatch, imageBase64, metadata] = await signosoftPad.stopSigning();
828
1016
  ```
829
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
+
830
1030
  ```js
831
- // [0] Points structure — same IPenData shape documented under onPen below
832
- {
833
- relativeX: number, // normalized 0..1
834
- relativeY: number, // normalized 0..1
835
- pressure: number,
836
- inContact: boolean,
837
- 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;
838
1034
  }
839
1035
  ```
840
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
+
841
1054
  ```js
842
- // [1] imageBase64 — base64 PNG data URL, or null if no image could be captured
843
- "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUA...";
1055
+ await signosoftPad.clear();
844
1056
  ```
845
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
+
846
1066
  ```js
847
- // [2] Metadata structure
848
- // The exact shape depends on whether a physical tablet or the mouse/touch
849
- // fallback produced the signature — the two paths report different fields.
850
-
851
- // Mouse / touch fallback:
852
- {
853
- canvas: {
854
- width: number,
855
- height: number,
856
- cssWidth: number,
857
- cssHeight: number,
858
- dpr: number
859
- },
860
- tablet: undefined,
861
- duration: number // seconds elapsed between the first and last recorded pen sample
862
- }
1067
+ await signosoftPad.cancel();
1068
+ ```
863
1069
 
864
- // Physical tablet (driver-reported):
865
- {
866
- canvas: {
867
- top?: number,
868
- left?: number,
869
- right?: number,
870
- bottom?: number,
871
- width: number,
872
- height: number
873
- },
874
- tablet: {
875
- name: string,
876
- aspectRatio: number,
877
- isCanvasStyleTablet: boolean,
878
- screenWidth: number,
879
- screenHeight: number,
880
- drawingArea: { x: number, y: number, width: number, height: number },
881
- serialNumber: Promise<string> | string,
882
- vendorId: number | null,
883
- productId: number | null
884
- }
885
- // no `duration` field is reported for the physical-tablet path
886
- }
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";
887
1121
  ```
888
1122
 
889
- `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>
890
1141
 
891
- #### 📜 `onPen` payload
1142
+ ## 🌐 Localization & Translation Keys
892
1143
 
893
- `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.
894
1145
 
895
- ```js
896
- {
897
- relativeX: number, // normalized 0..1
898
- relativeY: number, // normalized 0..1
899
- pressure: number,
900
- inContact: boolean,
901
- timestamp: number, // Unix ms (Date.now()) at the time of capture
902
- }
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
903
1167
  ```
904
1168
 
905
- > 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.
906
1170
 
907
- #### 📜 `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:
908
1172
 
909
- 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" } }
910
1175
 
911
- - `penDataBatch` — array of `IPenData` points collected during the session
912
- - `imageBase64` — PNG data URL of the signature drawn on canvas, or `null` if unavailable
913
- - `metadata` — object with `canvas` (dimensions, DPR) and `tablet` (device info) fields
1176
+ languageOptions: { lang: "cs", translations: { cs: { OK: "Potvrdit" }, en: { OK: "Confirm" } } }
1177
+ ```
914
1178
 
915
- #### 📜 `clear()`
1179
+ A JSON file you already have works the same way, import it and pass it:
916
1180
 
917
- 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";
918
1183
 
919
- #### 📜 `cancel()`
1184
+ languageOptions: { lang: "cs", translations: signpadTexts }
1185
+ ```
920
1186
 
921
- 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`.
922
1188
 
923
- ## 🔄 Component States
1189
+ **Changing the language at runtime.** Assign a new config with a different `lang`; the texts update without a reconnect:
924
1190
 
925
- 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
+ ```
926
1197
 
927
- | State | Value | Description |
928
- | :----------------------------- | :------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
929
- | `INITIAL` | `"initial"` | Component just mounted, no initialization yet. |
930
- | `CONNECTING` | `"connecting"` | Connection attempt in progress. |
931
- | `WAITING_FOR_DEVICE_SELECTION` | `"waiting_for_device_selection"` | Browser HID picker is open, waiting for user selection. |
932
- | `CONNECTED_PHYSICAL` | `"connected_physical"` | Physical tablet connected and ready. |
933
- | `CONNECTED_MOUSE_FALLBACK` | `"connected_mouse_fallback"` | No tablet found; mouse/touch fallback is active. |
934
- | `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`. |
935
- | `PROCESSING_OK` | `"processing_ok"` | OK action is being processed (finalizing signature). |
936
- | `PROCESSING_CLEAR` | `"processing_clear"` | Clear action is being processed. |
937
- | `PROCESSING_CANCEL` | `"processing_cancel"` | Cancel action is being processed. |
938
- | `DISCONNECTING` | `"disconnecting"` | Disconnect in progress. |
939
- | `DISCONNECTED` | `"disconnected"` | Component is idle and disconnected. |
940
- | `ERROR` | `"error"` | An error occurred (see `onError` callback). Signing automatically restarts a few seconds later unless the error's `category` is `NETWORK`. |
941
-
942
- ### 🚨 `SignpadErrorCategory`
943
-
944
- Also importable from `@signosoft/signpad-js`, carried on the `onError` payload (see [`onError` payload](#-onerror-payload-isignpaderrordetail) above):
945
-
946
- | Value | Value string | Auto-restarts signing? |
947
- | :--------------- | :------------------ | :---------------------- |
948
- | `BILLING_DENIED` | `"billing_denied"` | Yes |
949
- | `NETWORK` | `"network"` | No |
950
- | `OTHER` | `"other"` | Yes |
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. |
951
1246
 
952
- ---
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>
953
1250
 
954
1251
  ## 🎨 Styling & Theming
955
1252
 
@@ -958,36 +1255,49 @@ The component uses a hybrid theming model:
958
1255
  - It ships with complete default styles (works out of the box).
959
1256
  - You can override the visual design through CSS variables.
960
1257
 
961
- ## ✍️ Override via CSS
1258
+ ### Design tokens
962
1259
 
963
- 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.
964
1261
 
965
- ### 📎 Copy-Paste Starter
1262
+ <a id="override-via-css"></a>
966
1263
 
967
- 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:
968
1267
 
969
1268
  ```css
970
1269
  signosoft-signpad {
971
- /* Color Variables */
972
- --primary-color-0: #4e56ea;
973
- --primary-color-10: #7178ee;
974
- --background-color-0: #f1f2fd;
975
- --background-color-10: #e3e4fc;
976
- --text-color-0: #333e4a;
977
- --white-color: #ffffff;
978
- --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:
979
1283
 
1284
+ ```css
1285
+ signosoft-signpad {
980
1286
  /* Constraints */
981
- --min-height: 48px;
982
- --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;
983
1293
 
984
1294
  /* CSS Variables */
985
- --sign-font-family: Arial, Helvetica, sans-serif;
986
- --sign-common-border-radius: 8px;
1295
+ --sign-font-family: var(--fontFamily-primary), Arial, Helvetica, sans-serif;
1296
+ --sign-common-border-radius: var(--borderRadius-s);
987
1297
 
988
1298
  /* Top Bar */
989
- --sign-top-bar-bg-base: var(--background-color-10);
990
- --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);
991
1301
  --sign-top-bar-padding: var(--spacing-constraints);
992
1302
  --sign-top-bar-min-height: var(--min-height);
993
1303
  --sign-top-bar-border-top-left-radius: var(--sign-common-border-radius);
@@ -996,26 +1306,27 @@ signosoft-signpad {
996
1306
  --sign-top-bar-border-bottom-right-radius: 0;
997
1307
 
998
1308
  /* Canvas Area */
999
- --sign-canvas-bg-base: var(--background-color-0);
1309
+ --sign-canvas-bg-base: var(--brandBeige-100);
1000
1310
  --sign-canvas-wrapper-border-top-left-radius: 0;
1001
1311
  --sign-canvas-wrapper-border-top-right-radius: 0;
1002
1312
  --sign-canvas-wrapper-border-bottom-left-radius: 0;
1003
1313
  --sign-canvas-wrapper-border-bottom-right-radius: 0;
1004
1314
 
1005
1315
  /* Line */
1006
- --sign-line-height: 22px;
1007
- --sign-line-margin-bottom: 16px;
1008
- --sign-line-border-base: var(--primary-color-10);
1009
- --sign-line-additional-text-color: var(--text-color-0);
1010
- --sign-line-margin: 0px 24px var(--sign-line-margin-bottom) 24px;
1011
- --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);
1012
1323
  --sign-canvas-height-offset: var(--sign-line-height);
1013
1324
 
1014
1325
  /* Bottom Bar */
1015
- --sign-bottom-bar-bg-base: var(--background-color-10);
1326
+ --sign-bottom-bar-bg-base: var(--background-brandSubtle);
1016
1327
  --sign-bottom-bar-padding: var(--spacing-constraints);
1017
1328
  --sign-bottom-bar-min-height: var(--min-height);
1018
- --sign-bottom-bar-gap: 12px;
1329
+ --sign-bottom-bar-gap: var(--spacing-8);
1019
1330
  --sign-bottom-bar-border-top-left-radius: 0;
1020
1331
  --sign-bottom-bar-border-top-right-radius: 0;
1021
1332
  --sign-bottom-bar-border-bottom-left-radius: var(--sign-common-border-radius);
@@ -1024,76 +1335,66 @@ signosoft-signpad {
1024
1335
  );
1025
1336
 
1026
1337
  /* Button general settings */
1027
- --sign-button-font-size: 16px;
1028
- --sign-button-padding: 14px 16px;
1029
- --sign-button-min-height: var(--min-height);
1030
- --sign-button-border-radius: 8px;
1031
- --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);
1032
1343
 
1033
1344
  /* Primary Buttons (OK, Clear, Cancel) */
1034
- --sign-button-primary-bg-base: var(--primary-color-0);
1035
- --sign-button-primary-bg-hover: var(--primary-color-10);
1036
- --sign-button-primary-bg-disabled: var(--grey-color);
1037
- --sign-button-primary-text-base: var(--white-color);
1038
- --sign-button-primary-text-hover: var(--white-color);
1039
- --sign-button-primary-text-disabled: var(--white-color);
1040
- --sign-button-primary-border-base: 1px solid var(--primary-color-0);
1041
- --sign-button-primary-border-hover: 1px solid var(--primary-color-10);
1042
- --sign-button-primary-border-disabled: 1px solid var(--grey-color);
1043
-
1044
- /* Link Buttons (Connect signpad) */
1045
- --sign-button-link-bg-base: transparent;
1046
- --sign-button-link-bg-hover: var(--background-color-10);
1047
- --sign-button-link-bg-disabled: transparent;
1048
- --sign-button-link-text-base: var(--primary-color-0);
1049
- --sign-button-link-text-hover: var(--primary-color-10);
1050
- --sign-button-link-text-disabled: var(--grey-color);
1051
- --sign-button-link-border-base: 1px solid var(--primary-color-0);
1052
- --sign-button-link-border-hover: 1px solid var(--primary-color-10);
1053
- --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;
1054
1355
 
1055
1356
  /* Device State Text Colors */
1056
- --sign-device-state-text-color-connected: green;
1057
- --sign-device-state-text-color-disconnected: red;
1357
+ --sign-device-state-text-color-connected: var(--brand-650);
1058
1358
 
1059
1359
  /* Loading Overlay */
1060
- --sign-loading-overlay-bg-color: rgba(255, 255, 255, 0.8);
1061
- --sign-loading-overlay-text-color: var(--text-color-0);
1062
- --sign-loading-overlay-spinner-color: var(--primary-color-0);
1063
- --sign-loading-overlay-spinner-border-color: rgba(78, 86, 234, 0.1);
1064
- --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);
1065
1369
  }
1066
1370
  ```
1067
1371
 
1068
- ### 📋 Full CSS Variable Reference
1372
+ ### Full CSS Variable Reference
1069
1373
 
1070
- #### Base Colors & General
1374
+ #### General
1071
1375
 
1072
- | Variable | Default | Description |
1073
- | :---------------------------- | :------------------ | :---------------------------------------------- |
1074
- | `--primary-color-0` | `#4e56ea` | Primary brand color. |
1075
- | `--primary-color-10` | `#7178ee` | Primary hover/accent color. |
1076
- | `--background-color-0` | `#f1f2fd` | Secondary background color. |
1077
- | `--background-color-10` | `#e3e4fc` | Main bar background color. |
1078
- | `--text-color-0` | `#333e4a` | Main text color. |
1079
- | `--white-color` | `#ffffff` | Pure white color. |
1080
- | `--grey-color` | `#b5b9be` | Disabled state color. |
1081
- | `--sign-font-family` | `Arial, sans-serif` | Global font family. |
1082
- | `--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. |
1083
1380
 
1084
1381
  #### Layout & Constraints
1085
1382
 
1086
- | Variable | Default | Description |
1087
- | :---------------------- | :---------- | :------------------------------------- |
1088
- | `--min-height` | `48px` | Minimum height for bars and buttons. |
1089
- | `--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; }`).
1090
1391
 
1091
1392
  #### Top Bar
1092
1393
 
1093
1394
  | Variable | Default | Description |
1094
1395
  | :------------------------------------------ | :--------------------------------- | :---------------------------------- |
1095
- | `--sign-top-bar-bg-base` | `var(--background-color-10)` | Background color of the top bar. |
1096
- | `--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. |
1097
1398
  | `--sign-top-bar-padding` | `var(--spacing-constraints)` | Inner padding of the top bar. |
1098
1399
  | `--sign-top-bar-min-height` | `var(--min-height)` | Minimum height of the top bar. |
1099
1400
  | `--sign-top-bar-border-top-left-radius` | `var(--sign-common-border-radius)` | Top-left radius of the top bar. |
@@ -1103,29 +1404,29 @@ signosoft-signpad {
1103
1404
 
1104
1405
  #### Canvas & Signature Line
1105
1406
 
1106
- | Variable | Default | Description |
1107
- | :------------------------------------------------- | :--------------------------------------------- | :--------------------------------------------------------------------------- |
1108
- | `--sign-canvas-bg-base` | `var(--background-color-0)` | Background color of the drawing area. |
1109
- | `--sign-canvas-wrapper-border-top-left-radius` | `0` | Top-left canvas radius, typically rounded when the top bar is hidden. |
1110
- | `--sign-canvas-wrapper-border-top-right-radius` | `0` | Top-right canvas radius, typically rounded when the top bar is hidden. |
1111
- | `--sign-canvas-wrapper-border-bottom-left-radius` | `0` | Bottom-left canvas radius, typically rounded when the bottom bar is hidden. |
1112
- | `--sign-canvas-wrapper-border-bottom-right-radius` | `0` | Bottom-right canvas radius, typically rounded when the bottom bar is hidden. |
1113
- | `--sign-line-height` | `22px` | Vertical offset/height for the guide line. |
1114
- | `--sign-line-margin-bottom` | `16px` | Bottom spacing used in canvas line layout. |
1115
- | `--sign-line-border-base` | `var(--primary-color-10)` | Color of the signature guide line. |
1116
- | `--sign-line-additional-text-color` | `var(--text-color-0)` | Color of the helper text below the line. |
1117
- | `--sign-line-margin` | `0px 24px var(--sign-line-margin-bottom) 24px` | Spacing around the guide line. |
1118
- | `--sign-canvas-line-text-font-size` | `12px` | Font size for guide line labels. |
1119
- | `--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. |
1120
1421
 
1121
1422
  #### Bottom Bar
1122
1423
 
1123
1424
  | Variable | Default | Description |
1124
1425
  | :--------------------------------------------- | :--------------------------------- | :--------------------------------------- |
1125
- | `--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. |
1126
1427
  | `--sign-bottom-bar-padding` | `var(--spacing-constraints)` | Inner padding of the bottom bar. |
1127
1428
  | `--sign-bottom-bar-min-height` | `var(--min-height)` | Minimum height of the bottom bar. |
1128
- | `--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. |
1129
1430
  | `--sign-bottom-bar-border-top-left-radius` | `0` | Top-left radius of the bottom bar. |
1130
1431
  | `--sign-bottom-bar-border-top-right-radius` | `0` | Top-right radius of the bottom bar. |
1131
1432
  | `--sign-bottom-bar-border-bottom-left-radius` | `var(--sign-common-border-radius)` | Bottom-left radius of the bottom bar. |
@@ -1133,167 +1434,87 @@ signosoft-signpad {
1133
1434
 
1134
1435
  #### Buttons (General)
1135
1436
 
1136
- | Variable | Default | Description |
1137
- | :---------------------------- | :------------------ | :----------------------------- |
1138
- | `--sign-button-font-size` | `16px` | Font size for all buttons. |
1139
- | `--sign-button-padding` | `14px 16px` | Inner padding for all buttons. |
1140
- | `--sign-button-min-height` | `var(--min-height)` | Minimum height for buttons. |
1141
- | `--sign-button-border-radius` | `8px` | Border radius for all buttons. |
1142
- | `--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. |
1143
1444
 
1144
1445
  #### Primary Buttons (OK, Clear, Cancel)
1145
1446
 
1146
- | Variable | Description |
1147
- | :-------------------------------------- | :----------------------------------- |
1148
- | `--sign-button-primary-bg-base` | Background color of primary buttons. |
1149
- | `--sign-button-primary-bg-hover` | Hover background color. |
1150
- | `--sign-button-primary-bg-disabled` | Disabled background color. |
1151
- | `--sign-button-primary-text-base` | Text color for primary buttons. |
1152
- | `--sign-button-primary-text-hover` | Hover text color. |
1153
- | `--sign-button-primary-text-disabled` | Disabled text color. |
1154
- | `--sign-button-primary-border-base` | Border of primary buttons. |
1155
- | `--sign-button-primary-border-hover` | Hover border color. |
1156
- | `--sign-button-primary-border-disabled` | Disabled border color. |
1157
-
1158
- #### Link/Connect Buttons
1159
-
1160
- | Variable | Default | Description |
1161
- | :----------------------------------- | :---------------------------------- | :------------------------------------------- |
1162
- | `--sign-button-link-bg-base` | `transparent` | Background color for secondary/link buttons. |
1163
- | `--sign-button-link-bg-hover` | `var(--background-color-10)` | Hover background color. |
1164
- | `--sign-button-link-bg-disabled` | `transparent` | Disabled background color. |
1165
- | `--sign-button-link-text-base` | `var(--primary-color-0)` | Text color for secondary/link buttons. |
1166
- | `--sign-button-link-text-hover` | `var(--primary-color-10)` | Hover text color. |
1167
- | `--sign-button-link-text-disabled` | `var(--grey-color)` | Disabled text color. |
1168
- | `--sign-button-link-border-base` | `1px solid var(--primary-color-0)` | Border for secondary/link buttons. |
1169
- | `--sign-button-link-border-hover` | `1px solid var(--primary-color-10)` | Hover border color. |
1170
- | `--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. |
1171
1459
 
1172
1460
  #### Status & Overlays
1173
1461
 
1174
- | Variable | Default | Description |
1175
- | :-------------------------------------------- | :------------------------- | :--------------------------------- |
1176
- | `--sign-device-state-text-color-connected` | `green` | Text color when device is ready. |
1177
- | `--sign-device-state-text-color-disconnected` | `red` | Text color when disconnected. |
1178
- | `--sign-loading-overlay-bg-color` | `rgba(255, 255, 255, 0.8)` | Background of the loading spinner. |
1179
- | `--sign-loading-overlay-text-color` | `var(--text-color-0)` | Loading overlay text color. |
1180
- | `--sign-loading-overlay-spinner-color` | `var(--primary-color-0)` | Color of the animated spinner. |
1181
- | `--sign-loading-overlay-spinner-border-color` | `rgba(78, 86, 234, 0.1)` | Base border color of spinner ring. |
1182
- | `--sign-loading-overlay-spinner-size` | `30px` | Size of the spinner icon. |
1183
-
1184
- ## 💬 Feedback & Support
1185
-
1186
- 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:
1187
-
1188
- - 📧 **Support Email:** [esign@signosoft.com](mailto:esign@signosoft.com)
1189
- - 🌐 **Official Website:** [www.signosoft.com](https://www.signosoft.com)
1190
-
1191
- ## 🛠️ Contributing
1192
-
1193
- ### Commit naming convention
1194
-
1195
- - Every commit message must start with Jira task id and category:
1196
- - `MAIN-000/<category>: <summary>`
1197
- - For work in progress:
1198
- - `MAIN-000/<category>: WIP - <summary>`
1199
-
1200
- Allowed categories:
1201
-
1202
- - `feat`: adding a new feature
1203
- - `fix`: fixing a bug
1204
- - `refactor`: performance/readability/maintainability change
1205
- - `chore`: docs, formatting, tests, cleanup, tooling
1206
- - `merge`: branch merge into `main`
1207
-
1208
- Use infinitive verb form in commit summaries:
1209
-
1210
- - <span style="color: #16a34a;"><strong>RIGHT:</strong></span> `add`, `fix`, `remove`, `update`, `refactor`
1211
- - <span style="color: #dc2626;"><strong>WRONG:</strong></span> Continuous tense: `adding`, `removing` Past tense: `added`, `fixed`
1212
-
1213
- Examples:
1214
-
1215
- ```bash
1216
- git commit -m "MAIN-000/feat: add new button component; add button to templates"
1217
- ```
1218
-
1219
- ```bash
1220
- git commit -m "MAIN-000/fix: add stop directive to button component to prevent propagation"
1221
- ```
1222
-
1223
- ```bash
1224
- git commit -m "MAIN-000/refactor: rewrite button component in TypeScript"
1225
- ```
1226
-
1227
- ```bash
1228
- git commit -m "MAIN-000/chore: write button documentation"
1229
- ```
1230
-
1231
- ```bash
1232
- git commit -m "MAIN-000/merge: new button"
1233
- ```
1234
-
1235
- ```bash
1236
- git commit -m "MAIN-000/feat: WIP - basic signing functionality"
1237
- ```
1238
-
1239
- ### Versioning and release commits
1240
-
1241
- 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. |
1242
1470
 
1243
- - `MAJOR` (`x.0.0`): breaking changes
1244
- - `MINOR` (`x.y.0`): backward-compatible feature set
1245
- - `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.
1246
1472
 
1247
- Commit naming for version bumps:
1473
+ <a id="accessibility"></a>
1248
1474
 
1249
- - Version-only change: use `chore`
1250
- - Version bump with actual code/features in same commit: use `feat`/`fix`/`refactor` based on dominant change
1251
- - For release commits, include both versions in message: `release from <old> to <new>`
1475
+ ## ♿ Accessibility
1252
1476
 
1253
- Example (version bump with feature changes):
1477
+ The component targets WCAG 2.1 AA for everything it renders. What is in place:
1254
1478
 
1255
- ```bash
1256
- git commit -m "MAIN-000/feat: release from 0.2.4 to 0.3.0 - add signature method"
1257
- ```
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.
1258
1485
 
1259
- Example (version-only commit):
1486
+ **Known limitations (plan an alternative in your app):**
1260
1487
 
1261
- ```bash
1262
- git commit -m "MAIN-000/chore: release from 0.2.4 to 0.3.0"
1263
- ```
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.
1264
1491
 
1265
- 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.
1266
1493
 
1267
- - `PATCH` (`x.y.Z`): increase patch only (`1.4.2` -> `1.4.3`)
1268
- - `MINOR` (`x.Y.0`): increase minor, reset patch (`1.4.9` -> `1.5.0`)
1269
- - `MAJOR` (`X.0.0`): increase major, reset minor and patch (`1.9.7` -> `2.0.0`)
1494
+ <a id="feedback--support"></a>
1270
1495
 
1271
- Examples:
1496
+ ## 💬 Feedback & Support
1272
1497
 
1273
- ```bash
1274
- # patch
1275
- 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:
1276
1499
 
1277
- # minor
1278
- 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)
1279
1502
 
1280
- # major
1281
- git commit -m "MAIN-000/refactor: release from 1.1.1 to 2.0.0 - remove legacy API"
1282
- ```
1503
+ <a id="license"></a>
1283
1504
 
1284
1505
  ## 📄 License
1285
1506
 
1286
- **Proprietary Commercial License**
1507
+ **Source-available, proprietary commercial license**
1287
1508
 
1288
1509
  Copyright © 2026, Signosoft. All rights reserved.
1289
1510
 
1290
- 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.
1291
1512
 
1292
- ### 🔐 Commercial Usage
1513
+ ### Commercial Usage
1293
1514
 
1294
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).
1295
1516
 
1296
- ### 📜 Key Terms
1517
+ ### Key Terms
1297
1518
 
1298
1519
  - **Unauthorized Use:** Any use without a current, fully paid license is expressly prohibited and constitutes a violation of our intellectual property rights.
1299
1520
  - **Legal Action:** Signosoft reserves the right to pursue legal action to enforce its rights and seek damages for unauthorized use.