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

@fidt/dynamic-form

v1.0.1

Published

A dynamic form library for Vue 3 with built-in validation support (Zod, ArkType)

Readme

@fidt/dynamic-form

Thư viện dynamic form cho Vue 3 với hỗ trợ validation tích hợp (Zod, ArkType).

✨ Tính năng

  • 🎯 Dynamic Form Generation - Tạo form từ schema JSON
  • 📐 Flexible Layout - Hỗ trợ grid layout đa cột và nested fields
  • Built-in Validation - Validation tích hợp với Zod/ArkType
  • 🎨 UI Component Agnostic - Tương thích với bất kỳ UI component nào
  • 📦 Type-safe - Fully typed với TypeScript
  • 🔄 Reactive - Tích hợp Vue reactivity system

📦 Cài đặt

npm install @fidt/dynamic-form
# hoặc
pnpm add @fidt/dynamic-form

🚀 Sử dụng cơ bản

1. Import và đăng ký

import DymikForm from '@fidt/dynamic-form'
import '@fidt/dynamic-form/dist/dynamic-form.css'

// Đăng ký global (optional)
app.use(DymikForm)

// Hoặc import trực tiếp trong component
import { DynamicForm, FormModel } from '@fidt/dynamic-form'

2. Tạo form đơn giản

<script setup lang="ts">
import { reactive } from 'vue'
import { DynamicForm, FormModel } from '@fidt/dynamic-form'

const form = reactive(
  new FormModel({
    name: 'contact-form',
    description: 'Form liên hệ đơn giản',
    fields: [
      {
        name: 'fullName',
        label: 'Họ và tên',
        type: 'FInput',
        required: true,
        required_text: 'Vui lòng nhập họ tên',
        props: { placeholder: 'Nhập họ và tên' },
        validation_rules: [
          { type: 'minLength', value: 2, message: 'Tên phải có ít nhất 2 ký tự' }
        ]
      },
      {
        name: 'email',
        label: 'Email',
        type: 'FInput',
        required: true,
        props: { placeholder: '[email protected]' },
        validation_rules: [
          { type: 'email', message: 'Email không hợp lệ' }
        ]
      },
      {
        name: 'submit',
        type: 'FButton',
        role: 'submit',
        props: { label: 'Gửi', order: 'primary' }
      }
    ]
  })
)

function onSubmit(values: Record<string, unknown>) {
  console.log('Form data:', values)
}
</script>

<template>
  <DynamicForm :form="form" @submit="onSubmit" />
</template>

📐 Layout nâng cao

Grid Layout với nhiều cột

const form = reactive(
  new FormModel({
    name: 'registration-form',
    columns: 3, // Số cột trong grid
    layout: [
      ['fullName', 'email', 'phone'],          // Hàng 1: 3 fields
      [['idCard', 'issueDate'], 'address', null], // Hàng 2: nested fields, 1 field, 1 ô trống
      [null, null, 'submit'],                  // Hàng 3: button căn phải
    ],
    fields: {
      fullName: {
        label: 'Họ và tên',
        type: 'FInput',
        required: true,
        props: { placeholder: 'Nhập họ tên' }
      },
      email: {
        label: 'Email',
        type: 'FInput',
        required: true,
        validation_rules: [{ type: 'email', message: 'Email không hợp lệ' }]
      },
      phone: {
        label: 'Số điện thoại',
        type: 'FInput',
        props: { placeholder: '0123456789' }
      },
      idCard: {
        label: 'CCCD',
        type: 'FInput',
        required: true,
        props: { placeholder: 'Số CCCD' }
      },
      issueDate: {
        label: 'Ngày cấp',
        type: 'FInput',
        props: { type: 'date' }
      },
      address: {
        label: 'Địa chỉ',
        type: 'FInput',
        props: { placeholder: 'Địa chỉ hiện tại' }
      },
      submit: {
        type: 'FButton',
        role: 'submit',
        props: { label: 'Đăng ký', order: 'primary' }
      }
    }
  })
)

Giải thích Layout

  • columns: Số cột trong grid (mặc định: auto)
  • layout: Mảng 2D định nghĩa vị trí các field
    • 'fieldName' - Field đơn chiếm 1 ô
    • ['field1', 'field2'] - Nhiều field nested trong 1 ô
    • null - Ô trống (dùng để căn chỉnh)

Format Fields

Có 2 cách định nghĩa fields:

1. Array format (truyền thống):

fields: [
  {
    name: 'email',
    label: 'Email',
    type: 'FInput',
    // ...
  }
]

2. Object format (recommended với layout):

fields: {
  email: {
    label: 'Email',
    type: 'FInput',
    // ...
  }
  // name tự động lấy từ key
}

✅ Validation

Các loại validation được hỗ trợ

validation_rules: [
  // String validation
  { type: 'string', message: 'Phải là chuỗi' },
  { type: 'minLength', value: 3, message: 'Tối thiểu 3 ký tự' },
  { type: 'maxLength', value: 50, message: 'Tối đa 50 ký tự' },
  
  // Number validation
  { type: 'number', message: 'Phải là số' },
  { type: 'min', value: 18, message: 'Tối thiểu 18' },
  { type: 'max', value: 100, message: 'Tối đa 100' },
  
  // Special formats
  { type: 'email', message: 'Email không hợp lệ' },
  { type: 'url', message: 'URL không hợp lệ' },
  { type: 'regex', value: /^\d{9,12}$/, message: 'CCCD không đúng định dạng' },
  
  // Custom validation
  { 
    type: 'custom', 
    value: (value, formValues) => value !== formValues.password,
    message: 'Mật khẩu không khớp'
  }
]

Custom Validator

import { ValidatorUtils } from '@fidt/dynamic-form'

ValidatorUtils.addCustomValidator('isAdult', (rule, value) => {
  const age = parseInt(value)
  return age >= 18
})

// Sử dụng
validation_rules: [
  { type: 'isAdult', message: 'Phải từ 18 tuổi trở lên' }
]

🎯 FormModel API

Properties

interface FormItem {
  name: string                    // Tên form
  description?: string            // Mô tả
  id?: string                     // ID duy nhất
  fields: FormField[]             // Danh sách fields
  columns?: number                // Số cột grid
  layout?: (string | null | string[])[][]  // Layout grid
  css_classes?: string            // CSS classes tùy chỉnh
  disabled?: boolean              // Disable toàn bộ form
  invalid?: boolean               // Trạng thái invalid (readonly)
}

Methods

// Validation
form.validate(): boolean                    // Validate toàn bộ form
form.validateField(name, value): boolean    // Validate 1 field

// Get/Set values
form.getFormValue(): Record<string, any>              // Lấy tất cả giá trị
form.setFormValue(values: Record<string, any>): void  // Set nhiều giá trị
form.setFieldValue(name: string, value: any): void    // Set 1 giá trị

// Errors
form.getFormErrors(): Record<string, string>          // Lấy tất cả lỗi
form.getFormError(name: string): string | undefined   // Lấy lỗi của 1 field
form.setFormError(name: string, error: string): void  // Set lỗi cho 1 field

// Reset
form.reset(): void  // Reset form về trạng thái ban đầu

🎨 Events

<DynamicForm 
  :form="form"
  @submit="onSubmit"              
  @value-change="onValueChange"   
  @loading="onLoading"            
  @submit-result="onSubmitResult" 
/>

Event handlers

function onSubmit(values: Record<string, unknown>) {
  // Được gọi khi form submit và validation pass
  console.log('Submit data:', values)
}

function onValueChange(values: Record<string, unknown>) {
  // Được gọi mỗi khi có field thay đổi
  console.log('Current form values:', values)
}

function onLoading(isLoading: boolean) {
  // Trạng thái loading khi submit
  console.log('Loading:', isLoading)
}

function onSubmitResult(result: any) {
  // Kết quả sau khi submit (nếu có saulFunctionName)
  console.log('Result:', result)
}

🎛️ Field Configuration

FormField Interface

interface FormField {
  name: string                    // Tên field (unique)
  label?: string                  // Label hiển thị
  type: string                    // Component type (VD: 'FInput', 'FButton')
  role?: 'submit' | 'reset' | 'field'  // Vai trò của field
  required?: boolean              // Bắt buộc
  disabled?: boolean              // Disable
  required_text?: string          // Thông báo lỗi khi required
  props: any                      // Props truyền cho component
  error?: string                  // Error message (readonly)
  classes?: string                // CSS classes
  value?: any                     // Giá trị hiện tại
  validation_rules?: ValidationRule[]  // Quy tắc validation
}

Ví dụ Field types

// Input
{
  name: 'username',
  type: 'FInput',
  props: { placeholder: 'Enter username', type: 'text' }
}

// Button
{
  name: 'submit',
  type: 'FButton',
  role: 'submit',
  props: { label: 'Submit', order: 'primary', size: 'large' }
}

// Select (tùy component library)
{
  name: 'country',
  type: 'FSelect',
  props: { 
    options: [
      { label: 'Vietnam', value: 'vn' },
      { label: 'USA', value: 'us' }
    ]
  }
}

🎨 Custom Styling

CSS Classes

.dymik-form {
  // Form container
  
  &.layout-mode {
    // Grid layout mode
    display: grid;
    grid-template-columns: repeat(var(--columns), 1fr);
    gap: 1rem;
  }
  
  .field {
    // Field wrapper
    
    label {
      .required {
        color: red;
      }
    }
    
    .error {
      color: red;
      font-size: 0.875rem;
    }
  }
  
  .field-group {
    // Nested fields container
    
    .nested-fields {
      display: flex;
      gap: 0.5rem;
    }
  }
}

Custom CSS Classes

const form = new FormModel({
  name: 'my-form',
  css_classes: 'custom-form theme-dark', // Form-level classes
  fields: [
    {
      name: 'email',
      classes: 'highlighted-field',      // Field-level classes
      // ...
    }
  ]
})

🔌 Tích hợp với UI Components

Dynamic Form hoàn toàn component-agnostic. Bạn có thể sử dụng với bất kỳ UI library nào:

// Với FIDT UI Components
{
  type: 'FInput',
  props: { placeholder: 'Enter text' }
}

// Với Element Plus
{
  type: 'el-input',
  props: { placeholder: 'Enter text' }
}

// Với Ant Design Vue
{
  type: 'a-input',
  props: { placeholder: 'Enter text' }
}

// Hoặc custom component của bạn
{
  type: 'MyCustomInput',
  props: { /* custom props */ }
}

Lưu ý: Component phải hỗ trợ v-model và emit value-change event.

📚 Ví dụ hoàn chỉnh

Xem thêm các ví dụ trong thư mục examples/:

🤝 Contributing

Contributions, issues và feature requests đều được chào đón!

📝 License

MIT © fidt