ctrovalidate-browser 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +147 -0
- package/dist/index.cjs +1 -0
- package/dist/index.d.ts +137 -0
- package/dist/index.js +861 -0
- package/package.json +59 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Ctrotech
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
# ctrovalidate-browser
|
|
2
|
+
|
|
3
|
+
**DOM-first validation for vanilla JavaScript and HTML.**
|
|
4
|
+
|
|
5
|
+
`ctrovalidate-browser` provides a `Ctrovalidate` controller class that bridges [`ctrovalidate-core`](https://www.npmjs.com/package/ctrovalidate-core) validation logic to the DOM. Discover fields via data attributes, manage event listeners, display errors, and handle ARIA accessibility — all without a framework.
|
|
6
|
+
|
|
7
|
+
[](https://www.npmjs.com/package/ctrovalidate-browser)
|
|
8
|
+
[](https://opensource.org/licenses/MIT)
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## Installation
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
npm install ctrovalidate-browser ctrovalidate-core
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## Quick Start
|
|
21
|
+
|
|
22
|
+
### HTML
|
|
23
|
+
|
|
24
|
+
```html
|
|
25
|
+
<form id="signupForm" novalidate>
|
|
26
|
+
<div>
|
|
27
|
+
<input
|
|
28
|
+
type="email"
|
|
29
|
+
name="email"
|
|
30
|
+
data-ctrovalidate-rules="required|email"
|
|
31
|
+
/>
|
|
32
|
+
<div class="error-message"></div>
|
|
33
|
+
</div>
|
|
34
|
+
|
|
35
|
+
<div>
|
|
36
|
+
<input
|
|
37
|
+
type="password"
|
|
38
|
+
name="password"
|
|
39
|
+
data-ctrovalidate-rules="required|minLength:8|strongPassword"
|
|
40
|
+
/>
|
|
41
|
+
<div class="error-message"></div>
|
|
42
|
+
</div>
|
|
43
|
+
|
|
44
|
+
<button type="submit">Sign Up</button>
|
|
45
|
+
</form>
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### JavaScript
|
|
49
|
+
|
|
50
|
+
```javascript
|
|
51
|
+
import { Ctrovalidate } from 'ctrovalidate-browser';
|
|
52
|
+
|
|
53
|
+
const form = document.querySelector('#signupForm');
|
|
54
|
+
const validator = new Ctrovalidate(form, {
|
|
55
|
+
errorClass: 'is-invalid',
|
|
56
|
+
errorMessageClass: 'error-message',
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
form.addEventListener('submit', async (e) => {
|
|
60
|
+
e.preventDefault();
|
|
61
|
+
const isValid = await validator.validate();
|
|
62
|
+
if (isValid) {
|
|
63
|
+
// Submit
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
---
|
|
69
|
+
|
|
70
|
+
## Data Attributes
|
|
71
|
+
|
|
72
|
+
| Attribute | Description |
|
|
73
|
+
|-----------|-------------|
|
|
74
|
+
| `data-ctrovalidate-rules` | Pipe-delimited rule string (e.g. `required\|email\|minLength:8`) |
|
|
75
|
+
| `data-ctrovalidate-if` | Conditional validation dependency (e.g. `agreeTerms:checked`, `country:value=USA`) |
|
|
76
|
+
| `data-ctrovalidate-message` | Catch-all error message for the field |
|
|
77
|
+
| `data-ctrovalidate-{ruleName}-message` | Rule-specific error message |
|
|
78
|
+
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
## Configuration
|
|
82
|
+
|
|
83
|
+
```javascript
|
|
84
|
+
const validator = new Ctrovalidate(form, {
|
|
85
|
+
realTime: true, // Enable blur/input listeners
|
|
86
|
+
errorClass: 'is-invalid border-red-500', // Supports Tailwind space-separated
|
|
87
|
+
errorMessageClass: 'error-message', // Also space-separated
|
|
88
|
+
pendingClass: 'is-validating', // Added during async validation
|
|
89
|
+
logLevel: Ctrovalidate.LogLevel.NONE, // Logging verbosity
|
|
90
|
+
schema: { email: 'required|email' }, // Programmatic rules (merged with HTML)
|
|
91
|
+
aliases: { username: 'required|alphaDash|minLength:3' }, // Instance aliases
|
|
92
|
+
});
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
---
|
|
96
|
+
|
|
97
|
+
## Instance Methods
|
|
98
|
+
|
|
99
|
+
```javascript
|
|
100
|
+
await validator.validate(); // Validate all fields
|
|
101
|
+
validator.addField(element); // Register a field
|
|
102
|
+
validator.removeField(element); // Unregister a field
|
|
103
|
+
validator.refresh(); // Re-discover fields
|
|
104
|
+
validator.isDirty('email'); // Check if field was interacted with
|
|
105
|
+
validator.getError('email'); // Get current error message
|
|
106
|
+
validator.reset(); // Clear all validation state
|
|
107
|
+
validator.destroy(); // Full cleanup
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
---
|
|
111
|
+
|
|
112
|
+
## Static Methods
|
|
113
|
+
|
|
114
|
+
```javascript
|
|
115
|
+
Ctrovalidate.defineAlias('password', 'required|minLength:8|strongPassword');
|
|
116
|
+
Ctrovalidate.addRule('isEven', (v) => Number(v) % 2 === 0, 'Must be even.');
|
|
117
|
+
Ctrovalidate.addAsyncRule('checkEmail', async (v, p, el, signal) => { ... });
|
|
118
|
+
Ctrovalidate.setCustomMessages({ required: 'Cannot be empty.' });
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
---
|
|
122
|
+
|
|
123
|
+
## Accessibility
|
|
124
|
+
|
|
125
|
+
The controller automatically manages:
|
|
126
|
+
|
|
127
|
+
- `aria-invalid="true"` on validation failure
|
|
128
|
+
- `aria-describedby` linking to the error container
|
|
129
|
+
- `role="status"` and `aria-live="polite"` on fallback error containers
|
|
130
|
+
|
|
131
|
+
---
|
|
132
|
+
|
|
133
|
+
## Related Packages
|
|
134
|
+
|
|
135
|
+
- **[ctrovalidate-core](https://www.npmjs.com/package/ctrovalidate-core)** — Validation engine
|
|
136
|
+
- **[ctrovalidate-react](https://www.npmjs.com/package/ctrovalidate-react)** — React hook
|
|
137
|
+
- **[ctrovalidate-vue](https://www.npmjs.com/package/ctrovalidate-vue)** — Vue composable
|
|
138
|
+
- **[ctrovalidate-svelte](https://www.npmjs.com/package/ctrovalidate-svelte)** — Svelte stores
|
|
139
|
+
- **[ctrovalidate-next](https://www.npmjs.com/package/ctrovalidate-next)** — Next.js server actions
|
|
140
|
+
|
|
141
|
+
---
|
|
142
|
+
|
|
143
|
+
## License
|
|
144
|
+
|
|
145
|
+
MIT © [Ctrotech](https://github.com/ctrotech-tutor)
|
|
146
|
+
|
|
147
|
+
Full documentation: https://ctrovalidate.vercel.app
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";var Ke=Object.defineProperty;var oe=t=>{throw TypeError(t)};var Qe=(t,e,r)=>e in t?Ke(t,e,{enumerable:!0,configurable:!0,writable:!0,value:r}):t[e]=r;var de=(t,e,r)=>Qe(t,typeof e!="symbol"?e+"":e,r),j=(t,e,r)=>e.has(t)||oe("Cannot "+r);var s=(t,e,r)=>(j(t,e,"read from private field"),r?r.call(t):e.get(t)),o=(t,e,r)=>e.has(t)?oe("Cannot add the same private member more than once"):e instanceof WeakSet?e.add(t):e.set(t,r),c=(t,e,r,a)=>(j(t,e,"write to private field"),a?a.call(t,r):e.set(t,r),r),v=(t,e,r)=>(j(t,e,"access private method"),r);var ce=(t,e,r,a)=>({set _(i){c(t,e,i,r)},get _(){return s(t,e,a)}});Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});var ve=Object.defineProperty,be=t=>{throw TypeError(t)},Xe=(t,e,r)=>e in t?ve(t,e,{enumerable:!0,configurable:!0,writable:!0,value:r}):t[e]=r,Ye=(t,e)=>{for(var r in e)ve(t,r,{get:e[r],enumerable:!0})},ie=(t,e,r)=>Xe(t,typeof e!="symbol"?e+"":e,r),ye=(t,e,r)=>e.has(t)||be("Cannot "+r),N=(t,e,r)=>(ye(t,e,"read from private field"),r?r.call(t):e.get(t)),ue=(t,e,r)=>e.has(t)?be("Cannot add the same private member more than once"):e instanceof WeakSet?e.add(t):e.set(t,r),he=(t,e,r,a)=>(ye(t,e,"write to private field"),e.set(t,r),r);function fe(t){if(!t||typeof t!="string")return[];const e=[],r=t.split("|");for(const a of r){const i=a.trim();if(i==="")continue;const l=i.indexOf(":");if(l===-1)e.push({name:i,params:[]});else{const n=i.substring(0,l).trim(),d=i.substring(l+1).trim();if(n){const u=d.split(",").map(g=>g.trim());e.push({name:n,params:u})}}}return e}function ee(t,e={},r=new Set){if(!t)return[];let a;if(typeof t=="string")a=fe(t);else if(Array.isArray(t))a=t;else return[];return a.flatMap(i=>{const l=typeof i=="string"?i.split(":")[0]:i.name;if(e[l]&&!r.has(l)){const n=new Set(r);return n.add(l),ee(e[l],e,n)}return typeof i=="string"?fe(i):[i]})}var J=(t=>(t[t.NONE=0]="NONE",t[t.ERROR=1]="ERROR",t[t.WARN=2]="WARN",t[t.INFO=3]="INFO",t[t.DEBUG=4]="DEBUG",t))(J||{}),ne=class Ee{constructor(e=0){ie(this,"level"),this.level=e}setLevel(e){this.level=e}static setLevel(e){this.globalLevel=e}format(e){return`${Ee.prefix} ${e}`}error(e,...r){this.level>=1&&console.error(this.format(e),...r)}warn(e,...r){this.level>=2&&console.warn(this.format(e),...r)}info(e,...r){this.level>=3&&console.info(this.format(e),...r)}debug(e,...r){this.level>=4&&console.debug(this.format(e),...r)}static error(e,...r){this.globalLevel>=1&&console.error(`${this.prefix} ${e}`,...r)}static warn(e,...r){this.globalLevel>=2&&console.warn(`${this.prefix} ${e}`,...r)}static info(e,...r){this.globalLevel>=3&&console.info(`${this.prefix} ${e}`,...r)}static debug(e,...r){this.globalLevel>=4&&console.debug(`${this.prefix} ${e}`,...r)}};ie(ne,"globalLevel",1);ie(ne,"prefix","[Ctrovalidate]");var je=ne,et={};Ye(et,{alpha:()=>xe,alphaDash:()=>$e,alphaNum:()=>Re,alphaSpaces:()=>nt,between:()=>Ve,creditCard:()=>qe,decimal:()=>Te,email:()=>Ae,exactLength:()=>ke,integer:()=>Me,ipAddress:()=>_e,json:()=>Ie,max:()=>we,maxLength:()=>Ne,min:()=>Ce,minLength:()=>Se,numeric:()=>Le,phone:()=>De,required:()=>Fe,sameAs:()=>Be,strongPassword:()=>ze,url:()=>Pe});var Fe=t=>t==null?!1:typeof t=="boolean"?t:String(t).trim()!=="",tt=/^[^\s@]+@[^\s@]+\.[^\s@]+$/,Ae=t=>t?tt.test(String(t)):!0,Ce=(t,e=[])=>{if(t==null||t==="")return!0;if(!e[0])return console.error("[Ctrovalidate] Missing parameter for 'min' rule."),!1;const r=Number(e[0]);return Number(t)>=r},we=(t,e=[])=>{if(t==null||t==="")return!0;if(!e[0])return console.error("[Ctrovalidate] Missing parameter for 'max' rule."),!1;const r=Number(e[0]);return Number(t)<=r},Se=(t,e=[])=>{if(t==null||t==="")return!0;if(!e[0])return console.error("[Ctrovalidate] Missing parameter for 'minLength' rule."),!1;const r=Number(e[0]);return String(t).length>=r},Ne=(t,e=[])=>{if(t==null||t==="")return!0;if(!e[0])return console.error("[Ctrovalidate] Missing parameter for 'maxLength' rule."),!1;const r=Number(e[0]);return String(t).length<=r},rt=/^[a-zA-Z]+$/,xe=t=>t?rt.test(String(t)):!0,st=/^[a-zA-Z0-9]+$/,Re=t=>t?st.test(String(t)):!0,at=/^[a-zA-Z0-9-_]+$/,$e=t=>t?at.test(String(t)):!0,it=/^[a-zA-Z\s]+$/,nt=t=>t?it.test(String(t)):!0,Le=t=>!t&&t!==0?!0:!isNaN(Number(t))&&!isNaN(parseFloat(String(t))),lt=/^-?\d+$/,Me=t=>!t&&t!==0?!0:lt.test(String(t)),ot=/^-?\d+(\.\d+)?$/,Te=t=>!t&&t!==0?!0:ot.test(String(t)),dt=/^(https?:\/\/)([\da-z.-]+)\.([a-z.]{2,6})([/\w .-]*)*\/?$/,Pe=t=>t?dt.test(String(t)):!0,ct=/^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/,ut=/^(?:(?:[a-fA-F\d]{1,4}:){7}(?:[a-fA-F\d]{1,4}|:)|(?:[a-fA-F\d]{1,4}:){6}(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|:[a-fA-F\d]{1,4}|:)|(?:[a-fA-F\d]{1,4}:){5}(?::(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-fA-F\d]{1,4}){1,2}|:)|(?:[a-fA-F\d]{1,4}:){4}(?:(?::[a-fA-F\d]{1,4}){0,1}:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-fA-F\d]{1,4}){1,3}|:)|(?:[a-fA-F\d]{1,4}:){3}(?:(?::[a-fA-F\d]{1,4}){0,2}:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-fA-F\d]{1,4}){1,4}|:)|(?:[a-fA-F\d]{1,4}:){2}(?:(?::[a-fA-F\d]{1,4}){0,3}:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-fA-F\d]{1,4}){1,5}|:)|(?:[a-fA-F\d]{1,4}:){1}(?:(?::[a-fA-F\d]{1,4}){0,4}:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-fA-F\d]{1,4}){1,6}|:)|(?::(?:(?::[a-fA-F\d]{1,4}){0,5}:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-fA-F\d]{1,4}){1,7}|:)))(?:%[0-9a-zA-Z]{1,})?$/,_e=t=>{if(!t)return!0;const e=String(t);return ct.test(e)||ut.test(e)},Ie=t=>{if(!t)return!0;try{const e=JSON.parse(String(t));return typeof e=="object"&&e!==null}catch{return!1}},ht=/^[+]?([(]?[0-9]{1,4}[)]?)[-\s.]?[0-9]{3,4}[-\s.]?[0-9]{4,6}$/,De=t=>t?ht.test(String(t)):!0,qe=t=>{if(!t)return!0;const e=String(t).replace(/[- ]/g,"");if(!/^\d+$/.test(e))return!1;let r=0;const a=e.length%2;for(let i=0;i<e.length;i++){let l=parseInt(e[i],10);i%2===a&&(l*=2,l>9&&(l-=9)),r+=l}return r%10===0},ft=/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$/,ze=t=>t?ft.test(String(t)):!0,ke=(t,e=[])=>{if(t==null||t==="")return!0;if(!e[0])return console.error("[Ctrovalidate] Missing parameter for 'exactLength' rule."),!1;const r=Number(e[0]);return String(t).length===r},Ve=(t,e=[])=>{if(t==null||t==="")return!0;if(e.length<2)return console.error("[Ctrovalidate] Missing parameters for 'between' rule."),!1;const r=Number(e[0]),a=Number(e[1]);if(isNaN(r)||isNaN(a))return!1;if(!isNaN(Number(t))&&typeof t!="string"){const n=Number(t);return n>=r&&n<=a}const i=String(t);if(!isNaN(Number(i))&&i!==""){const n=Number(i);return n>=r&&n<=a}const l=i.length;return l>=r&&l<=a},Be=(t,e=[])=>{if(e.length===0||e[0]===void 0)return console.error("[Ctrovalidate] Missing parameter for 'sameAs' rule."),!1;const r=e[0];return t===r},mt={required:"This field is required.",email:"Please enter a valid email address.",min:"Minimum value is {0}.",max:"Maximum value is {0}.",minLength:"Minimum length is {0} characters.",maxLength:"Maximum length is {0} characters.",alpha:"This field may only contain alphabetic characters.",alphaNum:"This field may only contain alpha-numeric characters.",alphaDash:"This field may only contain alpha-numeric characters as well as dashes and underscores.",alphaSpaces:"This field may only contain alphabetic characters and spaces.",numeric:"This field must be a number.",integer:"This field must be an integer.",decimal:"This field must be a decimal number.",url:"Please enter a valid URL.",ipAddress:"Please enter a valid IP address.",json:"Please enter a valid JSON string.",phone:"Please enter a valid phone number.",creditCard:"Please enter a valid credit card number.",strongPassword:"Password must be at least 8 characters long and include an uppercase letter, a lowercase letter, a number, and a symbol.",exactLength:"This field must be exactly {0} characters long.",between:"This field must be between {0} and {1}.",sameAs:"This field must match {0}."},x,I,gt=class{constructor(){ue(this,x,{en:mt}),ue(this,I,"en")}setLocale(t){if(!N(this,x)[t]){console.warn(`[Ctrovalidate] Locale "${t}" not found. Falling back to "en".`),he(this,I,"en");return}he(this,I,t)}addMessages(t,e){N(this,x)[t]={...N(this,x)[t]||{},...e}}translate(t,e=[],r){const a=r||N(this,I),i=N(this,x)[a]||N(this,x).en;return(i[t]||i.default||"Invalid input.").replace(/{(\d+)}/g,(n,d)=>{const u=parseInt(d,10);return e[u]!==void 0?String(e[u]):n})}get currentLocale(){return N(this,I)}};x=new WeakMap;I=new WeakMap;new gt;var D,B,q,R,U,K,C,We,re,se;const Q=class Q{constructor({errorClass:e="ctrovalidate-error",errorMessageClass:r="ctrovalidate-error-message",pendingClass:a="ctrovalidate-pending"}){o(this,C);o(this,D);o(this,B);o(this,q);o(this,R,new WeakMap);o(this,U,new WeakMap);c(this,D,e),c(this,B,r),c(this,q,a)}displayError(e,r){const a=v(this,C,se).call(this,e);s(this,D).split(/\s+/).filter(Boolean).forEach(i=>e.classList.add(i)),e.setAttribute("aria-invalid","true"),a&&(a.textContent=r,a.style.display="",e.setAttribute("aria-describedby",a.id))}clearError(e){const r=v(this,C,se).call(this,e);s(this,D).split(/\s+/).filter(Boolean).forEach(a=>e.classList.remove(a)),e.removeAttribute("aria-invalid"),r&&(r.textContent="",r.style.display="none",e.getAttribute("aria-describedby")===r.id&&e.removeAttribute("aria-describedby"))}showPending(e){this.clearError(e),s(this,q).split(/\s+/).filter(Boolean).forEach(r=>e.classList.add(r))}hidePending(e){s(this,q).split(/\s+/).filter(Boolean).forEach(r=>e.classList.remove(r))}};D=new WeakMap,B=new WeakMap,q=new WeakMap,R=new WeakMap,U=new WeakMap,K=new WeakMap,C=new WeakSet,We=function(e){return typeof CSS<"u"&&typeof CSS.escape=="function"?CSS.escape(e):e.replace(/([!"#$%&'()*+,./:;<=>?@[\\\]^`{|}~[\]])/g,"\\$1")},re=function(e){let r;if((e instanceof HTMLInputElement||e instanceof HTMLSelectElement||e instanceof HTMLTextAreaElement)&&(r=e.name),r&&r.trim().length>0)return`ctrovalidate-error-${r.replace(/\s+/g,"-").replace(/[^A-Za-z0-9\-_:.]/g,"")}`;const a=`ctrovalidate-error-${ce(Q,K)._++}`;return s(this,U).set(e,a),a},se=function(e){if(s(this,R).has(e))return s(this,R).get(e)||null;const r=s(this,B).split(/\s+/).filter(Boolean);let a=e.parentElement,i=0;const l=3;for(;a&&i<l;){for(const g of r){const S=`.${v(this,C,We).call(this,g)}`,f=a.querySelector(S);if(f)return f.id||(f.id=v(this,C,re).call(this,e)),s(this,R).set(e,f),f}a=a.parentElement,i++}const n=e.parentElement;if(!n)return null;const d=document.createElement("div");d.className=r.join(" "),d.style.display="none",d.role="status",d.setAttribute("aria-live","polite");const u=v(this,C,re).call(this,e);d.id=u;try{e.insertAdjacentElement("afterend",d)}catch{n.appendChild(d)}return s(this,R).set(e,d),d},o(Q,K,0);let te=Q;var b,w,W,Z,H,O,X,Ze;class pt{constructor(e,{logger:r,uiManager:a,rules:i,asyncRules:l,messages:n}){o(this,X);o(this,b);o(this,w);o(this,W);o(this,Z);o(this,H);o(this,O);c(this,W,e),c(this,b,r),c(this,w,a),c(this,Z,i),c(this,H,l),c(this,O,n)}async validateField(e){const{element:r,rules:a,state:i,dependency:l}=e;if(!v(this,X,Ze).call(this,l))return s(this,b).debug(`[RuleEngine] Dependency for "${r.name}" not met. Skipping validation.`),s(this,w).clearError(r),e.state.lastError=null,!0;const n=r,d=n.type==="checkbox"?n.checked:n.value;i.abortController&&(i.abortController.abort(),s(this,b).debug(`[RuleEngine] Aborted previous validation for "${n.name}".`));for(const u of a){const g=s(this,Z)[u.name],S=s(this,H)[u.name];if(!g&&!S){s(this,b).warn(`[RuleEngine] Unknown rule "${u.name}" used on field:`,r);continue}let f=!1;if(g)f=g(d,u.params,r);else if(S){i.abortController=new AbortController,s(this,w).showPending(r);try{f=await S(d,u.params,r,i.abortController.signal)}catch(p){if(p instanceof Error&&p.name==="AbortError")return s(this,b).debug(`[RuleEngine] Async validation for "${n.name}" was successfully aborted.`),!0;s(this,b).error(`[RuleEngine] Async rule "${u.name}" threw an error.`,[p]),f=!1}finally{s(this,w).hidePending(r),i.abortController=null}}if(!f){let p=s(this,O)[u.name]||"Invalid input.";e.customMessages&&(e.customMessages[u.name]?p=e.customMessages[u.name]:e.customMessages["*"]&&(p=e.customMessages["*"]));const _=p.replace(/{(\d+)}/g,(Je,Ue)=>{const le=parseInt(Ue,10);return u.params[le]!==void 0?String(u.params[le]):Je});return s(this,w).displayError(r,_),e.state.lastError=_,s(this,b).debug(`[RuleEngine] Field validation failed for "${n.name}" on rule "${u.name}".`),!1}}return s(this,w).clearError(r),e.state.lastError=null,s(this,b).debug(`[RuleEngine] Field validation succeeded for "${n.name}".`),!0}}b=new WeakMap,w=new WeakMap,W=new WeakMap,Z=new WeakMap,H=new WeakMap,O=new WeakMap,X=new WeakSet,Ze=function(e){if(!e)return!0;const r=s(this,W).querySelector(`[name="${e.controllerName}"]`);if(!r)return!1;switch(e.type){case"checked":return r.checked;case"value":return r.value===e.value;case"present":return!!r.value;default:return!1}};var $,y,E,m,z,k,L,P,ae,He;class vt{constructor(e,{logger:r,schema:a={},aliases:i={},validationHandler:l}){o(this,P);o(this,$);o(this,y,[]);o(this,E,new Map);o(this,m);o(this,z);o(this,k);o(this,L);c(this,$,e),c(this,m,r),c(this,z,a),c(this,k,i),c(this,L,l)}discoverFields(){c(this,y,[]),s(this,E).clear();const e=s(this,$).querySelectorAll("[data-ctrovalidate-rules]");s(this,m).debug("[FormController] Discovered "+e.length+" fields to validate."),e.forEach(r=>this.addField(r,!1)),Object.keys(s(this,z)).forEach(r=>{const a=s(this,$).querySelector(`[name="${r}"]`);a&&!s(this,E).has(a)&&this.addField(a,!1)})}addField(e,r=!0){if(!e||s(this,E).has(e))return;const a=e.getAttribute("data-ctrovalidate-rules"),i=e.getAttribute("data-ctrovalidate-if"),l=e.name,n=ee(a||"",s(this,k)),d=l?ee(s(this,z)[l]||[],s(this,k)):[],u=[...n,...d];if(u.length===0&&!i)return;const g={},S=e.getAttribute("data-ctrovalidate-message");S&&(g["*"]=S);for(const p of Array.from(e.attributes))if(p.name.startsWith("data-ctrovalidate-")&&p.name.endsWith("-message")){const _=p.name.replace("data-ctrovalidate-","").replace("-message","");_!==""&&_!=="message"&&(g[_]=p.value)}const f={element:e,rules:u,dependency:v(this,P,He).call(this,i),customMessages:Object.keys(g).length>0?g:void 0,state:{isDirty:!1,abortController:null,lastError:null}};s(this,y).push(f),s(this,E).set(e,f),s(this,m).debug("[FormController] Added field:",e),r&&v(this,P,ae).call(this,f)}removeField(e){if(!e||!s(this,E).has(e))return;const r=s(this,E).get(e);if(c(this,y,s(this,y).filter(a=>a.element!==e)),r.listeners){const{onBlur:a,onInput:i,onControllerInput:l,controllerElement:n}=r.listeners;try{e.removeEventListener("blur",a),e.removeEventListener("input",i),n&&l&&n.removeEventListener("input",l)}catch(d){s(this,m).debug("[FormController] Error while removing listeners for",e,d)}delete r.listeners}s(this,E).delete(e),s(this,m).debug("[FormController] Removed field:",e)}attachEventListeners(e){if(!e){s(this,m).info("[FormController] Real-time validation is disabled.");return}s(this,m).info("[FormController] Real-time validation is enabled. Attaching listeners."),s(this,y).forEach(r=>v(this,P,ae).call(this,r))}reset(){s(this,y).forEach(e=>{e.state.isDirty=!1,e.state.lastError=null})}destroy(){[...s(this,y)].forEach(e=>{this.removeField(e.element)})}getFields(){return s(this,y)}}$=new WeakMap,y=new WeakMap,E=new WeakMap,m=new WeakMap,z=new WeakMap,k=new WeakMap,L=new WeakMap,P=new WeakSet,ae=function(e){const{element:r,dependency:a}=e;if(e.listeners)return;const i=()=>{s(this,m).debug(`[FormController] Blur event on "${r.name}". Validating.`),s(this,L).call(this,e),e.state.isDirty=!0},l=()=>{e.state.isDirty&&(s(this,m).debug(`[FormController] Input event on dirty field "${r.name}". Validating.`),s(this,L).call(this,e))};r.addEventListener("blur",i),r.addEventListener("input",l);let n=null,d=null;a&&(n=s(this,$).querySelector(`[name="${a.controllerName}"]`),n?(d=()=>{s(this,m).debug(`[FormController] Controller "${n.name}" changed. Re-validating dependent field "${r.name}".`),s(this,L).call(this,e)},n.addEventListener("input",d)):s(this,m).warn(`[FormController] Could not find controller field with name "${a.controllerName}".`)),e.listeners={onBlur:i,onInput:l,onControllerInput:d,controllerElement:n}},He=function(e){if(!e)return null;const r=e.split(":"),a=r[0],i=r[1]||"present";if(!a)return null;if(i.includes("=")){const[l,n]=i.split("=");return{controllerName:a,type:l,value:n}}else return{controllerName:a,type:i}};const bt=(t,e)=>{const r=t;if(!r.form)return"";const a=r.form.querySelector(`[name="${e}"]`);return a?a.value:""},yt=(t,e=[],r=null)=>{const a=String(e[0]||"");if(a===void 0)return console.error("[Ctrovalidate] Missing parameter for 'sameAs' rule."),!1;if(!r)return!1;const i=bt(r,a);return Be(t,[i])},Et=Object.freeze(Object.defineProperty({__proto__:null,alpha:xe,alphaDash:$e,alphaNum:Re,between:Ve,creditCard:qe,decimal:Te,email:Ae,exactLength:ke,integer:Me,ipAddress:_e,json:Ie,max:we,maxLength:Ne,min:Ce,minLength:Se,numeric:Le,phone:De,required:Fe,sameAs:yt,strongPassword:ze,url:Pe},Symbol.toStringTag,{value:"Module"})),me={...Et},ge={},pe={},Ft={required:"This field is required.",minLength:"This field must be at least {0} characters long.",maxLength:"This field cannot be longer than {0} characters.",between:"This field must be between {0} and {1} characters long.",email:"Please enter a valid email address.",phone:"Please enter a valid phone number.",url:"Please enter a valid URL.",numeric:"This field must contain only numbers.",alpha:"This field must contain only letters.",alphaDash:"This field must contain only letters, numbers, and dashes.",alphaSpaces:"This field must contain only letters and spaces.",alphaNum:"This field must contain only letters and numbers.",decimal:"This field must be a valid decimal number.",integer:"This field must be a valid integer.",min:"This field must be at least {0}.",max:"This field cannot be more than {0}.",exactLength:"This field must be exactly {0} characters long.",format:"This field format is invalid.",sameAs:"This field must match {0}.",strongPassword:"Password must be at least 8 characters long and contain at least one uppercase letter, one lowercase letter, one number, and one special character.",creditCard:"Please enter a valid credit card number.",ipAddress:"Please enter a valid IP address.",json:"Please enter a valid JSON string."},G={...Ft};function At(t){Object.assign(G,t)}var M,F,A,T,V,h,Y,Ge;class Oe{constructor(e,r={}){o(this,Y);o(this,M);o(this,F);o(this,A);o(this,T);o(this,V);o(this,h);if(!(e instanceof HTMLFormElement))throw new Error("Ctrovalidate requires a valid HTMLFormElement to be initialized.");c(this,M,e),c(this,F,{logLevel:J.NONE,errorClass:"is-invalid",errorMessageClass:"error-message",pendingClass:"ctrovalidate-pending",realTime:!0,schema:{},aliases:{},...r}),c(this,A,new je(s(this,F).logLevel)),c(this,T,new te(s(this,F))),c(this,V,new pt(s(this,M),{logger:s(this,A),uiManager:s(this,T),rules:me,asyncRules:ge,messages:G})),c(this,h,new vt(s(this,M),{logger:s(this,A),schema:s(this,F).schema,aliases:{...pe,...s(this,F).aliases},validationHandler:a=>s(this,V).validateField(a)})),v(this,Y,Ge).call(this)}static defineAlias(e,r){pe[e]=r}async validate(){return this.validateForm()}async validateForm(){s(this,A).info("[Ctrovalidate] Manually triggering form validation.");const e=s(this,h).getFields(),a=(await Promise.all(e.map(i=>s(this,V).validateField(i)))).every(i=>i===!0);return a?s(this,A).info("[Ctrovalidate] Form validation succeeded."):s(this,A).warn("[Ctrovalidate] Form validation failed."),a}static setCustomMessages(e){At(e)}static addRule(e,r,a){if(!e||typeof r!="function"){console.error("[Ctrovalidate] addRule requires a rule name and a logic function.");return}me[e]=r,a&&(G[e]=a)}static addAsyncRule(e,r,a){if(!e||typeof r!="function"){console.error("[Ctrovalidate] addAsyncRule requires a rule name and a logic function.");return}ge[e]=r,a&&(G[e]=a)}addField(e){s(this,h).addField(e)}removeField(e){s(this,h).removeField(e)}refresh(){s(this,h).discoverFields(),s(this,h).attachEventListeners(s(this,F).realTime)}getError(e){const r=s(this,h).getFields().find(a=>a.element.name===e);return r?r.state.lastError:null}isDirty(e){const r=s(this,h).getFields().find(a=>a.element.name===e);return r?r.state.isDirty:!1}reset(){s(this,h).reset(),s(this,h).getFields().forEach(e=>{s(this,T).clearError(e.element)})}destroy(){s(this,h).destroy(),s(this,h).getFields().forEach(e=>{s(this,T).clearError(e.element)})}}M=new WeakMap,F=new WeakMap,A=new WeakMap,T=new WeakMap,V=new WeakMap,h=new WeakMap,Y=new WeakSet,Ge=function(){s(this,A).info("[Ctrovalidate] Initializing library."),s(this,h).discoverFields(),s(this,h).attachEventListeners(s(this,F).realTime),s(this,M).setAttribute("novalidate","true")},de(Oe,"LogLevel",J);exports.Ctrovalidate=Oe;exports.LogLevel=J;
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { AsyncRuleLogic as AsyncRuleLogic_2 } from 'ctrovalidate-core';
|
|
2
|
+
import { DependencyDefinition } from 'ctrovalidate-core';
|
|
3
|
+
import { LogLevel } from 'ctrovalidate-core';
|
|
4
|
+
import { RuleDefinition } from 'ctrovalidate-core';
|
|
5
|
+
import { RuleLogic as RuleLogic_2 } from 'ctrovalidate-core';
|
|
6
|
+
import { SchemaRule } from 'ctrovalidate-core';
|
|
7
|
+
import { ValidationResult } from 'ctrovalidate-core';
|
|
8
|
+
import { ValidationSchema } from 'ctrovalidate-core';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* The logic function for an asynchronous validation rule (Browser context).
|
|
12
|
+
*/
|
|
13
|
+
export declare type AsyncRuleLogic = AsyncRuleLogic_2<HTMLElement>;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* The main public-facing class for the Ctrovalidate library.
|
|
17
|
+
*/
|
|
18
|
+
export declare class Ctrovalidate {
|
|
19
|
+
#private;
|
|
20
|
+
static LogLevel: typeof LogLevel;
|
|
21
|
+
/**
|
|
22
|
+
* @param formElement - The HTML form element to validate.
|
|
23
|
+
* @param options - Custom configuration options.
|
|
24
|
+
*/
|
|
25
|
+
constructor(formElement: HTMLFormElement, options?: CtrovalidateOptions);
|
|
26
|
+
/**
|
|
27
|
+
* Defines a rule alias (macro) globally.
|
|
28
|
+
*/
|
|
29
|
+
static defineAlias(name: string, rules: SchemaRule): void;
|
|
30
|
+
/**
|
|
31
|
+
* Validates the entire form programmatically.
|
|
32
|
+
*/
|
|
33
|
+
validate(): Promise<boolean>;
|
|
34
|
+
/**
|
|
35
|
+
* Validates the entire form programmatically.
|
|
36
|
+
*/
|
|
37
|
+
validateForm(): Promise<boolean>;
|
|
38
|
+
/**
|
|
39
|
+
* Allows the user to add custom validation messages globally.
|
|
40
|
+
*/
|
|
41
|
+
static setCustomMessages(customMessages: Record<string, string>): void;
|
|
42
|
+
/**
|
|
43
|
+
* Adds a custom synchronous validation rule globally.
|
|
44
|
+
*/
|
|
45
|
+
static addRule(name: string, logic: RuleLogic, message?: string): void;
|
|
46
|
+
/**
|
|
47
|
+
* Adds a custom asynchronous validation rule globally.
|
|
48
|
+
*/
|
|
49
|
+
static addAsyncRule(name: string, logic: AsyncRuleLogic, message?: string): void;
|
|
50
|
+
/**
|
|
51
|
+
* Manually adds a new field to be validated.
|
|
52
|
+
*/
|
|
53
|
+
addField(element: HTMLElement): void;
|
|
54
|
+
/**
|
|
55
|
+
* Manually removes a field from validation.
|
|
56
|
+
*/
|
|
57
|
+
removeField(element: HTMLElement): void;
|
|
58
|
+
/**
|
|
59
|
+
* Refreshes the discovered fields. Use if fields are added or removed from the DOM.
|
|
60
|
+
*/
|
|
61
|
+
refresh(): void;
|
|
62
|
+
/**
|
|
63
|
+
* Returns the current error for a field by its name.
|
|
64
|
+
*/
|
|
65
|
+
getError(fieldName: string): string | null;
|
|
66
|
+
/**
|
|
67
|
+
* Checks if a field has been interacted with.
|
|
68
|
+
*/
|
|
69
|
+
isDirty(fieldName: string): boolean;
|
|
70
|
+
/**
|
|
71
|
+
* Resets the validation state of the form.
|
|
72
|
+
*/
|
|
73
|
+
reset(): void;
|
|
74
|
+
/**
|
|
75
|
+
* Fully cleans up the validator instance.
|
|
76
|
+
*/
|
|
77
|
+
destroy(): void;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Configuration options for a Ctrovalidate instance.
|
|
82
|
+
*/
|
|
83
|
+
export declare interface CtrovalidateOptions {
|
|
84
|
+
logLevel?: number;
|
|
85
|
+
errorClass?: string;
|
|
86
|
+
errorMessageClass?: string;
|
|
87
|
+
pendingClass?: string;
|
|
88
|
+
realTime?: boolean;
|
|
89
|
+
schema?: ValidationSchema;
|
|
90
|
+
aliases?: Record<string, SchemaRule>;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export { DependencyDefinition }
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* The object representing a field being validated.
|
|
97
|
+
*/
|
|
98
|
+
export declare interface FieldObject {
|
|
99
|
+
element: HTMLElement;
|
|
100
|
+
rules: RuleDefinition[];
|
|
101
|
+
state: FieldState;
|
|
102
|
+
dependency: DependencyDefinition | null;
|
|
103
|
+
customMessages?: Record<string, string>;
|
|
104
|
+
listeners?: {
|
|
105
|
+
onBlur: (e: Event) => void;
|
|
106
|
+
onInput: (e: Event) => void;
|
|
107
|
+
onControllerInput: ((e: Event) => void) | null;
|
|
108
|
+
controllerElement: HTMLElement | null;
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* The state of a single validatable field.
|
|
114
|
+
*/
|
|
115
|
+
export declare interface FieldState {
|
|
116
|
+
isDirty: boolean;
|
|
117
|
+
abortController: AbortController | null;
|
|
118
|
+
lastError: string | null;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export { LogLevel }
|
|
122
|
+
|
|
123
|
+
export { RuleDefinition }
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* The logic function for a synchronous validation rule (Browser context).
|
|
127
|
+
* Context is strictly HTMLElement here.
|
|
128
|
+
*/
|
|
129
|
+
export declare type RuleLogic = RuleLogic_2<HTMLElement>;
|
|
130
|
+
|
|
131
|
+
export { SchemaRule }
|
|
132
|
+
|
|
133
|
+
export { ValidationResult }
|
|
134
|
+
|
|
135
|
+
export { ValidationSchema }
|
|
136
|
+
|
|
137
|
+
export { }
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,861 @@
|
|
|
1
|
+
var Ue = Object.defineProperty;
|
|
2
|
+
var oe = (t) => {
|
|
3
|
+
throw TypeError(t);
|
|
4
|
+
};
|
|
5
|
+
var Ke = (t, e, r) => e in t ? Ue(t, e, { enumerable: !0, configurable: !0, writable: !0, value: r }) : t[e] = r;
|
|
6
|
+
var de = (t, e, r) => Ke(t, typeof e != "symbol" ? e + "" : e, r), Y = (t, e, r) => e.has(t) || oe("Cannot " + r);
|
|
7
|
+
var s = (t, e, r) => (Y(t, e, "read from private field"), r ? r.call(t) : e.get(t)), o = (t, e, r) => e.has(t) ? oe("Cannot add the same private member more than once") : e instanceof WeakSet ? e.add(t) : e.set(t, r), c = (t, e, r, a) => (Y(t, e, "write to private field"), a ? a.call(t, r) : e.set(t, r), r), v = (t, e, r) => (Y(t, e, "access private method"), r);
|
|
8
|
+
var ce = (t, e, r, a) => ({
|
|
9
|
+
set _(i) {
|
|
10
|
+
c(t, e, i, r);
|
|
11
|
+
},
|
|
12
|
+
get _() {
|
|
13
|
+
return s(t, e, a);
|
|
14
|
+
}
|
|
15
|
+
});
|
|
16
|
+
var ve = Object.defineProperty, be = (t) => {
|
|
17
|
+
throw TypeError(t);
|
|
18
|
+
}, Qe = (t, e, r) => e in t ? ve(t, e, { enumerable: !0, configurable: !0, writable: !0, value: r }) : t[e] = r, Xe = (t, e) => {
|
|
19
|
+
for (var r in e)
|
|
20
|
+
ve(t, r, { get: e[r], enumerable: !0 });
|
|
21
|
+
}, ie = (t, e, r) => Qe(t, typeof e != "symbol" ? e + "" : e, r), ye = (t, e, r) => e.has(t) || be("Cannot " + r), S = (t, e, r) => (ye(t, e, "read from private field"), r ? r.call(t) : e.get(t)), ue = (t, e, r) => e.has(t) ? be("Cannot add the same private member more than once") : e instanceof WeakSet ? e.add(t) : e.set(t, r), he = (t, e, r, a) => (ye(t, e, "write to private field"), e.set(t, r), r);
|
|
22
|
+
function fe(t) {
|
|
23
|
+
if (!t || typeof t != "string")
|
|
24
|
+
return [];
|
|
25
|
+
const e = [], r = t.split("|");
|
|
26
|
+
for (const a of r) {
|
|
27
|
+
const i = a.trim();
|
|
28
|
+
if (i === "")
|
|
29
|
+
continue;
|
|
30
|
+
const l = i.indexOf(":");
|
|
31
|
+
if (l === -1)
|
|
32
|
+
e.push({ name: i, params: [] });
|
|
33
|
+
else {
|
|
34
|
+
const n = i.substring(0, l).trim(), d = i.substring(l + 1).trim();
|
|
35
|
+
if (n) {
|
|
36
|
+
const u = d.split(",").map((g) => g.trim());
|
|
37
|
+
e.push({ name: n, params: u });
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return e;
|
|
42
|
+
}
|
|
43
|
+
function j(t, e = {}, r = /* @__PURE__ */ new Set()) {
|
|
44
|
+
if (!t) return [];
|
|
45
|
+
let a;
|
|
46
|
+
if (typeof t == "string")
|
|
47
|
+
a = fe(t);
|
|
48
|
+
else if (Array.isArray(t))
|
|
49
|
+
a = t;
|
|
50
|
+
else
|
|
51
|
+
return [];
|
|
52
|
+
return a.flatMap((i) => {
|
|
53
|
+
const l = typeof i == "string" ? i.split(":")[0] : i.name;
|
|
54
|
+
if (e[l] && !r.has(l)) {
|
|
55
|
+
const n = new Set(r);
|
|
56
|
+
return n.add(l), j(e[l], e, n);
|
|
57
|
+
}
|
|
58
|
+
return typeof i == "string" ? fe(i) : [i];
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
var ee = /* @__PURE__ */ ((t) => (t[t.NONE = 0] = "NONE", t[t.ERROR = 1] = "ERROR", t[t.WARN = 2] = "WARN", t[t.INFO = 3] = "INFO", t[t.DEBUG = 4] = "DEBUG", t))(ee || {}), ne = class Ee {
|
|
62
|
+
constructor(e = 0) {
|
|
63
|
+
ie(this, "level"), this.level = e;
|
|
64
|
+
}
|
|
65
|
+
setLevel(e) {
|
|
66
|
+
this.level = e;
|
|
67
|
+
}
|
|
68
|
+
static setLevel(e) {
|
|
69
|
+
this.globalLevel = e;
|
|
70
|
+
}
|
|
71
|
+
format(e) {
|
|
72
|
+
return `${Ee.prefix} ${e}`;
|
|
73
|
+
}
|
|
74
|
+
// Instance methods
|
|
75
|
+
error(e, ...r) {
|
|
76
|
+
this.level >= 1 && console.error(this.format(e), ...r);
|
|
77
|
+
}
|
|
78
|
+
warn(e, ...r) {
|
|
79
|
+
this.level >= 2 && console.warn(this.format(e), ...r);
|
|
80
|
+
}
|
|
81
|
+
info(e, ...r) {
|
|
82
|
+
this.level >= 3 && console.info(this.format(e), ...r);
|
|
83
|
+
}
|
|
84
|
+
debug(e, ...r) {
|
|
85
|
+
this.level >= 4 && console.debug(this.format(e), ...r);
|
|
86
|
+
}
|
|
87
|
+
// Static methods (using global level)
|
|
88
|
+
static error(e, ...r) {
|
|
89
|
+
this.globalLevel >= 1 && console.error(`${this.prefix} ${e}`, ...r);
|
|
90
|
+
}
|
|
91
|
+
static warn(e, ...r) {
|
|
92
|
+
this.globalLevel >= 2 && console.warn(`${this.prefix} ${e}`, ...r);
|
|
93
|
+
}
|
|
94
|
+
static info(e, ...r) {
|
|
95
|
+
this.globalLevel >= 3 && console.info(`${this.prefix} ${e}`, ...r);
|
|
96
|
+
}
|
|
97
|
+
static debug(e, ...r) {
|
|
98
|
+
this.globalLevel >= 4 && console.debug(`${this.prefix} ${e}`, ...r);
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
ie(
|
|
102
|
+
ne,
|
|
103
|
+
"globalLevel",
|
|
104
|
+
1
|
|
105
|
+
/* ERROR */
|
|
106
|
+
);
|
|
107
|
+
ie(ne, "prefix", "[Ctrovalidate]");
|
|
108
|
+
var Ye = ne, je = {};
|
|
109
|
+
Xe(je, {
|
|
110
|
+
alpha: () => xe,
|
|
111
|
+
alphaDash: () => $e,
|
|
112
|
+
alphaNum: () => Re,
|
|
113
|
+
alphaSpaces: () => it,
|
|
114
|
+
between: () => Ve,
|
|
115
|
+
creditCard: () => qe,
|
|
116
|
+
decimal: () => Te,
|
|
117
|
+
email: () => Ae,
|
|
118
|
+
exactLength: () => ke,
|
|
119
|
+
integer: () => Le,
|
|
120
|
+
ipAddress: () => _e,
|
|
121
|
+
json: () => Ie,
|
|
122
|
+
max: () => we,
|
|
123
|
+
maxLength: () => Se,
|
|
124
|
+
min: () => Ce,
|
|
125
|
+
minLength: () => Ne,
|
|
126
|
+
numeric: () => Me,
|
|
127
|
+
phone: () => De,
|
|
128
|
+
required: () => Fe,
|
|
129
|
+
sameAs: () => Be,
|
|
130
|
+
strongPassword: () => ze,
|
|
131
|
+
url: () => Pe
|
|
132
|
+
});
|
|
133
|
+
var Fe = (t) => t == null ? !1 : typeof t == "boolean" ? t : String(t).trim() !== "", et = /^[^\s@]+@[^\s@]+\.[^\s@]+$/, Ae = (t) => t ? et.test(String(t)) : !0, Ce = (t, e = []) => {
|
|
134
|
+
if (t == null || t === "") return !0;
|
|
135
|
+
if (!e[0])
|
|
136
|
+
return console.error("[Ctrovalidate] Missing parameter for 'min' rule."), !1;
|
|
137
|
+
const r = Number(e[0]);
|
|
138
|
+
return Number(t) >= r;
|
|
139
|
+
}, we = (t, e = []) => {
|
|
140
|
+
if (t == null || t === "") return !0;
|
|
141
|
+
if (!e[0])
|
|
142
|
+
return console.error("[Ctrovalidate] Missing parameter for 'max' rule."), !1;
|
|
143
|
+
const r = Number(e[0]);
|
|
144
|
+
return Number(t) <= r;
|
|
145
|
+
}, Ne = (t, e = []) => {
|
|
146
|
+
if (t == null || t === "") return !0;
|
|
147
|
+
if (!e[0])
|
|
148
|
+
return console.error("[Ctrovalidate] Missing parameter for 'minLength' rule."), !1;
|
|
149
|
+
const r = Number(e[0]);
|
|
150
|
+
return String(t).length >= r;
|
|
151
|
+
}, Se = (t, e = []) => {
|
|
152
|
+
if (t == null || t === "") return !0;
|
|
153
|
+
if (!e[0])
|
|
154
|
+
return console.error("[Ctrovalidate] Missing parameter for 'maxLength' rule."), !1;
|
|
155
|
+
const r = Number(e[0]);
|
|
156
|
+
return String(t).length <= r;
|
|
157
|
+
}, tt = /^[a-zA-Z]+$/, xe = (t) => t ? tt.test(String(t)) : !0, rt = /^[a-zA-Z0-9]+$/, Re = (t) => t ? rt.test(String(t)) : !0, st = /^[a-zA-Z0-9-_]+$/, $e = (t) => t ? st.test(String(t)) : !0, at = /^[a-zA-Z\s]+$/, it = (t) => t ? at.test(String(t)) : !0, Me = (t) => !t && t !== 0 ? !0 : !isNaN(Number(t)) && !isNaN(parseFloat(String(t))), nt = /^-?\d+$/, Le = (t) => !t && t !== 0 ? !0 : nt.test(String(t)), lt = /^-?\d+(\.\d+)?$/, Te = (t) => !t && t !== 0 ? !0 : lt.test(String(t)), ot = /^(https?:\/\/)([\da-z.-]+)\.([a-z.]{2,6})([/\w .-]*)*\/?$/, Pe = (t) => t ? ot.test(String(t)) : !0, dt = /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/, ct = /^(?:(?:[a-fA-F\d]{1,4}:){7}(?:[a-fA-F\d]{1,4}|:)|(?:[a-fA-F\d]{1,4}:){6}(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|:[a-fA-F\d]{1,4}|:)|(?:[a-fA-F\d]{1,4}:){5}(?::(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-fA-F\d]{1,4}){1,2}|:)|(?:[a-fA-F\d]{1,4}:){4}(?:(?::[a-fA-F\d]{1,4}){0,1}:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-fA-F\d]{1,4}){1,3}|:)|(?:[a-fA-F\d]{1,4}:){3}(?:(?::[a-fA-F\d]{1,4}){0,2}:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-fA-F\d]{1,4}){1,4}|:)|(?:[a-fA-F\d]{1,4}:){2}(?:(?::[a-fA-F\d]{1,4}){0,3}:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-fA-F\d]{1,4}){1,5}|:)|(?:[a-fA-F\d]{1,4}:){1}(?:(?::[a-fA-F\d]{1,4}){0,4}:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-fA-F\d]{1,4}){1,6}|:)|(?::(?:(?::[a-fA-F\d]{1,4}){0,5}:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-fA-F\d]{1,4}){1,7}|:)))(?:%[0-9a-zA-Z]{1,})?$/, _e = (t) => {
|
|
158
|
+
if (!t) return !0;
|
|
159
|
+
const e = String(t);
|
|
160
|
+
return dt.test(e) || ct.test(e);
|
|
161
|
+
}, Ie = (t) => {
|
|
162
|
+
if (!t) return !0;
|
|
163
|
+
try {
|
|
164
|
+
const e = JSON.parse(String(t));
|
|
165
|
+
return typeof e == "object" && e !== null;
|
|
166
|
+
} catch {
|
|
167
|
+
return !1;
|
|
168
|
+
}
|
|
169
|
+
}, ut = /^[+]?([(]?[0-9]{1,4}[)]?)[-\s.]?[0-9]{3,4}[-\s.]?[0-9]{4,6}$/, De = (t) => t ? ut.test(String(t)) : !0, qe = (t) => {
|
|
170
|
+
if (!t) return !0;
|
|
171
|
+
const e = String(t).replace(/[- ]/g, "");
|
|
172
|
+
if (!/^\d+$/.test(e)) return !1;
|
|
173
|
+
let r = 0;
|
|
174
|
+
const a = e.length % 2;
|
|
175
|
+
for (let i = 0; i < e.length; i++) {
|
|
176
|
+
let l = parseInt(e[i], 10);
|
|
177
|
+
i % 2 === a && (l *= 2, l > 9 && (l -= 9)), r += l;
|
|
178
|
+
}
|
|
179
|
+
return r % 10 === 0;
|
|
180
|
+
}, ht = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$/, ze = (t) => t ? ht.test(String(t)) : !0, ke = (t, e = []) => {
|
|
181
|
+
if (t == null || t === "") return !0;
|
|
182
|
+
if (!e[0])
|
|
183
|
+
return console.error("[Ctrovalidate] Missing parameter for 'exactLength' rule."), !1;
|
|
184
|
+
const r = Number(e[0]);
|
|
185
|
+
return String(t).length === r;
|
|
186
|
+
}, Ve = (t, e = []) => {
|
|
187
|
+
if (t == null || t === "") return !0;
|
|
188
|
+
if (e.length < 2)
|
|
189
|
+
return console.error("[Ctrovalidate] Missing parameters for 'between' rule."), !1;
|
|
190
|
+
const r = Number(e[0]), a = Number(e[1]);
|
|
191
|
+
if (isNaN(r) || isNaN(a))
|
|
192
|
+
return !1;
|
|
193
|
+
if (!isNaN(Number(t)) && typeof t != "string") {
|
|
194
|
+
const n = Number(t);
|
|
195
|
+
return n >= r && n <= a;
|
|
196
|
+
}
|
|
197
|
+
const i = String(t);
|
|
198
|
+
if (!isNaN(Number(i)) && i !== "") {
|
|
199
|
+
const n = Number(i);
|
|
200
|
+
return n >= r && n <= a;
|
|
201
|
+
}
|
|
202
|
+
const l = i.length;
|
|
203
|
+
return l >= r && l <= a;
|
|
204
|
+
}, Be = (t, e = []) => {
|
|
205
|
+
if (e.length === 0 || e[0] === void 0)
|
|
206
|
+
return console.error("[Ctrovalidate] Missing parameter for 'sameAs' rule."), !1;
|
|
207
|
+
const r = e[0];
|
|
208
|
+
return t === r;
|
|
209
|
+
}, ft = {
|
|
210
|
+
required: "This field is required.",
|
|
211
|
+
email: "Please enter a valid email address.",
|
|
212
|
+
min: "Minimum value is {0}.",
|
|
213
|
+
max: "Maximum value is {0}.",
|
|
214
|
+
minLength: "Minimum length is {0} characters.",
|
|
215
|
+
maxLength: "Maximum length is {0} characters.",
|
|
216
|
+
alpha: "This field may only contain alphabetic characters.",
|
|
217
|
+
alphaNum: "This field may only contain alpha-numeric characters.",
|
|
218
|
+
alphaDash: "This field may only contain alpha-numeric characters as well as dashes and underscores.",
|
|
219
|
+
alphaSpaces: "This field may only contain alphabetic characters and spaces.",
|
|
220
|
+
numeric: "This field must be a number.",
|
|
221
|
+
integer: "This field must be an integer.",
|
|
222
|
+
decimal: "This field must be a decimal number.",
|
|
223
|
+
url: "Please enter a valid URL.",
|
|
224
|
+
ipAddress: "Please enter a valid IP address.",
|
|
225
|
+
json: "Please enter a valid JSON string.",
|
|
226
|
+
phone: "Please enter a valid phone number.",
|
|
227
|
+
creditCard: "Please enter a valid credit card number.",
|
|
228
|
+
strongPassword: "Password must be at least 8 characters long and include an uppercase letter, a lowercase letter, a number, and a symbol.",
|
|
229
|
+
exactLength: "This field must be exactly {0} characters long.",
|
|
230
|
+
between: "This field must be between {0} and {1}.",
|
|
231
|
+
sameAs: "This field must match {0}."
|
|
232
|
+
}, x, I, mt = class {
|
|
233
|
+
constructor() {
|
|
234
|
+
ue(this, x, { en: ft }), ue(this, I, "en");
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* Set the current locale.
|
|
238
|
+
*/
|
|
239
|
+
setLocale(t) {
|
|
240
|
+
if (!S(this, x)[t]) {
|
|
241
|
+
console.warn(
|
|
242
|
+
`[Ctrovalidate] Locale "${t}" not found. Falling back to "en".`
|
|
243
|
+
), he(this, I, "en");
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
he(this, I, t);
|
|
247
|
+
}
|
|
248
|
+
/**
|
|
249
|
+
* Add or update messages for a specific locale.
|
|
250
|
+
*/
|
|
251
|
+
addMessages(t, e) {
|
|
252
|
+
S(this, x)[t] = {
|
|
253
|
+
...S(this, x)[t] || {},
|
|
254
|
+
...e
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
/**
|
|
258
|
+
* Translates a rule name with optional parameters.
|
|
259
|
+
*/
|
|
260
|
+
translate(t, e = [], r) {
|
|
261
|
+
const a = r || S(this, I), i = S(this, x)[a] || S(this, x).en;
|
|
262
|
+
return (i[t] || i.default || "Invalid input.").replace(/{(\d+)}/g, (n, d) => {
|
|
263
|
+
const u = parseInt(d, 10);
|
|
264
|
+
return e[u] !== void 0 ? String(e[u]) : n;
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
/**
|
|
268
|
+
* Get the current active locale.
|
|
269
|
+
*/
|
|
270
|
+
get currentLocale() {
|
|
271
|
+
return S(this, I);
|
|
272
|
+
}
|
|
273
|
+
};
|
|
274
|
+
x = /* @__PURE__ */ new WeakMap();
|
|
275
|
+
I = /* @__PURE__ */ new WeakMap();
|
|
276
|
+
new mt();
|
|
277
|
+
var D, B, q, R, J, U, C, We, re, se;
|
|
278
|
+
const K = class K {
|
|
279
|
+
constructor({
|
|
280
|
+
errorClass: e = "ctrovalidate-error",
|
|
281
|
+
errorMessageClass: r = "ctrovalidate-error-message",
|
|
282
|
+
pendingClass: a = "ctrovalidate-pending"
|
|
283
|
+
}) {
|
|
284
|
+
o(this, C);
|
|
285
|
+
o(this, D);
|
|
286
|
+
o(this, B);
|
|
287
|
+
o(this, q);
|
|
288
|
+
// Cache field -> found error element
|
|
289
|
+
o(this, R, /* @__PURE__ */ new WeakMap());
|
|
290
|
+
// Optional map to store generated IDs per field when we create an element
|
|
291
|
+
o(this, J, /* @__PURE__ */ new WeakMap());
|
|
292
|
+
c(this, D, e), c(this, B, r), c(this, q, a);
|
|
293
|
+
}
|
|
294
|
+
/**
|
|
295
|
+
* Display a validation error message for the given field.
|
|
296
|
+
* Applies configured error classes and ARIA attributes.
|
|
297
|
+
*/
|
|
298
|
+
displayError(e, r) {
|
|
299
|
+
const a = v(this, C, se).call(this, e);
|
|
300
|
+
s(this, D).split(/\s+/).filter(Boolean).forEach((i) => e.classList.add(i)), e.setAttribute("aria-invalid", "true"), a && (a.textContent = r, a.style.display = "", e.setAttribute("aria-describedby", a.id));
|
|
301
|
+
}
|
|
302
|
+
/**
|
|
303
|
+
* Clear the validation error message and related styling/ARIA attributes.
|
|
304
|
+
*/
|
|
305
|
+
clearError(e) {
|
|
306
|
+
const r = v(this, C, se).call(this, e);
|
|
307
|
+
s(this, D).split(/\s+/).filter(Boolean).forEach((a) => e.classList.remove(a)), e.removeAttribute("aria-invalid"), r && (r.textContent = "", r.style.display = "none", e.getAttribute("aria-describedby") === r.id && e.removeAttribute("aria-describedby"));
|
|
308
|
+
}
|
|
309
|
+
/**
|
|
310
|
+
* Show pending state for a field (async validation).
|
|
311
|
+
*/
|
|
312
|
+
showPending(e) {
|
|
313
|
+
this.clearError(e), s(this, q).split(/\s+/).filter(Boolean).forEach((r) => e.classList.add(r));
|
|
314
|
+
}
|
|
315
|
+
/**
|
|
316
|
+
* Hide pending state for a field.
|
|
317
|
+
*/
|
|
318
|
+
hidePending(e) {
|
|
319
|
+
s(this, q).split(/\s+/).filter(Boolean).forEach((r) => e.classList.remove(r));
|
|
320
|
+
}
|
|
321
|
+
};
|
|
322
|
+
D = new WeakMap(), B = new WeakMap(), q = new WeakMap(), R = new WeakMap(), J = new WeakMap(), U = new WeakMap(), C = new WeakSet(), /**
|
|
323
|
+
* Safely escape a class name for use in querySelector.
|
|
324
|
+
* Uses CSS.escape when available, otherwise falls back to a robust regex.
|
|
325
|
+
*/
|
|
326
|
+
We = function(e) {
|
|
327
|
+
return typeof CSS < "u" && typeof CSS.escape == "function" ? CSS.escape(e) : e.replace(/([!"#$%&'()*+,./:;<=>?@[\\\]^`{|}~[\]])/g, "\\$1");
|
|
328
|
+
}, /**
|
|
329
|
+
* Build a DOM id from the field (name if available) or a generated counter.
|
|
330
|
+
*/
|
|
331
|
+
re = function(e) {
|
|
332
|
+
let r;
|
|
333
|
+
if ((e instanceof HTMLInputElement || e instanceof HTMLSelectElement || e instanceof HTMLTextAreaElement) && (r = e.name), r && r.trim().length > 0)
|
|
334
|
+
return `ctrovalidate-error-${r.replace(/\s+/g, "-").replace(/[^A-Za-z0-9\-_:.]/g, "")}`;
|
|
335
|
+
const a = `ctrovalidate-error-${ce(K, U)._++}`;
|
|
336
|
+
return s(this, J).set(e, a), a;
|
|
337
|
+
}, /**
|
|
338
|
+
* Finds (or creates) the dedicated error message container for a given input field.
|
|
339
|
+
* Supports multiple classes in `errorMessageClass` by trying each class individually.
|
|
340
|
+
*/
|
|
341
|
+
se = function(e) {
|
|
342
|
+
if (s(this, R).has(e))
|
|
343
|
+
return s(this, R).get(e) || null;
|
|
344
|
+
const r = s(this, B).split(/\s+/).filter(Boolean);
|
|
345
|
+
let a = e.parentElement, i = 0;
|
|
346
|
+
const l = 3;
|
|
347
|
+
for (; a && i < l; ) {
|
|
348
|
+
for (const g of r) {
|
|
349
|
+
const N = `.${v(this, C, We).call(this, g)}`, f = a.querySelector(
|
|
350
|
+
N
|
|
351
|
+
);
|
|
352
|
+
if (f)
|
|
353
|
+
return f.id || (f.id = v(this, C, re).call(this, e)), s(this, R).set(e, f), f;
|
|
354
|
+
}
|
|
355
|
+
a = a.parentElement, i++;
|
|
356
|
+
}
|
|
357
|
+
const n = e.parentElement;
|
|
358
|
+
if (!n) return null;
|
|
359
|
+
const d = document.createElement("div");
|
|
360
|
+
d.className = r.join(" "), d.style.display = "none", d.role = "status", d.setAttribute("aria-live", "polite");
|
|
361
|
+
const u = v(this, C, re).call(this, e);
|
|
362
|
+
d.id = u;
|
|
363
|
+
try {
|
|
364
|
+
e.insertAdjacentElement("afterend", d);
|
|
365
|
+
} catch {
|
|
366
|
+
n.appendChild(d);
|
|
367
|
+
}
|
|
368
|
+
return s(this, R).set(e, d), d;
|
|
369
|
+
}, // Global unique id counter for fallback ids
|
|
370
|
+
o(K, U, 0);
|
|
371
|
+
let te = K;
|
|
372
|
+
var b, w, W, Z, H, O, Q, Ze;
|
|
373
|
+
class gt {
|
|
374
|
+
/**
|
|
375
|
+
* @param formElement - The form element.
|
|
376
|
+
* @param dependencies - Core dependencies.
|
|
377
|
+
*/
|
|
378
|
+
constructor(e, { logger: r, uiManager: a, rules: i, asyncRules: l, messages: n }) {
|
|
379
|
+
o(this, Q);
|
|
380
|
+
o(this, b);
|
|
381
|
+
o(this, w);
|
|
382
|
+
o(this, W);
|
|
383
|
+
o(this, Z);
|
|
384
|
+
o(this, H);
|
|
385
|
+
o(this, O);
|
|
386
|
+
c(this, W, e), c(this, b, r), c(this, w, a), c(this, Z, i), c(this, H, l), c(this, O, n);
|
|
387
|
+
}
|
|
388
|
+
/**
|
|
389
|
+
* The core logic for validating a single field.
|
|
390
|
+
*/
|
|
391
|
+
async validateField(e) {
|
|
392
|
+
const { element: r, rules: a, state: i, dependency: l } = e;
|
|
393
|
+
if (!v(this, Q, Ze).call(this, l))
|
|
394
|
+
return s(this, b).debug(
|
|
395
|
+
`[RuleEngine] Dependency for "${r.name}" not met. Skipping validation.`
|
|
396
|
+
), s(this, w).clearError(r), e.state.lastError = null, !0;
|
|
397
|
+
const n = r, d = n.type === "checkbox" ? n.checked : n.value;
|
|
398
|
+
i.abortController && (i.abortController.abort(), s(this, b).debug(
|
|
399
|
+
`[RuleEngine] Aborted previous validation for "${n.name}".`
|
|
400
|
+
));
|
|
401
|
+
for (const u of a) {
|
|
402
|
+
const g = s(this, Z)[u.name], N = s(this, H)[u.name];
|
|
403
|
+
if (!g && !N) {
|
|
404
|
+
s(this, b).warn(
|
|
405
|
+
`[RuleEngine] Unknown rule "${u.name}" used on field:`,
|
|
406
|
+
r
|
|
407
|
+
);
|
|
408
|
+
continue;
|
|
409
|
+
}
|
|
410
|
+
let f = !1;
|
|
411
|
+
if (g)
|
|
412
|
+
f = g(d, u.params, r);
|
|
413
|
+
else if (N) {
|
|
414
|
+
i.abortController = new AbortController(), s(this, w).showPending(r);
|
|
415
|
+
try {
|
|
416
|
+
f = await N(
|
|
417
|
+
d,
|
|
418
|
+
u.params,
|
|
419
|
+
r,
|
|
420
|
+
i.abortController.signal
|
|
421
|
+
);
|
|
422
|
+
} catch (p) {
|
|
423
|
+
if (p instanceof Error && p.name === "AbortError")
|
|
424
|
+
return s(this, b).debug(
|
|
425
|
+
`[RuleEngine] Async validation for "${n.name}" was successfully aborted.`
|
|
426
|
+
), !0;
|
|
427
|
+
s(this, b).error(
|
|
428
|
+
`[RuleEngine] Async rule "${u.name}" threw an error.`,
|
|
429
|
+
[p]
|
|
430
|
+
), f = !1;
|
|
431
|
+
} finally {
|
|
432
|
+
s(this, w).hidePending(r), i.abortController = null;
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
if (!f) {
|
|
436
|
+
let p = s(this, O)[u.name] || "Invalid input.";
|
|
437
|
+
e.customMessages && (e.customMessages[u.name] ? p = e.customMessages[u.name] : e.customMessages["*"] && (p = e.customMessages["*"]));
|
|
438
|
+
const _ = p.replace(/{(\d+)}/g, (Ge, Je) => {
|
|
439
|
+
const le = parseInt(Je, 10);
|
|
440
|
+
return u.params[le] !== void 0 ? String(u.params[le]) : Ge;
|
|
441
|
+
});
|
|
442
|
+
return s(this, w).displayError(r, _), e.state.lastError = _, s(this, b).debug(
|
|
443
|
+
`[RuleEngine] Field validation failed for "${n.name}" on rule "${u.name}".`
|
|
444
|
+
), !1;
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
return s(this, w).clearError(r), e.state.lastError = null, s(this, b).debug(
|
|
448
|
+
`[RuleEngine] Field validation succeeded for "${n.name}".`
|
|
449
|
+
), !0;
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
b = new WeakMap(), w = new WeakMap(), W = new WeakMap(), Z = new WeakMap(), H = new WeakMap(), O = new WeakMap(), Q = new WeakSet(), /**
|
|
453
|
+
* Checks if a field's dependency condition is currently met.
|
|
454
|
+
*/
|
|
455
|
+
Ze = function(e) {
|
|
456
|
+
if (!e) return !0;
|
|
457
|
+
const r = s(this, W).querySelector(
|
|
458
|
+
`[name="${e.controllerName}"]`
|
|
459
|
+
);
|
|
460
|
+
if (!r) return !1;
|
|
461
|
+
switch (e.type) {
|
|
462
|
+
case "checked":
|
|
463
|
+
return r.checked;
|
|
464
|
+
case "value":
|
|
465
|
+
return r.value === e.value;
|
|
466
|
+
case "present":
|
|
467
|
+
return !!r.value;
|
|
468
|
+
default:
|
|
469
|
+
return !1;
|
|
470
|
+
}
|
|
471
|
+
};
|
|
472
|
+
var $, y, E, m, z, k, M, P, ae, He;
|
|
473
|
+
class pt {
|
|
474
|
+
/**
|
|
475
|
+
* @param formElement - The form element to control.
|
|
476
|
+
* @param dependencies - Core dependencies.
|
|
477
|
+
*/
|
|
478
|
+
constructor(e, {
|
|
479
|
+
logger: r,
|
|
480
|
+
schema: a = {},
|
|
481
|
+
aliases: i = {},
|
|
482
|
+
validationHandler: l
|
|
483
|
+
}) {
|
|
484
|
+
o(this, P);
|
|
485
|
+
o(this, $);
|
|
486
|
+
o(this, y, []);
|
|
487
|
+
o(this, E, /* @__PURE__ */ new Map());
|
|
488
|
+
o(this, m);
|
|
489
|
+
o(this, z);
|
|
490
|
+
o(this, k);
|
|
491
|
+
o(this, M);
|
|
492
|
+
c(this, $, e), c(this, m, r), c(this, z, a), c(this, k, i), c(this, M, l);
|
|
493
|
+
}
|
|
494
|
+
/**
|
|
495
|
+
* Scans the form to find all fields and prepares them for validation.
|
|
496
|
+
*/
|
|
497
|
+
discoverFields() {
|
|
498
|
+
c(this, y, []), s(this, E).clear();
|
|
499
|
+
const e = s(this, $).querySelectorAll(
|
|
500
|
+
"[data-ctrovalidate-rules]"
|
|
501
|
+
);
|
|
502
|
+
s(this, m).debug(
|
|
503
|
+
"[FormController] Discovered " + e.length + " fields to validate."
|
|
504
|
+
), e.forEach(
|
|
505
|
+
(r) => this.addField(r, !1)
|
|
506
|
+
), Object.keys(s(this, z)).forEach((r) => {
|
|
507
|
+
const a = s(this, $).querySelector(
|
|
508
|
+
`[name="${r}"]`
|
|
509
|
+
);
|
|
510
|
+
a && !s(this, E).has(a) && this.addField(a, !1);
|
|
511
|
+
});
|
|
512
|
+
}
|
|
513
|
+
/**
|
|
514
|
+
* Adds a single field to the validation controller.
|
|
515
|
+
*/
|
|
516
|
+
addField(e, r = !0) {
|
|
517
|
+
if (!e || s(this, E).has(e))
|
|
518
|
+
return;
|
|
519
|
+
const a = e.getAttribute("data-ctrovalidate-rules"), i = e.getAttribute("data-ctrovalidate-if"), l = e.name, n = j(a || "", s(this, k)), d = l ? j(s(this, z)[l] || [], s(this, k)) : [], u = [...n, ...d];
|
|
520
|
+
if (u.length === 0 && !i)
|
|
521
|
+
return;
|
|
522
|
+
const g = {}, N = e.getAttribute("data-ctrovalidate-message");
|
|
523
|
+
N && (g["*"] = N);
|
|
524
|
+
for (const p of Array.from(e.attributes))
|
|
525
|
+
if (p.name.startsWith("data-ctrovalidate-") && p.name.endsWith("-message")) {
|
|
526
|
+
const _ = p.name.replace("data-ctrovalidate-", "").replace("-message", "");
|
|
527
|
+
_ !== "" && _ !== "message" && (g[_] = p.value);
|
|
528
|
+
}
|
|
529
|
+
const f = {
|
|
530
|
+
element: e,
|
|
531
|
+
rules: u,
|
|
532
|
+
dependency: v(this, P, He).call(this, i),
|
|
533
|
+
customMessages: Object.keys(g).length > 0 ? g : void 0,
|
|
534
|
+
state: { isDirty: !1, abortController: null, lastError: null }
|
|
535
|
+
};
|
|
536
|
+
s(this, y).push(f), s(this, E).set(e, f), s(this, m).debug("[FormController] Added field:", e), r && v(this, P, ae).call(this, f);
|
|
537
|
+
}
|
|
538
|
+
/**
|
|
539
|
+
* Removes a single field from the validation controller.
|
|
540
|
+
*/
|
|
541
|
+
removeField(e) {
|
|
542
|
+
if (!e || !s(this, E).has(e))
|
|
543
|
+
return;
|
|
544
|
+
const r = s(this, E).get(e);
|
|
545
|
+
if (c(this, y, s(this, y).filter((a) => a.element !== e)), r.listeners) {
|
|
546
|
+
const { onBlur: a, onInput: i, onControllerInput: l, controllerElement: n } = r.listeners;
|
|
547
|
+
try {
|
|
548
|
+
e.removeEventListener("blur", a), e.removeEventListener("input", i), n && l && n.removeEventListener("input", l);
|
|
549
|
+
} catch (d) {
|
|
550
|
+
s(this, m).debug(
|
|
551
|
+
"[FormController] Error while removing listeners for",
|
|
552
|
+
e,
|
|
553
|
+
d
|
|
554
|
+
);
|
|
555
|
+
}
|
|
556
|
+
delete r.listeners;
|
|
557
|
+
}
|
|
558
|
+
s(this, E).delete(e), s(this, m).debug("[FormController] Removed field:", e);
|
|
559
|
+
}
|
|
560
|
+
/**
|
|
561
|
+
* Attaches all necessary event listeners for real-time validation.
|
|
562
|
+
*/
|
|
563
|
+
attachEventListeners(e) {
|
|
564
|
+
if (!e) {
|
|
565
|
+
s(this, m).info("[FormController] Real-time validation is disabled.");
|
|
566
|
+
return;
|
|
567
|
+
}
|
|
568
|
+
s(this, m).info(
|
|
569
|
+
"[FormController] Real-time validation is enabled. Attaching listeners."
|
|
570
|
+
), s(this, y).forEach(
|
|
571
|
+
(r) => v(this, P, ae).call(this, r)
|
|
572
|
+
);
|
|
573
|
+
}
|
|
574
|
+
/**
|
|
575
|
+
* Resets all field states to clean.
|
|
576
|
+
*/
|
|
577
|
+
reset() {
|
|
578
|
+
s(this, y).forEach((e) => {
|
|
579
|
+
e.state.isDirty = !1, e.state.lastError = null;
|
|
580
|
+
});
|
|
581
|
+
}
|
|
582
|
+
/**
|
|
583
|
+
* Cleans up all event listeners.
|
|
584
|
+
*/
|
|
585
|
+
destroy() {
|
|
586
|
+
[...s(this, y)].forEach((e) => {
|
|
587
|
+
this.removeField(e.element);
|
|
588
|
+
});
|
|
589
|
+
}
|
|
590
|
+
/**
|
|
591
|
+
* Returns all discovered fields.
|
|
592
|
+
*/
|
|
593
|
+
getFields() {
|
|
594
|
+
return s(this, y);
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
$ = new WeakMap(), y = new WeakMap(), E = new WeakMap(), m = new WeakMap(), z = new WeakMap(), k = new WeakMap(), M = new WeakMap(), P = new WeakSet(), /**
|
|
598
|
+
* Attaches event listeners to a single field object.
|
|
599
|
+
*/
|
|
600
|
+
ae = function(e) {
|
|
601
|
+
const { element: r, dependency: a } = e;
|
|
602
|
+
if (e.listeners) return;
|
|
603
|
+
const i = () => {
|
|
604
|
+
s(this, m).debug(
|
|
605
|
+
`[FormController] Blur event on "${r.name}". Validating.`
|
|
606
|
+
), s(this, M).call(this, e), e.state.isDirty = !0;
|
|
607
|
+
}, l = () => {
|
|
608
|
+
e.state.isDirty && (s(this, m).debug(
|
|
609
|
+
`[FormController] Input event on dirty field "${r.name}". Validating.`
|
|
610
|
+
), s(this, M).call(this, e));
|
|
611
|
+
};
|
|
612
|
+
r.addEventListener("blur", i), r.addEventListener("input", l);
|
|
613
|
+
let n = null, d = null;
|
|
614
|
+
a && (n = s(this, $).querySelector(
|
|
615
|
+
`[name="${a.controllerName}"]`
|
|
616
|
+
), n ? (d = () => {
|
|
617
|
+
s(this, m).debug(
|
|
618
|
+
`[FormController] Controller "${n.name}" changed. Re-validating dependent field "${r.name}".`
|
|
619
|
+
), s(this, M).call(this, e);
|
|
620
|
+
}, n.addEventListener("input", d)) : s(this, m).warn(
|
|
621
|
+
`[FormController] Could not find controller field with name "${a.controllerName}".`
|
|
622
|
+
)), e.listeners = {
|
|
623
|
+
onBlur: i,
|
|
624
|
+
onInput: l,
|
|
625
|
+
onControllerInput: d,
|
|
626
|
+
controllerElement: n
|
|
627
|
+
};
|
|
628
|
+
}, /**
|
|
629
|
+
* Parses the dependency condition string.
|
|
630
|
+
*/
|
|
631
|
+
He = function(e) {
|
|
632
|
+
if (!e) return null;
|
|
633
|
+
const r = e.split(":"), a = r[0], i = r[1] || "present";
|
|
634
|
+
if (!a) return null;
|
|
635
|
+
if (i.includes("=")) {
|
|
636
|
+
const [l, n] = i.split("=");
|
|
637
|
+
return { controllerName: a, type: l, value: n };
|
|
638
|
+
} else
|
|
639
|
+
return { controllerName: a, type: i };
|
|
640
|
+
};
|
|
641
|
+
const vt = (t, e) => {
|
|
642
|
+
const r = t;
|
|
643
|
+
if (!r.form)
|
|
644
|
+
return "";
|
|
645
|
+
const a = r.form.querySelector(
|
|
646
|
+
`[name="${e}"]`
|
|
647
|
+
);
|
|
648
|
+
return a ? a.value : "";
|
|
649
|
+
}, bt = (t, e = [], r = null) => {
|
|
650
|
+
const a = String(e[0] || "");
|
|
651
|
+
if (a === void 0)
|
|
652
|
+
return console.error("[Ctrovalidate] Missing parameter for 'sameAs' rule."), !1;
|
|
653
|
+
if (!r) return !1;
|
|
654
|
+
const i = vt(r, a);
|
|
655
|
+
return Be(t, [i]);
|
|
656
|
+
}, yt = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
|
|
657
|
+
__proto__: null,
|
|
658
|
+
alpha: xe,
|
|
659
|
+
alphaDash: $e,
|
|
660
|
+
alphaNum: Re,
|
|
661
|
+
between: Ve,
|
|
662
|
+
creditCard: qe,
|
|
663
|
+
decimal: Te,
|
|
664
|
+
email: Ae,
|
|
665
|
+
exactLength: ke,
|
|
666
|
+
integer: Le,
|
|
667
|
+
ipAddress: _e,
|
|
668
|
+
json: Ie,
|
|
669
|
+
max: we,
|
|
670
|
+
maxLength: Se,
|
|
671
|
+
min: Ce,
|
|
672
|
+
minLength: Ne,
|
|
673
|
+
numeric: Me,
|
|
674
|
+
phone: De,
|
|
675
|
+
required: Fe,
|
|
676
|
+
sameAs: bt,
|
|
677
|
+
strongPassword: ze,
|
|
678
|
+
url: Pe
|
|
679
|
+
}, Symbol.toStringTag, { value: "Module" })), me = {
|
|
680
|
+
...yt
|
|
681
|
+
}, ge = {}, pe = {}, Et = {
|
|
682
|
+
required: "This field is required.",
|
|
683
|
+
minLength: "This field must be at least {0} characters long.",
|
|
684
|
+
maxLength: "This field cannot be longer than {0} characters.",
|
|
685
|
+
between: "This field must be between {0} and {1} characters long.",
|
|
686
|
+
email: "Please enter a valid email address.",
|
|
687
|
+
phone: "Please enter a valid phone number.",
|
|
688
|
+
url: "Please enter a valid URL.",
|
|
689
|
+
numeric: "This field must contain only numbers.",
|
|
690
|
+
alpha: "This field must contain only letters.",
|
|
691
|
+
alphaDash: "This field must contain only letters, numbers, and dashes.",
|
|
692
|
+
alphaSpaces: "This field must contain only letters and spaces.",
|
|
693
|
+
alphaNum: "This field must contain only letters and numbers.",
|
|
694
|
+
decimal: "This field must be a valid decimal number.",
|
|
695
|
+
integer: "This field must be a valid integer.",
|
|
696
|
+
min: "This field must be at least {0}.",
|
|
697
|
+
max: "This field cannot be more than {0}.",
|
|
698
|
+
exactLength: "This field must be exactly {0} characters long.",
|
|
699
|
+
format: "This field format is invalid.",
|
|
700
|
+
sameAs: "This field must match {0}.",
|
|
701
|
+
strongPassword: "Password must be at least 8 characters long and contain at least one uppercase letter, one lowercase letter, one number, and one special character.",
|
|
702
|
+
creditCard: "Please enter a valid credit card number.",
|
|
703
|
+
ipAddress: "Please enter a valid IP address.",
|
|
704
|
+
json: "Please enter a valid JSON string."
|
|
705
|
+
}, G = {
|
|
706
|
+
...Et
|
|
707
|
+
};
|
|
708
|
+
function Ft(t) {
|
|
709
|
+
Object.assign(G, t);
|
|
710
|
+
}
|
|
711
|
+
var L, F, A, T, V, h, X, Oe;
|
|
712
|
+
class At {
|
|
713
|
+
/**
|
|
714
|
+
* @param formElement - The HTML form element to validate.
|
|
715
|
+
* @param options - Custom configuration options.
|
|
716
|
+
*/
|
|
717
|
+
constructor(e, r = {}) {
|
|
718
|
+
o(this, X);
|
|
719
|
+
o(this, L);
|
|
720
|
+
o(this, F);
|
|
721
|
+
o(this, A);
|
|
722
|
+
o(this, T);
|
|
723
|
+
o(this, V);
|
|
724
|
+
o(this, h);
|
|
725
|
+
if (!(e instanceof HTMLFormElement))
|
|
726
|
+
throw new Error(
|
|
727
|
+
"Ctrovalidate requires a valid HTMLFormElement to be initialized."
|
|
728
|
+
);
|
|
729
|
+
c(this, L, e), c(this, F, {
|
|
730
|
+
logLevel: ee.NONE,
|
|
731
|
+
errorClass: "is-invalid",
|
|
732
|
+
errorMessageClass: "error-message",
|
|
733
|
+
pendingClass: "ctrovalidate-pending",
|
|
734
|
+
realTime: !0,
|
|
735
|
+
schema: {},
|
|
736
|
+
aliases: {},
|
|
737
|
+
...r
|
|
738
|
+
}), c(this, A, new Ye(s(this, F).logLevel)), c(this, T, new te(s(this, F))), c(this, V, new gt(s(this, L), {
|
|
739
|
+
logger: s(this, A),
|
|
740
|
+
uiManager: s(this, T),
|
|
741
|
+
rules: me,
|
|
742
|
+
asyncRules: ge,
|
|
743
|
+
messages: G
|
|
744
|
+
})), c(this, h, new pt(s(this, L), {
|
|
745
|
+
logger: s(this, A),
|
|
746
|
+
schema: s(this, F).schema,
|
|
747
|
+
aliases: { ...pe, ...s(this, F).aliases },
|
|
748
|
+
validationHandler: (a) => s(this, V).validateField(a)
|
|
749
|
+
})), v(this, X, Oe).call(this);
|
|
750
|
+
}
|
|
751
|
+
/**
|
|
752
|
+
* Defines a rule alias (macro) globally.
|
|
753
|
+
*/
|
|
754
|
+
static defineAlias(e, r) {
|
|
755
|
+
pe[e] = r;
|
|
756
|
+
}
|
|
757
|
+
/**
|
|
758
|
+
* Validates the entire form programmatically.
|
|
759
|
+
*/
|
|
760
|
+
async validate() {
|
|
761
|
+
return this.validateForm();
|
|
762
|
+
}
|
|
763
|
+
/**
|
|
764
|
+
* Validates the entire form programmatically.
|
|
765
|
+
*/
|
|
766
|
+
async validateForm() {
|
|
767
|
+
s(this, A).info("[Ctrovalidate] Manually triggering form validation.");
|
|
768
|
+
const e = s(this, h).getFields(), a = (await Promise.all(
|
|
769
|
+
e.map((i) => s(this, V).validateField(i))
|
|
770
|
+
)).every((i) => i === !0);
|
|
771
|
+
return a ? s(this, A).info("[Ctrovalidate] Form validation succeeded.") : s(this, A).warn("[Ctrovalidate] Form validation failed."), a;
|
|
772
|
+
}
|
|
773
|
+
/**
|
|
774
|
+
* Allows the user to add custom validation messages globally.
|
|
775
|
+
*/
|
|
776
|
+
static setCustomMessages(e) {
|
|
777
|
+
Ft(e);
|
|
778
|
+
}
|
|
779
|
+
/**
|
|
780
|
+
* Adds a custom synchronous validation rule globally.
|
|
781
|
+
*/
|
|
782
|
+
static addRule(e, r, a) {
|
|
783
|
+
if (!e || typeof r != "function") {
|
|
784
|
+
console.error(
|
|
785
|
+
"[Ctrovalidate] addRule requires a rule name and a logic function."
|
|
786
|
+
);
|
|
787
|
+
return;
|
|
788
|
+
}
|
|
789
|
+
me[e] = r, a && (G[e] = a);
|
|
790
|
+
}
|
|
791
|
+
/**
|
|
792
|
+
* Adds a custom asynchronous validation rule globally.
|
|
793
|
+
*/
|
|
794
|
+
static addAsyncRule(e, r, a) {
|
|
795
|
+
if (!e || typeof r != "function") {
|
|
796
|
+
console.error(
|
|
797
|
+
"[Ctrovalidate] addAsyncRule requires a rule name and a logic function."
|
|
798
|
+
);
|
|
799
|
+
return;
|
|
800
|
+
}
|
|
801
|
+
ge[e] = r, a && (G[e] = a);
|
|
802
|
+
}
|
|
803
|
+
/**
|
|
804
|
+
* Manually adds a new field to be validated.
|
|
805
|
+
*/
|
|
806
|
+
addField(e) {
|
|
807
|
+
s(this, h).addField(e);
|
|
808
|
+
}
|
|
809
|
+
/**
|
|
810
|
+
* Manually removes a field from validation.
|
|
811
|
+
*/
|
|
812
|
+
removeField(e) {
|
|
813
|
+
s(this, h).removeField(e);
|
|
814
|
+
}
|
|
815
|
+
/**
|
|
816
|
+
* Refreshes the discovered fields. Use if fields are added or removed from the DOM.
|
|
817
|
+
*/
|
|
818
|
+
refresh() {
|
|
819
|
+
s(this, h).discoverFields(), s(this, h).attachEventListeners(s(this, F).realTime);
|
|
820
|
+
}
|
|
821
|
+
/**
|
|
822
|
+
* Returns the current error for a field by its name.
|
|
823
|
+
*/
|
|
824
|
+
getError(e) {
|
|
825
|
+
const r = s(this, h).getFields().find((a) => a.element.name === e);
|
|
826
|
+
return r ? r.state.lastError : null;
|
|
827
|
+
}
|
|
828
|
+
/**
|
|
829
|
+
* Checks if a field has been interacted with.
|
|
830
|
+
*/
|
|
831
|
+
isDirty(e) {
|
|
832
|
+
const r = s(this, h).getFields().find((a) => a.element.name === e);
|
|
833
|
+
return r ? r.state.isDirty : !1;
|
|
834
|
+
}
|
|
835
|
+
/**
|
|
836
|
+
* Resets the validation state of the form.
|
|
837
|
+
*/
|
|
838
|
+
reset() {
|
|
839
|
+
s(this, h).reset(), s(this, h).getFields().forEach((e) => {
|
|
840
|
+
s(this, T).clearError(e.element);
|
|
841
|
+
});
|
|
842
|
+
}
|
|
843
|
+
/**
|
|
844
|
+
* Fully cleans up the validator instance.
|
|
845
|
+
*/
|
|
846
|
+
destroy() {
|
|
847
|
+
s(this, h).destroy(), s(this, h).getFields().forEach((e) => {
|
|
848
|
+
s(this, T).clearError(e.element);
|
|
849
|
+
});
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
L = new WeakMap(), F = new WeakMap(), A = new WeakMap(), T = new WeakMap(), V = new WeakMap(), h = new WeakMap(), X = new WeakSet(), /**
|
|
853
|
+
* Initializes the library by discovering fields and attaching listeners.
|
|
854
|
+
*/
|
|
855
|
+
Oe = function() {
|
|
856
|
+
s(this, A).info("[Ctrovalidate] Initializing library."), s(this, h).discoverFields(), s(this, h).attachEventListeners(s(this, F).realTime), s(this, L).setAttribute("novalidate", "true");
|
|
857
|
+
}, de(At, "LogLevel", ee);
|
|
858
|
+
export {
|
|
859
|
+
At as Ctrovalidate,
|
|
860
|
+
ee as LogLevel
|
|
861
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "ctrovalidate-browser",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Browser adapter for Ctrovalidate - Declarative HTML-first form validation with data attributes.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.cjs",
|
|
7
|
+
"module": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"import": "./dist/index.js",
|
|
13
|
+
"require": "./dist/index.cjs"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"dist"
|
|
18
|
+
],
|
|
19
|
+
"scripts": {
|
|
20
|
+
"build": "vite build",
|
|
21
|
+
"test": "vitest run",
|
|
22
|
+
"test:coverage": "vitest run --coverage",
|
|
23
|
+
"lint": "eslint .",
|
|
24
|
+
"format": "prettier . --check"
|
|
25
|
+
},
|
|
26
|
+
"devDependencies": {
|
|
27
|
+
"@eslint/js": "^9.39.2",
|
|
28
|
+
"ctrovalidate-core": "^1.0.0",
|
|
29
|
+
"eslint": "^9.39.2",
|
|
30
|
+
"eslint-config-prettier": "^9.1.0",
|
|
31
|
+
"globals": "^17.2.0",
|
|
32
|
+
"prettier": "^3.1.1",
|
|
33
|
+
"vite": "^6.0.0",
|
|
34
|
+
"vite-plugin-dts": "^4.0.0",
|
|
35
|
+
"typescript": "^5.9.3",
|
|
36
|
+
"typescript-eslint": "^8.54.0",
|
|
37
|
+
"vitest": "^4.0.18"
|
|
38
|
+
},
|
|
39
|
+
"homepage": "https://ctrovalidate.vercel.app/guide/browser",
|
|
40
|
+
"keywords": [
|
|
41
|
+
"ctrovalidate",
|
|
42
|
+
"validation",
|
|
43
|
+
"form-validation",
|
|
44
|
+
"browser",
|
|
45
|
+
"javascript"
|
|
46
|
+
],
|
|
47
|
+
"repository": {
|
|
48
|
+
"type": "git",
|
|
49
|
+
"url": "git+https://github.com/ctrotech-tutor/ctrovalidate-browser.git"
|
|
50
|
+
},
|
|
51
|
+
"bugs": {
|
|
52
|
+
"url": "https://github.com/ctrotech-tutor/ctrovalidate-browser/issues"
|
|
53
|
+
},
|
|
54
|
+
"publishConfig": {
|
|
55
|
+
"access": "public"
|
|
56
|
+
},
|
|
57
|
+
"author": "Ctrotech (https://github.com/ctrotech-tutor)",
|
|
58
|
+
"license": "MIT"
|
|
59
|
+
}
|