npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@form-engine-ts/custom-survey-client

v8.1.7

Published

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:

  • SurveyEditor accepts translateSurveyPreview and updateSurveyDraft, and exposes useSurveyEditorController. Notifications, card settings, and submission settings can be supplied through slots or the render prop.
  • SurveyEditor forwards pageEditorMode="single" to edit one selected page at a time; the default "all" mode remains available.
  • SurveyFreeTextTable / useFreeTextAnswerTranslation normalize text-answer pages or FormResponse records, group by source locale, batch requests, track selection/status, and gate PII-bearing translations behind confirmation.
  • translateFreeTextAnswers and createFreeTextTranslationController translate arbitrary answer arrays without selection state and return completion status, counts, findings, and per-answer results. useFreeTextAnswerTranslation also exposes the same direct translate method; hasPiiCandidate and getFreeTextAnswerFindings cover application-owned PII dialogs.
  • useSurveyVersionActions manages quality checks, issue Accept/Reject, publish with warning confirmation, draft clone/delete, visibility changes, and async state. SurveyVersionActionAdapter is composable: each operation is optional, composeSurveyVersionActions combines independently implemented actions, and useSurveyVersionDomainActions accepts generic version/state records without schema conversion. The older SurveyVersionActionsAdapter and useSurveyVersionOperations names remain available.
  • SurveyResponseSummary and toSurveyResponseSummary accept a FormSchema or FormVersionRecord directly and resolve question and option labels for sourceLanguage. SurveyResponseSummaryCustomDomain and mapSurveyResponseSummary retain 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/setStatus names.
  • v7.2 introduced translateFreeTextAnswers, useSurveyVersionDomainActions, composeSurveyVersionActions, and SurveyProvider.
  • 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".