@esheet/renderer 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +212 -0
- package/dist/index.d.ts +97 -0
- package/dist/index.js +5112 -0
- package/package.json +53 -0
package/README.md
ADDED
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
# @esheet/renderer
|
|
2
|
+
|
|
3
|
+
Read-only questionnaire form renderer for eSheet. Renders forms in fill-out mode with conditional visibility logic.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- ✅ Renders all 19 eSheet field types (reuses `@esheet/fields` components)
|
|
8
|
+
- ✅ Conditional visibility enforcement (fields/sections hide based on logic rules)
|
|
9
|
+
- ✅ Section nesting with recursive rendering
|
|
10
|
+
- ✅ Initial response pre-fill support
|
|
11
|
+
- ✅ YAML/JSON schema parsing with Zod validation
|
|
12
|
+
- ✅ Ref API for collecting responses
|
|
13
|
+
- ✅ TypeScript-first with full type safety
|
|
14
|
+
|
|
15
|
+
## Installation
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npm install @esheet/renderer @esheet/fields @esheet/core
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Standalone and Blaze integrations now ship as separate packages:
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
npm install @esheet/renderer-standalone
|
|
25
|
+
npm install @esheet/renderer-blaze
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Migration for old subpath imports:
|
|
29
|
+
|
|
30
|
+
- `@esheet/renderer/standalone` -> `@esheet/renderer-standalone`
|
|
31
|
+
- `@esheet/renderer/blaze` -> `@esheet/renderer-blaze`
|
|
32
|
+
|
|
33
|
+
## Usage
|
|
34
|
+
|
|
35
|
+
### Basic Example
|
|
36
|
+
|
|
37
|
+
```tsx
|
|
38
|
+
import { EsheetRenderer } from '@esheet/renderer';
|
|
39
|
+
import type { FormDefinition } from '@esheet/core';
|
|
40
|
+
|
|
41
|
+
const myForm: FormDefinition = {
|
|
42
|
+
schemaType: 'mieforms-v1.0',
|
|
43
|
+
title: 'Patient Intake',
|
|
44
|
+
fields: [
|
|
45
|
+
{
|
|
46
|
+
id: 'name',
|
|
47
|
+
fieldType: 'text',
|
|
48
|
+
question: 'Full Name',
|
|
49
|
+
required: true,
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
id: 'age',
|
|
53
|
+
fieldType: 'text',
|
|
54
|
+
question: 'Age',
|
|
55
|
+
},
|
|
56
|
+
],
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
function App() {
|
|
60
|
+
return (
|
|
61
|
+
<div className="app-container">
|
|
62
|
+
<EsheetRenderer formData={myForm} />
|
|
63
|
+
</div>
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### With Response Collection
|
|
69
|
+
|
|
70
|
+
```tsx
|
|
71
|
+
import { useRef } from 'react';
|
|
72
|
+
import { EsheetRenderer, type EsheetRendererHandle } from '@esheet/renderer';
|
|
73
|
+
|
|
74
|
+
function App() {
|
|
75
|
+
const rendererRef = useRef<EsheetRendererHandle>(null);
|
|
76
|
+
|
|
77
|
+
const handleSubmit = () => {
|
|
78
|
+
const responses = rendererRef.current?.getResponse();
|
|
79
|
+
console.log('Form responses:', responses);
|
|
80
|
+
// { name: '...', age: '...' }
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
return (
|
|
84
|
+
<>
|
|
85
|
+
<EsheetRenderer formData={myForm} ref={rendererRef} />
|
|
86
|
+
<button onClick={handleSubmit}>Submit</button>
|
|
87
|
+
</>
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### With Pre-filled Data
|
|
93
|
+
|
|
94
|
+
```tsx
|
|
95
|
+
<EsheetRenderer
|
|
96
|
+
formData={myForm}
|
|
97
|
+
initialResponses={{
|
|
98
|
+
name: 'John Doe',
|
|
99
|
+
age: '42',
|
|
100
|
+
}}
|
|
101
|
+
/>
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### With YAML/JSON String
|
|
105
|
+
|
|
106
|
+
```tsx
|
|
107
|
+
const yamlSchema = `
|
|
108
|
+
schemaType: mieforms-v1.0
|
|
109
|
+
title: Simple Form
|
|
110
|
+
fields:
|
|
111
|
+
- id: q1
|
|
112
|
+
fieldType: text
|
|
113
|
+
question: Your name?
|
|
114
|
+
`;
|
|
115
|
+
|
|
116
|
+
<EsheetRenderer formData={yamlSchema} />;
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
## API
|
|
120
|
+
|
|
121
|
+
### `<EsheetRenderer>`
|
|
122
|
+
|
|
123
|
+
**Props:**
|
|
124
|
+
|
|
125
|
+
- `formData: FormDefinition | string` - Form schema (object, JSON string, or YAML string)
|
|
126
|
+
- `initialResponses?: FormResponse` - Pre-fill form with initial data
|
|
127
|
+
- `className?: string` - Additional CSS classes for root container
|
|
128
|
+
- `ref?: Ref<EsheetRendererHandle>` - Access ref API for collecting responses
|
|
129
|
+
|
|
130
|
+
**Ref API:**
|
|
131
|
+
|
|
132
|
+
```tsx
|
|
133
|
+
interface EsheetRendererHandle {
|
|
134
|
+
getResponse: () => FormResponse;
|
|
135
|
+
getFormStore: () => FormStore;
|
|
136
|
+
getUIStore: () => UIStore;
|
|
137
|
+
}
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
## Architecture
|
|
141
|
+
|
|
142
|
+
EsheetRenderer is a thin wrapper that:
|
|
143
|
+
|
|
144
|
+
1. Creates form and UI stores (vanilla Zustand)
|
|
145
|
+
2. Parses and validates input (YAML/JSON → Zod schema check)
|
|
146
|
+
3. Loads definition into store
|
|
147
|
+
4. Sets preview mode (read-only, no editing UI)
|
|
148
|
+
5. Iterates over visible fields via `RendererBody`
|
|
149
|
+
6. Renders each field via `FieldNode` (uses `@esheet/fields` components)
|
|
150
|
+
|
|
151
|
+
**Conditional Logic:**
|
|
152
|
+
|
|
153
|
+
- Reuses `form.isVisible()`, `form.isEnabled()`, `form.isRequired()` from core
|
|
154
|
+
- Sections auto-hide when all children are invisible
|
|
155
|
+
- Field visibility updates reactively when responses change
|
|
156
|
+
|
|
157
|
+
**Section Nesting:**
|
|
158
|
+
|
|
159
|
+
- `FieldNode` recursively renders section children
|
|
160
|
+
- Each depth level adds left border and padding
|
|
161
|
+
- Respects visibility rules at every level
|
|
162
|
+
|
|
163
|
+
## Example: Conditional Visibility
|
|
164
|
+
|
|
165
|
+
```tsx
|
|
166
|
+
const conditionalForm: FormDefinition = {
|
|
167
|
+
schemaType: 'mieforms-v1.0',
|
|
168
|
+
title: 'Conditional Form',
|
|
169
|
+
fields: [
|
|
170
|
+
{
|
|
171
|
+
id: 'hasAllergies',
|
|
172
|
+
fieldType: 'boolean',
|
|
173
|
+
question: 'Do you have any allergies?',
|
|
174
|
+
},
|
|
175
|
+
{
|
|
176
|
+
id: 'allergyList',
|
|
177
|
+
fieldType: 'longtext',
|
|
178
|
+
question: 'Please list your allergies',
|
|
179
|
+
visible: {
|
|
180
|
+
conditions: [
|
|
181
|
+
{
|
|
182
|
+
conditionType: 'comparison',
|
|
183
|
+
fieldId: 'hasAllergies',
|
|
184
|
+
operator: '==',
|
|
185
|
+
value: true,
|
|
186
|
+
},
|
|
187
|
+
],
|
|
188
|
+
logicalOperator: 'AND',
|
|
189
|
+
},
|
|
190
|
+
},
|
|
191
|
+
],
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
// "allergyList" only shows when "hasAllergies" is checked
|
|
195
|
+
<EsheetRenderer formData={conditionalForm} />;
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
## CSS Architecture
|
|
199
|
+
|
|
200
|
+
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.
|
|
201
|
+
|
|
202
|
+
## License
|
|
203
|
+
|
|
204
|
+
MIT
|
|
205
|
+
|
|
206
|
+
## Building
|
|
207
|
+
|
|
208
|
+
Run `nx build @esheet/renderer` to build the library.
|
|
209
|
+
|
|
210
|
+
## Running unit tests
|
|
211
|
+
|
|
212
|
+
Run `nx test @esheet/renderer` to execute the unit tests via [Vitest](https://vitest.dev/).
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { default as default_2 } from 'react';
|
|
2
|
+
import { FormDefinition } from '@esheet/core';
|
|
3
|
+
import { FormResponse } from '@esheet/core';
|
|
4
|
+
import { FormStore } from '@esheet/core';
|
|
5
|
+
import { JSX } from 'react/jsx-runtime';
|
|
6
|
+
import { UIStore } from '@esheet/core';
|
|
7
|
+
import { ValidationError } from '@esheet/core';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* EsheetRenderer - Read-only questionnaire form renderer
|
|
11
|
+
*
|
|
12
|
+
* Renders a form in fill-out mode with conditional visibility logic.
|
|
13
|
+
* Reuses all field components from @esheet/fields.
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```tsx
|
|
17
|
+
* const rendererRef = useRef<EsheetRendererHandle>(null);
|
|
18
|
+
*
|
|
19
|
+
* <EsheetRenderer
|
|
20
|
+
* formData={myFormDefinition}
|
|
21
|
+
* initialResponses={{ field1: 'answer' }}
|
|
22
|
+
* ref={rendererRef}
|
|
23
|
+
* />
|
|
24
|
+
*
|
|
25
|
+
* // Later: get responses
|
|
26
|
+
* const responses = rendererRef.current?.getResponse();
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
29
|
+
export declare const EsheetRenderer: default_2.ForwardRefExoticComponent<EsheetRendererProps & default_2.RefAttributes<EsheetRendererHandle>>;
|
|
30
|
+
|
|
31
|
+
export declare interface EsheetRendererHandle {
|
|
32
|
+
/** Get current form responses */
|
|
33
|
+
getResponse: () => FormResponse;
|
|
34
|
+
/** Get form store instance */
|
|
35
|
+
getFormStore: () => FormStore;
|
|
36
|
+
/** Get UI store instance */
|
|
37
|
+
getUIStore: () => UIStore;
|
|
38
|
+
/** Get validated form responses (returns null if invalid) */
|
|
39
|
+
getValidResponse: () => {
|
|
40
|
+
response: FormResponse | null;
|
|
41
|
+
errors: ValidationError[];
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export declare interface EsheetRendererProps {
|
|
46
|
+
/** Form definition (JSON object, JSON string, or YAML string) */
|
|
47
|
+
formData: FormDefinition | string;
|
|
48
|
+
/** Additional CSS classes for root container */
|
|
49
|
+
className?: string;
|
|
50
|
+
/** Initial form responses (pre-fill data) */
|
|
51
|
+
initialResponses?: FormResponse;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* FieldNode - Renders a single field or section with recursive children
|
|
56
|
+
*
|
|
57
|
+
* Looks up the field component from the registry and passes field props.
|
|
58
|
+
* For sections, recursively renders visible children.
|
|
59
|
+
*/
|
|
60
|
+
export declare const FieldNode: default_2.NamedExoticComponent<FieldNodeProps>;
|
|
61
|
+
|
|
62
|
+
declare interface FieldNodeProps {
|
|
63
|
+
id: string;
|
|
64
|
+
form: FormStore;
|
|
65
|
+
ui: UIStore;
|
|
66
|
+
/** Nesting depth for visual styling (default: 0) */
|
|
67
|
+
depth?: number;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* RendererBody - Iterates over visible root fields and renders them
|
|
72
|
+
*
|
|
73
|
+
* Respects conditional visibility logic from form store.
|
|
74
|
+
* Only renders fields where isVisible() returns true.
|
|
75
|
+
* Sections recursively render their visible children.
|
|
76
|
+
*/
|
|
77
|
+
export declare function RendererBody({ form, ui }: RendererBodyProps): JSX.Element;
|
|
78
|
+
|
|
79
|
+
declare interface RendererBodyProps {
|
|
80
|
+
form: FormStore;
|
|
81
|
+
ui: UIStore;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Initialize renderer with form definition
|
|
86
|
+
*
|
|
87
|
+
* - Parses YAML/JSON string input or accepts object directly
|
|
88
|
+
* - Validates against formDefinitionSchema
|
|
89
|
+
* - Loads definition into form store
|
|
90
|
+
* - Sets UI to preview mode
|
|
91
|
+
* - Applies initial responses if provided
|
|
92
|
+
*/
|
|
93
|
+
export declare function useRendererInit(form: FormStore, ui: UIStore, formData: FormDefinition | string, initialResponses?: FormResponse): void;
|
|
94
|
+
|
|
95
|
+
export { ValidationError }
|
|
96
|
+
|
|
97
|
+
export { }
|