entasis 0.13.4 → 0.13.6

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.
Files changed (83) hide show
  1. package/dist/components/Address/Address.svelte +44 -0
  2. package/dist/components/Address/Address.svelte.d.ts +4 -0
  3. package/dist/components/Address/address.d.ts +99 -0
  4. package/dist/components/Address/address.google.d.ts +89 -0
  5. package/dist/components/Address/address.google.fixtures.json +808 -0
  6. package/dist/components/Address/address.google.js +184 -0
  7. package/dist/components/Address/address.js +109 -0
  8. package/dist/components/Address/address.mcp.d.ts +1 -0
  9. package/dist/components/Address/address.mcp.js +100 -0
  10. package/dist/components/Address/address.props.d.ts +26 -0
  11. package/dist/components/Address/address.props.js +1 -0
  12. package/dist/components/Address/address.provider.d.ts +19 -0
  13. package/dist/components/Address/address.provider.js +69 -0
  14. package/dist/components/Address/address.theme.d.ts +63 -0
  15. package/dist/components/Address/address.theme.js +42 -0
  16. package/dist/components/Address/index.d.ts +6 -0
  17. package/dist/components/Address/index.js +5 -0
  18. package/dist/components/Code/Code.svelte +7 -2
  19. package/dist/components/Code/CodeTheme.svelte +12 -1
  20. package/dist/components/Code/code.highlighter.js +2 -2
  21. package/dist/components/Code/code.mcp.d.ts +1 -1
  22. package/dist/components/Code/code.mcp.js +3 -1
  23. package/dist/components/Code/code.theme.js +6 -5
  24. package/dist/components/Form/AddressInput/AddressInput.svelte +482 -0
  25. package/dist/components/Form/AddressInput/AddressInput.svelte.d.ts +4 -0
  26. package/dist/components/Form/AddressInput/addressInput.mcp.d.ts +1 -0
  27. package/dist/components/Form/AddressInput/addressInput.mcp.js +110 -0
  28. package/dist/components/Form/AddressInput/addressInput.props.d.ts +72 -0
  29. package/dist/components/Form/AddressInput/addressInput.props.js +1 -0
  30. package/dist/components/Form/AddressInput/addressInput.theme.d.ts +63 -0
  31. package/dist/components/Form/AddressInput/addressInput.theme.js +55 -0
  32. package/dist/components/Form/AddressInput/index.d.ts +3 -0
  33. package/dist/components/Form/AddressInput/index.js +2 -0
  34. package/dist/components/Form/Field/field.d.ts +5 -2
  35. package/dist/components/Form/Field/field.state.svelte.js +4 -0
  36. package/dist/components/Form/Field/index.d.ts +1 -1
  37. package/dist/components/Form/Field/schemas.js +8 -0
  38. package/dist/components/Form/Form/FormInputRenderer.svelte +6 -0
  39. package/dist/components/Form/Form/form.d.ts +6 -0
  40. package/dist/components/Form/Form/form.mcp.d.ts +1 -1
  41. package/dist/components/Form/Form/form.mcp.js +1 -1
  42. package/dist/components/Form/Form/form.state.svelte.d.ts +142 -11
  43. package/dist/components/Form/Form/form.state.svelte.js +2 -0
  44. package/dist/components/Form/Form/visibility.d.ts +186 -20
  45. package/dist/components/Form/MultiStepForm/multiStepForm.state.svelte.d.ts +136 -0
  46. package/dist/components/Form/SignatureInput/SignatureInput.svelte +327 -0
  47. package/dist/components/Form/SignatureInput/SignatureInput.svelte.d.ts +4 -0
  48. package/dist/components/Form/SignatureInput/index.d.ts +4 -0
  49. package/dist/components/Form/SignatureInput/index.js +2 -0
  50. package/dist/components/Form/SignatureInput/signatureInput.mcp.d.ts +1 -0
  51. package/dist/components/Form/SignatureInput/signatureInput.mcp.js +74 -0
  52. package/dist/components/Form/SignatureInput/signatureInput.props.d.ts +29 -0
  53. package/dist/components/Form/SignatureInput/signatureInput.props.js +1 -0
  54. package/dist/components/Form/SignatureInput/signatureInput.strokes.d.ts +77 -0
  55. package/dist/components/Form/SignatureInput/signatureInput.strokes.js +149 -0
  56. package/dist/components/Form/SignatureInput/signatureInput.theme.d.ts +99 -0
  57. package/dist/components/Form/SignatureInput/signatureInput.theme.js +70 -0
  58. package/dist/components/Sidebar/sidebar-layout.js +3 -1
  59. package/dist/components/Timeline/Timeline.svelte +1 -1
  60. package/dist/components/Timeline/TimelineEntry.svelte +16 -2
  61. package/dist/components/Timeline/index.d.ts +1 -1
  62. package/dist/components/Timeline/timeline.items.d.ts +1 -1
  63. package/dist/components/Timeline/timeline.items.js +7 -2
  64. package/dist/components/Timeline/timeline.mcp.d.ts +1 -1
  65. package/dist/components/Timeline/timeline.mcp.js +35 -3
  66. package/dist/components/Timeline/timeline.props.d.ts +17 -1
  67. package/dist/components/Timeline/timeline.theme.d.ts +36 -0
  68. package/dist/components/Timeline/timeline.theme.js +27 -4
  69. package/dist/generated/componentAliases.d.ts +3 -0
  70. package/dist/generated/componentAliases.js +3 -0
  71. package/dist/generated/componentContract.d.ts +47 -2
  72. package/dist/generated/componentContract.js +114 -0
  73. package/dist/generated/componentMcpRegistry.js +6 -0
  74. package/dist/i18n/ar.js +18 -0
  75. package/dist/i18n/de.js +18 -0
  76. package/dist/i18n/en.d.ts +18 -0
  77. package/dist/i18n/en.js +18 -0
  78. package/dist/i18n/es.js +18 -0
  79. package/dist/i18n/fr.js +18 -0
  80. package/dist/i18n/ja.js +18 -0
  81. package/dist/i18n/pt.js +18 -0
  82. package/dist/i18n/zh.js +18 -0
  83. package/package.json +25 -1
@@ -437,6 +437,40 @@ export declare class MultiStepFormState<I extends MultiStepFormItems = FormStep[
437
437
  [key: symbol]: import("svelte/attachments").Attachment<HTMLElement> | null | undefined;
438
438
  }, "visible"> & {
439
439
  visible?: boolean | ((value: FormValueRecord) => boolean);
440
+ }) | (Omit<{
441
+ type: "signature";
442
+ } & Omit<import("../Field/field.ts").InputProps<"signature">, "theme"> & {
443
+ format?: import("../SignatureInput/signatureInput.strokes.ts").SignatureFormat;
444
+ inkColor?: string;
445
+ strokeWidth?: number;
446
+ placeholder?: string;
447
+ clearLabel?: string;
448
+ undoLabel?: string;
449
+ i18n?: Partial<import("../../../i18n/en.ts").Messages>;
450
+ theme?: import("../SignatureInput/signatureInput.theme.ts").SignatureInputThemeProps & import("../Field/field.ts").InputProps<"signature">["theme"];
451
+ } & import("../../../types/props.ts").DataAttributes & {
452
+ [key: symbol]: import("svelte/attachments").Attachment<HTMLElement> | null | undefined;
453
+ }, "visible"> & {
454
+ visible?: boolean | ((value: FormValueRecord) => boolean);
455
+ }) | (Omit<{
456
+ type: "address";
457
+ } & Omit<import("../Field/field.ts").InputProps<"address">, "theme" | "placeholder"> & {
458
+ provider?: import("../../Address/address.ts").AddressProvider | null;
459
+ fields?: readonly import("../AddressInput/addressInput.props.ts").AddressFieldRow[];
460
+ editableFields?: readonly import("../../Address/address.ts").AddressField[];
461
+ requiredFields?: readonly import("../../Address/address.ts").AddressField[];
462
+ countries?: readonly string[];
463
+ defaultCountry?: string;
464
+ layout?: import("../AddressInput/addressInput.props.ts").AddressInputLayout;
465
+ minSearchLength?: number;
466
+ placeholder?: string;
467
+ onResolve?: (address: import("../../Address/address.ts").Address) => void;
468
+ i18n?: Partial<import("../../../i18n/en.ts").Messages>;
469
+ theme?: import("../AddressInput/addressInput.theme.ts").AddressInputThemeProps & import("../Field/field.ts").InputProps<"address">["theme"];
470
+ } & import("../../../types/props.ts").DataAttributes & {
471
+ [key: symbol]: import("svelte/attachments").Attachment<HTMLElement> | null | undefined;
472
+ }, "visible"> & {
473
+ visible?: boolean | ((value: FormValueRecord) => boolean);
440
474
  }) | (Omit<{
441
475
  type: "slider";
442
476
  } & Omit<import("../Field/field.ts").InputProps<"slider">, "onValueChange" | "value" | "defaultValue" | "onValidate"> & {
@@ -1288,6 +1322,40 @@ export declare class MultiStepFormState<I extends MultiStepFormItems = FormStep[
1288
1322
  [key: symbol]: import("svelte/attachments").Attachment<HTMLElement> | null | undefined;
1289
1323
  }, "visible"> & {
1290
1324
  visible?: boolean | ((value: FormValueRecord) => boolean);
1325
+ }) | (Omit<{
1326
+ type: "signature";
1327
+ } & Omit<import("../Field/field.ts").InputProps<"signature">, "theme"> & {
1328
+ format?: import("../SignatureInput/signatureInput.strokes.ts").SignatureFormat;
1329
+ inkColor?: string;
1330
+ strokeWidth?: number;
1331
+ placeholder?: string;
1332
+ clearLabel?: string;
1333
+ undoLabel?: string;
1334
+ i18n?: Partial<import("../../../i18n/en.ts").Messages>;
1335
+ theme?: import("../SignatureInput/signatureInput.theme.ts").SignatureInputThemeProps & import("../Field/field.ts").InputProps<"signature">["theme"];
1336
+ } & import("../../../types/props.ts").DataAttributes & {
1337
+ [key: symbol]: import("svelte/attachments").Attachment<HTMLElement> | null | undefined;
1338
+ }, "visible"> & {
1339
+ visible?: boolean | ((value: FormValueRecord) => boolean);
1340
+ }) | (Omit<{
1341
+ type: "address";
1342
+ } & Omit<import("../Field/field.ts").InputProps<"address">, "theme" | "placeholder"> & {
1343
+ provider?: import("../../Address/address.ts").AddressProvider | null;
1344
+ fields?: readonly import("../AddressInput/addressInput.props.ts").AddressFieldRow[];
1345
+ editableFields?: readonly import("../../Address/address.ts").AddressField[];
1346
+ requiredFields?: readonly import("../../Address/address.ts").AddressField[];
1347
+ countries?: readonly string[];
1348
+ defaultCountry?: string;
1349
+ layout?: import("../AddressInput/addressInput.props.ts").AddressInputLayout;
1350
+ minSearchLength?: number;
1351
+ placeholder?: string;
1352
+ onResolve?: (address: import("../../Address/address.ts").Address) => void;
1353
+ i18n?: Partial<import("../../../i18n/en.ts").Messages>;
1354
+ theme?: import("../AddressInput/addressInput.theme.ts").AddressInputThemeProps & import("../Field/field.ts").InputProps<"address">["theme"];
1355
+ } & import("../../../types/props.ts").DataAttributes & {
1356
+ [key: symbol]: import("svelte/attachments").Attachment<HTMLElement> | null | undefined;
1357
+ }, "visible"> & {
1358
+ visible?: boolean | ((value: FormValueRecord) => boolean);
1291
1359
  }) | (Omit<{
1292
1360
  type: "slider";
1293
1361
  } & Omit<import("../Field/field.ts").InputProps<"slider">, "onValueChange" | "value" | "defaultValue" | "onValidate"> & {
@@ -2335,6 +2403,40 @@ export declare class MultiStepFormState<I extends MultiStepFormItems = FormStep[
2335
2403
  [key: symbol]: import("svelte/attachments").Attachment<HTMLElement> | null | undefined;
2336
2404
  }, "visible"> & {
2337
2405
  visible?: boolean | ((value: FormValueRecord) => boolean);
2406
+ }) | (Omit<{
2407
+ type: "signature";
2408
+ } & Omit<import("../Field/field.ts").InputProps<"signature">, "theme"> & {
2409
+ format?: import("../SignatureInput/signatureInput.strokes.ts").SignatureFormat;
2410
+ inkColor?: string;
2411
+ strokeWidth?: number;
2412
+ placeholder?: string;
2413
+ clearLabel?: string;
2414
+ undoLabel?: string;
2415
+ i18n?: Partial<import("../../../i18n/en.ts").Messages>;
2416
+ theme?: import("../SignatureInput/signatureInput.theme.ts").SignatureInputThemeProps & import("../Field/field.ts").InputProps<"signature">["theme"];
2417
+ } & import("../../../types/props.ts").DataAttributes & {
2418
+ [key: symbol]: import("svelte/attachments").Attachment<HTMLElement> | null | undefined;
2419
+ }, "visible"> & {
2420
+ visible?: boolean | ((value: FormValueRecord) => boolean);
2421
+ }) | (Omit<{
2422
+ type: "address";
2423
+ } & Omit<import("../Field/field.ts").InputProps<"address">, "theme" | "placeholder"> & {
2424
+ provider?: import("../../Address/address.ts").AddressProvider | null;
2425
+ fields?: readonly import("../AddressInput/addressInput.props.ts").AddressFieldRow[];
2426
+ editableFields?: readonly import("../../Address/address.ts").AddressField[];
2427
+ requiredFields?: readonly import("../../Address/address.ts").AddressField[];
2428
+ countries?: readonly string[];
2429
+ defaultCountry?: string;
2430
+ layout?: import("../AddressInput/addressInput.props.ts").AddressInputLayout;
2431
+ minSearchLength?: number;
2432
+ placeholder?: string;
2433
+ onResolve?: (address: import("../../Address/address.ts").Address) => void;
2434
+ i18n?: Partial<import("../../../i18n/en.ts").Messages>;
2435
+ theme?: import("../AddressInput/addressInput.theme.ts").AddressInputThemeProps & import("../Field/field.ts").InputProps<"address">["theme"];
2436
+ } & import("../../../types/props.ts").DataAttributes & {
2437
+ [key: symbol]: import("svelte/attachments").Attachment<HTMLElement> | null | undefined;
2438
+ }, "visible"> & {
2439
+ visible?: boolean | ((value: FormValueRecord) => boolean);
2338
2440
  }) | (Omit<{
2339
2441
  type: "slider";
2340
2442
  } & Omit<import("../Field/field.ts").InputProps<"slider">, "onValueChange" | "value" | "defaultValue" | "onValidate"> & {
@@ -3186,6 +3288,40 @@ export declare class MultiStepFormState<I extends MultiStepFormItems = FormStep[
3186
3288
  [key: symbol]: import("svelte/attachments").Attachment<HTMLElement> | null | undefined;
3187
3289
  }, "visible"> & {
3188
3290
  visible?: boolean | ((value: FormValueRecord) => boolean);
3291
+ }) | (Omit<{
3292
+ type: "signature";
3293
+ } & Omit<import("../Field/field.ts").InputProps<"signature">, "theme"> & {
3294
+ format?: import("../SignatureInput/signatureInput.strokes.ts").SignatureFormat;
3295
+ inkColor?: string;
3296
+ strokeWidth?: number;
3297
+ placeholder?: string;
3298
+ clearLabel?: string;
3299
+ undoLabel?: string;
3300
+ i18n?: Partial<import("../../../i18n/en.ts").Messages>;
3301
+ theme?: import("../SignatureInput/signatureInput.theme.ts").SignatureInputThemeProps & import("../Field/field.ts").InputProps<"signature">["theme"];
3302
+ } & import("../../../types/props.ts").DataAttributes & {
3303
+ [key: symbol]: import("svelte/attachments").Attachment<HTMLElement> | null | undefined;
3304
+ }, "visible"> & {
3305
+ visible?: boolean | ((value: FormValueRecord) => boolean);
3306
+ }) | (Omit<{
3307
+ type: "address";
3308
+ } & Omit<import("../Field/field.ts").InputProps<"address">, "theme" | "placeholder"> & {
3309
+ provider?: import("../../Address/address.ts").AddressProvider | null;
3310
+ fields?: readonly import("../AddressInput/addressInput.props.ts").AddressFieldRow[];
3311
+ editableFields?: readonly import("../../Address/address.ts").AddressField[];
3312
+ requiredFields?: readonly import("../../Address/address.ts").AddressField[];
3313
+ countries?: readonly string[];
3314
+ defaultCountry?: string;
3315
+ layout?: import("../AddressInput/addressInput.props.ts").AddressInputLayout;
3316
+ minSearchLength?: number;
3317
+ placeholder?: string;
3318
+ onResolve?: (address: import("../../Address/address.ts").Address) => void;
3319
+ i18n?: Partial<import("../../../i18n/en.ts").Messages>;
3320
+ theme?: import("../AddressInput/addressInput.theme.ts").AddressInputThemeProps & import("../Field/field.ts").InputProps<"address">["theme"];
3321
+ } & import("../../../types/props.ts").DataAttributes & {
3322
+ [key: symbol]: import("svelte/attachments").Attachment<HTMLElement> | null | undefined;
3323
+ }, "visible"> & {
3324
+ visible?: boolean | ((value: FormValueRecord) => boolean);
3189
3325
  }) | (Omit<{
3190
3326
  type: "slider";
3191
3327
  } & Omit<import("../Field/field.ts").InputProps<"slider">, "onValueChange" | "value" | "defaultValue" | "onValidate"> & {
@@ -0,0 +1,327 @@
1
+ <script lang="ts">
2
+ import { untrack } from 'svelte';
3
+ import Button from '../../Button/Button.svelte';
4
+ import { arrowCounterClockwiseIcon } from '../../Icons/arrowCounterClockwise.js';
5
+ import { eraserIcon } from '../../Icons/eraser.js';
6
+ import { tooltip } from '../../Tooltip/tooltip.attachment.svelte.js';
7
+ import Field from '../Field/Field.svelte';
8
+ import FieldHiddenInputs from '../Field/FieldHiddenInputs.svelte';
9
+ import { createFieldState } from '../Field/field.state.svelte.js';
10
+ import { useI18n } from '../../../i18n/context.svelte.js';
11
+ import type { SignatureInputProps } from './signatureInput.props.js';
12
+ import {
13
+ exportStrokes,
14
+ strokeShapes,
15
+ type SignaturePoint,
16
+ type SignatureStroke
17
+ } from './signatureInput.strokes.js';
18
+ import { useSignatureInputTheme } from './signatureInput.theme.js';
19
+
20
+ let {
21
+ defaultValue = null,
22
+ value = $bindable(),
23
+ errors = $bindable([]),
24
+ focused = $bindable(false),
25
+ required = false,
26
+ disabled,
27
+ readOnly,
28
+ messages,
29
+ name,
30
+ onValidate,
31
+ onValueChange,
32
+ visible,
33
+ size = 'normal',
34
+ format = 'png',
35
+ inkColor = '#000000',
36
+ strokeWidth = 2.5,
37
+ placeholder,
38
+ clearLabel,
39
+ undoLabel,
40
+ i18n,
41
+ label,
42
+ theme,
43
+ ...rest
44
+ }: SignatureInputProps = $props();
45
+ if (value === undefined) value = untrack(() => defaultValue);
46
+
47
+ const id = $props.id();
48
+ const statusId = `${id}-status`;
49
+
50
+ const field = createFieldState({
51
+ id,
52
+ get value() {
53
+ return value;
54
+ },
55
+ set value(nextValue) {
56
+ value = nextValue;
57
+ },
58
+ get errors() {
59
+ return errors;
60
+ },
61
+ set errors(nextErrors: string[] | boolean) {
62
+ errors = nextErrors;
63
+ },
64
+ get focused() {
65
+ return focused;
66
+ },
67
+ set focused(nextFocused: boolean) {
68
+ focused = nextFocused;
69
+ },
70
+ onValueChange: (nextValue) => onValueChange?.(nextValue),
71
+ get disabled() {
72
+ return disabled;
73
+ },
74
+ set disabled(nextDisabled: boolean | undefined) {
75
+ disabled = nextDisabled;
76
+ },
77
+ get readOnly() {
78
+ return readOnly;
79
+ },
80
+ get messages() {
81
+ return messages;
82
+ },
83
+ get required() {
84
+ return required;
85
+ },
86
+ get name() {
87
+ return name;
88
+ },
89
+ set name(nextName: string | undefined) {
90
+ name = nextName;
91
+ },
92
+ get onValidate() {
93
+ return onValidate;
94
+ },
95
+ get visible() {
96
+ return visible;
97
+ },
98
+ type: 'signature'
99
+ });
100
+
101
+ const t = $derived(useI18n(i18n));
102
+ const classes = $derived(useSignatureInputTheme(theme));
103
+ const interactive = $derived(!field.disabled && !field.readOnly);
104
+ const resolvedClearLabel = $derived(clearLabel ?? t.signatureInputClear);
105
+ const resolvedUndoLabel = $derived(undoLabel ?? t.signatureInputUndo);
106
+
107
+ /** Finished strokes, drawn since the pad was last cleared. */
108
+ let strokes = $state<SignatureStroke[]>([]);
109
+ /** The stroke under the pointer, until the pointer is lifted. */
110
+ let current = $state<SignaturePoint[] | null>(null);
111
+ /** The value these strokes exported. Any other value came from outside. */
112
+ let exported = $state<string | null>(null);
113
+
114
+ // A value from outside (a saved signature, a reset) replaces the strokes: they no longer
115
+ // describe it, so it shows as an image until someone draws again.
116
+ $effect(() => {
117
+ const nextValue = field.value ?? null;
118
+ untrack(() => {
119
+ if (nextValue === exported) return;
120
+ strokes = [];
121
+ current = null;
122
+ exported = null;
123
+ });
124
+ });
125
+
126
+ const hasInk = $derived(strokes.length > 0 || current !== null);
127
+ const image = $derived(hasInk ? null : (field.value ?? null));
128
+ const shapes = $derived(
129
+ [...strokes, ...(current ? [current] : [])].flatMap((stroke) =>
130
+ strokeShapes(stroke, strokeWidth)
131
+ )
132
+ );
133
+
134
+ let pad = $state<HTMLDivElement | null>(null);
135
+ let pointerId: number | null = null;
136
+
137
+ const pointFrom = (event: PointerEvent): SignaturePoint => {
138
+ const box = pad!.getBoundingClientRect();
139
+ return {
140
+ x: event.clientX - box.left,
141
+ y: event.clientY - box.top,
142
+ pressure: event.pointerType === 'pen' && event.pressure > 0 ? event.pressure : 0.5
143
+ };
144
+ };
145
+
146
+ const commit = () => {
147
+ const next = exportStrokes(strokes, {
148
+ format,
149
+ color: inkColor,
150
+ width: strokeWidth,
151
+ padding: strokeWidth * 2,
152
+ scale: 2
153
+ });
154
+ exported = next;
155
+ field.setValue(next);
156
+ };
157
+
158
+ const finishStroke = () => {
159
+ if (!current) return;
160
+ strokes = [...strokes, current];
161
+ current = null;
162
+ pointerId = null;
163
+ commit();
164
+ };
165
+
166
+ const undo = () => {
167
+ if (!interactive || strokes.length === 0) return;
168
+ strokes = strokes.slice(0, -1);
169
+ commit();
170
+ };
171
+
172
+ const clear = () => {
173
+ if (!interactive || (!hasInk && !field.value)) return;
174
+ strokes = [];
175
+ current = null;
176
+ exported = null;
177
+ field.setValue(null);
178
+ pad?.focus();
179
+ };
180
+
181
+ const onpointerdown = (event: PointerEvent) => {
182
+ if (!interactive || !pad || current) return;
183
+ if (event.pointerType === 'mouse' && event.button !== 0) return;
184
+ // The pad's `touch-action: none` and `user-select: none` keep the page from scrolling or
185
+ // selecting under the stroke. The default is not prevented, so the browser focuses the pad
186
+ // as for any click, without a keyboard focus ring.
187
+ pointerId = event.pointerId;
188
+ pad.setPointerCapture?.(event.pointerId);
189
+ current = [pointFrom(event)];
190
+ };
191
+
192
+ const onpointermove = (event: PointerEvent) => {
193
+ if (!current || event.pointerId !== pointerId) return;
194
+ // Coalesced events carry the samples between frames, so a fast stroke stays round.
195
+ const samples = event.getCoalescedEvents?.() ?? [];
196
+ const next = [...current];
197
+ for (const sample of samples.length ? samples : [event]) {
198
+ const point = pointFrom(sample);
199
+ const last = next[next.length - 1]!;
200
+ if (Math.hypot(point.x - last.x, point.y - last.y) >= 1) next.push(point);
201
+ }
202
+ current = next;
203
+ };
204
+
205
+ const onpointerup = (event: PointerEvent) => {
206
+ if (event.pointerId === pointerId) finishStroke();
207
+ };
208
+
209
+ // Delete or Backspace clears the pad, Ctrl/Cmd+Z undoes the last stroke.
210
+ const onkeydown = (event: KeyboardEvent) => {
211
+ if (!interactive) return;
212
+ if (event.key === 'Delete' || event.key === 'Backspace') {
213
+ if (!hasInk && !field.value) return;
214
+ event.preventDefault();
215
+ clear();
216
+ } else if ((event.ctrlKey || event.metaKey) && !event.shiftKey && event.key === 'z') {
217
+ if (strokes.length === 0) return;
218
+ event.preventDefault();
219
+ undo();
220
+ }
221
+ };
222
+
223
+ // A pad disabled or made read-only mid-stroke drops the stroke it was drawing.
224
+ $effect(() => {
225
+ if (interactive) return;
226
+ untrack(() => {
227
+ current = null;
228
+ pointerId = null;
229
+ });
230
+ });
231
+ </script>
232
+
233
+ <Field {field} {size} {theme} {label} labelFor={false} {...rest}>
234
+ <div data-slot="signature-frame" class={classes.frame()}>
235
+ <!-- The pad is the field's control: it takes focus and carries the label, the state and the
236
+ errors. Drawing is a pointer gesture; the buttons and the keys clear or undo it. -->
237
+ <div
238
+ bind:this={pad}
239
+ {@attach (node) => {
240
+ field.node = node;
241
+ return () => {
242
+ if (field.node === node) field.node = null;
243
+ };
244
+ }}
245
+ {id}
246
+ data-slot="signature-pad"
247
+ data-filled={field.value ? 'true' : undefined}
248
+ data-drawing={current ? 'true' : undefined}
249
+ role="img"
250
+ tabindex={field.disabled ? -1 : 0}
251
+ aria-label={label ? undefined : t.signatureInput}
252
+ aria-describedby={statusId}
253
+ aria-disabled={field.disabled || undefined}
254
+ class={classes.pad({
255
+ size,
256
+ interactive,
257
+ disabled: !!field.disabled,
258
+ hasError: field.hasError
259
+ })}
260
+ {onpointerdown}
261
+ {onpointermove}
262
+ {onpointerup}
263
+ onpointercancel={onpointerup}
264
+ onlostpointercapture={onpointerup}
265
+ {onkeydown}
266
+ onfocus={() => (field.focused = true)}
267
+ onblur={() => (field.focused = false)}
268
+ >
269
+ {#if !hasInk && !image}
270
+ <div
271
+ data-slot="signature-placeholder"
272
+ class={classes.placeholder({ size })}
273
+ aria-hidden="true"
274
+ >
275
+ <span>×</span>
276
+ <span>{placeholder ?? t.signatureInputPlaceholder}</span>
277
+ </div>
278
+ {/if}
279
+ {#if image}
280
+ <img data-slot="signature-image" src={image} alt="" class={classes.image()} />
281
+ {/if}
282
+ <svg data-slot="signature-ink" class={classes.ink()} aria-hidden="true" focusable="false">
283
+ <g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round">
284
+ {#each shapes as shape, index (index)}
285
+ {#if shape.kind === 'dot'}
286
+ <circle cx={shape.x} cy={shape.y} r={shape.r} fill="currentColor" stroke="none" />
287
+ {:else}
288
+ <path d={shape.d} stroke-width={shape.width} />
289
+ {/if}
290
+ {/each}
291
+ </g>
292
+ </svg>
293
+ <span id={statusId} class="sr-only">
294
+ {field.value ? t.signatureInputSigned : t.signatureInputEmpty}
295
+ </span>
296
+ </div>
297
+ {#if interactive && (hasInk || image)}
298
+ <div data-slot="signature-actions" class={classes.actions()}>
299
+ {#if strokes.length > 0}
300
+ <Button
301
+ variant="ghost"
302
+ color="neutral"
303
+ size="small"
304
+ squared
305
+ label={resolvedUndoLabel}
306
+ onclick={undo}
307
+ {@attach tooltip({ content: resolvedUndoLabel, position: 'top', size: 'small' })}
308
+ >
309
+ {@render arrowCounterClockwiseIcon({ size: 14 })}
310
+ </Button>
311
+ {/if}
312
+ <Button
313
+ variant="ghost"
314
+ color="neutral"
315
+ size="small"
316
+ squared
317
+ label={resolvedClearLabel}
318
+ onclick={clear}
319
+ {@attach tooltip({ content: resolvedClearLabel, position: 'top', size: 'small' })}
320
+ >
321
+ {@render eraserIcon({ size: 14 })}
322
+ </Button>
323
+ </div>
324
+ {/if}
325
+ </div>
326
+ <FieldHiddenInputs {field} value={field.value} />
327
+ </Field>
@@ -0,0 +1,4 @@
1
+ import type { SignatureInputProps } from './signatureInput.props.js';
2
+ declare const SignatureInput: import("svelte").Component<SignatureInputProps, {}, "value" | "errors" | "focused">;
3
+ type SignatureInput = ReturnType<typeof SignatureInput>;
4
+ export default SignatureInput;
@@ -0,0 +1,4 @@
1
+ export { default as SignatureInput } from './SignatureInput.svelte';
2
+ export type { SignatureInputProps } from './signatureInput.props.js';
3
+ export type { SignatureFormat, SignaturePoint, SignatureStroke } from './signatureInput.strokes.js';
4
+ export { signatureInputTheme, setSignatureInputTheme, useSignatureInputTheme, type SignatureInputTheme, type SignatureInputThemeProps } from './signatureInput.theme.js';
@@ -0,0 +1,2 @@
1
+ export { default as SignatureInput } from './SignatureInput.svelte';
2
+ export { signatureInputTheme, setSignatureInputTheme, useSignatureInputTheme } from './signatureInput.theme.js';
@@ -0,0 +1 @@
1
+ export declare const signatureInputDescription = "\n# SignatureInput Component\n\nSignatureInput is a form field where a person signs with a mouse, a finger or a pen. Its value is the signature as an image: a data URL cropped to the ink (PNG by default, or SVG), ready to store, submit or place in a document.\n\n## Basic usage\n\n```svelte\n<script lang=\"ts\">\n import { SignatureInput } from './index.ts';\n\n let signature = $state<string | null>(null);\n</script>\n\n<SignatureInput label=\"Signature\" required bind:value={signature} />\n```\n\nIn a Form or `ask()`, use `type: 'signature'`:\n\n```ts\nimport { ask } from 'entasis/ask';\n\nconst result = await ask({\n title: 'Sign the mandate',\n inputs: { signature: { type: 'signature', label: 'Signature', required: true } },\n confirm: 'Sign',\n cancel: 'Cancel'\n});\n```\n\n## Props\n\nExtends the shared Field props (`label`, `description`, `helper`, `required`, `disabled`, `readOnly`, `name`, `size`, `density`, `labelPosition`, `messages`, `onValidate`, `errors`, `focused`, `visible`) plus:\n\n- **value**: `string | null` (bindable) \u2014 the signature as a data URL, or `null` while the pad is empty.\n- **defaultValue**: `string | null` \u2014 initial value when `value` is omitted. Any image data URL works: a saved signature shows in the pad, scaled to fit.\n- **onValueChange**: `(value: string | null) => void` \u2014 called once per finished stroke, undo and clear.\n- **format**: `'png' | 'svg'` (default `'png'`) \u2014 the image the value holds. A PNG is drawn at twice the pad's resolution; an SVG keeps every stroke as a vector path.\n- **inkColor**: `string` (default `'#000000'`) \u2014 CSS color of the ink in the exported image, so a signature reads on a white document whatever the app's theme. The pad itself draws in the theme's text color.\n- **strokeWidth**: `number` (default `2.5`) \u2014 line width in CSS pixels at a medium pen pressure. A pen's pressure makes the line thinner or thicker (half to one and a half times); a mouse or a finger draws at this width.\n- **placeholder**: `string` \u2014 hint on the signing line while the pad is empty. Defaults to the i18n `signatureInputPlaceholder` (\"Sign here\").\n- **clearLabel**, **undoLabel**: `string` \u2014 accessible labels and tooltips of the two buttons.\n- **i18n**: `Partial<Messages>` \u2014 per-instance translations.\n- **size**: `'small' | 'normal' | 'large'` \u2014 pad height (112, 144 or 176 px) and placeholder text size. Override the height with the `pad` theme part.\n- **theme**: `SignatureInputThemeProps` and Field theme parts.\n\n## Behaviour\n\n- Each stroke runs from pointer down to pointer up and is smoothed through its samples. The value updates when a stroke ends, not while it is drawn.\n- The undo button (or Ctrl/Cmd+Z on the pad) removes the last stroke; the clear button (or Delete/Backspace on the pad) empties the pad and sets the value to `null`. Both show only while the pad is editable and has a signature.\n- A value set from outside (a saved signature, a Form reset) replaces the strokes and shows as an image. Drawing over it starts a new signature: the image is replaced, not added to. Undo only steps back through strokes drawn since.\n- `disabled` and `readOnly` stop drawing and hide the buttons; a read-only pad still shows, focuses, validates and submits its value. A stroke in progress is dropped when the pad becomes disabled or read-only.\n- Required: an empty pad (`null` or `''`) fails `required`.\n- Native forms: a hidden input (`FieldHiddenInputs`) submits the data URL under `name`.\n\n## Accessibility\n\n- The pad is the field's control: it is focusable, labelled by the field label (or \"Signature\" without one), and has `role=\"img\"` with a description of its state (\"No signature yet\u2026\" or \"Signed.\"). Errors and descriptions are linked by Field.\n- Drawing is a path-based gesture with no keyboard equivalent: signing by hand is the purpose of the field. Keyboard users can clear and undo with the buttons and keys above. When a keyboard-only path to consent is needed, pair the pad with another field (a typed full name, a consent checkbox).\n- The ink, placeholder and image are decorative (`aria-hidden`, empty `alt`).\n\n## Styling hooks\n\n- `data-slot`: `signature-frame`, `signature-pad`, `signature-ink`, `signature-image`, `signature-placeholder`, `signature-actions`.\n- On the pad: `data-filled=\"true\"` while it holds a signature, `data-drawing=\"true\"` during a stroke.\n\n## Theme parts\n\n`frame` (the pad and its buttons), `pad` (variants `size`, `interactive`, `disabled`, `hasError`), `ink` (the SVG of strokes), `image` (a value set from outside), `placeholder` (the signing line and hint), `actions` (the undo and clear buttons).\n\n## Exports\n\n`SignatureInput`, `SignatureInputProps`, `SignatureFormat`, `SignaturePoint`, `SignatureStroke`, `signatureInputTheme`, `setSignatureInputTheme`, `useSignatureInputTheme`, `SignatureInputTheme`, `SignatureInputThemeProps`.\n";
@@ -0,0 +1,74 @@
1
+ export const signatureInputDescription = `
2
+ # SignatureInput Component
3
+
4
+ SignatureInput is a form field where a person signs with a mouse, a finger or a pen. Its value is the signature as an image: a data URL cropped to the ink (PNG by default, or SVG), ready to store, submit or place in a document.
5
+
6
+ ## Basic usage
7
+
8
+ \`\`\`svelte
9
+ <script lang="ts">
10
+ import { SignatureInput } from './index.ts';
11
+
12
+ let signature = $state<string | null>(null);
13
+ </script>
14
+
15
+ <SignatureInput label="Signature" required bind:value={signature} />
16
+ \`\`\`
17
+
18
+ In a Form or \`ask()\`, use \`type: 'signature'\`:
19
+
20
+ \`\`\`ts
21
+ import { ask } from '../Ask/index.ts';
22
+
23
+ const result = await ask({
24
+ title: 'Sign the mandate',
25
+ inputs: { signature: { type: 'signature', label: 'Signature', required: true } },
26
+ confirm: 'Sign',
27
+ cancel: 'Cancel'
28
+ });
29
+ \`\`\`
30
+
31
+ ## Props
32
+
33
+ Extends the shared Field props (\`label\`, \`description\`, \`helper\`, \`required\`, \`disabled\`, \`readOnly\`, \`name\`, \`size\`, \`density\`, \`labelPosition\`, \`messages\`, \`onValidate\`, \`errors\`, \`focused\`, \`visible\`) plus:
34
+
35
+ - **value**: \`string | null\` (bindable) — the signature as a data URL, or \`null\` while the pad is empty.
36
+ - **defaultValue**: \`string | null\` — initial value when \`value\` is omitted. Any image data URL works: a saved signature shows in the pad, scaled to fit.
37
+ - **onValueChange**: \`(value: string | null) => void\` — called once per finished stroke, undo and clear.
38
+ - **format**: \`'png' | 'svg'\` (default \`'png'\`) — the image the value holds. A PNG is drawn at twice the pad's resolution; an SVG keeps every stroke as a vector path.
39
+ - **inkColor**: \`string\` (default \`'#000000'\`) — CSS color of the ink in the exported image, so a signature reads on a white document whatever the app's theme. The pad itself draws in the theme's text color.
40
+ - **strokeWidth**: \`number\` (default \`2.5\`) — line width in CSS pixels at a medium pen pressure. A pen's pressure makes the line thinner or thicker (half to one and a half times); a mouse or a finger draws at this width.
41
+ - **placeholder**: \`string\` — hint on the signing line while the pad is empty. Defaults to the i18n \`signatureInputPlaceholder\` ("Sign here").
42
+ - **clearLabel**, **undoLabel**: \`string\` — accessible labels and tooltips of the two buttons.
43
+ - **i18n**: \`Partial<Messages>\` — per-instance translations.
44
+ - **size**: \`'small' | 'normal' | 'large'\` — pad height (112, 144 or 176 px) and placeholder text size. Override the height with the \`pad\` theme part.
45
+ - **theme**: \`SignatureInputThemeProps\` and Field theme parts.
46
+
47
+ ## Behaviour
48
+
49
+ - Each stroke runs from pointer down to pointer up and is smoothed through its samples. The value updates when a stroke ends, not while it is drawn.
50
+ - The undo button (or Ctrl/Cmd+Z on the pad) removes the last stroke; the clear button (or Delete/Backspace on the pad) empties the pad and sets the value to \`null\`. Both show only while the pad is editable and has a signature.
51
+ - A value set from outside (a saved signature, a Form reset) replaces the strokes and shows as an image. Drawing over it starts a new signature: the image is replaced, not added to. Undo only steps back through strokes drawn since.
52
+ - \`disabled\` and \`readOnly\` stop drawing and hide the buttons; a read-only pad still shows, focuses, validates and submits its value. A stroke in progress is dropped when the pad becomes disabled or read-only.
53
+ - Required: an empty pad (\`null\` or \`''\`) fails \`required\`.
54
+ - Native forms: a hidden input (\`FieldHiddenInputs\`) submits the data URL under \`name\`.
55
+
56
+ ## Accessibility
57
+
58
+ - The pad is the field's control: it is focusable, labelled by the field label (or "Signature" without one), and has \`role="img"\` with a description of its state ("No signature yet…" or "Signed."). Errors and descriptions are linked by Field.
59
+ - Drawing is a path-based gesture with no keyboard equivalent: signing by hand is the purpose of the field. Keyboard users can clear and undo with the buttons and keys above. When a keyboard-only path to consent is needed, pair the pad with another field (a typed full name, a consent checkbox).
60
+ - The ink, placeholder and image are decorative (\`aria-hidden\`, empty \`alt\`).
61
+
62
+ ## Styling hooks
63
+
64
+ - \`data-slot\`: \`signature-frame\`, \`signature-pad\`, \`signature-ink\`, \`signature-image\`, \`signature-placeholder\`, \`signature-actions\`.
65
+ - On the pad: \`data-filled="true"\` while it holds a signature, \`data-drawing="true"\` during a stroke.
66
+
67
+ ## Theme parts
68
+
69
+ \`frame\` (the pad and its buttons), \`pad\` (variants \`size\`, \`interactive\`, \`disabled\`, \`hasError\`), \`ink\` (the SVG of strokes), \`image\` (a value set from outside), \`placeholder\` (the signing line and hint), \`actions\` (the undo and clear buttons).
70
+
71
+ ## Exports
72
+
73
+ \`SignatureInput\`, \`SignatureInputProps\`, \`SignatureFormat\`, \`SignaturePoint\`, \`SignatureStroke\`, \`signatureInputTheme\`, \`setSignatureInputTheme\`, \`useSignatureInputTheme\`, \`SignatureInputTheme\`, \`SignatureInputThemeProps\`.
74
+ `;
@@ -0,0 +1,29 @@
1
+ import type { Messages } from '../../../i18n/en.js';
2
+ import type { WithAttachments } from '../../../types/props.js';
3
+ import type { InputProps } from '../Field/field.js';
4
+ import type { SignatureFormat } from './signatureInput.strokes.js';
5
+ import type { SignatureInputThemeProps } from './signatureInput.theme.js';
6
+ export type SignatureInputProps = WithAttachments<Omit<InputProps<'signature'>, 'theme'> & {
7
+ /**
8
+ * Image the value holds, as a data URL cropped to the ink: `'png'` (default, drawn at twice
9
+ * the pad's resolution) or `'svg'`.
10
+ */
11
+ format?: SignatureFormat;
12
+ /**
13
+ * CSS color of the ink in the exported image. Defaults to black, so a signature reads on a
14
+ * white document whatever the app's theme; the pad itself draws in the theme's text color.
15
+ */
16
+ inkColor?: string;
17
+ /** Line width in CSS pixels at a medium pen pressure. Defaults to 2.5. */
18
+ strokeWidth?: number;
19
+ /** Hint on the signing line while the pad is empty. Defaults to the i18n `signatureInputPlaceholder`. */
20
+ placeholder?: string;
21
+ /** Accessible label and tooltip of the clear button. */
22
+ clearLabel?: string;
23
+ /** Accessible label and tooltip of the undo button. */
24
+ undoLabel?: string;
25
+ /** Per-instance translations merged over the global i18n catalog. */
26
+ i18n?: Partial<Messages>;
27
+ /** Theme overrides for the pad parts and the surrounding field. */
28
+ theme?: SignatureInputThemeProps & InputProps<'signature'>['theme'];
29
+ }>;