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

@ankurah/react-forms

v0.5.0

Published

Form components for Ankurah entities with overlay-based staged edits

Readme

@ankurah/react-forms

Form components for creating and editing Ankurah entities with overlay-based staged edits.

Installation

npm install @ankurah/react-forms

Setup

Initialize the library with your Ankurah context at app startup:

import { ctx } from "your-wasm-bindings"
import { initAnkurahForms, setUIComponents } from "@ankurah/react-forms"

// Required: Connect to Ankurah
initAnkurahForms({ getContext: () => ctx() })

// Optional: Use your UI library components (e.g., shadcn/ui)
import { Input, Button, Label, Select, SelectTrigger, SelectContent, SelectItem, SelectValue } from "@/components/ui"

setUIComponents({ Input, Button, Label, Select, SelectTrigger, SelectContent, SelectItem, SelectValue })

How It Works

The library uses an overlay model for staged edits:

  • overlay starts empty - only contains fields the user has edited
  • Display value = overlay[field] ?? view[field]
  • Dirty = field is in overlay and differs from view
  • When the view updates remotely, untouched fields show new values
  • When the view updates to match an overlay value, that overlay entry is removed
  • On save, only dirty fields are applied to the view

Usage

Edit Mode

import { EntityForm, Field, Submit, SaveError } from "@ankurah/react-forms"

function CustomerEditor({ customerView }) {
  return (
    <EntityForm view={customerView} onSuccess={() => navigate('/customers')}>
      <Field name="name" label="Name" />
      <Field name="email" label="Email" type="email" />
      <SaveError />
      <Submit>Save</Submit>
    </EntityForm>
  )
}

Create Mode

<EntityForm model={Customer} onCreate={(view) => navigate(`/customers/${view.id}`)}>
  <Field name="name" label="Name" />
  <Field name="email" label="Email" type="email" />
  <Submit>Create</Submit>
</EntityForm>

With Custom Layout

Field components use React context, so nest any layout between EntityForm and Field:

<EntityForm view={customer}>
  <Card>
    <CardContent className="grid grid-cols-2 gap-4">
      <Field name="name" label="Name" />
      <Field name="email" label="Email" type="email" />
    </CardContent>
  </Card>
  <SaveError />
  <Submit>Save</Submit>
</EntityForm>

Edit Triggers

Control how edit mode is entered with the editTrigger prop (mode="rw" only):

// "field" (default): clicking any field enters edit mode
<EntityForm view={customer} editTrigger="field">

// "form": clicking anywhere in the form enters edit mode
<EntityForm view={customer} editTrigger="form">

// null: only via EditTrigger button
<EntityForm view={customer} editTrigger={null}>
  <div className="flex items-center justify-between">
    <h2>Customer Info</h2>
    <EditTrigger><Pencil className="w-4 h-4" /></EditTrigger>
  </div>
  <Field name="name" label="Name" />
</EntityForm>

Field Types

<Field name="name" label="Name" type="text" />
<Field name="email" label="Email" type="email" />
<Field name="phone" label="Phone" type="tel" />
<Field name="website" label="Website" type="url" />
<Field name="password" label="Password" type="password" />
<Field name="age" label="Age" type="number" />
<Field name="bio" label="Bio" type="textarea" />
<Field name="active" label="Active" type="checkbox" />
<Field name="status" label="Status" type="select" options={[
  { value: "active", label: "Active" },
  { value: "inactive", label: "Inactive" },
]} />

Icons

Fields can display with an icon:

import { Mail, Phone } from "lucide-react"

<Field name="email" label="Email" type="email" icon={<Mail className="w-4 h-4" />} />
<Field name="phone" label="Phone" type="tel" icon={<Phone className="w-4 h-4" />} />

Save Errors

// Default error rendering
<SaveError />

// Custom message (for layout-specific messaging)
<SaveError>We could not save your changes</SaveError>

Conditional Rendering

<EntityForm view={customer}>
  <ViewOnly>
    {/* Only shown when not editing */}
    <div className="text-lg font-bold">{customer.name}</div>
  </ViewOnly>
  <EditOnly>
    {/* Only shown when editing */}
    <Field name="name" label="Name" />
  </EditOnly>
</EntityForm>

Using the Edit State Hook

function CustomComponent() {
  const { editing, isNew, formMode } = useEditing()

  return (
    <div>
      {isNew ? "Creating new" : "Editing"}
      {editing ? " (active)" : " (viewing)"}
      {formMode === "r" ? " (read-only)" : null}
    </div>
  )
}

API Reference

Components

| Component | Description | |-----------|-------------| | EntityForm | Form wrapper with overlay and transaction handling | | Field | Auto-rendering field with dirty styling | | Submit | Submit button, disabled when no dirty fields | | SaveError | Displays save errors, auto-clears on edit | | ViewOnly | Renders children only in view mode | | EditOnly | Renders children only in edit mode | | EditTrigger | Button to activate edit mode |

Functions

| Function | Description | |----------|-------------| | initAnkurahForms(deps) | Initialize with Ankurah context | | setUIComponents(components) | Configure UI components | | useEditing() | Hook to access editing state from context |

EntityForm Props

| Prop | Type | Description | |------|------|-------------| | view | EditableView | Existing view for edit mode | | model | ModelClass | Model class for create mode | | defaultValues | Record<string, any> | Default values for create mode | | mode | "r" \| "rw" \| "w" | Form mode (read-only, view+edit, or write-only) | | editTrigger | "field" \| "form" \| null | How edit mode is entered (mode="rw" only) | | onStartEditing | () => void | Called when edit mode is entered | | onStopEditing | () => void | Called when edit mode is exited | | submitTimeoutMs | number | Max time to wait for save before error (ms), 0 disables | | onCreate | (view) => void | Called after successful create | | onSuccess | () => void | Called after successful edit | | onError | (error) => void | Called on error |

Field Props

| Prop | Type | Description | |------|------|-------------| | name | string | Field name (must match entity property) | | label | string | Label text | | type | FieldType | Input type (default: "text") | | placeholder | string | Placeholder text (edit mode, defaults to emptyText) | | emptyText | string | Text shown in view mode when value is empty (defaults to placeholder) | | options | SelectOption[] | Options for select type | | disabled | boolean | Disable the field | | icon | ReactNode | Icon to display | | className | string | Class for field wrapper | | labelClassName | string | Class for label element |

Styling

Field components render data attributes for styling:

| Attribute | Description | |-----------|-------------| | data-field | Present on all field wrappers | | data-field-type | The field type (text, email, select, etc.) | | data-dirty | Present when field has unsaved changes | | data-error | Present when field matches a save error | | data-editing | Present when form is in edit mode |

Example CSS:

/* Field wrapper layout */
[data-field] {
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
}

/* Dirty field styling */
[data-field][data-dirty] input {
  border-color: orange;
}

/* Error field styling */
[data-field][data-error] input {
  border-color: red;
}

/* View mode - borderless inputs */
[data-field]:not([data-editing]) input {
  border-color: transparent;
  background-color: transparent;
}

License

MIT