@codeleap/form 6.8.0 → 7.0.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/fields/bool.js +15 -0
- package/dist/fields/bool.js.map +1 -0
- package/dist/fields/date.js +56 -0
- package/dist/fields/date.js.map +1 -0
- package/dist/fields/file.js +16 -0
- package/dist/fields/file.js.map +1 -0
- package/dist/fields/group.js +16 -0
- package/dist/fields/group.js.map +1 -0
- package/dist/fields/index.js +33 -0
- package/dist/fields/index.js.map +1 -0
- package/dist/fields/list.js +50 -0
- package/dist/fields/list.js.map +1 -0
- package/dist/fields/number.js +35 -0
- package/dist/fields/number.js.map +1 -0
- package/dist/fields/selectable.js +22 -0
- package/dist/fields/selectable.js.map +1 -0
- package/dist/fields/text.js +16 -0
- package/dist/fields/text.js.map +1 -0
- package/dist/hooks/index.js +3 -0
- package/dist/hooks/index.js.map +1 -0
- package/dist/hooks/useField.js +24 -0
- package/dist/hooks/useField.js.map +1 -0
- package/dist/hooks/useValidate.js +54 -0
- package/dist/hooks/useValidate.js.map +1 -0
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/Field.js +346 -0
- package/dist/lib/Field.js.map +1 -0
- package/dist/lib/Form.js +197 -0
- package/dist/lib/Form.js.map +1 -0
- package/dist/lib/factories.js +15 -0
- package/dist/lib/factories.js.map +1 -0
- package/dist/lib/index.js +3 -0
- package/dist/lib/index.js.map +1 -0
- package/dist/lib/useFieldBinding.js +48 -0
- package/dist/lib/useFieldBinding.js.map +1 -0
- package/dist/types/field.js +2 -0
- package/dist/types/field.js.map +1 -0
- package/dist/types/form.js +2 -0
- package/dist/types/form.js.map +1 -0
- package/dist/types/globals.js +2 -0
- package/dist/types/globals.js.map +1 -0
- package/dist/types/index.js +2 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/validation.js +2 -0
- package/dist/types/validation.js.map +1 -0
- package/dist/validators/index.js +2 -0
- package/dist/validators/index.js.map +1 -0
- package/dist/validators/zod.js +43 -0
- package/dist/validators/zod.js.map +1 -0
- package/package.json +10 -10
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { Field } from "../lib/Field";
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { zodValidator } from "../validators";
|
|
4
|
+
/**
|
|
5
|
+
* Field for boolean values (checkboxes, toggles). `Form.setValues` treats
|
|
6
|
+
* this field specially: it will only set a value when the incoming value is
|
|
7
|
+
* strictly `boolean`, because falsy coercion would prevent setting `false`.
|
|
8
|
+
*/
|
|
9
|
+
export class BooleanField extends Field {
|
|
10
|
+
constructor(options) {
|
|
11
|
+
super(Object.assign({ validate: zodValidator(z.boolean()) }, options));
|
|
12
|
+
this._type = "BOOLEAN";
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
//# sourceMappingURL=bool.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bool.js","sourceRoot":"","sources":["../../src/fields/bool.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,cAAc,CAAA;AACpC,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAEvB,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAA;AAS5C;;;;GAIG;AACH,MAAM,OAAO,YAAgD,SAAQ,KAAwB;IAG3F,YAAY,OAAsC;QAChD,KAAK,iBACH,QAAQ,EAAE,YAAY,CAAC,CAAC,CAAC,OAAO,EAAE,CAAwB,IACvD,OAAO,EACV,CAAA;QANJ,UAAK,GAAG,SAAS,CAAA;IAOjB,CAAC;CACF"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
var __rest = (this && this.__rest) || function (s, e) {
|
|
2
|
+
var t = {};
|
|
3
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
|
|
4
|
+
t[p] = s[p];
|
|
5
|
+
if (s != null && typeof Object.getOwnPropertySymbols === "function")
|
|
6
|
+
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
|
|
7
|
+
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
|
|
8
|
+
t[p[i]] = s[p[i]];
|
|
9
|
+
}
|
|
10
|
+
return t;
|
|
11
|
+
};
|
|
12
|
+
import { Field } from "../lib/Field";
|
|
13
|
+
import { z } from 'zod';
|
|
14
|
+
import { zodValidator } from "../validators";
|
|
15
|
+
import dayjs from 'dayjs';
|
|
16
|
+
function normalizeToDate(val) {
|
|
17
|
+
if (!val)
|
|
18
|
+
return undefined;
|
|
19
|
+
if (val instanceof Date)
|
|
20
|
+
return val;
|
|
21
|
+
if (dayjs.isDayjs(val))
|
|
22
|
+
return val.startOf('day').toDate();
|
|
23
|
+
if (typeof val === 'string') {
|
|
24
|
+
const parsed = dayjs(val);
|
|
25
|
+
return parsed.isValid() ? parsed.startOf('day').toDate() : undefined;
|
|
26
|
+
}
|
|
27
|
+
return undefined;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Field for `Date` values with optional `minimumDate` / `maximumDate` bounds.
|
|
31
|
+
* The built-in validator normalises the raw input via `dayjs` before running
|
|
32
|
+
* boundary checks, so it transparently accepts `Date` objects, dayjs instances,
|
|
33
|
+
* and ISO date strings. Invalid or unparseable values are normalised to
|
|
34
|
+
* `undefined`, which fails the `z.date()` check. Bounds are enforced at the
|
|
35
|
+
* start of the day (`startOf('day')`) for dayjs inputs.
|
|
36
|
+
*/
|
|
37
|
+
export class DateField extends Field {
|
|
38
|
+
constructor(options) {
|
|
39
|
+
const { minimumDate, maximumDate } = options, rest = __rest(options, ["minimumDate", "maximumDate"]);
|
|
40
|
+
const schema = z.preprocess((val) => {
|
|
41
|
+
const normalized = normalizeToDate(val);
|
|
42
|
+
if (!normalized || isNaN(normalized.getTime()))
|
|
43
|
+
return undefined;
|
|
44
|
+
return normalized;
|
|
45
|
+
}, z.date()
|
|
46
|
+
.refine(d => !minimumDate || d >= minimumDate, {
|
|
47
|
+
message: `Date must be on or after ${minimumDate === null || minimumDate === void 0 ? void 0 : minimumDate.toDateString()}`,
|
|
48
|
+
})
|
|
49
|
+
.refine(d => !maximumDate || d <= maximumDate, {
|
|
50
|
+
message: `Date must be on or before ${maximumDate === null || maximumDate === void 0 ? void 0 : maximumDate.toDateString()}`,
|
|
51
|
+
}));
|
|
52
|
+
super(Object.assign({ validate: zodValidator(schema) }, options));
|
|
53
|
+
this._type = "DATE";
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
//# sourceMappingURL=date.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"date.js","sourceRoot":"","sources":["../../src/fields/date.ts"],"names":[],"mappings":";;;;;;;;;;;AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,cAAc,CAAA;AACpC,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAEvB,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAA;AAC5C,OAAO,KAAK,MAAM,OAAO,CAAA;AAUzB,SAAS,eAAe,CAAC,GAAY;IACnC,IAAI,CAAC,GAAG;QAAE,OAAO,SAAS,CAAA;IAE1B,IAAI,GAAG,YAAY,IAAI;QAAE,OAAO,GAAG,CAAA;IACnC,IAAI,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC;QAAE,OAAO,GAAG,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,MAAM,EAAE,CAAA;IAC1D,IAAI,OAAO,GAAG,KAAK,QAAQ,EAAE,CAAC;QAC5B,MAAM,MAAM,GAAG,KAAK,CAAC,GAAG,CAAC,CAAA;QACzB,OAAO,MAAM,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,SAAS,CAAA;IACtE,CAAC;IAED,OAAO,SAAS,CAAA;AAClB,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,OAAO,SAA0C,SAAQ,KAAqB;IAGlF,YAAY,OAAmC;QAC7C,MAAM,EAAE,WAAW,EAAE,WAAW,KAAc,OAAO,EAAhB,IAAI,UAAK,OAAO,EAA/C,8BAAqC,CAAU,CAAA;QAErD,MAAM,MAAM,GAAG,CAAC,CAAC,UAAU,CAAC,CAAC,GAAY,EAAE,EAAE;YAC3C,MAAM,UAAU,GAAG,eAAe,CAAC,GAAG,CAAC,CAAA;YAEvC,IAAI,CAAC,UAAU,IAAI,KAAK,CAAC,UAAU,CAAC,OAAO,EAAE,CAAC;gBAAE,OAAO,SAAS,CAAA;YAChE,OAAO,UAAU,CAAA;QACnB,CAAC,EAAE,CAAC,CAAC,IAAI,EAAE;aACR,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,WAAW,IAAI,CAAC,IAAI,WAAW,EAAE;YAC7C,OAAO,EAAE,4BAA4B,WAAW,aAAX,WAAW,uBAAX,WAAW,CAAE,YAAY,EAAE,EAAE;SACnE,CAAC;aACD,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,WAAW,IAAI,CAAC,IAAI,WAAW,EAAE;YAC7C,OAAO,EAAE,6BAA6B,WAAW,aAAX,WAAW,uBAAX,WAAW,CAAE,YAAY,EAAE,EAAE;SACpE,CAAC,CACH,CAAA;QAED,KAAK,iBACH,QAAQ,EAAE,YAAY,CAAC,MAAM,CAAwB,IAClD,OAAO,EACV,CAAA;QAtBJ,UAAK,GAAG,MAAM,CAAA;IAuBd,CAAC;CACF"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { Field } from "../lib/Field";
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { zodValidator } from "../validators";
|
|
4
|
+
/**
|
|
5
|
+
* Field for file uploads. The value type is `any` because the shape of a
|
|
6
|
+
* "file" differs between web (`File` / `FileList`) and React Native (asset
|
|
7
|
+
* descriptor objects). Supply a custom `validate` function to enforce whatever
|
|
8
|
+
* shape your platform produces.
|
|
9
|
+
*/
|
|
10
|
+
export class FileField extends Field {
|
|
11
|
+
constructor(options) {
|
|
12
|
+
super(Object.assign({ validate: zodValidator(z.any()) }, options));
|
|
13
|
+
this._type = "FILE";
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=file.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"file.js","sourceRoot":"","sources":["../../src/fields/file.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,cAAc,CAAA;AACpC,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAEvB,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAA;AAO5C;;;;;GAKG;AACH,MAAM,OAAO,SAA0C,SAAQ,KAAoB;IAGjF,YAAY,OAAmC;QAC7C,KAAK,iBACH,QAAQ,EAAE,YAAY,CAAC,CAAC,CAAC,GAAG,EAAE,CAAwB,IACnD,OAAO,EACV,CAAA;QANJ,UAAK,GAAG,MAAM,CAAA;IAOd,CAAC;CACF"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { Field } from "../lib/Field";
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { zodValidator } from "../validators";
|
|
4
|
+
/**
|
|
5
|
+
* Field that represents a selected group or category, stored as a string key.
|
|
6
|
+
* Structurally identical to `TextField` but carries the `"group"` type tag so
|
|
7
|
+
* platform renderers can distinguish it and render a group-picker control
|
|
8
|
+
* instead of a free-text input.
|
|
9
|
+
*/
|
|
10
|
+
export class GroupField extends Field {
|
|
11
|
+
constructor(options) {
|
|
12
|
+
super(Object.assign({ validate: zodValidator(z.string()) }, options));
|
|
13
|
+
this._type = "group";
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=group.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"group.js","sourceRoot":"","sources":["../../src/fields/group.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,cAAc,CAAA;AACpC,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAEvB,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAA;AAQ5C;;;;;GAKG;AACH,MAAM,OAAO,UAA4C,SAAQ,KAAuB;IAGtF,YAAY,OAAoC;QAC9C,KAAK,iBACH,QAAQ,EAAE,YAAY,CAAC,CAAC,CAAC,MAAM,EAAE,CAAwB,IACtD,OAAO,EACV,CAAA;QANJ,UAAK,GAAG,OAAO,CAAA;IAOf,CAAC;CACF"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { fieldFactory } from "../lib/factories";
|
|
2
|
+
import { TextField } from "./text";
|
|
3
|
+
import { ListField, listFieldFactory } from "./list";
|
|
4
|
+
import { NumberField } from "./number";
|
|
5
|
+
import { BooleanField } from "./bool";
|
|
6
|
+
import { SelectableField } from "./selectable";
|
|
7
|
+
import { DateField } from "./date";
|
|
8
|
+
import { FileField } from "./file";
|
|
9
|
+
/**
|
|
10
|
+
* Convenience namespace that exposes factory functions for every built-in field
|
|
11
|
+
* type. Prefer these over calling constructors directly — they provide better
|
|
12
|
+
* generic inference for the `validate` option.
|
|
13
|
+
*
|
|
14
|
+
* ```ts
|
|
15
|
+
* const emailField = fields.text({ validate: zodValidator(z.string().email()) })
|
|
16
|
+
* const tagsField = fields.list({ item: fields.text(), defaultValue: [] })
|
|
17
|
+
* ```
|
|
18
|
+
*
|
|
19
|
+
* Note: `fields.list` is the only entry that is not wrapped with
|
|
20
|
+
* `fieldFactory`, because `ListField` requires the `item` option's type to be
|
|
21
|
+
* inferred at the call site.
|
|
22
|
+
*/
|
|
23
|
+
export const fields = {
|
|
24
|
+
text: fieldFactory(TextField),
|
|
25
|
+
list: listFieldFactory,
|
|
26
|
+
number: fieldFactory(NumberField),
|
|
27
|
+
boolean: fieldFactory(BooleanField),
|
|
28
|
+
selectable: fieldFactory(SelectableField),
|
|
29
|
+
date: fieldFactory(DateField),
|
|
30
|
+
file: fieldFactory(FileField),
|
|
31
|
+
};
|
|
32
|
+
export { TextField, ListField, NumberField, BooleanField, SelectableField, DateField, FileField, };
|
|
33
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/fields/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAA;AAC/C,OAAO,EAAE,SAAS,EAAE,MAAM,QAAQ,CAAA;AAClC,OAAO,EAAE,SAAS,EAAG,gBAAgB,EAAC,MAAM,QAAQ,CAAA;AACpD,OAAO,EAAE,WAAW,EAAE,MAAM,UAAU,CAAA;AACtC,OAAO,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAA;AACrC,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAA;AAC9C,OAAO,EAAE,SAAS,EAAE,MAAM,QAAQ,CAAA;AAClC,OAAO,EAAE,SAAS,EAAE,MAAM,QAAQ,CAAA;AAGlC;;;;;;;;;;;;;GAaG;AACH,MAAM,CAAC,MAAM,MAAM,GAAG;IACpB,IAAI,EAAE,YAAY,CAAC,SAAS,CAAC;IAC7B,IAAI,EAAE,gBAAgB;IACtB,MAAM,EAAE,YAAY,CAAC,WAAW,CAAC;IACjC,OAAO,EAAE,YAAY,CAAC,YAAY,CAAC;IACnC,UAAU,EAAE,YAAY,CAAC,eAAe,CAAC;IACzC,IAAI,EAAE,YAAY,CAAC,SAAS,CAAC;IAC7B,IAAI,EAAE,YAAY,CAAC,SAAS,CAAC;CAC9B,CAAA;AAED,OAAO,EACL,SAAS,EACT,SAAS,EACT,WAAW,EACX,YAAY,EACZ,eAAe,EACf,SAAS,EACT,SAAS,GACV,CAAA"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { Field } from "../lib/Field";
|
|
2
|
+
/**
|
|
3
|
+
* Field that holds an array of values, each validated by a delegate `item`
|
|
4
|
+
* field. The built-in validator iterates every element through
|
|
5
|
+
* `options.item.validate` and collects all failures into an error array. The
|
|
6
|
+
* list is considered valid only when every element passes. If `item` has no
|
|
7
|
+
* validator, the list is always valid.
|
|
8
|
+
*
|
|
9
|
+
* The `item` field acts as a prototype — it is not mounted independently and
|
|
10
|
+
* does not own its own atom. Do not call `use()` on it directly.
|
|
11
|
+
*/
|
|
12
|
+
export class ListField extends Field {
|
|
13
|
+
constructor(options) {
|
|
14
|
+
super(Object.assign(Object.assign({}, options), { validate: ((v, form) => {
|
|
15
|
+
var _a, _b, _c;
|
|
16
|
+
if (!((_a = options.item) === null || _a === void 0 ? void 0 : _a.validate))
|
|
17
|
+
return {
|
|
18
|
+
isValid: true
|
|
19
|
+
};
|
|
20
|
+
const errors = [];
|
|
21
|
+
for (const value of v) {
|
|
22
|
+
const validation = (_c = (_b = options.item) === null || _b === void 0 ? void 0 : _b.validate) === null || _c === void 0 ? void 0 : _c.call(_b, value);
|
|
23
|
+
if (!validation.isValid) {
|
|
24
|
+
errors.push(validation);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
if (errors.length) {
|
|
28
|
+
return {
|
|
29
|
+
isValid: false,
|
|
30
|
+
error: errors,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
return {
|
|
34
|
+
isValid: true,
|
|
35
|
+
};
|
|
36
|
+
}) }));
|
|
37
|
+
this._type = "LIST";
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Convenience factory for `ListField`. Unlike `fieldFactory`-based helpers,
|
|
42
|
+
* `ListField` requires an `item` option whose generic type must be inferred at
|
|
43
|
+
* the call site, so it cannot share the same factory wrapper. This function
|
|
44
|
+
* exists to provide a consistent `fields.list(...)` call signature alongside
|
|
45
|
+
* the other entries in the `fields` namespace.
|
|
46
|
+
*/
|
|
47
|
+
export function listFieldFactory(options) {
|
|
48
|
+
return new ListField(options);
|
|
49
|
+
}
|
|
50
|
+
//# sourceMappingURL=list.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"list.js","sourceRoot":"","sources":["../../src/fields/list.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,cAAc,CAAA;AA6BpC;;;;;;;;;GASG;AACH,MAAM,OAAO,SAAqC,SAAQ,KAAiD;IAGzG,YAAY,OAA4B;QACtC,KAAK,CAAC,gCACD,OAAO,KACV,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,EAAE;;gBACrB,IAAI,CAAC,CAAA,MAAA,OAAO,CAAC,IAAI,0CAAE,QAAQ,CAAA;oBAAE,OAAO;wBAClC,OAAO,EAAE,IAAI;qBACd,CAAA;gBAED,MAAM,MAAM,GAAG,EAAE,CAAA;gBAEjB,KAAK,MAAM,KAAK,IAAI,CAAC,EAAE,CAAC;oBACtB,MAAM,UAAU,GAAG,MAAA,MAAA,OAAO,CAAC,IAAI,0CAAE,QAAQ,mDAAG,KAAK,CAAC,CAAA;oBAElD,IAAI,CAAC,UAAU,CAAC,OAAO,EAAE,CAAC;wBACxB,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC,CAAA;oBACzB,CAAC;gBACH,CAAC;gBAED,IAAI,MAAM,CAAC,MAAM,EAAE,CAAC;oBAClB,OAAO;wBACL,OAAO,EAAE,KAAK;wBACd,KAAK,EAAE,MAAM;qBACd,CAAA;gBACH,CAAC;gBAED,OAAO;oBACL,OAAO,EAAE,IAAI;iBACd,CAAA;YACH,CAAC,CAAuB,GAC8C,CAAC,CAAA;QA/B3E,UAAK,GAAG,MAAM,CAAA;IAgCd,CAAC;CACF;AAED;;;;;;GAMG;AACH,MAAM,UAAU,gBAAgB,CAA2B,OAA4B;IACrF,OAAO,IAAI,SAAS,CAAC,OAAO,CAAC,CAAA;AAC/B,CAAC"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
var __rest = (this && this.__rest) || function (s, e) {
|
|
2
|
+
var t = {};
|
|
3
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
|
|
4
|
+
t[p] = s[p];
|
|
5
|
+
if (s != null && typeof Object.getOwnPropertySymbols === "function")
|
|
6
|
+
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
|
|
7
|
+
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
|
|
8
|
+
t[p[i]] = s[p[i]];
|
|
9
|
+
}
|
|
10
|
+
return t;
|
|
11
|
+
};
|
|
12
|
+
import { Field } from "../lib/Field";
|
|
13
|
+
import { z } from 'zod';
|
|
14
|
+
import { zodValidator } from "../validators";
|
|
15
|
+
const MAX_VALID_DIGITS = 1000000000000000; // maximum number of digits that the input supports to perform operations
|
|
16
|
+
/**
|
|
17
|
+
* Field for numeric values. Supports both single numbers and arrays of numbers
|
|
18
|
+
* (range / multi-handle sliders) — the mode is inferred from whether
|
|
19
|
+
* `defaultValue` is an array. `min` defaults to `0` and `max` defaults to
|
|
20
|
+
* `1_000_000_000_000_000` (the maximum safe integer range the underlying input
|
|
21
|
+
* supports for arithmetic operations). Values outside the range fail
|
|
22
|
+
* validation.
|
|
23
|
+
*/
|
|
24
|
+
export class NumberField extends Field {
|
|
25
|
+
constructor(options) {
|
|
26
|
+
const { min = 0, max = MAX_VALID_DIGITS, defaultValue } = options, others = __rest(options, ["min", "max", "defaultValue"]);
|
|
27
|
+
const isMultiple = Array.isArray(defaultValue);
|
|
28
|
+
const zScheme = z.number().min(min).max(max);
|
|
29
|
+
super(Object.assign({ validate: zodValidator(isMultiple ? z.array(zScheme) : zScheme), min,
|
|
30
|
+
max,
|
|
31
|
+
defaultValue }, others));
|
|
32
|
+
this._type = "NUMBER";
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
//# sourceMappingURL=number.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"number.js","sourceRoot":"","sources":["../../src/fields/number.ts"],"names":[],"mappings":";;;;;;;;;;;AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,cAAc,CAAA;AACpC,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAEvB,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAA;AAY5C,MAAM,gBAAgB,GAAG,gBAAgB,CAAA,CAAC,yEAAyE;AAEnH;;;;;;;GAOG;AACH,MAAM,OAAO,WAA8C,SAAQ,KAAkC;IAGnG,YAAY,OAAqC;QAC/C,MAAM,EAAE,GAAG,GAAG,CAAC,EAAE,GAAG,GAAG,gBAAgB,EAAE,YAAY,KAAgB,OAAO,EAAlB,MAAM,UAAK,OAAO,EAAtE,8BAA4D,CAAU,CAAA;QAE5E,MAAM,UAAU,GAAG,KAAK,CAAC,OAAO,CAAC,YAAY,CAAC,CAAA;QAE9C,MAAM,OAAO,GAAG,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;QAE5C,KAAK,CAAC,gBACJ,QAAQ,EAAE,YAAY,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,CAAwB,EACtF,GAAG;YACH,GAAG;YACH,YAAY,IACT,MAAM,CACsB,CAAC,CAAA;QAfpC,UAAK,GAAG,QAAQ,CAAA;IAgBhB,CAAC;CACF"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { Field } from "../lib/Field";
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { zodValidator } from "../validators";
|
|
4
|
+
/**
|
|
5
|
+
* Field for single or multi-select inputs whose options are a fixed list of
|
|
6
|
+
* string/number keys. `minItems` defaults to `1` (at least one selection
|
|
7
|
+
* required) and `maxItems` defaults to the length of the provided `options`
|
|
8
|
+
* array (all items selectable). The built-in validator accepts either a single
|
|
9
|
+
* value or an array, so the same field type covers both radio-style and
|
|
10
|
+
* checkbox-style UIs.
|
|
11
|
+
*/
|
|
12
|
+
export class SelectableField extends Field {
|
|
13
|
+
constructor(options) {
|
|
14
|
+
var _a;
|
|
15
|
+
const { minItems = 1, maxItems = (_a = options === null || options === void 0 ? void 0 : options.options) === null || _a === void 0 ? void 0 : _a.length } = options;
|
|
16
|
+
const zScheme = z.string().or(z.number());
|
|
17
|
+
super(Object.assign({ validate: zodValidator(z.array(zScheme).min(minItems).max(maxItems).or(zScheme)), minItems,
|
|
18
|
+
maxItems }, options));
|
|
19
|
+
this._type = "SELECTABLE";
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
//# sourceMappingURL=selectable.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"selectable.js","sourceRoot":"","sources":["../../src/fields/selectable.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,cAAc,CAAA;AACpC,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAEvB,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAA;AAc5C;;;;;;;GAOG;AACH,MAAM,OAAO,eAA0E,SAAQ,KAAkB;IAG/G,YAAY,OAA4C;;QACtD,MAAM,EAAE,QAAQ,GAAG,CAAC,EAAE,QAAQ,GAAG,MAAA,OAAO,aAAP,OAAO,uBAAP,OAAO,CAAE,OAAO,0CAAE,MAAM,EAAE,GAAG,OAAO,CAAA;QAErE,MAAM,OAAO,GAAG,CAAC,CAAC,MAAM,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAA;QAEzC,KAAK,iBACH,QAAQ,EAAE,YAAY,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC,EAAE,CAAC,OAAO,CAAC,CAAwB,EACvG,QAAQ;YACR,QAAQ,IACL,OAAO,EACV,CAAA;QAZJ,UAAK,GAAG,YAAY,CAAA;IAapB,CAAC;CACF"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { Field } from "../lib/Field";
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { zodValidator } from "../validators";
|
|
4
|
+
/**
|
|
5
|
+
* Field for plain string values. Ships with a `z.string()` fallback validator
|
|
6
|
+
* so the field is always valid when no custom `validate` option is provided.
|
|
7
|
+
* Accepts `secure` (password masking) and `multiline` hints that are forwarded
|
|
8
|
+
* as props to the underlying input component.
|
|
9
|
+
*/
|
|
10
|
+
export class TextField extends Field {
|
|
11
|
+
constructor(options) {
|
|
12
|
+
super(Object.assign({ validate: zodValidator(z.string().optional()) }, options));
|
|
13
|
+
this._type = "TEXT";
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=text.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"text.js","sourceRoot":"","sources":["../../src/fields/text.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,cAAc,CAAA;AACpC,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAEvB,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAA;AAY5C;;;;;GAKG;AACH,MAAM,OAAO,SAA0C,SAAQ,KAAuB;IAGpF,YAAY,OAAmC;QAC7C,KAAK,iBACH,QAAQ,EAAE,YAAY,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAwB,IACjE,OAAO,EACV,CAAA;QANJ,UAAK,GAAG,MAAM,CAAA;IAOd,CAAC;CACF"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/hooks/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAA;AACrC,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAA"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { useMemo } from "react";
|
|
2
|
+
/**
|
|
3
|
+
* Convenience hook for components that accept an optional `field` prop. When
|
|
4
|
+
* `field` is provided it is used directly; when it is absent (falsy), a
|
|
5
|
+
* temporary field created by `defaultField` is memoised for the component's
|
|
6
|
+
* lifetime.
|
|
7
|
+
*
|
|
8
|
+
* This lets components remain uncontrolled by default (using their own
|
|
9
|
+
* internal field) while still accepting external control when a `field` prop
|
|
10
|
+
* is supplied — without conditional hook calls.
|
|
11
|
+
*
|
|
12
|
+
* `params` are forwarded to `field.use()` in either branch, so imperative
|
|
13
|
+
* ref bindings work regardless of which field is active.
|
|
14
|
+
*/
|
|
15
|
+
export function useField(field, params, defaultField) {
|
|
16
|
+
if (field) {
|
|
17
|
+
return field.use(params === null || params === void 0 ? void 0 : params[0], params === null || params === void 0 ? void 0 : params[1]);
|
|
18
|
+
}
|
|
19
|
+
const tempField = useMemo(() => {
|
|
20
|
+
return defaultField();
|
|
21
|
+
}, []);
|
|
22
|
+
return tempField.use(params[0], params === null || params === void 0 ? void 0 : params[1]);
|
|
23
|
+
}
|
|
24
|
+
//# sourceMappingURL=useField.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useField.js","sourceRoot":"","sources":["../../src/hooks/useField.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,OAAO,CAAA;AAG/B;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,QAAQ,CAAkC,KAAQ,EAAE,MAA4B,EAAE,YAAqB;IACrH,IAAI,KAAK,EAAE,CAAC;QACV,OAAO,KAAK,CAAC,GAAG,CAAC,MAAM,aAAN,MAAM,uBAAN,MAAM,CAAG,CAAC,CAAC,EAAE,MAAM,aAAN,MAAM,uBAAN,MAAM,CAAG,CAAC,CAAC,CAAyB,CAAA;IACpE,CAAC;IAED,MAAM,SAAS,GAAG,OAAO,CAAC,GAAG,EAAE;QAC7B,OAAO,YAAY,EAAE,CAAA;IACvB,CAAC,EAAE,EAAE,CAAC,CAAA;IAEN,OAAO,SAAS,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,MAAM,aAAN,MAAM,uBAAN,MAAM,CAAG,CAAC,CAAC,CAAyB,CAAA;AACtE,CAAC"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { useCallback, useRef, useState } from 'react';
|
|
2
|
+
import { ValidationError } from '../lib/Field';
|
|
3
|
+
/**
|
|
4
|
+
* Standalone validation hook for values managed outside a `Field` instance.
|
|
5
|
+
* Useful when you have a raw state value and a validator function but do not
|
|
6
|
+
* want to create a full `Field` object.
|
|
7
|
+
*
|
|
8
|
+
* Error display follows the same blur policy as `Field.useValidation`: if
|
|
9
|
+
* `value` is `undefined` on the first render (`startedUnset`), the error is
|
|
10
|
+
* hidden until the user has blurred the input. Call the returned
|
|
11
|
+
* `onInputBlurred` handler on the input's blur event to trigger visibility.
|
|
12
|
+
*
|
|
13
|
+
* `message` is resolved from `readableError` first, then from the first
|
|
14
|
+
* element's `.message` when `error` is an array.
|
|
15
|
+
*
|
|
16
|
+
* A {@link ValidationError} thrown by `providedValidate` is caught and
|
|
17
|
+
* normalised; other exceptions propagate.
|
|
18
|
+
*/
|
|
19
|
+
export const useValidate = (value, providedValidate) => {
|
|
20
|
+
var _a, _b, _c, _d;
|
|
21
|
+
const [hasBlurred, setHasBlurred] = useState(false);
|
|
22
|
+
const isUnset = typeof value === 'undefined';
|
|
23
|
+
const startedUnset = useRef(isUnset).current;
|
|
24
|
+
const validate = useCallback((value) => {
|
|
25
|
+
try {
|
|
26
|
+
const result = providedValidate === null || providedValidate === void 0 ? void 0 : providedValidate(value, {});
|
|
27
|
+
return result;
|
|
28
|
+
}
|
|
29
|
+
catch (e) {
|
|
30
|
+
if (e instanceof ValidationError) {
|
|
31
|
+
return {
|
|
32
|
+
isValid: false,
|
|
33
|
+
error: e.data
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
throw e;
|
|
37
|
+
}
|
|
38
|
+
}, []);
|
|
39
|
+
const onInputBlurred = useCallback(() => {
|
|
40
|
+
setHasBlurred(true);
|
|
41
|
+
}, []);
|
|
42
|
+
const validation = validate(value);
|
|
43
|
+
const isValid = (_a = validation === null || validation === void 0 ? void 0 : validation.isValid) !== null && _a !== void 0 ? _a : true;
|
|
44
|
+
const isInvalid = !isValid;
|
|
45
|
+
const message = (_b = validation === null || validation === void 0 ? void 0 : validation.readableError) !== null && _b !== void 0 ? _b : (_d = (_c = validation === null || validation === void 0 ? void 0 : validation.error) === null || _c === void 0 ? void 0 : _c[0]) === null || _d === void 0 ? void 0 : _d.message;
|
|
46
|
+
const errorDisplayRequiresBlur = startedUnset;
|
|
47
|
+
const showError = isInvalid && (errorDisplayRequiresBlur ? hasBlurred : true);
|
|
48
|
+
return {
|
|
49
|
+
onInputBlurred,
|
|
50
|
+
showError,
|
|
51
|
+
message,
|
|
52
|
+
};
|
|
53
|
+
};
|
|
54
|
+
//# sourceMappingURL=useValidate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useValidate.js","sourceRoot":"","sources":["../../src/hooks/useValidate.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAA;AAErD,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAA;AAE9C;;;;;;;;;;;;;;;GAeG;AACH,MAAM,CAAC,MAAM,WAAW,GAAG,CAAsC,KAAQ,EAAE,gBAAmB,EAAE,EAAE;;IAChG,MAAM,CAAC,UAAU,EAAE,aAAa,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAA;IAEnD,MAAM,OAAO,GAAG,OAAO,KAAK,KAAK,WAAW,CAAA;IAE5C,MAAM,YAAY,GAAG,MAAM,CAAC,OAAO,CAAC,CAAC,OAAO,CAAA;IAE5C,MAAM,QAAQ,GAAG,WAAW,CAAC,CAAC,KAAQ,EAAE,EAAE;QACxC,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,gBAAgB,aAAhB,gBAAgB,uBAAhB,gBAAgB,CAAG,KAAK,EAAE,EAAE,CAAC,CAAA;YAE5C,OAAO,MAAM,CAAA;QACf,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,IAAI,CAAC,YAAY,eAAe,EAAE,CAAC;gBACjC,OAAO;oBACL,OAAO,EAAE,KAAK;oBACd,KAAK,EAAE,CAAC,CAAC,IAAI;iBACd,CAAA;YACH,CAAC;YAED,MAAM,CAAC,CAAA;QACT,CAAC;IACH,CAAC,EAAE,EAAE,CAAC,CAAA;IAEN,MAAM,cAAc,GAAG,WAAW,CAAC,GAAG,EAAE;QACtC,aAAa,CAAC,IAAI,CAAC,CAAA;IACrB,CAAC,EAAE,EAAE,CAAC,CAAA;IAEN,MAAM,UAAU,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAA;IAElC,MAAM,OAAO,GAAG,MAAA,UAAU,aAAV,UAAU,uBAAV,UAAU,CAAE,OAAO,mCAAI,IAAI,CAAA;IAE3C,MAAM,SAAS,GAAG,CAAC,OAAO,CAAA;IAE1B,MAAM,OAAO,GAAG,MAAA,UAAU,aAAV,UAAU,uBAAV,UAAU,CAAE,aAAa,mCAAI,MAAA,MAAA,UAAU,aAAV,UAAU,uBAAV,UAAU,CAAE,KAAK,0CAAG,CAAC,CAAC,0CAAE,OAAO,CAAA;IAE5E,MAAM,wBAAwB,GAAG,YAAY,CAAA;IAE7C,MAAM,SAAS,GAAG,SAAS,IAAI,CAAC,wBAAwB,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,CAAA;IAE7E,OAAO;QACL,cAAc;QACd,SAAS;QACT,OAAO;KACR,CAAA;AACH,CAAC,CAAA"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,UAAU,CAAA;AACxB,cAAc,SAAS,CAAA;AACvB,cAAc,OAAO,CAAA;AACrB,cAAc,cAAc,CAAA;AAC5B,cAAc,SAAS,CAAA"}
|
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
import { atom } from 'nanostores';
|
|
2
|
+
import { TypeGuards } from '@codeleap/types';
|
|
3
|
+
import { useStore } from '@nanostores/react';
|
|
4
|
+
import { useFieldBinding } from './useFieldBinding';
|
|
5
|
+
import { createRef, useRef, useState } from 'react';
|
|
6
|
+
import { logger } from '@codeleap/logger';
|
|
7
|
+
/**
|
|
8
|
+
* Thrown inside a validator function to signal a structured validation failure
|
|
9
|
+
* without propagating as an unhandled exception. The `data` payload is forwarded
|
|
10
|
+
* verbatim as the `error` field of the resulting {@link ValidationResult}.
|
|
11
|
+
*
|
|
12
|
+
* Throw this instead of returning `{ isValid: false }` when you need to exit
|
|
13
|
+
* validation from a nested helper that cannot easily return a value.
|
|
14
|
+
*/
|
|
15
|
+
export class ValidationError extends Error {
|
|
16
|
+
constructor(data) {
|
|
17
|
+
super();
|
|
18
|
+
this.data = data;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Base class for all form fields. Owns the nanostores atom that holds the
|
|
23
|
+
* field's current value, runs synchronous validation on demand, and exposes
|
|
24
|
+
* React hooks (`use`, `useValue`, `useValidation`) that components call to
|
|
25
|
+
* subscribe to reactive updates.
|
|
26
|
+
*
|
|
27
|
+
* **Validation lifecycle**
|
|
28
|
+
* Validation is always synchronous and called eagerly — there is no deferred
|
|
29
|
+
* or debounced step. `validate()` runs against the current atom value each
|
|
30
|
+
* time it is called, so repeated calls are cheap but not memoised. Error
|
|
31
|
+
* visibility is decoupled from validity: errors are hidden until
|
|
32
|
+
* `revealError()` is called (or the field has been blurred while starting
|
|
33
|
+
* with an undefined value), allowing UX to control when messages appear.
|
|
34
|
+
*
|
|
35
|
+
* **Initialization order constraint**
|
|
36
|
+
* The constructor calls `loadState()` synchronously. If `defaultValue` is a
|
|
37
|
+
* Promise or a function that returns a Promise, the atom is initialised with
|
|
38
|
+
* `undefined` immediately and the resolved value is set asynchronously once
|
|
39
|
+
* the promise settles. Do not read `initialValue` before the promise resolves.
|
|
40
|
+
*
|
|
41
|
+
* **Platform methods**
|
|
42
|
+
* `measurePosition`, `scrollTo`, and `getPadding` delegate to static method
|
|
43
|
+
* slots (`methodMeasurePosition`, `methodScrollTo`, `methodGetPadding`) that
|
|
44
|
+
* must be assigned by the platform layer (web or mobile) before use; calling
|
|
45
|
+
* them without an implementation throws.
|
|
46
|
+
*
|
|
47
|
+
* **Prop transformers**
|
|
48
|
+
* `props()` pipes the field's properties through every registered transformer
|
|
49
|
+
* in insertion order. Transformers are global and shared across all field
|
|
50
|
+
* instances via `Field.transformers`.
|
|
51
|
+
*/
|
|
52
|
+
export class Field {
|
|
53
|
+
/**
|
|
54
|
+
* Marks the field's error as visible. Typically called by `Form.validate`
|
|
55
|
+
* when `revealErrors: true` is passed, or imperatively after a failed submit.
|
|
56
|
+
* Has no effect on the underlying validity — only on whether the error
|
|
57
|
+
* message is shown to the user.
|
|
58
|
+
*/
|
|
59
|
+
revealError() {
|
|
60
|
+
this.errorRevealed.set(true);
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Resets error visibility to hidden without changing the field's value or
|
|
64
|
+
* validity state. Useful when clearing a form section programmatically
|
|
65
|
+
* while preserving the current value.
|
|
66
|
+
*/
|
|
67
|
+
hideError() {
|
|
68
|
+
this.errorRevealed.set(false);
|
|
69
|
+
}
|
|
70
|
+
get isErrorRevealed() {
|
|
71
|
+
return this.errorRevealed.get();
|
|
72
|
+
}
|
|
73
|
+
constructor(options) {
|
|
74
|
+
this.properties = {};
|
|
75
|
+
this.options = options;
|
|
76
|
+
this.ref = createRef();
|
|
77
|
+
this.loadState();
|
|
78
|
+
this.errorRevealed = atom(false);
|
|
79
|
+
this.setValue = this.setValue.bind(this);
|
|
80
|
+
this.use = this.use.bind(this);
|
|
81
|
+
this.useBinding = this.useBinding.bind(this);
|
|
82
|
+
this.revealError = this.revealError.bind(this);
|
|
83
|
+
this.hideError = this.hideError.bind(this);
|
|
84
|
+
this.properties = this.toInternalProperties(options);
|
|
85
|
+
}
|
|
86
|
+
get name() {
|
|
87
|
+
return this.options.name;
|
|
88
|
+
}
|
|
89
|
+
get value() {
|
|
90
|
+
return this.state.get();
|
|
91
|
+
}
|
|
92
|
+
get isValid() {
|
|
93
|
+
const res = this.validate();
|
|
94
|
+
return res.isValid;
|
|
95
|
+
}
|
|
96
|
+
toInternalProperties(options) {
|
|
97
|
+
const internalKeys = new Set(['name', 'defaultValue', 'state', 'validate', 'loader', 'onValueChange']);
|
|
98
|
+
const values = Object.assign({ name: this.name }, Object.fromEntries(Object.entries(options).filter(([key]) => !internalKeys.has(key))));
|
|
99
|
+
return Object.assign({ field: this }, values);
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Replaces the field's internal atom with a slice of the owning `Form`'s
|
|
103
|
+
* global state atom and migrates the current value into it. Called
|
|
104
|
+
* automatically by `Form.attachState()` during construction — do not call
|
|
105
|
+
* this manually unless you are building a custom form container.
|
|
106
|
+
*/
|
|
107
|
+
attach(to) {
|
|
108
|
+
const val = this.value;
|
|
109
|
+
this.state = to;
|
|
110
|
+
this.setValue(val);
|
|
111
|
+
}
|
|
112
|
+
setValue(to) {
|
|
113
|
+
if (this.options.onValueChange)
|
|
114
|
+
this.options.onValueChange(to);
|
|
115
|
+
return this.state.set(to);
|
|
116
|
+
}
|
|
117
|
+
useValue() {
|
|
118
|
+
const value = useStore(this.state);
|
|
119
|
+
return value;
|
|
120
|
+
}
|
|
121
|
+
resetValue() {
|
|
122
|
+
this.setValue(this.initialValue);
|
|
123
|
+
this.errorRevealed.set(false);
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Primary React hook for consuming a field inside a component. Subscribes to
|
|
127
|
+
* the field's value atom and validation state, and optionally wires an
|
|
128
|
+
* imperative ref handle via `useFieldBinding`. Must be called
|
|
129
|
+
* unconditionally at the component's top level.
|
|
130
|
+
*
|
|
131
|
+
* The `changed` flag compares the current value to the value captured at
|
|
132
|
+
* field construction time (or after the last `resetValue`), not to any
|
|
133
|
+
* previous render.
|
|
134
|
+
*/
|
|
135
|
+
use(impl, deps) {
|
|
136
|
+
const value = this.useValue();
|
|
137
|
+
const validation = this.useValidation();
|
|
138
|
+
// Yes, this is dangerous and doesn't follow the rules, but not passing an implementation to imperative handle after passing it once would break the app anyway
|
|
139
|
+
if (impl) {
|
|
140
|
+
this.useBinding(impl, deps);
|
|
141
|
+
}
|
|
142
|
+
const changed = value != this.initialValue;
|
|
143
|
+
return {
|
|
144
|
+
validation,
|
|
145
|
+
value,
|
|
146
|
+
setValue: this.setValue,
|
|
147
|
+
changed,
|
|
148
|
+
representation: this.toRepresentation(value),
|
|
149
|
+
options: this.options,
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
useBinding(...args) {
|
|
153
|
+
useFieldBinding(this.ref, ...args);
|
|
154
|
+
}
|
|
155
|
+
changed() {
|
|
156
|
+
return this.state.get() != this.initialValue;
|
|
157
|
+
}
|
|
158
|
+
// If we make this async, the js engine will not delay further execution while the funcion is not done. This way we wait until we know wheter there's a promise or not
|
|
159
|
+
loadState() {
|
|
160
|
+
var _a, _b, _c;
|
|
161
|
+
let defaultValuePromise = undefined;
|
|
162
|
+
let defaultValue = undefined;
|
|
163
|
+
if (TypeGuards.isFunction(this.options.defaultValue)) {
|
|
164
|
+
const v = this.options.defaultValue();
|
|
165
|
+
if (TypeGuards.isPromise(v)) {
|
|
166
|
+
defaultValuePromise = v;
|
|
167
|
+
}
|
|
168
|
+
else {
|
|
169
|
+
defaultValue = v;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
else {
|
|
173
|
+
const v = (_a = this.options) === null || _a === void 0 ? void 0 : _a.defaultValue;
|
|
174
|
+
if (TypeGuards.isPromise(v)) {
|
|
175
|
+
defaultValuePromise = v;
|
|
176
|
+
}
|
|
177
|
+
else {
|
|
178
|
+
defaultValue = v;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
this.state = (_c = (_b = this === null || this === void 0 ? void 0 : this.options) === null || _b === void 0 ? void 0 : _b.state) !== null && _c !== void 0 ? _c : atom(defaultValue);
|
|
182
|
+
if (!defaultValuePromise) {
|
|
183
|
+
this.initialValue = defaultValue;
|
|
184
|
+
}
|
|
185
|
+
if (!!defaultValuePromise) {
|
|
186
|
+
this.log('debug', 'Waiting for initial value');
|
|
187
|
+
return defaultValuePromise
|
|
188
|
+
.then((v) => {
|
|
189
|
+
this.state.set(v);
|
|
190
|
+
this.initialValue = v;
|
|
191
|
+
this.log('debug', `Got initial value`, v);
|
|
192
|
+
})
|
|
193
|
+
.catch(e => {
|
|
194
|
+
this.log('error', `Failed to resolve default value`, e);
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
else {
|
|
198
|
+
return Promise.resolve();
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
formatLog(...args) {
|
|
202
|
+
const [firstArgument, ...otherArgs] = args;
|
|
203
|
+
let PREFIX = `(FIELD: ${this.name})`;
|
|
204
|
+
if (TypeGuards.isString(firstArgument)) {
|
|
205
|
+
PREFIX += ` ${firstArgument}`;
|
|
206
|
+
}
|
|
207
|
+
else {
|
|
208
|
+
otherArgs.unshift(firstArgument);
|
|
209
|
+
}
|
|
210
|
+
const logArgs = [
|
|
211
|
+
PREFIX,
|
|
212
|
+
...otherArgs
|
|
213
|
+
];
|
|
214
|
+
return logArgs;
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Runs the field's validator synchronously against `value` (or the current
|
|
218
|
+
* atom value when omitted). A {@link ValidationError} thrown inside the
|
|
219
|
+
* validator is caught and converted to `{ isValid: false, error: e.data }`;
|
|
220
|
+
* any other exception is re-thrown.
|
|
221
|
+
*
|
|
222
|
+
* This method is called on every render inside `useValidation` — keep
|
|
223
|
+
* validators fast and free of side effects.
|
|
224
|
+
*/
|
|
225
|
+
validate(value) {
|
|
226
|
+
const validate = this.options.validate;
|
|
227
|
+
const valueToCheck = TypeGuards.isUndefined(value) ? this.state.get() : this.toInternalValue(value);
|
|
228
|
+
try {
|
|
229
|
+
const result = validate(valueToCheck, {});
|
|
230
|
+
return result;
|
|
231
|
+
}
|
|
232
|
+
catch (e) {
|
|
233
|
+
if (e instanceof ValidationError) {
|
|
234
|
+
return {
|
|
235
|
+
isValid: false,
|
|
236
|
+
error: e.data
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
throw e;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* React hook that returns reactive validation state for the field's current
|
|
244
|
+
* value. Error display policy:
|
|
245
|
+
* - If the field started with an `undefined` value (`startedUnset`), the
|
|
246
|
+
* error is hidden until the user has blurred the input **or** until
|
|
247
|
+
* `revealError()` has been called.
|
|
248
|
+
* - If the field started with a defined value, the error is shown
|
|
249
|
+
* immediately whenever the field is invalid.
|
|
250
|
+
*
|
|
251
|
+
* `message` is resolved from `readableError` first, falling back to the
|
|
252
|
+
* first element's `.message` when `error` is an array.
|
|
253
|
+
*/
|
|
254
|
+
useValidation() {
|
|
255
|
+
var _a, _b;
|
|
256
|
+
const value = this.useValue();
|
|
257
|
+
const revealed = useStore(this.errorRevealed);
|
|
258
|
+
const isUnset = typeof value === 'undefined';
|
|
259
|
+
const startedUnset = useRef(isUnset).current;
|
|
260
|
+
const isSet = !isUnset;
|
|
261
|
+
const validation = this.validate(value);
|
|
262
|
+
const isValid = validation.isValid;
|
|
263
|
+
const isInvalid = !isValid;
|
|
264
|
+
const hasChanged = this.initialValue != value;
|
|
265
|
+
const message = (_a = validation.readableError) !== null && _a !== void 0 ? _a : (Array.isArray(validation.error) ? (_b = validation.error[0]) === null || _b === void 0 ? void 0 : _b.message : undefined);
|
|
266
|
+
const errorDisplayRequiresBlur = startedUnset;
|
|
267
|
+
const [hasBlurred, setHasBlurred] = useState(false);
|
|
268
|
+
const showError = isInvalid && (errorDisplayRequiresBlur ? revealed || hasBlurred : true);
|
|
269
|
+
return {
|
|
270
|
+
onInputBlurred() {
|
|
271
|
+
setHasBlurred(true);
|
|
272
|
+
},
|
|
273
|
+
hasBlurred,
|
|
274
|
+
hasChanged,
|
|
275
|
+
startedUnset,
|
|
276
|
+
isSet,
|
|
277
|
+
isInvalid,
|
|
278
|
+
isValid,
|
|
279
|
+
message,
|
|
280
|
+
showError,
|
|
281
|
+
isUnset,
|
|
282
|
+
validation,
|
|
283
|
+
value
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
log(level = 'log', ...args) {
|
|
287
|
+
if (Field.enableLogs)
|
|
288
|
+
logger[level](...this.formatLog(...args));
|
|
289
|
+
}
|
|
290
|
+
toInternalValue(v) {
|
|
291
|
+
return v;
|
|
292
|
+
}
|
|
293
|
+
toRepresentation(v) {
|
|
294
|
+
return v;
|
|
295
|
+
}
|
|
296
|
+
/**
|
|
297
|
+
* Registers a global prop transformer under `key`. Transformers are applied
|
|
298
|
+
* in insertion order by `props()`. Re-registering an existing key replaces
|
|
299
|
+
* the previous function.
|
|
300
|
+
*/
|
|
301
|
+
static attachTransformer(key, fn) {
|
|
302
|
+
Field.transformers.set(key, fn);
|
|
303
|
+
}
|
|
304
|
+
/**
|
|
305
|
+
* Removes the transformer registered under `key`. Idempotent — no-op if the
|
|
306
|
+
* key is not present.
|
|
307
|
+
*/
|
|
308
|
+
static detachTransformer(key) {
|
|
309
|
+
Field.transformers.delete(key);
|
|
310
|
+
}
|
|
311
|
+
/**
|
|
312
|
+
* Returns the field's properties after running them through every registered
|
|
313
|
+
* global transformer. The raw `properties` object is built from `options`
|
|
314
|
+
* with internal keys (`name`, `defaultValue`, `state`, `validate`, `loader`,
|
|
315
|
+
* `onValueChange`) stripped out, plus a `field` reference to `this`.
|
|
316
|
+
* Use this when passing field metadata to a component that does not use the
|
|
317
|
+
* `use()` hook directly.
|
|
318
|
+
*/
|
|
319
|
+
props() {
|
|
320
|
+
return Array.from(Field.transformers.values()).reduce((acc, transformer) => transformer(acc), this.properties);
|
|
321
|
+
}
|
|
322
|
+
measurePosition(wrapperRef) {
|
|
323
|
+
return Field.methodMeasurePosition(this, wrapperRef);
|
|
324
|
+
}
|
|
325
|
+
scrollTo(scrollRef, measure) {
|
|
326
|
+
return Field.methodScrollTo(this, scrollRef, measure);
|
|
327
|
+
}
|
|
328
|
+
getPadding() {
|
|
329
|
+
return Field.methodGetPadding(this);
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
Field.transformers = new Map();
|
|
333
|
+
Field.enableLogs = false;
|
|
334
|
+
Field.getProps = (field) => {
|
|
335
|
+
throw new Error('Field.getProps not implemented');
|
|
336
|
+
};
|
|
337
|
+
Field.methodMeasurePosition = (field, wrapperRef) => {
|
|
338
|
+
throw new Error('Field.measurePosition not implemented');
|
|
339
|
+
};
|
|
340
|
+
Field.methodScrollTo = (field, scrollRef, measure) => {
|
|
341
|
+
throw new Error('Field.scrollTo not implemented');
|
|
342
|
+
};
|
|
343
|
+
Field.methodGetPadding = (field) => {
|
|
344
|
+
throw new Error('Field.getPadding not implemented');
|
|
345
|
+
};
|
|
346
|
+
//# sourceMappingURL=Field.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Field.js","sourceRoot":"","sources":["../../src/lib/Field.ts"],"names":[],"mappings":"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"}
|
package/dist/lib/Form.js
ADDED
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
import { createStateSlice, globalState } from "@codeleap/store";
|
|
2
|
+
import { TypeGuards } from "@codeleap/types";
|
|
3
|
+
import { useCallback, useEffect, useMemo, useState } from "react";
|
|
4
|
+
import { useUnmount } from "@codeleap/hooks";
|
|
5
|
+
function buildState(def) {
|
|
6
|
+
const stateArg = {};
|
|
7
|
+
for (const [name, field] of Object.entries(def)) {
|
|
8
|
+
stateArg[name] = field.value;
|
|
9
|
+
}
|
|
10
|
+
return stateArg;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Container that owns the shared nanostores `GlobalState` atom for a group of
|
|
14
|
+
* fields and wires each field's individual atom to a slice of that shared
|
|
15
|
+
* state. This ensures a single source of truth: reading `form.values` always
|
|
16
|
+
* reflects the live atom values of every field.
|
|
17
|
+
*
|
|
18
|
+
* **Initialization order**
|
|
19
|
+
* The constructor calls `attachState()` synchronously, which replaces each
|
|
20
|
+
* field's own atom with a derived slice and sets the field's `name` from its
|
|
21
|
+
* object key in the shape. Fields must therefore be fully constructed before
|
|
22
|
+
* being passed to `Form`.
|
|
23
|
+
*
|
|
24
|
+
* **React usage**
|
|
25
|
+
* - `use(selector)` — mounts the form in a component, subscribes to state
|
|
26
|
+
* changes via `selector`, and resets all field values on unmount. Prefer
|
|
27
|
+
* this for full-page forms.
|
|
28
|
+
* - `useShared(selector)` — subscribes without the unmount-reset side-effect.
|
|
29
|
+
* Use when the form outlives the component (e.g. a global singleton).
|
|
30
|
+
*
|
|
31
|
+
* **Validation**
|
|
32
|
+
* `validate()` runs every field's synchronous validator and returns a map of
|
|
33
|
+
* results keyed by field name. Pass `revealErrors: true` to simultaneously
|
|
34
|
+
* flip `errorRevealed` on every field, triggering error display in the UI.
|
|
35
|
+
*/
|
|
36
|
+
class Form {
|
|
37
|
+
constructor(id, shape) {
|
|
38
|
+
this.id = id;
|
|
39
|
+
this.fields = shape;
|
|
40
|
+
this.state = globalState(buildState(this.fields));
|
|
41
|
+
this.attachState();
|
|
42
|
+
this.use = this.use.bind(this);
|
|
43
|
+
this.useShared = this.useShared.bind(this);
|
|
44
|
+
this.useReset = this.useReset.bind(this);
|
|
45
|
+
}
|
|
46
|
+
get values() {
|
|
47
|
+
return this.state.get();
|
|
48
|
+
}
|
|
49
|
+
get isChanged() {
|
|
50
|
+
return this.changed();
|
|
51
|
+
}
|
|
52
|
+
changed() {
|
|
53
|
+
for (const [fieldName, field] of Object.entries(this.fields)) {
|
|
54
|
+
if (field.changed())
|
|
55
|
+
return true;
|
|
56
|
+
}
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
get isValid() {
|
|
60
|
+
const res = this.validate();
|
|
61
|
+
return Object.values(res).every((result) => result.isValid);
|
|
62
|
+
}
|
|
63
|
+
slice(field) {
|
|
64
|
+
const fieldSlice = createStateSlice(this.state, (v) => v[field], (value) => {
|
|
65
|
+
return {
|
|
66
|
+
[field]: value
|
|
67
|
+
};
|
|
68
|
+
});
|
|
69
|
+
return fieldSlice;
|
|
70
|
+
}
|
|
71
|
+
iterFields(cb) {
|
|
72
|
+
const results = [];
|
|
73
|
+
let index = 0;
|
|
74
|
+
for (const [name, field] of Object.entries(this.fields)) {
|
|
75
|
+
const result = cb([name, field], index);
|
|
76
|
+
results.push(result);
|
|
77
|
+
index++;
|
|
78
|
+
}
|
|
79
|
+
return results;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Bulk-sets field values from a partial record. Boolean fields require an
|
|
83
|
+
* explicit `boolean` value; all other fields skip `undefined` and falsy
|
|
84
|
+
* values. Use `resetValues()` to restore all fields to their initial values.
|
|
85
|
+
*/
|
|
86
|
+
setValues(values) {
|
|
87
|
+
this.iterFields(([name, field]) => {
|
|
88
|
+
const value = values === null || values === void 0 ? void 0 : values[name];
|
|
89
|
+
if (field._type === 'BOOLEAN' && TypeGuards.isBoolean(value))
|
|
90
|
+
field.setValue(value);
|
|
91
|
+
else if (value)
|
|
92
|
+
field.setValue(value);
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
resetValues() {
|
|
96
|
+
this.iterFields(([name, field]) => {
|
|
97
|
+
field.resetValue();
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
attachState() {
|
|
101
|
+
this.iterFields(([name, field]) => {
|
|
102
|
+
field.options.name = name;
|
|
103
|
+
field.attach(this.slice(name));
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Returns the first field (in definition order) that fails validation, along
|
|
108
|
+
* with its `ValidationResult`. Returns `undefined` when all fields are
|
|
109
|
+
* valid. Useful for scrolling to the first error on submit.
|
|
110
|
+
*/
|
|
111
|
+
firstInvalid() {
|
|
112
|
+
for (const [fieldName, field] of Object.entries(this.fields)) {
|
|
113
|
+
const validation = field.validate();
|
|
114
|
+
if (!validation.isValid)
|
|
115
|
+
return {
|
|
116
|
+
field,
|
|
117
|
+
validation,
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
validate(options) {
|
|
122
|
+
const { fields, revealErrors } = options !== null && options !== void 0 ? options : {};
|
|
123
|
+
const validateFields = fields !== null && fields !== void 0 ? fields : Object.keys(this.fields);
|
|
124
|
+
const results = this.iterFields(([name, field]) => {
|
|
125
|
+
if (!validateFields.includes(name))
|
|
126
|
+
return null;
|
|
127
|
+
return [name, field.validate()];
|
|
128
|
+
});
|
|
129
|
+
const resultMap = Object.fromEntries(results.filter((v) => !TypeGuards.isNil(v)));
|
|
130
|
+
if (revealErrors) {
|
|
131
|
+
this.iterFields(([_, field]) => {
|
|
132
|
+
field.revealError();
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
return resultMap;
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Returns the transformed props for `field` via `Field.props()`. Throws if
|
|
139
|
+
* the field key does not exist in this form's shape, making typos a runtime
|
|
140
|
+
* error rather than a silent no-op.
|
|
141
|
+
*/
|
|
142
|
+
register(field) {
|
|
143
|
+
if (!this.fields[field]) {
|
|
144
|
+
throw new Error(`Field "${field}" not found in "${this.id}" form`);
|
|
145
|
+
}
|
|
146
|
+
return this.fields[field].props();
|
|
147
|
+
}
|
|
148
|
+
use(selector) {
|
|
149
|
+
const value = this.useShared(selector);
|
|
150
|
+
this.useReset();
|
|
151
|
+
return value;
|
|
152
|
+
}
|
|
153
|
+
useReset() {
|
|
154
|
+
useUnmount(() => {
|
|
155
|
+
this.resetValues();
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
useShared(selector) {
|
|
159
|
+
const [selected, setSelected] = useState(() => selector(this));
|
|
160
|
+
const reselect = useCallback(() => {
|
|
161
|
+
setSelected(selector(this));
|
|
162
|
+
}, [selector]);
|
|
163
|
+
useEffect(() => {
|
|
164
|
+
return this.state.listen((value, previous) => {
|
|
165
|
+
if (value != previous) {
|
|
166
|
+
reselect();
|
|
167
|
+
}
|
|
168
|
+
});
|
|
169
|
+
}, [reselect]);
|
|
170
|
+
return selected;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Creates a `Form` instance that is stable for the lifetime of the component
|
|
175
|
+
* (memoised on `name`). Use this when the form is local to a single mounted
|
|
176
|
+
* component. For forms that must survive unmounts (e.g. multi-step flows),
|
|
177
|
+
* construct the `Form` outside React via `form()`.
|
|
178
|
+
*
|
|
179
|
+
* Note: `def` is only read on the first render — changing it after mount has
|
|
180
|
+
* no effect.
|
|
181
|
+
*/
|
|
182
|
+
export function useForm(name, def) {
|
|
183
|
+
const form = useMemo(() => {
|
|
184
|
+
return new Form(name, def);
|
|
185
|
+
}, [name]);
|
|
186
|
+
return form;
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Constructs a `Form` outside of React — suitable for module-level singletons,
|
|
190
|
+
* server-side construction, or multi-step flows where the form must outlive
|
|
191
|
+
* any individual component. Values are **not** automatically reset on unmount;
|
|
192
|
+
* call `resetValues()` explicitly when needed.
|
|
193
|
+
*/
|
|
194
|
+
export function form(...args) {
|
|
195
|
+
return new Form(...args);
|
|
196
|
+
}
|
|
197
|
+
//# sourceMappingURL=Form.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Form.js","sourceRoot":"","sources":["../../src/lib/Form.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAe,WAAW,EAAE,MAAM,iBAAiB,CAAA;AAC5E,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAA;AAC5C,OAAO,EAAkB,WAAW,EAAE,SAAS,EAAmB,OAAO,EAAU,QAAQ,EAAE,MAAM,OAAO,CAAA;AAE1G,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAA;AAK5C,SAAS,UAAU,CAAoB,GAAM;IAC3C,MAAM,QAAQ,GAA4B,EAAE,CAAA;IAE5C,KAAI,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC;QAC/C,QAAQ,CAAC,IAAI,CAAC,GAAG,KAAK,CAAC,KAAK,CAAA;IAC9B,CAAC;IAED,OAAO,QAAyB,CAAA;AAClC,CAAC;AAOD;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,MAAM,IAAI;IAMR,YAAY,EAAU,EAAE,KAAQ;QAC9B,IAAI,CAAC,EAAE,GAAG,EAAE,CAAA;QACZ,IAAI,CAAC,MAAM,GAAG,KAAK,CAAA;QAEnB,IAAI,CAAC,KAAK,GAAG,WAAW,CACtB,UAAU,CAAC,IAAI,CAAC,MAAM,CAAC,CACxB,CAAA;QAED,IAAI,CAAC,WAAW,EAAE,CAAA;QAClB,IAAI,CAAC,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;QAC9B,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;QAC1C,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;IAC1C,CAAC;IAED,IAAI,MAAM;QACR,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,EAAE,CAAA;IACzB,CAAC;IAED,IAAI,SAAS;QACX,OAAO,IAAI,CAAC,OAAO,EAAE,CAAA;IACvB,CAAC;IAGD,OAAO;QACL,KAAI,MAAM,CAAC,SAAS,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,EAAC,CAAC;YAC3D,IAAG,KAAK,CAAC,OAAO,EAAE;gBAAE,OAAO,IAAI,CAAA;QACjC,CAAC;QAED,OAAO,KAAK,CAAA;IACd,CAAC;IAED,IAAI,OAAO;QACT,MAAM,GAAG,GAAG,IAAI,CAAC,QAAQ,EAAE,CAAA;QAE3B,OAAQ,MAAM,CAAC,MAAM,CAAC,GAAG,CAAkC,CAAC,KAAK,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,CAAA;IAC/F,CAAC;IAED,KAAK,CAA0B,KAAQ;QAErC,MAAM,UAAU,GAAG,gBAAgB,CACjC,IAAI,CAAC,KAAK,EACV,CAAC,CAAC,EAAE,EAAE,CAAE,CAA6B,CAAC,KAAe,CAAC,EACtD,CAAC,KAAK,EAAE,EAAE;YACR,OAAO;gBACL,CAAC,KAAK,CAAC,EAAE,KAAK;aACE,CAAA;QACpB,CAAC,CAEF,CAAA;QAED,OAAO,UAAU,CAAA;IACnB,CAAC;IAED,UAAU,CAAI,EAA+C;QAC3D,MAAM,OAAO,GAAO,EAAE,CAAA;QACtB,IAAI,KAAK,GAAG,CAAC,CAAA;QAEb,KAAI,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC;YACvD,MAAM,MAAM,GAAG,EAAE,CAAC,CAAE,IAAI,EAAE,KAAK,CAAoB,EAAE,KAAK,CAAC,CAAA;YAE3D,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAAA;YAEpB,KAAK,EAAE,CAAA;QACT,CAAC;QAED,OAAO,OAAO,CAAA;IAChB,CAAC;IAED;;;;OAIG;IACH,SAAS,CAAC,MAA8B;QACtC,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,IAAI,EAAE,KAAK,CAAC,EAAE,EAAE;YAChC,MAAM,KAAK,GAAG,MAAM,aAAN,MAAM,uBAAN,MAAM,CAAG,IAAI,CAAC,CAAA;YAC5B,IAAI,KAAK,CAAC,KAAK,KAAK,SAAS,IAAI,UAAU,CAAC,SAAS,CAAC,KAAK,CAAC;gBAAE,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAA;iBAC9E,IAAI,KAAK;gBAAE,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAA;QACvC,CAAC,CAAC,CAAA;IACJ,CAAC;IAED,WAAW;QACT,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,IAAI,EAAE,KAAK,CAAC,EAAE,EAAE;YAChC,KAAK,CAAC,UAAU,EAAE,CAAA;QACpB,CAAC,CAAC,CAAA;IACJ,CAAC;IAED,WAAW;QACT,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,IAAI,EAAE,KAAK,CAAC,EAAE,EAAE;YAChC,KAAK,CAAC,OAAO,CAAC,IAAI,GAAG,IAAI,CAAA;YAEzB,KAAK,CAAC,MAAM,CACV,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CACjB,CAAA;QACH,CAAC,CAAC,CAAA;IACJ,CAAC;IAED;;;;OAIG;IACH,YAAY;QACV,KAAI,MAAM,CAAC,SAAS,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,EAAC,CAAC;YAC3D,MAAM,UAAU,GAAG,KAAK,CAAC,QAAQ,EAAE,CAAA;YAEnC,IAAG,CAAC,UAAU,CAAC,OAAO;gBAAE,OAAO;oBAC7B,KAAK;oBACL,UAAU;iBACX,CAAA;QACH,CAAC;IACH,CAAC;IAED,QAAQ,CAAmD,OAAqD;QAE9G,MAAM,EAAE,MAAM,EAAE,YAAY,EAAE,GAAG,OAAO,aAAP,OAAO,cAAP,OAAO,GAAI,EAAE,CAAA;QAE9C,MAAM,cAAc,GAAG,MAAM,aAAN,MAAM,cAAN,MAAM,GAAI,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,CAAA;QAEzD,MAAM,OAAO,GAAG,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,IAAI,EAAE,KAAK,CAAC,EAAE,EAAE;YAChD,IAAG,CAAC,cAAc,CAAC,QAAQ,CAAC,IAAI,CAAC;gBAAE,OAAO,IAAI,CAAA;YAE9C,OAAO,CAAC,IAAI,EAAE,KAAK,CAAC,QAAQ,EAAE,CAA8C,CAAA;QAC9E,CAAC,CAAC,CAAA;QAEF,MAAM,SAAS,GAAG,MAAM,CAAC,WAAW,CAClC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAkD,EAAE,CAAC,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAC5F,CAAA;QAED,IAAG,YAAY,EAAC,CAAC;YACf,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,EAAC,KAAK,CAAC,EAAE,EAAE;gBAC5B,KAAK,CAAC,WAAW,EAAE,CAAA;YACrB,CAAC,CAAC,CAAA;QACJ,CAAC;QAED,OAAO,SAA6E,CAAA;IACtF,CAAC;IAID;;;;OAIG;IACH,QAAQ,CAAC,KAAoB;QAC3B,IAAG,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,EAAC,CAAC;YACtB,MAAM,IAAI,KAAK,CAAC,UAAU,KAAK,mBAAmB,IAAI,CAAC,EAAE,QAAQ,CAAC,CAAA;QACpE,CAAC;QACD,OAAO,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,CAAA;IACnC,CAAC;IAED,GAAG,CAAW,QAAmC;QAC9C,MAAM,KAAK,GAAG,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAA;QAErC,IAAI,CAAC,QAAQ,EAAE,CAAA;QAEhB,OAAO,KAAK,CAAA;IACf,CAAC;IAED,QAAQ;QACL,UAAU,CAAC,GAAG,EAAE;YACf,IAAI,CAAC,WAAW,EAAE,CAAA;QACnB,CAAC,CAAC,CAAA;IACL,CAAC;IACD,SAAS,CAAW,QAAmC;QACrD,MAAM,CAAC,QAAQ,EAAE,WAAW,CAAC,GAAG,QAAQ,CAAC,GAAG,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAA;QAE9D,MAAM,QAAQ,GAAG,WAAW,CAAC,GAAG,EAAE;YAChC,WAAW,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAA;QAC7B,CAAC,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAA;QAEd,SAAS,CAAC,GAAG,EAAE;YACb,OAAO,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,QAAQ,EAAE,EAAE;gBAC3C,IAAG,KAAK,IAAI,QAAQ,EAAC,CAAC;oBACpB,QAAQ,EAAE,CAAA;gBACZ,CAAC;YACH,CAAC,CAAC,CAAA;QACJ,CAAC,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAA;QAEd,OAAO,QAAQ,CAAA;IACjB,CAAC;CACF;AAGD;;;;;;;;GAQG;AACH,MAAM,UAAU,OAAO,CAAoB,IAAY,EAAE,GAAM;IAC7D,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,EAAE;QACxB,OAAO,IAAI,IAAI,CAAC,IAAI,EAAE,GAAG,CAAC,CAAA;IAC5B,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC,CAAA;IAGV,OAAO,IAAI,CAAA;AACb,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,IAAI,CAAsB,GAAG,IAA6C;IACxF,OAAO,IAAI,IAAI,CAAC,GAAG,IAAI,CAAC,CAAA;AAC1B,CAAC"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wraps a `Field` subclass constructor in a factory function, eliminating the
|
|
3
|
+
* need to call `new` at the call site and improving inference of the
|
|
4
|
+
* `validate` option's generic type. The returned factory accepts the same
|
|
5
|
+
* options as the class constructor and produces a fully typed `Field` instance.
|
|
6
|
+
*
|
|
7
|
+
* All entries in the `fields` namespace are built with this helper.
|
|
8
|
+
*/
|
|
9
|
+
export function fieldFactory(cls) {
|
|
10
|
+
return (options) => {
|
|
11
|
+
// @ts-expect-error
|
|
12
|
+
return new cls(options !== null && options !== void 0 ? options : {});
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
//# sourceMappingURL=factories.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"factories.js","sourceRoot":"","sources":["../../src/lib/factories.tsx"],"names":[],"mappings":"AAMA;;;;;;;GAOG;AACH,MAAM,UAAU,YAAY,CAI1B,GAAM;IACN,OAAO,CAAwC,OAAW,EAAgC,EAAE;QAE1F,mBAAmB;QACnB,OAAO,IAAI,GAAG,CAAC,OAAO,aAAP,OAAO,cAAP,OAAO,GAAI,EAAE,CAAC,CAAA;IAC/B,CAAC,CAAA;AACH,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/lib/index.ts"],"names":[],"mappings":"AAAA,cAAc,QAAQ,CAAA;AACtB,OAAO,EAAC,KAAK,EAAC,MAAM,SAAS,CAAA"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
2
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
3
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
4
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
5
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
6
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
7
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
|
+
});
|
|
9
|
+
};
|
|
10
|
+
import { useImperativeHandle } from "react";
|
|
11
|
+
/**
|
|
12
|
+
* Attaches an imperative handle to `ref` via `useImperativeHandle`. Every
|
|
13
|
+
* method in {@link IFieldRef} is given a default implementation that throws
|
|
14
|
+
* `"ref.<method> not implemented"`, so callers get a clear error rather than a
|
|
15
|
+
* silent no-op when a method is missing from `impl`.
|
|
16
|
+
*
|
|
17
|
+
* `impl` is a partial override — only supply the methods your component
|
|
18
|
+
* actually supports. Unimplemented methods remain as throwing stubs.
|
|
19
|
+
* `deps` is forwarded directly to `useImperativeHandle`; include anything
|
|
20
|
+
* `impl` closes over that can change between renders.
|
|
21
|
+
*/
|
|
22
|
+
export function useFieldBinding(ref, impl, deps = []) {
|
|
23
|
+
const notImplemented = (method) => {
|
|
24
|
+
throw new Error(`ref.${method} not implemented`);
|
|
25
|
+
};
|
|
26
|
+
useImperativeHandle(ref, () => (Object.assign({ blur: () => {
|
|
27
|
+
notImplemented('blur');
|
|
28
|
+
}, emit: () => {
|
|
29
|
+
notImplemented('emit');
|
|
30
|
+
}, focus: () => {
|
|
31
|
+
notImplemented('focus');
|
|
32
|
+
},
|
|
33
|
+
// @ts-expect-error
|
|
34
|
+
getValue: () => {
|
|
35
|
+
notImplemented('getValue');
|
|
36
|
+
}, scrollIntoView: () => __awaiter(this, void 0, void 0, function* () {
|
|
37
|
+
notImplemented('scrollIntoView');
|
|
38
|
+
}), hideValue() {
|
|
39
|
+
notImplemented('hideValue');
|
|
40
|
+
},
|
|
41
|
+
revealValue() {
|
|
42
|
+
notImplemented('revealValue');
|
|
43
|
+
},
|
|
44
|
+
toggleValueVisibility() {
|
|
45
|
+
notImplemented('toggleValueVisibility');
|
|
46
|
+
} }, impl)), deps);
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=useFieldBinding.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useFieldBinding.js","sourceRoot":"","sources":["../../src/lib/useFieldBinding.ts"],"names":[],"mappings":";;;;;;;;;AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,OAAO,CAAA;AAG3C;;;;;;;;;;GAUG;AACH,MAAM,UAAU,eAAe,CAAI,GAA+C,EAAE,IAA2B,EAAE,OAA6B,EAAE;IAE9I,MAAM,cAAc,GAAG,CAAC,MAAc,EAAE,EAAE;QACxC,MAAM,IAAI,KAAK,CAAC,OAAO,MAAM,kBAAkB,CAAC,CAAA;IAClD,CAAC,CAAA;IAGD,mBAAmB,CAAC,GAAG,EAAE,GAAI,EAAE,CAAC,iBAC9B,IAAI,EAAE,GAAG,EAAE;YACT,cAAc,CAAC,MAAM,CAAC,CAAA;QACxB,CAAC,EACD,IAAI,EAAE,GAAG,EAAE;YACT,cAAc,CAAC,MAAM,CAAC,CAAA;QACxB,CAAC,EACD,KAAK,EAAE,GAAG,EAAE;YACV,cAAc,CAAC,OAAO,CAAC,CAAA;QACzB,CAAC;QACD,mBAAmB;QACnB,QAAQ,EAAE,GAAG,EAAE;YACb,cAAc,CAAC,UAAU,CAAC,CAAA;QAC5B,CAAC,EACD,cAAc,EAAE,GAAS,EAAE;YACzB,cAAc,CAAC,gBAAgB,CAAC,CAAA;QAClC,CAAC,CAAA,EACD,SAAS;YACP,cAAc,CAAC,WAAW,CAAC,CAAA;QAC7B,CAAC;QACD,WAAW;YACT,cAAc,CAAC,aAAa,CAAC,CAAA;QAC/B,CAAC;QACD,qBAAqB;YACnB,cAAc,CAAC,uBAAuB,CAAC,CAAA;QACzC,CAAC,IACE,IAAI,EACP,EAAE,IAAI,CAAC,CAAA;AAEX,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"field.js","sourceRoot":"","sources":["../../src/types/field.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"form.js","sourceRoot":"","sources":["../../src/types/form.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"globals.js","sourceRoot":"","sources":["../../src/types/globals.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validation.js","sourceRoot":"","sources":["../../src/types/validation.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/validators/index.ts"],"names":[],"mappings":"AAAA,cAAc,OAAO,CAAA"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { TypeGuards } from '@codeleap/types';
|
|
2
|
+
/**
|
|
3
|
+
* Adapts a Zod schema into the `Validator` contract expected by `Field` and
|
|
4
|
+
* `useValidate`. Uses `safeParse` internally so Zod errors are captured as
|
|
5
|
+
* `{ isValid: false, error: ZodIssue[] }` rather than thrown exceptions.
|
|
6
|
+
*
|
|
7
|
+
* The returned function is synchronous — async Zod schemas (`z.promise`,
|
|
8
|
+
* `.parseAsync`) are not supported.
|
|
9
|
+
*/
|
|
10
|
+
export function zodValidator(model) {
|
|
11
|
+
return (value) => {
|
|
12
|
+
var _a;
|
|
13
|
+
const result = model.safeParse(value);
|
|
14
|
+
return {
|
|
15
|
+
isValid: result.success,
|
|
16
|
+
error: (_a = result.error) === null || _a === void 0 ? void 0 : _a.issues,
|
|
17
|
+
result: result.data
|
|
18
|
+
};
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
const isZodIssue = (val) => {
|
|
22
|
+
return ['code', 'expected', 'received', 'path', 'message'].every(x => x in val);
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* Type guard that narrows a `ValidationResult`-shaped value to one produced by
|
|
26
|
+
* `zodValidator`. A valid result must have `result` present; an invalid result
|
|
27
|
+
* must have `error` as an array of `ZodIssue` objects. Use this to
|
|
28
|
+
* distinguish Zod results from results produced by custom validators when
|
|
29
|
+
* handling errors generically.
|
|
30
|
+
*/
|
|
31
|
+
export function isZodValidationResult(val) {
|
|
32
|
+
const isValidABoolean = TypeGuards.isBoolean(val.isValid);
|
|
33
|
+
if (isValidABoolean) {
|
|
34
|
+
if (!val.isValid) {
|
|
35
|
+
return TypeGuards.isArray(val.error) && val.error.every(isZodIssue);
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
38
|
+
return 'result' in val;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
//# sourceMappingURL=zod.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"zod.js","sourceRoot":"","sources":["../../src/validators/zod.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAA;AAI5C;;;;;;;GAOG;AACH,MAAM,UAAU,YAAY,CAAsB,KAAQ;IACxD,OAAO,CAAC,KAAc,EAA0B,EAAE;;QAChD,MAAM,MAAM,GAAG,KAAK,CAAC,SAAS,CAAC,KAAK,CAAC,CAAA;QAErC,OAAO;YACL,OAAO,EAAE,MAAM,CAAC,OAAO;YACvB,KAAK,EAAE,MAAA,MAAM,CAAC,KAAK,0CAAE,MAAM;YAC3B,MAAM,EAAE,MAAM,CAAC,IAAI;SACpB,CAAA;IACH,CAAC,CAAA;AACH,CAAC;AAGD,MAAM,UAAU,GAAG,CAAC,GAAQ,EAAmB,EAAE;IAC/C,OAAO,CAAC,MAAM,EAAC,UAAU,EAAC,UAAU,EAAC,MAAM,EAAC,SAAS,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,GAAG,CAAC,CAAA;AAC7E,CAAC,CAAA;AAED;;;;;;GAMG;AACH,MAAM,UAAU,qBAAqB,CAAC,GAAQ;IAC5C,MAAM,eAAe,GAAG,UAAU,CAAC,SAAS,CAAC,GAAG,CAAC,OAAO,CAAC,CAAA;IAEzD,IAAG,eAAe,EAAE,CAAC;QACnB,IAAG,CAAC,GAAG,CAAC,OAAO,EAAE,CAAC;YAChB,OAAO,UAAU,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,UAAU,CAAC,CAAA;QACrE,CAAC;aAAM,CAAC;YACN,OAAO,QAAQ,IAAI,GAAG,CAAA;QACxB,CAAC;IACH,CAAC;IAED,OAAO,KAAK,CAAA;AAEd,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@codeleap/form",
|
|
3
|
-
"version": "
|
|
4
|
-
"main": "
|
|
3
|
+
"version": "7.0.1",
|
|
4
|
+
"main": "dist/index.js",
|
|
5
5
|
"types": "dist/index.d.ts",
|
|
6
6
|
"exports": {
|
|
7
7
|
".": {
|
|
@@ -22,10 +22,10 @@
|
|
|
22
22
|
"directory": "packages/form"
|
|
23
23
|
},
|
|
24
24
|
"devDependencies": {
|
|
25
|
-
"@codeleap/config": "
|
|
26
|
-
"@codeleap/types": "
|
|
27
|
-
"@codeleap/store": "
|
|
28
|
-
"@codeleap/hooks": "
|
|
25
|
+
"@codeleap/config": "7.0.1",
|
|
26
|
+
"@codeleap/types": "7.0.1",
|
|
27
|
+
"@codeleap/store": "7.0.1",
|
|
28
|
+
"@codeleap/hooks": "7.0.1",
|
|
29
29
|
"zod": "4.4.3",
|
|
30
30
|
"ts-node-dev": "1.1.8"
|
|
31
31
|
},
|
|
@@ -35,10 +35,10 @@
|
|
|
35
35
|
"playground": "bun src/test.ts"
|
|
36
36
|
},
|
|
37
37
|
"peerDependencies": {
|
|
38
|
-
"@codeleap/types": "
|
|
39
|
-
"@codeleap/store": "
|
|
40
|
-
"@codeleap/logger": "
|
|
41
|
-
"@codeleap/hooks": "
|
|
38
|
+
"@codeleap/types": "7.0.1",
|
|
39
|
+
"@codeleap/store": "7.0.1",
|
|
40
|
+
"@codeleap/logger": "7.0.1",
|
|
41
|
+
"@codeleap/hooks": "7.0.1",
|
|
42
42
|
"zod": "*",
|
|
43
43
|
"react": "19.1.0",
|
|
44
44
|
"typescript": "5.5.2"
|