@samline/forms 2.4.0 → 2.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +8 -7
  3. package/dist/browser/global.d.ts +10 -16
  4. package/dist/browser/global.global.js +319 -49
  5. package/dist/browser/global.global.js.map +1 -1
  6. package/dist/browser/index.cjs +1309 -0
  7. package/dist/browser/index.cjs.map +1 -0
  8. package/dist/browser/index.d.cts +14 -0
  9. package/dist/browser/index.d.ts +14 -0
  10. package/dist/browser/index.js +23 -0
  11. package/dist/browser/index.js.map +1 -0
  12. package/dist/chunk-L4GRKSQ5.js +1270 -0
  13. package/dist/chunk-L4GRKSQ5.js.map +1 -0
  14. package/dist/index.cjs +134 -23
  15. package/dist/index.cjs.map +1 -1
  16. package/dist/index.d.cts +4 -158
  17. package/dist/index.d.ts +4 -158
  18. package/dist/index.js +7 -1148
  19. package/dist/index.js.map +1 -1
  20. package/dist/registry-BpqJetAt.d.cts +148 -0
  21. package/dist/registry-BpqJetAt.d.ts +148 -0
  22. package/docs/api/append.md +96 -0
  23. package/docs/api/auto-submit.md +91 -0
  24. package/docs/api/clear-errors.md +69 -0
  25. package/docs/api/destroy.md +65 -0
  26. package/docs/api/disable-auto-submit.md +65 -0
  27. package/docs/api/element.md +78 -0
  28. package/docs/api/form.md +154 -0
  29. package/docs/api/format.md +270 -0
  30. package/docs/api/get-data.md +90 -0
  31. package/docs/api/get-field.md +87 -0
  32. package/docs/api/get-state.md +95 -0
  33. package/docs/api/get-value.md +114 -0
  34. package/docs/api/index.md +54 -0
  35. package/docs/api/observe.md +126 -0
  36. package/docs/api/on-submit.md +90 -0
  37. package/docs/api/parse-form-data.md +94 -0
  38. package/docs/api/prefill.md +89 -0
  39. package/docs/api/regex.md +155 -0
  40. package/docs/api/reset.md +60 -0
  41. package/docs/api/revalidate.md +50 -0
  42. package/docs/api/set-errors.md +97 -0
  43. package/docs/api/set-value.md +120 -0
  44. package/docs/api/subscribe.md +91 -0
  45. package/docs/api/unwatch.md +74 -0
  46. package/docs/api/validate-field-value.md +108 -0
  47. package/docs/api/validate-values.md +100 -0
  48. package/docs/api/validate.md +100 -0
  49. package/docs/api/watch.md +117 -0
  50. package/docs/browser.md +156 -0
  51. package/docs/css-styling.md +187 -0
  52. package/docs/getting-started.md +217 -0
  53. package/docs/options.md +259 -0
  54. package/docs/recipes.md +477 -0
  55. package/docs/typescript.md +576 -0
  56. package/docs/vanilla.md +12 -0
  57. package/package.json +58 -14
@@ -0,0 +1,576 @@
1
+ # TypeScript Reference
2
+
3
+ This page lists every type exported from `@samline/forms`, what it represents, and where it shows up. Each type links to the method(s) that consume or produce it.
4
+
5
+ All types are exported from the package root:
6
+
7
+ ```ts
8
+ import type {
9
+ AppendContentOptions,
10
+ AutoSubmitOptions,
11
+ FieldFormatConfig,
12
+ FieldFormatConfigMap,
13
+ FieldValidationContext,
14
+ FieldValidationRules,
15
+ FieldValidator,
16
+ FormatType,
17
+ FormController,
18
+ FormControllerOptions,
19
+ FormDataPrimitive,
20
+ FormErrors,
21
+ FormFieldElement,
22
+ FormFieldValue,
23
+ FormFieldWatcher,
24
+ FormsApi,
25
+ FormsAvailable,
26
+ FormStateListener,
27
+ FormStateSnapshot,
28
+ FormSubmitHandler,
29
+ FormTarget,
30
+ FormValues,
31
+ NewFormInput,
32
+ RuleConfig,
33
+ SerializedFormResult,
34
+ SerializedFormValue,
35
+ ValidationResult,
36
+ ValidationSchema,
37
+ VisualAttributes
38
+ } from '@samline/forms'
39
+ ```
40
+
41
+ ---
42
+
43
+ ## `FormController`
44
+
45
+ The full controller surface returned by [`form()`](api/form.md). All public methods are defined here.
46
+
47
+ ```ts
48
+ interface FormController {
49
+ readonly element: HTMLFormElement | null
50
+ readonly f: HTMLFormElement | null
51
+ readonly options: FormControllerOptions
52
+ onSubmit: (callback: FormSubmitHandler, preventDefault?: boolean) => FormController
53
+ watch: (field: string, callback: FormFieldWatcher) => FormController
54
+ observe: (field: string, callback: FormFieldWatcher) => () => void
55
+ unwatch: (field?: string, callback?: FormFieldWatcher) => FormController
56
+ subscribe: (listener: FormStateListener) => () => void
57
+ prefill: (fieldName?: string) => FormController
58
+ append: (options: AppendContentOptions) => HTMLElement | null
59
+ setErrors: (fields: string[] | FormErrors) => FormController
60
+ clearErrors: (fields?: string[]) => FormController
61
+ setValue: (name: string, value: unknown) => FormController
62
+ validate: (fields?: string[]) => ValidationResult
63
+ revalidate: (fields?: string[]) => ValidationResult
64
+ /**
65
+ * Apply the same `@samline/formatter` pipeline to a single field or
66
+ * to a list of fields sharing one configuration. Chainable.
67
+ *
68
+ * Requires the optional peer dependency `@samline/formatter`. When
69
+ * the peer is not installed the call logs a single `console.error`
70
+ * and returns the controller unchanged.
71
+ */
72
+ format: (config: FieldFormatConfig) => FormController
73
+ formatAll: (config: FieldFormatConfig) => FormController
74
+ reset: () => FormController
75
+ autoSubmit: (options?: boolean | AutoSubmitOptions) => FormController
76
+ disableAutoSubmit: () => FormController
77
+ getValue: (name: string) => FormFieldValue
78
+ getField: (name: string) => FormFieldElement | FormFieldElement[] | null
79
+ getData: () => SerializedFormResult
80
+ getState: () => FormStateSnapshot
81
+ destroy: () => void
82
+ }
83
+ ```
84
+
85
+ ---
86
+
87
+ ## `FormControllerOptions`
88
+
89
+ The second argument to [`form()`](api/form.md). See [docs/options.md](options.md) for the full reference.
90
+
91
+ ```ts
92
+ interface FormControllerOptions {
93
+ attributes?: Partial<VisualAttributes>
94
+ autoValidate?: boolean
95
+ autoSubmit?: boolean | AutoSubmitOptions
96
+ clearManualErrorsOnChange?: boolean
97
+ clearErrorsOnSubmit?: boolean
98
+ validators?: ValidationSchema
99
+ /**
100
+ * Declarative format configuration. Each entry is applied during
101
+ * `form()` initialization using the same logic as `controller.format(...)`.
102
+ * The key is just an identifier — the canonical field name lives in
103
+ * `FieldFormatConfig.field`.
104
+ */
105
+ formats?: FieldFormatConfigMap
106
+ }
107
+ ```
108
+
109
+ ---
110
+
111
+ ## `FormTarget`
112
+
113
+ What [`form()`](api/form.md) accepts as its first argument.
114
+
115
+ ```ts
116
+ type FormTarget =
117
+ | string // element id
118
+ | HTMLFormElement // direct element
119
+ | { current: HTMLFormElement | null } // ref-like
120
+ | null
121
+ | undefined
122
+ ```
123
+
124
+ A string is treated as a `document.getElementById(id)` lookup; only `HTMLFormElement` matches count. A ref-like value lets you pass a Vue/React-style ref object.
125
+
126
+ ---
127
+
128
+ ## `FormFieldElement`
129
+
130
+ The set of DOM field types the controller manages.
131
+
132
+ ```ts
133
+ type FormFieldElement =
134
+ | HTMLInputElement
135
+ | HTMLSelectElement
136
+ | HTMLTextAreaElement
137
+ ```
138
+
139
+ Returned by [`getField`](api/get-field.md).
140
+
141
+ ---
142
+
143
+ ## `FormFieldValue`
144
+
145
+ The normalized value of a field.
146
+
147
+ ```ts
148
+ type FormFieldValue = string | string[] | File[] | undefined
149
+ ```
150
+
151
+ - `string` — for text inputs, textareas, single selects, and single checked checkboxes / radios.
152
+ - `string[]` — for checkbox groups, multiple selects, and repeated `name="field[]"` controls. Radio groups remain scalar.
153
+ - `File[]` — for `<input type="file">` (may be empty).
154
+ - `undefined` — when no field with that name exists.
155
+
156
+ Returned by [`getValue`](api/get-value.md).
157
+
158
+ ---
159
+
160
+ ## `FormValues`
161
+
162
+ The aggregated values map produced by the controller and the helpers.
163
+
164
+ ```ts
165
+ type FormValues = Record<string, FormFieldValue>
166
+ ```
167
+
168
+ Useful when you build custom validators that need cross-field logic beyond exact equality (`sameAs` handles equality directly):
169
+
170
+ ```ts
171
+ form('booking-form', {
172
+ validators: {
173
+ end_date: {
174
+ validate: ({ value, values }) =>
175
+ typeof value === 'string' &&
176
+ typeof values.start_date === 'string' &&
177
+ value >= values.start_date
178
+ ? null
179
+ : 'End date must not precede start date.'
180
+ }
181
+ }
182
+ })
183
+ ```
184
+
185
+ The controller can infer reactive dependencies from `sameAs`, but not from arbitrary reads inside `validate`. If `start_date` changes after `end_date` has been validated, explicitly revalidate `end_date` or watch the source field.
186
+
187
+ ---
188
+
189
+ ## `FormErrors`
190
+
191
+ A map from field name to an array of error messages. Each field can have multiple errors (one per failed rule or per failing custom validator).
192
+
193
+ ```ts
194
+ type FormErrors = Record<string, string[]>
195
+ ```
196
+
197
+ Drives the `css-error` attribute on fields and is returned inside [`FormStateSnapshot`](#formstatesnapshot) and [`ValidationResult`](#validationresult).
198
+
199
+ ---
200
+
201
+ ## `FormStateSnapshot`
202
+
203
+ The shape returned by [`getState()`](api/get-state.md). Built fresh on every call — it does not retain references to controller internals.
204
+
205
+ ```ts
206
+ interface FormStateSnapshot {
207
+ values: FormValues
208
+ errors: FormErrors
209
+ filledFields: string[]
210
+ isValid: boolean
211
+ isValidated: boolean
212
+ autoSubmit: boolean
213
+ submitCount: number
214
+ }
215
+ ```
216
+
217
+ | Field | Meaning |
218
+ | --- | --- |
219
+ | `values` | Current values for every tracked field. |
220
+ | `errors` | Merged validation and manual errors. |
221
+ | `filledFields` | Names of fields that have a non-empty value. |
222
+ | `isValid` | `true` when `errors` has no entries. |
223
+ | `isValidated` | `true` once [`validate`](api/validate.md) has run at least once. |
224
+ | `autoSubmit` | `true` while auto-submit is enabled. |
225
+ | `submitCount` | Number of submit attempts (valid or invalid). |
226
+
227
+ ---
228
+
229
+ ## `FormStateListener`
230
+
231
+ The callback passed to [`subscribe`](api/subscribe.md).
232
+
233
+ ```ts
234
+ type FormStateListener = (state: FormStateSnapshot) => void
235
+ ```
236
+
237
+ Receives the current snapshot immediately, then at controller notification points such as handled input, manual-error changes, reset, submit attempts, auto-submit toggles, and observed DOM mutations. A direct `validate()` call does not independently notify it.
238
+
239
+ ---
240
+
241
+ ## `FormSubmitHandler`
242
+
243
+ The callback passed to [`onSubmit`](api/on-submit.md).
244
+
245
+ ```ts
246
+ type FormSubmitHandler = (
247
+ form: HTMLFormElement,
248
+ data: Record<string, SerializedFormValue>,
249
+ formData: FormData,
250
+ state: FormStateSnapshot
251
+ ) => void
252
+ ```
253
+
254
+ Only invoked when the form is valid. `data` and `formData` are produced fresh on each invocation.
255
+
256
+ ---
257
+
258
+ ## `FormFieldWatcher`
259
+
260
+ The callback passed to [`watch`](api/watch.md) and [`observe`](api/observe.md).
261
+
262
+ ```ts
263
+ type FormFieldWatcher = (
264
+ value: FormFieldValue,
265
+ field: FormFieldElement | FormFieldElement[] | null,
266
+ form: HTMLFormElement,
267
+ state: FormStateSnapshot
268
+ ) => void
269
+ ```
270
+
271
+ - `value` — the current value of the field that just changed.
272
+ - `field` — the DOM element(s) backing the field (same shape as [`getField`](api/get-field.md) returns). For a single matching field it is `HTMLInputElement` / `HTMLSelectElement` / `HTMLTextAreaElement`; for repeated fields (e.g. checkbox / radio groups) it is an array of them; it is `null` when no element matches the name.
273
+ - `form` — the bound `HTMLFormElement`.
274
+ - `state` — a snapshot of the whole controller state (see [`FormStateSnapshot`](#formstatesnapshot)).
275
+
276
+ `observe` and `watch` fire once immediately with the current value, then on every matching `input` event. `observe` returns an unsubscribe function; `watch` returns the controller.
277
+
278
+ ---
279
+
280
+ ## `SerializedFormResult`
281
+
282
+ The shape returned by [`getData()`](api/get-data.md) and [`parseFormData()`](api/parse-form-data.md).
283
+
284
+ ```ts
285
+ interface SerializedFormResult {
286
+ data: Record<string, SerializedFormValue>
287
+ formData: FormData
288
+ }
289
+ ```
290
+
291
+ `data` is a plain object mirror of the `FormData`. Repeated names become arrays (e.g. `interests: ['design', 'code']`). Empty file entries are filtered out.
292
+
293
+ ---
294
+
295
+ ## `SerializedFormValue`
296
+
297
+ The value shape inside `SerializedFormResult.data`.
298
+
299
+ ```ts
300
+ type SerializedFormValue = FormDataPrimitive | FormDataPrimitive[]
301
+
302
+ type FormDataPrimitive = FormDataEntryValue
303
+ ```
304
+
305
+ `FormDataEntryValue` is `string | File` in the DOM lib types.
306
+
307
+ ---
308
+
309
+ ## `ValidationResult`
310
+
311
+ The shape returned by [`validate`](api/validate.md), [`revalidate`](api/revalidate.md), and [`validateValues`](api/validate-values.md).
312
+
313
+ ```ts
314
+ interface ValidationResult {
315
+ isValid: boolean
316
+ errors: FormErrors
317
+ }
318
+ ```
319
+
320
+ `errors` is always a fresh object — safe to mutate.
321
+
322
+ ---
323
+
324
+ ## `ValidationSchema`
325
+
326
+ The shape of `options.validators`.
327
+
328
+ ```ts
329
+ type ValidationSchema = Record<string, FieldValidationRules>
330
+ ```
331
+
332
+ ```ts
333
+ form('signup-form', {
334
+ validators: {
335
+ email: { required: true, pattern: /^[^\s@]+@[^\s@]+\.[^\s@]+$/ },
336
+ password: { required: true, minLength: 8 }
337
+ }
338
+ })
339
+ ```
340
+
341
+ ---
342
+
343
+ ## `FieldValidationRules`
344
+
345
+ The rule set for a single field.
346
+
347
+ ```ts
348
+ interface FieldValidationRules {
349
+ required?: RuleConfig<boolean>
350
+ minLength?: RuleConfig<number>
351
+ maxLength?: RuleConfig<number>
352
+ pattern?: RuleConfig<RegExp>
353
+ sameAs?: RuleConfig<string>
354
+ validate?: FieldValidator | FieldValidator[]
355
+ }
356
+ ```
357
+
358
+ Rules run in the order: `required` → `minLength` → `maxLength` → `pattern` → `sameAs` → `validate`. All are optional; an empty rules object contributes nothing.
359
+
360
+ `sameAs` names another exact field key. It compares non-empty strings exactly and arrays by ordered contents; file entries compare by `File` object identity. In a controller, changing the referenced field automatically revalidates the field that declares `sameAs`. Reciprocal declarations are cycle-safe, but usually duplicate the same error on both controls; prefer declaring the rule only on the confirmation field.
361
+
362
+ ---
363
+
364
+ ## `RuleConfig<T>`
365
+
366
+ Lets a rule be configured with a plain value or a `{ value, message }` object.
367
+
368
+ ```ts
369
+ type RuleConfig<T> = T | { value: T; message?: string }
370
+ ```
371
+
372
+ ```ts
373
+ form('signup-form', {
374
+ validators: {
375
+ password: {
376
+ minLength: { value: 8, message: 'Use at least 8 characters.' }
377
+ },
378
+ terms: {
379
+ required: { value: true, message: 'Please accept the terms.' }
380
+ }
381
+ }
382
+ })
383
+ ```
384
+
385
+ When the plain form is used, default messages are produced automatically.
386
+
387
+ ---
388
+
389
+ ## `FieldValidationContext`
390
+
391
+ The argument passed to a custom validator.
392
+
393
+ ```ts
394
+ interface FieldValidationContext {
395
+ field: string
396
+ value: FormFieldValue
397
+ values: FormValues
398
+ }
399
+ ```
400
+
401
+ Use it to write cross-field validators:
402
+
403
+ ```ts
404
+ form('checkout-form', {
405
+ validators: {
406
+ card: {
407
+ validate: ({ value, field }) => {
408
+ if (typeof value !== 'string') return `${field} is required.`
409
+ return /^\d{16}$/.test(value.replace(/\s+/g, ''))
410
+ ? null
411
+ : 'Card number must be 16 digits.'
412
+ }
413
+ }
414
+ }
415
+ })
416
+ ```
417
+
418
+ ---
419
+
420
+ ## `FieldValidator`
421
+
422
+ The custom validator signature.
423
+
424
+ ```ts
425
+ type FieldValidator = (
426
+ context: FieldValidationContext
427
+ ) => string | undefined | null | false | true
428
+ ```
429
+
430
+ | Return | Meaning |
431
+ | --- | --- |
432
+ | `string` (non-empty) | Push as the error message. |
433
+ | `undefined`, `null`, `true` | Pass. |
434
+ | `false` | Push the generic error message `"Validation failed."`. |
435
+
436
+ Multiple validators can be chained by passing an array as `validate`:
437
+
438
+ ```ts
439
+ form('order-form', {
440
+ validators: {
441
+ quantity: {
442
+ validate: [
443
+ ({ value }) => (typeof value === 'string' && Number(value) > 0 ? null : 'Must be greater than zero.'),
444
+ ({ value }) => (typeof value === 'string' && Number.isInteger(Number(value)) ? null : 'Must be an integer.')
445
+ ]
446
+ }
447
+ }
448
+ })
449
+ ```
450
+
451
+ ---
452
+
453
+ ## `AutoSubmitOptions`
454
+
455
+ The argument to [`autoSubmit()`](api/auto-submit.md) and the `autoSubmit` option.
456
+
457
+ ```ts
458
+ interface AutoSubmitOptions {
459
+ debounce?: number
460
+ }
461
+ ```
462
+
463
+ `debounce` is in milliseconds.
464
+
465
+ ---
466
+
467
+ ## `AppendContentOptions`
468
+
469
+ The argument to [`append()`](api/append.md).
470
+
471
+ ```ts
472
+ interface AppendContentOptions {
473
+ tag: keyof HTMLElementTagNameMap
474
+ content: string
475
+ class?: string
476
+ atStart?: boolean
477
+ }
478
+ ```
479
+
480
+ | Field | Meaning |
481
+ | --- | --- |
482
+ | `tag` | The HTML tag to create. |
483
+ | `content` | `innerHTML` content for the new node. |
484
+ | `class` | Optional class name. If a node with the same first class already exists inside the form, it is removed before the new one is inserted. |
485
+ | `atStart` | When `true`, inserts at the start of the form. Defaults to `false` (append at the end). |
486
+
487
+ ---
488
+
489
+ ## `VisualAttributes`
490
+
491
+ The shape of `options.attributes`.
492
+
493
+ ```ts
494
+ interface VisualAttributes {
495
+ filled: string
496
+ error: string
497
+ }
498
+ ```
499
+
500
+ `Partial<VisualAttributes>` is accepted, so you can override only one of them. See [docs/css-styling.md](css-styling.md).
501
+
502
+ ---
503
+
504
+ ## `FieldFormatConfig` and `FieldFormatConfigMap`
505
+
506
+ The argument to [`format()`](api/format.md) and [`formatAll()`](api/format.md). The map is the shape of `options.formats`.
507
+
508
+ ```ts
509
+ type FormatType =
510
+ | 'general'
511
+ | 'phone'
512
+ | 'numeral'
513
+ | 'date'
514
+ | 'time'
515
+ | 'creditCard'
516
+ | 'creditCardType'
517
+
518
+ interface FieldFormatConfig {
519
+ type: FormatType
520
+ field: string | string[]
521
+ displayField?: string
522
+ options?: Record<string, unknown>
523
+ }
524
+
525
+ type FieldFormatConfigMap = Record<string, FieldFormatConfig>
526
+ ```
527
+
528
+ The `field` name is the **canonical** name of the formatted pair. A custom `displayField` is valid only for one string field; passing it with a field array logs an error and leaves the form unchanged. Array configurations derive `${fieldName}_displayed` independently for every field. Every map value includes its own `field` because the map key is only an identifier. See [The mirror convention](api/format.md#the-mirror-convention).
529
+
530
+ ---
531
+
532
+ ## `FormsApi`
533
+
534
+ The shape of the [`browser`](../getting-started.md#browser-registry-helpers) singleton exported from the vanilla entrypoint. The IIFE bundle exposes the same shape as `window.Forms`.
535
+
536
+ ```ts
537
+ interface FormsApi {
538
+ form: (target: FormTarget, options?: FormControllerOptions) => FormController
539
+ newForm: (input: NewFormInput) => FormController | undefined
540
+ destroyForm: (id: string) => void
541
+ available: FormsAvailable
542
+ }
543
+ ```
544
+
545
+ `form` is the same reference exported from `@samline/forms`. `newForm` and `destroyForm` manage the module-level `available` registry keyed by the form id. Spread `browser` into a new object to derive an independent registry (with its own `available` map); see the getting-started guide for the pattern.
546
+
547
+ ---
548
+
549
+ ## `NewFormInput`
550
+
551
+ The argument to `FormsApi.newForm`.
552
+
553
+ ```ts
554
+ interface NewFormInput {
555
+ id: string
556
+ options?: FormControllerOptions
557
+ }
558
+ ```
559
+
560
+ A missing `id` logs `Form ID is required` to `console.error` and `newForm` returns `undefined` without touching the registry.
561
+
562
+ ---
563
+
564
+ ## `FormsAvailable`
565
+
566
+ The shape of `FormsApi.available`.
567
+
568
+ ```ts
569
+ interface FormsAvailable {
570
+ [id: string]: FormController
571
+ }
572
+ ```
573
+
574
+ Use it to inspect or iterate over every live controller in a registry. Each entry is the `FormController` returned by `newForm` for that id.
575
+
576
+ `Partial<VisualAttributes>` is accepted, so you can override only one of them. See [docs/css-styling.md](css-styling.md).
@@ -0,0 +1,12 @@
1
+ # Vanilla
2
+
3
+ The vanilla documentation has been reorganised into a modular tree. Start here:
4
+
5
+ - [Getting started](getting-started.md) — concepts, observable contract, lifecycle, and the side-effect overview.
6
+ - [Options reference](options.md) — every `FormControllerOptions` field with defaults.
7
+ - [CSS styling](css-styling.md) — `css-filled` / `css-error` recipes and accessibility notes.
8
+ - [TypeScript reference](typescript.md) — every exported type, with examples.
9
+ - [API reference](api/index.md) — one page per public method.
10
+ - [Recipes](recipes.md) — end-to-end patterns.
11
+
12
+ If you arrived here from an older link, the equivalent of the previous one-page vanilla guide is now [docs/getting-started.md](getting-started.md).
package/package.json CHANGED
@@ -1,10 +1,25 @@
1
1
  {
2
2
  "name": "@samline/forms",
3
- "version": "2.4.0",
4
- "description": "Form controller for vanilla JS and direct browser usage.",
3
+ "version": "2.6.0",
4
+ "description": "TypeScript-first, framework-free form controller with validation, reactive state, serialization, auto-submit, and input formatting.",
5
+ "keywords": [
6
+ "forms",
7
+ "form-validation",
8
+ "form-state",
9
+ "typescript",
10
+ "vanilla-javascript",
11
+ "browser"
12
+ ],
5
13
  "type": "module",
6
14
  "license": "MIT",
7
15
  "homepage": "https://samline.github.io/forms",
16
+ "repository": {
17
+ "type": "git",
18
+ "url": "git+https://github.com/samline/forms.git"
19
+ },
20
+ "bugs": {
21
+ "url": "https://github.com/samline/forms/issues"
22
+ },
8
23
  "publishConfig": {
9
24
  "access": "public",
10
25
  "tag": "latest"
@@ -12,21 +27,40 @@
12
27
  "engines": {
13
28
  "node": ">=20"
14
29
  },
15
- "sideEffects": false,
30
+ "sideEffects": [
31
+ "./dist/browser/index.js",
32
+ "./dist/browser/index.cjs",
33
+ "./dist/browser/global.global.js"
34
+ ],
16
35
  "files": [
17
- "dist"
36
+ "dist",
37
+ "docs"
18
38
  ],
19
39
  "main": "./dist/index.cjs",
20
40
  "module": "./dist/index.js",
21
41
  "types": "./dist/index.d.ts",
22
42
  "exports": {
23
43
  ".": {
24
- "types": "./dist/index.d.ts",
25
- "import": "./dist/index.js",
26
- "require": "./dist/index.cjs"
44
+ "import": {
45
+ "types": "./dist/index.d.ts",
46
+ "default": "./dist/index.js"
47
+ },
48
+ "require": {
49
+ "types": "./dist/index.d.cts",
50
+ "default": "./dist/index.cjs"
51
+ }
27
52
  },
28
53
  "./browser": {
29
- "types": "./dist/browser/global.d.ts",
54
+ "import": {
55
+ "types": "./dist/browser/index.d.ts",
56
+ "default": "./dist/browser/index.js"
57
+ },
58
+ "require": {
59
+ "types": "./dist/browser/index.d.cts",
60
+ "default": "./dist/browser/index.cjs"
61
+ }
62
+ },
63
+ "./browser/global": {
30
64
  "default": "./dist/browser/global.global.js"
31
65
  }
32
66
  },
@@ -37,8 +71,10 @@
37
71
  "pack:check": "npm pack --dry-run",
38
72
  "typecheck": "tsc --noEmit",
39
73
  "test": "vitest run",
74
+ "test:coverage": "vitest run --coverage",
75
+ "test:package": "node scripts/test-package.mjs && tsc -p tsconfig.package.json",
40
76
  "test:watch": "vitest",
41
- "prepublishOnly": "npm run clean && npm run build && npm run typecheck && npm test"
77
+ "prepublishOnly": "npm run clean && npm run build && npm run typecheck && npm test && npm run test:package && npm run pack:check"
42
78
  },
43
79
  "peerDependencies": {
44
80
  "@samline/formatter": "^2.0.0"
@@ -48,12 +84,20 @@
48
84
  "optional": true
49
85
  }
50
86
  },
87
+ "overrides": {
88
+ "esbuild": "^0.28.1",
89
+ "nanoid": "^3.3.18",
90
+ "postcss": "^8.5.23",
91
+ "vite": "^8.0.0"
92
+ },
51
93
  "devDependencies": {
52
94
  "@samline/formatter": "^2.0.0",
53
- "@types/node": "^24.0.0",
54
- "jsdom": "^26.1.0",
55
- "tsup": "^8.5.0",
56
- "typescript": "^5.8.3",
57
- "vitest": "^3.1.1"
95
+ "@types/node": "^26.6.2",
96
+ "@vitest/coverage-v8": "^5.0.1",
97
+ "jsdom": "^30.1.0",
98
+ "tsup": "^8.5.1",
99
+ "typescript": "^5.9.3",
100
+ "vite": "^8.0.0",
101
+ "vitest": "^5.0.1"
58
102
  }
59
103
  }