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

@mmstack/form-adapters

v21.0.0

Published

Provides a collection of **headless, reusable state adapters** for common form field types. Built upon [@mmstack/form-core](https://www.npmjs.com/package/@mmstack/form-core) and integrating with [@mmstack/form-validation](https://www.npmjs.com/package/@mm

Readme

@mmstack/form-adapters

Provides a collection of headless, reusable state adapters for common form field types. Built upon @mmstack/form-core and integrating with @mmstack/form-validation, this library allows you to define reactive form state logic independently from any specific UI component library.

npm version License

Use these adapters to manage state for inputs like text fields, selects, date pickers, checkboxes, etc., and then bind that state to your chosen UI components. They are extended & re-exported by specific ui libraries like @mmstack/form-material.

Installation

npm install @mmstack/form-adapters @mmstack/form-core @mmstack/form-validation

Core Concept: State Adapters

Each "adapter" represents the complete reactive state for a specific type of form field. It bundles the core signals (value, error, disabled, touched, label, hint etc.) from @mmstack/form-core's FormControlSignal with additional signals, properties, and helper functions relevant to that field type (e.g., placeholder for text, options for select, step for number).

Key characteristics:

  • XState Type: Each adapter exports a specific type (e.g., StringState, SelectState<T>) describing its structure, inheriting from FormControlSignal.
  • type Discriminator: State objects include a type property (e.g., { type: 'string' }, { type: 'select' }) for easy identification in code or templates.
  • Creation Factories: Each adapter provides functions to create instances of its state object, typically with and without relying on Angular's Dependency Injection.

createXState vs injectCreateXState

All adapters offer two ways to create their state object:

createXState(...)

  • Pure function: Does not use Angular's Dependency Injection (DI).
  • Requires manual configuration, including providing a complete validator function via its options if validation is needed.
  • Use Case: Useful when creating state outside an Angular injection context, or when you need absolute control over validator creation without using @mmstack/form-validation's DI features.

injectCreateXState()

  • DI-based: Returns a factory function that uses Angular's DI (specifically injectValidators from @mmstack/form-validation and often LOCALE_ID).
  • Offers a convenient API accepting simplified validation options (e.g., { validation: () => ({ required: true, minLength: 5 }) }) which use the corresponding XxxValidatorOptions type from @mmstack/form-validation.
  • Automatically creates the final validator function using globally configured (and potentially localized) validation rules.
  • May provide enhanced features like error message/tooltip splitting.
  • Use Case: The recommended approach within Angular applications for easy integration with validation and localization.

Available Adapters

This library provides state adapters for various common form field types:

| Adapter Type | State Type Export | Value Type | Key Added Properties/Signals | Creation Functions | | :--------------- | :------------------------------- | :--------------- | :---------------------------------------------------------------------------------------------------------- | :--------------------------------------------------------- | | String | StringState<TParent> | string \| null | placeholder, autocomplete | createStringState, injectCreateStringState | | Textarea | TextareaState<TParent> | string \| null | placeholder, autocomplete, rows, minRows, maxRows | createTextareaState, injectCreateTextareaState | | Autocomplete | AutocompleteState<TParent> | string \| null | placeholder, autocomplete, options (filtered string[]), displayWith | createAutocompleteState, injectCreateAutocompleteState | | Number | NumberState<TParent> | number \| null | placeholder, step, localizedValue, setLocalizedValue, inputType, keydownHandler | createNumberState, injectCreateNumberState | | Date | DateState<TParent, TDate> | TDate \| null | placeholder, min, max | createDateState, injectCreateDateState | | Time | TimeState<TParent, TDate> | TDate \| null | placeholder, min, max | createTimeState, injectCreateTimeState | | DateTime | DateTimeState<TParent, TDate> | TDate \| null | placeholder, min, max, timeControl, dateControl | createDateTimeState, injectCreateDateTimeState | | Boolean | BooleanState<TParent> | boolean | (Type discriminator) | createBooleanState, injectCreateBooleanState | | Toggle | ToggleState<TParent> | boolean | (Type discriminator) | createToggleState, injectCreateToggleState | | Select | SelectState<T, TParent> | T | placeholder, options (T[]), valueLabel, identify, display, equal | createSelectState, injectCreateSelectState | | Multi-Select | MultiSelectState<T[], TParent> | T[] | placeholder, options (T[number][]), valueLabel (joined), identify, display (elem), equal (elem) | createMultiSelectState, injectCreateMultiSelectState | | Button Group | ButtonGroupState<T, TParent> | T | options (T[]), valueLabel, identify, display, equal (inherits Select minus placeholder) | createButtonGroupState, injectCreateButtonGroupState | | Search | SearchState<T, TParent> | T | placeholder, query, request, identify, displayWith, valueLabel, valueId, onSelected | createSearchState, injectCreateSearchState |

Refer to JSDoc comments for detailed descriptions of state properties and options.

Usage Example (Using injectCreate...)

// Example Factory Provider or Component logic
import { Component, computed, inject, signal, isSignal, type Signal } from '@angular/core';
import { formGroup, derived, type DerivedSignal } from '@mmstack/form-core';
import {
  // Import desired adapter factories and state types
  injectCreateStringState,
  StringState,
  injectCreateNumberState,
  NumberState,
  injectCreateSelectState,
  SelectState,
  injectCreateBooleanState,
  BooleanState,
  // ... import other needed adapter types and factories
} from '@mmstack/form-adapters';
// Validation options come from @mmstack/form-validation
import { StringValidatorOptions, NumberValidatorOptions } from '@mmstack/form-validation';

// Assume Validation is configured globally via provideValidatorConfig

interface Settings {
  notifyByEmail: boolean;
  email: string | null;
  maxItems: number | null;
  defaultView: 'list' | 'grid';
}

// Factory function using injected adapters
function injectSettingsFormState<TParent = undefined>(
  // Can accept raw value, signal, or derived signal
  initialValue: Settings | DerivedSignal<TParent, Settings> = { notifyByEmail: true, email: null, maxItems: 10, defaultView: 'list' },
) {
  // Get injected factories within an injection context
  const createBoolean = injectCreateBooleanState();
  const createString = injectCreateStringState();
  const createNumber = injectCreateNumberState();
  const createSelect = injectCreateSelectState();

  // Ensure we have a WritableSignal (or DerivedSignal) for formGroup
  const settingsSignal = isSignal(initialValue) ? initialValue : signal(initialValue); // Create a signal if raw value passed

  // Create the form group using derived controls
  return formGroup(settingsSignal, {
    // Use derived() to link child state to parent signal properties
    notifyByEmail: createBoolean(derived(settingsSignal, 'notifyByEmail'), {
      label: () => 'Notify by Email',
      // No validation for boolean here
    }),

    // Conditionally validate email based on the notifyByEmail flag
    email: createString(derived(settingsSignal, 'email'), {
      label: () => 'Notification Email',
      // Validation rules depend on another control's value from the PARENT signal
      validation: () => ({
        // Only require email if notifications are enabled
        required: settingsSignal().notifyByEmail, // Read from parent signal
        pattern: settingsSignal().notifyByEmail ? 'email' : undefined,
      }),
    }),

    maxItems: createNumber(derived(settingsSignal, 'maxItems'), {
      label: () => 'Max Items per Page',
      validation: () => ({ required: true, min: 5, max: 100, integer: true }),
    }),

    defaultView: createSelect<'list' | 'grid'>(derived(settingsSignal, 'defaultView'), {
      label: () => 'Default View',
      options: () => ['list', 'grid'],
      display: () => (value) => value === 'list' ? 'List view' : 'Grid view'
      // Add required validation for the select
      validation: () => ({ required: true }),
    }),
  });
}

Validation Integration

The injectCreateXState factories are designed to work seamlessly with @mmstack/form-validation. Pass a validation function in the options object. This function should return the appropriate XxxValidatorOptions object (e.g., StringValidatorOptions, NumberValidatorOptions, ArrayValidatorOptions for MultiSelect). The factory uses the injected validators service to create the final validation logic, respecting global configuration like localization.

// Example: Get factory and configure validation
const createNum = injectCreateNumberState();
const countState = createNum(0, {
  label: () => 'Count',
  // Pass NumberValidatorOptions via the validation function
  validation: () => ({ required: true, min: 0, max: 10 }),
});

Using Adapters with UI Components

This library provides only the reactive state (headless). You need separate UI components (from libraries like @mmstack/form-material, Angular Material itself, PrimeNG, Bootstrap, etc., or your own custom components) to render the actual form fields.

These UI components would typically:

  1. Accept the corresponding XState object as an @Input().
  2. Bind their internal HTML elements (e.g., <input>, <select>, <mat-select>) to the signals and properties provided by the state object (state.value, state.label, state.placeholder, state.disabled, state.error, state.options, etc.).
  3. Call state methods like state.value.set() or state.markAsTouched() in response to user interactions.

SignalErrorValidator Directive

This library also exports SignalErrorValidator, a directive useful for integrating the error() signal from any form state adapter with template-driven forms (ngModel), particularly when using UI component libraries like Angular Material's <mat-form-field> that rely on NgControl's validation status derived from ngModel. Bind the state's error() signal to the [mmSignalError] input on an element that also has ngModel.

<input matInput [(ngModel)]="myStringState.value" [mmSignalError]="myStringState.error()" (blur)="myStringState.markAsTouched()" #myNgModel="ngModel" /> <mat-error>{{ myStringState.error() }}</mat-error>

Refer to the JSDoc for SignalErrorValidator for more details.

Contributing

Contributions, issues, and feature requests are welcome!