oxform-core 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +751 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +443 -0
- package/dist/index.d.ts +443 -0
- package/dist/index.js +747 -0
- package/dist/index.js.map +1 -0
- package/package.json +20 -15
- package/export/index.ts +0 -7
- package/export/schema.ts +0 -1
- package/src/field-api.constants.ts +0 -15
- package/src/field-api.ts +0 -139
- package/src/form-api.ts +0 -84
- package/src/form-api.types.ts +0 -148
- package/src/form-array-field-api.ts +0 -233
- package/src/form-context-api.ts +0 -232
- package/src/form-field-api.ts +0 -174
- package/src/more-types.ts +0 -178
- package/src/tests/array/append.spec.ts +0 -138
- package/src/tests/array/insert.spec.ts +0 -182
- package/src/tests/array/move.spec.ts +0 -175
- package/src/tests/array/prepend.spec.ts +0 -138
- package/src/tests/array/remove.spec.ts +0 -174
- package/src/tests/array/swap.spec.ts +0 -152
- package/src/tests/array/update.spec.ts +0 -148
- package/src/tests/field/change.spec.ts +0 -226
- package/src/tests/field/reset.spec.ts +0 -617
- package/src/tests/field/set-errors.spec.ts +0 -254
- package/src/tests/field-api/field-api.spec.ts +0 -341
- package/src/tests/form-api/reset.spec.ts +0 -535
- package/src/tests/form-api/submit.spec.ts +0 -409
- package/src/types.ts +0 -5
- package/src/utils/get.ts +0 -5
- package/src/utils/testing/sleep.ts +0 -1
- package/src/utils/testing/tests.ts +0 -18
- package/src/utils/update.ts +0 -6
- package/src/utils/validate.ts +0 -8
- package/tsconfig.json +0 -3
- package/tsdown.config.ts +0 -10
- package/vitest.config.ts +0 -3
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,751 @@
|
|
|
1
|
+
let remeda = require("remeda");
|
|
2
|
+
let _tanstack_store = require("@tanstack/store");
|
|
3
|
+
|
|
4
|
+
//#region src/field-api.constants.ts
|
|
5
|
+
const defaultMeta = {
|
|
6
|
+
blurred: false,
|
|
7
|
+
touched: false,
|
|
8
|
+
dirty: false
|
|
9
|
+
};
|
|
10
|
+
const defaultStatus = {
|
|
11
|
+
submits: 0,
|
|
12
|
+
submitting: false,
|
|
13
|
+
validating: false,
|
|
14
|
+
successful: false,
|
|
15
|
+
dirty: false
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
//#endregion
|
|
19
|
+
//#region src/utils/get.ts
|
|
20
|
+
const get = (data, path) => {
|
|
21
|
+
return path.reduce((acc, key) => {
|
|
22
|
+
if (acc === void 0) return void 0;
|
|
23
|
+
if (acc === null) return null;
|
|
24
|
+
return acc[key];
|
|
25
|
+
}, data);
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
//#endregion
|
|
29
|
+
//#region src/utils/update.ts
|
|
30
|
+
function update(updater, input) {
|
|
31
|
+
return typeof updater === "function" ? updater(input) : updater;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
//#endregion
|
|
35
|
+
//#region src/form-array-field-api.ts
|
|
36
|
+
var FormArrayFieldApi = class {
|
|
37
|
+
context;
|
|
38
|
+
field;
|
|
39
|
+
constructor({ field, context }) {
|
|
40
|
+
this.context = context;
|
|
41
|
+
this.field = field;
|
|
42
|
+
}
|
|
43
|
+
insert = (name, index, value, options) => {
|
|
44
|
+
if (index < 0) index = 0;
|
|
45
|
+
this.field.change(name, (current) => {
|
|
46
|
+
const array = current ?? [];
|
|
47
|
+
return [
|
|
48
|
+
...array.slice(0, index),
|
|
49
|
+
...Array.from({ length: index - array.length }, () => void 0),
|
|
50
|
+
update(value, current),
|
|
51
|
+
...array.slice(index)
|
|
52
|
+
];
|
|
53
|
+
}, options);
|
|
54
|
+
this.context.persisted.setState((current) => {
|
|
55
|
+
const fields = { ...current.fields };
|
|
56
|
+
const length = get(current.values, (0, remeda.stringToPath)(name))?.length;
|
|
57
|
+
if (length === void 0) return current;
|
|
58
|
+
for (let i = index; i < length; i++) {
|
|
59
|
+
const moving = current.fields[`${name}.${i}`];
|
|
60
|
+
fields[`${name}.${i + 1}`] = moving ?? defaultMeta;
|
|
61
|
+
}
|
|
62
|
+
fields[`${name}.${index}`] = defaultMeta;
|
|
63
|
+
return {
|
|
64
|
+
...current,
|
|
65
|
+
fields
|
|
66
|
+
};
|
|
67
|
+
});
|
|
68
|
+
};
|
|
69
|
+
append = (name, value, options) => {
|
|
70
|
+
const current = this.field.get(name);
|
|
71
|
+
return this.insert(name, current?.length ?? 0, value, options);
|
|
72
|
+
};
|
|
73
|
+
prepend = (name, value, options) => {
|
|
74
|
+
return this.insert(name, 0, value, options);
|
|
75
|
+
};
|
|
76
|
+
swap = (name, from, to, options) => {
|
|
77
|
+
const start = from >= 0 ? from : 0;
|
|
78
|
+
const end = to >= 0 ? to : 0;
|
|
79
|
+
this.field.change(name, (current) => {
|
|
80
|
+
const array = current ?? [];
|
|
81
|
+
if (start === end) return array;
|
|
82
|
+
const a = array[start];
|
|
83
|
+
const b = array[end];
|
|
84
|
+
return [
|
|
85
|
+
...array.slice(0, start),
|
|
86
|
+
b,
|
|
87
|
+
...array.slice(start + 1, end),
|
|
88
|
+
a,
|
|
89
|
+
...array.slice(end + 1)
|
|
90
|
+
];
|
|
91
|
+
}, options);
|
|
92
|
+
this.context.persisted.setState((current) => {
|
|
93
|
+
const fields = { ...current.fields };
|
|
94
|
+
fields[`${name}.${start}`] = current.fields[`${name}.${end}`] ?? defaultMeta;
|
|
95
|
+
fields[`${name}.${end}`] = current.fields[`${name}.${start}`] ?? defaultMeta;
|
|
96
|
+
return {
|
|
97
|
+
...current,
|
|
98
|
+
fields
|
|
99
|
+
};
|
|
100
|
+
});
|
|
101
|
+
};
|
|
102
|
+
move(name, _from, _to, options) {
|
|
103
|
+
const from = Math.max(_from, 0);
|
|
104
|
+
const to = Math.max(_to, 0);
|
|
105
|
+
const backwards = from > to;
|
|
106
|
+
this.field.change(name, (current) => {
|
|
107
|
+
const array = current ? [...current] : [];
|
|
108
|
+
if (from === to) return array;
|
|
109
|
+
const moved = array[from];
|
|
110
|
+
return array.toSpliced(backwards ? to : to + 1, 0, moved).toSpliced(backwards ? from + 1 : from, 1);
|
|
111
|
+
}, options);
|
|
112
|
+
this.context.persisted.setState((current) => {
|
|
113
|
+
const fields = { ...current.fields };
|
|
114
|
+
const length = get(current.values, (0, remeda.stringToPath)(name))?.length;
|
|
115
|
+
const start = Math.min(from, to);
|
|
116
|
+
const end = Math.max(from, to);
|
|
117
|
+
if (length === void 0) return current;
|
|
118
|
+
if (!backwards) fields[`${name}.${to}`] = current.fields[`${name}.${from}`] ?? defaultMeta;
|
|
119
|
+
for (let i = backwards ? start + 1 : start; i < end; i++) {
|
|
120
|
+
const shift = backwards ? -1 : 1;
|
|
121
|
+
const moving = current.fields[`${name}.${i + shift}`];
|
|
122
|
+
fields[`${name}.${i}`] = moving ?? defaultMeta;
|
|
123
|
+
}
|
|
124
|
+
return {
|
|
125
|
+
...current,
|
|
126
|
+
fields
|
|
127
|
+
};
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
update = (name, index, value, options) => {
|
|
131
|
+
this.field.change(name, (current) => {
|
|
132
|
+
const array = current ?? [];
|
|
133
|
+
const position = Math.max(Math.min(index, array.length - 1), 0);
|
|
134
|
+
return [
|
|
135
|
+
...array.slice(0, position),
|
|
136
|
+
update(value, current),
|
|
137
|
+
...array.slice(position + 1)
|
|
138
|
+
];
|
|
139
|
+
}, options);
|
|
140
|
+
this.context.resetFieldMeta(`${name}.${index}`);
|
|
141
|
+
this.context.setFieldMeta(`${name}.${index}`, {
|
|
142
|
+
dirty: options?.should?.dirty !== false,
|
|
143
|
+
touched: options?.should?.touch !== false
|
|
144
|
+
});
|
|
145
|
+
};
|
|
146
|
+
remove(name, index, options) {
|
|
147
|
+
let position = index;
|
|
148
|
+
this.field.change(name, (current) => {
|
|
149
|
+
const array = current ?? [];
|
|
150
|
+
position = Math.max(Math.min(index, array.length - 1), 0);
|
|
151
|
+
return [...array.slice(0, position), ...array.slice(position + 1)];
|
|
152
|
+
}, {
|
|
153
|
+
...options,
|
|
154
|
+
should: {
|
|
155
|
+
...options?.should,
|
|
156
|
+
validate: false
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
this.context.persisted.setState((current) => {
|
|
160
|
+
const fields = { ...current.fields };
|
|
161
|
+
const length = get(current.values, (0, remeda.stringToPath)(name))?.length ?? 0;
|
|
162
|
+
for (let i = position; i < length; i++) {
|
|
163
|
+
const moving = current.fields[`${name}.${i + 1}`];
|
|
164
|
+
fields[`${name}.${i}`] = moving ?? defaultMeta;
|
|
165
|
+
}
|
|
166
|
+
delete fields[`${name}.${length}`];
|
|
167
|
+
return {
|
|
168
|
+
...current,
|
|
169
|
+
fields
|
|
170
|
+
};
|
|
171
|
+
});
|
|
172
|
+
if (options?.should?.validate !== false) this.context.validate(name, { type: "change" });
|
|
173
|
+
}
|
|
174
|
+
replace(name, value, options) {
|
|
175
|
+
this.context.resetFieldMeta(name);
|
|
176
|
+
this.field.change(name, value, options);
|
|
177
|
+
}
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
//#endregion
|
|
181
|
+
//#region src/utils/validate.ts
|
|
182
|
+
const validate = async (schema, input) => {
|
|
183
|
+
let result = schema["~standard"].validate(input);
|
|
184
|
+
if (result instanceof Promise) result = await result;
|
|
185
|
+
return result;
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
//#endregion
|
|
189
|
+
//#region src/form-context-api.ts
|
|
190
|
+
var FormContextApi = class {
|
|
191
|
+
options;
|
|
192
|
+
persisted;
|
|
193
|
+
store;
|
|
194
|
+
constructor(options) {
|
|
195
|
+
const values = (0, remeda.mergeDeep)(options.defaultValues, options.values ?? {});
|
|
196
|
+
this.options = options;
|
|
197
|
+
this.persisted = new _tanstack_store.Store({
|
|
198
|
+
values,
|
|
199
|
+
fields: {},
|
|
200
|
+
refs: {},
|
|
201
|
+
status: defaultStatus,
|
|
202
|
+
errors: {}
|
|
203
|
+
});
|
|
204
|
+
this.store = new _tanstack_store.Derived({
|
|
205
|
+
deps: [this.persisted],
|
|
206
|
+
fn: ({ currDepVals }) => {
|
|
207
|
+
const persisted = currDepVals[0];
|
|
208
|
+
const invalid = Object.values(persisted.errors).some((issues) => issues.length > 0);
|
|
209
|
+
const dirty = Object.values(persisted.fields).some((meta) => meta.dirty);
|
|
210
|
+
const fields = Object.fromEntries(Object.entries(persisted.fields).map(([key, meta]) => {
|
|
211
|
+
return [key, this.buildFieldMeta(key, meta, persisted.values, persisted.errors)];
|
|
212
|
+
}));
|
|
213
|
+
return {
|
|
214
|
+
...persisted,
|
|
215
|
+
fields,
|
|
216
|
+
status: {
|
|
217
|
+
...persisted.status,
|
|
218
|
+
submitted: persisted.status.submits > 0,
|
|
219
|
+
valid: !invalid,
|
|
220
|
+
dirty: persisted.status.dirty || dirty
|
|
221
|
+
}
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
get status() {
|
|
227
|
+
return this.store.state.status;
|
|
228
|
+
}
|
|
229
|
+
get values() {
|
|
230
|
+
return this.store.state.values;
|
|
231
|
+
}
|
|
232
|
+
get validator() {
|
|
233
|
+
const store = this.store.state;
|
|
234
|
+
const validate$1 = this.options.validate;
|
|
235
|
+
return {
|
|
236
|
+
change: (0, remeda.isFunction)(validate$1?.change) ? validate$1.change(store) : validate$1?.change,
|
|
237
|
+
submit: (0, remeda.isFunction)(validate$1?.submit) ? validate$1.submit(store) : validate$1?.submit ?? this.options.schema,
|
|
238
|
+
blur: (0, remeda.isFunction)(validate$1?.blur) ? validate$1.blur(store) : validate$1?.blur,
|
|
239
|
+
focus: (0, remeda.isFunction)(validate$1?.focus) ? validate$1.focus(store) : validate$1?.focus
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
buildFieldMeta = (fieldName, persistedMeta, values, errors) => {
|
|
243
|
+
const path = (0, remeda.stringToPath)(fieldName);
|
|
244
|
+
const value = get(values, path);
|
|
245
|
+
const defaultValue = get(this.options.defaultValues, path);
|
|
246
|
+
const invalid = errors[fieldName]?.length > 0;
|
|
247
|
+
const baseMeta = persistedMeta ?? defaultMeta;
|
|
248
|
+
return {
|
|
249
|
+
...baseMeta,
|
|
250
|
+
default: (0, remeda.isDeepEqual)(value, defaultValue),
|
|
251
|
+
pristine: !baseMeta.dirty,
|
|
252
|
+
valid: !invalid
|
|
253
|
+
};
|
|
254
|
+
};
|
|
255
|
+
setFieldMeta = (name, meta) => {
|
|
256
|
+
this.persisted.setState((current) => {
|
|
257
|
+
return {
|
|
258
|
+
...current,
|
|
259
|
+
fields: {
|
|
260
|
+
...current.fields,
|
|
261
|
+
[name]: {
|
|
262
|
+
...defaultMeta,
|
|
263
|
+
...this.persisted.state.fields[name],
|
|
264
|
+
...meta
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
};
|
|
268
|
+
});
|
|
269
|
+
};
|
|
270
|
+
resetFieldMeta = (name) => {
|
|
271
|
+
this.persisted.setState((current) => {
|
|
272
|
+
const fields = { ...current.fields };
|
|
273
|
+
const affected = Object.keys(current.fields).filter((key) => key.startsWith(name));
|
|
274
|
+
for (const key of affected) delete fields[key];
|
|
275
|
+
return {
|
|
276
|
+
...current,
|
|
277
|
+
fields
|
|
278
|
+
};
|
|
279
|
+
});
|
|
280
|
+
};
|
|
281
|
+
recomputeFieldMeta = (name) => {
|
|
282
|
+
this.persisted.setState((current) => {
|
|
283
|
+
const related = this.options.related?.[name] ?? [];
|
|
284
|
+
const updated = Object.keys(current.fields).filter((key) => key.startsWith(name) || related.includes(key)).reduce((acc, key) => {
|
|
285
|
+
return {
|
|
286
|
+
...acc,
|
|
287
|
+
[key]: this.buildFieldMeta(key, current.fields[key], current.values, current.errors)
|
|
288
|
+
};
|
|
289
|
+
}, {});
|
|
290
|
+
return {
|
|
291
|
+
...current,
|
|
292
|
+
fields: {
|
|
293
|
+
...current.fields,
|
|
294
|
+
...updated
|
|
295
|
+
}
|
|
296
|
+
};
|
|
297
|
+
});
|
|
298
|
+
};
|
|
299
|
+
setStatus = (status) => {
|
|
300
|
+
this.persisted.setState((current) => {
|
|
301
|
+
return {
|
|
302
|
+
...current,
|
|
303
|
+
status: {
|
|
304
|
+
...current.status,
|
|
305
|
+
...status
|
|
306
|
+
}
|
|
307
|
+
};
|
|
308
|
+
});
|
|
309
|
+
};
|
|
310
|
+
validate = async (field, options) => {
|
|
311
|
+
const validator = options?.type ? this.validator[options.type] : this.options.schema;
|
|
312
|
+
if (!validator) return [];
|
|
313
|
+
this.setStatus({ validating: true });
|
|
314
|
+
const { issues: allIssues } = await validate(validator, this.store.state.values);
|
|
315
|
+
if (!allIssues) {
|
|
316
|
+
this.persisted.setState((current) => {
|
|
317
|
+
return {
|
|
318
|
+
...current,
|
|
319
|
+
errors: {},
|
|
320
|
+
status: {
|
|
321
|
+
...current.status,
|
|
322
|
+
validating: false
|
|
323
|
+
}
|
|
324
|
+
};
|
|
325
|
+
});
|
|
326
|
+
return [];
|
|
327
|
+
}
|
|
328
|
+
const fields = field ? Array.isArray(field) ? field : [field] : void 0;
|
|
329
|
+
const related = fields?.flatMap((field$1) => this.options.related?.[field$1] ?? []) ?? [];
|
|
330
|
+
const affected = [...fields ?? [], ...related];
|
|
331
|
+
const issues = allIssues.filter((issue) => {
|
|
332
|
+
const path = issue.path?.join(".") ?? "root";
|
|
333
|
+
return !fields || affected.some((key) => path.startsWith(key));
|
|
334
|
+
});
|
|
335
|
+
const errors = issues.reduce((acc, issue) => {
|
|
336
|
+
const path = issue.path?.join(".") ?? "root";
|
|
337
|
+
return {
|
|
338
|
+
...acc,
|
|
339
|
+
[path]: [...acc[path] ?? [], issue]
|
|
340
|
+
};
|
|
341
|
+
}, {});
|
|
342
|
+
this.persisted.setState((current) => {
|
|
343
|
+
const existing = { ...current.errors };
|
|
344
|
+
for (const key of affected) delete existing[key];
|
|
345
|
+
return {
|
|
346
|
+
...current,
|
|
347
|
+
errors: {
|
|
348
|
+
...fields ? existing : {},
|
|
349
|
+
...errors
|
|
350
|
+
}
|
|
351
|
+
};
|
|
352
|
+
});
|
|
353
|
+
this.setStatus({ validating: false });
|
|
354
|
+
return issues;
|
|
355
|
+
};
|
|
356
|
+
};
|
|
357
|
+
|
|
358
|
+
//#endregion
|
|
359
|
+
//#region src/form-field-api.ts
|
|
360
|
+
var FormFieldApi = class {
|
|
361
|
+
constructor(context) {
|
|
362
|
+
this.context = context;
|
|
363
|
+
}
|
|
364
|
+
/**
|
|
365
|
+
* Changes the value of a specific field with optional control over side effects.
|
|
366
|
+
* @param name - The name of the field to change
|
|
367
|
+
* @param value - The new value to set for the field
|
|
368
|
+
* @param options - Optional configuration for controlling validation, dirty state, and touched state
|
|
369
|
+
*/
|
|
370
|
+
change = (name, updater, options) => {
|
|
371
|
+
const shouldDirty = options?.should?.dirty !== false;
|
|
372
|
+
const shouldTouch = options?.should?.touch !== false;
|
|
373
|
+
const shouldValidate = options?.should?.validate !== false;
|
|
374
|
+
this.context.persisted.setState((current) => {
|
|
375
|
+
const value = get(current.values, (0, remeda.stringToPath)(name));
|
|
376
|
+
const values = (0, remeda.setPath)(current.values, (0, remeda.stringToPath)(name), update(updater, value));
|
|
377
|
+
return {
|
|
378
|
+
...current,
|
|
379
|
+
values
|
|
380
|
+
};
|
|
381
|
+
});
|
|
382
|
+
if (shouldValidate) this.context.validate(name, { type: "change" });
|
|
383
|
+
(0, _tanstack_store.batch)(() => {
|
|
384
|
+
if (shouldDirty) this.context.setFieldMeta(name, { dirty: true });
|
|
385
|
+
if (shouldTouch) this.context.setFieldMeta(name, { touched: true });
|
|
386
|
+
});
|
|
387
|
+
};
|
|
388
|
+
focus = (name) => {
|
|
389
|
+
const ref = this.context.store.state.refs[name];
|
|
390
|
+
if (ref) ref.focus();
|
|
391
|
+
this.context.setFieldMeta(name, { touched: true });
|
|
392
|
+
this.context.validate(name, { type: "focus" });
|
|
393
|
+
};
|
|
394
|
+
blur = (name) => {
|
|
395
|
+
const ref = this.context.store.state.refs[name];
|
|
396
|
+
if (ref) ref.blur();
|
|
397
|
+
this.context.setFieldMeta(name, { blurred: true });
|
|
398
|
+
this.context.validate(name, { type: "blur" });
|
|
399
|
+
};
|
|
400
|
+
get = (name) => {
|
|
401
|
+
return get(this.context.store.state.values, (0, remeda.stringToPath)(name));
|
|
402
|
+
};
|
|
403
|
+
meta = (name) => {
|
|
404
|
+
const meta = this.context.store.state.fields[name];
|
|
405
|
+
if (meta) return meta;
|
|
406
|
+
const updated = this.context.buildFieldMeta(name, void 0, this.context.store.state.values, this.context.store.state.errors);
|
|
407
|
+
this.context.setFieldMeta(name, updated);
|
|
408
|
+
return updated;
|
|
409
|
+
};
|
|
410
|
+
register = (name) => {
|
|
411
|
+
return (element) => {
|
|
412
|
+
if (!element) return;
|
|
413
|
+
this.context.persisted.setState((current) => {
|
|
414
|
+
return {
|
|
415
|
+
...current,
|
|
416
|
+
refs: {
|
|
417
|
+
...current.refs,
|
|
418
|
+
[name]: element
|
|
419
|
+
}
|
|
420
|
+
};
|
|
421
|
+
});
|
|
422
|
+
};
|
|
423
|
+
};
|
|
424
|
+
errors = (name) => {
|
|
425
|
+
return this.context.store.state.errors[name] ?? [];
|
|
426
|
+
};
|
|
427
|
+
setErrors = (name, errors, options) => {
|
|
428
|
+
this.context.persisted.setState((current) => {
|
|
429
|
+
const existing = current.errors[name] ?? [];
|
|
430
|
+
let updated;
|
|
431
|
+
switch (options?.mode) {
|
|
432
|
+
case "append":
|
|
433
|
+
updated = [...existing, ...errors];
|
|
434
|
+
break;
|
|
435
|
+
case "keep":
|
|
436
|
+
updated = existing.length > 0 ? existing : errors;
|
|
437
|
+
break;
|
|
438
|
+
case "replace":
|
|
439
|
+
default:
|
|
440
|
+
updated = errors;
|
|
441
|
+
break;
|
|
442
|
+
}
|
|
443
|
+
return {
|
|
444
|
+
...current,
|
|
445
|
+
errors: {
|
|
446
|
+
...current.errors,
|
|
447
|
+
[name]: updated
|
|
448
|
+
}
|
|
449
|
+
};
|
|
450
|
+
});
|
|
451
|
+
};
|
|
452
|
+
reset = (name, options) => {
|
|
453
|
+
const path = (0, remeda.stringToPath)(name);
|
|
454
|
+
const defaultValue = get(this.context.options.defaultValues, path);
|
|
455
|
+
const value = options?.value ?? defaultValue;
|
|
456
|
+
this.context.persisted.setState((current) => {
|
|
457
|
+
const values = (0, remeda.setPath)(current.values, path, value);
|
|
458
|
+
const fields = { ...current.fields };
|
|
459
|
+
const refs = { ...current.refs };
|
|
460
|
+
const errors = { ...current.errors };
|
|
461
|
+
if (options?.meta) fields[name] = {
|
|
462
|
+
...options?.keep?.meta ? fields[name] ?? defaultMeta : defaultMeta,
|
|
463
|
+
...options.meta
|
|
464
|
+
};
|
|
465
|
+
else if (!options?.keep?.meta) delete fields[name];
|
|
466
|
+
if (!options?.keep?.refs) delete refs[name];
|
|
467
|
+
if (!options?.keep?.errors) delete errors[name];
|
|
468
|
+
return {
|
|
469
|
+
...current,
|
|
470
|
+
values,
|
|
471
|
+
fields,
|
|
472
|
+
refs,
|
|
473
|
+
errors
|
|
474
|
+
};
|
|
475
|
+
});
|
|
476
|
+
};
|
|
477
|
+
};
|
|
478
|
+
|
|
479
|
+
//#endregion
|
|
480
|
+
//#region src/form-api.ts
|
|
481
|
+
var FormApi = class {
|
|
482
|
+
context;
|
|
483
|
+
field;
|
|
484
|
+
array;
|
|
485
|
+
constructor(options) {
|
|
486
|
+
this.context = new FormContextApi(options);
|
|
487
|
+
this.field = new FormFieldApi(this.context);
|
|
488
|
+
this.array = new FormArrayFieldApi({
|
|
489
|
+
field: this.field,
|
|
490
|
+
context: this.context
|
|
491
|
+
});
|
|
492
|
+
}
|
|
493
|
+
"~mount" = () => {
|
|
494
|
+
return this.context.store.mount();
|
|
495
|
+
};
|
|
496
|
+
"~update" = (options) => {
|
|
497
|
+
this.context.options = options;
|
|
498
|
+
};
|
|
499
|
+
store = () => {
|
|
500
|
+
return this.context.store;
|
|
501
|
+
};
|
|
502
|
+
status = () => {
|
|
503
|
+
return this.context.store.state.status;
|
|
504
|
+
};
|
|
505
|
+
values = () => {
|
|
506
|
+
return this.context.store.state.values;
|
|
507
|
+
};
|
|
508
|
+
options = () => {
|
|
509
|
+
return this.context.options;
|
|
510
|
+
};
|
|
511
|
+
get validate() {
|
|
512
|
+
return this.context.validate;
|
|
513
|
+
}
|
|
514
|
+
submit = (onSuccess, onError) => async () => {
|
|
515
|
+
this.context.setStatus({
|
|
516
|
+
submitting: true,
|
|
517
|
+
dirty: true
|
|
518
|
+
});
|
|
519
|
+
const issues = await this.context.validate(void 0, { type: "submit" });
|
|
520
|
+
const valid = issues.length === 0;
|
|
521
|
+
if (valid) await onSuccess(this.context.store.state.values, this);
|
|
522
|
+
else await onError?.(issues, this);
|
|
523
|
+
this.context.setStatus({
|
|
524
|
+
submits: this.context.persisted.state.status.submits + 1,
|
|
525
|
+
submitting: false,
|
|
526
|
+
successful: valid
|
|
527
|
+
});
|
|
528
|
+
};
|
|
529
|
+
reset = (options) => {
|
|
530
|
+
this.context.persisted.setState((current) => {
|
|
531
|
+
return {
|
|
532
|
+
values: options?.values ?? this.context.options.defaultValues,
|
|
533
|
+
fields: options?.keep?.fields ? current.fields : {},
|
|
534
|
+
refs: options?.keep?.refs ? current.refs : {},
|
|
535
|
+
errors: options?.keep?.errors ? current.errors : {},
|
|
536
|
+
status: {
|
|
537
|
+
...defaultStatus,
|
|
538
|
+
...options?.status
|
|
539
|
+
}
|
|
540
|
+
};
|
|
541
|
+
});
|
|
542
|
+
};
|
|
543
|
+
};
|
|
544
|
+
|
|
545
|
+
//#endregion
|
|
546
|
+
//#region src/field-api.ts
|
|
547
|
+
var FieldApi = class {
|
|
548
|
+
_options;
|
|
549
|
+
form;
|
|
550
|
+
_store;
|
|
551
|
+
constructor(options) {
|
|
552
|
+
this._options = options;
|
|
553
|
+
this.form = options.form;
|
|
554
|
+
this._store = new _tanstack_store.Derived({
|
|
555
|
+
deps: [this.form.store()],
|
|
556
|
+
fn: ({ prevVal }) => {
|
|
557
|
+
const previous = prevVal;
|
|
558
|
+
const updated = {
|
|
559
|
+
value: this.form.field.get(this._options.name),
|
|
560
|
+
defaultValue: get(this.form.options().defaultValues, (0, remeda.stringToPath)(this._options.name)),
|
|
561
|
+
meta: this.form.field.meta(this._options.name),
|
|
562
|
+
errors: this.form.field.errors(this._options.name)
|
|
563
|
+
};
|
|
564
|
+
if (previous && (0, remeda.isDeepEqual)(previous, updated)) return previous;
|
|
565
|
+
return updated;
|
|
566
|
+
}
|
|
567
|
+
});
|
|
568
|
+
}
|
|
569
|
+
"~mount" = () => {
|
|
570
|
+
return this._store.mount();
|
|
571
|
+
};
|
|
572
|
+
"~update" = (options) => {
|
|
573
|
+
this._options = options;
|
|
574
|
+
};
|
|
575
|
+
options = () => {
|
|
576
|
+
return this._options;
|
|
577
|
+
};
|
|
578
|
+
store = () => {
|
|
579
|
+
return this._store;
|
|
580
|
+
};
|
|
581
|
+
state = () => {
|
|
582
|
+
return this._store.state;
|
|
583
|
+
};
|
|
584
|
+
focus = () => {
|
|
585
|
+
return this.form.field.focus(this._options.name);
|
|
586
|
+
};
|
|
587
|
+
blur = () => {
|
|
588
|
+
return this.form.field.blur(this._options.name);
|
|
589
|
+
};
|
|
590
|
+
/**
|
|
591
|
+
* Changes the value of this field with optional control over side effects.
|
|
592
|
+
* @param value - The new value to set for the field
|
|
593
|
+
* @param options - Optional configuration for controlling validation, dirty state, and touched state
|
|
594
|
+
*/
|
|
595
|
+
change = (value, options) => {
|
|
596
|
+
return this.form.field.change(this._options.name, value, options);
|
|
597
|
+
};
|
|
598
|
+
register = () => this.form.field.register(this._options.name);
|
|
599
|
+
/**
|
|
600
|
+
* Validates this specific field using the specified validation type.
|
|
601
|
+
* @param options - Optional validation options specifying the validation type ('change' | 'submit' | 'blur' | 'focus')
|
|
602
|
+
* @returns Promise resolving to an array of validation issues for this field
|
|
603
|
+
*/
|
|
604
|
+
validate = (options) => {
|
|
605
|
+
return this.form.validate(this._options.name, options);
|
|
606
|
+
};
|
|
607
|
+
/**
|
|
608
|
+
* Resets this field to its default value and optionally resets metadata and errors.
|
|
609
|
+
* @param options - Reset options for controlling what gets reset and what gets kept
|
|
610
|
+
*/
|
|
611
|
+
reset = (options) => {
|
|
612
|
+
return this.form.field.reset(this._options.name, options);
|
|
613
|
+
};
|
|
614
|
+
/**
|
|
615
|
+
* Sets validation errors for this specific field.
|
|
616
|
+
* @param errors - Array of validation errors to set
|
|
617
|
+
* @param mode - How to handle existing errors: 'replace' (default), 'append', or 'keep'
|
|
618
|
+
*/
|
|
619
|
+
setErrors = (errors, options) => {
|
|
620
|
+
return this.form.field.setErrors(this._options.name, errors, options);
|
|
621
|
+
};
|
|
622
|
+
};
|
|
623
|
+
const createFieldApi = (options) => {
|
|
624
|
+
return new FieldApi(options);
|
|
625
|
+
};
|
|
626
|
+
|
|
627
|
+
//#endregion
|
|
628
|
+
//#region src/array-field-api.ts
|
|
629
|
+
var ArrayFieldApi = class {
|
|
630
|
+
field;
|
|
631
|
+
constructor(options) {
|
|
632
|
+
this.field = new FieldApi(options);
|
|
633
|
+
}
|
|
634
|
+
get _options() {
|
|
635
|
+
return this.field.options();
|
|
636
|
+
}
|
|
637
|
+
"~mount" = () => {
|
|
638
|
+
return this.field.store().mount();
|
|
639
|
+
};
|
|
640
|
+
"~update" = (options) => {
|
|
641
|
+
this.field["~update"](options);
|
|
642
|
+
};
|
|
643
|
+
store = () => {
|
|
644
|
+
return this.field.store();
|
|
645
|
+
};
|
|
646
|
+
state = () => {
|
|
647
|
+
return this.field.store().state;
|
|
648
|
+
};
|
|
649
|
+
/**
|
|
650
|
+
* Appends a new item to the end of the array.
|
|
651
|
+
* @param value - The value to append to the array
|
|
652
|
+
* @param options - Optional configuration for controlling validation, dirty state, and touched state
|
|
653
|
+
*/
|
|
654
|
+
append = (value, options) => {
|
|
655
|
+
return this._options.form.array.append(this._options.name, value, options);
|
|
656
|
+
};
|
|
657
|
+
/**
|
|
658
|
+
* Prepends a new item to the beginning of the array.
|
|
659
|
+
* @param value - The value to prepend to the array
|
|
660
|
+
* @param options - Optional configuration for controlling validation, dirty state, and touched state
|
|
661
|
+
*/
|
|
662
|
+
prepend = (value, options) => {
|
|
663
|
+
return this._options.form.array.prepend(this._options.name, value, options);
|
|
664
|
+
};
|
|
665
|
+
/**
|
|
666
|
+
* Inserts a new item at the specified index in the array.
|
|
667
|
+
* @param index - The index at which to insert the value
|
|
668
|
+
* @param value - The value to insert into the array
|
|
669
|
+
* @param options - Optional configuration for controlling validation, dirty state, and touched state
|
|
670
|
+
*/
|
|
671
|
+
insert = (index, value, options) => {
|
|
672
|
+
return this._options.form.array.insert(this._options.name, index, value, options);
|
|
673
|
+
};
|
|
674
|
+
/**
|
|
675
|
+
* Updates an item at the specified index in the array.
|
|
676
|
+
* @param index - The index of the item to update
|
|
677
|
+
* @param value - The new value or updater function
|
|
678
|
+
* @param options - Optional configuration for controlling validation, dirty state, and touched state
|
|
679
|
+
*/
|
|
680
|
+
update = (index, value, options) => {
|
|
681
|
+
return this._options.form.array.update(this._options.name, index, value, options);
|
|
682
|
+
};
|
|
683
|
+
/**
|
|
684
|
+
* Removes an item at the specified index from the array.
|
|
685
|
+
* @param index - The index of the item to remove
|
|
686
|
+
* @param options - Optional configuration for controlling validation, dirty state, and touched state
|
|
687
|
+
*/
|
|
688
|
+
remove = (index, options) => {
|
|
689
|
+
return this._options.form.array.remove(this._options.name, index, options);
|
|
690
|
+
};
|
|
691
|
+
/**
|
|
692
|
+
* Swaps two items in the array by their indices.
|
|
693
|
+
* @param from - The index of the first item
|
|
694
|
+
* @param to - The index of the second item
|
|
695
|
+
* @param options - Optional configuration for controlling validation, dirty state, and touched state
|
|
696
|
+
*/
|
|
697
|
+
swap = (from, to, options) => {
|
|
698
|
+
return this._options.form.array.swap(this._options.name, from, to, options);
|
|
699
|
+
};
|
|
700
|
+
/**
|
|
701
|
+
* Moves an item from one index to another in the array.
|
|
702
|
+
* @param from - The index of the item to move
|
|
703
|
+
* @param to - The target index
|
|
704
|
+
* @param options - Optional configuration for controlling validation, dirty state, and touched state
|
|
705
|
+
*/
|
|
706
|
+
move = (from, to, options) => {
|
|
707
|
+
return this._options.form.array.move(this._options.name, from, to, options);
|
|
708
|
+
};
|
|
709
|
+
/**
|
|
710
|
+
* Replaces the entire array with a new value.
|
|
711
|
+
* @param value - The new array value or updater function
|
|
712
|
+
* @param options - Optional configuration for controlling validation, dirty state, and touched state
|
|
713
|
+
*/
|
|
714
|
+
replace = (value, options) => {
|
|
715
|
+
return this._options.form.array.replace(this._options.name, value, options);
|
|
716
|
+
};
|
|
717
|
+
/**
|
|
718
|
+
* Validates this specific array field using the specified validation type.
|
|
719
|
+
* @param options - Optional validation options specifying the validation type ('change' | 'submit' | 'blur' | 'focus')
|
|
720
|
+
* @returns Promise resolving to an array of validation issues for this field
|
|
721
|
+
*/
|
|
722
|
+
validate = (options) => {
|
|
723
|
+
return this._options.form.validate(this._options.name, options);
|
|
724
|
+
};
|
|
725
|
+
/**
|
|
726
|
+
* Resets this array field to its default value and optionally resets metadata and errors.
|
|
727
|
+
* @param options - Reset options for controlling what gets reset and what gets kept
|
|
728
|
+
*/
|
|
729
|
+
reset = (options) => {
|
|
730
|
+
return this._options.form.field.reset(this._options.name, options);
|
|
731
|
+
};
|
|
732
|
+
/**
|
|
733
|
+
* Sets validation errors for this specific array field.
|
|
734
|
+
* @param errors - Array of validation errors to set
|
|
735
|
+
* @param mode - How to handle existing errors: 'replace' (default), 'append', or 'keep'
|
|
736
|
+
*/
|
|
737
|
+
setErrors = (errors, options) => {
|
|
738
|
+
return this._options.form.field.setErrors(this._options.name, errors, options);
|
|
739
|
+
};
|
|
740
|
+
};
|
|
741
|
+
const createArrayFieldApi = (options) => {
|
|
742
|
+
return new ArrayFieldApi(options);
|
|
743
|
+
};
|
|
744
|
+
|
|
745
|
+
//#endregion
|
|
746
|
+
exports.ArrayFieldApi = ArrayFieldApi;
|
|
747
|
+
exports.FieldApi = FieldApi;
|
|
748
|
+
exports.FormApi = FormApi;
|
|
749
|
+
exports.createArrayFieldApi = createArrayFieldApi;
|
|
750
|
+
exports.createFieldApi = createFieldApi;
|
|
751
|
+
//# sourceMappingURL=index.cjs.map
|