@form-engine-ts/custom-survey-client
v8.1.7
Published
Maintainers
Readme
@form-engine-ts/custom-survey-client
React composite APIs for survey applications. The package composes the existing Form Engine builder and translation primitives while keeping persistence, translation services, authentication, and state libraries injectable.
<SurveyUiProvider locale="ja" translationAdapter={uiTranslations}>
<SurveyEditor schema={schema} adapter={editorAdapter} onChange={setSchema} />
<SurveyResponseSummary summary={summary} version={version} sourceLanguage="ja" />
</SurveyUiProvider>The package has no dependency on Maker authentication, tRPC, Jotai, or URL state. Network and persistence concerns are injected through transport-neutral adapters:
SurveyEditoracceptstranslateSurveyPreviewandupdateSurveyDraft, and exposesuseSurveyEditorController. Notifications, card settings, and submission settings can be supplied through slots or therenderprop.SurveyEditorforwardspageEditorMode="single"to edit one selected page at a time; the default"all"mode remains available.SurveyFreeTextTable/useFreeTextAnswerTranslationnormalize text-answer pages orFormResponserecords, group by source locale, batch requests, track selection/status, and gate PII-bearing translations behind confirmation.translateFreeTextAnswersandcreateFreeTextTranslationControllertranslate arbitrary answer arrays without selection state and return completion status, counts, findings, and per-answer results.useFreeTextAnswerTranslationalso exposes the same directtranslatemethod;hasPiiCandidateandgetFreeTextAnswerFindingscover application-owned PII dialogs.useSurveyVersionActionsmanages quality checks, issue Accept/Reject, publish with warning confirmation, draft clone/delete, visibility changes, and async state.SurveyVersionActionAdapteris composable: each operation is optional,composeSurveyVersionActionscombines independently implemented actions, anduseSurveyVersionDomainActionsaccepts generic version/state records without schema conversion. The olderSurveyVersionActionsAdapteranduseSurveyVersionOperationsnames remain available.SurveyResponseSummaryandtoSurveyResponseSummaryaccept aFormSchemaorFormVersionRecorddirectly and resolve question and option labels forsourceLanguage.SurveyResponseSummaryCustomDomainandmapSurveyResponseSummaryretain application-owned aggregates, language tabs, skip reasons, definitions, and labels for custom render slots.
AI survey creation
SurveyAiCreationPanel provides an adapter-injected conversation, brief summary, generated-schema review, question removal, respondent preview, retry/error states, and completion callback. It uses useFormCreationAssistant; provider credentials and AI responses remain in the host adapters.
import { SurveyAiCreationPanel } from "@form-engine-ts/custom-survey-client/ai-creation";
import "@form-engine-ts/custom-survey-client/ai-creation/styles.css";
<SurveyAiCreationPanel
initialSchema={schema}
sourceLocale="ja"
creationAdapter={creationAdapter}
authoringAdapter={authoringAdapter}
labels={labels}
onComplete={setSchema}
onCancel={closeDialog}
/>SurveyAiCreationLabels.error receives codes such as provider_unavailable, network_error, invalid_response, and stale_schema; fieldType supplies localized question-type names. onCancel is optional and is called after any in-flight request is aborted. previewMode="inline" renders the respondent preview inside a host dialog; the default is its own accessible dialog. SurveyEditorPreviewDialog is exported from the same subpath for reuse.
Set initialNotice, initialPrompt, and initialQuickReplies to customize the empty conversation state. The generated survey brief is visible by default; pass showBrief={false} to hide it and make the conversation full-width, or pass briefInitiallyVisible={false} to hide it initially while keeping its toggle. Localize that toggle with showBrief and hideBrief.
Use renderMessageInput to replace the default text input with a host UI component such as MUI TextField without adding a UI-library dependency to this package.
Use createSurveyTranslationAdapter and createSurveyTranslator to adapt application translation functions without an unsafe cast. SurveyProvider is the unified provider for Form Engine and survey translations; it accepts a typed translation scope, a structural i18next-compatible i18n instance, or a transport-neutral translation adapter. When no local i18n props are passed it composes with the surrounding Form Engine provider instead of replacing it. @form-engine-ts/custom-survey-client is publishable with ESM, CommonJS, and declaration outputs; React and Form Engine packages are peer dependencies.
v7.7 APIs
Survey Viewer and Maker can share one explicit definition conversion boundary. The converter accepts the supported
question types, maps single-choice to the Form Engine's radio or select, and normalizes those fields back to
single-choice when converting a schema:
import { formSchemaToSurveyDefinition, surveyDefinitionToFormSchema } from "@form-engine-ts/custom-survey-client";
const schema = surveyDefinitionToFormSchema({
id: "customer-survey",
version: 1,
locale: "en",
title: "Customer survey",
fields: [
{
id: "contact",
type: "single-choice",
selectionStyle: "radio",
title: "Contact method",
required: true,
options: [{ id: "email", label: "Email" }]
}
]
});
const definition = formSchemaToSurveyDefinition(schema);Conversion also carries submission settings, pages, conditions, translations and their metadata, quiz/poll metadata,
choice settings, and field/option metadata. Use SurveyMetadataCodec<TMetadata> when application metadata has a typed
domain shape; the codec is applied at every schema, page, field, option, and submission-settings metadata boundary.
useSurveyMappingCrud updates mappings and revision before calling onConflict. The callback receives
expectedRevision, currentRevision, and currentMappings; use retry() to replay the last failed mutation or
reload() to fetch the current server state. refresh() remains available as a compatibility alias.
createSurveyTextMetadataCodec({ preserveUnknown: true, sourceTextHash: "auto" }) provides the common text metadata
boundary. It retains source text, generates the source hash, normalizes automatic/manual state and legacy flags, and
preserves translation/edit dates and unknown JSON metadata. Pass it as metadataCodec to translateSurveySchema to
keep the same metadata contract while forwarding its AbortSignal to the asynchronous translation adapter.
Response Summary can load a summary lazily for each selected language. The hook caches successful language results, aborts the previous request when the language changes, and exposes typed loading/error/reload state:
const summaryDomain = useSurveyResponseSummaryDomain({
summary,
version,
domainAdapter,
languageOptions,
summaryLoader: ({ language, signal }) => api.loadSummary({ language, signal })
});
<SurveyResponseSummaryDomain {...summaryDomain} />;Domain response summaries also render mapped skip reasons by default, using the selected locale for number formatting.
Pass labels.skipReasons to localize the heading or replace the complete area with slots.skipReasons; empty results
remain hidden.
Opt into the package-provided rich response-summary renderer with variant="rich". It groups each question into a
card, displays answered/unanswered counts, renders choice distributions with accessible progress bars, and renders
numeric/rating or checkbox statistics as cards. The existing default renderer is unchanged, and slots.question /
slots.skipReasons still take precedence over the rich defaults:
<SurveyResponseSummaryDomain
{...summaryDomain}
variant="rich"
locale="ja-JP"
labels={{
answered: "回答済み",
unanswered: "未回答",
options: "選択肢",
statistics: "集計",
average: "平均",
minimum: "最小",
maximum: "最大",
total: "合計",
checked: "チェック済み",
unchecked: "未チェック",
skipReasons: "スキップ理由"
}}
slots={{ question: renderQuestion, skipReasons: renderSkipReasons }}
/>;locale controls localized count, statistic, and percentage formatting; when omitted, the active summary language is
used. Progress values are normalized to the 0..100 range, including exact 0% and 100% values. CSS can target
the stable form-engine-response-summary__* classes and data-summary-variant="rich" attribute without adding a UI
library dependency.
For a Material UI presentation layer, install the optional @form-engine-ts/mui package and use its
MuiSurveyResponseSummaryDomain component. The core survey-client package remains usable without MUI or Emotion.
createSurveySchemaDomainAdapter provides one shared text metadata codec for application-owned schema records. Its
conversion boundary writes the canonical source-text hash and translation source/date fields, and normalizes legacy
isManuallyEdited/isManual metadata on read. Use the codec for form title/description/completion text, page titles,
question titles, and choice labels so all schema text slots follow the same rules:
const adapter = createSurveySchemaDomainAdapter({
toFormSchema,
fromFormSchema,
textMetadata: { toEngine, fromEngine }
});Mapping create, remove, reorder, and atomic reorderMany requests carry expectedRevision. Revision-bearing
mutation responses update the complete local mapping state. When the transport returns REVISION_CONFLICT, the hook
exposes state.revisionConflict with the latest mappings/revision and state.canRetry; call refresh() or retry after
the application accepts that state. isSurveyMappingRevisionConflict is available for transport-level error guards.
v7.6 APIs
Response Summary language state, language labels, language tabs, and language-specific unanswered counts can now be owned by the package hook. The hook result is directly spreadable into the component. The default tab is now the all-languages aggregate, followed by each language:
const summaryDomain = useSurveyResponseSummaryDomain({
summary,
version,
domainAdapter,
languageOptions,
languageLabel: (language) => languageNames[language] ?? language
});
<SurveyResponseSummaryDomain {...summaryDomain} />The all-languages tab uses the top-level summary and does not call summaryLoader. Use selectedTab, defaultTab, and
onTabChange for explicit scope-aware control. tabOptions contains { scope: "all" } and { scope: "language" }
entries, and slots.summaryTabs / slots.renderSummaryTabs receive the same options and selection state. The older
language-only props and tab slot remain available for compatibility.
toLanguageSummaryInput keeps application-owned aggregate conversion inside the domain adapter. For schema translation,
use the async-only adapter and package-owned metadata policy/report callback:
await translateSurveySchema({
schema,
sourceLocale: "en",
targetLocale: "ja",
signal,
translationAdapter: { translateText, translateBatch },
metadataPolicy: { source: "AI", preserveManualEdits: true, updateSourceTextHash: true },
onReport: setTranslationReport
});Mapping reorder supports one atomic operation that returns the committed order and revision. The transport adapter must
perform the transaction and may provide rollbackReorder for a failed commit or result validation:
const result = await mappingCrud.reorderMany({ mappings, selection, signal, expectedRevision });reorderMany is the atomic contract: the adapter performs one transaction and returns the committed mappings and
revision. translateSurveySchema requires AsyncTranslationAdapter; synchronous translation belongs to other legacy
adapter APIs and is not used for schema translation.
v7.5 APIs
SurveyWorkflowControlled now treats expanded as the source of truth for step
visibility. Set showToggle={false} when the host renders its own header control,
or use the toggle slot for a custom package control. Domain response summaries
support the languageTabs slot, localized default labels, and languageLabel.
<SurveyResponseSummaryDomain
{...props}
labels={{ languages: "言語", answered: "回答済み", unanswered: "未回答" }}
languageLabel={(language) => languageNames[language] ?? language}
slots={{ languageTabs: renderLanguageTabs }}
/>Use translateSurveySchema for one package-owned translation operation covering
form, page, question, choice, and translation metadata slots. Mapping CRUD also
exposes reorderMany, and mapSurveyQualityIssue converts domain fields such as
issueId, severity, category, and language into the package quality shape.
useSurveyTranslation().locale returns the active UI locale.
v7.3 migration guide
This package has two layers. Controllers and hooks are transport-neutral: they cover
survey editor persistence, response translation, quality checks, version lifecycle,
response summaries, and free-text normalization. The package UI is intentionally
small and slot-based: SurveyEditor, SurveyQualityPanel, SurveyVersionHistory,
SurveyWorkflowPanel, SurveyMappingPanel, SurveyResponseSummary,
SurveyFreeTextTable, and SurveyVersionPanel provide accessible defaults that can
be replaced with render or slots.
The application remains responsible for tRPC procedures, authentication, React Query cache invalidation, domain-specific warning copy, and dialogs. Inject those concerns through adapters and slots:
const actions = useSurveyVersionDomainActions({ version, state, adapter });
<SurveyVersionPanel
version={version}
actions={actions}
slots={{
qualityWarningDialog: ({ issues, confirm, cancel }) => (
<MakerWarningDialog issues={issues} onConfirm={confirm} onCancel={cancel} />
),
notifications: (current) => <MakerNotifications actions={current} />
}}
/>;Use composeSurveyVersionActions(qualityAdapter, publishAdapter, lifecycleAdapter,
visibilityAdapter) to keep each operation independently implemented. Every action is
optional; an unused decideQualityIssue or quality adapter does not need a dummy
implementation.
Domain records can be mapped once and reused by package features:
const schemaAdapter = createSurveySchemaDomainAdapter((surveyVersion) => toFormSchema(surveyVersion));
const editor = useSurveyEditorDomain({
domain: surveyVersion,
domainAdapter: { ...schemaAdapter, fromFormSchema },
adapter: { translateSurveyPreview, updateSurveyDraft }
});
const summary = toSurveyResponseSummaryFromDomain(analytics, surveyVersion, schemaAdapter, "ja");
const translations = useFreeTextDomainAnswerTranslation({
items: answers,
domainAdapter: { toFreeTextAnswerItem: toTranslationItem },
adapter: translationAdapter,
targetLanguage: "ja"
});
const result = await translations.translate(answers, {
onPiiConfirmation: (findings) => openPiiDialog(findings)
});For direct answer translation, v7.3 updates the hook's item state as well as returning the outcome. PII confirmation can be application-owned without a second Map:
const result = await translations.translate(answers, {
onPiiConfirmation: (findings) => openPiiDialog(findings)
});publishResult/decideQualityIssueResult/cloneDraftResult/deleteDraftResult/
setVisibilityResult adapter methods return structured { succeeded, error, response,
metadata } data without changing the old void-returning action methods. The matching
controller methods return { succeeded, error } and preserve any adapter response;
the default boolean methods remain available for v7.2 callers.
quality.result contains the original quality-check payload for custom quality UI.
v7.1 to v7.2 to v7.3
- v7.1 callers can keep the legacy
translate/save,qualityCheck/duplicate,delete/setStatusnames. - v7.2 introduced
translateFreeTextAnswers,useSurveyVersionDomainActions,composeSurveyVersionActions, andSurveyProvider. - v7.3 adds direct translation state updates, PII callbacks, structured action results,
domain adapters for editor/summary/free text, and the slot-based
SurveyVersionPanel.
Provider usage is unified through SurveyProvider (or SurveyUiProvider). It accepts
common/customSurvey-style namespace names and a real application i18next instance
directly through the generic i18n prop. Use commonNamespace and
customSurveyNamespace when those are the application's namespace names; the package validates the t function at
runtime and does not add an i18next dependency. Headless use with no provider remains
supported.
Before v7.3, an application had to map Domain answers before calling the controller:
await controller.translate(answers.map(toFreeTextAnswerItem));In v7.3, use the Domain hook and pass the application records directly:
const translation = useFreeTextDomainAnswerTranslation({
items: answers,
domainAdapter: { toFreeTextAnswerItem },
adapter: translationAdapter,
targetLanguage: "ja"
});
await translation.translate(answers);v7.3 to v7.4 migration guide
v7.4 adds the P0 domain-first surfaces. Migrate feature by feature: keep the Maker adapter for tRPC, authentication, aggregation, and business rules, and remove the local DTO mapper, controller state, and generic panel once the corresponding package hook is adopted. The old v7.3 names remain available while the migration is staged.
| Local implementation | v7.4 replacement | Keep in Maker |
| --- | --- | --- |
| client/editor.tsx, schema conversion, generic builder state | useSurveyEditorDomain and SurveyEditorDomainAdapter | Domain adapter, validation policy, tRPC/auth |
| Response summary mapper and language-tab state | useSurveyResponseSummaryDomain / SurveyResponseSummaryDomain | Aggregation query and custom render slots |
| qualityIssuesRef, raw quality DTO conversion | useSurveyVersionDomainActions and SurveyVersionQualityResult | Quality procedure, policy, dialog copy |
| surveyWorkflow.ts display wrapper | SurveyWorkflowControlled / useSurveyWorkflowControlled | Workflow calculation and tab routing |
| Mapping CRUD state and reload handling | useSurveyMappingCrud / SurveyMappingCrudAdapter | Deck/group selection semantics and auth |
| Local SurveyUiProvider translation wrapper | SurveyProvider, SurveyTranslationScope | Resource loading and locale selection |
The package root exports these types and controllers; feature-folder paths are implementation details. MUI dialogs and application notifications remain slots, and tRPC calls remain ordinary adapter functions.
Adapter integration boundaries
The adapters are deliberately shaped like ordinary async functions, so tRPC and React Query can be used without a package dependency:
const publish = trpc.survey.publish.useMutation({
onSuccess: () => queryClient.invalidateQueries({ queryKey: ["survey", version.id] })
});
const actions = useSurveyVersionDomainActions({
version,
adapter: { publish: ({ version, allowWarnings }) => publish.mutateAsync({ version, allowWarnings }) }
});MUI remains an application choice. Pass a MUI dialog through
qualityWarningDialog or visibilityDialog; the package supplies the findings,
status, and callbacks but does not own the dialog or warning copy.
v7.4 migration and deletion map
v7.4 completes the generic boundary needed by a Maker application. The package owns the controller state and default UI; the application supplies only domain mapping, transport, authentication, business rules, and screen-specific dialogs.
| Feature | v7.2 / v7.3 | v7.4 surface | Maker code that can be deleted | Responsibility that remains in Maker |
| --- | --- | --- | --- | --- |
| Editor | Schema-first editor and v7.3 domain helper | useSurveyEditorDomain, SurveyEditorDomainAdapter, question adapter, settings slots | FormSchema conversion hooks, save/preview controller, generic question reorder/add state | Domain toFormSchema/fromFormSchema, tRPC/auth, business validation policy |
| Response summary | toSurveyResponseSummary for Form Analytics | useSurveyResponseSummaryDomain / SurveyResponseSummaryDomain with language aggregates, skip reasons, question/choice definitions, label resolution, and render slots | FormSchema conversion, summary mapper, language-tab state, generic summary view state | Maker aggregation query and domain-specific aggregate calculation |
| Quality / Version | Separate quality panel and version actions | useSurveyQualityController, original response/rawResponse, runId, checkedRevision, unified accept/reject, action effects | qualityIssuesRef, generic quality result conversion, cache/notification plumbing | Quality provider procedure, auth, warning/dialog copy, quality policy |
| Workflow | Uncontrolled transition list | SurveyWorkflowControlled / useSurveyWorkflowControlled with generic state, expanded/onToggle, progress, navigation, and slots | Workflow panel state, progress calculation wiring, tab transition plumbing | Calculation of domain completion/progress and application tab routing |
| Mapping | Save-only mapping adapter | SurveyMappingCrudAdapter / useSurveyMappingCrud with individual create/remove/reorder, list refresh, selection payload, operation state, and invalidation | Mapping CRUD state, reload/error/loading handling | Deck/group query semantics, auth, domain mapping rules |
| Provider | SurveyUiProvider and i18next-shaped i18n | One provider for Form Engine plus common/customSurvey namespaces | Local provider and translation-function wrapper | Resource loading and application locale selection |
Domain-first editor usage:
const editor = useSurveyEditorDomain({
domain: surveyVersion,
domainAdapter: { toFormSchema, fromFormSchema },
adapter: { translateSurveyPreview, updateSurveyDraft },
questionAdapter: { addQuestion, reorderQuestions },
onDomainChange: setSurveyVersion
});The domain-first editor keeps the Maker version record as the source of truth.
questionAdapter handles domain-specific add/remove/reorder rules and slots
replace card, submission, toolbar, and notification UI:
const editor = useSurveyEditorDomain({
domain: surveyVersion,
domainAdapter: { toFormSchema, fromFormSchema },
adapter: { translateSurveyPreview, updateSurveyDraft },
questionAdapter: { addQuestion, removeQuestion, reorderQuestions },
slots: { cardAppearance: renderCardSettings, submissionSettings: renderResponseSettings },
onDomainChange: setSurveyVersion
});For application-owned response aggregates, no intermediate Maker mapper is required:
const summary = useSurveyResponseSummaryDomain({
summary: makerSummary,
version: surveyVersion,
domainAdapter: { toSummaryInput, toFormSchema, sourceLanguage, mapLanguages, mapSkipReasons },
selectedLanguage,
onLanguageChange: setSelectedLanguage,
slots: { question: renderQuestion, skipReasons: renderSkipReasons }
});Quality adapters return the provider payload instead of forcing the application to reconstruct it:
const quality = useSurveyQualityController({
version,
adapter: {
run: ({ version, signal }) => trpc.qualityCheck({ version, signal }),
decide: ({ issue, decision, result }) => trpc.decideQuality({ issue, decision, result }),
invalidate: () => queryClient.invalidateQueries({ queryKey: ["survey", version.id] })
}
});Quality issues can request a provider-neutral authoring fix with
createAuthoringRequestFromQualityIssue(issue, schema). After the user applies
the selected suggestion, compose the existing apply callback with
applyAndRecheckQuality(() => assistant.applySelected(), quality.run) so a
successful apply immediately starts a fresh quality check; failed or empty
applies do not recheck.
The v7.2 names remain available for migration. New code should use the
domain/controller names and root exports; feature folders (editor/*,
response/*, quality/*, workflow/*, mapping/*, and shared/*) are
implementation boundaries, not application-owned deep-import contracts.
Deprecated compatibility names are useSurveyEditor, translate/save,
qualityCheck, duplicate, delete, and setStatus. They remain available
for v7.2/v7.3 migration and should not be used in new Maker code.
Content mode editor slots
SurveyEditor accepts optional builderSlots separately from its existing slots.
This forwards FormBuilder slots without replacing the editor or its save/translation
pipeline. Configuration slot render props also expose optional onChange(schema)
for metadata updates. Combine these with Core's getContentModePolicy, and validate
with validateContentMode inside the save adapter. Basic schema validation remains
unchanged. SurveyDefinition conversion preserves mode, quiz field metadata and
unknown JSON metadata in both directions; radio still maps to single-choice with
selectionStyle: "radio".
