npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

form-esm

v1.0.0

Published

A dynamic form generator module with schema-driven forms, supporting Angular 18+ and modern frontend frameworks

Downloads

18

Readme

FormESM

一个功能强大的动态表单生成器模块,通过 JSON 元数据自动生成完整的响应式表单,支持 Angular 18+ 和现代前端框架。

特性

  • 📝 Schema-Driven Forms - 通过 JSON 配置自动生成表单
  • 🎯 多种字段类型 - text, email, password, number, textarea, select, checkbox, radio, date, file, custom
  • 🏗️ 嵌套表单组 - 支持对象和数组类型的嵌套结构
  • 🔗 条件显示 - 字段间的联动,根据条件显示/隐藏字段
  • 🌐 异步验证 - 支持远程校验,防抖处理
  • ⚠️ 实时错误提示 - 即时验证反馈,友好的错误消息
  • 🎨 现代化样式 - 内置美观的默认样式,支持自定义
  • 📱 响应式设计 - 自动适配不同屏幕尺寸
  • 🔧 TypeScript 支持 - 完整的类型定义
  • 🌍 跨框架 - 兼容 Angular 18+、React、Vue 等现代框架

安装

npm install form-esm

快速开始

基础使用

import { createFormGenerator } from 'form-esm';

const schema = {
  fields: [
    {
      key: 'username',
      type: 'text',
      label: '用户名',
      placeholder: '请输入用户名',
      required: true,
      validations: [
        { type: 'required', message: '用户名不能为空' },
        { type: 'minLength', value: 3, message: '用户名至少3个字符' }
      ]
    },
    {
      key: 'email',
      type: 'email',
      label: '邮箱',
      placeholder: '请输入邮箱地址',
      required: true,
      validations: [
        { type: 'required', message: '邮箱不能为空' },
        { type: 'email', message: '请输入有效的邮箱地址' }
      ]
    }
  ]
};

const form = createFormGenerator({
  schema,
  events: {
    onValueChange: (value) => {
      console.log('表单值变化:', value);
    },
    onSubmit: async (value) => {
      console.log('提交表单:', value);
      await submitToServer(value);
    }
  }
});

// 渲染表单
const container = document.getElementById('form-container');
const { FieldRenderer } = require('form-esm');
const renderer = new FieldRenderer(form);

form.schema.fields.forEach(field => {
  const fieldContainer = document.createElement('div');
  renderer.render(field, fieldContainer);
  container.appendChild(fieldContainer);
});

字段类型

文本字段 (Text)

{
  key: 'username',
  type: 'text',
  label: '用户名',
  placeholder: '请输入用户名',
  required: true,
  validations: [
    { type: 'minLength', value: 3, message: '至少3个字符' },
    { type: 'maxLength', value: 20, message: '最多20个字符' },
    { type: 'pattern', value: /^[a-zA-Z0-9_]+$/, message: '只能包含字母、数字和下划线' }
  ]
}

数字字段 (Number)

{
  key: 'age',
  type: 'number',
  label: '年龄',
  min: 18,
  max: 120,
  step: 1,
  required: true,
  validations: [
    { type: 'min', value: 18, message: '年龄必须满18岁' },
    { type: 'max', value: 120, message: '年龄不能超过120岁' }
  ]
}

文本域 (Textarea)

{
  key: 'bio',
  type: 'textarea',
  label: '个人简介',
  placeholder: '请输入个人简介',
  rows: 4,
  resize: 'vertical',
  validations: [
    { type: 'maxLength', value: 500, message: '简介最多500个字符' }
  ]
}

选择字段 (Select)

{
  key: 'gender',
  type: 'select',
  label: '性别',
  required: true,
  options: [
    { label: '男', value: 'male' },
    { label: '女', value: 'female' },
    { label: '其他', value: 'other' }
  ]
}

多选:

{
  key: 'hobbies',
  type: 'select',
  label: '爱好',
  multiple: true,
  options: [
    { label: '阅读', value: 'reading' },
    { label: '运动', value: 'sports' },
    { label: '音乐', value: 'music' }
  ]
}

分组选项:

{
  key: 'category',
  type: 'select',
  label: '分类',
  optionGroups: {
    '前端': [
      { label: 'JavaScript', value: 'js' },
      { label: 'TypeScript', value: 'ts' }
    ],
    '后端': [
      { label: 'Node.js', value: 'node' },
      { label: 'Python', value: 'python' }
    ]
  }
}

复选框 (Checkbox)

{
  key: 'agree',
  type: 'checkbox',
  label: '我同意服务条款',
  required: true,
  trueValue: 'yes',
  falseValue: 'no'
}

单选框 (Radio)

{
  key: 'gender',
  type: 'radio',
  label: '性别',
  options: [
    { label: '男', value: 'male' },
    { label: '女', value: 'female' }
  ]
}

日期字段 (Date)

{
  key: 'birthDate',
  type: 'date',
  label: '出生日期',
  min: '1900-01-01',
  max: '2024-12-31',
  required: true
}

文件字段 (File)

{
  key: 'avatar',
  type: 'file',
  label: '头像',
  accept: 'image/*',
  multiple: false,
  maxSize: 5 * 1024 * 1024, // 5MB
  allowedTypes: ['image/jpeg', 'image/png', 'image/gif']
}

自定义字段 (Custom)

{
  key: 'customField',
  type: 'custom',
  label: '自定义组件',
  component: MyCustomComponent,
  componentInputs: {
    placeholder: '请输入...',
    maxLength: 100
  },
  componentOutputs: {
    onChange: (value) => console.log('值变化:', value)
  }
}

嵌套表单组

对象类型

{
  key: 'personalInfo',
  type: 'group',
  label: '个人信息',
  fields: [
    {
      key: 'firstName',
      type: 'text',
      label: '名',
      required: true
    },
    {
      key: 'lastName',
      type: 'text',
      label: '姓',
      required: true
    }
  ]
}

数组类型

{
  key: 'skills',
  type: 'array',
  label: '技能列表',
  minItems: 1,
  maxItems: 5,
  defaultItems: 1,
  addButtonText: '+ 添加技能',
  removeButtonText: '删除',
  itemConfig: {
    key: 'skill',
    type: 'text',
    label: '技能名称',
    placeholder: '例如:JavaScript',
    required: true
  }
}

数组中的嵌套对象:

{
  key: 'experience',
  type: 'array',
  label: '工作经历',
  itemConfig: {
    type: 'group',
    fields: [
      {
        key: 'company',
        type: 'text',
        label: '公司名称',
        required: true
      },
      {
        key: 'position',
        type: 'text',
        label: '职位',
        required: true
      }
    ]
  }
}

条件显示

根据其他字段的值来显示或隐藏字段:

const schema = {
  fields: [
    {
      key: 'hasExperience',
      type: 'checkbox',
      label: '有工作经验'
    },
    {
      key: 'yearsOfExperience',
      type: 'number',
      label: '工作年限',
      conditionalDisplay: {
        when: 'hasExperience',
        operator: 'eq',
        value: true,
        show: true
      }
    },
    {
      key: 'studentStatus',
      type: 'select',
      label: '学生状态',
      options: [
        { label: '在读', value: 'studying' },
        { label: '毕业', value: 'graduated' }
      ],
      conditionalDisplay: {
        when: 'hasExperience',
        operator: 'eq',
        value: false,
        show: true
      }
    }
  ]
};

支持的操作符:

  • eq - 等于
  • ne - 不等于
  • gt - 大于
  • lt - 小于
  • gte - 大于等于
  • lte - 小于等于
  • in - 包含在数组中
  • notIn - 不包含在数组中
  • contains - 包含字符串
  • notContains - 不包含字符串

异步验证

支持远程校验,自动防抖:

{
  key: 'username',
  type: 'text',
  label: '用户名',
  required: true,
  asyncValidation: {
    url: '/api/check-username',
    method: 'POST',
    headers: {
      'Authorization': 'Bearer token'
    },
    debounce: 500,
    transformRequest: (value) => ({
      username: value,
      timestamp: Date.now()
    }),
    transformResponse: (response) => {
      if (response.available) {
        return { valid: true };
      } else {
        return { valid: false, message: '用户名已存在' };
      }
    }
  }
}

验证规则

内置验证器

validations: [
  { type: 'required', message: '必填项' },
  { type: 'minLength', value: 3, message: '最小长度' },
  { type: 'maxLength', value: 20, message: '最大长度' },
  { type: 'min', value: 0, message: '最小值' },
  { type: 'max', value: 100, message: '最大值' },
  { type: 'pattern', value: /^[a-zA-Z]+$/, message: '格式不正确' },
  { type: 'email', message: '邮箱格式不正确' },
  { type: 'url', message: 'URL格式不正确' }
]

自定义验证器

{
  key: 'password',
  type: 'password',
  label: '密码',
  validations: [
    {
      type: 'custom',
      validator: (value) => {
        return value.length >= 8 && 
               /[A-Z]/.test(value) && 
               /[0-9]/.test(value);
      },
      message: '密码必须包含大写字母和数字,至少8个字符'
    }
  ]
}

API 参考

FormGenerator

构造函数

const form = createFormGenerator(options);

options:

  • schema (FormSchema) - 表单配置
  • initialValue (Object) - 初始值
  • events (FormEvents) - 事件处理器
  • cssClass (string) - 容器 CSS 类
  • style (Object) - 容器样式
  • debug (boolean) - 调试模式

方法

| 方法 | 说明 | 返回值 | |------|------|--------| | setValue(key, value) | 设置字段值 | void | | getValue(key) | 获取字段值 | any | | setValues(values) | 设置多个字段值 | void | | getValues() | 获取所有表单值 | Object | | validate() | 验证整个表单 | Promise<ValidationResult> | | validateField(key) | 验证单个字段 | Promise<ValidationResult> | | reset() | 重置表单到初始值 | void | | clear() | 清空表单 | void | | addField(config) | 添加字段 | void | | removeField(key) | 删除字段 | void | | showField(key) | 显示字段 | void | | hideField(key) | 隐藏字段 | void | | enableField(key) | 启用字段 | void | | disableField(key) | 禁用字段 | void | | getErrors() | 获取所有错误 | Object | | getFieldErrors(key) | 获取字段错误 | string[] | | isDirty() | 表单是否被修改 | boolean | | isTouched() | 表单是否被触摸 | boolean | | isValid() | 表单是否有效 | boolean | | dispose() | 销毁表单 | void |

事件

events: {
  onValueChange: (value) => void,
  onFieldChange: (key, value) => void,
  onFieldFocus: (key) => void,
  onFieldBlur: (key) => void,
  onSubmit: (value) => void | Promise<void>,
  onReset: () => void,
  onError: (errors) => void
}

框架集成

Angular 18+

import { Component, ElementRef, ViewChild } from '@angular/core';
import { createFormGenerator } from 'form-esm';

@Component({
  selector: 'app-dynamic-form',
  template: `<div #formContainer></div>`
})
export class DynamicFormComponent {
  @ViewChild('formContainer') formContainer!: ElementRef;
  private form: any;

  ngAfterViewInit() {
    this.form = createFormGenerator({
      schema: { fields: [...] },
      events: { onValueChange: (value) => console.log(value) }
    });

    this.renderForm();
  }

  private renderForm() {
    const { FieldRenderer } = require('form-esm');
    const renderer = new FieldRenderer(this.form);
    // 渲染表单...
  }
}

React

import { useEffect, useRef } from 'react';
import { createFormGenerator } from 'form-esm';

function DynamicForm() {
  const formContainerRef = useRef(null);
  const [form, setForm] = useState(null);

  useEffect(() => {
    const generator = createFormGenerator({
      schema: { fields: [...] },
      events: { onValueChange: (value) => console.log(value) }
    });

    setForm(generator);
    renderForm(generator, formContainerRef.current);

    return () => generator.dispose();
  }, []);

  return <div ref={formContainerRef}></div>;
}

Vue

<template>
  <div ref="formContainer"></div>
</template>

<script>
import { createFormGenerator } from 'form-esm';

export default {
  mounted() {
    this.form = createFormGenerator({
      schema: { fields: [...] },
      events: { onValueChange: (value) => console.log(value) }
    });

    this.renderForm();
  },
  methods: {
    renderForm() {
      const { FieldRenderer } = require('form-esm');
      const renderer = new FieldRenderer(this.form);
      // 渲染表单...
    }
  }
}
</script>

样式定制

使用默认样式

<link rel="stylesheet" href="node_modules/form-esm/src/styles/default.css">

自定义样式

:root {
  --form-primary-color: #667eea;
  --form-error-color: #ef4444;
  --form-border-color: #d1d5db;
  --form-radius: 6px;
}

.form-input {
  border: 2px solid var(--form-primary-color);
  border-radius: var(--form-radius);
}

.form-error {
  color: var(--form-error-color);
  font-weight: 600;
}

浏览器支持

  • Chrome 61+
  • Firefox 60+
  • Safari 11+
  • Edge 16+

许可证

MIT License

相关链接