表单校验

原生约束优先的零依赖校验层:required / type / minlength / maxlength / pattern / min / max / step 即 schema,data-icen-validate 只承载额外规则(matches:<字段名> 跨字段、registerValidator 注…

必填,3 个以上字符;admin 会被 mock 重名校验拒绝
选填,18–120

直接提交看全量校验 → 改出错字段即时复验 → 全部通过派 icen:form-valid

html
<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

一行引入该组件(CSS 与 behavior 自动带上,无需关心内部 CSS 文件名):

js
import { initFormValidate } from '@icen.ai/ui/kit/form-validate';initFormValidate();

也可以用脚手架直接打印引入行:

bash
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 项目无此问题。

用法

js
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 }