@rhi-zone/rainbow-ui 0.2.0-alpha.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/elements.d.ts +95 -0
- package/dist/elements.js +118 -0
- package/dist/elements.test.d.ts +1 -0
- package/dist/form-state.d.ts +173 -0
- package/dist/form-state.js +98 -0
- package/dist/form-state.test.d.ts +1 -0
- package/dist/html-C8SnQjvU.js +238 -0
- package/dist/html.d.ts +499 -0
- package/dist/html.js +90 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +72 -0
- package/dist/keybinds.d.ts +103 -0
- package/dist/widget.d.ts +420 -0
- package/dist/widget.js +347 -0
- package/dist/widget.test.d.ts +7 -0
- package/package.json +44 -0
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @rhi-zone/rainbow-ui/elements
|
|
3
|
+
*
|
|
4
|
+
* `defineElement` — wrap a Widget<T> in a native custom element.
|
|
5
|
+
*
|
|
6
|
+
* Bridges the HTML attribute/property system into rainbow signals so that
|
|
7
|
+
* `<my-card label="Alice" count="3">` works from plain HTML, and
|
|
8
|
+
* `el.label = "Bob"` works from JavaScript, both updating the widget reactively.
|
|
9
|
+
*
|
|
10
|
+
* Attributes are treated as a boundary adapter: HTML attributes are always
|
|
11
|
+
* `string | null`, and `AttrSchema<T>` maps each observed attribute to an
|
|
12
|
+
* `Optic<string | null, T[K]>` that parses (view) and serialises (review) it.
|
|
13
|
+
*
|
|
14
|
+
* Shadow DOM defaults to "open". Styles are applied via `adoptedStyleSheets`
|
|
15
|
+
* when shadow DOM is used.
|
|
16
|
+
*/
|
|
17
|
+
import type { Optic } from "@rhi-zone/rainbow";
|
|
18
|
+
import type { AnyEl } from "./html.js";
|
|
19
|
+
import type { Widget } from "./widget.js";
|
|
20
|
+
/**
|
|
21
|
+
* Maps field names of `T` to optics that convert between `string | null`
|
|
22
|
+
* (raw HTML attribute) and `T[K]` (typed signal field).
|
|
23
|
+
*
|
|
24
|
+
* - `optic.view(raw)` — parse attribute string into `T[K]`;
|
|
25
|
+
* `undefined` means use `defaults[K]`
|
|
26
|
+
* - `optic.review(v, _)` — serialise `T[K]` back to a string for reflection
|
|
27
|
+
*/
|
|
28
|
+
export type AttrSchema<T> = {
|
|
29
|
+
[K in keyof T]?: Optic<string | null, T[K]>;
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* Pass-through: `view` returns the raw string, or `undefined` when absent.
|
|
33
|
+
* `review` returns the value unchanged.
|
|
34
|
+
*/
|
|
35
|
+
export declare const attrString: Optic<string | null, string>;
|
|
36
|
+
/**
|
|
37
|
+
* Numeric attribute: `view` converts with `Number()`; returns `undefined` on
|
|
38
|
+
* absent attribute or NaN. `review` serialises with `String()`.
|
|
39
|
+
*/
|
|
40
|
+
export declare const attrNumber: Optic<string | null, number>;
|
|
41
|
+
/**
|
|
42
|
+
* Boolean attribute: absent → `undefined`; `"false"` or `"0"` → `false`;
|
|
43
|
+
* anything else → `true`. `review` serialises with `String()`.
|
|
44
|
+
*/
|
|
45
|
+
export declare const attrBoolean: Optic<string | null, boolean>;
|
|
46
|
+
/**
|
|
47
|
+
* JSON attribute: `view` parses with `JSON.parse`; returns `undefined` on
|
|
48
|
+
* absent attribute or parse error. `review` serialises with `JSON.stringify`.
|
|
49
|
+
*/
|
|
50
|
+
export declare function attrJson<T>(): Optic<string | null, T>;
|
|
51
|
+
/**
|
|
52
|
+
* Subset of `AttrSchema<T>` containing only the primitive fields of `T`
|
|
53
|
+
* (`string | number | boolean`).
|
|
54
|
+
*/
|
|
55
|
+
export type PrimitiveAttrSchema<T> = {
|
|
56
|
+
[K in keyof T as T[K] extends string | number | boolean ? K : never]: Optic<string | null, T[K]>;
|
|
57
|
+
};
|
|
58
|
+
/**
|
|
59
|
+
* Auto-derive an `AttrSchema` from `defaults` for all primitive fields
|
|
60
|
+
* (`string`, `number`, `boolean`). Complex fields are excluded.
|
|
61
|
+
*
|
|
62
|
+
* @example
|
|
63
|
+
* // Zero repetition for primitive-only T:
|
|
64
|
+
* attrs: attrsFrom(defaults)
|
|
65
|
+
*
|
|
66
|
+
* // Mixed case — spread and add complex fields:
|
|
67
|
+
* attrs: { ...attrsFrom(defaults), createdAt: attrJson<Date>() }
|
|
68
|
+
*/
|
|
69
|
+
export declare function attrsFrom<T extends object>(defaults: T): PrimitiveAttrSchema<T>;
|
|
70
|
+
/**
|
|
71
|
+
* Register a custom element backed by a `Widget<T>`.
|
|
72
|
+
*
|
|
73
|
+
* The element's signal starts from `defaults`. Attributes listed in `attrs`
|
|
74
|
+
* are observed; each attribute change is parsed via the corresponding optic's
|
|
75
|
+
* `view` method and written into the signal. All fields of `T` get JS property
|
|
76
|
+
* accessors regardless of whether they appear in `attrs`.
|
|
77
|
+
*
|
|
78
|
+
* @example
|
|
79
|
+
* defineElement("score-card", {
|
|
80
|
+
* widget: scoreCardWidget,
|
|
81
|
+
* defaults: { label: "", score: 0 },
|
|
82
|
+
* attrs: { label: attrString, score: attrNumber },
|
|
83
|
+
* styles: `:host { display: block; font-family: sans-serif }`,
|
|
84
|
+
* })
|
|
85
|
+
*
|
|
86
|
+
* // In HTML:
|
|
87
|
+
* // <score-card label="Alice" score="42"></score-card>
|
|
88
|
+
*/
|
|
89
|
+
export declare function defineElement<T extends object>(tagName: string, config: {
|
|
90
|
+
widget: Widget<T, AnyEl>;
|
|
91
|
+
defaults: T;
|
|
92
|
+
attrs?: AttrSchema<T>;
|
|
93
|
+
shadow?: "open" | "closed" | false;
|
|
94
|
+
styles?: CSSStyleSheet | string | (CSSStyleSheet | string)[];
|
|
95
|
+
}): void;
|
package/dist/elements.js
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
var y = Object.defineProperty;
|
|
2
|
+
var m = (t, e, n) => e in t ? y(t, e, { enumerable: !0, configurable: !0, writable: !0, value: n }) : t[e] = n;
|
|
3
|
+
var a = (t, e, n) => m(t, typeof e != "symbol" ? e + "" : e, n);
|
|
4
|
+
import { signal as v } from "@rhi-zone/rainbow";
|
|
5
|
+
import { mount as S } from "./widget.js";
|
|
6
|
+
const w = {
|
|
7
|
+
view(t) {
|
|
8
|
+
return t ?? void 0;
|
|
9
|
+
},
|
|
10
|
+
review(t) {
|
|
11
|
+
return t;
|
|
12
|
+
}
|
|
13
|
+
}, k = {
|
|
14
|
+
view(t) {
|
|
15
|
+
if (t == null) return;
|
|
16
|
+
const e = Number(t);
|
|
17
|
+
return isNaN(e) ? void 0 : e;
|
|
18
|
+
},
|
|
19
|
+
review(t) {
|
|
20
|
+
return String(t);
|
|
21
|
+
}
|
|
22
|
+
}, N = {
|
|
23
|
+
view(t) {
|
|
24
|
+
if (t != null)
|
|
25
|
+
return t !== "false" && t !== "0";
|
|
26
|
+
},
|
|
27
|
+
review(t) {
|
|
28
|
+
return String(t);
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
function A() {
|
|
32
|
+
return {
|
|
33
|
+
view(t) {
|
|
34
|
+
if (t != null)
|
|
35
|
+
try {
|
|
36
|
+
return JSON.parse(t);
|
|
37
|
+
} catch {
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
review(t) {
|
|
42
|
+
return JSON.stringify(t);
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
function J(t) {
|
|
47
|
+
const e = {};
|
|
48
|
+
for (const n of Object.keys(t)) {
|
|
49
|
+
const s = typeof t[n];
|
|
50
|
+
s === "string" ? e[n] = w : s === "number" ? e[n] = k : s === "boolean" && (e[n] = N);
|
|
51
|
+
}
|
|
52
|
+
return e;
|
|
53
|
+
}
|
|
54
|
+
function h(t) {
|
|
55
|
+
if (typeof t == "string") {
|
|
56
|
+
const e = new CSSStyleSheet();
|
|
57
|
+
return e.replaceSync(t), e;
|
|
58
|
+
}
|
|
59
|
+
return t;
|
|
60
|
+
}
|
|
61
|
+
function x(t, e) {
|
|
62
|
+
const {
|
|
63
|
+
widget: n,
|
|
64
|
+
defaults: s,
|
|
65
|
+
attrs: u = {},
|
|
66
|
+
shadow: l = "open",
|
|
67
|
+
styles: i
|
|
68
|
+
} = e, g = Object.keys(u), c = i == null ? [] : Array.isArray(i) ? i.map(h) : [h(i)];
|
|
69
|
+
class f extends HTMLElement {
|
|
70
|
+
constructor() {
|
|
71
|
+
super(...arguments);
|
|
72
|
+
// Use underscore prefix rather than # so Object.defineProperty below can
|
|
73
|
+
// access instance state from outside the class body.
|
|
74
|
+
a(this, "_rb_signal", v({ ...s }));
|
|
75
|
+
a(this, "_rb_cleanup", null);
|
|
76
|
+
}
|
|
77
|
+
static get observedAttributes() {
|
|
78
|
+
return g;
|
|
79
|
+
}
|
|
80
|
+
connectedCallback() {
|
|
81
|
+
const r = l !== !1 ? this.shadowRoot ?? this.attachShadow({ mode: l }) : this;
|
|
82
|
+
l !== !1 && c.length > 0 && (r.adoptedStyleSheets = c), this._rb_cleanup = S(n, this._rb_signal, r);
|
|
83
|
+
}
|
|
84
|
+
disconnectedCallback() {
|
|
85
|
+
var r;
|
|
86
|
+
(r = this._rb_cleanup) == null || r.call(this), this._rb_cleanup = null;
|
|
87
|
+
}
|
|
88
|
+
attributeChangedCallback(r, O, _) {
|
|
89
|
+
const d = u[r];
|
|
90
|
+
if (d == null) return;
|
|
91
|
+
const p = d.view(_) ?? s[r];
|
|
92
|
+
this._rb_signal.set({ ...this._rb_signal.get(), [r]: p });
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
for (const o of Object.keys(s))
|
|
96
|
+
Object.defineProperty(f.prototype, o, {
|
|
97
|
+
get() {
|
|
98
|
+
return this._rb_signal.get()[o];
|
|
99
|
+
},
|
|
100
|
+
set(b) {
|
|
101
|
+
this._rb_signal.set({
|
|
102
|
+
...this._rb_signal.get(),
|
|
103
|
+
[o]: b
|
|
104
|
+
});
|
|
105
|
+
},
|
|
106
|
+
configurable: !0,
|
|
107
|
+
enumerable: !0
|
|
108
|
+
});
|
|
109
|
+
customElements.define(t, f);
|
|
110
|
+
}
|
|
111
|
+
export {
|
|
112
|
+
N as attrBoolean,
|
|
113
|
+
A as attrJson,
|
|
114
|
+
k as attrNumber,
|
|
115
|
+
w as attrString,
|
|
116
|
+
J as attrsFrom,
|
|
117
|
+
x as defineElement
|
|
118
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @rhi-zone/rainbow-ui/form-state
|
|
3
|
+
*
|
|
4
|
+
* Form state as a data model. Rendering is entirely the app's responsibility.
|
|
5
|
+
*
|
|
6
|
+
* A form field is just a lens:
|
|
7
|
+
*
|
|
8
|
+
* focus(inputWidget(), composeLens(field("values"), field("name")))
|
|
9
|
+
* // Widget<FormState<Profile>, InputEl>
|
|
10
|
+
*
|
|
11
|
+
* The data model:
|
|
12
|
+
*
|
|
13
|
+
* FormState<T> = { values, fieldErrors, formErrors, touched, submitting, submitCount }
|
|
14
|
+
*
|
|
15
|
+
* Validation adapters: FormValidator<T> is a plain function — bridge your
|
|
16
|
+
* schema library in the app layer, e.g. valibot:
|
|
17
|
+
*
|
|
18
|
+
* const validate: FormValidator<T> = (values) => {
|
|
19
|
+
* const r = v.safeParse(Schema, values)
|
|
20
|
+
* if (r.success) return {}
|
|
21
|
+
* const fieldErrors: FieldErrors<T> = {}
|
|
22
|
+
* for (const issue of r.issues) {
|
|
23
|
+
* const key = issue.path?.[0]?.key as keyof T & string
|
|
24
|
+
* if (key) (fieldErrors[key] ??= []).push(issue.message)
|
|
25
|
+
* }
|
|
26
|
+
* return { fieldErrors }
|
|
27
|
+
* }
|
|
28
|
+
*/
|
|
29
|
+
import type { Signal } from "@rhi-zone/rainbow";
|
|
30
|
+
import type { Widget } from "./widget.js";
|
|
31
|
+
import type { AnyEl } from "./html.js";
|
|
32
|
+
/** Per-field error arrays keyed by field name. */
|
|
33
|
+
export type FieldErrors<T> = Partial<Record<keyof T & string, string[]>>;
|
|
34
|
+
/**
|
|
35
|
+
* Complete form state. All fields are readonly — mutations go through
|
|
36
|
+
* `Signal<FormState<T>>.set`.
|
|
37
|
+
*/
|
|
38
|
+
export type FormState<T> = {
|
|
39
|
+
readonly values: T;
|
|
40
|
+
/** Per-field validation errors. Empty array or undefined = no errors. */
|
|
41
|
+
readonly fieldErrors: FieldErrors<T>;
|
|
42
|
+
/** Cross-field or server-side errors, not attributed to a specific field. */
|
|
43
|
+
readonly formErrors: string[];
|
|
44
|
+
/**
|
|
45
|
+
* Which fields the user has interacted with. Gate error display on this
|
|
46
|
+
* using `subscribe` + `on(el, "focusout", ...)` in the app's field renderer.
|
|
47
|
+
*/
|
|
48
|
+
readonly touched: Partial<Record<keyof T & string, boolean>>;
|
|
49
|
+
/** True while `handleSubmit`'s `onValid` promise is pending. */
|
|
50
|
+
readonly submitting: boolean;
|
|
51
|
+
/**
|
|
52
|
+
* Increments on each submit attempt. When > 0, show all field errors
|
|
53
|
+
* immediately (not just for touched fields).
|
|
54
|
+
*/
|
|
55
|
+
readonly submitCount: number;
|
|
56
|
+
};
|
|
57
|
+
/**
|
|
58
|
+
* Form-level validator. Receives the full values object; returns any
|
|
59
|
+
* combination of field errors (keyed by field name) and form-level errors.
|
|
60
|
+
* Return `{}` or `undefined` when valid.
|
|
61
|
+
*/
|
|
62
|
+
export type FormValidator<T> = (values: T) => {
|
|
63
|
+
fieldErrors?: FieldErrors<T>;
|
|
64
|
+
formErrors?: string[];
|
|
65
|
+
} | null | undefined;
|
|
66
|
+
/** Construct initial `FormState<T>` from default values. */
|
|
67
|
+
export declare function createFormState<T>(defaults: T): FormState<T>;
|
|
68
|
+
/**
|
|
69
|
+
* True when the error for `key` should be shown to the user.
|
|
70
|
+
* Errors are suppressed until the field is touched OR a submit has been attempted.
|
|
71
|
+
*/
|
|
72
|
+
export declare function shouldShowError<T>(state: FormState<T>, key: keyof T & string): boolean;
|
|
73
|
+
/**
|
|
74
|
+
* True when `state` has no field errors and no form-level errors.
|
|
75
|
+
* An empty array for `fieldErrors[key]` is treated as valid.
|
|
76
|
+
*/
|
|
77
|
+
export declare function isFormValid<T>(state: FormState<T>): boolean;
|
|
78
|
+
/**
|
|
79
|
+
* True when `state.values` differs from `initial` (by JSON equality).
|
|
80
|
+
* Useful for enabling/disabling a "Save" button.
|
|
81
|
+
*/
|
|
82
|
+
export declare function isDirty<T>(initial: T, state: FormState<T>): boolean;
|
|
83
|
+
/**
|
|
84
|
+
* Create a form controller backed by a `Signal<FormState<T>>`.
|
|
85
|
+
*
|
|
86
|
+
* A form field is a focused widget — no special combinator needed:
|
|
87
|
+
*
|
|
88
|
+
* focus(inputWidget(), composeLens(field("values"), field("name")))
|
|
89
|
+
*
|
|
90
|
+
* The app owns error display and touched tracking:
|
|
91
|
+
*
|
|
92
|
+
* subscribe(state, (s) => {
|
|
93
|
+
* const show = (s.touched.name || s.submitCount > 0) && s.fieldErrors.name?.length
|
|
94
|
+
* errorEl.style.display = show ? "" : "none"
|
|
95
|
+
* errorEl.textContent = s.fieldErrors.name?.[0] ?? ""
|
|
96
|
+
* })
|
|
97
|
+
* on(inputEl, "focusout", () =>
|
|
98
|
+
* state.set({ ...state.get(), touched: { ...state.get().touched, name: true } })
|
|
99
|
+
* )
|
|
100
|
+
*
|
|
101
|
+
* @returns
|
|
102
|
+
* `state` — the reactive form signal; pass to `mount` and `focus`
|
|
103
|
+
* `handleSubmit` — returns an event handler; attach to the form's submit event
|
|
104
|
+
* `reset` — restore all state to `defaults`
|
|
105
|
+
* `setErrors` — write server-returned errors back into the signal
|
|
106
|
+
*/
|
|
107
|
+
export declare function createForm<T extends object>(options: {
|
|
108
|
+
readonly defaults: T;
|
|
109
|
+
readonly validate?: FormValidator<T>;
|
|
110
|
+
}): {
|
|
111
|
+
readonly state: Signal<FormState<T>>;
|
|
112
|
+
/**
|
|
113
|
+
* Bind a widget to a specific values field. Shorthand for
|
|
114
|
+
* `focus(widget, composeLens(field("values"), field(key)))`.
|
|
115
|
+
* `T` is captured by closure so no explicit type parameters are needed.
|
|
116
|
+
*
|
|
117
|
+
* @example
|
|
118
|
+
* const { state, bind } = createForm({ defaults: { name: "", email: "" } })
|
|
119
|
+
* mount(stack(
|
|
120
|
+
* bind("name", inputWidget({ placeholder: "Name" })),
|
|
121
|
+
* bind("email", inputWidget({ placeholder: "Email" })),
|
|
122
|
+
* ), state, root)
|
|
123
|
+
*/
|
|
124
|
+
readonly bind: <K extends keyof T & string, E extends AnyEl>(key: K, widget: Widget<T[K], E>) => Widget<FormState<T>, E>;
|
|
125
|
+
readonly handleSubmit: (onValid: (values: T) => Promise<void>) => (e?: Event) => void;
|
|
126
|
+
readonly reset: () => void;
|
|
127
|
+
readonly setErrors: (fieldErrors?: FieldErrors<T>, formErrors?: string[]) => void;
|
|
128
|
+
/**
|
|
129
|
+
* Replace the form's defaults and reset all state to a fresh `FormState`
|
|
130
|
+
* built from `newDefaults`. Use this when reusing the same form instance
|
|
131
|
+
* across different records (e.g. switching between contacts).
|
|
132
|
+
*/
|
|
133
|
+
readonly reinitialize: (newDefaults: T) => void;
|
|
134
|
+
/**
|
|
135
|
+
* Build a fully-wired form field element: wrapper div → label → input (or
|
|
136
|
+
* textarea when `rows` is set) → error span.
|
|
137
|
+
*
|
|
138
|
+
* The input is bound to `state` via `bind`. A `focusout` listener marks the
|
|
139
|
+
* field as touched. The error span is shown when
|
|
140
|
+
* `(touched[key] || submitCount > 0) && fieldErrors[key]?.length > 0`.
|
|
141
|
+
*
|
|
142
|
+
* Must be called inside a widget rendering context (i.e. inside a `mount`
|
|
143
|
+
* call or another widget) so that the `subscribe` for error display is
|
|
144
|
+
* tracked for cleanup.
|
|
145
|
+
*
|
|
146
|
+
* @example
|
|
147
|
+
* mount(
|
|
148
|
+
* stack(
|
|
149
|
+
* (s) => form.field("name", "Full name"),
|
|
150
|
+
* (s) => form.field("email", "Email", { type: "email" }),
|
|
151
|
+
* (s) => form.field("bio", "Bio", { rows: 4 }),
|
|
152
|
+
* ),
|
|
153
|
+
* form.state,
|
|
154
|
+
* root,
|
|
155
|
+
* )
|
|
156
|
+
*/
|
|
157
|
+
readonly field: (key: keyof T & string, label: string, options?: {
|
|
158
|
+
type?: "text" | "email" | "tel" | "password" | "number" | "search";
|
|
159
|
+
rows?: number;
|
|
160
|
+
}) => HTMLElement;
|
|
161
|
+
/**
|
|
162
|
+
* Return a `<div class="form-errors">` that is hidden when `state.formErrors`
|
|
163
|
+
* is empty, and shows all form-level errors (one `<p>` per error) when
|
|
164
|
+
* non-empty. Uses `subscribe` to stay reactive.
|
|
165
|
+
*
|
|
166
|
+
* Must be called inside a widget rendering context (i.e. inside a `mount`
|
|
167
|
+
* call or another widget) so that the subscription is tracked for cleanup.
|
|
168
|
+
*
|
|
169
|
+
* @example
|
|
170
|
+
* formEl.appendChild(editFormState.formErrors())
|
|
171
|
+
*/
|
|
172
|
+
readonly formErrors: () => HTMLElement;
|
|
173
|
+
};
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { signal as C, field as h } from "@rhi-zone/rainbow";
|
|
2
|
+
import { subscribe as b, textareaWidget as y, inputWidget as w } from "./widget.js";
|
|
3
|
+
function E(o) {
|
|
4
|
+
return {
|
|
5
|
+
values: { ...o },
|
|
6
|
+
fieldErrors: {},
|
|
7
|
+
formErrors: [],
|
|
8
|
+
touched: {},
|
|
9
|
+
submitting: !1,
|
|
10
|
+
submitCount: 0
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
function S(o, n) {
|
|
14
|
+
var d;
|
|
15
|
+
return (o.touched[n] === !0 || o.submitCount > 0) && (((d = o.fieldErrors[n]) == null ? void 0 : d.length) ?? 0) > 0;
|
|
16
|
+
}
|
|
17
|
+
function x(o) {
|
|
18
|
+
return o.formErrors.length > 0 ? !1 : Object.values(o.fieldErrors).every(
|
|
19
|
+
(n) => !n || n.length === 0
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
function W(o, n) {
|
|
23
|
+
return JSON.stringify(o) !== JSON.stringify(n.values);
|
|
24
|
+
}
|
|
25
|
+
function L(o) {
|
|
26
|
+
const { defaults: n, validate: d } = o, e = C(E(n)), f = (r, s) => (t) => s(t.focus(h("values")).focus(h(r))), p = (r) => {
|
|
27
|
+
if (!d) return { fieldErrors: {}, formErrors: [] };
|
|
28
|
+
const s = d(r) ?? {};
|
|
29
|
+
return {
|
|
30
|
+
fieldErrors: s.fieldErrors ?? {},
|
|
31
|
+
formErrors: s.formErrors ?? []
|
|
32
|
+
};
|
|
33
|
+
};
|
|
34
|
+
return { state: e, bind: f, handleSubmit: (r) => (s) => {
|
|
35
|
+
s == null || s.preventDefault();
|
|
36
|
+
const t = e.get(), l = Object.fromEntries(
|
|
37
|
+
Object.keys(t.values).map((u) => [u, !0])
|
|
38
|
+
), { fieldErrors: i, formErrors: m } = p(t.values);
|
|
39
|
+
e.set({
|
|
40
|
+
...t,
|
|
41
|
+
touched: { ...t.touched, ...l },
|
|
42
|
+
fieldErrors: i,
|
|
43
|
+
formErrors: m,
|
|
44
|
+
submitCount: t.submitCount + 1
|
|
45
|
+
}), x({ ...t, fieldErrors: i, formErrors: m }) && (e.set({ ...e.get(), submitting: !0, formErrors: [] }), r(e.get().values).then(
|
|
46
|
+
() => {
|
|
47
|
+
e.set({ ...e.get(), submitting: !1 });
|
|
48
|
+
},
|
|
49
|
+
(u) => {
|
|
50
|
+
const c = u instanceof Error ? u.message : String(u);
|
|
51
|
+
e.set({ ...e.get(), submitting: !1, formErrors: [c] });
|
|
52
|
+
}
|
|
53
|
+
));
|
|
54
|
+
}, reset: () => {
|
|
55
|
+
e.set(E(n));
|
|
56
|
+
}, setErrors: (r, s) => {
|
|
57
|
+
e.set({
|
|
58
|
+
...e.get(),
|
|
59
|
+
fieldErrors: r ?? {},
|
|
60
|
+
formErrors: s ?? []
|
|
61
|
+
});
|
|
62
|
+
}, reinitialize: (r) => {
|
|
63
|
+
e.set(E(r));
|
|
64
|
+
}, field: (r, s, t) => {
|
|
65
|
+
const l = document.createElement("div");
|
|
66
|
+
l.className = "form-field";
|
|
67
|
+
const i = document.createElement("label");
|
|
68
|
+
i.textContent = s, l.appendChild(i);
|
|
69
|
+
const u = ((t == null ? void 0 : t.rows) != null ? f(r, y({ rows: t.rows })) : f(r, w({ type: (t == null ? void 0 : t.type) ?? "text" })))(e);
|
|
70
|
+
l.appendChild(u.node), u.node.addEventListener("focusout", () => {
|
|
71
|
+
const a = e.get();
|
|
72
|
+
e.set({ ...a, touched: { ...a.touched, [r]: !0 } });
|
|
73
|
+
});
|
|
74
|
+
const c = document.createElement("span");
|
|
75
|
+
return c.className = "field-error", c.style.display = "none", l.appendChild(c), b(e, (a) => {
|
|
76
|
+
var g;
|
|
77
|
+
const v = S(a, r);
|
|
78
|
+
c.style.display = v ? "" : "none", c.textContent = ((g = a.fieldErrors[r]) == null ? void 0 : g[0]) ?? "";
|
|
79
|
+
}), l;
|
|
80
|
+
}, formErrors: () => {
|
|
81
|
+
const r = document.createElement("div");
|
|
82
|
+
return r.className = "form-errors", r.style.display = "none", b(e, (s) => {
|
|
83
|
+
const t = s.formErrors.length > 0;
|
|
84
|
+
if (r.style.display = t ? "" : "none", r.textContent = "", t)
|
|
85
|
+
for (const l of s.formErrors) {
|
|
86
|
+
const i = document.createElement("p");
|
|
87
|
+
i.textContent = l, r.appendChild(i);
|
|
88
|
+
}
|
|
89
|
+
}), r;
|
|
90
|
+
} };
|
|
91
|
+
}
|
|
92
|
+
export {
|
|
93
|
+
L as createForm,
|
|
94
|
+
E as createFormState,
|
|
95
|
+
W as isDirty,
|
|
96
|
+
x as isFormValid,
|
|
97
|
+
S as shouldShowError
|
|
98
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|