@duskmoon-dev/components

中文文档
0.6.4发布于 2026/11/13TypeScriptES Module构建来源已验证近 30 天下载 2502

The DuskMoon React component package.

原始使用文档
文档目录7 节
  1. Installation
  2. Features
  3. Usage
  4. Public Surface
  5. Settings menus, context and timeline approvals
  6. Development
  7. JSON Schema forms

@duskmoon-dev/components

The DuskMoon React component package.

Installation

bun add @duskmoon-dev/components @duskmoon-dev/core react react-dom

Import the stylesheet once in your app:

import "@duskmoon-dev/components/styles.css";

Features

  • React 19 ready: Components and examples target the current React runtime.
  • TypeScript first: Public props and helper APIs ship with declarations.
  • Modular exports: Import from the root package or from component subpaths.
  • DuskMoon styling: Component classes and CSS are backed by @duskmoon-dev/core.

Usage

import "@duskmoon-dev/components/styles.css";
import { Button } from "@duskmoon-dev/components/button";

export default function MyComponent() {
  return <Button>Hello World</Button>;
}

Root imports are also supported:

import { Button, DmTable, theme } from "@duskmoon-dev/components";

Public Surface

  • 79 standard components such as Button, Chat, OtpInput, Swap, Fab, Megamenu, ConsolePage, Table, Modal, Select, and Typography.
  • 21 DuskMoon workflow components such as DmLayout, DmSearch, DmTable, DmProTable, and DmToolbar.
  • 13 infrastructure exports including theme, version, unstableSetRender, GetProps, GetRef, and DuskMoon theme helpers.
  • Component subpath exports follow @duskmoon-dev/components/{component-id}, for example @duskmoon-dev/components/date-picker.

OtpInput uses one native input with decorative slots. Swap uses a native checkbox. Fab, Megamenu, and the mobile menu in ConsolePage use the browser's Popover API for opening, Escape dismissal, and focus restoration. These components require @duskmoon-dev/core 1.20.0 or newer.

Form.Item associates its label with a single child control, preserving an existing child id. For composite controls, pass htmlFor with the target control's id. Badge appearance="outline" emits both badge-outlined and the supported badge-outline alias.

Chat.Scroll provides a fixed-height transcript with a sticky reply rail. Pair assistant rows and indicator buttons with timeline slots 1–24; user rows do not need ticks. Chat.Status and Chat.Actions accept ordinary content and button callbacks, so the application owns generation state and metrics. The Chat docs include RTL, live states, tool statuses, message actions and the Core 1.20.6 agent, queue and long-message contracts. Use Chat.Agent with Chat.Content for unboxed assistant answers. Group activity in native Chat.Work, Chat.WorkSummary and Chat.WorkBody disclosures, keeping the final answer outside them. User messages use Chat placement="end" and Chat.Bubble; opt into collapsible for automatic six-line measurement, resize/text updates, accessible expansion and focus cleanup. The package now requires Core 1.21.1, which supplies the shared Composer editor, attachment, suggestion and status parts.

Chat.InputArea attaches a controlled Chat.Queue to native Chat.ComposerForm, Chat.ComposerInput, Chat.ComposerActions and Chat.ComposerHint primitives. Queue items have stable id/text values and optional { src, alt } attachments. onSteer and onRemove request application actions; the component never dequeues locally. Supply pendingIds while awaiting acknowledgment and update items only after success. Restore focus after removing a focused row, and announce queue changes once in an application status region. The application owns Enter/Shift+Enter/steering, IME guards, transport, ordering and failure policy. The interactive docs demonstrate those actual state changes.

The existing controlled Chat.Composer retains its div ref, attachment and completion APIs, revision-safe asynchronous draft clearing, and Cmd/Ctrl+Enter submission with ordinary Enter for newlines. Use the native composer primitives when form submission and the reference's Enter-to-queue contract are needed. Supply scope={{ contextKey, conversationId }} when reusing a Composer between repositories or conversations: a late acceptance or error from the previous scope cannot clear the current draft or call its error handler. Pending admission leaves editing available. The host still owns the controlled draft and files.

Composer actions use icon buttons with accessible names and title hints in both layouts. attachLabel, sendLabel and stopLabel customize those names and hints. Attachment retry/cancel/remove and default queue actions follow the same pattern. The shared composer padding is 0.5rem.

Set compact on Chat.Composer, ChatSessionComposer or RichChatComposer for a compact toolbar with accessible icon buttons and a horizontal strip of simple ready-image thumbnails. Mixed files, preparation, errors and custom attachment renderers retain full rows. The default wrapping layout remains available. Compose host-owned settings through children, using Core's native popover chat-composer-menu classes when a configuration menu is needed. The compact + opens a native Add menu for files, explicit draft save/reload and optional addMenu content. Routine save/admission status is announced without adding rows. Its primary button sends an eligible draft or stops the running turn when submission is unavailable; the default layout retains separate controls.

Root and /chat exports also provide editor-independent ComposerDocument, ComposerDraft, submission/receipt, provider, attachment and queue types. readComposerDocument and readComposerDraft preserve incompatible input for recovery; snapshotComposerDocument and snapshotComposerSubmission validate, copy and freeze portable JSON. exportComposerText and exportComposerMarkdown project references through plainText and retain quote attribution. Invalid snapshots throw instead of silently discarding unsupported fields. Portable examples are published at @duskmoon-dev/components/chat/contracts.v1.json. ChatSessionComposer uses those contracts with a controlled semantic document, application-owned ComposerSessionHost, scope and draft key. The original string/File-based Chat.Composer remains available with its existing ref and callbacks. The optional editor is imported separately:

import { useState } from "react";
import type {
  ComposerDocumentUpdate,
  ComposerScope,
  ComposerSessionHost,
} from "@duskmoon-dev/components/chat";
import { RichChatComposer } from "@duskmoon-dev/components/chat/rich";

export function PromptBox({
  host,
  scope,
}: {
  host: ComposerSessionHost;
  scope: ComposerScope;
}) {
  const [value, setValue] = useState<ComposerDocumentUpdate>({
    document: { schemaVersion: 1, content: [{ type: "text", text: "" }] },
    revision: 0,
    origin: "replace",
    originId: "initial-draft",
  });
  return (
    <RichChatComposer
      value={value}
      onDocumentChange={setValue}
      scope={scope}
      draftKey="main-prompt"
      host={host}
    />
  );
}

Use ChatSessionComposer from root or /chat with the same props for text-only semantic drafts. RichChatComposer is available only from /chat/rich. Keep a single compatible Lexical runtime in consumers that also import Lexical directly; ordinary root and /chat imports do not initialize it.

Echo each onDocumentChange update unchanged. The rich editor retains selection and undo for echoes; explicit replace, restore, scope switch and clear operations reset its history. Remounts restore semantic content, not undo history. Its ref inserts references, quotes, text and claimed commands without exposing Lexical. References and quotes are atomic. Claimed command identity stays distinct from editable skill text. Rich copy/cut includes portable semantic content and plain text; rich paste creates fresh occurrence IDs, while external paste uses plain text. Mixed file/text paste preserves text and validates files through the shared picker/paste/drop policy. Unsupported content is retained for recovery rather than silently flattened if the editor cannot load or a draft is incompatible.

Keep host.recovery = createComposerRecoveryOwner() outside the editor's mount lifetime. Pending and unknown attempts retain the exact immutable submission snapshot and ID. An accepted prompt reports actual delivery and persistence; an accepted command returns a result without claiming a queued prompt or turn. Unknown outcomes preserve the draft: reconcile using host.reconcile before any explicit host.authorizeRetry of the same snapshot. Admission is not completed generation. Stop is a separate onStop action and remains available while file preparation or admission is pending. Host capabilities determine which active-turn intents and controls are available.

Optional attachmentOptions validates and prepares each file occurrence, with cancellation, progress, failure and retry. Only ready portable handles enter a submission; runtime File objects and preview URLs stay out of saved documents. Recovered files require reattachment unless the host explicitly confirms handle recovery. A stable optional draftAdapter loads scoped drafts and supports explicit Save draft; storage durability, retention and coordination with other writers belong to the application. The UI never automatically overwrites incompatible stored content.

providers supply scoped asynchronous candidates, disabled reasons, directory drill-down and insert/invoke actions. In compact mode, results open in a native popover above the composer, with grouped rows, keyboard navigation, filtering and native dismissal. Optional provider icon and sourceLabel decorate the rows; they never enter portable documents or submissions. Use a leading / provider for skills and commands: skills insert editable instructions, rich claimed commands retain their identity until submission, and invoke actions call onProviderInvoke independently. The plain editor supports text skills and host invocation; claimed commands require the rich editor. The host resolves and authorizes references, quote provenance, commands and attachment handles; portable validity is not authorization. ChatAuthoritativeQueue renders confirmed projections and requests revision-checked edit/remove/promote mutations. Apply canonical projections through onProjection; an optimistic click does not confirm membership or order. ComposerSettings likewise keeps values controlled until the host acknowledges and applies a request. The host owns transport, model execution, uploads, idempotency, persistence and execution policy. The Chat documentation includes interactive fake-host examples with real local state transitions; their memory storage survives editor remounts only and makes no durable delivery claim.

Settings menus, context and timeline approvals

Core 1.21.5 or newer is required. Import these APIs from the package root or @duskmoon-dev/components/chat; import @duskmoon-dev/components/styles.css for the complete Core and React stylesheet.

ComposerSettings variant="menu" uses the same serialized request, revision, abort and acknowledgment protocol as the default select presentation. Choice metadata accepts description, tone="warning" and icon. An ordered setting renders its choices as a range and requests a change on pointer/key release; defaultValue enables reset. groups combines settingIds under a trigger, with optional nestedSettingIds and appearance="access" | "model". Values remain controlled by the host, including rejected or stale acknowledgments. The menus close after choosing; status feedback remains announced outside them.

Both Chat.Composer and ChatSessionComposer accept context, rendered first inside the composer. Compose Chat.ComposerContext, Chat.ComposerContextItem, Chat.ComposerContextEnd and Chat.ComposerConnection there. Items accept an icon and an optional action with a required accessible label and onClick. Connection state is connected, connecting or disconnected; label customizes its visible status. Core joins the bar to default, compact and ghost composer surfaces using the shared inset; no additional consumer CSS is needed.

Place Chat.Request next to Chat.Tool inside Chat.Agent. Supply a title, detail children and ordered actions (id, label, onSelect, optional tone="primary" | "danger"). The host owns pending while deciding and state: pending, approved, approved-for-session, declined, expired or answer-elsewhere. Only the pending state renders actions; pending disables them while a decision is in flight. explanation and statusLabel provide host context and localized status. Status updates are polite; incoming updates never move focus out of the composer. If resolution removes a focused decision, focus stays on that request without scrolling.

Core 1.21.1 still has a known Moonlight contrast gap for neutral outlined/ghost labels (duskmoonui#78). The upstream fix is required before claiming complete palette accessibility.

Existing Chat.Bubble streaming remains usable; Chat.Content streaming is the preferred agent presentation. No public Chat API is removed or deprecated.

Normal Tooltip button/link triggers use native interestfor, hint popovers and CSS anchors. Browsers must support those APIs; there is no feature polyfill. Keep essential help inline. Controlled open uses native manual popovers; non-invoker children retain the older imperative compatibility adapter. Custom trigger components must forward interestfor, style and aria-describedby to their native control. Tooltip content is brief and noninteractive; put links, buttons and confirmation forms in Popover/Popconfirm. The existing deprecated arrow prop remains accepted; native surfaces are arrowless.

import { Chat } from "@duskmoon-dev/components/chat";
import { useId } from "react";

export function Conversation() {
  const replyId = useId();
  async function copyReply() {
    await navigator.clipboard.writeText("The build passed.");
  }

  return (
    <Chat.Scroll style={{ height: 320 }} aria-label="Conversation">
      <Chat.ScrollTrack aria-label="Assistant replies">
        <Chat.ScrollIndicator timeline={1} targetId={replyId} />
      </Chat.ScrollTrack>
      <Chat.ScrollBody>
        <Chat.Agent id={replyId} timeline={1}>
          <Chat.Content>The build passed.</Chat.Content>
          <Chat.Status>
            <Chat.StatusItem>Completed</Chat.StatusItem>
            <Chat.StatusItem>
              <Chat.StatusValue>42</Chat.StatusValue> token/s
            </Chat.StatusItem>
          </Chat.Status>
          <Chat.Actions hover>
            <button type="button" onClick={copyReply}>
              Copy reply
            </button>
          </Chat.Actions>
        </Chat.Agent>
      </Chat.ScrollBody>
    </Chat.Scroll>
  );
}

Modal requires a modern browser with native <dialog> support. It opens and closes through showModal() and close(), while its forwarded ref and div props remain on the inner modal box. Controlled callers must update open in onCancel; maskClassName is applied to the dialog for ::backdrop styling. The static Modal.confirm, info, success, error, and warning handles remain exported but do not render a service surface yet.

Development

# Build the package
bun run build

# Run tests
bun run test

# Typecheck
bun run typecheck

# Check parity manifest coverage
bun run parity:components

JSON Schema forms

@duskmoon-dev/components/json-schema-form exports JsonSchemaForm, compileForm, JsonSchemaWidget, and the RenderableSchema, CompiledForm, FormValue, JsonValue, FormErrors, FormValidationResult, JsonSchemaFormProps, JsonSchemaWidgetProps, and widget contract types. Ajv and ajv-formats are installed as package runtime dependencies. The package stylesheet includes the renderer layout. Import these APIs from the explicit subpath; schema compilation is kept separate from the root component bundle.

import { useState } from "react";
import "@duskmoon-dev/components/styles.css";
import {
  JsonSchemaForm,
  type FormErrors,
  type FormValue,
  type RenderableSchema,
} from "@duskmoon-dev/components/json-schema-form";

const schema: RenderableSchema = {
  type: "object",
  required: ["name"],
  properties: {
    name: { type: "string", title: "Name", minLength: 1 },
    age: { type: "integer", title: "Age", minimum: 0 },
  },
};

export function ProfileIsland({ send }: { send: (value: FormValue) => void }) {
  const [value, setValue] = useState<FormValue>({ name: "Ada" });
  const [errors, setErrors] = useState<FormErrors>({});
  return (
    <JsonSchemaForm
      schema={schema}
      value={value}
      onChange={setValue}
      errors={errors}
      onErrorsChange={setErrors}
      onSubmit={send}
    />
  );
}

Mount the React root inside a LiveView element with a stable id and phx-update="ignore". Parse the backend's schema JSON and call compileForm when accepting untrusted input; catch compilation errors before rendering. Pass the returned object as compiled instead of schema to compile once. Send the validated object from onSubmit through the island's LiveView hook; backend validation is still required. Use a new React key when replacing a schema and starting a fresh form.

value and onChange control values. Omit value for internal state, optionally seeded by defaultValue. Reset restores defaultValue or schema defaults, notifies onChange, clears validation errors, and calls onReset. Backend errors merge with local errors and use escaped JSON Pointer keys such as /address/email (the empty key denotes a form error). Clear backend errors in onErrorsChange, which receives an empty object on edits/reset and validation errors on submission. onSubmit runs only for valid values and receives a copied JSON object with numbers and booleans preserved.

The supported schema is a strict JSON Schema draft 2020-12 subset: object roots, nested objects, homogeneous arrays, primitive fields/enums/defaults, required fields, additionalProperties: false, string constraints/formats, numeric constraints, and array/object size constraints. References, combinators, nullable types, and arbitrary keywords are rejected. Validation does not coerce, remove, or default submitted values.

Use x-widget for explicit component selection, x-options for typed option labels/hierarchies, and x-widget-options for orientation, length, count, allowHalf, min, max, and step where supported. widgetNames, WidgetName, WidgetOption, WidgetConfig, and OrientationWidgetConfig expose this contract; compileForm checks widget/type/config compatibility. Upload widgets submit JSON file metadata (name, size, type, lastModified), with file transfer owned by the application.

版本与发布记录

@duskmoon-dev/components 共发布 21 个版本,首次发布于 2026/06/11;过去 12 个月发布了 21 个版本。最新版本附带 npm provenance 构建来源证明,可追溯到公开的 CI 构建记录。

版本发布日期距今
0.6.4latest2026/11/13今天
0.6.32026/11/121 天前
0.6.22026/11/085 天前
0.6.12026/11/085 天前
0.6.02026/11/076 天前
0.5.72026/11/076 天前

关于 @duskmoon-dev/components 的常见问题

@duskmoon-dev/components 是什么?

The DuskMoon React component package.

如何安装 @duskmoon-dev/components?

在项目目录运行 npm install @duskmoon-dev/components,或使用 pnpm add / yarn add / bun add。它还需要宿主项目提供 peer 依赖:react(>=19.0.0)、react-dom(>=19.0.0)、@duskmoon-dev/core(>=1.21.5)。npm 7 及以上与 pnpm 8 及以上默认会自动安装必需的 peer 依赖,Yarn 需要手动安装。

@duskmoon-dev/components 支持 TypeScript 吗?

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

@duskmoon-dev/components 支持 ESM 还是 CommonJS?

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

@duskmoon-dev/components 可以商用吗?

@duskmoon-dev/components 使用 未声明 许可。package.json 没有声明许可证。没有许可证的代码默认保留全部权利,商用前请联系作者或查看仓库中的 LICENSE 文件。以上为概括,不构成法律意见,请以许可原文为准。

@duskmoon-dev/components 的下载量有多少?

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

@duskmoon-dev/components 还在维护吗?

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