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

@infeya/data-model

v1.1.0

Published

Enterprise-grade declarative form and multi-step wizard orchestration engine for React

Readme

@infeya/data-model

Enterprise-grade declarative form and multi-step wizard orchestration engine for React.

npm version License: MIT TypeScript Declarations Included

@infeya/data-model is an all-in-one UI engine that simplifies building complex form and wizard flows in modern React applications. It unifies modal/offcanvas/inline layouts, per-step isolated data stores, multi-stage validation strategies, deterministic asynchronous action pipelines (with live progress tracking, abort capability, and timeout guards), and pluggable design-system adapters (Bootstrap 5, Tailwind CSS, and Material UI).


📑 Table of Contents


📦 Installation

Install via npm:

npm install @infeya/data-model @infeya/api-center

Or via yarn:

yarn add @infeya/data-model @infeya/api-center

Or via pnpm:

pnpm add @infeya/data-model @infeya/api-center

Peer Dependencies: Ensure react and react-dom (>= 16.8.0, 17.x, 18.x, or 19.x) are installed in your project.


✨ Core Highlights

  • 🚀 Declarative Orchestration: Define complex UI forms, multi-step wizards, and drawers using clean, declarative JavaScript objects.
  • 🪝 Modern React Hooks: Full functional component support via useDataModel() and useDataModelForm().
  • 🔀 Conditional Step Branching: Dynamically show, hide, or skip wizard steps based on real-time form data using when: (formData) => boolean.
  • 🛡️ Async Step Transition Guards: Protect step advances with beforeNext: async (stepData) => ... (e.g. server verification, availability checks).
  • 🗂️ Multi-Step Wizards: Built-in support for linear, open, and locked navigation modes (STEP_NAV_MODE), step skipping, step validation strategies, and automatic review/overview summary screens.
  • ⚡ Zero-Bloat Async Validation Engine: First-class async schema validation with auto-detection for Zod, Yup, Joi, Valibot (Standard Schema v1), and Ajv.
  • ♿ Accessibility & Keyboard Navigation: Native Escape key close support, configurable backdrop modes, and standard ARIA dialog roles.
  • 🛡️ Fail-Safe Action Pipeline: Deterministic 4-stage execution (beforeProcess $\rightarrow$ API $\rightarrow$ Custom logic $\rightarrow$ afterProcess) with automatic timeout guards and double-click submission locking.
  • 🛑 Native Abort Signal Cancellation: In-flight network request cancellation powered by @infeya/api-center.
  • 📊 Real-Time Progress Tracking: Track upload/download and action progress with animated progress bars and button loaders.
  • 📐 3 Flexible Layouts: Render inline directly on a page, inside a Modal dialog, or as an Offcanvas slide-out drawer.
  • 🔌 Decoupled & Pluggable UI: Built-in support for Bootstrap 5, Tailwind CSS, and Material UI (MUI), plus full support for custom CSS class tokens.

🏗️ Architectural Overview

graph TD
    A[Component Props & Configuration] --> B[DataModel Orchestrator]
    B --> C[DataModelProvider Context]
    C --> D1[StepController / StepRegistry]
    C --> D2[StepDataStore]
    C --> D3[ValidationRunner]
    C --> D4[ActionResolver]
    C --> D5[OpenCloseController]
    C --> D6[ProgressController]
    
    D1 --> E[Layout Resolver]
    D2 --> E
    D3 --> E
    D4 --> E
    D5 --> E
    D6 --> E
    
    E --> F1[InlineLayout]
    E --> F2[ModalLayout]
    E --> F3[OffcanvasLayout]
    
    F1 --> G[Body Renderer]
    F2 --> G
    F3 --> G
    
    G --> H1[Single FormRenderer]
    G --> H2[StepRenderer Wizard]
    
    H1 --> I[UI Theme Adapter]
    H2 --> I
    I --> J1[Bootstrap 5]
    I --> J2[Tailwind CSS]
    I --> J3[Material UI]
    I --> J4[Custom Theme]

🚀 Quick Start

1. Basic Modal Form

import React, { useState } from 'react';
import Datamodel, { LAYOUT_TYPES } from '@infeya/data-model';

// Form body component using standard React class
class UserForm extends React.Component {
  state = {
    name: this.props.data?.name || '',
    email: this.props.data?.email || '',
  };

  getForm() {
    return this.state;
  }

  setErrors(errors) {
    this.setState({ errors });
  }

  reset() {
    this.setState({ name: '', email: '' });
  }

  render() {
    return (
      <div>
        <div className="mb-3">
          <label className="form-label">Name</label>
          <input
            className="form-control"
            value={this.state.name}
            onChange={(e) => this.setState({ name: e.target.value })}
          />
        </div>
        <div className="mb-3">
          <label className="form-label">Email</label>
          <input
            className="form-control"
            value={this.state.email}
            onChange={(e) => this.setState({ email: e.target.value })}
          />
        </div>
      </div>
    );
  }
}

export default function App() {
  const [open, setOpen] = useState(false);

  return (
    <div className="p-4">
      <button className="btn btn-primary" onClick={() => setOpen(true)}>
        Open Form
      </button>

      <Datamodel
        name="Create User"
        type={LAYOUT_TYPES.MODAL}
        open={open}
        onClose={() => setOpen(false)}
        body={UserForm}
        action={{
          api: {
            route: '/api/v1/users',
            method: 'POST',
          },
          onComplete: (result) => {
            console.log('Saved successfully:', result);
          },
        }}
      />
    </div>
  );
}

2. Modern Functional Form Body (useDataModelForm)

The useDataModelForm hook bridges functional components directly into DataModel's validation and submission lifecycle:

import React from 'react';
import Datamodel, { LAYOUT_TYPES, useDataModelForm } from '@infeya/data-model';

function UserFormBody({ data }) {
  const { values, handleChange, errors } = useDataModelForm({
    initialValues: data,
  });

  return (
    <div>
      <div className="mb-3">
        <label className="form-label">Username</label>
        <input
          name="username"
          className="form-control"
          value={values.username || ''}
          onChange={handleChange}
        />
        {errors.username && (
          <div className="text-danger small mt-1">{errors.username[0]}</div>
        )}
      </div>

      <div className="mb-3">
        <label className="form-label">Email</label>
        <input
          name="email"
          type="email"
          className="form-control"
          value={values.email || ''}
          onChange={handleChange}
        />
        {errors.email && (
          <div className="text-danger small mt-1">{errors.email[0]}</div>
        )}
      </div>
    </div>
  );
}

3. Enterprise Multi-Step Wizard

import React, { useState } from 'react';
import Datamodel, {
  LAYOUT_TYPES,
  STEP_MODES,
  STEP_VALIDATION_MODE,
  STEP_NAV_MODE,
} from '@infeya/data-model';
import { z } from 'zod';

const personalSchema = z.object({
  fullName: z.string().min(2, 'Name is required'),
  email: z.string().email('Invalid email address'),
});

const companySchema = z.object({
  companyName: z.string().min(2, 'Company name is required'),
  taxId: z.string().min(5, 'Valid Tax ID required'),
});

export default function OnboardingWizard() {
  const [open, setOpen] = useState(true);

  return (
    <Datamodel
      name="Business Onboarding"
      type={LAYOUT_TYPES.MODAL}
      mode={STEP_MODES.STEP}
      size="lg"
      open={open}
      onClose={() => setOpen(false)}
      steps={{
        nav: {
          mode: STEP_NAV_MODE.LINEAR, // Enforce strict sequential completion
        },
        validation: {
          mode: STEP_VALIDATION_MODE.ON_NEXT, // Validate before allowing Next step
        },
        overview: {
          enabled: true,
          title: 'Review & Confirm',
        },
        steps: [
          {
            id: 'personal',
            title: 'Account Holder',
            body: PersonalStepForm,
            validate: {
              schema: personalSchema,
            },
            // Async server guard check before advancing to next step
            beforeNext: async (stepData, { abortSignal }) => {
              const res = await checkEmailAvailability(stepData.email, { signal: abortSignal });
              if (!res.isAvailable) {
                return { valid: false, message: 'This email is already registered.' };
              }
              return true;
            },
          },
          {
            id: 'company',
            title: 'Company Details',
            body: CompanyStepForm,
            validate: {
              schema: companySchema,
            },
            // Dynamically show this step only for business accounts
            when: (formData) => formData.accountType === 'business',
          },
          {
            id: 'preferences',
            title: 'Preferences',
            body: PreferencesStepForm,
            skip: true, // Allow user to skip this step
          },
        ],
      }}
      action={{
        beforeProcess: async (payload) => ({
          ...payload,
          submittedAt: new Date().toISOString(),
        }),
        custom: async (payload, { onProgress }) => {
          onProgress({ progress: 50 });
          const response = await submitRegistration(payload);
          onProgress({ progress: 100 });
          return response;
        },
        onComplete: (result) => {
          alert('Onboarding complete!');
        },
      }}
    />
  );
}

⚙️ Props API Reference

<Datamodel /> Props

| Prop | Type | Default | Description | | :--- | :--- | :--- | :--- | | name | string \| ReactNode | undefined | Header title string or custom React element displayed in title bars. | | type | 'inline' \| 'modal' \| 'offcanvas' | 'inline' | Layout container format. | | mode | 'single' \| 'step' | 'single' | Orchestration mode: single form body vs multi-step wizard. | | size | 'sm' \| 'md' \| 'lg' \| 'xl' | 'md' | Modal dialog width size. | | placement | 'start' \| 'end' \| 'top' \| 'bottom' | 'end' | Offcanvas slide-in direction. | | open | boolean | false | Controls open/visible state for Modal or Offcanvas. | | onClose | (result?: any) => void | undefined | Callback invoked when modal/drawer closes. | | onOpen | () => void | undefined | Callback invoked when modal/drawer opens. | | closeButton | boolean | true | Whether to show the header '✕' close icon button. | | body | React.ComponentType | undefined | Form body component used when mode="single". | | bodyData | object | {} | Initial data injected into the form / step repository. | | bodyProps | object | {} | Additional props passed directly into the body component. | | steps | object \| array | null | Step engine configuration object or array for mode="step". | | validate | object \| Function | null | Validation schema or custom validation function for single form mode. | | action | object \| Function | null | Action execution pipeline configuration or custom async submit function. | | buttons | object \| boolean | { layout: 'end' } | Footer button configuration; pass false to suppress all footer buttons. | | resetAfterSubmit| boolean | true | Automatically reset form inputs and step stores after successful action. | | reset | boolean | false | Reset form state on mount. | | header | boolean \| ReactNode | true | Pass false to completely hide header bar. | | headerRender | (props) => ReactNode | undefined | Render-prop for 100% custom header UI. | | headerComponent| React.ComponentType | undefined | Custom React component replacing header bar. | | footer | boolean \| ReactNode | true | Pass false to completely hide footer bar. | | footerRender | (props) => ReactNode | undefined | Render-prop for 100% custom footer UI. | | footerComponent| React.ComponentType | undefined | Custom React component replacing footer bar. | | uiAdapter | 'bootstrap' \| 'tailwind' \| 'mui' \| object | 'bootstrap' | UI design token adapter or custom CSS class override object. | | modalComponent | React.ComponentType | Built-in | Custom Modal container override component. | | offcanvasComponent| React.ComponentType| Built-in | Custom Offcanvas container override component. | | backdrop | boolean \| 'static' | 'static' (modal) / false (offcanvas) | Overlay behavior. 'static' prevents dismiss on outside click. | | keyboard | boolean | true | Closes modal/offcanvas when pressing Escape. | | hasPermission | (permission: string) => boolean | undefined | Global permission gate function evaluating step permission flags. |


Buttons Configuration Object (buttons)

The buttons prop controls action button visibility, layouts, labels, and confirmations:

buttons: {
  // Placement layout
  layout: 'between', // 'start' | 'center' | 'end' | 'between' | 'around' | 'block' | 'stack' | 'row'

  // Submit / Primary action button
  actionButton: {
    text: 'Submit Application',
    className: 'btn-success',
    variant: 'primary',
    confirmation: true, // or confirmation options object
    timeout: 15000,
    hidden: false,
    disabled: false,
  },

  // Cancel button
  cancelButton: {
    text: 'Discard',
    className: 'btn-outline-secondary',
    hidden: false,
  },

  // Step wizard Next button
  nextButton: {
    text: 'Continue →',
  },

  // Step wizard Back button
  backButton: {
    text: '← Back',
  },

  // Step wizard Skip button
  skipButton: {
    text: 'Skip Step',
  },

  // Custom secondary buttons
  custom: [
    {
      id: 'draft',
      text: 'Save Draft',
      variant: 'secondary',
      onClick: async (context) => {
        const data = context.getFormData();
        await saveDraftToLocalStorage(data);
      },
    },
  ],
}

Step System Configuration (steps)

steps: {
  // Step navigation rules
  nav: {
    mode: 'linear', // 'linear' | 'open' | 'locked'
    canNavigate: ({ fromIndex, toIndex, steps, stepState, source }) => boolean,
  },

  // Validation execution timing
  validation: {
    mode: 'onNext', // 'onSubmit' | 'onNext' | 'onJump' | 'strict' | 'none'
  },

  // Automatic summary / review screen
  overview: {
    enabled: true,
    title: 'Review Summary',
    body: CustomOverviewComponent, // Optional custom summary renderer
  },

  backHide: false, // Hide back button on wizard
  nextHide: false, // Hide next button on wizard

  // Array of step definitions
  steps: [
    /* Step definitions */
  ],
}

Individual Step Definition Schema

| Property | Type | Default | Description | | :--- | :--- | :--- | :--- | | id | string | (Required) | Unique identifier string for the step. | | title | string | (Required) | Title displayed in tabs, headers, and overview cards. | | body | React.ComponentType | undefined | React component rendered as the body of this step. | | bodyProps | object | {} | Props passed directly to the step body component. | | when | (formData, context) => boolean | undefined | Dynamic conditional predicate. Return false to omit step. | | visible | boolean \| Function | true | Static or function determining step visibility. | | hidden | boolean | false | Explicitly hides step if true. | | permission | string | undefined | Permission key evaluated via hasPermission prop. | | skip | boolean | false | When true, displays a "Skip" button allowing users to bypass this step. | | validate | object | undefined | Step-level validation schema: { schema, adapter, options }. | | beforeNext | async (stepData, runtime) => boolean \| object | undefined | Async transition guard before advancing. Return false or { valid: false, message } to block. | | beforeBack | async (stepData, runtime) => boolean | undefined | Async transition guard before navigating backwards. |


🪝 React Hooks API

useDataModel()

Exposes full imperative controls and reactive state to any component rendered inside <Datamodel> or <DataModelProvider>:

import { useDataModel } from '@infeya/data-model';

function CustomStepSummary() {
  const {
    // Lifecycle
    open,
    openModal,
    close,
    toggleModal,

    // Step state
    isStepMode,
    currentStep,
    currentStepId,
    currentStepIndex,
    steps,
    goToStep,
    next,
    back,

    // Form data store
    getFormData,
    setFormData,
    getStepData,
    setStepData,
    syncFormData,

    // Actions & progress
    runAction,
    activeAction,
    isSubmitting,
    progress,
    abort,

    // UI
    ui,
  } = useDataModel();

  const allData = getFormData();

  return (
    <div>
      <h4>Review for {allData.fullName}</h4>
      <p>Current Step: {currentStep?.title} ({currentStepIndex + 1}/{steps.length})</p>
      {isSubmitting && <p>Submitting... ({progress}%)</p>}
      <button onClick={() => goToStep('personal')}>Edit Personal Info</button>
    </div>
  );
}

useDataModelForm(options)

Bridges React function component form state with DataModel's FormController:

import { useDataModelForm } from '@infeya/data-model';

function ProfileForm() {
  const {
    values,         // Current form state object { [fieldName]: any }
    setValues,      // (newValues) => void (bulk replace)
    setFieldValue,  // (name, value) => void (single field updater)
    handleChange,   // (event) => void (standard HTML input change handler)
    errors,         // Current error map { [fieldName]: string[] }
    setErrors,      // (errorMap) => void (custom error setter)
    clearErrors,    // () => void (clears all validation errors)
    reset,          // () => void (resets to initial values)
    isSubmitting,   // boolean
    context,        // Underlying DataModel context object
  } = useDataModelForm({
    initialValues: { username: '', bio: '' },
    onChange: (values) => console.log('Values changed:', values),
  });

  return (
    <form>
      <input name="username" value={values.username} onChange={handleChange} />
      {errors.username && <span className="text-danger">{errors.username[0]}</span>}
    </form>
  );
}

🔀 Step Navigation & Policies

Navigation Modes (STEP_NAV_MODE)

| Mode | Identifier | Behavior | | :--- | :--- | :--- | | LINEAR | 'linear' | Default. Enforces strict sequential step progression. Users cannot jump ahead until preceding steps are validated and completed. Backwards navigation is always permitted. | | OPEN | 'open' | Allows arbitrary direct jumping to any step (via tab clicks or progress items) at any time. | | LOCKED | 'locked' | Direct tab clicks are completely disabled. Navigation is only permitted programmatically or via "Next" / "Back" action buttons. | | CUSTOM | 'custom' | Fully driven by custom canNavigate hook. |

<Datamodel
  mode="step"
  steps={{
    nav: {
      mode: STEP_NAV_MODE.LINEAR,
      // Optional fine-grained navigation gate
      canNavigate: ({ fromIndex, toIndex, steps, stepState, source }) => {
        if (source === 'tab' && toIndex > fromIndex + 1) return false;
        return true;
      },
    },
    steps: [...],
  }}
/>

Step Guards (beforeNext and beforeBack)

Protect transitions with asynchronous verification before navigation takes place:

{
  id: 'shipping_address',
  title: 'Shipping',
  body: ShippingForm,
  beforeNext: async (stepData, { allData, abortSignal, stepController }) => {
    // 1. Return false to simply block navigation:
    // return false;

    // 2. Return validation error object to show toast and highlight input errors:
    const validation = await verifyAddressWithPostalAPI(stepData, { signal: abortSignal });
    if (!validation.valid) {
      return {
        valid: false,
        message: 'Invalid postal address',
        errors: { zipCode: ['Postal code not recognized'] },
      };
    }

    return true; // Proceed to next step
  },
}

Dynamic Conditional Branching (when and visible)

Steps can dynamically appear or disappear in real-time as users fill out prior steps:

{
  id: 'billing_vat',
  title: 'EU VAT Registration',
  body: VatForm,
  // Automatically included only when user is located in the EU:
  when: (formData) => formData.countryCode === 'EU' && formData.isBusiness === true,
}

Automated Overview & Review Screen

DataModel can automatically append an Overview Step (__overview__) summarizing all filled step data before submission:

<Datamodel
  mode="step"
  steps={{
    overview: {
      enabled: true,
      title: 'Review All Information',
      // Optional: override preview body renderer
      body: CustomOverviewSummary,
    },
    steps: [...],
  }}
/>

🛡️ Validation Engine & Adapters

DataModel features a unified validation runner with automatic schema detection for the 5 most popular validation frameworks.

graph LR
    Schema[Validation Schema] --> Detector{detectAdapter}
    Detector -->|safeParse| Zod[Zod Adapter]
    Detector -->|validateSync / isYup| Yup[Yup Adapter]
    Detector -->|~standard| Valibot[Valibot Adapter]
    Detector -->|compiled fn| Ajv[Ajv Adapter]
    Detector -->|validate / isJoi| Joi[Joi Adapter]
    Detector -->|Fallback| Default[Default Adapter]

Validation Timing Modes (STEP_VALIDATION_MODE)

| Mode | Value | Behavior | | :--- | :--- | :--- | | ON_SUBMIT | 'onSubmit' | Default. Validates only when the final Action/Submit button is pressed. | | ON_NEXT | 'onNext' | Validates the current step immediately when clicking the Next button before advancing. | | ON_JUMP | 'onJump' | Validates before jumping across steps via tab clicks. | | STRICT | 'strict' | Validates on every navigation event (Next, Tab, Jump). | | NONE | 'none' | Disables automated validation checks. |


Schema Examples

1. Zod

import { z } from 'zod';

const userSchema = z.object({
  email: z.string().email('Invalid email'),
  age: z.number().min(18, 'Must be at least 18'),
});

<Datamodel validate={{ schema: userSchema }} body={UserForm} />

2. Yup

import * as Yup from 'yup';

const schema = Yup.object().shape({
  username: Yup.string().min(3).required(),
});

<Datamodel validate={{ schema }} body={UserForm} />

3. Valibot (Standard Schema v1)

import * as v from 'valibot';

const schema = v.object({
  title: v.pipe(v.string(), v.minLength(1, 'Title required')),
});

<Datamodel validate={{ schema }} body={ProductForm} />

4. Ajv (JSON Schema)

import Ajv from 'ajv';

const ajv = new Ajv({ allErrors: true });
const schema = ajv.compile({
  type: 'object',
  properties: { sku: { type: 'string', minLength: 4 } },
  required: ['sku'],
});

<Datamodel validate={{ schema }} body={ItemForm} />

5. Joi

import Joi from 'joi';

const schema = Joi.object({
  code: Joi.string().alphanum().min(3).required(),
});

<Datamodel validate={{ schema }} body={CodeForm} />

Heterogeneous Per-Step Wizard Schemas

In a multi-step wizard, different steps can use completely different schema libraries:

<Datamodel
  mode="step"
  steps={{
    steps: [
      { id: 's1', title: 'Step 1', validate: { schema: zodSchema } },
      { id: 's2', title: 'Step 2', validate: { schema: yupSchema } },
      { id: 's3', title: 'Step 3', validate: { schema: joiSchema } },
    ],
  }}
/>

Custom Validation Adapter Registration

Register your own custom validation engine (e.g. Superstruct, TypeBox, or custom logic):

import { registerValidationAdapter } from '@infeya/data-model';

registerValidationAdapter('my-validator', async (schema, data, options) => {
  const result = await schema.run(data);
  if (!result.ok) {
    return { value: data, error: { field: ['Validation failed'] } };
  }
  return { value: result.data, error: null };
});

🔧 Asynchronous Action Pipeline

Actions follow a deterministic, fail-safe 4-stage execution sequence:

graph LR
    A[1. beforeProcess] --> B[2. API Call]
    B --> C[3. Custom Logic]
    C --> D[4. afterProcess]

Action Configuration Properties

action: {
  actionType: 'custom', // 'custom' | 'api' | 'create' | 'update'

  // 1. Stage 1: Payload Enrichment
  beforeProcess: async (payload, { abortSignal }) => {
    return { ...payload, clientTimestamp: Date.now() };
  },

  // 2. Stage 2: Built-in HTTP Transport via @infeya/api-center
  api: {
    route: (payload) => `/api/v1/orders/${payload.orderId || ''}`,
    method: 'POST',
    headers: { 'Authorization': 'Bearer ...' },
    query: { verbose: true },
    body: (payload) => ({ order: payload }),
  },

  // 3. Stage 3: Custom Business Logic
  custom: async (apiResult, { onProgress, abortSignal, stepController }) => {
    onProgress({ progress: 50 });
    const finalData = await processCustomLogic(apiResult);
    onProgress({ progress: 100 });
    return finalData;
  },

  // 4. Stage 4: Result Transformation
  afterProcess: async (result, workingPayload) => {
    return { ...result, formatted: true };
  },

  timeout: 30000,         // Guard timeout in milliseconds (default: 30s)
  showProgress: true,     // Render automated progress bar during execution
  onComplete: (result, formData) => {
    console.log('Action complete:', result);
  },
}

Abort Signal & Request Cancellation

When a modal is closed, cancelled, or timed out, DataModel triggers an AbortSignal automatically. You can forward this signal directly to fetch, axios, or @infeya/api-center:

action: {
  custom: async (formData, { abortSignal }) => {
    const response = await fetch('/api/upload', {
      method: 'POST',
      body: JSON.stringify(formData),
      signal: abortSignal, // 👈 Automatically cancelled if user closes modal!
    });
    return response.json();
  },
}

💬 Confirmation Dialogs & Notifications

SweetAlert2 Confirmation Dialogs

<Datamodel
  buttons={{
    actionButton: {
      text: 'Delete Account',
      confirmation: {
        title: 'Delete Account?',
        text: 'This action cannot be undone. All data will be lost.',
        icon: 'warning',
        confirmButtonText: 'Yes, Delete Permanently',
        cancelButtonText: 'Cancel',
        confirmButtonColor: '#dc2626',
      },
    },
  }}
/>

Custom Global Dialog & Notification Handlers

Connect DataModel directly to your application's notification library (Toastr, Notistack, Sonner, React-Toastify) and confirmation dialog system:

import { setNotificationHandler, setDialogHandler } from '@infeya/data-model';

// Plug in toast notifications
setNotificationHandler((type, message, title) => {
  // type: 'success' | 'error' | 'warning' | 'info'
  myToastLibrary[type](message);
});

// Plug in confirmation dialogs
setDialogHandler(async (options) => {
  // options: { title, text, icon, confirmButtonText, cancelButtonText, ... }
  const confirmed = await myCustomModalDialog(options);
  return { isConfirmed: confirmed };
});

🎨 Multi-Theme UI Adapters (Bootstrap, Tailwind, MUI)

DataModel decouples business logic from presentation. Switch styling across all buttons, modal backdrops, dialogs, drawers, and tabs with zero code changes:

Global Adapter Switch

import { configureDataModel } from '@infeya/data-model';

configureDataModel({
  uiAdapter: 'tailwind', // 'bootstrap' | 'tailwind' | 'mui'
});

Per-Component Theme Override

<Datamodel uiAdapter="tailwind" name="Tailwind Drawer" type="offcanvas" open={isOpen} />
<Datamodel uiAdapter="mui" name="Material Wizard" mode="step" steps={steps} />

Defining Custom UI Design Tokens

import { registerUiAdapter } from '@infeya/data-model';

registerUiAdapter('company-theme', {
  buttons: {
    base: 'font-semibold rounded-xl transition duration-200',
    primary: 'bg-violet-600 hover:bg-violet-700 text-white shadow-md',
    secondary: 'bg-zinc-100 hover:bg-zinc-200 text-zinc-900',
    layouts: {
      end: 'flex justify-end gap-3',
      between: 'flex justify-between items-center',
    },
  },
  modal: {
    backdrop: 'fixed inset-0 bg-black/60 backdrop-blur-sm z-50 flex items-center justify-center p-4',
    content: 'bg-white rounded-2xl shadow-2xl max-w-lg w-full overflow-hidden',
  },
});

📐 Header & Footer Customization

Hide Header or Footer

<Datamodel header={false} footer={false} body={MyBody} />

Custom Render-Props

<Datamodel
  headerRender={({ name, close }) => (
    <div className="p-4 bg-gradient-to-r from-blue-600 to-indigo-600 text-white flex justify-between">
      <h3 className="font-bold">{name}</h3>
      <button onClick={() => close()}>✕</button>
    </div>
  )}
  footerRender={({ close, runAction, isSubmitting }) => (
    <div className="p-4 border-t flex justify-end gap-2">
      <button onClick={() => close()} disabled={isSubmitting}>Cancel</button>
      <button onClick={() => runAction()} disabled={isSubmitting}>Save Changes</button>
    </div>
  )}
/>

🧩 Constants & Enums Reference

import {
  LAYOUT_TYPES,
  STEP_MODES,
  ACTION_TYPES,
  VALIDATION_MODES,
  BUTTON_TYPES,
  BUTTON_LAYOUTS,
  STEP_STATUS,
  NAV_SOURCE,
  STEP_NAV_MODE,
  STEP_VALIDATION_MODE,
} from '@infeya/data-model';
  • LAYOUT_TYPES: INLINE: 'inline', MODAL: 'modal', OFFCANVAS: 'offcanvas'
  • STEP_MODES: SINGLE: 'single', STEP: 'step'
  • BUTTON_LAYOUTS: 'start', 'center', 'end', 'between', 'around', 'block', 'stack', 'row'
  • STEP_NAV_MODE: LINEAR: 'linear', OPEN: 'open', LOCKED: 'locked', CUSTOM: 'custom'
  • STEP_VALIDATION_MODE: ON_SUBMIT: 'onSubmit', ON_NEXT: 'onNext', ON_JUMP: 'onJump', STRICT: 'strict', NONE: 'none'
  • STEP_STATUS: PRISTINE: 'pristine', ACTIVE: 'active', COMPLETED: 'completed', INVALID: 'invalid', SKIPPED: 'skipped'
  • NAV_SOURCE: NEXT: 'next', BACK: 'back', TAB: 'tab', OVERVIEW: 'overview', PROGRAM: 'program'

📘 TypeScript Support

@infeya/data-model includes full TypeScript definitions out of the box:

import Datamodel, { DatamodelProps } from '@infeya/data-model';

interface UserRegistrationData {
  username: string;
  email: string;
  tier: 'free' | 'pro' | 'enterprise';
}

export function RegistrationModal() {
  return (
    <Datamodel<UserRegistrationData>
      name="Register"
      type="modal"
      bodyData={{ username: '', email: '', tier: 'free' }}
      action={{
        custom: async (data) => {
          // data is strictly typed as UserRegistrationData!
          console.log(data.tier);
        },
      }}
    />
  );
}

📄 License

MIT © Infeya