Skip to content

Latest commit

 

History

History

README.md

@esheet/renderer

Read-only questionnaire form renderer for eSheet. Renders forms in fill-out mode with conditional visibility logic.

Features

  • ✅ Renders all 19 eSheet field types (reuses @esheet/fields components)
  • ✅ Conditional visibility enforcement (fields/sections hide based on logic rules)
  • ✅ Section nesting with recursive rendering
  • ✅ Initial response pre-fill support
  • ✅ YAML/JSON schema parsing with Zod validation
  • ✅ Ref API for collecting responses
  • ✅ TypeScript-first with full type safety

Installation

npm install @esheet/renderer @esheet/fields @esheet/core

Standalone and Blaze integrations now ship as separate packages:

npm install @esheet/renderer-standalone
npm install @esheet/renderer-blaze

Migration for old subpath imports:

  • @esheet/renderer/standalone -> @esheet/renderer-standalone
  • @esheet/renderer/blaze -> @esheet/renderer-blaze

Usage

Basic Example

import { EsheetRenderer } from '@esheet/renderer';
import type { FormDefinition } from '@esheet/core';

const myForm: FormDefinition = {
  schemaType: 'mieforms-v1.0',
  title: 'Patient Intake',
  fields: [
    {
      id: 'name',
      fieldType: 'text',
      question: 'Full Name',
      required: true,
    },
    {
      id: 'age',
      fieldType: 'text',
      question: 'Age',
    },
  ],
};

function App() {
  return (
    <div className="app-container">
      <EsheetRenderer formData={myForm} />
    </div>
  );
}

With Response Collection

import { useRef } from 'react';
import { EsheetRenderer, type EsheetRendererHandle } from '@esheet/renderer';

function App() {
  const rendererRef = useRef<EsheetRendererHandle>(null);

  const handleSubmit = () => {
    const responses = rendererRef.current?.getResponse();
    console.log('Form responses:', responses);
    // { name: '...', age: '...' }
  };

  return (
    <>
      <EsheetRenderer formData={myForm} ref={rendererRef} />
      <button onClick={handleSubmit}>Submit</button>
    </>
  );
}

With Pre-filled Data

<EsheetRenderer
  formData={myForm}
  initialResponses={{
    name: 'John Doe',
    age: '42',
  }}
/>

With YAML/JSON String

const yamlSchema = `
schemaType: mieforms-v1.0
title: Simple Form
fields:
  - id: q1
    fieldType: text
    question: Your name?
`;

<EsheetRenderer formData={yamlSchema} />;

API

<EsheetRenderer>

Props:

  • formData: FormDefinition | string - Form schema (object, JSON string, or YAML string)
  • initialResponses?: FormResponse - Pre-fill form with initial data
  • className?: string - Additional CSS classes for root container
  • ref?: Ref<EsheetRendererHandle> - Access ref API for collecting responses

Ref API:

interface EsheetRendererHandle {
  getResponse: () => FormResponse;
  getFormStore: () => FormStore;
  getUIStore: () => UIStore;
}

Architecture

EsheetRenderer is a thin wrapper that:

  1. Creates form and UI stores (vanilla Zustand)
  2. Parses and validates input (YAML/JSON → Zod schema check)
  3. Loads definition into store
  4. Sets preview mode (read-only, no editing UI)
  5. Iterates over visible fields via RendererBody
  6. Renders each field via FieldNode (uses @esheet/fields components)

Conditional Logic:

  • Reuses form.isVisible(), form.isEnabled(), form.isRequired() from core
  • Sections auto-hide when all children are invisible
  • Field visibility updates reactively when responses change

Section Nesting:

  • FieldNode recursively renders section children
  • Each depth level adds left border and padding
  • Respects visibility rules at every level

Example: Conditional Visibility

const conditionalForm: FormDefinition = {
  schemaType: 'mieforms-v1.0',
  title: 'Conditional Form',
  fields: [
    {
      id: 'hasAllergies',
      fieldType: 'boolean',
      question: 'Do you have any allergies?',
    },
    {
      id: 'allergyList',
      fieldType: 'longtext',
      question: 'Please list your allergies',
      visible: {
        conditions: [
          {
            conditionType: 'comparison',
            fieldId: 'hasAllergies',
            operator: '==',
            value: true,
          },
        ],
        logicalOperator: 'AND',
      },
    },
  ],
};

// "allergyList" only shows when "hasAllergies" is checked
<EsheetRenderer formData={conditionalForm} />;

CSS Architecture

The renderer uses Tailwind CSS v4 with ms: prefix. CSS is compiled via @tailwindcss/cli and embedded into the JS bundle at build time — consumers never need to import a stylesheet. A scoped reset on .esheet-renderer-root prevents style leakage in either direction. Dark mode is supported via .dark class on the root.

License

MIT

Building

Run nx build @esheet/renderer to build the library.

Running unit tests

Run nx test @esheet/renderer to execute the unit tests via Vitest.