@verbb/formie-browser 1.0.9 → 1.0.10
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/CHANGELOG.md +6 -1
- package/dist/chunks/{calculations-CeDgB9jt.js → calculations-HAZ8wUrT.js} +1 -1
- package/dist/chunks/checkbox-radio-BY2aTCOG.js +177 -0
- package/dist/chunks/{conditions-B6_nzpeE.js → conditions-BiA_uANV.js} +1 -1
- package/dist/chunks/{dist-BQ4a9hmy.js → dist-BaHXfvBO.js} +1 -1
- package/dist/chunks/password-validation-D5AirwUM.js +49 -0
- package/dist/chunks/{text-limit-DbgEFHLO.js → text-limit-nSATKyhp.js} +1 -1
- package/dist/css/theme/fields/_check-radio.css +25 -0
- package/dist/index.js +10 -13
- package/dist/js/core/create-formie-client.d.ts.map +1 -1
- package/dist/js/modules/fields/checkbox-radio.d.ts.map +1 -1
- package/dist/js/modules/fields/index.d.ts.map +1 -1
- package/dist/js/modules/fields/password-validation.d.ts +3 -0
- package/dist/js/modules/fields/password-validation.d.ts.map +1 -0
- package/dist/js/validation/rules/match.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/css/theme/fields/_check-radio.css +25 -0
- package/src/js/core/create-formie-client.ts +9 -1
- package/src/js/modules/fields/checkbox-radio.ts +133 -4
- package/src/js/modules/fields/index.ts +1 -0
- package/src/js/modules/fields/password-validation.ts +132 -0
- package/src/js/validation/rules/match.ts +5 -4
- package/src/js/validation/rules/minmax.ts +2 -2
- package/src/js/validation/rules/number.ts +1 -1
- package/dist/chunks/checkbox-radio-DQ0H67Tj.js +0 -125
package/CHANGELOG.md
CHANGED
|
@@ -2,7 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
-
## 1.0.
|
|
5
|
+
## 1.0.10 - 2026-06-26
|
|
6
|
+
|
|
7
|
+
### Changed
|
|
8
|
+
- Align client-side validation message copy for checkbox/radio option limits, match, number, and min/max rules with server-side Formie validation messages, including `data-formie-validation-match-message` override support on match fields. ([#2907](https://github.com/verbb/formie/issues/2907))
|
|
9
|
+
|
|
10
|
+
## 1.0.9 - 2026-06-25
|
|
6
11
|
|
|
7
12
|
### Changed
|
|
8
13
|
- Improve default theme color contrast for WCAG 2.2 AA — darker error text, muted helper text, and border-only control styling with split border tokens. ([#2475](https://github.com/verbb/formie/issues/2475))
|
|
@@ -2,7 +2,7 @@ import { t as e } from "./debug-BV0DvdHx.js";
|
|
|
2
2
|
import { r as t, t as n } from "./field-references.keys-58ZSTrCW.js";
|
|
3
3
|
import { n as r, r as i } from "./field-references.resolver-CHwn0G0L.js";
|
|
4
4
|
import { r as a, t as o } from "./shared-Bx9s0i0P.js";
|
|
5
|
-
import { c as s, n as c, r as l, s as u, t as d } from "./dist-
|
|
5
|
+
import { c as s, n as c, r as l, s as u, t as d } from "./dist-BaHXfvBO.js";
|
|
6
6
|
//#region src/js/utils/field-references.row-scope.ts
|
|
7
7
|
var f = new Set([
|
|
8
8
|
"first",
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import { t as e } from "./debug-BV0DvdHx.js";
|
|
2
|
+
import { c as t, l as n, n as r, t as i } from "./shared-Bx9s0i0P.js";
|
|
3
|
+
//#region src/js/modules/fields/checkbox-radio.ts
|
|
4
|
+
var a = "[data-formie-checkboxes-field-layout], [data-formie-radio-field-layout]", o = "minmaxOptions", s = "otherOptionText", c = "data-formie-checkbox-radio-max-disabled", l = "checkbox-radio", u = "checkbox-radio", d = e("fields", "checkbox-radio");
|
|
5
|
+
function f(e) {
|
|
6
|
+
return e.hasAttribute("data-checkbox-toggle") || e.hasAttribute("data-formie-checkbox-toggle");
|
|
7
|
+
}
|
|
8
|
+
function p(e) {
|
|
9
|
+
let t = e(o);
|
|
10
|
+
if (!t || t === !0 || typeof t != "object") return {
|
|
11
|
+
min: null,
|
|
12
|
+
max: null
|
|
13
|
+
};
|
|
14
|
+
let n = t;
|
|
15
|
+
return {
|
|
16
|
+
min: typeof n.min == "number" ? n.min : null,
|
|
17
|
+
max: typeof n.max == "number" ? n.max : null
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
function m(e) {
|
|
21
|
+
return e.hasAttribute("data-formie-other-option") || !!e.closest("[data-formie-other-option]");
|
|
22
|
+
}
|
|
23
|
+
function h(e) {
|
|
24
|
+
let t = [];
|
|
25
|
+
return e.querySelectorAll("[data-formie-other-option-text]").forEach((e) => {
|
|
26
|
+
if (!(e instanceof HTMLInputElement)) return;
|
|
27
|
+
let n = e.closest("[data-formie-field-option]") ?? e.parentElement;
|
|
28
|
+
if (!n) return;
|
|
29
|
+
let r = n.querySelector("input[type=\"checkbox\"][data-formie-other-option], input[type=\"radio\"][data-formie-other-option]") ?? n.querySelector("input[type=\"checkbox\"], input[type=\"radio\"]");
|
|
30
|
+
r instanceof HTMLInputElement && t.push({
|
|
31
|
+
choiceInput: r,
|
|
32
|
+
textInput: e
|
|
33
|
+
});
|
|
34
|
+
}), t;
|
|
35
|
+
}
|
|
36
|
+
function g(e) {
|
|
37
|
+
h(e).forEach(({ choiceInput: e, textInput: t }) => {
|
|
38
|
+
let n = e.checked;
|
|
39
|
+
t.disabled = !n, n || (t.value = "");
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
function _(e) {
|
|
43
|
+
let t = h(e);
|
|
44
|
+
if (!t.length) return () => {};
|
|
45
|
+
let n = [];
|
|
46
|
+
return Array.from(e.querySelectorAll("input[type=\"checkbox\"], input[type=\"radio\"]")).filter((e) => e instanceof HTMLInputElement && !f(e)).forEach((t) => {
|
|
47
|
+
let r = () => {
|
|
48
|
+
g(e);
|
|
49
|
+
};
|
|
50
|
+
t.addEventListener("change", r), n.push(() => {
|
|
51
|
+
t.removeEventListener("change", r);
|
|
52
|
+
});
|
|
53
|
+
}), t.forEach(({ textInput: t }) => {
|
|
54
|
+
let r = () => {
|
|
55
|
+
g(e);
|
|
56
|
+
};
|
|
57
|
+
t.addEventListener("input", r), t.addEventListener("change", r), n.push(() => {
|
|
58
|
+
t.removeEventListener("input", r), t.removeEventListener("change", r);
|
|
59
|
+
});
|
|
60
|
+
}), g(e), () => {
|
|
61
|
+
n.forEach((e) => {
|
|
62
|
+
e();
|
|
63
|
+
});
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
function v(e) {
|
|
67
|
+
return Array.from(e.querySelectorAll("input[type=\"checkbox\"]")).filter((e) => e instanceof HTMLInputElement && !f(e)).filter((e) => e.checked).length;
|
|
68
|
+
}
|
|
69
|
+
function y(e) {
|
|
70
|
+
n(e, u, (e) => {
|
|
71
|
+
e.addValidator(s, ({ field: e, getRule: t }) => !e || !t(s) ? !0 : h(e).every(({ choiceInput: e, textInput: t }) => e.checked ? t.value.trim() !== "" : !0), ({ field: e, label: t, t: n, getRule: r }) => {
|
|
72
|
+
if (!e || !r(s)) return n("{label} is invalid.", { label: t });
|
|
73
|
+
let i = h(e).find(({ choiceInput: e }) => e.checked)?.choiceInput.closest("[data-formie-field-option]")?.querySelector("[data-formie-field-option-label]")?.textContent?.trim() ?? t;
|
|
74
|
+
return e.getAttribute("data-formie-validation-other-option-text-message") ?? n("Please enter a value for “{label}”.", { label: i });
|
|
75
|
+
}), e.addValidator(o, ({ field: e, getRule: t }) => {
|
|
76
|
+
if (!e || !t(o)) return !0;
|
|
77
|
+
let n = v(e), { min: r, max: i } = p(t);
|
|
78
|
+
return !(r !== null && n < r || i !== null && n > i);
|
|
79
|
+
}, ({ field: e, label: t, t: n, getRule: r }) => {
|
|
80
|
+
if (!e) return n("{label} is invalid.", { label: t });
|
|
81
|
+
let i = v(e), { min: a, max: o } = p(r);
|
|
82
|
+
return a !== null && i < a ? e.getAttribute("data-formie-validation-min-options-message") ?? n("{label} should contain at least {min, number} {min, plural, one{option} other{options}}.", {
|
|
83
|
+
label: t,
|
|
84
|
+
min: a
|
|
85
|
+
}) : o !== null && i > o ? e.getAttribute("data-formie-validation-max-options-message") ?? n("{label} should contain at most {max, number} {max, plural, one{option} other{options}}.", {
|
|
86
|
+
label: t,
|
|
87
|
+
max: o
|
|
88
|
+
}) : n("{label} is invalid.", { label: t });
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
function b(e) {
|
|
93
|
+
t(e, u, [o, s]);
|
|
94
|
+
}
|
|
95
|
+
function x(e) {
|
|
96
|
+
e.checked ? e.setAttribute("checked", "") : e.removeAttribute("checked");
|
|
97
|
+
}
|
|
98
|
+
function S(e) {
|
|
99
|
+
let t = Array.from(e.querySelectorAll("input[type=\"checkbox\"][required][data-formie-checkbox-input]")).filter((e) => e instanceof HTMLInputElement);
|
|
100
|
+
if (!t.length) return;
|
|
101
|
+
let n = t.some((e) => e.checked);
|
|
102
|
+
t.forEach((e) => {
|
|
103
|
+
if (n) {
|
|
104
|
+
e.removeAttribute("required"), e.setAttribute("aria-required", "false");
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
e.setAttribute("required", "true"), e.setAttribute("aria-required", "true");
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
function C(e) {
|
|
111
|
+
let t = parseInt(e.closest("[data-formie-field-handle]")?.getAttribute("data-formie-max-options") || "", 10);
|
|
112
|
+
if (!(t > 0)) return;
|
|
113
|
+
let n = Array.from(e.querySelectorAll("input[type=\"checkbox\"]")).filter((e) => e instanceof HTMLInputElement && !f(e) && !m(e)), r = n.filter((e) => e.checked).length >= t;
|
|
114
|
+
n.forEach((e) => {
|
|
115
|
+
let t = r && !e.checked, n = e.hasAttribute(c);
|
|
116
|
+
if (t) {
|
|
117
|
+
e.disabled || (e.disabled = !0, e.setAttribute(c, "true"));
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
n && (e.disabled = !1, e.removeAttribute(c));
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
function w(e, t) {
|
|
124
|
+
Array.from(e.querySelectorAll("input[type=\"checkbox\"]")).filter((e) => e instanceof HTMLInputElement && e !== t && !f(e)).forEach((e) => {
|
|
125
|
+
e.disabled && !e.checked || (e.checked = t.checked, x(e), e.dispatchEvent(new Event("change", { bubbles: !0 })), e.dispatchEvent(new Event("input", { bubbles: !0 })));
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
function T(e, t) {
|
|
129
|
+
if (!e.checked || !e.name) {
|
|
130
|
+
x(e);
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
Array.from(t.querySelectorAll(`input[type="radio"][name="${r(e.name)}"]`)).filter((e) => e instanceof HTMLInputElement).forEach((e) => {
|
|
134
|
+
x(e);
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
function E(e) {
|
|
138
|
+
let t = Array.from(e.querySelectorAll("input[type=\"checkbox\"], input[type=\"radio\"]")).filter((e) => e instanceof HTMLInputElement);
|
|
139
|
+
if (!t.length) return d.log("No checkbox/radio inputs found for field."), () => {};
|
|
140
|
+
let n = t.map((t) => {
|
|
141
|
+
let n = t.type === "radio" ? "change" : "click", r = () => {
|
|
142
|
+
x(t), t.type === "checkbox" && f(t) && w(e, t), t.type === "radio" && T(t, e), S(e), C(e), queueMicrotask(() => {
|
|
143
|
+
g(e);
|
|
144
|
+
}), d.log("Input interaction processed.", {
|
|
145
|
+
inputName: t.name,
|
|
146
|
+
inputType: t.type,
|
|
147
|
+
checked: t.checked
|
|
148
|
+
});
|
|
149
|
+
};
|
|
150
|
+
return t.addEventListener(n, r), x(t), () => {
|
|
151
|
+
t.removeEventListener(n, r);
|
|
152
|
+
};
|
|
153
|
+
}), r = _(e);
|
|
154
|
+
return S(e), C(e), g(e), i(e, l, "init", { checkboxRadio: e }), () => {
|
|
155
|
+
n.forEach((e) => {
|
|
156
|
+
e();
|
|
157
|
+
}), r();
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
var D = {
|
|
161
|
+
id: l,
|
|
162
|
+
kind: "field",
|
|
163
|
+
match: (e) => e.target instanceof HTMLElement && (e.target.matches(a) || !!e.target.querySelector(a)),
|
|
164
|
+
setup: async (e) => {
|
|
165
|
+
if (!(e.target instanceof HTMLElement)) return;
|
|
166
|
+
let t = e.target.matches(a) ? [e.target] : Array.from(e.target.querySelectorAll(a)).filter((e) => e instanceof HTMLElement);
|
|
167
|
+
y(e.form), d.log("Module setup.", { fieldCount: t.length });
|
|
168
|
+
let n = t.map((e) => E(e));
|
|
169
|
+
return await e.emit("formie:module:checkbox-radio:init", { count: t.length }), { destroy: () => {
|
|
170
|
+
n.forEach((e) => {
|
|
171
|
+
e();
|
|
172
|
+
}), b(e.form), d.log("Module destroy.", { fieldCount: t.length }), e.emit("formie:module:checkbox-radio:destroy", {});
|
|
173
|
+
} };
|
|
174
|
+
}
|
|
175
|
+
};
|
|
176
|
+
//#endregion
|
|
177
|
+
export { D as checkboxRadioModule };
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { t as e } from "./debug-BV0DvdHx.js";
|
|
2
2
|
import { t } from "./field-references.keys-58ZSTrCW.js";
|
|
3
3
|
import { n } from "./shared-Bx9s0i0P.js";
|
|
4
|
-
import { a as r, o as i } from "./dist-
|
|
4
|
+
import { a as r, o as i } from "./dist-BaHXfvBO.js";
|
|
5
5
|
//#region src/js/modules/fields/conditions/config.ts
|
|
6
6
|
var a = "[data-formie-conditions]";
|
|
7
7
|
function o(e) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
//#region
|
|
1
|
+
//#region node_modules/@verbb/formie-core/dist/index.js
|
|
2
2
|
var e = Object.create, t = Object.defineProperty, n = Object.getOwnPropertyDescriptor, r = Object.getOwnPropertyNames, i = Object.getPrototypeOf, a = Object.prototype.hasOwnProperty, o = (e, t) => () => (t || (e((t = { exports: {} }).exports, t), e = null), t.exports), s = (e, i, o, s) => {
|
|
3
3
|
if (i && typeof i == "object" || typeof i == "function") for (var c = r(i), l = 0, u = c.length, d; l < u; l++) d = c[l], !a.call(e, d) && d !== o && t(e, d, {
|
|
4
4
|
get: ((e) => i[e]).bind(null, d),
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { c as e, i as t, l as n } from "./shared-Bx9s0i0P.js";
|
|
2
|
+
import { i as r } from "./dist-BaHXfvBO.js";
|
|
3
|
+
//#region src/js/modules/fields/password-validation.ts
|
|
4
|
+
var i = "input[data-formie-password-input]", a = [
|
|
5
|
+
"passwordMinLength",
|
|
6
|
+
"passwordUppercase",
|
|
7
|
+
"passwordLowercase",
|
|
8
|
+
"passwordSpecialCharacter"
|
|
9
|
+
], o = "password-validation";
|
|
10
|
+
function s(e) {
|
|
11
|
+
return e instanceof HTMLInputElement && e.matches(i);
|
|
12
|
+
}
|
|
13
|
+
function c(e) {
|
|
14
|
+
return parseInt(e.getAttribute("data-formie-password-min-length") || "", 10) || 0;
|
|
15
|
+
}
|
|
16
|
+
function l(e) {
|
|
17
|
+
return e.hasAttribute("data-formie-password-min-length") || e.hasAttribute("data-formie-password-require-uppercase") || e.hasAttribute("data-formie-password-require-lowercase") || e.hasAttribute("data-formie-password-require-special-character");
|
|
18
|
+
}
|
|
19
|
+
function u(e) {
|
|
20
|
+
return e.value === "";
|
|
21
|
+
}
|
|
22
|
+
function d(e) {
|
|
23
|
+
n(e, o, (e) => {
|
|
24
|
+
e.addValidator("passwordMinLength", ({ input: e }) => {
|
|
25
|
+
if (!s(e)) return !0;
|
|
26
|
+
let t = c(e);
|
|
27
|
+
return !t || u(e) ? !0 : r(e.value).graphemeCount >= t;
|
|
28
|
+
}, ({ label: e, input: t, t: n }) => t.getAttribute("data-formie-validation-min-characters-message") || n("{label} must be no less than {min} characters.", {
|
|
29
|
+
label: e,
|
|
30
|
+
min: t.getAttribute("data-formie-password-min-length") || ""
|
|
31
|
+
})), e.addValidator("passwordUppercase", ({ input: e }) => !s(e) || !e.hasAttribute("data-formie-password-require-uppercase") || u(e) ? !0 : /[A-Z]/.test(e.value), ({ label: e, input: t, t: n }) => t.getAttribute("data-formie-validation-password-uppercase-message") || n("{label} must contain at least one uppercase letter.", { label: e })), e.addValidator("passwordLowercase", ({ input: e }) => !s(e) || !e.hasAttribute("data-formie-password-require-lowercase") || u(e) ? !0 : /[a-z]/.test(e.value), ({ label: e, input: t, t: n }) => t.getAttribute("data-formie-validation-password-lowercase-message") || n("{label} must contain at least one lowercase letter.", { label: e })), e.addValidator("passwordSpecialCharacter", ({ input: e }) => !s(e) || !e.hasAttribute("data-formie-password-require-special-character") || u(e) ? !0 : /[^a-zA-Z0-9]/.test(e.value), ({ label: e, input: t, t: n }) => t.getAttribute("data-formie-validation-password-special-character-message") || n("{label} must contain at least one special character.", { label: e }));
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
function f(t) {
|
|
35
|
+
e(t, o, a);
|
|
36
|
+
}
|
|
37
|
+
var p = {
|
|
38
|
+
id: "password-validation",
|
|
39
|
+
kind: "field",
|
|
40
|
+
match: (e) => !!e.target.querySelector(`${i}[data-formie-password-min-length], ${i}[data-formie-password-require-uppercase], ${i}[data-formie-password-require-lowercase], ${i}[data-formie-password-require-special-character]`),
|
|
41
|
+
setup: async (e) => {
|
|
42
|
+
let n = t(e), r = Array.from((n || e.target).querySelectorAll(i)).filter((e) => e instanceof HTMLInputElement && l(e));
|
|
43
|
+
return d(e.form), await e.emit("formie:module:password-validation:init", { count: r.length }), { destroy: () => {
|
|
44
|
+
f(e.form), e.emit("formie:module:password-validation:destroy", {});
|
|
45
|
+
} };
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
//#endregion
|
|
49
|
+
export { p as passwordValidationModule };
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { i as e } from "./i18n-BY1ds1BL.js";
|
|
2
2
|
import { t } from "./styles-BfoIZwJp.js";
|
|
3
3
|
import { c as n, i as r, l as i } from "./shared-Bx9s0i0P.js";
|
|
4
|
-
import { i as a } from "./dist-
|
|
4
|
+
import { i as a } from "./dist-BaHXfvBO.js";
|
|
5
5
|
//#region src/css/theme/fields/_text-limit.css?inline
|
|
6
6
|
var o = "@layer formie-theme{.formie-limit-number{font-weight:var(--formie-font-weight-semibold);color:var(--formie-color-text)}.formie-limit-number-error{color:var(--formie-color-danger)}}", s = "input[data-formie-single-line-text-input], textarea[data-formie-multi-line-text-input]", c = [
|
|
7
7
|
"textMinCharacterLimit",
|
|
@@ -105,4 +105,29 @@
|
|
|
105
105
|
.formie-field-has-error .formie-radio-input:focus-visible+.formie-radio-option-label::before {
|
|
106
106
|
box-shadow: var(--formie-shadow-danger-focus);
|
|
107
107
|
}
|
|
108
|
+
|
|
109
|
+
.formie-other-option-text {
|
|
110
|
+
display: none;
|
|
111
|
+
flex: 0 0 100%;
|
|
112
|
+
width: 100%;
|
|
113
|
+
max-width: 100%;
|
|
114
|
+
margin-top: var(--formie-space-1);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
.formie-field-option:has(> input[data-formie-other-option]),
|
|
118
|
+
.formie-other-option {
|
|
119
|
+
flex-wrap: wrap;
|
|
120
|
+
align-items: flex-start;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
.formie-field-option:has(> input[data-formie-other-option]:checked) > .formie-other-option-text,
|
|
124
|
+
.formie-field-option:has(> input[data-formie-other-option]:checked) > label ~ .formie-other-option-text,
|
|
125
|
+
.formie-other-option:has(> input[data-formie-other-option]:checked) > .formie-other-option-text {
|
|
126
|
+
display: block;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
.formie-layout-horizontal .formie-field-option:has(> input[data-formie-other-option]),
|
|
130
|
+
.formie-layout-horizontal .formie-other-option {
|
|
131
|
+
flex: 1 1 100%;
|
|
132
|
+
}
|
|
108
133
|
}
|
package/dist/index.js
CHANGED
|
@@ -858,16 +858,12 @@ var ct = {
|
|
|
858
858
|
},
|
|
859
859
|
message: ({ input: e, label: t, getRule: n, t: r }) => {
|
|
860
860
|
let i = n("number"), a = i !== !0 && i && typeof i == "object" && typeof i.min == "number" ? i.min : null, o = i !== !0 && i && typeof i == "object" && typeof i.max == "number" ? i.max : null;
|
|
861
|
-
return a !== null && o !== null ? e.getAttribute("data-formie-validation-number-min-message") ?? r("{label} must be
|
|
861
|
+
return a !== null && o !== null || a !== null ? e.getAttribute("data-formie-validation-number-min-message") ?? r("{label} must be no less than {min}.", {
|
|
862
862
|
label: t,
|
|
863
|
-
min: a
|
|
864
|
-
|
|
865
|
-
}) : a === null ? o === null ? e.getAttribute("data-formie-validation-number-message") ?? e.getAttribute("data-formie-pattern-number-message") ?? e.getAttribute("data-pattern-number-message") ?? r("{label} is not a valid number.", { label: t }) : e.getAttribute("data-formie-validation-number-max-message") ?? r("{label} must be no greater than {max}.", {
|
|
863
|
+
min: a
|
|
864
|
+
}) : o === null ? e.getAttribute("data-formie-validation-number-message") ?? e.getAttribute("data-formie-pattern-number-message") ?? e.getAttribute("data-pattern-number-message") ?? r("{label} is not a valid number.", { label: t }) : e.getAttribute("data-formie-validation-number-max-message") ?? r("{label} must be no greater than {max}.", {
|
|
866
865
|
label: t,
|
|
867
866
|
max: o
|
|
868
|
-
}) : e.getAttribute("data-formie-validation-number-min-message") ?? r("{label} must be no less than {min}.", {
|
|
869
|
-
label: t,
|
|
870
|
-
min: a
|
|
871
867
|
});
|
|
872
868
|
}
|
|
873
869
|
},
|
|
@@ -878,7 +874,7 @@ var ct = {
|
|
|
878
874
|
},
|
|
879
875
|
message: (e) => {
|
|
880
876
|
let t = st(e)?.closest("[data-formie-field-handle]"), n = ot(t);
|
|
881
|
-
return e.t("{label} must match {value}.", {
|
|
877
|
+
return e.input.getAttribute("data-formie-validation-match-message") ?? e.t("{label} must match {value}.", {
|
|
882
878
|
label: e.label,
|
|
883
879
|
value: n
|
|
884
880
|
});
|
|
@@ -1386,10 +1382,10 @@ var L = class {
|
|
|
1386
1382
|
snaptcha: () => import("./chunks/snaptcha-DKx98oon.js").then((e) => e.snaptchaModule),
|
|
1387
1383
|
turnstile: () => import("./chunks/turnstile-DJakPzQD.js").then((e) => e.turnstileModule)
|
|
1388
1384
|
}, Xt = {
|
|
1389
|
-
calculations: () => import("./chunks/calculations-
|
|
1390
|
-
"checkbox-radio": () => import("./chunks/checkbox-radio-
|
|
1385
|
+
calculations: () => import("./chunks/calculations-HAZ8wUrT.js").then((e) => e.calculationsModule),
|
|
1386
|
+
"checkbox-radio": () => import("./chunks/checkbox-radio-BY2aTCOG.js").then((e) => e.checkboxRadioModule),
|
|
1391
1387
|
combobox: () => import("./chunks/combobox-C0EuExSI.js").then((e) => e.comboboxModule),
|
|
1392
|
-
conditions: () => import("./chunks/conditions-
|
|
1388
|
+
conditions: () => import("./chunks/conditions-BiA_uANV.js").then((e) => e.conditionsModule),
|
|
1393
1389
|
"custom-google-maps": () => import("./chunks/custom-google-maps-BsaSirEZ.js").then((e) => e.customGoogleMapsModule),
|
|
1394
1390
|
"custom-link": () => import("./chunks/custom-link-CJ1-FjEM.js").then((e) => e.customLinkModule),
|
|
1395
1391
|
"custom-maps": () => import("./chunks/custom-maps-DYW_pBid.js").then((e) => e.customMapsModule),
|
|
@@ -1398,6 +1394,7 @@ var L = class {
|
|
|
1398
1394
|
"upload-manager": () => import("./chunks/upload-manager-W-QDgxKD.js").then((e) => e.uploadManagerModule),
|
|
1399
1395
|
hidden: () => import("./chunks/hidden-D7_Ch-QN.js").then((e) => e.hiddenModule),
|
|
1400
1396
|
"phone-country": () => import("./chunks/phone-country-D3ayr60L.js").then((e) => e.phoneCountryModule),
|
|
1397
|
+
"password-validation": () => import("./chunks/password-validation-D5AirwUM.js").then((e) => e.passwordValidationModule),
|
|
1401
1398
|
"address-country": () => import("./chunks/address-country-Rx6GQTND.js").then((e) => e.addressCountryModule),
|
|
1402
1399
|
"address-state": () => import("./chunks/address-state-BwE01m2_.js").then((e) => e.addressStateModule),
|
|
1403
1400
|
repeater: () => import("./chunks/repeater-BVnPUqTj.js").then((e) => e.repeaterModule),
|
|
@@ -1408,7 +1405,7 @@ var L = class {
|
|
|
1408
1405
|
"survey-rank": () => import("./chunks/survey-rank-DN6x4IZM.js").then((e) => e.surveyRankModule),
|
|
1409
1406
|
"survey-rating": () => import("./chunks/survey-rating-BnsNL9U5.js").then((e) => e.surveyRatingModule),
|
|
1410
1407
|
table: () => import("./chunks/table-CAjCJHrL.js").then((e) => e.tableModule),
|
|
1411
|
-
"text-limit": () => import("./chunks/text-limit-
|
|
1408
|
+
"text-limit": () => import("./chunks/text-limit-nSATKyhp.js").then((e) => e.textLimitModule)
|
|
1412
1409
|
}, Zt = {
|
|
1413
1410
|
bpoint: () => import("./chunks/bpoint-C0VOftX9.js").then((e) => e.bpointModule),
|
|
1414
1411
|
eway: () => import("./chunks/eway-CS6Y1Yvo.js").then((e) => e.ewayModule),
|
|
@@ -1856,7 +1853,7 @@ function nr(e, t) {
|
|
|
1856
1853
|
if (t) {
|
|
1857
1854
|
if (t.csrf?.param && t.csrf?.token) {
|
|
1858
1855
|
let n = e.querySelector(`input[name="${t.csrf.param}"]`);
|
|
1859
|
-
n
|
|
1856
|
+
n ? n.value = t.csrf.token : (n = document.createElement("input"), n.type = "hidden", n.name = t.csrf.param, n.value = t.csrf.token, n.setAttribute("autocomplete", "off"), e.prepend(n));
|
|
1860
1857
|
}
|
|
1861
1858
|
if (t.requestToken) {
|
|
1862
1859
|
let n = e.querySelector("input[name=\"requestToken\"]");
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"create-formie-client.d.ts","sourceRoot":"","sources":["../../../src/js/core/create-formie-client.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACR,YAAY,EAGf,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"create-formie-client.d.ts","sourceRoot":"","sources":["../../../src/js/core/create-formie-client.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACR,YAAY,EAGf,MAAM,mBAAmB,CAAC;AA81B3B,wBAAgB,kBAAkB,IAAI,YAAY,CAysBjD"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"checkbox-radio.d.ts","sourceRoot":"","sources":["../../../../src/js/modules/fields/checkbox-radio.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"checkbox-radio.d.ts","sourceRoot":"","sources":["../../../../src/js/modules/fields/checkbox-radio.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,oBAAoB,CAAC;AA8XjE,eAAO,MAAM,mBAAmB,EAAE,sBA+CjC,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/js/modules/fields/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,oBAAoB,CAAC;AAEjE,eAAO,MAAM,yBAAyB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,OAAO,CAAC,sBAAsB,CAAC,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/js/modules/fields/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,oBAAoB,CAAC;AAEjE,eAAO,MAAM,yBAAyB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,OAAO,CAAC,sBAAsB,CAAC,CA2B3F,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"password-validation.d.ts","sourceRoot":"","sources":["../../../../src/js/modules/fields/password-validation.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,oBAAoB,CAAC;AAwGjE,eAAO,MAAM,wBAAwB,EAAE,sBAyBtC,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"match.d.ts","sourceRoot":"","sources":["../../../../src/js/validation/rules/match.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,mBAAmB,CAAC;AAGlE,QAAA,MAAM,KAAK,EAAE,
|
|
1
|
+
{"version":3,"file":"match.d.ts","sourceRoot":"","sources":["../../../../src/js/validation/rules/match.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,mBAAmB,CAAC;AAGlE,QAAA,MAAM,KAAK,EAAE,wBAqBZ,CAAC;AAEF,eAAe,KAAK,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@verbb/formie-browser",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.10",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"module": "./dist/index.js",
|
|
@@ -70,7 +70,7 @@
|
|
|
70
70
|
"@placekit/autocomplete-js": "^2.2.1",
|
|
71
71
|
"@uppy/core": "^5.2.0",
|
|
72
72
|
"@uppy/xhr-upload": "^5.2.0",
|
|
73
|
-
"@verbb/formie-core": "1.0.
|
|
73
|
+
"@verbb/formie-core": "1.0.10",
|
|
74
74
|
"expression-language": "^2.5.4",
|
|
75
75
|
"flatpickr": "^4.6.13",
|
|
76
76
|
"friendly-challenge": "^0.9.19",
|
|
@@ -105,4 +105,29 @@
|
|
|
105
105
|
.formie-field-has-error .formie-radio-input:focus-visible+.formie-radio-option-label::before {
|
|
106
106
|
box-shadow: var(--formie-shadow-danger-focus);
|
|
107
107
|
}
|
|
108
|
+
|
|
109
|
+
.formie-other-option-text {
|
|
110
|
+
display: none;
|
|
111
|
+
flex: 0 0 100%;
|
|
112
|
+
width: 100%;
|
|
113
|
+
max-width: 100%;
|
|
114
|
+
margin-top: var(--formie-space-1);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
.formie-field-option:has(> input[data-formie-other-option]),
|
|
118
|
+
.formie-other-option {
|
|
119
|
+
flex-wrap: wrap;
|
|
120
|
+
align-items: flex-start;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
.formie-field-option:has(> input[data-formie-other-option]:checked) > .formie-other-option-text,
|
|
124
|
+
.formie-field-option:has(> input[data-formie-other-option]:checked) > label ~ .formie-other-option-text,
|
|
125
|
+
.formie-other-option:has(> input[data-formie-other-option]:checked) > .formie-other-option-text {
|
|
126
|
+
display: block;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
.formie-layout-horizontal .formie-field-option:has(> input[data-formie-other-option]),
|
|
130
|
+
.formie-layout-horizontal .formie-other-option {
|
|
131
|
+
flex: 1 1 100%;
|
|
132
|
+
}
|
|
108
133
|
}
|
|
@@ -415,9 +415,17 @@ function applyRefreshTokensToForm(form: HTMLFormElement, refreshTokens: FormEndp
|
|
|
415
415
|
// Refresh-tokens mutates continuity inputs in place so cached or long-lived
|
|
416
416
|
// forms keep submitting with fresh CSRF/request/captcha state.
|
|
417
417
|
if (refreshTokens.csrf?.param && refreshTokens.csrf?.token) {
|
|
418
|
-
|
|
418
|
+
let csrfInput = form.querySelector(`input[name="${refreshTokens.csrf.param}"]`) as HTMLInputElement | null;
|
|
419
|
+
|
|
419
420
|
if (csrfInput) {
|
|
420
421
|
csrfInput.value = refreshTokens.csrf.token;
|
|
422
|
+
} else {
|
|
423
|
+
csrfInput = document.createElement('input');
|
|
424
|
+
csrfInput.type = 'hidden';
|
|
425
|
+
csrfInput.name = refreshTokens.csrf.param;
|
|
426
|
+
csrfInput.value = refreshTokens.csrf.token;
|
|
427
|
+
csrfInput.setAttribute('autocomplete', 'off');
|
|
428
|
+
form.prepend(csrfInput);
|
|
421
429
|
}
|
|
422
430
|
}
|
|
423
431
|
|
|
@@ -4,6 +4,7 @@ import { createDebug } from '#utils/debug';
|
|
|
4
4
|
|
|
5
5
|
const FIELD_SELECTOR = '[data-formie-checkboxes-field-layout], [data-formie-radio-field-layout]';
|
|
6
6
|
const CHECKBOX_MINMAX_VALIDATOR = 'minmaxOptions';
|
|
7
|
+
const OTHER_OPTION_TEXT_VALIDATOR = 'otherOptionText';
|
|
7
8
|
const MAX_DISABLED_ATTR = 'data-formie-checkbox-radio-max-disabled';
|
|
8
9
|
const MODULE_ID = 'checkbox-radio';
|
|
9
10
|
const VALIDATOR_SCOPE = 'checkbox-radio';
|
|
@@ -31,6 +32,94 @@ function getMinMaxRule(getRule: (rule: string) => unknown): { min: number | null
|
|
|
31
32
|
};
|
|
32
33
|
}
|
|
33
34
|
|
|
35
|
+
function isOtherOptionInput(input: HTMLInputElement): boolean {
|
|
36
|
+
return input.hasAttribute('data-formie-other-option')
|
|
37
|
+
|| !!input.closest('[data-formie-other-option]');
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function findOtherOptionGroups(field: HTMLElement): Array<{ choiceInput: HTMLInputElement; textInput: HTMLInputElement }> {
|
|
41
|
+
const groups: Array<{ choiceInput: HTMLInputElement; textInput: HTMLInputElement }> = [];
|
|
42
|
+
|
|
43
|
+
field.querySelectorAll('[data-formie-other-option-text]').forEach((node) => {
|
|
44
|
+
if (!(node instanceof HTMLInputElement)) {
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const container = node.closest('[data-formie-field-option]') ?? node.parentElement;
|
|
49
|
+
|
|
50
|
+
if (!container) {
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const choiceInput = container.querySelector('input[type="checkbox"][data-formie-other-option], input[type="radio"][data-formie-other-option]')
|
|
55
|
+
?? container.querySelector('input[type="checkbox"], input[type="radio"]');
|
|
56
|
+
|
|
57
|
+
if (!(choiceInput instanceof HTMLInputElement)) {
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
groups.push({ choiceInput, textInput: node });
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
return groups;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function syncOtherOptionField(field: HTMLElement): void {
|
|
68
|
+
findOtherOptionGroups(field).forEach(({ choiceInput, textInput }) => {
|
|
69
|
+
const showTextInput = choiceInput.checked;
|
|
70
|
+
|
|
71
|
+
textInput.disabled = !showTextInput;
|
|
72
|
+
|
|
73
|
+
if (!showTextInput) {
|
|
74
|
+
textInput.value = '';
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function bindOtherOptionField(field: HTMLElement): () => void {
|
|
80
|
+
const otherOptionGroups = findOtherOptionGroups(field);
|
|
81
|
+
|
|
82
|
+
if (!otherOptionGroups.length) {
|
|
83
|
+
return () => {};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const listeners: Array<() => void> = [];
|
|
87
|
+
|
|
88
|
+
Array.from(field.querySelectorAll('input[type="checkbox"], input[type="radio"]')).filter((input): input is HTMLInputElement => {
|
|
89
|
+
return input instanceof HTMLInputElement && !isToggleCheckbox(input);
|
|
90
|
+
}).forEach((input) => {
|
|
91
|
+
const handler = () => {
|
|
92
|
+
syncOtherOptionField(field);
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
input.addEventListener('change', handler);
|
|
96
|
+
listeners.push(() => {
|
|
97
|
+
input.removeEventListener('change', handler);
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
otherOptionGroups.forEach(({ textInput }) => {
|
|
102
|
+
const handler = () => {
|
|
103
|
+
syncOtherOptionField(field);
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
textInput.addEventListener('input', handler);
|
|
107
|
+
textInput.addEventListener('change', handler);
|
|
108
|
+
listeners.push(() => {
|
|
109
|
+
textInput.removeEventListener('input', handler);
|
|
110
|
+
textInput.removeEventListener('change', handler);
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
syncOtherOptionField(field);
|
|
115
|
+
|
|
116
|
+
return () => {
|
|
117
|
+
listeners.forEach((unbind) => {
|
|
118
|
+
unbind();
|
|
119
|
+
});
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
|
|
34
123
|
function getSelectedOptionCount(field: HTMLElement): number {
|
|
35
124
|
return Array.from(field.querySelectorAll('input[type="checkbox"]')).filter((input): input is HTMLInputElement => {
|
|
36
125
|
return input instanceof HTMLInputElement && !isToggleCheckbox(input);
|
|
@@ -43,6 +132,39 @@ function registerValidators(form: HTMLFormElement | null): void {
|
|
|
43
132
|
// Several checkbox/radio module instances can exist in one form, but the
|
|
44
133
|
// custom validator should only be registered once per form lifecycle.
|
|
45
134
|
retainFormValidators(form, VALIDATOR_SCOPE, (validator) => {
|
|
135
|
+
validator.addValidator(
|
|
136
|
+
OTHER_OPTION_TEXT_VALIDATOR,
|
|
137
|
+
({ field, getRule }) => {
|
|
138
|
+
if (!field || !getRule(OTHER_OPTION_TEXT_VALIDATOR)) {
|
|
139
|
+
return true;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return findOtherOptionGroups(field).every(({ choiceInput, textInput }) => {
|
|
143
|
+
if (!choiceInput.checked) {
|
|
144
|
+
return true;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return textInput.value.trim() !== '';
|
|
148
|
+
});
|
|
149
|
+
},
|
|
150
|
+
({ field, label, t, getRule }) => {
|
|
151
|
+
if (!field || !getRule(OTHER_OPTION_TEXT_VALIDATOR)) {
|
|
152
|
+
return t('{label} is invalid.', { label });
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const otherGroup = findOtherOptionGroups(field).find(({ choiceInput }) => {
|
|
156
|
+
return choiceInput.checked;
|
|
157
|
+
});
|
|
158
|
+
const otherLabel = otherGroup?.choiceInput.closest('[data-formie-field-option]')
|
|
159
|
+
?.querySelector('[data-formie-field-option-label]')
|
|
160
|
+
?.textContent
|
|
161
|
+
?.trim()
|
|
162
|
+
?? label;
|
|
163
|
+
|
|
164
|
+
return field.getAttribute('data-formie-validation-other-option-text-message')
|
|
165
|
+
?? t('Please enter a value for “{label}”.', { label: otherLabel });
|
|
166
|
+
},
|
|
167
|
+
);
|
|
46
168
|
validator.addValidator(
|
|
47
169
|
CHECKBOX_MINMAX_VALIDATOR,
|
|
48
170
|
({ field, getRule }) => {
|
|
@@ -65,7 +187,7 @@ function registerValidators(form: HTMLFormElement | null): void {
|
|
|
65
187
|
},
|
|
66
188
|
({ field, label, t, getRule }) => {
|
|
67
189
|
if (!field) {
|
|
68
|
-
return t('{label}
|
|
190
|
+
return t('{label} is invalid.', { label });
|
|
69
191
|
}
|
|
70
192
|
|
|
71
193
|
const selected = getSelectedOptionCount(field);
|
|
@@ -81,14 +203,14 @@ function registerValidators(form: HTMLFormElement | null): void {
|
|
|
81
203
|
?? t('{label} should contain at most {max, number} {max, plural, one{option} other{options}}.', { label, max });
|
|
82
204
|
}
|
|
83
205
|
|
|
84
|
-
return t('{label}
|
|
206
|
+
return t('{label} is invalid.', { label });
|
|
85
207
|
},
|
|
86
208
|
);
|
|
87
209
|
});
|
|
88
210
|
}
|
|
89
211
|
|
|
90
212
|
function unregisterValidators(form: HTMLFormElement | null): void {
|
|
91
|
-
releaseFormValidators(form, VALIDATOR_SCOPE, [CHECKBOX_MINMAX_VALIDATOR]);
|
|
213
|
+
releaseFormValidators(form, VALIDATOR_SCOPE, [CHECKBOX_MINMAX_VALIDATOR, OTHER_OPTION_TEXT_VALIDATOR]);
|
|
92
214
|
}
|
|
93
215
|
|
|
94
216
|
function syncCheckedAttribute(input: HTMLInputElement): void {
|
|
@@ -134,7 +256,7 @@ function enforceMaxOptions(field: HTMLElement): void {
|
|
|
134
256
|
}
|
|
135
257
|
|
|
136
258
|
const checkboxes = Array.from(field.querySelectorAll('input[type="checkbox"]')).filter((input): input is HTMLInputElement => {
|
|
137
|
-
return input instanceof HTMLInputElement && !isToggleCheckbox(input);
|
|
259
|
+
return input instanceof HTMLInputElement && !isToggleCheckbox(input) && !isOtherOptionInput(input);
|
|
138
260
|
});
|
|
139
261
|
|
|
140
262
|
const checked = checkboxes.filter((input) => {
|
|
@@ -223,6 +345,9 @@ function bindField(field: HTMLElement): () => void {
|
|
|
223
345
|
|
|
224
346
|
syncRequiredCheckboxes(field);
|
|
225
347
|
enforceMaxOptions(field);
|
|
348
|
+
queueMicrotask(() => {
|
|
349
|
+
syncOtherOptionField(field);
|
|
350
|
+
});
|
|
226
351
|
debug.log('Input interaction processed.', {
|
|
227
352
|
inputName: input.name,
|
|
228
353
|
inputType: input.type,
|
|
@@ -238,8 +363,11 @@ function bindField(field: HTMLElement): () => void {
|
|
|
238
363
|
};
|
|
239
364
|
});
|
|
240
365
|
|
|
366
|
+
const destroyOtherOptionBinding = bindOtherOptionField(field);
|
|
367
|
+
|
|
241
368
|
syncRequiredCheckboxes(field);
|
|
242
369
|
enforceMaxOptions(field);
|
|
370
|
+
syncOtherOptionField(field);
|
|
243
371
|
dispatchFieldEvent(field, MODULE_ID, 'init', {
|
|
244
372
|
checkboxRadio: field,
|
|
245
373
|
});
|
|
@@ -248,6 +376,7 @@ function bindField(field: HTMLElement): () => void {
|
|
|
248
376
|
listeners.forEach((unbind) => {
|
|
249
377
|
unbind();
|
|
250
378
|
});
|
|
379
|
+
destroyOtherOptionBinding();
|
|
251
380
|
};
|
|
252
381
|
}
|
|
253
382
|
|
|
@@ -15,6 +15,7 @@ export const builtinFieldModuleLoaders: Record<string, () => Promise<FormieModul
|
|
|
15
15
|
'upload-manager': () => import('#modules/fields/upload-manager').then((module) => module.uploadManagerModule),
|
|
16
16
|
'hidden': () => import('#modules/fields/hidden').then((module) => module.hiddenModule),
|
|
17
17
|
'phone-country': () => import('#modules/fields/phone-country').then((module) => module.phoneCountryModule),
|
|
18
|
+
'password-validation': () => import('#modules/fields/password-validation').then((module) => module.passwordValidationModule),
|
|
18
19
|
'address-country': () => import('#modules/fields/address-country').then((module) => module.addressCountryModule),
|
|
19
20
|
'address-state': () => import('#modules/fields/address-state').then((module) => module.addressStateModule),
|
|
20
21
|
'repeater': () => import('#modules/fields/repeater').then((module) => module.repeaterModule),
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { getTextLimitMetrics } from '@verbb/formie-core';
|
|
2
|
+
|
|
3
|
+
import type { FormieModuleDefinition } from '#contracts/modules';
|
|
4
|
+
import { getModuleFieldTarget, releaseFormValidators, retainFormValidators } from '#modules/fields/shared';
|
|
5
|
+
|
|
6
|
+
const INPUT_SELECTOR = 'input[data-formie-password-input]';
|
|
7
|
+
const PASSWORD_VALIDATORS = [
|
|
8
|
+
'passwordMinLength',
|
|
9
|
+
'passwordUppercase',
|
|
10
|
+
'passwordLowercase',
|
|
11
|
+
'passwordSpecialCharacter',
|
|
12
|
+
] as const;
|
|
13
|
+
const VALIDATOR_SCOPE = 'password-validation';
|
|
14
|
+
|
|
15
|
+
function isPasswordInput(input: Element): input is HTMLInputElement {
|
|
16
|
+
return input instanceof HTMLInputElement && input.matches(INPUT_SELECTOR);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function getMinLength(input: HTMLInputElement): number {
|
|
20
|
+
return parseInt(input.getAttribute('data-formie-password-min-length') || '', 10) || 0;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function hasAnyPasswordValidationAttributes(input: HTMLInputElement): boolean {
|
|
24
|
+
return input.hasAttribute('data-formie-password-min-length') ||
|
|
25
|
+
input.hasAttribute('data-formie-password-require-uppercase') ||
|
|
26
|
+
input.hasAttribute('data-formie-password-require-lowercase') ||
|
|
27
|
+
input.hasAttribute('data-formie-password-require-special-character');
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function hasNoRawValue(input: HTMLInputElement): boolean {
|
|
31
|
+
return input.value === '';
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function registerValidators(form: HTMLFormElement | null): void {
|
|
35
|
+
retainFormValidators(form, VALIDATOR_SCOPE, (validator) => {
|
|
36
|
+
validator.addValidator('passwordMinLength', ({ input }) => {
|
|
37
|
+
if (!isPasswordInput(input)) {
|
|
38
|
+
return true;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const limit = getMinLength(input);
|
|
42
|
+
|
|
43
|
+
if (!limit || hasNoRawValue(input)) {
|
|
44
|
+
return true;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return getTextLimitMetrics(input.value).graphemeCount >= limit;
|
|
48
|
+
}, ({ label, input, t: translate }) => {
|
|
49
|
+
return input.getAttribute('data-formie-validation-min-characters-message')
|
|
50
|
+
|| translate('{label} must be no less than {min} characters.', {
|
|
51
|
+
label,
|
|
52
|
+
min: input.getAttribute('data-formie-password-min-length') || '',
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
validator.addValidator('passwordUppercase', ({ input }) => {
|
|
57
|
+
if (!isPasswordInput(input) || !input.hasAttribute('data-formie-password-require-uppercase')) {
|
|
58
|
+
return true;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (hasNoRawValue(input)) {
|
|
62
|
+
return true;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return /[A-Z]/.test(input.value);
|
|
66
|
+
}, ({ label, input, t: translate }) => {
|
|
67
|
+
return input.getAttribute('data-formie-validation-password-uppercase-message')
|
|
68
|
+
|| translate('{label} must contain at least one uppercase letter.', { label });
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
validator.addValidator('passwordLowercase', ({ input }) => {
|
|
72
|
+
if (!isPasswordInput(input) || !input.hasAttribute('data-formie-password-require-lowercase')) {
|
|
73
|
+
return true;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (hasNoRawValue(input)) {
|
|
77
|
+
return true;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return /[a-z]/.test(input.value);
|
|
81
|
+
}, ({ label, input, t: translate }) => {
|
|
82
|
+
return input.getAttribute('data-formie-validation-password-lowercase-message')
|
|
83
|
+
|| translate('{label} must contain at least one lowercase letter.', { label });
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
validator.addValidator('passwordSpecialCharacter', ({ input }) => {
|
|
87
|
+
if (!isPasswordInput(input) || !input.hasAttribute('data-formie-password-require-special-character')) {
|
|
88
|
+
return true;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (hasNoRawValue(input)) {
|
|
92
|
+
return true;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return /[^a-zA-Z0-9]/.test(input.value);
|
|
96
|
+
}, ({ label, input, t: translate }) => {
|
|
97
|
+
return input.getAttribute('data-formie-validation-password-special-character-message')
|
|
98
|
+
|| translate('{label} must contain at least one special character.', { label });
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function unregisterValidators(form: HTMLFormElement | null): void {
|
|
104
|
+
releaseFormValidators(form, VALIDATOR_SCOPE, PASSWORD_VALIDATORS);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export const passwordValidationModule: FormieModuleDefinition = {
|
|
108
|
+
id: 'password-validation',
|
|
109
|
+
kind: 'field',
|
|
110
|
+
match: (ctx) => {
|
|
111
|
+
return !!ctx.target.querySelector(`${INPUT_SELECTOR}[data-formie-password-min-length], ${INPUT_SELECTOR}[data-formie-password-require-uppercase], ${INPUT_SELECTOR}[data-formie-password-require-lowercase], ${INPUT_SELECTOR}[data-formie-password-require-special-character]`);
|
|
112
|
+
},
|
|
113
|
+
setup: async (ctx) => {
|
|
114
|
+
const field = getModuleFieldTarget(ctx);
|
|
115
|
+
const inputs = Array.from((field || ctx.target).querySelectorAll(INPUT_SELECTOR)).filter((input): input is HTMLInputElement => {
|
|
116
|
+
return input instanceof HTMLInputElement && hasAnyPasswordValidationAttributes(input);
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
registerValidators(ctx.form);
|
|
120
|
+
|
|
121
|
+
await ctx.emit('formie:module:password-validation:init', {
|
|
122
|
+
count: inputs.length,
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
return {
|
|
126
|
+
destroy: () => {
|
|
127
|
+
unregisterValidators(ctx.form);
|
|
128
|
+
void ctx.emit('formie:module:password-validation:destroy', {});
|
|
129
|
+
},
|
|
130
|
+
};
|
|
131
|
+
},
|
|
132
|
+
};
|
|
@@ -16,10 +16,11 @@ const match: ValidationRuleDefinition = {
|
|
|
16
16
|
const sourceField = sourceInput?.closest('[data-formie-field-handle]') as HTMLElement | null;
|
|
17
17
|
const sourceLabel = getLabelText(sourceField);
|
|
18
18
|
|
|
19
|
-
return ctx.
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
19
|
+
return ctx.input.getAttribute('data-formie-validation-match-message')
|
|
20
|
+
?? ctx.t('{label} must match {value}.', {
|
|
21
|
+
label: ctx.label,
|
|
22
|
+
value: sourceLabel,
|
|
23
|
+
});
|
|
23
24
|
},
|
|
24
25
|
};
|
|
25
26
|
|
|
@@ -29,7 +29,7 @@ const minmax: ValidationRuleDefinition = {
|
|
|
29
29
|
const max = input.hasAttribute('max') ? parseFloat(input.getAttribute('max') || '') : null;
|
|
30
30
|
|
|
31
31
|
if (min !== null && max !== null) {
|
|
32
|
-
return t('{label} must be
|
|
32
|
+
return t('{label} must be no less than {min}.', { label, min });
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
if (min !== null) {
|
|
@@ -40,7 +40,7 @@ const minmax: ValidationRuleDefinition = {
|
|
|
40
40
|
return t('{label} must be no greater than {max}.', { label, max });
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
-
return t('{label}
|
|
43
|
+
return t('{label} is not a valid number.', { label });
|
|
44
44
|
},
|
|
45
45
|
};
|
|
46
46
|
|
|
@@ -36,7 +36,7 @@ const number: ValidationRuleDefinition = {
|
|
|
36
36
|
|
|
37
37
|
if (min !== null && max !== null) {
|
|
38
38
|
return input.getAttribute('data-formie-validation-number-min-message')
|
|
39
|
-
?? t('{label} must be
|
|
39
|
+
?? t('{label} must be no less than {min}.', { label, min });
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
if (min !== null) {
|
|
@@ -1,125 +0,0 @@
|
|
|
1
|
-
import { t as e } from "./debug-BV0DvdHx.js";
|
|
2
|
-
import { c as t, l as n, n as r, t as i } from "./shared-Bx9s0i0P.js";
|
|
3
|
-
//#region src/js/modules/fields/checkbox-radio.ts
|
|
4
|
-
var a = "[data-formie-checkboxes-field-layout], [data-formie-radio-field-layout]", o = "minmaxOptions", s = "data-formie-checkbox-radio-max-disabled", c = "checkbox-radio", l = "checkbox-radio", u = e("fields", "checkbox-radio");
|
|
5
|
-
function d(e) {
|
|
6
|
-
return e.hasAttribute("data-checkbox-toggle") || e.hasAttribute("data-formie-checkbox-toggle");
|
|
7
|
-
}
|
|
8
|
-
function f(e) {
|
|
9
|
-
let t = e(o);
|
|
10
|
-
if (!t || t === !0 || typeof t != "object") return {
|
|
11
|
-
min: null,
|
|
12
|
-
max: null
|
|
13
|
-
};
|
|
14
|
-
let n = t;
|
|
15
|
-
return {
|
|
16
|
-
min: typeof n.min == "number" ? n.min : null,
|
|
17
|
-
max: typeof n.max == "number" ? n.max : null
|
|
18
|
-
};
|
|
19
|
-
}
|
|
20
|
-
function p(e) {
|
|
21
|
-
return Array.from(e.querySelectorAll("input[type=\"checkbox\"]")).filter((e) => e instanceof HTMLInputElement && !d(e)).filter((e) => e.checked).length;
|
|
22
|
-
}
|
|
23
|
-
function m(e) {
|
|
24
|
-
n(e, l, (e) => {
|
|
25
|
-
e.addValidator(o, ({ field: e, getRule: t }) => {
|
|
26
|
-
if (!e || !t(o)) return !0;
|
|
27
|
-
let n = p(e), { min: r, max: i } = f(t);
|
|
28
|
-
return !(r !== null && n < r || i !== null && n > i);
|
|
29
|
-
}, ({ field: e, label: t, t: n, getRule: r }) => {
|
|
30
|
-
if (!e) return n("{label} has an invalid value.", { label: t });
|
|
31
|
-
let i = p(e), { min: a, max: o } = f(r);
|
|
32
|
-
return a !== null && i < a ? e.getAttribute("data-formie-validation-min-options-message") ?? n("{label} should contain at least {min, number} {min, plural, one{option} other{options}}.", {
|
|
33
|
-
label: t,
|
|
34
|
-
min: a
|
|
35
|
-
}) : o !== null && i > o ? e.getAttribute("data-formie-validation-max-options-message") ?? n("{label} should contain at most {max, number} {max, plural, one{option} other{options}}.", {
|
|
36
|
-
label: t,
|
|
37
|
-
max: o
|
|
38
|
-
}) : n("{label} has an invalid value.", { label: t });
|
|
39
|
-
});
|
|
40
|
-
});
|
|
41
|
-
}
|
|
42
|
-
function h(e) {
|
|
43
|
-
t(e, l, [o]);
|
|
44
|
-
}
|
|
45
|
-
function g(e) {
|
|
46
|
-
e.checked ? e.setAttribute("checked", "") : e.removeAttribute("checked");
|
|
47
|
-
}
|
|
48
|
-
function _(e) {
|
|
49
|
-
let t = Array.from(e.querySelectorAll("input[type=\"checkbox\"][required][data-formie-checkbox-input]")).filter((e) => e instanceof HTMLInputElement);
|
|
50
|
-
if (!t.length) return;
|
|
51
|
-
let n = t.some((e) => e.checked);
|
|
52
|
-
t.forEach((e) => {
|
|
53
|
-
if (n) {
|
|
54
|
-
e.removeAttribute("required"), e.setAttribute("aria-required", "false");
|
|
55
|
-
return;
|
|
56
|
-
}
|
|
57
|
-
e.setAttribute("required", "true"), e.setAttribute("aria-required", "true");
|
|
58
|
-
});
|
|
59
|
-
}
|
|
60
|
-
function v(e) {
|
|
61
|
-
let t = parseInt(e.closest("[data-formie-field-handle]")?.getAttribute("data-formie-max-options") || "", 10);
|
|
62
|
-
if (!(t > 0)) return;
|
|
63
|
-
let n = Array.from(e.querySelectorAll("input[type=\"checkbox\"]")).filter((e) => e instanceof HTMLInputElement && !d(e)), r = n.filter((e) => e.checked).length >= t;
|
|
64
|
-
n.forEach((e) => {
|
|
65
|
-
let t = r && !e.checked, n = e.hasAttribute(s);
|
|
66
|
-
if (t) {
|
|
67
|
-
e.disabled || (e.disabled = !0, e.setAttribute(s, "true"));
|
|
68
|
-
return;
|
|
69
|
-
}
|
|
70
|
-
n && (e.disabled = !1, e.removeAttribute(s));
|
|
71
|
-
});
|
|
72
|
-
}
|
|
73
|
-
function y(e, t) {
|
|
74
|
-
Array.from(e.querySelectorAll("input[type=\"checkbox\"]")).filter((e) => e instanceof HTMLInputElement && e !== t && !d(e)).forEach((e) => {
|
|
75
|
-
e.disabled && !e.checked || (e.checked = t.checked, g(e), e.dispatchEvent(new Event("change", { bubbles: !0 })), e.dispatchEvent(new Event("input", { bubbles: !0 })));
|
|
76
|
-
});
|
|
77
|
-
}
|
|
78
|
-
function b(e, t) {
|
|
79
|
-
if (!e.checked || !e.name) {
|
|
80
|
-
g(e);
|
|
81
|
-
return;
|
|
82
|
-
}
|
|
83
|
-
Array.from(t.querySelectorAll(`input[type="radio"][name="${r(e.name)}"]`)).filter((e) => e instanceof HTMLInputElement).forEach((e) => {
|
|
84
|
-
g(e);
|
|
85
|
-
});
|
|
86
|
-
}
|
|
87
|
-
function x(e) {
|
|
88
|
-
let t = Array.from(e.querySelectorAll("input[type=\"checkbox\"], input[type=\"radio\"]")).filter((e) => e instanceof HTMLInputElement);
|
|
89
|
-
if (!t.length) return u.log("No checkbox/radio inputs found for field."), () => {};
|
|
90
|
-
let n = t.map((t) => {
|
|
91
|
-
let n = t.type === "radio" ? "change" : "click", r = () => {
|
|
92
|
-
g(t), t.type === "checkbox" && d(t) && y(e, t), t.type === "radio" && b(t, e), _(e), v(e), u.log("Input interaction processed.", {
|
|
93
|
-
inputName: t.name,
|
|
94
|
-
inputType: t.type,
|
|
95
|
-
checked: t.checked
|
|
96
|
-
});
|
|
97
|
-
};
|
|
98
|
-
return t.addEventListener(n, r), g(t), () => {
|
|
99
|
-
t.removeEventListener(n, r);
|
|
100
|
-
};
|
|
101
|
-
});
|
|
102
|
-
return _(e), v(e), i(e, c, "init", { checkboxRadio: e }), () => {
|
|
103
|
-
n.forEach((e) => {
|
|
104
|
-
e();
|
|
105
|
-
});
|
|
106
|
-
};
|
|
107
|
-
}
|
|
108
|
-
var S = {
|
|
109
|
-
id: c,
|
|
110
|
-
kind: "field",
|
|
111
|
-
match: (e) => e.target instanceof HTMLElement && (e.target.matches(a) || !!e.target.querySelector(a)),
|
|
112
|
-
setup: async (e) => {
|
|
113
|
-
if (!(e.target instanceof HTMLElement)) return;
|
|
114
|
-
let t = e.target.matches(a) ? [e.target] : Array.from(e.target.querySelectorAll(a)).filter((e) => e instanceof HTMLElement);
|
|
115
|
-
m(e.form), u.log("Module setup.", { fieldCount: t.length });
|
|
116
|
-
let n = t.map((e) => x(e));
|
|
117
|
-
return await e.emit("formie:module:checkbox-radio:init", { count: t.length }), { destroy: () => {
|
|
118
|
-
n.forEach((e) => {
|
|
119
|
-
e();
|
|
120
|
-
}), h(e.form), u.log("Module destroy.", { fieldCount: t.length }), e.emit("formie:module:checkbox-radio:destroy", {});
|
|
121
|
-
} };
|
|
122
|
-
}
|
|
123
|
-
};
|
|
124
|
-
//#endregion
|
|
125
|
-
export { S as checkboxRadioModule };
|