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 🙏

© 2025 – Pkg Stats / Ryan Hefner

extended-dynamic-forms

v0.3.13

Published

Extended React JSON Schema Form (RJSF) v6 with custom components, widgets, templates, layouts, and form events

Downloads

151

Readme

Extended Dynamic Forms

Declarative Configuration (JSON-only)

Conditional logic is configured with pure JSON via x-edf:rules in your schema and uiSchema. Function-based conditions and external hooks are not required and should not be used in app code.

Project Status

Important Notes:

  • Requires RJSF source in sibling directory for development
  • Cannot be installed from npm due to local file dependencies
  • JSON logic conditions don't work with vanilla JavaScript builds
  • Array drag-and-drop is not implemented
  • Automated coverage is provided by logic and snapshot suites; component/UI tests are intentionally omitted
  • JSON Patch implementation supports add/remove/replace only (not move/copy/test)

Overview

Extended Dynamic Forms is an extension library for React JSON Schema Form (RJSF) v6 that adds:

  • Declarative conditionals with O(N) performance (rules + dynamic arrays)
  • Multi-step wizard forms with automatic step detection
  • Central event orchestration with webhook support
  • 25+ Ant Design components for professional UI
  • TypeScript-first development with full type safety

Ant Design Prop Pass-through

All custom widgets built on Ant Design components forward their props via ui:options. For example, Rate, Slider, DatePicker, Input, InputNumber, ColorPicker, and TimePicker can be configured using the same prop names from AntD. In addition, you can customize the containing Form.Item using ui:options.formItem. See docs/custom-widgets-configuration.md for details and examples.

  • DatePicker notes:
    • JSON-only flags: disablePastDates, disableFutureDates, disableWeekends (builds disabledDate internally)
    • Range support: set ui:options.range: true to render AntD RangePicker (see examples below)

Installation

Note: This library currently requires local development setup and cannot be installed via npm due to local file dependencies.

# Clone RJSF as sibling directory (required)
cd ..
git clone https://github.com/rjsf-team/react-jsonschema-form.git

# Setup Extended Dynamic Forms
cd extended-dynamic-forms
npm install
npm run dev

Important: The library is currently designed for internal use or as part of a monorepo setup. Standalone npm package distribution is not yet supported.

Testing

The project uses Vitest with Node environment to cover pure logic and serialized outputs. Component or DOM-level tests are intentionally excluded.

  • Run the full suite: npm test
  • Logic-only focus: npm test -- tests/logic
  • Snapshot updates: npm test -- tests/snapshots -- -u

After pulling dependencies or updating package.json, run npm install (or yarn install) so the lockfile reflects removed packages such as @testing-library/* and jsdom.

Core Architecture

Declarative Conditionals (JSON-only)

Embed rules directly inside your configs. EDF evaluates JsonLogic condition and applies JSON Patch effect internally. No hooks, functions, or classes in user code.

  • UI rules → uiSchema['x-edf:rules']
  • Schema rules → schema['x-edf:rules']

Example (UI rules):

{
  "x-edf:rules": [
    {
      "name": "Show license number when has license",
      "condition": { "==": [{ "var": "hasDriversLicense" }, true] },
      "effect": [{ "op": "remove", "path": "/licenseNumber/ui:widget" }]
    }
  ]
}

Example (Schema rules):

{
  "x-edf:rules": [
    {
      "name": "Add premium fields",
      "condition": { "==": [{ "var": "accountType" }, "premium"] },
      "effect": [
        { "op": "add", "path": "/properties/premiumFeatures", "value": { "type": "object" } }
      ]
    }
  ]
}

Dynamic Arrays with O(N) Performance

Use declarative ui:dynamicItems with per-item JsonLogic. No functions:

{
  "directors": {
    "items": {
      "ui:dynamicItems": {
        "base": { "shareholdingPercentage": { "ui:widget": "hidden" } },
        "rules": [
          {
            "condition": { "===": [{ "var": "hasShareholding" }, true] },
            "ui": { "shareholdingPercentage": { "ui:widget": "updown" } },
            "otherwise": { "shareholdingPercentage": { "ui:widget": "hidden" } }
          }
        ]
      }
    }
  }
}

Multi-Step Wizard Forms

Create wizard forms with automatic step detection:

import { WizardForm } from 'extended-dynamic-forms';

<WizardForm
  schema={wizardSchema}
  uiSchema={uiSchema}
  formData={formData}
  onStepChange={(fromStep, toStep) => {
    console.log(`Step ${fromStep} → ${toStep}`);
  }}
/>

Important: Wizard forms flatten nested step data. Use flat paths in conditions:

// Schema uses STEP_ prefixes
const schema = {
  properties: {
    STEP_PERSONAL: {
      properties: {
        hasDriversLicense: { type: 'boolean' }
      }
    }
  }
};

// Conditions refer to flattened form data (no STEP_ prefix)
// Example rule (in uiSchema.x-edf:rules): { "==": [{ "var": "hasDriversLicense" }, true] }

Note on wizard detection:

  • Wizard mode activates only when the root schema contains two or more properties whose keys start with STEP_.
  • With a single STEP_… property, the form renders as a single-page form (no progress/navigation UI).
  • To display steps UI, define at least two step objects (e.g., STEP_PERSONAL, STEP_REVIEW).

Step Layouts with LayoutGridField

Wizard steps can define their own grid layout using a custom object field (e.g., LayoutGridField). Place the layout at the step key in uiSchema and pass your custom field via fields to WizardForm.

import { WizardForm } from 'extended-dynamic-forms';

const schema = {
  type: 'object',
  properties: {
    STEP_LAYOUT: {
      type: 'object',
      title: 'Personal Info',
      properties: {
        firstName: { type: 'string', title: 'First Name' },
        lastName: { type: 'string', title: 'Last Name' },
        age: { type: 'number', title: 'Age' },
        gender: { type: 'string', enum: ['Male','Female','Other'] },
        favoriteBook: { type: 'string' },
      }
    }
  }
};

const uiSchema = {
  STEP_LAYOUT: {
    'ui:field': 'LayoutGridField',
    'ui:layoutGrid': {
      'ui:row': [
        { 'ui:row': { gutter: [16,16], children: [
          { 'ui:col': { span: 12, children: ['firstName'] } },
          { 'ui:col': { span: 12, children: ['lastName'] } }
        ]}},
        { 'ui:row': { gutter: [16,16], children: [
          { 'ui:col': { span: 24, children: ['age'] } }
        ]}},
        { 'ui:row': { gutter: [16,16], children: [
          { 'ui:col': { span: 24, children: ['gender'] } }
        ]}},
        { 'ui:row': { gutter: [16,16], children: [
          { 'ui:col': { span: 24, children: ['favoriteBook'] } }
        ]}}
      ]
    }
  }
};

// LayoutGridField is included by default in EDF; no extra wiring needed
<WizardForm schema={schema} uiSchema={uiSchema} />

Notes:

  • EDF includes LayoutGridField out of the box. Just set ui:field: 'LayoutGridField' on the step object.
  • EDF variant uses 'ui:layoutGrid' with AntD gutter and 24-grid spans. The RJSF canonical layout ('ui:layout' with 12-grid widths) is also parsed.
  • You can still override or extend via the fields prop if needed.
  • LayoutGridField now writes nested updates immutably using path metadata. This is enabled by default; set ui:options.pathAware: false on a step to fall back to the previous shallow merge behaviour.

Event System & Webhooks

Central event orchestration for all form interactions:

<ExtendedForm
  schema={schema}
  uiSchema={uiSchema}
  webhooks={[{
    url: 'https://api.example.com/events',
    events: ['change', 'blur'],
    debounceMs: 500,
    retries: 3
  }]}
  onFieldChange={(event) => console.log(event.fieldId, event.fieldValue)}
/>

Event Types:

  • focus - Field focus
  • blur - Field blur
  • change - Value changes
  • submit - Form submission
  • stepChange - Wizard navigation
  • validationError - Validation failures

Custom Widgets

All widgets must integrate with the event system:

import { WidgetProps } from '@rjsf/utils';
import { useFieldEventHandlers } from 'extended-dynamic-forms/events';

export const CustomWidget: FC<WidgetProps> = ({ id, value, onChange, schema }) => {
  const fieldEventHandlers = useFieldEventHandlers(id, schema);
  
  const handleChange = async (newValue) => {
    onChange(newValue);  // RJSF update
    await fieldEventHandlers.onChange(newValue);  // Event system
  };
  
  return <Input value={value} onChange={(e) => handleChange(e.target.value)} />;
};

Auto-registered by Default

  • All EDF custom widgets are included by default; no wiring is required.
  • Every widget is also exposed as a field, so you can use either ui:widget or ui:field with the same key.

Examples:

{
  "schema": { "type": "string", "title": "First Name" },
  "uiSchema": { "ui:widget": "text" }
}
{
  "schema": { "type": "string", "title": "First Name" },
  "uiSchema": { "ui:field": "text" }
}

JsonLogic Migration Examples

Common patterns for migrating from function-based conditions:

// Equality
// Before: (formData) => formData.field === "value"
// After:  { "==": [{ "var": "field" }, "value"] }

// Boolean check
// Before: (formData) => formData.isEnabled
// After:  { "var": "isEnabled" }

// AND condition
// Before: (formData) => formData.a && formData.b
// After:  { "and": [{ "var": "a" }, { "var": "b" }] }

// Array contains
// Before: (formData) => formData.roles?.includes("admin")
// After:  { "in": ["admin", { "var": "roles" }] }

Available Widgets

Complete Widget List

Choice Widget (Unified Selection)

  • ChoiceWidget - Intelligent widget that automatically adapts based on schema:
    • Renders as dropdown/select for regular choices
    • Renders as radio group when ui:widget is "radio"
    • Renders as checkbox group for multi-select arrays
    • Supports static and dynamic data sources (API, WebSocket)

Text Input Widgets

  • TextWidget - Standard text input with Ant Design styling
  • TextareaWidget - Multi-line text input
  • PasswordWidget - Password input with visibility toggle

Specialized Input Widgets

  • ColorWidget - Color picker using HTML5 color input
  • EmailWidget - Email input with validation
  • URLWidget - URL input with validation
  • TelephoneWidget - Phone number input
  • NumberWidget - Numeric input with increment/decrement controls

Visual Input Widgets

  • RangeWidget / SliderWidget - Slider for numeric ranges
  • RatingWidget - Star rating component

Date & Time Widgets

  • DateWidget / EnhancedDateWidget - Date picker with Ant Design DatePicker
  • DateTimeWidget - Combined date and time picker

File Upload Widgets

  • FileWidget - Basic file input
  • FileUploadWidget - Enhanced upload with progress, preview, and base64 support

Widget Configuration Examples

Basic Text Input

{
  "schema": {
    "type": "string",
    "title": "Full Name"
  },
  "uiSchema": {
    "ui:widget": "text",
    "ui:placeholder": "Enter your full name"
  }
}

Text Input with Variant and Auto-complete

{
  "schema": { "type": "string", "title": "Username" },
  "uiSchema": {
    "ui:widget": "text",
    "ui:options": {
      "variant": "outlined",
      "autoFocus": true,
      "autoComplete": "username",
      "allowClear": true
    }
  }
}

Date Range (RangePicker)

{
  "schema": {
    "type": "array",
    "title": "Date Range",
    "items": { "type": "string" },
    "minItems": 2,
    "maxItems": 2
  },
  "uiSchema": {
    "ui:widget": "date",
    "ui:options": {
      "range": true,
      "format": "YYYY-MM-DD",
      "placeholder": ["Start date", "End date"],
      "picker": "date"
    }
  }
}

With time selection:

{
  "schema": {
    "type": "array",
    "title": "Date Range With Time",
    "items": { "type": "string" },
    "minItems": 2,
    "maxItems": 2
  },
  "uiSchema": {
    "ui:widget": "date",
    "ui:options": {
      "range": true,
      "showTime": true,
      "format": "YYYY-MM-DD HH:mm",
      "placeholder": ["Start date/time", "End date/time"]
    }
  }
}

Month range:

{
  "schema": {
    "type": "array",
    "title": "Month Range",
    "items": { "type": "string" },
    "minItems": 2,
    "maxItems": 2
  },
  "uiSchema": {
    "ui:widget": "date",
    "ui:options": {
      "range": true,
      "picker": "month",
      "format": "YYYY-MM",
      "placeholder": ["Start month", "End month"]
    }
  }
}

Number Input with Range

{
  "schema": {
    "type": "number",
    "title": "Age",
    "minimum": 0,
    "maximum": 120
  },
  "uiSchema": {
    "ui:widget": "number"
  }
}

Rating Widget

{
  "schema": {
    "type": "number",
    "title": "Rate your experience",
    "minimum": 0,
    "maximum": 5
  },
  "uiSchema": {
    "ui:widget": "rating",
    "ui:options": {
      "count": 5,
      "allowHalf": true
    }
  }
}

Choice Widget (Dropdown)

{
  "schema": {
    "type": "string",
    "title": "Country",
    "enum": ["us", "uk", "au", "ca"],
    "enumNames": ["United States", "United Kingdom", "Australia", "Canada"]
  },
  "uiSchema": {
    "ui:widget": "choice"
  }
}

Choice Widget with AntD props (Dropdown)

{
  "schema": { "type": "string", "title": "Country" },
  "uiSchema": {
    "ui:widget": "choice",
    "ui:options": {
      "choiceConfig": {
        "presentationMode": "dropdown",
        "dataSource": { "type": "static", "options": [
          { "label": "USA", "value": "us" }, { "label": "UK", "value": "uk" }
        ]},
        "antd": { "select": { "dropdownMatchSelectWidth": 280, "placement": "bottomLeft" } }
      }
    }
  }
}

Choice Widget (Radio Group)

{
  "schema": {
    "type": "string",
    "title": "Gender",
    "enum": ["male", "female", "other"]
  },
  "uiSchema": {
    "ui:widget": "choice",
    "ui:options": {
      "presenter": "radio"
    }
  }
}

Choice Widget (Checkbox Group - Multi-select)

{
  "schema": {
    "type": "array",
    "title": "Interests",
    "items": {
      "type": "string",
      "enum": ["sports", "music", "reading", "travel"]
    },
    "uniqueItems": true
  },
  "uiSchema": {
    "ui:widget": "choice",
    "ui:options": {
      "presenter": "checkbox"
    }
  }
}

Date Picker

{
  "schema": {
    "type": "string",
    "title": "Date of Birth",
    "format": "date"
  },
  "uiSchema": {
    "ui:widget": "date",
    "ui:options": {
      "format": "DD/MM/YYYY",
      "picker": "date"
    }
  }
}

File Upload

{
  "schema": {
    "type": "string",
    "title": "Resume",
    "format": "data-url"
  },
  "uiSchema": {
    "ui:widget": "fileUpload",
    "ui:options": {
      "accept": ".pdf,.doc,.docx",
      "maxSize": 5242880,
      "multiple": false
    }
  }
}

File Upload (Ant Upload options)

{
  "schema": { "type": "string", "title": "Profile Picture" },
  "uiSchema": {
    "ui:widget": "fileUpload",
    "ui:options": {
      "useAntUpload": true,
      "listType": "picture-card",
      "showUploadList": { "showPreviewIcon": true, "showRemoveIcon": true },
      "maxCount": 1,
      "accept": "image/*"
    }
  }
}

Color Picker

{
  "schema": {
    "type": "string",
    "title": "Favorite Color",
    "format": "color"
  },
  "uiSchema": {
    "ui:widget": "color"
  }
}

Range/Slider Widget

{
  "schema": {
    "type": "number",
    "title": "Volume",
    "minimum": 0,
    "maximum": 100
  },
  "uiSchema": {
    "ui:widget": "range",
    "ui:options": {
      "marks": {
        "0": "Mute",
        "50": "50%",
        "100": "Max"
      }
    }
  }
}

Hidden Field

{
  "schema": {
    "type": "string",
    "title": "User ID"
  },
  "uiSchema": {
    "ui:widget": "hidden"
  }
}

Widget Registration (optional)

Widgets are auto-registered. You can still override or pass a subset if needed:

import { ExtendedForm, customWidgets } from 'extended-dynamic-forms';

// Override or register specific widgets
<ExtendedForm 
  widgets={{
    text: customWidgets.text,
    date: customWidgets.date,
    choice: customWidgets.choice
  }}
/>

Development

# Development
npm run dev              # Start playground
npm run test             # Run tests
npm run test:ui          # Test with UI

# Building
npm run build            # Build library
npm run build:vanilla    # Build standalone (currently broken - JsonLogic incompatible)
npm run preview          # Preview build

# Code Quality
npm run lint             # ESLint
npm run format           # Prettier

Project Structure

src/
├── components/          # Core form components
├── conditionals/        # Declarative conditional engines (internal)
│   └── v2/             # Current implementation
├── events/             # Event orchestration
├── layouts/            # Layout systems
│   └── wizard/         # Wizard form components
├── widgets/            # Input widgets
└── utils/              # Utilities

Examples

Interactive examples available at /playground/src/demos/:

  • Wizard Forms: Australian Company Onboarding, Employee Onboarding
  • Conditionals: Dynamic visibility, validation, array handling
  • Events: Webhook integration, field tracking
  • Widgets: All available components

Run npm run dev to explore examples.

Known Limitations

  1. Dependencies: Requires RJSF source in sibling directory
  2. Installation: Cannot install from npm
  3. Vanilla JS: JsonLogic not supported in standalone builds
  4. Arrays: No drag-and-drop functionality
  5. JSON Patch: Only supports add/remove/replace operations
  6. Test Coverage: Limited for UI components and events

TypeScript

Full TypeScript support with exported types:

import { ExtendedForm, WizardForm } from 'extended-dynamic-forms';
// Config is JSON-only; rules are embedded via x-edf:rules

Contributing

  1. Fork the repository
  2. Create your feature branch
  3. Add tests for new functionality
  4. Ensure all tests pass
  5. Submit a pull request

License

MIT License - see LICENSE file for details.