@verbb/formie-browser 1.0.9 → 1.0.11

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 CHANGED
@@ -2,7 +2,21 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
- ## 1.0.9 - 2026-06-24
5
+ ## 1.0.11 - 2026-06-26
6
+
7
+ ### Added
8
+ - Add Radio field **Other** choice support with free-text input, show/hide behaviour, and client-side validation when the Other option is selected. ([#1875](https://github.com/verbb/formie/issues/1875))
9
+ - Add the `password-validation` field module for Password fields, enforcing minimum length and uppercase, lowercase, and special character requirements on the front-end. ([#1880](https://github.com/verbb/formie/issues/1880))
10
+
11
+ ### Changed
12
+ - Insert CSRF hidden inputs during token refresh when cached or statically rendered forms omit them via the `csrfInput: false` render option. ([#2121](https://github.com/verbb/formie/discussions/2121))
13
+
14
+ ## 1.0.10 - 2026-06-26
15
+
16
+ ### Changed
17
+ - 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))
18
+
19
+ ## 1.0.9 - 2026-06-25
6
20
 
7
21
  ### Changed
8
22
  - 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-BQ4a9hmy.js";
5
+ import { c as s, i as c, r as l, s as u, t as d } from "./dist-BdUy8m_p.js";
6
6
  //#region src/js/utils/field-references.row-scope.ts
7
7
  var f = new Set([
8
8
  "first",
@@ -159,10 +159,10 @@ function w(e, t, n) {
159
159
  return t.forEach(([e, t]) => {
160
160
  let n = x(t);
161
161
  if (String(n.scope || "").trim()) {
162
- o[e] = c(t, y(t.sourceKey || "", n, a).value);
162
+ o[e] = l(t, y(t.sourceKey || "", n, a).value);
163
163
  return;
164
164
  }
165
- o[e] = c(t, r(t.sourceKey || "", a).value);
165
+ o[e] = l(t, r(t.sourceKey || "", a).value);
166
166
  }), d(o, n.formatting);
167
167
  }
168
168
  function T(e, r) {
@@ -187,12 +187,12 @@ function T(e, r) {
187
187
  }), o;
188
188
  }
189
189
  function E(e, t, n, r) {
190
- let i = s(r), a = l(r), c = /* @__PURE__ */ new Map(), d = null, f = !1, p = !1, m = !1, h = () => {
191
- c.forEach((e, t) => {
190
+ let i = s(r), a = c(r), l = /* @__PURE__ */ new Map(), d = null, f = !1, p = !1, m = !1, h = () => {
191
+ l.forEach((e, t) => {
192
192
  e.forEach((e, n) => {
193
193
  t.removeEventListener(n, e);
194
194
  });
195
- }), c.clear();
195
+ }), l.clear();
196
196
  }, g = (e) => {
197
197
  !e || f || queueMicrotask(() => {
198
198
  f || (n.dispatchEvent(new Event("input", { bubbles: !0 })), n.dispatchEvent(new Event("change", { bubbles: !0 })));
@@ -257,8 +257,8 @@ function E(e, t, n, r) {
257
257
  };
258
258
  ["input", "change"].forEach((t) => {
259
259
  e.addEventListener(t, r);
260
- let n = c.get(e) || /* @__PURE__ */ new Map();
261
- n.set(t, r), c.set(e, n);
260
+ let n = l.get(e) || /* @__PURE__ */ new Map();
261
+ n.set(t, r), l.set(e, n);
262
262
  });
263
263
  }, b = () => {
264
264
  m || f || (m = !0, queueMicrotask(() => {
@@ -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-BQ4a9hmy.js";
4
+ import { a as r, o as i } from "./dist-BdUy8m_p.js";
5
5
  //#region src/js/modules/fields/conditions/config.ts
6
6
  var a = "[data-formie-conditions]";
7
7
  function o(e) {
@@ -2656,4 +2656,4 @@ function P(e) {
2656
2656
  };
2657
2657
  }
2658
2658
  //#endregion
2659
- export { g as a, x as c, P as i, T as n, h as o, S as r, E as s, C as t };
2659
+ export { g as a, x as c, S as i, P as n, h as o, T as r, E as s, C as t };
@@ -0,0 +1,49 @@
1
+ import { c as e, i as t, l as n } from "./shared-Bx9s0i0P.js";
2
+ import { n as r } from "./dist-BdUy8m_p.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-BQ4a9hmy.js";
4
+ import { n as a } from "./dist-BdUy8m_p.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 between {min} and {max}.", {
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
- max: o
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-CeDgB9jt.js").then((e) => e.calculationsModule),
1390
- "checkbox-radio": () => import("./chunks/checkbox-radio-DQ0H67Tj.js").then((e) => e.checkboxRadioModule),
1385
+ calculations: () => import("./chunks/calculations-DERi2xaC.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-B6_nzpeE.js").then((e) => e.conditionsModule),
1388
+ conditions: () => import("./chunks/conditions-65kBcFWW.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-T0vDQPCr.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-DbgEFHLO.js").then((e) => e.textLimitModule)
1408
+ "text-limit": () => import("./chunks/text-limit-Bn18WrPU.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 && (n.value = t.csrf.token);
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;AAs1B3B,wBAAgB,kBAAkB,IAAI,YAAY,CAysBjD"}
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;AA6PjE,eAAO,MAAM,mBAAmB,EAAE,sBA+CjC,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,CA0B3F,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,3 @@
1
+ import type { FormieModuleDefinition } from '#contracts/modules';
2
+ export declare const passwordValidationModule: FormieModuleDefinition;
3
+ //# sourceMappingURL=password-validation.d.ts.map
@@ -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,wBAoBZ,CAAC;AAEF,eAAe,KAAK,CAAC"}
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.9",
3
+ "version": "1.0.11",
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.9",
73
+ "@verbb/formie-core": "1.0.11",
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
- const csrfInput = form.querySelector(`input[name="${refreshTokens.csrf.param}"]`) as HTMLInputElement | null;
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} has an invalid value.', { 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} has an invalid value.', { 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.t('{label} must match {value}.', {
20
- label: ctx.label,
21
- value: sourceLabel,
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 between {min} and {max}.', { label, min, max });
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} has an invalid value.', { 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 between {min} and {max}.', { label, min, max });
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 };