@featherk/composables 0.8.1 → 0.9.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.
@@ -0,0 +1,150 @@
1
+ # useFieldsetValidationKit
2
+
3
+ [← Back to Composables README](https://github.com/NantHealth/featherk/blob/integration/packages/composables/README.md)
4
+
5
+ Form orchestration composable for delayed fieldset validation UX.
6
+
7
+ `useFieldsetValidationKit` composes `useFieldsetTouchTracker` and provides a kit-oriented API for:
8
+
9
+ - delayed validation gate (`submitted || touched`)
10
+ - field-level delayed validity wrappers
11
+ - first-invalid focus targeting
12
+ - reset behavior that avoids immediate re-trigger from reset-related focus transitions
13
+
14
+ > `focusFirstInvalidField` is currently included as a convenience helper. It is
15
+ > form-scoped rather than fieldset-scoped, so it does not cleanly belong to this
16
+ > kit and is expected to move to a separate utility in a future release.
17
+
18
+ ## Prerequisites
19
+
20
+ - Vue 3 Composition API
21
+
22
+ ## Quick Start
23
+
24
+ ```vue
25
+ <script setup lang="ts">
26
+ import { computed, ref } from "vue";
27
+ import { useFieldsetValidationKit } from "@featherk/composables/form";
28
+
29
+ const fieldsetRef = ref<HTMLFieldSetElement | null>(null);
30
+ const formRef = ref<HTMLFormElement | null>(null);
31
+
32
+ const submitted = ref(false);
33
+
34
+ const firstName = ref("");
35
+ const city = ref("");
36
+
37
+ const firstNameValidRule = computed(() => firstName.value.trim().length >= 2);
38
+ const cityValidRule = computed(() => city.value.trim().length >= 2);
39
+
40
+ const isFieldsetValid = computed(
41
+ () => firstNameValidRule.value && cityValidRule.value,
42
+ );
43
+
44
+ const kit = useFieldsetValidationKit(fieldsetRef, {
45
+ submitted,
46
+ isValid: () => isFieldsetValid.value,
47
+ });
48
+
49
+ const { touched, fieldsetValidate, onFocusout, reset, focusFirstInvalidField } =
50
+ kit;
51
+
52
+ const fieldValidations = kit.createFieldValidationsMap({
53
+ firstName: () => firstNameValidRule.value,
54
+ city: () => cityValidRule.value,
55
+ });
56
+
57
+ // fieldValidations.firstName and fieldValidations.city are ComputedRef<boolean>
58
+ // values that stay true until fieldset validation is active.
59
+
60
+ function onSubmit() {
61
+ submitted.value = true;
62
+ if (!isFieldsetValid.value) {
63
+ focusFirstInvalidField(formRef);
64
+ }
65
+ }
66
+
67
+ function onReset() {
68
+ submitted.value = false;
69
+ reset();
70
+ }
71
+ </script>
72
+
73
+ <template>
74
+ <form ref="formRef" @submit.prevent="onSubmit" @reset.prevent="onReset">
75
+ <fieldset ref="fieldsetRef" @focusout="onFocusout">
76
+ <!-- controls bind to fieldsetValidate + fieldValidations -->
77
+ </fieldset>
78
+ </form>
79
+
80
+ <p>Touched: {{ touched }}</p>
81
+ <p>Validate: {{ fieldsetValidate }}</p>
82
+ </template>
83
+ ```
84
+
85
+ ## What createFieldValidationsMap Returns
86
+
87
+ You pass one object argument whose properties are the field rules:
88
+
89
+ ```ts
90
+ const rules = {
91
+ firstName: () => firstNameValidRule.value,
92
+ city: () => cityValidRule.value,
93
+ };
94
+ ```
95
+
96
+ And the kit returns the same shape, but each field becomes a delayed
97
+ `ComputedRef<boolean>`:
98
+
99
+ ```ts
100
+ const fieldValidations = kit.createFieldValidationsMap(rules);
101
+
102
+ fieldValidations.firstName.value;
103
+ fieldValidations.city.value;
104
+ ```
105
+
106
+ This is important because it lets you define field rules once, keep the field
107
+ names intact, and apply the same delayed-validation gate consistently across
108
+ the whole fieldset.
109
+
110
+ ## API
111
+
112
+ ### useFieldsetValidationKit(fieldsetRef, options)
113
+
114
+ Creates delayed-validation behavior for a fieldset.
115
+
116
+ #### Options
117
+
118
+ | Option | Type | Required | Description |
119
+ |--------|------|----------|-------------|
120
+ | `submitted` | `MaybeRefOrGetter<boolean>` | Yes | Consumer-owned submit state. Never mutated by the kit. |
121
+ | `isValid` | `() => boolean` | Yes | Returns the entire fieldset's current true validity. |
122
+
123
+ #### Returns
124
+
125
+ | Property | Type | Description |
126
+ |----------|------|-------------|
127
+ | `touched` | `Ref<boolean>` | Becomes `true` once focus leaves the fieldset. |
128
+ | `fieldsetValidate` | `ComputedRef<boolean>` | Delayed validation gate: `submitted || touched`. |
129
+ | `onFocusout` | `(event: FocusEvent) => void` | Bind to fieldset `focusout`. Handles touch tracking. |
130
+ | `reset` | `() => void` | Clears touch state and suppresses one immediate post-reset focusout. |
131
+ | `createFieldValidity` | `(isValid: MaybeRefOrGetter<boolean>) => ComputedRef<boolean>` | Wraps a field rule with delayed-validation gate behavior. |
132
+ | `createFieldValidationsMap` | `<T>(isValidMap: T) => { [K in keyof T]: ComputedRef<boolean> }` | Accepts one object argument of named field rules and returns the same shape with delayed `ComputedRef<boolean>` values. |
133
+ | `focusFirstInvalidField` | `(formRef: MaybeRefOrGetter<HTMLFormElement \| null>) => void` | Convenience helper that focuses the first invalid Kendo control in the form using `.k-invalid` selectors. This is expected to move out of the kit in a future release. |
134
+
135
+ ## Behavior Notes
136
+
137
+ - Validation visibility is inactive until either:
138
+ - consumer sets `submitted = true`, or
139
+ - focus leaves the fieldset and marks `touched = true`.
140
+ - `createFieldValidity` and `createFieldValidationsMap` return `true` while gated off, then expose real rule results once gated on.
141
+ - `reset()` intentionally suppresses one immediate post-reset focusout to avoid reset-click focus churn re-arming validation immediately.
142
+ - `reset()` also resets the underlying touch tracker to keep kit and tracker state synchronized.
143
+ - `focusFirstInvalidField()` is available for now as a convenience, but it is a
144
+ form-level concern and not a natural fieldset-kit responsibility.
145
+
146
+ ## Recommended Usage
147
+
148
+ For app forms, prefer this composable over `useFieldsetTouchTracker` directly.
149
+
150
+ Use the touch tracker directly only when you need custom orchestration not covered by this kit.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@featherk/composables",
3
- "version": "0.8.1",
3
+ "version": "0.9.0",
4
4
  "main": "dist/featherk-composables.umd.js",
5
5
  "module": "dist/featherk-composables.es.js",
6
6
  "types": "dist/index.d.ts",
@@ -35,6 +35,11 @@
35
35
  "import": "./dist/featherk-composables.es.js",
36
36
  "require": "./dist/featherk-composables.umd.js"
37
37
  },
38
+ "./form": {
39
+ "types": "./dist/form/index.d.ts",
40
+ "import": "./dist/featherk-composables.es.js",
41
+ "require": "./dist/featherk-composables.umd.js"
42
+ },
38
43
  "./address": {
39
44
  "types": "./dist/address/index.d.ts",
40
45
  "import": "./dist/featherk-composables.es.js",
@@ -49,7 +54,7 @@
49
54
  "build": "vite build && npm run build:types",
50
55
  "build:types": "vue-tsc --emitDeclarationOnly --declaration --declarationDir dist",
51
56
  "test": "vitest --config vitest.config.ts",
52
- "test:run": "vitest run --config vitest.config.ts",
57
+ "test:unit": "vitest run --config vitest.config.ts",
53
58
  "test:coverage": "vitest run --config vitest.config.ts --coverage",
54
59
  "clean": "rm -rf dist",
55
60
  "prepublishOnly": "npm run build"