Skip to content

feat(fields): adds new features for FHIR Support capability + Minor Enhancements#88

Merged
lattln merged 15 commits into
mainfrom
feature/add-fhir-requirements
Jul 7, 2026
Merged

feat(fields): adds new features for FHIR Support capability + Minor Enhancements#88
lattln merged 15 commits into
mainfrom
feature/add-fhir-requirements

Conversation

@lattln

@lattln lattln commented Jul 1, 2026

Copy link
Copy Markdown
Collaborator

Overview

This PR adds FHIR-required questionnaire support for file upload and open-choice responses.

It introduces two new field types, file and openchoice, and wires them through the core type system, field registry, builder, renderer, condition logic, response hydration, validation, and FHIR adapter. These changes allow questionnaires to capture attachment-based answers and choice answers that can include user-entered custom values.

What Changed

  • Added a new FileField component for single or multiple file uploads.

  • Added a new OpenChoiceField component for predefined options with custom user-entered values.

  • Extended core field definitions, Zod schemas, and field type metadata for file and openchoice.

  • Added fileData response support for attachment answers.

  • Updated the field registry with metadata for the new field types.

  • Registered the new fields across the builder and renderer default field sets.

  • Added icons and exports for the new field components.

  • Updated condition evaluation to support:

    • open-choice selected values
    • custom open-choice values
    • file count expressions
    • file attachment values in condition checks
  • Updated validation so uploaded files are treated as non-empty responses.

  • Improved response hydration for file/media attachment answers.

  • Updated the FHIR adapter to map:

    • file responses to FHIR valueAttachment
    • open-choice responses to coding answers, including custom values
  • Added expanded test coverage for condition behavior and adapter conversion.

How to Test

  1. Create a questionnaire with a file field.
  2. Verify the file field renders correctly in the builder and renderer.
  3. Upload a single file and confirm the response stores the attachment metadata.
  4. Configure the file field for multiple files and verify multiple attachments are stored correctly.
  5. Add validation constraints such as accepted file types or maximum file size and verify the UI behavior.
  6. Create a questionnaire with an openchoice field.
  7. Select a predefined option and confirm the selected value is stored correctly.
  8. Enter a custom open-choice value and confirm it is stored and hydrated correctly.
  9. Add conditional logic that depends on an open-choice response and verify it evaluates correctly.
  10. Add conditional logic that depends on uploaded file count and verify it evaluates correctly.
  11. Convert a questionnaire with file and open-choice fields through the FHIR adapter.
  12. Verify file responses map to FHIR attachment answers.
  13. Verify open-choice responses map to coding answers, including custom user-entered values.
  14. Run the affected core logic and FHIR adapter test suites.

Breaking Changes

No breaking changes.

lattln added 3 commits July 1, 2026 16:05
+ added Open Choice Field, with Radio Field functionality + an additional "other" field that can have custom input.
+ Added File Field Type (which includes, max file, max file size, accepted file type) + stores as base64.
+ Remove selection / highlighting from occuring when double clicking on all field Options
+ fix response handling for fileField
lattln added 12 commits July 1, 2026 17:13
+ adds maxfiles properties for file
+ add OtherLabel for openChoice
- Add question-based slug generation for field IDs with collision-safe fallback.
- Rewrite field references when IDs change, including rules, expressions, display content, and calculations.
- Seed computed text values into responses so they behave like editable defaults.
- Show a suggested ID in the editor UI and add tests for the new behavior.
@lattln lattln requested a review from Copilot July 7, 2026 23:01
@lattln lattln merged commit 5750de9 into main Jul 7, 2026
2 of 3 checks passed

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR extends the eSheet Nx monorepo to support FHIR-required questionnaire capabilities for file uploads and open-choice responses by introducing new file and openchoice field types and wiring them through the core type system, registry, builder/renderer default registrations, condition logic, validation/hydration, and FHIR/SurveyJS adapters.

Changes:

  • Added new FileField and OpenChoiceField components and registered them in builder/renderer defaults and package exports.
  • Extended core types/schemas/registry and condition + validation + hydration logic for fileData and open-choice custom values; introduced setValue effect plumbing into the renderer (computed values).
  • Updated FHIR + SurveyJS + MCP adapters, builder UI logic tooling (LogicEditor), ID generation/suggestion (slugify), and added/updated related tests.

Reviewed changes

Copilot reviewed 46 out of 48 changed files in this pull request and generated 10 comments.

Show a summary per file
File Description
packages/renderer/src/lib/renderer.ts Adds setValue computedValue resolution into render tree and helper to apply computed values back into responses.
packages/renderer/src/lib/register-defaults.ts Registers openchoice and file renderer field components by default.
packages/renderer/src/index.ts Re-exports render tree builder utilities/types.
packages/fields/src/index.ts Exports OpenChoiceField and FileField from the fields package API.
packages/fields/src/icons.tsx Adds UploadIcon for file upload UI.
packages/fields/src/fields/text/TextField.tsx Seeds unanswered text responses from computedValue (setValue effects).
packages/fields/src/fields/selection/RadioField.tsx Adds ms:select-none to improve selection UX.
packages/fields/src/fields/selection/OpenChoiceField.tsx Introduces the new open-choice field UI (predefined options + “other” free text).
packages/fields/src/fields/selection/index.ts Re-exports OpenChoiceField.
packages/fields/src/fields/selection/CheckField.tsx Adds ms:select-none to improve selection UX.
packages/fields/src/fields/rich/index.ts Re-exports FileField.
packages/fields/src/fields/rich/FileField.tsx Introduces file upload field UI with drag/drop, constraints, and builder configuration UI.
packages/fields/src/fields/matrix/SingleMatrixField.tsx Adds ms:select-none for matrix option labels.
packages/fields/src/fields/matrix/MultiMatrixField.tsx Adds ms:select-none for matrix option labels.
packages/fields/src/fields/index.ts Wires new fields into the umbrella fields export.
packages/core/src/lib/types.ts Adds file/openchoice types + schemas, adds fileData + attachment size, adds setValue conditional effect, removes readOnly from schema (TODO).
packages/core/src/lib/types.spec.ts Updates built-in field type count expectations for new types.
packages/core/src/lib/stores/form-store.ts Adds cross-field reference rewrite on rename; updates calculation behavior; disables readOnly behavior (TODO).
packages/core/src/lib/stores/form-store.spec.ts Adds rename/reference rewrite test coverage.
packages/core/src/lib/registry.ts Registers metadata for the new built-in field types.
packages/core/src/lib/registry.spec.ts Updates registry counts for new types.
packages/core/src/lib/logic/validate.ts Treats fileData as non-empty for required/emptiness logic.
packages/core/src/lib/logic/resolve.ts Adds resolveSetValue (value effect) and refactors boolean effect handling.
packages/core/src/lib/logic/conditions.ts Adds expression normalization helper; adds openchoice/file support for expression/value extraction.
packages/core/src/lib/logic/comprehensive-conditions.spec.ts Adds integration tests for openchoice and file condition behavior.
packages/core/src/lib/functions/ids.ts Adds slugifyQuestion and enhances generateFieldId to use question-based slugs.
packages/core/src/lib/functions/ids.spec.ts Adds tests for slug-based ID generation and slugify behavior.
packages/core/src/lib/functions/hydrate-response.ts Ensures attachment hydration can return fileData for media answers.
packages/core/src/lib/field-component-props.ts Adds computedValue to field component props.
packages/core/src/index.ts Re-exports new types/functions (slugify/normalizeExpression/resolveSetValue, etc.).
packages/builder/tsconfig.lib.json Adds renderer project reference ordering/update.
packages/builder/tsconfig.json Adds renderer reference (and adjusts ordering).
packages/builder/src/lib/register-defaults.ts Registers openchoice and file builder field components by default.
packages/builder/src/lib/components/FieldWrapper.tsx Plumbs computedValue through wrapper render props.
packages/builder/src/lib/components/edit-panel/LogicEditor.tsx Adds setValue effect UI, function chips, and openchoice/file operator handling.
packages/builder/src/lib/components/edit-panel/EditPanel.tsx Removes readOnly prop wiring (readOnly not implemented).
packages/builder/src/lib/components/edit-panel/CommonEditor.tsx Adds suggested ID button using slugifyQuestion(question).
packages/builder/src/lib/components/Canvas.tsx Builds a render tree to compute and pass setValue computedValue into field rendering.
packages/builder/src/index.ts Normalizes exports (type exports inline).
packages/builder/package.json Adds dependency on @esheet/renderer.
packages/adapters/src/lib/surveyjs-converter.ts Maps SurveyJS select → openchoice and file → file, and attempts to preserve readOnly.
packages/adapters/src/lib/mcp.ts Adds MCP export handling/metadata for openchoice and file fields.
packages/adapters/src/fhir/utils.ts Maps FHIR attachment/open-choice itemControl to file/openchoice field types.
packages/adapters/src/fhir/fhir-adapter.ts Imports FHIR expressions as setValue rules; maps file/openchoice answers to FHIR; updates attachment handling.
packages/adapters/src/fhir/fhir-adapter.spec.ts Updates tests for attachment mapping and open-choice mapping.
eslint.config.mjs Ignores apps/docs/static/** in ESLint.
apps/docs/static/loco.min.js Modifies a bundled/minified static asset.

Comment on lines 454 to 462
// Base field
const base = {
id,
question: element.title ?? element.name,
required: element.isRequired ?? false,
...(element.readOnly ? { readOnly: element.readOnly } : {}),
rules,
_sourceData,
};
Comment on lines 132 to 138
// Build base field
const base = {
id: item.linkId,
question: item.text,
required: item.required,
...(item.readOnly ? { readOnly: item.readOnly } : {}),
};
Comment on lines +79 to +82
for (const rule of rules) {
if (evaluateRule(rule, normalized, responses, dangerouslyAllowJS)) {
// Rule matched; now extract and evaluate the expression condition to get the value.
// Typically, a setValue rule will have ONE expression condition that produces the value.
Comment on lines +2245 to +2250
it('equals — matches __other__ when user types custom text', () => {
const { normalized, field } = setup(openChoiceDef);
field.rules = [fieldRule('visible', 'other-1', 'equals', '__other__')];
const resp: FieldResponse = {
selected: { id: '__other__', value: 'custom text' },
};
Comment on lines +2280 to +2300
it('expression — returns user-typed text for __other__', () => {
const driver: FieldDefinition = {
id: 'other-5',
fieldType: 'openchoice',
question: 'Source',
options: [{ id: 'other-6', value: 'X' }],
};
const target: FieldDefinition = {
id: 'other-7',
fieldType: 'text',
question: 'Target',
rules: [exprRule('visible', "{other-5} == 'custom text'")],
};
const normalized = normalizeDefinition([driver, target]);
const resp: FieldResponse = {
selected: { id: '__other__', value: 'custom text' },
};
expect(
resolveEffect('visible', target, normalized, { 'other-5': resp })
).toBe(true);
});
Comment on lines +121 to +138
<div className="ms:mt-2">
<input
id={`${instanceId}-openchoice-other-${def.id}`}
type="text"
value={otherText}
onChange={(e) => {
onResponse({
selected: { id: otherOptionId, value: e.target.value },
});
}}
disabled={!isOtherSelected}
className={`ms:w-full ms:px-3 ms:py-2 ms:h-9 ms:rounded-lg ms:transition-all ${
isOtherSelected
? 'ms:border ms:border-msborder ms:focus:border-msprimary ms:focus:ring-1 ms:focus:ring-msprimary/30 ms:outline-none ms:bg-mssurface ms:text-mstext ms:cursor-text'
: 'ms:border ms:border-dashed ms:border-msborder ms:bg-mssurface ms:text-mstextmuted ms:opacity-40 ms:cursor-not-allowed'
}`}
/>
</div>
Comment on lines +378 to +389
<div>
<label className="ms:block ms:text-sm ms:font-medium ms:text-mstextmuted ms:mb-2">
Accepted types
</label>
<input
type="text"
placeholder="Search file types..."
value={typeSearch}
onChange={(e) => setTypeSearch(e.target.value)}
className="ms:px-3 ms:py-2 ms:h-10 ms:w-full ms:border ms:border-msborder ms:bg-mssurface ms:text-mstext ms:rounded-lg ms:mb-2"
/>
<div className="ms:space-y-2 ms:h-28 ms:overflow-y-auto ms:border ms:border-msborder ms:rounded-lg ms:p-3 ms:bg-mssurface">
Comment on lines +420 to +442
<div>
<label className="ms:block ms:text-sm ms:font-medium ms:text-mstextmuted ms:mb-1">
Max file size
</label>
<div className="ms:flex ms:gap-2">
<input
type="number"
value={
def.maxFileSize
? convertBytesToUnit(def.maxFileSize, sizeUnit)
: ''
}
onChange={(e) => {
const val = e.target.value ? Number(e.target.value) : undefined;
onUpdate({
maxFileSize: val
? convertUnitToBytes(val, sizeUnit)
: undefined,
});
}}
placeholder="Optional"
className="ms:px-3 ms:py-2 ms:h-10 ms:flex-1 ms:border ms:border-msborder ms:bg-mssurface ms:text-mstext ms:rounded-lg"
/>
Comment on lines +443 to +455
<select
value={sizeUnit}
onChange={(e) => {
const newUnit = e.target.value as SizeUnit;
setSizeUnit(newUnit);
}}
className="ms:px-3 ms:py-2 ms:h-10 ms:border ms:border-msborder ms:bg-mssurface ms:text-mstext ms:rounded-lg"
>
<option value="KB">KB</option>
<option value="MB">MB</option>
<option value="GB">GB</option>
</select>
</div>
Comment on lines +462 to +479
<input
type="number"
min="1"
value={def.maxFiles ?? ''}
onChange={(e) =>
onUpdate({
maxFiles: e.target.value ? Number(e.target.value) : undefined,
})
}
onBlur={(e) => {
const val = e.target.value ? Number(e.target.value) : 0;
if (val < 1) {
onUpdate({ maxFiles: 1 });
}
}}
placeholder="1"
className="ms:px-3 ms:py-2 ms:h-10 ms:w-full ms:border ms:border-msborder ms:bg-mssurface ms:text-mstext ms:rounded-lg"
/>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Feature Request: Auto-generate field IDs from question text New Field : File Upload Field New Field : Open Choice Field

2 participants