@doany-ai/forms

中文文档
0.2.3发布于 2026/11/13MITTypeScriptES Module近 30 天下载 2623

The business's forms on a Doany site: a form hook and a default component that renders a form's questions and submits the answers

原始使用文档
文档目录7 节
  1. Records
  2. Form
  3. Hooks
  4. Labels
  5. Customizing
  6. Four runnable usages
  7. Develop

@doany-ai/forms

The business's forms on a Doany site: contact, inquiry, subscription, application, quiz. The business defines a form (its questions, which services it asks about, what to show after); the site renders it and submits what the visitor fills in. A submission lands in the business's Inbox. Nothing here creates forms.

import { Form } from "@doany-ai/forms";

// /contact — the form by its id, as the business defined it
<Form formId="…" context={{ page_path: "/contact" }} />

List the page in doany/features.jsonc under forms.

Records

  • FormView (useForm): id, type (contact | inquiry | subscription | promotion | application | quiz | custom…), definition_hash (identifies what the visitor saw; sent back with the answers), definition (title, description, fields[], settings), availability (can_submit, reason: preview_only where the platform takes no submission (an address that is not the site's), example_only for the package's examples, no_service_options when a required service question has nothing to offer), livemode (false in the website editor: a submission there is a test).
  • FormField: key, type, label, help, required, control (radio | dropdown for a choice; hidden on a service question, which is then not shown and answered with the services it offers), validation (min_length, max_length, min, max, after_field), options[] (value, label), selection and resolved_options[] (service questions: the services on offer, service_mode booking | order | inquiry), consent (mode checkbox | on_submit).
  • Field types: text, textarea, email, phone, number, date, select, multiselect, checkbox, scale, agreement, consent, hidden, statement, service.
  • FormSubmitReceipt (useSubmitForm): id, submitted_at, score and outcome (a quiz), completion (the form's thank_you_message, redirect_url, cta).

Form

Renders the questions, submits the answers, shows the confirmation the form sets (or a quiz's outcome), redirects when the form says so.

prop
formIdrequired
context{ page_path?, referrer?, utm?, language?, timezone?, source_service_id? }, kept with the submission
defaultAnswersanswers to start with, by field key (a hidden value, a preselected service)
submitLabelwins over the submit label
renderSuccess(receipt) => node: replaces the confirmation
showTitlethe form's title and description above the questions; default true
variant"single" (default): every question on one page. "steps": the questions in pages with a progress bar and Back / Next — a survey. The pages are steps when given, else one page per statement question (the statement heads its page), else one page. Each page's required questions gate Next
stepsfor variant="steps": the field keys of each page, in order; keys left out go on the last page
frame"plain" (default): just the questions. "card": on a raised card, the way a form embeds on a page
onSubmitted(receipt) => void
labels, className

Answers go to the platform by field key: text, date and single choices as strings, several choices as arrays, checkbox / agreement / consent as booleans, numbers as numbers; blanks are left out. A consent with mode: "on_submit" is sent as agreed when the form is submitted.

For two date questions, set validation: { "after_field": "arrival" } on the departure question in the form definition. When both are answered, departure must be strictly later than arrival. Form checks the pair before submission, including when the questions are on different steps, and shows dateAfterField(earlierLabel) under the later-date question. The platform checks the same rule for all submissions. Use each question's required flag if both answers are mandatory.

A question that may be left blank says Optional at the end of its label row; required questions carry nothing (the merchant app's convention). A required text, textarea, email or phone question that is blank once trimmed shows answerRequired under the question and takes focus. Invalid phone and email formats show phoneFormat and emailFormat the same way. Next in a stepped form checks its page before advancing. Email still uses the browser's email-format check, and the browser checks the remaining native constraints. The fields are @doany-ai/app-core/form's, shared with the booking form: an email question shows an envelope and an example address; a phone question is typed as a local number behind a country, shown as its flag and calling code (the chip opens a searchable list, the United States first and to start with; a number typed with its own + or 00 moves the flag), and is sent in E.164.

In the website editor a submission is saved as a test (docs/specs/preview-test-records.md): it shows in the business's Inbox with a Test tag, signs no one up and reaches no client. The form itself looks and sends exactly as on the published site (the receipt's livemode is false); the editor says what was saved above the page.

Where the platform takes no submission (availability.reason preview_only, or the examples' example_only), a notice appears before the questions. Visitors can fill in their answers, validate them and move through every page of a stepped form. The final action checks locally, keeps the answers and sends no submission; it never calls onSubmitted, renders a success receipt or follows a completion redirect. A backend INVALID_STATE with details.reason="preview_only" is handled the same way if the page had older availability. A required service question with no options still blocks submission.

Failures: VERSION_CONFLICT (the questions changed meanwhile) re-reads the form and says so; RATE_LIMITED asks to wait; a VALIDATION_ERROR whose details.errors[].field names a question the visitor is asked (answers.<key>) is said under it (answerRequired or answerInvalid), on its page; anything else shows the site's submitFailed label when set, else the platform's message.

To put the form in a popup, a sidebar or a slide-in panel, wrap it in Embed from @doany-ai/app-core/ui; inside, useEmbed().markSubmitted() and close() tell the shell the visitor is done.

Hooks

hookreturns
useForm(formId)query of the FormView
useSubmitForm(formId)mutation: mutate({ definition_hash, answers, context? }) → FormSubmitReceipt

Labels

submit, submitting, thanks, score(score), loading, loadError, submitFailed, previewOnly, previewChecked, noServiceOptions, definitionChanged, rateLimited, tryAgain, notFound, choose, optional, emailPlaceholder, emailFormat, dateAfterField(earlierLabel), country, searchCountry, noCountry, phoneFormat, answerRequired, answerInvalid (required is kept for sites that set it, unused). loading appears with the skeleton while the form loads. The questions' own words come from the form's definition.

Customizing

  1. Theme: the site's variables style the inputs, buttons and the confirmation.
  2. Props: labels, submitLabel, renderSuccess, defaultAnswers, className.
  3. Hooks: draw the form yourself on useForm and useSubmitForm.

Inside the form, each question is its label, its field, its help text and its error message (a <p> under the field, shown once an answer is refused); choices are <button>s. A site rule on those elements, or on them under a wrapper (.contact p), restyles them too: see Customizing in @doany-ai/app-core.

The package ships no CSS: the site's Tailwind build scans node_modules/@doany-ai/forms/dist.

Four runnable usages

The official examples live with this package, under @doany-ai/forms/examples:

import { FormExample } from "@doany-ai/forms/examples";

<FormExample mode="default" />
<FormExample mode="css" />
<FormExample mode="props" />
<FormExample mode="hook" />

mode defaults to default. Each mounts a private DoanyProvider and query cache, reads the same three-field contact fixture, and keeps its own answers. It never uses an enclosing provider's connected client.

  • Default: the actual Form and its local preview validation.
  • CSS: identical Form props; only scoped theme variables change. No generated stylesheet or copied field styles. These examples have no portals; a real site's global theme belongs on :root.
  • Props: a card, two steps and custom button/progress labels using Form.
  • Hook: explicit name/email/question markup using useForm, useSubmitForm mutation state and app-core controls. Native fields check email syntax and lengths; the example checks trimmed required values. It supports only this fixture, and never calls mutate. General question rendering, survey navigation and connected submission remain in Form.

formExampleView and createFormExampleClient() are also exported. The client returns a fresh copy for its one local form ID, refuses submissions with INVALID_STATE / example_only, and rejects unrelated account/upload operations with EXAMPLE_ONLY. It makes no network request or real submission, never produces a receipt, and does not follow a completion redirect. Use the site's SDK client and the main package entry for a connected form.

Develop

npm install
npm run typecheck   # includes tests/sdk-contract.types.ts
npm test
npm run build

Native dropdowns use app-core’s NativeSelect, with left-aligned text and an inset arrow. Date fields keep the browser’s date picker at the right edge. These controls require @doany-ai/app-core 0.2.3 or later in the 0.2 series.

版本与发布记录

@doany-ai/forms 共发布 23 个版本,首次发布于 2026/10/09;过去 12 个月发布了 23 个版本。

版本发布日期距今
0.2.3latest2026/11/13今天
0.2.22026/11/094 天前
0.2.12026/10/3014 天前
0.2.02026/10/2915 天前
0.1.72026/10/2816 天前
0.1.62026/10/2618 天前

其他发布通道:alpha → 0.1.0-alpha.9。

关于 @doany-ai/forms 的常见问题

@doany-ai/forms 是什么?

The business's forms on a Doany site: a form hook and a default component that renders a form's questions and submits the answers

如何安装 @doany-ai/forms?

在项目目录运行 npm install @doany-ai/forms,或使用 pnpm add / yarn add / bun add。它还需要宿主项目提供 peer 依赖:react(^18.2.0 || ^19.0.0)、react-dom(^18.2.0 || ^19.0.0)、@doany-ai/app-core(^0.2.3)、@tanstack/react-query(^5.0.0)。npm 7 及以上与 pnpm 8 及以上默认会自动安装必需的 peer 依赖,Yarn 需要手动安装。

@doany-ai/forms 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@doany-ai/forms 支持 ESM 还是 CommonJS?

以 ES Module 形式发布,推荐使用 import 引入。在 CommonJS 项目中,Node.js 20.19+ / 22.12+ 可以直接 require() 不含顶层 await 的 ES Module,更早的版本需要改用动态 import()。

@doany-ai/forms 可以商用吗?

@doany-ai/forms 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@doany-ai/forms 的下载量有多少?

根据 npm 官方下载统计,@doany-ai/forms 在 2026/10/13 – 2026/11/11 这 30 天内被下载 2,623 次,日均约 87 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@doany-ai/forms 还在维护吗?

最新版本 0.2.3 发布于 2026/11/13(今天);至今共发布 23 个版本,其中过去 12 个月发布了 23 个;首次发布于 2026/10/09。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。