@esheet/renderer 0.0.4-0 → 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 (46) hide show
  1. package/dist/index.d.ts +9 -291
  2. package/dist/index.d.ts.map +1 -0
  3. package/dist/index.js +115 -115
  4. package/dist/lib/EsheetRenderer.d.ts +116 -0
  5. package/dist/lib/EsheetRenderer.d.ts.map +1 -0
  6. package/dist/lib/EsheetRenderer.js +115 -0
  7. package/dist/lib/components/FieldNode.d.ts +17 -0
  8. package/dist/lib/components/FieldNode.d.ts.map +1 -0
  9. package/dist/lib/components/FieldNode.js +96 -0
  10. package/dist/lib/components/RendererBody.d.ts +14 -0
  11. package/dist/lib/components/RendererBody.d.ts.map +1 -0
  12. package/dist/lib/components/RendererBody.js +45 -0
  13. package/dist/lib/components/index.d.ts +3 -0
  14. package/dist/lib/components/index.d.ts.map +1 -0
  15. package/dist/lib/components/index.js +2 -0
  16. package/dist/lib/hooks/index.d.ts +2 -0
  17. package/dist/lib/hooks/index.d.ts.map +1 -0
  18. package/dist/lib/hooks/index.js +1 -0
  19. package/dist/lib/hooks/useRendererInit.d.ts +12 -0
  20. package/dist/lib/hooks/useRendererInit.d.ts.map +1 -0
  21. package/dist/lib/hooks/useRendererInit.js +87 -0
  22. package/dist/lib/mcp/index.d.ts +7 -0
  23. package/dist/lib/mcp/index.d.ts.map +1 -0
  24. package/dist/lib/mcp/index.js +4 -0
  25. package/dist/lib/mcp/system-prompt.d.ts +2 -0
  26. package/dist/lib/mcp/system-prompt.d.ts.map +1 -0
  27. package/dist/lib/mcp/system-prompt.js +24 -0
  28. package/dist/lib/mcp/tool-definitions.d.ts +10 -0
  29. package/dist/lib/mcp/tool-definitions.d.ts.map +1 -0
  30. package/dist/lib/mcp/tool-definitions.js +66 -0
  31. package/dist/lib/mcp/tool-executor.d.ts +5 -0
  32. package/dist/lib/mcp/tool-executor.d.ts.map +1 -0
  33. package/dist/lib/mcp/tool-executor.js +55 -0
  34. package/dist/lib/mcp/useRendererToolBridge.d.ts +28 -0
  35. package/dist/lib/mcp/useRendererToolBridge.d.ts.map +1 -0
  36. package/dist/lib/mcp/useRendererToolBridge.js +53 -0
  37. package/dist/lib/register-defaults.d.ts +2 -0
  38. package/dist/lib/register-defaults.d.ts.map +1 -0
  39. package/dist/lib/register-defaults.js +36 -0
  40. package/dist/lib/renderer-tools.d.ts +56 -0
  41. package/dist/lib/renderer-tools.d.ts.map +1 -0
  42. package/dist/lib/renderer-tools.js +305 -0
  43. package/dist/lib/renderer.d.ts +28 -0
  44. package/dist/lib/renderer.d.ts.map +1 -0
  45. package/dist/lib/renderer.js +33 -0
  46. package/package.json +5 -5
@@ -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"}
@@ -0,0 +1,66 @@
1
+ export const RENDERER_TOOL_DEFINITIONS = [
2
+ {
3
+ type: 'function',
4
+ function: {
5
+ name: 'get_form',
6
+ description: 'Returns visible fields split into filled vs unfilled. Response: {formId, totalFields, filledCount, unfilledFields (full details), filledFieldIds (just IDs)}. Use unfilledFields to see what needs filling.\n\nFILL LOOP PATTERN:\n1. Call get_form — fill every field in unfilledFields using fill_field.\n2. Call get_form again. If new fields appeared in unfilledFields (revealed by conditional logic), fill those too.\n3. Repeat until unfilledFields is empty.\n\nNever assume you are done after a single pass — conditional logic frequently reveals new fields.',
7
+ parameters: { type: 'object', properties: {} },
8
+ },
9
+ },
10
+ {
11
+ type: 'function',
12
+ function: {
13
+ name: 'get_form_raw',
14
+ description: 'Returns ALL fields in the form regardless of visibility, conditional rules, or enabled state. Use this to see the full form schema.',
15
+ parameters: { type: 'object', properties: {} },
16
+ },
17
+ },
18
+ {
19
+ type: 'function',
20
+ function: {
21
+ name: 'get_responses',
22
+ description: 'Get the current raw response values for all fields.',
23
+ parameters: { type: 'object', properties: {} },
24
+ },
25
+ },
26
+ {
27
+ type: 'function',
28
+ function: {
29
+ name: 'get_valid_response',
30
+ description: 'Run form validation. Returns the full response object if all required fields pass, or null plus field-level errors if validation fails. Call this before submitting.',
31
+ parameters: { type: 'object', properties: {} },
32
+ },
33
+ },
34
+ {
35
+ type: 'function',
36
+ function: {
37
+ name: 'fill_field',
38
+ description: 'Set a response value for ONE visible field. Returns { result, filledCount, unfilledFields } where unfilledFields contains ONLY the fields still needing values (with full details including options/valueFormat). You are done when unfilledFields is empty.\n\nField type rules:\n- radio/dropdown/boolean/rating/slider: pass a single string matching one of the options values\n- check/multiselectdropdown: pass an array of strings matching option values\n- ranking: pass an ordered array of option value strings representing the desired rank order\n- multitext: pass an array of strings, one per option slot in order\n- singlematrix: pass an object { "Row Label": "Column Label" } for each row\n- multimatrix: pass an object { "Row Label": ["Col1", "Col2"] } for each row\n- text: use the valueFormat from the field\'s schema (e.g. YYYY-MM-DD for date, YYYY-MM-DDTHH:mm for datetime-local, YYYY-MM for month, HH:mm for time). Wrong formats are rejected with an error.',
39
+ parameters: {
40
+ type: 'object',
41
+ properties: {
42
+ fieldId: { type: 'string' },
43
+ fieldQuestion: { type: 'string' },
44
+ value: {},
45
+ },
46
+ required: ['value'],
47
+ },
48
+ },
49
+ },
50
+ {
51
+ type: 'function',
52
+ function: {
53
+ name: 'clear_responses',
54
+ description: 'Clear all current form responses.',
55
+ parameters: { type: 'object', properties: {} },
56
+ },
57
+ },
58
+ {
59
+ type: 'function',
60
+ function: {
61
+ name: 'get_form_tree',
62
+ description: 'Get the full rendered field tree including visibility, enabled, and required state for each field given current responses.',
63
+ parameters: { type: 'object', properties: {} },
64
+ },
65
+ },
66
+ ];
@@ -0,0 +1,5 @@
1
+ import type { RendererTools } from '../renderer-tools.js';
2
+ type ToolArgs = Record<string, unknown>;
3
+ export declare function executeToolCall(toolName: string, args: ToolArgs, tools: RendererTools): string | Record<string, unknown>;
4
+ export {};
5
+ //# sourceMappingURL=tool-executor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tool-executor.d.ts","sourceRoot":"","sources":["../../../src/lib/mcp/tool-executor.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAE1D,KAAK,QAAQ,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAExC,wBAAgB,eAAe,CAC7B,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,QAAQ,EACd,KAAK,EAAE,aAAa,GACnB,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAoBlC"}
@@ -0,0 +1,55 @@
1
+ export function executeToolCall(toolName, args, tools) {
2
+ switch (toolName) {
3
+ case 'get_form':
4
+ return getFormOptimized(tools);
5
+ case 'get_form_raw':
6
+ return tools.getFormRaw();
7
+ case 'get_responses':
8
+ return tools.getResponses();
9
+ case 'get_valid_response':
10
+ return tools.getValidResponse();
11
+ case 'fill_field':
12
+ return fillField(args, tools);
13
+ case 'clear_responses':
14
+ tools.clearResponses();
15
+ return 'Responses cleared';
16
+ case 'get_form_tree':
17
+ return { fields: tools.getFormTree() };
18
+ default:
19
+ return `Unknown tool: ${toolName}`;
20
+ }
21
+ }
22
+ /** Optimized get_form: returns full details only for unfilled fields to reduce token usage. */
23
+ function getFormOptimized(tools) {
24
+ const form = tools.getForm();
25
+ const filledFields = form.fields.filter((f) => f.hasValue);
26
+ const unfilledFields = form.fields.filter((f) => !f.hasValue);
27
+ return {
28
+ formId: form.formId,
29
+ totalFields: form.fieldCount,
30
+ filledCount: filledFields.length,
31
+ // Only unfilled fields need full details (options, questions, etc.)
32
+ unfilledFields,
33
+ // Filled fields: just IDs for reference
34
+ filledFieldIds: filledFields.map((f) => f.id),
35
+ };
36
+ }
37
+ function fillField(args, tools) {
38
+ const fieldId = tools.resolveFieldId(args['fieldId'], args['fieldQuestion']);
39
+ if (!fieldId)
40
+ return 'Error: field not found — provide fieldId or fieldQuestion';
41
+ const result = tools.fillField(fieldId, args['value']);
42
+ if (typeof result === 'string')
43
+ return result; // format validation error
44
+ if (!result)
45
+ return `Error: field "${fieldId}" is not currently visible — call get_form to see the current visible fields`;
46
+ const form = tools.getForm();
47
+ // Return only unfilled fields with full details (token optimization)
48
+ const filledFields = form.fields.filter((f) => f.hasValue);
49
+ const unfilledFields = form.fields.filter((f) => !f.hasValue);
50
+ return {
51
+ result: `Field "${fieldId}" updated`,
52
+ filledCount: filledFields.length,
53
+ unfilledFields,
54
+ };
55
+ }