@bitakit/ui

中文文档
0.2.10发布于 2026/11/01MITTypeScriptES Module构建来源已验证近 30 天下载 2635

React bindings for BitaKit (Actions, services, slots, settings) and optional standard components.

原始使用文档
文档目录15 节
  1. First binding
  2. Provider
  3. Bind actions
  4. Read services, settings and events
  5. Plugin UI
  6. Keyboard shortcuts
  7. Translation
  8. Components and theme
  9. Testing
  10. Client authentication
  11. Source organization
  12. Awaited runtime replacement
  13. Host updates and inspection
  14. Isolate a widget render failure
  15. Host-owned plugin controls

@bitakit/ui

React bindings for the headless BitaKit runtime. The optional ./components entry provides shared standard visual components; Core remains independent of React and UI libraries. Since 0.2 this package exports only React bindings: import define* helpers and other Core names from @bitakit/core (npx @bitakit/cli migrate moves existing imports).

First binding

In an app with the generated BitaKit provider and an existing counter.increment Action:

import { Action } from '@bitakit/ui';
import { Button } from '@bitakit/ui/components';

<Action id="counter.increment">
  <Button />
</Action>;

The Action supplies the label and guarded execution. To define that Action first, follow the small Counter lesson. The provider and binding options below are reference material.

Provider

BitaKitRuntimeProvider mounts one runtime per configuration. It owns the root ActionProvider by default and reuses an existing outer provider during migration. Apps render the generated BitaKitProvider from src/bitakit.gen.tsx, which passes the discovered config; the framework adapters add navigation and query state.

PropMeaning
configThe generated configuration (createBitaKitConfig()), or an explicit one
additionalPluginsPlugins added to the configuration; an entry with an existing ID replaces it in place
controlsProp-name adapters per control kind, for example { button: (s) => ({ isDisabled: s.disabled }) }
notify(message, kind) for action feedback toasts
servicesHost services under their contract names, such as { auth }
translate, labels, uiTranslator, default copy and the standard visual mapping
actionOptionsOptions of the owned ActionProvider, such as shortcuts
isolateFailuresKeep the app running when a plugin fails to start; on in development builds

In development, a plugin that fails to start is turned off together with the plugins that depend on it, and a startup report names the plugin, the cause and the file; a page that still requires one of its services shows an explanation instead of crashing. Production fails fast.

After hydration the provider marks its startup on the document element for tools without React access, such as npx bitakit check-page and browser tests: <html data-bitakit-runtime> is starting, then ready once initial startup completed (plugins turned off in development included) or failed when the app cannot start. Server and hydration markup never carry it.

Bind actions

import { Action, ActionButton, useAction } from '@bitakit/ui';
import { Button } from '@bitakit/ui/components';

<Action id="todos.add" input={draft}><Button /></Action>          // empty child: icon + title
<Action id="todos.open"><a /></Action>                              // link for actions with href
<Action id="todos.show-done" kind="toggle"><Switch /></Action>      // checked / onCheckedChange
<ActionButton action="todos.add" input={draft} as={Button} />       // sugar on <Action>

const add = useAction('todos.add', draft);
<button {...add?.props()}>{add?.title}</button>

<Action id input kind?> merges the control props into its child: handlers are chained, class names joined and disabled combined. kind is button, link, toggle, choice or menuItem; the default is link for actions with an href, otherwise button. A render function receives the control. useAction(id, input) returns the resolved state (title, enabled, visible, pending, disabled, checked, value, href, status, error), a guarded execute(input?) and the prop getters props(), linkProps(), checkedProps(), valueProps() and menuItemProps(); it returns undefined for an unregistered action. All bindings also accept typed references such as app.actions.todos.add.

useActionGroup(group, input?) returns a group's visible actions as controls for menus, toolbars and row actions; ActionMenu renders a group with the host menu component. ActionBoundary gates children on an action's availability. useActions() lists public snapshots.

Read services, settings and events

const todos = useServiceState('todos.list', (state) => state.todos, []); // reactive
const service = useService('todos.list'); // the instance
useEvent('todos.changed', (event) => {
  /* event.data */
});
const pageSize = useSetting('todos.pageSize'); // { value, ready, writable, pending, error, set, reset }

useSetting(key) reads one setting the runtime owns and writes through it; the value is the confirmed value, disabled covers not loaded, read-only and saving. Layout and settings hosts use useSettingsCatalog() (sections derived from every plugin's settings.ts and ui/settings/page.tsx), useNavigation(slotName) (bound navigation items plus the router's pathname, link and isActive), useSlot(name, { payload }) (typed action/component/data items, payload, pending and error) and useCurrentPage() (the current plugin page with its layout name). Dialog actions name a frame kind (dialog: { component, kind: 'sheet' }); register frames on BitaKitRuntimeProvider through dialogs={{ sheet: SheetFrame }}. Two kinds work without a registered frame: dialog (the default, with a visible header) and command, shadcn's CommandDialog composition for a search palette whose content leads with its own search field; the title stays available to assistive technology.

useServiceState(name, selector?, fallback?) subscribes to the store a service exposes as state (or a subscribe/getSnapshot pair) and returns the fallback until the service and its owner are ready. useEvent subscribes to the runtime bus with component cleanup. useBitaKitApp() returns the typed app facade (app.services, app.actions, app.config, app.has()); usePluginConfig(moduleId, path, fallback) reads the config snapshot.

Plugin UI

<PluginPage pathname? fallback? unavailable? /> renders the plugin page registered for the current path and passes params (ui/pages/[id].tsx receives { id }); <PluginSlot name payload? /> renders the components plugins placed in a slot. Declared targets read owned live attachments; legacy targets retain the existing view path. Hiding an outlet never detaches its entries. Both paths retain the runtime-wide readiness barrier used by service hooks. PluginSlot passes the validated payload to each component. Action and data slots use useSlot and the owner's renderer.

<PluginSlot name payload? fallback? errorFallback?>{children}</PluginSlot>: fallback renders while no contribution is placed; children reach each contribution as a prop (a layout wraps the page). For a single slot, useSlot keeps only the candidate selectSingle picks (the app's choose entry, the app's own contribution, or the only candidate), reports ambiguous or choice-unavailable in diagnostics.reason with diagnostics.candidates, logs a tie once, and shows the selected file contribution before the runtime has started; a failing occupant renders the fallback. SlotProps<'slot.id'> types a contribution's { payload, children }. React providers from ui/providers wrap the app automatically. DialogHost and ConfirmDialog render dialog and confirm actions through the host's dialog primitives.

Keyboard shortcuts

The root ActionProvider binds shortcut metadata once per registered action using @tanstack/hotkeys. Buttons and menu mounting are irrelevant to execution; actionOptions={{ shortcuts: { scopes: ['global', 'editor'] } }} activates scopes, shortcuts: false disables binding. Hidden, disabled, pending and internal actions cannot execute. Inputs, selects, textareas and contenteditable ignore shortcuts unless allowInInputs: true. Mod means Command on macOS and Control elsewhere. The collision policy keeps the first registration and reports the conflict through onError. StandardActionMenu displays shortcuts; other renderers may use ActionShortcutLabel, which formats after hydration and never binds a key.

Translation

const t = useTranslation() inside the provider: t('feature.key', { name: 'Ada' }, 'Fallback'), t.has(key) and t.getCatalog(namespace?). The hook follows runtime translation notifications. Replace the translation integration through the Core translationService contract; there is no second locale store. Package-owned English and German messages live in locales/ and are exported as ./locales/en.json and ./locales/de.json; compose them with mergePluginMessages, where app catalogs override matching keys.

Components and theme

See the developer theme contract for one host theme shared by Shell, settings, Quick Search, native auth/organization controls and Devtools, including typography, border width, shadows, focus and motion. Define tokens after the generated root CSS import; consumer apps do not patch individual plugin selectors.

The optional ./components entry exports Button, Input, Field, Label, Badge, Checkbox, RadioGroup, RadioGroupItem, Dialog, DropdownMenu, Sidebar, NavigationMenu, Sheet, Skeleton, Tooltip, Separator, PageHeader, AppBar, StandardActionMenu, ActionSidebarNavigation, ActionNavigationMenu and standardBitaKitUI. These compose the host theme tokens and contain no app, Settings or Shell behavior. ./theme.css exports the shared theme tokens and control geometry; generated apps import it through src/bitakit.gen.css.

Testing

renderWithBitaKit(element, { plugins, config, services, ui }) from @bitakit/ui/testing mounts the element inside BitaKitRuntimeProvider, wrapped in act, and resolves after every plugin's onStart. It returns runtime, container, events.recorded, rerender(element) and unmount(). events.recorded lists { type, data } of every event emitted while mounted, startup included, in the shape mountPlugin records. plugins are added to config (an existing ID is replaced in place); services are host services such as a fake auth. A failing start rejects with the attributed startup error and leaves nothing mounted. The helper imports no DOM implementation: install jsdom's window and document as globals first, as tests/support.mjs does, and pass your own ui when the standard primitives are not wanted.

import { renderWithBitaKit } from '@bitakit/ui/testing';

const view = await renderWithBitaKit(<PluginPage pathname="/notes" />, { plugins: [notes] });
await act(async () => view.container.querySelector('button').click());
assert.deepEqual(view.events.recorded, [{ type: 'notes.added', data: { title: 'Write tests' } }]);
await view.unmount();

Headless services, actions and events are tested without React through mountPlugin from @bitakit/core/testing.

Client authentication

useAuth observes the registered AuthService and exposes its snapshot plus signIn and signOut. AuthBoundary handles pending, authenticated, unauthenticated and error rendering through host-supplied content; it uses the existing navigation binding for login. It has no visual dependency and does not enforce server authorization. See configuration and adapter examples.

Source organization

  • components/: <Action>, ActionButton, ActionLink, ActionMenu, ActionBoundary, dialog hosts, plugin pages and slots, standard primitives under components/primitives.
  • hooks/: useAction, useActionGroup, useService, useServiceState, useSetting, useSettingsCatalog, useNavigation, useSlot, useEvent, useBitaKitApp, usePluginConfig, useTranslation, useAuth.
  • providers/: BitaKitRuntimeProvider, ActionProvider, the shared contexts and the data-bitakit-runtime startup marker.
  • bindings/: keyboard shortcut registration.
  • utils/: mergeProps.
  • testing/: renderWithBitaKit, the @bitakit/ui/testing entry (needs react-dom; imports no DOM).
  • types/: host UI contracts and React presentation typing.
  • index.ts, actions/index.tsx, components.ts: public exports without implementation.

Core contains no React imports or renderer entry points; this package augments Core's opaque presentation types with React types. Run npm test --workspace @bitakit/ui.

Awaited runtime replacement

BitaKitRuntimeProvider uses Core's startup controller for fail-fast and isolation policies. Changing configuration waits for the previous runtime's complete async cleanup, including when intermediate configurations are skipped. Cleanup failure prevents automatic replacement. renderWithBitaKit(...).unmount() awaits this cleanup; recorded events include runtime.lifecycle. Runtime state is available through runtime.lifecycle.get()/watch().

Placed Action items expose item.action; pass that control to Action, ActionButton or ActionLink to preserve the outlet's mapped input. Groups remain logical queries. PluginSlot passes the validated outlet payload to contributed components.

Slot outlets accept entryKey to select a keyed component and return only entries whose optional when({ payload }) predicate matches that occurrence. Render with item.key; outlet filtering never detaches the shared entry. PluginSlot forwards the parsed payload. The existing catalog also carries each page's opaque boundary, including inheritance for additional pages.

An outlet follows only the Actions its own entries reference. A component or data slot does not render again when an Action runs or presentation is refreshed after a service change; an Actions slot renders again when one of its own Actions changes. PluginSlot memoizes each contribution on its component and payload, so a memoized payload lets contributions skip their parent's renders.

ActionControl.linkProps({ href }) lets a renderer adapt a URL for its route base/query while retaining safe URL checks, disabled guards and consistent native/client navigation.

Host updates and inspection

useBitaKit().runtime.reconcilePlugins(nextPlugins) supports explicit compatible plugin updates. Await its result from host management code. It preserves unrelated service instances and keeps unaffected pages/widgets mounted. useBitaKitReady() means initial readiness; pass a plugin ID to observe that owner. useService and useServiceState follow ready instances without detaching unchanged sources. PluginPage accepts an unavailable fallback; useSlot exposes its declared owner's phase, with failed/blocked distinct from pending. Affected pages/projections can remount; affected dialogs close before replacement. Provider config prop changes still replace the whole runtime. This does not promise file-HMR state retention.

import { useRuntimeInventory } from '@bitakit/ui';
function RuntimeStatus() {
  const snapshot = useRuntimeInventory();
  return <output>{snapshot.live?.phase}</output>;
}

The hook reads Core's shared versioned inventory with stable snapshots. It owns no application values. live is null during server rendering and hydration, because the runtime starts only in the browser; the live state follows right after hydration. Use ordinary Action/service hooks for interactive operations. App Preferences and Devtools use this same inventory; static CLI fields may be explicitly unavailable.

Isolate a widget render failure

PluginSlot isolates each component automatically. Healthy siblings remain mounted; its default fallback names the plugin/slot and uses the existing host Button for retry. Override one outlet with errorFallback={({ error, retry }) => ...} when needed. SlotRenderError includes slot, plugin, contribution and cause; default UI never prints raw causes or stacks. Reporting uses the existing provider actionOptions.onError callback.

Custom useSlot renderers can wrap each entry in SlotErrorBoundary with slot, plugin, contribution and children. Keep the entry key stable and place the boundary inside access controls. Explicit retry or a changed owner definition/component resets an error; unrelated updates and equivalent stabilized payload objects do not. Context revalidation/hiding may unmount an occurrence and discard its local error. Retry does not restart services or attach twice.

This client render boundary uses react-error-boundary; it does not handle slot payload/binding errors, server rendering, event handlers or later async failures. Suspense stays with the host. A failing custom fallback propagates normally. Core lifecycle and shared inventory are unchanged.

Host-owned plugin controls

The runtime provider registers the internal pluginManagementAction (runtime.plugins.change) after startup on the existing root ActionList. Bind it with Action/useAction and { id, operation, expectedRevision, preserve }. Disable uses the standard confirmation with the affected plugin names. Execution revalidates current state and reports safe translated feedback. pluginChangeExplanation(runtime, preview) translates typed planner reasons; raw causes remain diagnostic. This Action is absent from public action enumeration.

The optional @bitakit/ui/components entry also exports the standard Card and Item primitives. Settings surfaces share those components and the central theme; the generic root stays free of visual primitives.

版本与发布记录

@bitakit/ui 共发布 12 个版本,首次发布于 2026/10/01;过去 12 个月发布了 12 个版本。最新版本附带 npm provenance 构建来源证明,可追溯到公开的 CI 构建记录。

版本发布日期距今
0.2.10latest2026/11/017 天前
0.2.92026/10/317 天前
0.2.82026/10/2712 天前
0.2.72026/10/2514 天前
0.2.62026/10/2514 天前
0.2.52026/10/2415 天前

关于 @bitakit/ui 的常见问题

@bitakit/ui 是什么?

React bindings for BitaKit (Actions, services, slots, settings) and optional standard components.

如何安装 @bitakit/ui?

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

@bitakit/ui 支持 TypeScript 吗?

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

@bitakit/ui 支持 ESM 还是 CommonJS?

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

@bitakit/ui 可以商用吗?

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

@bitakit/ui 的下载量有多少?

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

@bitakit/ui 还在维护吗?

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