@sfxcode/nuxt-ui-formkit 1.2.0 → 1.2.2
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 +25 -0
- package/dist/module.json +1 -1
- package/dist/module.mjs +1 -1
- package/dist/runtime/components/FUAutoForm.d.vue.ts +65 -0
- package/dist/runtime/components/FUAutoForm.vue +59 -0
- package/dist/runtime/components/FUAutoForm.vue.d.ts +65 -0
- package/dist/runtime/components/FUDataEdit.d.vue.ts +1 -1
- package/dist/runtime/components/FUDataEdit.vue.d.ts +1 -1
- package/dist/runtime/components/FUDataView.d.vue.ts +1 -1
- package/dist/runtime/components/FUDataView.vue.d.ts +1 -1
- package/dist/runtime/components/inputs/FUCalendar.d.vue.ts +15 -5
- package/dist/runtime/components/inputs/FUCalendar.vue +16 -4
- package/dist/runtime/components/inputs/FUCalendar.vue.d.ts +15 -5
- package/dist/runtime/components/inputs/FUInputDate.d.vue.ts +18 -5
- package/dist/runtime/components/inputs/FUInputDate.vue +24 -6
- package/dist/runtime/components/inputs/FUInputDate.vue.d.ts +18 -5
- package/dist/runtime/composables/useFormKitAutoForm.d.ts +38 -0
- package/dist/runtime/composables/useFormKitAutoForm.js +349 -0
- package/dist/runtime/composables/useFormKitForm.d.ts +11 -0
- package/dist/runtime/composables/useFormKitForm.js +38 -0
- package/dist/runtime/composables/useFormKitMultiStep.d.ts +5 -0
- package/dist/runtime/composables/useFormKitMultiStep.js +65 -0
- package/dist/runtime/composables/useFormKitRepeater.d.ts +4 -0
- package/dist/runtime/composables/useFormKitRepeater.js +138 -0
- package/dist/runtime/formkit/createNuxtUiFormkitConfig.d.ts +19 -0
- package/dist/runtime/formkit/createNuxtUiFormkitConfig.js +12 -0
- package/dist/runtime/formkit/createNuxtUiFormkitConfig.mjs +12 -0
- package/dist/runtime/formkit/definitions/index.d.ts +3 -0
- package/dist/runtime/formkit/definitions/index.js +4 -0
- package/dist/runtime/formkit/definitions/index.mjs +4 -0
- package/dist/runtime/formkit/definitions/input.js +2 -2
- package/dist/runtime/formkit/definitions/input.mjs +2 -2
- package/dist/runtime/formkit/definitions/multiStep.d.ts +3 -0
- package/dist/runtime/formkit/definitions/multiStep.js +90 -0
- package/dist/runtime/formkit/definitions/multiStep.mjs +90 -0
- package/dist/runtime/formkit/definitions/repeater.js +3 -119
- package/dist/runtime/formkit/definitions/repeater.mjs +3 -119
- package/dist/runtime/formkit/index.d.ts +10 -0
- package/dist/runtime/formkit/index.js +1 -0
- package/dist/runtime/formkit/index.mjs +1 -0
- package/dist/runtime/formkit/plugins/index.d.ts +1 -0
- package/dist/runtime/formkit/plugins/index.js +1 -0
- package/dist/runtime/formkit/plugins/index.mjs +1 -0
- package/dist/runtime/plugin.js +2 -1
- package/dist/runtime/utils/dateValueConversion.d.ts +36 -0
- package/dist/runtime/utils/dateValueConversion.js +80 -0
- package/package.json +6 -3
|
@@ -0,0 +1,349 @@
|
|
|
1
|
+
import { isDateValue } from "../utils/dateValueConversion.js";
|
|
2
|
+
const REPEATER_BUTTON_DEFAULTS = {
|
|
3
|
+
displayCloneButton: true,
|
|
4
|
+
displayDeleteButton: true,
|
|
5
|
+
buttonGroupClass: "flex gap-2 justify-end"
|
|
6
|
+
};
|
|
7
|
+
const ISO_DATE_PATTERN = /^\d{4}-\d{2}-\d{2}(?:[T ]\d{2}:\d{2}(?::\d{2}(?:\.\d+)?)?(?:Z|[+-]\d{2}:?\d{2})?)?$/;
|
|
8
|
+
function isIsoDateString(value) {
|
|
9
|
+
return ISO_DATE_PATTERN.test(value) && !Number.isNaN(Date.parse(value));
|
|
10
|
+
}
|
|
11
|
+
function humanizeKey(key) {
|
|
12
|
+
return key.replace(/[_-]+/g, " ").replace(/([a-z\d])([A-Z])/g, "$1 $2").split(" ").filter(Boolean).map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
|
|
13
|
+
}
|
|
14
|
+
function isPlainObject(value) {
|
|
15
|
+
if (typeof value !== "object" || value === null) {
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
18
|
+
const prototype = Object.getPrototypeOf(value);
|
|
19
|
+
return prototype === Object.prototype || prototype === null;
|
|
20
|
+
}
|
|
21
|
+
function blankValue(value) {
|
|
22
|
+
if (typeof value === "boolean") {
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
if (typeof value === "number") {
|
|
26
|
+
return 0;
|
|
27
|
+
}
|
|
28
|
+
if (typeof value === "string") {
|
|
29
|
+
return isIsoDateString(value) ? null : "";
|
|
30
|
+
}
|
|
31
|
+
if (Array.isArray(value)) {
|
|
32
|
+
return [];
|
|
33
|
+
}
|
|
34
|
+
if (isPlainObject(value)) {
|
|
35
|
+
return blankItem(value);
|
|
36
|
+
}
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
function blankItem(item) {
|
|
40
|
+
const blank = {};
|
|
41
|
+
for (const [key, value] of Object.entries(item)) {
|
|
42
|
+
blank[key] = blankValue(value);
|
|
43
|
+
}
|
|
44
|
+
return blank;
|
|
45
|
+
}
|
|
46
|
+
function inferNode(name, value, overrides, path) {
|
|
47
|
+
const label = humanizeKey(name);
|
|
48
|
+
if (typeof value === "boolean") {
|
|
49
|
+
return { $formkit: "nuxtUISwitch", name, label };
|
|
50
|
+
}
|
|
51
|
+
if (typeof value === "number") {
|
|
52
|
+
return { $formkit: "nuxtUIInputNumber", name, label };
|
|
53
|
+
}
|
|
54
|
+
if (value instanceof Date) {
|
|
55
|
+
return { $formkit: "nuxtUIInputDate", name, label, valueType: "date" };
|
|
56
|
+
}
|
|
57
|
+
if (isDateValue(value)) {
|
|
58
|
+
return { $formkit: "nuxtUIInputDate", name, label, valueType: "calendar" };
|
|
59
|
+
}
|
|
60
|
+
if (typeof value === "string") {
|
|
61
|
+
if (isIsoDateString(value)) {
|
|
62
|
+
return { $formkit: "nuxtUIInputDate", name, label, valueType: "iso" };
|
|
63
|
+
}
|
|
64
|
+
if (value.includes("\n")) {
|
|
65
|
+
return { $formkit: "nuxtUITextarea", name, label };
|
|
66
|
+
}
|
|
67
|
+
return { $formkit: "nuxtUIInput", name, label };
|
|
68
|
+
}
|
|
69
|
+
if (Array.isArray(value)) {
|
|
70
|
+
const [first] = value;
|
|
71
|
+
if (isPlainObject(first)) {
|
|
72
|
+
return {
|
|
73
|
+
$formkit: "nuxtUIRepeater",
|
|
74
|
+
name,
|
|
75
|
+
label,
|
|
76
|
+
newItem: blankItem(first),
|
|
77
|
+
children: inferNodes(first, overrides, path),
|
|
78
|
+
...REPEATER_BUTTON_DEFAULTS
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
return { $formkit: "nuxtUIInputTags", name, label };
|
|
82
|
+
}
|
|
83
|
+
if (isPlainObject(value)) {
|
|
84
|
+
return { $formkit: "group", name, children: inferNodes(value, overrides, path) };
|
|
85
|
+
}
|
|
86
|
+
return void 0;
|
|
87
|
+
}
|
|
88
|
+
function isDirectChildPath(path, prefix) {
|
|
89
|
+
if (!prefix) {
|
|
90
|
+
return !path.includes(".");
|
|
91
|
+
}
|
|
92
|
+
return path.startsWith(`${prefix}.`) && !path.slice(prefix.length + 1).includes(".");
|
|
93
|
+
}
|
|
94
|
+
function buildNodes(source, inferEntry, overrides, pathPrefix = "") {
|
|
95
|
+
const nodes = [];
|
|
96
|
+
const consumedPaths = /* @__PURE__ */ new Set();
|
|
97
|
+
for (const [key, value] of Object.entries(source)) {
|
|
98
|
+
const path = pathPrefix ? `${pathPrefix}.${key}` : key;
|
|
99
|
+
const override = overrides?.[path];
|
|
100
|
+
if (override !== void 0) {
|
|
101
|
+
consumedPaths.add(path);
|
|
102
|
+
}
|
|
103
|
+
if (override === false) {
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
const node = inferEntry(key, value, path);
|
|
107
|
+
if (override && override !== true) {
|
|
108
|
+
nodes.push({ $formkit: "nuxtUIInput", name: key, label: humanizeKey(key), ...node, ...override });
|
|
109
|
+
} else if (node) {
|
|
110
|
+
nodes.push(node);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
if (overrides) {
|
|
114
|
+
for (const [path, override] of Object.entries(overrides)) {
|
|
115
|
+
if (typeof override === "boolean" || consumedPaths.has(path) || !isDirectChildPath(path, pathPrefix)) {
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
const key = pathPrefix ? path.slice(pathPrefix.length + 1) : path;
|
|
119
|
+
nodes.push({ $formkit: "nuxtUIInput", name: key, label: humanizeKey(key), ...override });
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
return nodes;
|
|
123
|
+
}
|
|
124
|
+
function inferNodes(data, overrides, pathPrefix = "") {
|
|
125
|
+
return buildNodes(data, (key, value, path) => inferNode(key, value, overrides, path), overrides, pathPrefix);
|
|
126
|
+
}
|
|
127
|
+
export function inferFormSchema(data, overrides) {
|
|
128
|
+
return inferNodes(data, overrides);
|
|
129
|
+
}
|
|
130
|
+
const OPTIONAL_WRAPPER_TYPES = /* @__PURE__ */ new Set(["optional", "nullable", "nullish", "exact_optional"]);
|
|
131
|
+
function unwrapValibot(entry) {
|
|
132
|
+
let schema = entry;
|
|
133
|
+
let required = true;
|
|
134
|
+
while (OPTIONAL_WRAPPER_TYPES.has(schema.type) && schema.wrapped) {
|
|
135
|
+
required = false;
|
|
136
|
+
schema = schema.wrapped;
|
|
137
|
+
}
|
|
138
|
+
return { schema, required };
|
|
139
|
+
}
|
|
140
|
+
function deriveValibotValidation(schema, required) {
|
|
141
|
+
const rules = [];
|
|
142
|
+
if (required && schema.type !== "boolean") {
|
|
143
|
+
rules.push("required");
|
|
144
|
+
}
|
|
145
|
+
let minLength;
|
|
146
|
+
let maxLength;
|
|
147
|
+
for (const entry of schema.pipe ?? []) {
|
|
148
|
+
if (entry.kind !== "validation") {
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
if (entry.type === "email" || entry.type === "url") {
|
|
152
|
+
rules.push(entry.type);
|
|
153
|
+
} else if (entry.type === "min_length" && typeof entry.requirement === "number") {
|
|
154
|
+
minLength = entry.requirement;
|
|
155
|
+
} else if (entry.type === "max_length" && typeof entry.requirement === "number") {
|
|
156
|
+
maxLength = entry.requirement;
|
|
157
|
+
} else if (entry.type === "min_value" && typeof entry.requirement === "number") {
|
|
158
|
+
rules.push(`min:${entry.requirement}`);
|
|
159
|
+
} else if (entry.type === "max_value" && typeof entry.requirement === "number") {
|
|
160
|
+
rules.push(`max:${entry.requirement}`);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
if (maxLength !== void 0) {
|
|
164
|
+
rules.push(`length:${minLength ?? 0},${maxLength}`);
|
|
165
|
+
} else if (minLength !== void 0) {
|
|
166
|
+
rules.push(`length:${minLength}`);
|
|
167
|
+
}
|
|
168
|
+
return rules.length ? rules.join("|") : void 0;
|
|
169
|
+
}
|
|
170
|
+
function blankValueFromValibot(entry) {
|
|
171
|
+
if (entry.default !== void 0) {
|
|
172
|
+
return entry.default;
|
|
173
|
+
}
|
|
174
|
+
const { schema } = unwrapValibot(entry);
|
|
175
|
+
switch (schema.type) {
|
|
176
|
+
case "string":
|
|
177
|
+
return "";
|
|
178
|
+
case "number":
|
|
179
|
+
return 0;
|
|
180
|
+
case "boolean":
|
|
181
|
+
return false;
|
|
182
|
+
case "array":
|
|
183
|
+
return [];
|
|
184
|
+
case "object":
|
|
185
|
+
return schema.entries ? blankItemFromValibot(schema.entries) : {};
|
|
186
|
+
default:
|
|
187
|
+
return null;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
function blankItemFromValibot(entries) {
|
|
191
|
+
const blank = {};
|
|
192
|
+
for (const [key, entry] of Object.entries(entries)) {
|
|
193
|
+
blank[key] = blankValueFromValibot(entry);
|
|
194
|
+
}
|
|
195
|
+
return blank;
|
|
196
|
+
}
|
|
197
|
+
function inferValibotNode(name, entry, overrides, path) {
|
|
198
|
+
const { schema, required } = unwrapValibot(entry);
|
|
199
|
+
const label = humanizeKey(name);
|
|
200
|
+
const validation = deriveValibotValidation(schema, required);
|
|
201
|
+
const base = validation ? { name, label, validation } : { name, label };
|
|
202
|
+
switch (schema.type) {
|
|
203
|
+
case "string":
|
|
204
|
+
return { $formkit: "nuxtUIInput", ...base };
|
|
205
|
+
case "number":
|
|
206
|
+
return { $formkit: "nuxtUIInputNumber", ...base };
|
|
207
|
+
case "boolean":
|
|
208
|
+
return { $formkit: "nuxtUISwitch", ...base };
|
|
209
|
+
case "date":
|
|
210
|
+
return { $formkit: "nuxtUIInputDate", ...base, valueType: "date" };
|
|
211
|
+
case "array": {
|
|
212
|
+
const item = schema.item ? unwrapValibot(schema.item).schema : void 0;
|
|
213
|
+
if (item?.type === "object" && item.entries) {
|
|
214
|
+
return {
|
|
215
|
+
$formkit: "nuxtUIRepeater",
|
|
216
|
+
...base,
|
|
217
|
+
newItem: blankItemFromValibot(item.entries),
|
|
218
|
+
children: inferValibotNodes(item.entries, overrides, path),
|
|
219
|
+
...REPEATER_BUTTON_DEFAULTS
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
return { $formkit: "nuxtUIInputTags", ...base };
|
|
223
|
+
}
|
|
224
|
+
case "object":
|
|
225
|
+
return schema.entries ? { $formkit: "group", name, children: inferValibotNodes(schema.entries, overrides, path) } : void 0;
|
|
226
|
+
default:
|
|
227
|
+
return void 0;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
function inferValibotNodes(entries, overrides, pathPrefix = "") {
|
|
231
|
+
return buildNodes(entries, (key, entry, path) => inferValibotNode(key, entry, overrides, path), overrides, pathPrefix);
|
|
232
|
+
}
|
|
233
|
+
export function inferFormSchemaFromValibot(schema, overrides) {
|
|
234
|
+
const root = schema;
|
|
235
|
+
if (root.kind !== "schema" || root.type !== "object" || !root.entries) {
|
|
236
|
+
return [];
|
|
237
|
+
}
|
|
238
|
+
return inferValibotNodes(root.entries, overrides);
|
|
239
|
+
}
|
|
240
|
+
const ZOD_UNWRAPPABLE_TYPES = /* @__PURE__ */ new Set(["optional", "nullable", "nullish", "default"]);
|
|
241
|
+
function unwrapZod(entry) {
|
|
242
|
+
let schema = entry;
|
|
243
|
+
let required = true;
|
|
244
|
+
while (ZOD_UNWRAPPABLE_TYPES.has(schema.type) && typeof schema.unwrap === "function") {
|
|
245
|
+
required = false;
|
|
246
|
+
schema = schema.unwrap();
|
|
247
|
+
}
|
|
248
|
+
return { schema, required };
|
|
249
|
+
}
|
|
250
|
+
function deriveZodValidation(schema, required) {
|
|
251
|
+
const rules = [];
|
|
252
|
+
if (required && schema.type !== "boolean") {
|
|
253
|
+
rules.push("required");
|
|
254
|
+
}
|
|
255
|
+
if (schema.format === "email" || schema.format === "url") {
|
|
256
|
+
rules.push(schema.format);
|
|
257
|
+
}
|
|
258
|
+
if (Number.isFinite(schema.minValue)) {
|
|
259
|
+
rules.push(`min:${schema.minValue}`);
|
|
260
|
+
}
|
|
261
|
+
if (Number.isFinite(schema.maxValue)) {
|
|
262
|
+
rules.push(`max:${schema.maxValue}`);
|
|
263
|
+
}
|
|
264
|
+
if (typeof schema.maxLength === "number") {
|
|
265
|
+
rules.push(`length:${schema.minLength ?? 0},${schema.maxLength}`);
|
|
266
|
+
} else if (typeof schema.minLength === "number") {
|
|
267
|
+
rules.push(`length:${schema.minLength}`);
|
|
268
|
+
}
|
|
269
|
+
return rules.length ? rules.join("|") : void 0;
|
|
270
|
+
}
|
|
271
|
+
function blankValueFromZod(entry) {
|
|
272
|
+
let schema = entry;
|
|
273
|
+
while (true) {
|
|
274
|
+
if (schema.def?.defaultValue !== void 0) {
|
|
275
|
+
return schema.def.defaultValue;
|
|
276
|
+
}
|
|
277
|
+
if (!ZOD_UNWRAPPABLE_TYPES.has(schema.type) || typeof schema.unwrap !== "function") {
|
|
278
|
+
break;
|
|
279
|
+
}
|
|
280
|
+
schema = schema.unwrap();
|
|
281
|
+
}
|
|
282
|
+
switch (schema.type) {
|
|
283
|
+
case "string":
|
|
284
|
+
return "";
|
|
285
|
+
case "number":
|
|
286
|
+
return 0;
|
|
287
|
+
case "boolean":
|
|
288
|
+
return false;
|
|
289
|
+
case "array":
|
|
290
|
+
return [];
|
|
291
|
+
case "object":
|
|
292
|
+
return schema.shape ? blankItemFromZod(schema.shape) : {};
|
|
293
|
+
default:
|
|
294
|
+
return null;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
function blankItemFromZod(shape) {
|
|
298
|
+
const blank = {};
|
|
299
|
+
for (const [key, entry] of Object.entries(shape)) {
|
|
300
|
+
blank[key] = blankValueFromZod(entry);
|
|
301
|
+
}
|
|
302
|
+
return blank;
|
|
303
|
+
}
|
|
304
|
+
function inferZodNode(name, entry, overrides, path) {
|
|
305
|
+
const { schema, required } = unwrapZod(entry);
|
|
306
|
+
const label = humanizeKey(name);
|
|
307
|
+
const validation = deriveZodValidation(schema, required);
|
|
308
|
+
const base = validation ? { name, label, validation } : { name, label };
|
|
309
|
+
switch (schema.type) {
|
|
310
|
+
case "string":
|
|
311
|
+
return { $formkit: "nuxtUIInput", ...base };
|
|
312
|
+
case "number":
|
|
313
|
+
return { $formkit: "nuxtUIInputNumber", ...base };
|
|
314
|
+
case "boolean":
|
|
315
|
+
return { $formkit: "nuxtUISwitch", ...base };
|
|
316
|
+
case "date":
|
|
317
|
+
return { $formkit: "nuxtUIInputDate", ...base, valueType: "date" };
|
|
318
|
+
case "array": {
|
|
319
|
+
const item = schema.element ? unwrapZod(schema.element).schema : void 0;
|
|
320
|
+
if (item?.type === "object" && item.shape) {
|
|
321
|
+
return {
|
|
322
|
+
$formkit: "nuxtUIRepeater",
|
|
323
|
+
...base,
|
|
324
|
+
newItem: blankItemFromZod(item.shape),
|
|
325
|
+
children: inferZodNodes(item.shape, overrides, path),
|
|
326
|
+
...REPEATER_BUTTON_DEFAULTS
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
return { $formkit: "nuxtUIInputTags", ...base };
|
|
330
|
+
}
|
|
331
|
+
case "object":
|
|
332
|
+
return schema.shape ? { $formkit: "group", name, children: inferZodNodes(schema.shape, overrides, path) } : void 0;
|
|
333
|
+
default:
|
|
334
|
+
return void 0;
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
function inferZodNodes(shape, overrides, pathPrefix = "") {
|
|
338
|
+
return buildNodes(shape, (key, entry, path) => inferZodNode(key, entry, overrides, path), overrides, pathPrefix);
|
|
339
|
+
}
|
|
340
|
+
export function inferFormSchemaFromZod(schema, overrides) {
|
|
341
|
+
const root = schema;
|
|
342
|
+
if (root.type !== "object" || !root.shape) {
|
|
343
|
+
return [];
|
|
344
|
+
}
|
|
345
|
+
return inferZodNodes(root.shape, overrides);
|
|
346
|
+
}
|
|
347
|
+
export function useFormKitAutoForm() {
|
|
348
|
+
return { inferFormSchema, inferFormSchemaFromValibot, inferFormSchemaFromZod };
|
|
349
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { ErrorMessages } from '@formkit/core';
|
|
2
|
+
export declare function useFormKitForm(id: string): {
|
|
3
|
+
submit: (handler?: () => unknown | Promise<unknown>) => void;
|
|
4
|
+
reset: (resetTo?: unknown) => void;
|
|
5
|
+
setErrors: (localErrors: ErrorMessages, childErrors?: ErrorMessages) => void;
|
|
6
|
+
clearErrors: (clearChildren?: boolean) => void;
|
|
7
|
+
isValid: import("vue").ComputedRef<boolean>;
|
|
8
|
+
isSubmitted: import("vue").ComputedRef<boolean>;
|
|
9
|
+
hasErrors: import("vue").ComputedRef<boolean>;
|
|
10
|
+
isLoading: import("vue").Ref<boolean, boolean>;
|
|
11
|
+
};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { clearErrors, reset, setErrors, submitForm } from "@formkit/core";
|
|
2
|
+
import { useFormKitContextById } from "@formkit/vue";
|
|
3
|
+
import { computed, ref } from "vue";
|
|
4
|
+
export function useFormKitForm(id) {
|
|
5
|
+
const context = useFormKitContextById(id);
|
|
6
|
+
const isLoading = ref(false);
|
|
7
|
+
const isValid = computed(() => context.value?.state.valid ?? false);
|
|
8
|
+
const isSubmitted = computed(() => context.value?.state.submitted ?? false);
|
|
9
|
+
const hasErrors = computed(() => context.value?.state.errors ?? false);
|
|
10
|
+
function submit(handler) {
|
|
11
|
+
submitForm(id);
|
|
12
|
+
if (!handler)
|
|
13
|
+
return;
|
|
14
|
+
isLoading.value = true;
|
|
15
|
+
void Promise.resolve(handler()).finally(() => {
|
|
16
|
+
isLoading.value = false;
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
function resetForm(resetTo) {
|
|
20
|
+
reset(id, resetTo);
|
|
21
|
+
}
|
|
22
|
+
function setFormErrors(localErrors, childErrors) {
|
|
23
|
+
setErrors(id, localErrors, childErrors);
|
|
24
|
+
}
|
|
25
|
+
function clearFormErrors(clearChildren) {
|
|
26
|
+
clearErrors(id, clearChildren);
|
|
27
|
+
}
|
|
28
|
+
return {
|
|
29
|
+
submit,
|
|
30
|
+
reset: resetForm,
|
|
31
|
+
setErrors: setFormErrors,
|
|
32
|
+
clearErrors: clearFormErrors,
|
|
33
|
+
isValid,
|
|
34
|
+
isSubmitted,
|
|
35
|
+
hasErrors,
|
|
36
|
+
isLoading
|
|
37
|
+
};
|
|
38
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { ref, watchEffect } from "vue";
|
|
2
|
+
export function useFormKitMultiStep() {
|
|
3
|
+
function buildTabItems(node) {
|
|
4
|
+
const steps = Array.isArray(node.props.steps) ? node.props.steps : [];
|
|
5
|
+
const validStepIcon = typeof node.props.validStepIcon === "string" ? node.props.validStepIcon : "i-lucide-check";
|
|
6
|
+
const allowIncomplete = node.props.allowIncomplete !== false;
|
|
7
|
+
const activeIndex = steps.findIndex((step) => step.node?.name === node.props.activeStep);
|
|
8
|
+
const activeStepInvalid = activeIndex >= 0 && steps[activeIndex]?.isValid === false;
|
|
9
|
+
return steps.map((step, index) => {
|
|
10
|
+
const totalErrorCount = (step.errorCount ?? 0) + (step.blockingCount ?? 0);
|
|
11
|
+
const item = {
|
|
12
|
+
label: step.stepName,
|
|
13
|
+
value: step.node?.name,
|
|
14
|
+
// Jumping ahead to a step you haven't visited yet while the current
|
|
15
|
+
// step is invalid would just be silently blocked by the addon's own
|
|
16
|
+
// `isTargetStepAllowed` gating on click - disable it proactively
|
|
17
|
+
// instead so it's not clickable-but-inert. Steps already visited
|
|
18
|
+
// (including the active one) stay enabled, matching the addon's own
|
|
19
|
+
// "moving backward is always allowed" rule.
|
|
20
|
+
disabled: !allowIncomplete && !step.hasBeenVisited && index > activeIndex && activeStepInvalid
|
|
21
|
+
};
|
|
22
|
+
const isActive = index === activeIndex;
|
|
23
|
+
if (step.showStepErrors && totalErrorCount > 0)
|
|
24
|
+
item.badge = { label: totalErrorCount, color: "error", variant: isActive ? "solid" : void 0 };
|
|
25
|
+
else if (step.isValid && step.hasBeenVisited)
|
|
26
|
+
item.badge = { icon: validStepIcon, color: "success", variant: isActive ? "solid" : void 0 };
|
|
27
|
+
return item;
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
function addMultiStepHandler(node) {
|
|
31
|
+
const structuralVersion = ref(0);
|
|
32
|
+
const bumpStructuralVersion = () => {
|
|
33
|
+
structuralVersion.value++;
|
|
34
|
+
};
|
|
35
|
+
node.on("child", bumpStructuralVersion);
|
|
36
|
+
node.on("childRemoved", bumpStructuralVersion);
|
|
37
|
+
node.on("prop:steps", bumpStructuralVersion);
|
|
38
|
+
node.on("prop:activeStep", bumpStructuralVersion);
|
|
39
|
+
node.on("created", () => {
|
|
40
|
+
if (!node.context)
|
|
41
|
+
return;
|
|
42
|
+
watchEffect(() => {
|
|
43
|
+
void structuralVersion.value;
|
|
44
|
+
if (node.context)
|
|
45
|
+
node.context.tabItems = buildTabItems(node);
|
|
46
|
+
});
|
|
47
|
+
node.context.handleTabChange = (value) => {
|
|
48
|
+
node.goTo(value);
|
|
49
|
+
};
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
function addStepHandler(node) {
|
|
53
|
+
node.on("created", () => {
|
|
54
|
+
if (!node.context)
|
|
55
|
+
return;
|
|
56
|
+
node.context.internalStepActionsClass = node.context.stepActionsClass ? `formkit-step-actions ${node.context.stepActionsClass}` : "formkit-step-actions flex gap-2 pt-2";
|
|
57
|
+
watchEffect(() => {
|
|
58
|
+
const allowIncomplete = node.parent?.props.allowIncomplete !== false;
|
|
59
|
+
if (node.context)
|
|
60
|
+
node.context.nextDisabled = !allowIncomplete && node.context.isValid === false;
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
return { addMultiStepHandler, addStepHandler };
|
|
65
|
+
}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
export function useFormKitRepeater() {
|
|
2
|
+
function addRepeaterHandler(node) {
|
|
3
|
+
const swapElements = (array, index1, index2) => {
|
|
4
|
+
const [element] = array.splice(index1, 1);
|
|
5
|
+
if (element !== void 0) {
|
|
6
|
+
array.splice(index2, 0, element);
|
|
7
|
+
}
|
|
8
|
+
return array;
|
|
9
|
+
};
|
|
10
|
+
const uuid = typeof crypto !== "undefined" && crypto.randomUUID ? crypto.randomUUID() : Math.random().toString(36).slice(2);
|
|
11
|
+
const syncOuterValue = (value) => {
|
|
12
|
+
node.input(value, false);
|
|
13
|
+
};
|
|
14
|
+
node.on("created", () => {
|
|
15
|
+
if (node.context) {
|
|
16
|
+
let dragStartIndex = null;
|
|
17
|
+
let dragOverIndex = null;
|
|
18
|
+
const newItem = node.context.newItem || {};
|
|
19
|
+
node.context.listName = node.name;
|
|
20
|
+
node.context.renderButtons = !node.context.hideButtonGroup;
|
|
21
|
+
node.context.insertButtonSize = node.context.insertButtonSize ? node.context.insertButtonSize : "md";
|
|
22
|
+
node.context.buttonSize = node.context.buttonSize ? node.context.buttonSize : "md";
|
|
23
|
+
node.context.renderMoveButtons = !node.context.hideMoveButtons;
|
|
24
|
+
node.context.draggable = !!node.context.draggable;
|
|
25
|
+
node.context.renderDragHandle = !!node.context.displayDragHandle;
|
|
26
|
+
node.context.dragHandleIconName = node.context.dragHandleIconName || "i-lucide-grip-vertical";
|
|
27
|
+
node.context.internalDragHandleClass = node.context.dragHandleClass ? `formkit-repeater-drag-handle ${node.context.dragHandleClass}` : "formkit-repeater-drag-handle";
|
|
28
|
+
node.context.internalListClass = node.context.listClass ? `formkit-items ${node.context.listClass}` : "formkit-items";
|
|
29
|
+
node.context.internalListItemClass = node.context.listItemClass ? `formkit-item ${node.context.listItemClass}` : "formkit-item";
|
|
30
|
+
node.context.internalListId = `formkit-items-${uuid}`;
|
|
31
|
+
node.context.minItems = node.context.minItems !== void 0 ? node.context.minItems : 0;
|
|
32
|
+
node.context.maxItems = node.context.maxItems !== void 0 ? node.context.maxItems : Number.POSITIVE_INFINITY;
|
|
33
|
+
node.context.repeaterValidation = [["length", node.context.minItems, node.context.maxItems]];
|
|
34
|
+
node.context.getListItemClass = (index) => {
|
|
35
|
+
if (dragOverIndex === index && dragStartIndex !== index)
|
|
36
|
+
return `${node.context?.internalListItemClass} formkit-repeater-drop-target`;
|
|
37
|
+
return node.context?.internalListItemClass?.toString() ?? "";
|
|
38
|
+
};
|
|
39
|
+
node.context.getListItemId = (index) => {
|
|
40
|
+
return `formkit-item-${index}-${uuid}`;
|
|
41
|
+
};
|
|
42
|
+
node.context.insertNode = (parentNode) => () => {
|
|
43
|
+
if (parentNode && Array.isArray(parentNode._value)) {
|
|
44
|
+
const newArray = [{ ...newItem }, ...parentNode.value];
|
|
45
|
+
parentNode.input(newArray, false);
|
|
46
|
+
syncOuterValue(newArray);
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
node.context.removeNode = (parentNode, index) => () => {
|
|
50
|
+
if (parentNode && Array.isArray(parentNode._value)) {
|
|
51
|
+
const newArray = parentNode._value.filter((_, i) => i !== index);
|
|
52
|
+
parentNode.input(newArray, false);
|
|
53
|
+
syncOuterValue(newArray);
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
node.context.addNode = (parentNode, index) => () => {
|
|
57
|
+
if (parentNode && Array.isArray(parentNode._value)) {
|
|
58
|
+
const array = parentNode.value;
|
|
59
|
+
array.splice(index + 1, 0, { ...newItem });
|
|
60
|
+
parentNode.input(array, false);
|
|
61
|
+
syncOuterValue(array);
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
node.context.cloneNode = (parentNode, index) => () => {
|
|
65
|
+
if (parentNode && Array.isArray(parentNode._value)) {
|
|
66
|
+
const item = parentNode.value[index];
|
|
67
|
+
const array = parentNode.value;
|
|
68
|
+
array.splice(index + 1, 0, { ...item });
|
|
69
|
+
parentNode.input(array, false);
|
|
70
|
+
syncOuterValue(array);
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
node.context.moveNodeUp = (parentNode, index) => () => {
|
|
74
|
+
if (parentNode && Array.isArray(parentNode._value)) {
|
|
75
|
+
if (index > 0) {
|
|
76
|
+
const newArray = swapElements(parentNode.value, index - 1, index);
|
|
77
|
+
parentNode.input(newArray, false);
|
|
78
|
+
syncOuterValue(newArray);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
node.context.moveNodeDown = (parentNode, index) => () => {
|
|
83
|
+
if (parentNode && Array.isArray(parentNode._value)) {
|
|
84
|
+
if (index < parentNode.value.length - 1) {
|
|
85
|
+
const newArray = swapElements(parentNode.value, index, index + 1);
|
|
86
|
+
parentNode.input(newArray, false);
|
|
87
|
+
syncOuterValue(newArray);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
node.context.dragNodeStart = (_parentNode, index) => (event) => {
|
|
92
|
+
dragStartIndex = index;
|
|
93
|
+
if (event?.dataTransfer) {
|
|
94
|
+
event.dataTransfer.effectAllowed = "move";
|
|
95
|
+
event.dataTransfer.setData("text/plain", String(index));
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
node.context.dragNodeOver = (targetIndex) => (event) => {
|
|
99
|
+
event?.preventDefault();
|
|
100
|
+
dragOverIndex = targetIndex;
|
|
101
|
+
if (event?.dataTransfer)
|
|
102
|
+
event.dataTransfer.dropEffect = "move";
|
|
103
|
+
};
|
|
104
|
+
node.context.dragNodeLeave = (targetIndex) => () => {
|
|
105
|
+
if (dragOverIndex === targetIndex)
|
|
106
|
+
dragOverIndex = null;
|
|
107
|
+
};
|
|
108
|
+
node.context.dropNode = (parentNode, targetIndex) => (event) => {
|
|
109
|
+
event?.preventDefault();
|
|
110
|
+
if (!parentNode || !Array.isArray(parentNode._value)) {
|
|
111
|
+
dragOverIndex = null;
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
const sourceFromEvent = Number.parseInt(event?.dataTransfer?.getData("text/plain") || "", 10);
|
|
115
|
+
const sourceIndex = Number.isInteger(sourceFromEvent) ? sourceFromEvent : dragStartIndex;
|
|
116
|
+
if (sourceIndex === null || sourceIndex === targetIndex) {
|
|
117
|
+
dragOverIndex = null;
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
if (sourceIndex < 0 || sourceIndex >= parentNode.value.length) {
|
|
121
|
+
dragOverIndex = null;
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
const newArray = swapElements(parentNode.value, sourceIndex, targetIndex);
|
|
125
|
+
parentNode.input(newArray, false);
|
|
126
|
+
syncOuterValue(newArray);
|
|
127
|
+
dragStartIndex = null;
|
|
128
|
+
dragOverIndex = null;
|
|
129
|
+
};
|
|
130
|
+
node.context.dragNodeEnd = () => {
|
|
131
|
+
dragStartIndex = null;
|
|
132
|
+
dragOverIndex = null;
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
return { addRepeaterHandler };
|
|
138
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { DefaultConfigOptions } from '@formkit/vue';
|
|
2
|
+
export interface CreateNuxtUiFormkitConfigOptions {
|
|
3
|
+
/**
|
|
4
|
+
* Registers `createMultiStepPlugin()` so `nuxtUIMultiStep`/`nuxtUIStep`
|
|
5
|
+
* actually work. The input type registrations themselves are always
|
|
6
|
+
* included below regardless of this flag - they're inert for forms that
|
|
7
|
+
* don't use them, but the plugin has real runtime behavior (it hooks
|
|
8
|
+
* every node to check its type), so it's opt-in.
|
|
9
|
+
*/
|
|
10
|
+
multiStep?: boolean;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Assembles the `inputs`/`plugins` this module needs into a plain object a
|
|
14
|
+
* consumer spreads into their own `defaultConfig({ ...createNuxtUiFormkitConfig(), ... })`
|
|
15
|
+
* call inside `formkit.config.ts` - it doesn't call `defaultConfig` itself,
|
|
16
|
+
* so it stays composable with whatever else that config already does
|
|
17
|
+
* (locales, `iconLoader`, other plugins like `createAutoAnimatePlugin`).
|
|
18
|
+
*/
|
|
19
|
+
export declare function createNuxtUiFormkitConfig(options?: CreateNuxtUiFormkitConfigOptions): Pick<DefaultConfigOptions, 'inputs' | 'plugins'>;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { createMultiStepPlugin } from "@formkit/addons";
|
|
2
|
+
import { nuxtUIInputs, nuxtUIOutputs } from "./definitions/index.js";
|
|
3
|
+
import { addNuxtAsteriskPlugin } from "./plugins/index.js";
|
|
4
|
+
export function createNuxtUiFormkitConfig(options = {}) {
|
|
5
|
+
const plugins = [addNuxtAsteriskPlugin];
|
|
6
|
+
if (options.multiStep)
|
|
7
|
+
plugins.push(createMultiStepPlugin());
|
|
8
|
+
return {
|
|
9
|
+
inputs: { ...nuxtUIInputs, ...nuxtUIOutputs },
|
|
10
|
+
plugins
|
|
11
|
+
};
|
|
12
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { createMultiStepPlugin } from "@formkit/addons";
|
|
2
|
+
import { nuxtUIInputs, nuxtUIOutputs } from "./definitions/index.mjs";
|
|
3
|
+
import { addNuxtAsteriskPlugin } from "./plugins/index.mjs";
|
|
4
|
+
export function createNuxtUiFormkitConfig(options = {}) {
|
|
5
|
+
const plugins = [addNuxtAsteriskPlugin];
|
|
6
|
+
if (options.multiStep)
|
|
7
|
+
plugins.push(createMultiStepPlugin());
|
|
8
|
+
return {
|
|
9
|
+
inputs: { ...nuxtUIInputs, ...nuxtUIOutputs },
|
|
10
|
+
plugins
|
|
11
|
+
};
|
|
12
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export * from './input.js';
|
|
2
|
+
export * from './multiStep.js';
|
|
2
3
|
export * from './output.js';
|
|
3
4
|
export declare const nuxtUIInputs: {
|
|
4
5
|
nuxtUICalendar: import("@formkit/core").FormKitTypeDefinition;
|
|
@@ -14,11 +15,13 @@ export declare const nuxtUIInputs: {
|
|
|
14
15
|
nuxtUIInputTags: import("@formkit/core").FormKitTypeDefinition;
|
|
15
16
|
nuxtUIInputTime: import("@formkit/core").FormKitTypeDefinition;
|
|
16
17
|
nuxtUIListbox: import("@formkit/core").FormKitTypeDefinition;
|
|
18
|
+
nuxtUIMultiStep: import("@formkit/core").FormKitTypeDefinition;
|
|
17
19
|
nuxtUIPinInput: import("@formkit/core").FormKitTypeDefinition;
|
|
18
20
|
nuxtUIRadioGroup: import("@formkit/core").FormKitTypeDefinition;
|
|
19
21
|
nuxtUISelect: import("@formkit/core").FormKitTypeDefinition;
|
|
20
22
|
nuxtUISelectMenu: import("@formkit/core").FormKitTypeDefinition;
|
|
21
23
|
nuxtUISlider: import("@formkit/core").FormKitTypeDefinition;
|
|
24
|
+
nuxtUIStep: import("@formkit/core").FormKitTypeDefinition;
|
|
22
25
|
nuxtUISwitch: import("@formkit/core").FormKitTypeDefinition;
|
|
23
26
|
nuxtUITextarea: import("@formkit/core").FormKitTypeDefinition;
|
|
24
27
|
nuxtUIRepeater: import("@formkit/core").FormKitTypeDefinition;
|