@esheet/renderer 0.0.3 → 0.0.4-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.
Files changed (47) hide show
  1. package/README.md +3 -3
  2. package/dist/index.d.ts +9 -102
  3. package/dist/index.d.ts.map +1 -0
  4. package/dist/index.js +5284 -4315
  5. package/dist/lib/EsheetRenderer.d.ts +116 -0
  6. package/dist/lib/EsheetRenderer.d.ts.map +1 -0
  7. package/dist/lib/EsheetRenderer.js +115 -0
  8. package/dist/lib/components/FieldNode.d.ts +17 -0
  9. package/dist/lib/components/FieldNode.d.ts.map +1 -0
  10. package/dist/lib/components/FieldNode.js +96 -0
  11. package/dist/lib/components/RendererBody.d.ts +14 -0
  12. package/dist/lib/components/RendererBody.d.ts.map +1 -0
  13. package/dist/lib/components/RendererBody.js +45 -0
  14. package/dist/lib/components/index.d.ts +3 -0
  15. package/dist/lib/components/index.d.ts.map +1 -0
  16. package/dist/lib/components/index.js +2 -0
  17. package/dist/lib/hooks/index.d.ts +2 -0
  18. package/dist/lib/hooks/index.d.ts.map +1 -0
  19. package/dist/lib/hooks/index.js +1 -0
  20. package/dist/lib/hooks/useRendererInit.d.ts +12 -0
  21. package/dist/lib/hooks/useRendererInit.d.ts.map +1 -0
  22. package/dist/lib/hooks/useRendererInit.js +87 -0
  23. package/dist/lib/mcp/index.d.ts +7 -0
  24. package/dist/lib/mcp/index.d.ts.map +1 -0
  25. package/dist/lib/mcp/index.js +4 -0
  26. package/dist/lib/mcp/system-prompt.d.ts +2 -0
  27. package/dist/lib/mcp/system-prompt.d.ts.map +1 -0
  28. package/dist/lib/mcp/system-prompt.js +24 -0
  29. package/dist/lib/mcp/tool-definitions.d.ts +10 -0
  30. package/dist/lib/mcp/tool-definitions.d.ts.map +1 -0
  31. package/dist/lib/mcp/tool-definitions.js +66 -0
  32. package/dist/lib/mcp/tool-executor.d.ts +5 -0
  33. package/dist/lib/mcp/tool-executor.d.ts.map +1 -0
  34. package/dist/lib/mcp/tool-executor.js +55 -0
  35. package/dist/lib/mcp/useRendererToolBridge.d.ts +28 -0
  36. package/dist/lib/mcp/useRendererToolBridge.d.ts.map +1 -0
  37. package/dist/lib/mcp/useRendererToolBridge.js +53 -0
  38. package/dist/lib/register-defaults.d.ts +2 -0
  39. package/dist/lib/register-defaults.d.ts.map +1 -0
  40. package/dist/lib/register-defaults.js +36 -0
  41. package/dist/lib/renderer-tools.d.ts +56 -0
  42. package/dist/lib/renderer-tools.d.ts.map +1 -0
  43. package/dist/lib/renderer-tools.js +305 -0
  44. package/dist/lib/renderer.d.ts +28 -0
  45. package/dist/lib/renderer.d.ts.map +1 -0
  46. package/dist/lib/renderer.js +33 -0
  47. package/package.json +5 -5
@@ -0,0 +1,116 @@
1
+ import React from 'react';
2
+ import { type FormResponse, type FormStore, type UIStore, type ValidationError } from '@esheet/core';
3
+ import { type FhirQuestionnaireResponse, type ResponseExportOptions } from '@esheet/adapters';
4
+ import { type RendererTools } from './renderer-tools.js';
5
+ export interface EsheetRendererProps {
6
+ /** Form definition — accepts FormDefinition, SurveyJS schema, MCP elicitation envelope,
7
+ * or any of the above as a JSON/YAML string. Auto-detected and converted internally.
8
+ * Set `strict` to disable auto-conversion and require a valid FormDefinition directly. */
9
+ formDataInput: unknown;
10
+ /** Additional CSS classes for root container */
11
+ className?: string;
12
+ /** Initial form responses (pre-fill data) */
13
+ initialResponses?: FormResponse;
14
+ /** When true, disables auto-detection of MCP/SurveyJS formats.
15
+ * Only accepts a valid eSheet FormDefinition (or JSON/YAML string thereof). */
16
+ strict?: boolean;
17
+ /** Called after the form definition has been parsed and loaded into the store. */
18
+ onReady?: () => void;
19
+ /**
20
+ * Called once after the renderer mounts, providing a narrow `RendererTools`
21
+ * facade for MCP / AI tool integrations.
22
+ */
23
+ onRendererToolsReady?: (tools: RendererTools) => void;
24
+ /**
25
+ * Enable touch-optimized mode with larger touch targets.
26
+ * - `true`: Always enable touch mode
27
+ * - `false`: Never enable touch mode (CSS media query still applies)
28
+ * - `'auto'`: Enable based on viewport width (<980px) via JavaScript
29
+ * - `undefined`: Rely on CSS media query only (default)
30
+ */
31
+ touchMode?: boolean | 'auto';
32
+ /**
33
+ * Called when touch mode changes (via auto-detection or programmatic toggle).
34
+ * Useful for syncing external UI with the renderer's touch state.
35
+ */
36
+ onTouchModeChange?: (enabled: boolean) => void;
37
+ }
38
+ /** Response format options for getResponse() */
39
+ export type ResponseFormat = 'native' | 'fhir';
40
+ /** Options for getResponse() */
41
+ export interface GetResponseOptions {
42
+ /** Output format: 'native' (default) or 'fhir' (FHIR QuestionnaireResponse) */
43
+ format?: ResponseFormat;
44
+ /** FHIR export options (required when format is 'fhir') */
45
+ fhir?: Omit<ResponseExportOptions, 'questionnaireUrl'> & {
46
+ /** Canonical URL of the questionnaire. Falls back to form._sourceData metadata if available. */
47
+ questionnaireUrl?: string;
48
+ };
49
+ }
50
+ /** Return type for getResponse() — varies by format */
51
+ export type GetResponseResult<T extends ResponseFormat = 'native'> = T extends 'fhir' ? FhirQuestionnaireResponse : FormResponse;
52
+ export interface EsheetRendererHandle {
53
+ /** Get current form responses */
54
+ getRawResponse: () => FormResponse;
55
+ /**
56
+ * Get form responses in the specified format.
57
+ *
58
+ * @param options - Format and export options
59
+ * @returns Native FormResponse or FHIR QuestionnaireResponse
60
+ *
61
+ * @example
62
+ * ```ts
63
+ * // Native format (default)
64
+ * const native = ref.current.getResponse();
65
+ *
66
+ * // FHIR QuestionnaireResponse
67
+ * const fhir = ref.current.getResponse({
68
+ * format: 'fhir',
69
+ * fhir: {
70
+ * questionnaireUrl: 'http://example.org/Questionnaire/my-form',
71
+ * status: 'completed',
72
+ * }
73
+ * });
74
+ * ```
75
+ */
76
+ getResponse: <T extends ResponseFormat = 'native'>(options?: GetResponseOptions & {
77
+ format?: T;
78
+ }) => GetResponseResult<T>;
79
+ /** Get form store instance */
80
+ getFormStore: () => FormStore;
81
+ /** Get UI store instance */
82
+ getUIStore: () => UIStore;
83
+ /** Get validated form responses (returns null if invalid) */
84
+ getValidResponse: () => {
85
+ response: FormResponse | null;
86
+ errors: ValidationError[];
87
+ };
88
+ /** Returns true if touch mode is currently enabled */
89
+ isTouchModeEnabled: () => boolean;
90
+ /** Toggle touch mode on/off. Only works when touchMode prop is 'auto' or undefined. Overrides auto-detection. */
91
+ setTouchMode: (enabled: boolean) => void;
92
+ /** Reset to auto-detection mode (clears manual override). Only works when touchMode='auto'. */
93
+ resetTouchMode: () => void;
94
+ }
95
+ /**
96
+ * EsheetRenderer - Read-only questionnaire form renderer
97
+ *
98
+ * Renders a form in fill-out mode with conditional visibility logic.
99
+ * Reuses all field components from @esheet/fields.
100
+ *
101
+ * @example
102
+ * ```tsx
103
+ * const rendererRef = useRef<EsheetRendererHandle>(null);
104
+ *
105
+ * <EsheetRenderer
106
+ * formData={myFormDefinition}
107
+ * initialResponses={{ field1: 'answer' }}
108
+ * ref={rendererRef}
109
+ * />
110
+ *
111
+ * // Later: get responses
112
+ * const responses = rendererRef.current?.getRawResponse();
113
+ * ```
114
+ */
115
+ export declare const EsheetRenderer: React.ForwardRefExoticComponent<EsheetRendererProps & React.RefAttributes<EsheetRendererHandle>>;
116
+ //# sourceMappingURL=EsheetRenderer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"EsheetRenderer.d.ts","sourceRoot":"","sources":["../../src/lib/EsheetRenderer.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAML,KAAK,YAAY,EACjB,KAAK,SAAS,EACd,KAAK,OAAO,EACZ,KAAK,eAAe,EACrB,MAAM,cAAc,CAAC;AACtB,OAAO,EAGL,KAAK,yBAAyB,EAC9B,KAAK,qBAAqB,EAC3B,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAuB,KAAK,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAW9E,MAAM,WAAW,mBAAmB;IAClC;;+FAE2F;IAC3F,aAAa,EAAE,OAAO,CAAC;IACvB,gDAAgD;IAChD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,6CAA6C;IAC7C,gBAAgB,CAAC,EAAE,YAAY,CAAC;IAChC;oFACgF;IAChF,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,kFAAkF;IAClF,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB;;;OAGG;IACH,oBAAoB,CAAC,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,CAAC;IACtD;;;;;;OAMG;IACH,SAAS,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC;IAC7B;;;OAGG;IACH,iBAAiB,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,CAAC;CAChD;AAMD,gDAAgD;AAChD,MAAM,MAAM,cAAc,GAAG,QAAQ,GAAG,MAAM,CAAC;AAE/C,gCAAgC;AAChC,MAAM,WAAW,kBAAkB;IACjC,+EAA+E;IAC/E,MAAM,CAAC,EAAE,cAAc,CAAC;IACxB,2DAA2D;IAC3D,IAAI,CAAC,EAAE,IAAI,CAAC,qBAAqB,EAAE,kBAAkB,CAAC,GAAG;QACvD,gGAAgG;QAChG,gBAAgB,CAAC,EAAE,MAAM,CAAC;KAC3B,CAAC;CACH;AAED,uDAAuD;AACvD,MAAM,MAAM,iBAAiB,CAAC,CAAC,SAAS,cAAc,GAAG,QAAQ,IAC/D,CAAC,SAAS,MAAM,GAAG,yBAAyB,GAAG,YAAY,CAAC;AAE9D,MAAM,WAAW,oBAAoB;IACnC,iCAAiC;IACjC,cAAc,EAAE,MAAM,YAAY,CAAC;IACnC;;;;;;;;;;;;;;;;;;;;OAoBG;IACH,WAAW,EAAE,CAAC,CAAC,SAAS,cAAc,GAAG,QAAQ,EAC/C,OAAO,CAAC,EAAE,kBAAkB,GAAG;QAAE,MAAM,CAAC,EAAE,CAAC,CAAA;KAAE,KAC1C,iBAAiB,CAAC,CAAC,CAAC,CAAC;IAC1B,8BAA8B;IAC9B,YAAY,EAAE,MAAM,SAAS,CAAC;IAC9B,4BAA4B;IAC5B,UAAU,EAAE,MAAM,OAAO,CAAC;IAC1B,6DAA6D;IAC7D,gBAAgB,EAAE,MAAM;QACtB,QAAQ,EAAE,YAAY,GAAG,IAAI,CAAC;QAC9B,MAAM,EAAE,eAAe,EAAE,CAAC;KAC3B,CAAC;IACF,sDAAsD;IACtD,kBAAkB,EAAE,MAAM,OAAO,CAAC;IAClC,iHAAiH;IACjH,YAAY,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,CAAC;IACzC,+FAA+F;IAC/F,cAAc,EAAE,MAAM,IAAI,CAAC;CAC5B;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,eAAO,MAAM,cAAc,kGA2BzB,CAAC"}
@@ -0,0 +1,115 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import React from 'react';
3
+ import { createFormStore, createUIStore, normalizeResponses, hydrateDefinition, validateForm, } from '@esheet/core';
4
+ import { exportResponseToFhir, } from '@esheet/adapters';
5
+ import { createRendererTools } from './renderer-tools.js';
6
+ import { FormStoreContext, UIContext, ZodIssuesPanel, useTouchMode, } from '@esheet/fields';
7
+ import { ensureDefaultFieldComponentsRegistered } from './register-defaults.js';
8
+ import { useRendererInit } from './hooks/useRendererInit.js';
9
+ import { RendererBody } from './components/RendererBody.js';
10
+ /**
11
+ * EsheetRenderer - Read-only questionnaire form renderer
12
+ *
13
+ * Renders a form in fill-out mode with conditional visibility logic.
14
+ * Reuses all field components from @esheet/fields.
15
+ *
16
+ * @example
17
+ * ```tsx
18
+ * const rendererRef = useRef<EsheetRendererHandle>(null);
19
+ *
20
+ * <EsheetRenderer
21
+ * formData={myFormDefinition}
22
+ * initialResponses={{ field1: 'answer' }}
23
+ * ref={rendererRef}
24
+ * />
25
+ *
26
+ * // Later: get responses
27
+ * const responses = rendererRef.current?.getRawResponse();
28
+ * ```
29
+ */
30
+ export const EsheetRenderer = React.forwardRef(function EsheetRenderer(props, ref) {
31
+ ensureDefaultFieldComponentsRegistered();
32
+ const formStore = React.useMemo(() => createFormStore(), []);
33
+ const uiStore = React.useMemo(() => createUIStore(), []);
34
+ React.useEffect(() => {
35
+ if (props.onRendererToolsReady)
36
+ props.onRendererToolsReady(createRendererTools(formStore));
37
+ // eslint-disable-next-line react-hooks/exhaustive-deps
38
+ }, [formStore]);
39
+ return (_jsx(FormStoreContext.Provider, { value: formStore, children: _jsx(UIContext.Provider, { value: uiStore, children: _jsx(EsheetRendererInner, { ...props, formStore: formStore, uiStore: uiStore, ref: ref }) }) }));
40
+ });
41
+ const EsheetRendererInner = React.forwardRef(function EsheetRendererInner({ formDataInput: formData, className = '', initialResponses, strict = false, onReady, formStore, uiStore, touchMode, onTouchModeChange, }, ref) {
42
+ const [validationErrors, setValidationErrors] = React.useState([]);
43
+ // Touch mode state using shared hook
44
+ const { isTouchEnabled, isManualOverride, setTouchMode: setTouchModeInternal, resetTouchMode: resetTouchModeInternal, } = useTouchMode({ mode: touchMode, onChange: onTouchModeChange });
45
+ // Initialize form definition and set preview mode
46
+ useRendererInit(formStore, uiStore, formData, initialResponses, setValidationErrors, strict, onReady);
47
+ // Expose ref API
48
+ React.useImperativeHandle(ref, () => ({
49
+ getRawResponse: () => formStore.getState().responses,
50
+ getResponse: (options) => {
51
+ const state = formStore.getState();
52
+ const responses = state.responses;
53
+ if (options?.format === 'fhir') {
54
+ // Build FHIR QuestionnaireResponse
55
+ const normalizedAnswers = normalizeResponses(responses);
56
+ // Try to get questionnaire URL from options or formSourceData
57
+ const fhirMeta = state.formSourceData;
58
+ const questionnaireUrl = options.fhir?.questionnaireUrl ??
59
+ fhirMeta?.url ??
60
+ `urn:uuid:${state.formId}`;
61
+ const exportOptions = {
62
+ questionnaireUrl,
63
+ status: options.fhir?.status ?? 'completed',
64
+ subject: options.fhir?.subject,
65
+ author: options.fhir?.author,
66
+ resourceId: options.fhir?.resourceId,
67
+ };
68
+ // Rebuild form definition from normalized state
69
+ const formDefinition = {
70
+ id: state.formId,
71
+ fields: hydrateDefinition(state.normalized),
72
+ _sourceData: state.formSourceData,
73
+ };
74
+ return exportResponseToFhir(formDefinition, normalizedAnswers, exportOptions);
75
+ }
76
+ // Native format (default)
77
+ return responses;
78
+ },
79
+ getFormStore: () => formStore,
80
+ getUIStore: () => uiStore,
81
+ getValidResponse: () => {
82
+ const state = formStore.getState();
83
+ const errors = validateForm(state.normalized, state.responses);
84
+ return {
85
+ response: errors.length === 0 ? state.responses : null,
86
+ errors,
87
+ };
88
+ },
89
+ isTouchModeEnabled: () => isTouchEnabled,
90
+ setTouchMode: setTouchModeInternal,
91
+ resetTouchMode: resetTouchModeInternal,
92
+ }), [
93
+ formStore,
94
+ uiStore,
95
+ isTouchEnabled,
96
+ setTouchModeInternal,
97
+ resetTouchModeInternal,
98
+ ]);
99
+ // Determine if touch mode class should be applied
100
+ // true: always apply, 'auto'/undefined: apply based on isTouchEnabled state, false: never apply (CSS only)
101
+ const applyTouchClass = touchMode === true ||
102
+ ((touchMode === 'auto' || touchMode === undefined) && isTouchEnabled);
103
+ // Apply disabled class when user explicitly disabled touch mode (prevents CSS media query)
104
+ const applyDisabledClass = isManualOverride && !isTouchEnabled;
105
+ const rootClasses = [
106
+ 'esheet-renderer-root',
107
+ applyTouchClass && 'esheet-touch-active',
108
+ applyDisabledClass && 'touch-mode-disabled',
109
+ 'ms:w-full ms:max-w-2xl ms:mx-auto ms:p-4 ms:text-mstext',
110
+ className,
111
+ ]
112
+ .filter(Boolean)
113
+ .join(' ');
114
+ return (_jsxs("div", { className: rootClasses, children: [_jsx(ZodIssuesPanel, { issues: validationErrors }), _jsx(RendererBody, { form: formStore, ui: uiStore })] }));
115
+ });
@@ -0,0 +1,17 @@
1
+ import React from 'react';
2
+ import type { FormStore, UIStore } from '@esheet/core';
3
+ export interface FieldNodeProps {
4
+ id: string;
5
+ form: FormStore;
6
+ ui: UIStore;
7
+ /** Nesting depth for visual styling (default: 0) */
8
+ depth?: number;
9
+ }
10
+ /**
11
+ * FieldNode - Renders a single field or section with recursive children
12
+ *
13
+ * Looks up the field component from the registry and passes field props.
14
+ * For sections, recursively renders visible children.
15
+ */
16
+ export declare const FieldNode: React.NamedExoticComponent<FieldNodeProps>;
17
+ //# sourceMappingURL=FieldNode.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"FieldNode.d.ts","sourceRoot":"","sources":["../../../src/lib/components/FieldNode.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,KAAK,EAAuB,SAAS,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAG5E,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,SAAS,CAAC;IAChB,EAAE,EAAE,OAAO,CAAC;IACZ,oDAAoD;IACpD,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;;;;GAKG;AACH,eAAO,MAAM,SAAS,4CAiKpB,CAAC"}
@@ -0,0 +1,96 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import React from 'react';
3
+ import { getFieldComponent } from '@esheet/fields';
4
+ /**
5
+ * FieldNode - Renders a single field or section with recursive children
6
+ *
7
+ * Looks up the field component from the registry and passes field props.
8
+ * For sections, recursively renders visible children.
9
+ */
10
+ export const FieldNode = React.memo(function FieldNode({ id, form, ui, depth = 1, }) {
11
+ // Subscribe to field data
12
+ const field = React.useSyncExternalStore((cb) => form.subscribe(cb), () => form.getState().getField(id), () => form.getState().getField(id));
13
+ const normalized = React.useSyncExternalStore((cb) => form.subscribe(cb), () => form.getState().normalized, () => form.getState().normalized);
14
+ const responses = React.useSyncExternalStore((cb) => form.subscribe(cb), () => form.getState().responses, () => form.getState().responses);
15
+ // Get visible children for sections
16
+ const visibleChildIds = React.useMemo(() => {
17
+ if (!field || field.definition.fieldType !== 'section')
18
+ return [];
19
+ const node = normalized.byId[id];
20
+ if (!node || node.childIds.length === 0)
21
+ return [];
22
+ const cache = new Map();
23
+ const isFieldRenderable = (fieldId) => {
24
+ const cached = cache.get(fieldId);
25
+ if (cached !== undefined)
26
+ return cached;
27
+ const isVisible = form.getState().isVisible(fieldId);
28
+ if (!isVisible) {
29
+ cache.set(fieldId, false);
30
+ return false;
31
+ }
32
+ const childNode = normalized.byId[fieldId];
33
+ if (!childNode) {
34
+ cache.set(fieldId, false);
35
+ return false;
36
+ }
37
+ if (childNode.definition.fieldType !== 'section') {
38
+ cache.set(fieldId, true);
39
+ return true;
40
+ }
41
+ const hasRenderableChild = childNode.childIds.some((cid) => isFieldRenderable(cid));
42
+ cache.set(fieldId, hasRenderableChild);
43
+ return hasRenderableChild;
44
+ };
45
+ return node.childIds.filter((childId) => isFieldRenderable(childId));
46
+ }, [field, id, form, normalized, responses]);
47
+ // Render nested children for sections
48
+ const nestedChildren = React.useMemo(() => {
49
+ if (!field ||
50
+ field.definition.fieldType !== 'section' ||
51
+ visibleChildIds.length === 0) {
52
+ return null;
53
+ }
54
+ const containerClass = depth === 1
55
+ ? 'section-children ms:space-y-2'
56
+ : 'section-children ms:space-y-2 ms:border-l ms:border-msborder ms:pl-3';
57
+ return (_jsx("div", { className: containerClass, "data-depth": depth, children: visibleChildIds.map((childId) => (_jsx(FieldNode, { id: childId, form: form, ui: ui, depth: depth + 1 }, childId))) }));
58
+ }, [field, visibleChildIds, id, form, ui, depth]);
59
+ if (!field)
60
+ return null;
61
+ const Component = getFieldComponent(field.definition.fieldType);
62
+ // Check if field is enabled/required via conditional logic
63
+ const isVisible = form.getState().isVisible(field.definition.id);
64
+ const isEnabled = form.getState().isEnabled(field.definition.id);
65
+ const isRequired = form.getState().isRequired(field.definition.id);
66
+ const response = form.getState().getResponse(field.definition.id);
67
+ if (!isVisible)
68
+ return null;
69
+ const props = {
70
+ field,
71
+ form,
72
+ ui,
73
+ isSelected: false,
74
+ isPreview: true,
75
+ isEnabled,
76
+ isRequired,
77
+ response,
78
+ onRemove: () => undefined, // No-op in renderer
79
+ onUpdate: () => undefined, // No-op in renderer
80
+ onResponse: (value) => form.getState().setResponse(field.definition.id, value),
81
+ };
82
+ const isSection = field.definition.fieldType === 'section';
83
+ const parentNode = field.parentId
84
+ ? form.getState().getField(field.parentId)
85
+ : null;
86
+ const isChildOfSection = parentNode?.definition.fieldType === 'section';
87
+ const wrapperClass = `field-wrapper ms:bg-mssurface${isSection ? ' ms:mb-2 ms:border ms:border-msborder ms:rounded' : ''}${!isSection && !isChildOfSection
88
+ ? ' ms:mb-2 ms:p-6 ms:border ms:border-msborder ms:rounded'
89
+ : ''}${isChildOfSection
90
+ ? ' ms:p-6 ms:border-b ms:border-msborder ms:last:border-b-0'
91
+ : ''}${!isEnabled ? ' ms:opacity-50 ms:pointer-events-none' : ''}`;
92
+ return (_jsx("div", { className: wrapperClass, "data-field-type": field.definition.fieldType, "data-field-id": field.definition.id, "aria-disabled": !isEnabled || undefined, children: field.definition.fieldType === 'section' ? ((() => {
93
+ const SectionComponent = Component;
94
+ return (_jsx(SectionComponent, { ...props, nestedChildren: nestedChildren }));
95
+ })()) : (_jsx(Component, { ...props })) }));
96
+ });
@@ -0,0 +1,14 @@
1
+ import type { FormStore, UIStore } from '@esheet/core';
2
+ export interface RendererBodyProps {
3
+ form: FormStore;
4
+ ui: UIStore;
5
+ }
6
+ /**
7
+ * RendererBody - Iterates over visible root fields and renders them
8
+ *
9
+ * Respects conditional visibility logic from form store.
10
+ * Only renders fields where isVisible() returns true.
11
+ * Sections recursively render their visible children.
12
+ */
13
+ export declare function RendererBody({ form, ui }: RendererBodyProps): import("react/jsx-runtime").JSX.Element;
14
+ //# sourceMappingURL=RendererBody.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RendererBody.d.ts","sourceRoot":"","sources":["../../../src/lib/components/RendererBody.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAGvD,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,SAAS,CAAC;IAChB,EAAE,EAAE,OAAO,CAAC;CACb;AAED;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE,EAAE,iBAAiB,2CAyD3D"}
@@ -0,0 +1,45 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import React from 'react';
3
+ import { FieldNode } from './FieldNode.js';
4
+ /**
5
+ * RendererBody - Iterates over visible root fields and renders them
6
+ *
7
+ * Respects conditional visibility logic from form store.
8
+ * Only renders fields where isVisible() returns true.
9
+ * Sections recursively render their visible children.
10
+ */
11
+ export function RendererBody({ form, ui }) {
12
+ // Subscribe to form state for visibility updates and responses
13
+ const normalized = React.useSyncExternalStore((cb) => form.subscribe(cb), () => form.getState().normalized, () => form.getState().normalized);
14
+ const responses = React.useSyncExternalStore((cb) => form.subscribe(cb), () => form.getState().responses, () => form.getState().responses);
15
+ // Compute visible root fields
16
+ const visibleRootIds = React.useMemo(() => {
17
+ const cache = new Map();
18
+ const isFieldRenderable = (fieldId) => {
19
+ const cached = cache.get(fieldId);
20
+ if (cached !== undefined)
21
+ return cached;
22
+ const isVisible = form.getState().isVisible(fieldId);
23
+ if (!isVisible) {
24
+ cache.set(fieldId, false);
25
+ return false;
26
+ }
27
+ const node = normalized.byId[fieldId];
28
+ if (!node) {
29
+ cache.set(fieldId, false);
30
+ return false;
31
+ }
32
+ // Non-section fields are renderable if visible
33
+ if (node.definition.fieldType !== 'section') {
34
+ cache.set(fieldId, true);
35
+ return true;
36
+ }
37
+ // Sections are renderable only if they have at least one renderable child
38
+ const hasRenderableChild = node.childIds.some((childId) => isFieldRenderable(childId));
39
+ cache.set(fieldId, hasRenderableChild);
40
+ return hasRenderableChild;
41
+ };
42
+ return normalized.rootIds.filter((id) => isFieldRenderable(id));
43
+ }, [form, normalized, responses]);
44
+ return (_jsx("div", { className: "canvas-fields renderer-body ms:space-y-0", children: visibleRootIds.map((id) => (_jsx(FieldNode, { id: id, form: form, ui: ui }, id))) }));
45
+ }
@@ -0,0 +1,3 @@
1
+ export { RendererBody } from './RendererBody.js';
2
+ export { FieldNode } from './FieldNode.js';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/lib/components/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACjD,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC"}
@@ -0,0 +1,2 @@
1
+ export { RendererBody } from './RendererBody.js';
2
+ export { FieldNode } from './FieldNode.js';
@@ -0,0 +1,2 @@
1
+ export { useRendererInit } from './useRendererInit.js';
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/lib/hooks/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC"}
@@ -0,0 +1 @@
1
+ export { useRendererInit } from './useRendererInit.js';
@@ -0,0 +1,12 @@
1
+ import { type FormResponse, type FormStore, type UIStore } from '@esheet/core';
2
+ /**
3
+ * Initialize renderer with form definition.
4
+ *
5
+ * Auto-detects and converts the following input formats:
6
+ * - eSheet FormDefinition (object or JSON/YAML string)
7
+ * - FHIR R4 Questionnaire resource
8
+ * - MCP elicitation/create envelope
9
+ * - SurveyJS schema (has top-level `pages` or `elements` array)
10
+ */
11
+ export declare function useRendererInit(form: FormStore, ui: UIStore, formData: unknown, initialResponses?: FormResponse, onValidationError?: (errors: string[]) => void, strict?: boolean, onReady?: () => void): void;
12
+ //# sourceMappingURL=useRendererInit.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useRendererInit.d.ts","sourceRoot":"","sources":["../../../src/lib/hooks/useRendererInit.ts"],"names":[],"mappings":"AAEA,OAAO,EAGL,KAAK,YAAY,EACjB,KAAK,SAAS,EACd,KAAK,OAAO,EACb,MAAM,cAAc,CAAC;AAYtB;;;;;;;;GAQG;AACH,wBAAgB,eAAe,CAC7B,IAAI,EAAE,SAAS,EACf,EAAE,EAAE,OAAO,EACX,QAAQ,EAAE,OAAO,EACjB,gBAAgB,CAAC,EAAE,YAAY,EAC/B,iBAAiB,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,IAAI,EAC9C,MAAM,UAAQ,EACd,OAAO,CAAC,EAAE,MAAM,IAAI,GACnB,IAAI,CAqFN"}
@@ -0,0 +1,87 @@
1
+ import React from 'react';
2
+ import YAML from 'js-yaml';
3
+ import { formatZodValidationError, formDefinitionSchema, } from '@esheet/core';
4
+ import { importFromMcp, convertSurveyJS, isSurveyJSSchema, isMcpElicitationRequest, importFromFhir, isFhirQuestionnaire, } from '@esheet/adapters';
5
+ /**
6
+ * Initialize renderer with form definition.
7
+ *
8
+ * Auto-detects and converts the following input formats:
9
+ * - eSheet FormDefinition (object or JSON/YAML string)
10
+ * - FHIR R4 Questionnaire resource
11
+ * - MCP elicitation/create envelope
12
+ * - SurveyJS schema (has top-level `pages` or `elements` array)
13
+ */
14
+ export function useRendererInit(form, ui, formData, initialResponses, onValidationError, strict = false, onReady) {
15
+ // Store onReady in a ref so it never causes the effect to re-run
16
+ const onReadyRef = React.useRef(onReady);
17
+ onReadyRef.current = onReady;
18
+ React.useEffect(() => {
19
+ try {
20
+ // Parse input if string
21
+ let parsed;
22
+ if (typeof formData === 'string') {
23
+ const trimmed = formData.trim();
24
+ // Detect format: YAML if starts with non-brace, JSON otherwise
25
+ const isYaml = !trimmed.startsWith('{') && !trimmed.startsWith('[');
26
+ parsed = isYaml ? YAML.load(trimmed) : JSON.parse(trimmed);
27
+ }
28
+ else {
29
+ parsed = formData;
30
+ }
31
+ // Auto-detect and convert FHIR, MCP or SurveyJS input (skipped in strict mode)
32
+ if (!strict && isFhirQuestionnaire(parsed)) {
33
+ parsed = importFromFhir(parsed);
34
+ }
35
+ else if (!strict && isMcpElicitationRequest(parsed)) {
36
+ const mcpReq = parsed;
37
+ if (mcpReq.params.mode !== 'url') {
38
+ parsed = importFromMcp(mcpReq.params.requestedSchema, {
39
+ mcpId: mcpReq.id,
40
+ mcpMessage: mcpReq.params.message,
41
+ });
42
+ }
43
+ }
44
+ else if (!strict && isSurveyJSSchema(parsed)) {
45
+ parsed = convertSurveyJS(parsed);
46
+ }
47
+ // Validate schema
48
+ const validated = formDefinitionSchema.safeParse(parsed);
49
+ if (!validated.success) {
50
+ const errors = validated.error.issues.map(formatZodValidationError);
51
+ console.error('[EsheetRenderer] Invalid form definition:', validated.error.issues);
52
+ onValidationError?.(errors);
53
+ // Load empty form instead of crashing
54
+ form.getState().loadDefinition({
55
+ id: 'invalid-form',
56
+ title: 'Invalid Form',
57
+ fields: [],
58
+ });
59
+ ui.getState().setMode('preview');
60
+ return;
61
+ }
62
+ // Clear any previous validation errors on success
63
+ onValidationError?.([]);
64
+ // Load validated definition
65
+ form.getState().loadDefinition(validated.data);
66
+ // Apply initial responses if provided
67
+ if (initialResponses && Object.keys(initialResponses).length > 0) {
68
+ for (const [fieldId, value] of Object.entries(initialResponses)) {
69
+ form.getState().setResponse(fieldId, value);
70
+ }
71
+ }
72
+ // Set preview mode
73
+ ui.getState().setMode('preview');
74
+ onReadyRef.current?.();
75
+ }
76
+ catch (error) {
77
+ console.error('[EsheetRenderer] Failed to initialize:', error);
78
+ // Load empty form as fallback
79
+ form.getState().loadDefinition({
80
+ id: 'error-form',
81
+ title: 'Error',
82
+ fields: [],
83
+ });
84
+ ui.getState().setMode('preview');
85
+ }
86
+ }, [form, ui, formData, initialResponses, onValidationError, strict]);
87
+ }
@@ -0,0 +1,7 @@
1
+ export { executeToolCall } from './tool-executor.js';
2
+ export { RENDERER_TOOL_DEFINITIONS } from './tool-definitions.js';
3
+ export { RENDERER_SYSTEM_PROMPT } from './system-prompt.js';
4
+ export type { ToolDefinition } from './tool-definitions.js';
5
+ export { useRendererMcpToolHandler } from './useRendererToolBridge.js';
6
+ export type { UseRendererMcpToolHandlerOptions } from './useRendererToolBridge.js';
7
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/lib/mcp/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AACrD,OAAO,EAAE,yBAAyB,EAAE,MAAM,uBAAuB,CAAC;AAClE,OAAO,EAAE,sBAAsB,EAAE,MAAM,oBAAoB,CAAC;AAC5D,YAAY,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAC5D,OAAO,EAAE,yBAAyB,EAAE,MAAM,4BAA4B,CAAC;AACvE,YAAY,EAAE,gCAAgC,EAAE,MAAM,4BAA4B,CAAC"}
@@ -0,0 +1,4 @@
1
+ export { executeToolCall } from './tool-executor.js';
2
+ export { RENDERER_TOOL_DEFINITIONS } from './tool-definitions.js';
3
+ export { RENDERER_SYSTEM_PROMPT } from './system-prompt.js';
4
+ export { useRendererMcpToolHandler } from './useRendererToolBridge.js';
@@ -0,0 +1,2 @@
1
+ export declare const RENDERER_SYSTEM_PROMPT: string;
2
+ //# sourceMappingURL=system-prompt.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"system-prompt.d.ts","sourceRoot":"","sources":["../../../src/lib/mcp/system-prompt.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,sBAAsB,QAwByC,CAAC"}
@@ -0,0 +1,24 @@
1
+ export const RENDERER_SYSTEM_PROMPT = 'Form renderer assistant. You can read the current form state, inspect field values, fill in responses, and clear answers.\n\n' +
2
+ 'FILL WORKFLOW:\n' +
3
+ '1. Call get_form — returns {totalFields, filledCount, unfilledFields, filledFieldIds}.\n' +
4
+ '2. Fill every field in unfilledFields using fill_field.\n' +
5
+ '3. After each fill, check unfilledFields in the response for remaining empty fields. Fill them immediately.\n' +
6
+ '4. You are done when unfilledFields is empty.\n' +
7
+ 'NEVER skip a field because you are unsure of its value — generate a realistic mock value for every field including dates, times, and appointment times.\n' +
8
+ 'NEVER plan all fills upfront — conditional logic reveals new fields dynamically and you must respond to unfilledFields after each fill.\n\n' +
9
+ 'TEXT FIELD FORMAT RULES — each text field includes a valueFormat property; use it exactly:\n' +
10
+ ' valueFormat "YYYY-MM-DD" → date, e.g. "2026-05-21"\n' +
11
+ ' valueFormat "YYYY-MM-DDTHH:mm" → datetime-local, e.g. "2026-05-21T14:30"\n' +
12
+ ' valueFormat "YYYY-MM" → month, e.g. "2026-05"\n' +
13
+ ' valueFormat "HH:mm" → time (24-hour), e.g. "14:30"\n' +
14
+ 'Wrong formats are rejected with an error — always use the valueFormat from the field schema.\n\n' +
15
+ 'OTHER FIELD RULES:\n' +
16
+ ' radio/dropdown/boolean/rating/slider → single string matching an option value\n' +
17
+ ' check/multiselectdropdown → array of strings matching option values\n' +
18
+ ' ranking → ordered array of option value strings\n' +
19
+ ' multitext → array of strings, one per option slot\n' +
20
+ ' singlematrix → { "Row Label": "Column Label" }\n' +
21
+ ' multimatrix → { "Row Label": ["Col1", "Col2"] }\n\n' +
22
+ 'Use get_form_raw to see ALL fields. Use get_form_tree for visibility/enabled/required state. ' +
23
+ 'Use get_valid_response before submitting. Use clear_responses to reset. ' +
24
+ 'For conversational questions reply in plain text without calling a tool.';
@@ -0,0 +1,10 @@
1
+ export interface ToolDefinition {
2
+ type: string;
3
+ function: {
4
+ name: string;
5
+ description: string;
6
+ parameters: Record<string, unknown>;
7
+ };
8
+ }
9
+ export declare const RENDERER_TOOL_DEFINITIONS: readonly ToolDefinition[];
10
+ //# sourceMappingURL=tool-definitions.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tool-definitions.d.ts","sourceRoot":"","sources":["../../../src/lib/mcp/tool-definitions.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE;QACR,IAAI,EAAE,MAAM,CAAC;QACb,WAAW,EAAE,MAAM,CAAC;QACpB,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;KACrC,CAAC;CACH;AAED,eAAO,MAAM,yBAAyB,EAAE,SAAS,cAAc,EAsE9D,CAAC"}