表单校验
原生约束优先的零依赖校验层:required / type / minlength / maxlength / pattern / min / max / step 即 schema,data-icen-validate 只承载额外规则(matches:<字段名> 跨字段、registerValidator 注…
直接提交看全量校验 → 改出错字段即时复验 → 全部通过派 icen:form-valid
<form class="icen-form" id="demo-fv-form" data-icen-validate data-icen-validate-summary data-icen-validate-show-valid style="max-width:460px"> <div class="icen-field"> <label class="icen-field-label" for="demo-fv-user">用户名</label> <input id="demo-fv-user" class="icen-form-input" name="username" required minlength="3" data-icen-validate="taken" data-icen-msg-required="请输入用户名" placeholder="试试 admin(异步重名)" /> <span class="icen-field-hint">必填,3 个以上字符;admin 会被 mock 重名校验拒绝</span> </div> <div class="icen-field"> <label class="icen-field-label" for="demo-fv-email">邮箱</label> <input id="demo-fv-email" class="icen-form-input" name="email" type="email" required placeholder="you@icen.ai" /> </div> <div class="icen-field"> <label class="icen-field-label" for="demo-fv-pwd">密码</label> <input id="demo-fv-pwd" class="icen-form-input" name="password" type="password" required minlength="6" /> </div> <div class="icen-field"> <label class="icen-field-label" for="demo-fv-pwd2">确认密码</label> <input id="demo-fv-pwd2" class="icen-form-input" name="confirm" type="password" data-icen-validate="matches:password" /> </div> <div class="icen-field"> <label class="icen-field-label" for="demo-fv-age">年龄</label> <input id="demo-fv-age" class="icen-form-input" name="age" type="number" min="18" max="120" /> <span class="icen-field-hint">选填,18–120</span> </div> <div class="icen-form-actions"> <button type="submit" class="icen-btn icen-btn-primary">提交</button> <button type="button" class="icen-btn icen-btn-ghost" id="demo-fv-reset">复位校验态</button> </div></form><p class="demo-label" id="demo-fv-log" style="margin-top:10px">直接提交看全量校验 → 改出错字段即时复验 → 全部通过派 icen:form-valid</p>
行为与接口说明
原生约束优先的零依赖校验层:required / type / minlength / maxlength / pattern / min / max / step 即 schema,data-icen-validate 只承载额外规则(matches:<字段名> 跨字段、registerValidator 注册的具名自定义校验可异步);data-icen-msg-<rule> 单字段消息,setValidateMessages 全局 i18n。默认策略 submit 优先:首次提交全量校验,之后出错字段 input 即时复验(160ms 防抖)、未触碰字段 blur 补验;IME 组合态挂起。错误呈现:最近 .icen-field 挂 .is-error + 自动 <p class="icen-field-error" role="alert">,控件 aria-invalid + aria-describedby 接线;异步校验 .is-validating + aria-busy,竞态守卫最新为准;data-icen-validate-summary 顶部错误清单 + 聚焦首错。事件 icen:form-field-validate { name, valid, message? } / icen:form-valid { names } / icen:form-invalid { errors, first }。import 本模块自动注册 AI 工具 show_form(向用户渲染结构化表单收集输入,run 返回 Promise:提交 resolve 值对象,取消 resolve { cancelled: true })。
安装
bun add @icen.ai/ui pnpm add @icen.ai/ui npm install @icen.ai/ui yarn add @icen.ai/ui 一行引入该组件(CSS 与 behavior 自动带上,无需关心内部 CSS 文件名):
import { initFormValidate } from '@icen.ai/ui/kit/form-validate';initFormValidate();
也可以用脚手架直接打印引入行:
bunx --bun @icen.ai/ui add form-validate
Astro 项目注意:kit 入口里的 CSS import 在页面 <script> 里会被 Astro 的 client bundle 摇掉——请改在布局 frontmatter 里引 CSS(import '@icen.ai/ui/components/form-validate.css';),JS 行为仍可走 kit。Vite SPA / webpack 项目无此问题。
用法
import { initFormValidate, createFormValidator, registerValidator, setValidateMessages } from '@icen.ai/ui/kit/form-validate';initFormValidate(); // document 委托 + 幂等;form[data-icen-validate] 自动挂 novalidate<!-- HTML5 属性即 schema;data-icen-validate 只承载额外规则 --><form data-icen-validate data-icen-validate-summary data-icen-validate-show-valid> <input name="email" type="email" required data-icen-msg-required="邮箱必填" /> <input name="confirm" data-icen-validate="matches:password" /> <input name="code" data-icen-validate="unique:invite" /></form><!-- 具名自定义校验(可异步;true 通过,string 即消息) -->registerValidator('unique', async (value, field, form) => { const res = await fetch('/api/check?code=' + encodeURIComponent(value)); return res.ok ? true : '邀请码无效';});<!-- i18n 全局消息表(占位符 {min} {max} {step} {length} {label}) -->setValidateMessages({ required: '{label}不能为空', email: '邮箱格式有误' });<!-- 程序化单表单(同 form 单例,init 与显式创建共享) -->const handle = createFormValidator(formEl, { strategy: { on: 'submit', debounceMs: 160 }, // on: 'submit'|'blur'|'input';缺省 = submit 优先策略 scrollToError: true, // 失败聚焦首错 + scrollIntoView});await handle.validate(); // true | false;事件 icen:form-valid / icen:form-invalid 双通道await handle.validateField('email');handle.errors(); // { 字段名: 消息 }handle.reset(); // 清状态与呈现(不动值)handle.destroy();// 事件(detail 契约见 events.ts IcenEventMap):// icen:form-field-validate { name, valid, message? } 每字段校验完// icen:form-valid { names } 整表通过(通过字段名数组)// icen:form-invalid { errors, first } 未过(errors + 第一个错误字段名)// AI 工具(import 本模块即自动注册):// show_form —— 向用户渲染一个结构化表单收集输入;// area.call('show_form', { title, fields: [{ name, label, type, required, pattern, min, max, options, help }], submitLabel })// → run 返回 Promise:提交(过校验)resolve 表单值对象;「取消」resolve { cancelled: true }