@dugyu/luna-studio

中文文档
0.2.0发布于 2026/05/17Apache-2.0TypeScriptES Module构建来源已验证近 30 天下载 5519

Reusable multi-stage choreography layer for the L.U.N.A project

原始使用文档
文档目录20 节
  1. Installation
  2. Public API
  3. Quick Start
  4. Core Concepts
  5. StudioStage
  6. StudioResolvedStage
  7. StudioStageSlice
  8. StudioLayout
  9. StudioResolvedLayout
  10. StudioViewMode
  11. Interaction Model
  12. InteractionParams
  13. interactionTarget
  14. onInteraction
  15. onLynxRuntimeCall
  16. resolveFocusKey
  17. buildStageGlobalProps
  18. Choreography API
  19. Scope
  20. Intended Consumers

luna-studio

@dugyu/luna-studio is a reusable multi-stage choreography layer for Lynx-based previews and comparison canvases.

It packages the rendering, layout, focus, and interaction plumbing needed for:

  • compare / focus / lineup stage arrangements
  • shared or per-stage theme application
  • host-side stage-container interaction handling
  • embedded Lynx runtime callback forwarding
  • app-defined focus resolution and global-props injection

It does not ship an application shell.

The following app-level pieces stay outside this package:

  • Studio
  • MenuBar
  • StudioFrame
  • keyboard shortcut glue
  • demo-specific runtime handling
  • app-owned layout catalogs and business metadata

If you only need a single device-framed preview, use @dugyu/luna-stage directly.

Installation

npm i @dugyu/luna-studio @lynx-js/web-core motion react

luna-studio depends on @dugyu/luna-stage internally for its rendering primitives and expects the Lynx Web runtime to be available through @lynx-js/web-core.

Public API

Current public exports from @dugyu/luna-studio are grouped by components and types:

  • Components
    • Choreography
  • Studio model types
    • StudioStage
    • StudioStageSlice
    • StudioResolvedStage
    • ResolveStudioLayoutParams
    • StageGlobalPropsBuilder
    • StudioGridSpec
    • StudioLayout
    • StudioResolvedLayout
    • StudioModeGrid
    • StudioViewMode
    • LunaThemeKey
    • LunaThemeMode
    • LunaThemeVariant
  • Choreography types
    • ChoreographyBaseProps
    • ChoreographyProps
    • ChoreographyInteractionProps
    • ChoreographyViewProps
    • InteractionParams
    • InteractionTarget
    • FocusKeyResolver
  • Lynx bridge types
    • LynxRuntimeCall
  • Helpers
    • resolveStudioLayout
    • indexResolvedLayout
    • getPayloadString

ChoreographyView and StudioLynxStage exist inside the package as internal implementation layers, but they are not part of the public component API.

Quick Start

import { Choreography } from '@dugyu/luna-studio';
import type { StudioModeGrid, StudioResolvedLayout } from '@dugyu/luna-studio';

const modeGrid: StudioModeGrid = {
  compare: { cols: 2, rows: 1 },
  focus: { cols: 3, rows: 1 },
  lineup: { cols: 3, rows: 1 },
};

const layout: StudioResolvedLayout = {
  compare: [
    {
      id: 'button-light',
      entry: 'ActButton',
      theme: 'luna-light',
      focusKey: 'button',
      style: { gridColumn: '1 / span 1', gridRow: '1 / span 1' },
    },
    {
      id: 'button-dark',
      entry: 'ActButton',
      theme: 'luna-dark',
      focusKey: 'button',
      style: { gridColumn: '2 / span 1', gridRow: '1 / span 1' },
    },
  ],
  focus: [
    {
      id: 'button-focus',
      entry: 'ActButton',
      theme: 'luna-light',
      focusKey: 'button',
      style: { gridColumn: '2 / span 1', gridRow: '1 / span 1' },
    },
  ],
  lineup: [
    {
      id: 'button-lineup',
      entry: 'ActButton',
      theme: 'lunaris-dark',
      focusKey: 'button',
      style: { gridColumn: '2 / span 1', gridRow: '1 / span 1' },
    },
  ],
};

export function Demo() {
  return (
    <div className='size-full'>
      <Choreography
        bundleRoot='/bundles/'
        layout={layout}
        modeGrid={modeGrid}
        viewMode='compare'
        themeKey='lunaris-dark'
        interactionTarget='content'
      />
    </div>
  );
}

If your host prefers an authoring model that separates a resource pool (entry/bundleRoot/theme defaults) from per-mode layout overrides, you can build the resolved layout with resolveStudioLayout:

import { resolveStudioLayout } from '@dugyu/luna-studio';
import type {
  ResolveStudioLayoutParams,
  StudioLayout,
  StudioStage,
} from '@dugyu/luna-studio';

const stagePool: Record<string, StudioStage> = {
  Button: { id: 'Button', entry: 'ActButton', theme: 'luna-light' },
};

const layoutSpec: StudioLayout = {
  compare: [{ id: 'Button', style: { gridColumn: '1 / 2', gridRow: '1 / 2' } }],
  focus: [{ id: 'Button', style: { gridColumn: '1 / 2', gridRow: '1 / 2' } }],
  lineup: [{ id: 'Button', style: { gridColumn: '1 / 2', gridRow: '1 / 2' } }],
};

const resolvedLayout = resolveStudioLayout(
  {
    stagePool,
    layoutSpec,
  } satisfies ResolveStudioLayoutParams,
);

Core Concepts

StudioStage

StudioStage is the resource/pool definition for a stage (resource locator + defaults).

type StudioStage = {
  id: string;
  entry: string;
  theme: LunaThemeKey;
  bundleRoot?: string;
  focusKey?: string;
};

Notes:

  • entry and bundleRoot are the resource locator used to locate the Lynx bundle.
  • theme and focusKey are stage-level defaults used during layout resolution.

StudioResolvedStage

StudioResolvedStage is the per-stage data boundary consumed by the choreography layer.

type StudioResolvedStage = {
  id: string;
  className?: string;
  style?: CSSProperties;
  bundleRoot?: string;
  focusKey?: string;
  entry: string;
  theme: LunaThemeKey;
};

Notes:

  • id is the stable stage identity used for layout, motion, and interaction correlation.
  • className and style apply to the stage container.
  • bundleRoot can be provided per-stage; otherwise Choreography.bundleRoot may act as a host-level fallback.
  • app-specific metadata should live in app-local extension types rather than reopening the public StudioResolvedStage contract.

StudioStageSlice

StudioStageSlice is the per-mode layout projection for a stage. It references a pool stage by id and carries host-side overrides for that mode.

type StudioStageSlice = {
  id: string;
  className?: string;
  style?: CSSProperties;
  theme?: LunaThemeKey;
  focusKey?: string;
};

Notes:

  • Slice ids must be unique within the same StudioViewMode. Choreography uses the resolved stage.id as the stable render identity (React key, Motion layout id, and emitted interaction.stageId).
  • To render the same Lynx entry multiple times, define multiple StudioStage pool items with different id values but the same entry.

StudioLayout

StudioLayout is the authoring layout specification for the three built-in presentation modes. Each item references a stage by id and carries host-side overrides (e.g., className, style, theme, focusKey).

type StudioLayout = Record<
  'compare' | 'focus' | 'lineup',
  StudioStageSlice[]
>;

StudioResolvedLayout

StudioResolvedLayout contains one stage list for each built-in presentation mode.

type StudioResolvedLayout = Record<
  'compare' | 'focus' | 'lineup',
  StudioResolvedStage[]
>;

modeGrid can optionally provide the grid dimensions for each mode when the host wants the container layout to be grid-driven.

StudioViewMode

StudioViewMode selects which layout slice is active.

ModeBehavior
compareRenders multiple stages side-by-side and allows per-stage theme
focusRenders a shared theme with 3D focal emphasis
lineupRenders a shared theme in the lineup arrangement

Interaction Model

InteractionParams

InteractionParams is the normalized interaction envelope used by choreography callbacks.

type InteractionParams =
  | {
    target: 'stage';
    stageId: string;
    entry: string;
    payload?: unknown;
    containerEvent: MouseEvent | PointerEvent;
  }
  | {
    target: 'content';
    stageId: string;
    entry: string;
    payload?: unknown;
    runtimeCall: LynxRuntimeCall;
  };

Notes:

  • target: 'stage' means the interaction is attributed to the host-side stage layer; the raw browser payload is exposed as containerEvent.
  • target: 'content' means the interaction is attributed to embedded Lynx content; the raw Lynx runtime payload is exposed as runtimeCall.
  • containerEvent.type distinguishes host-side click / pointer variants.
  • runtimeCall.name distinguishes content-side interaction kinds emitted through onNativeModulesCall.

interactionTarget

interactionTarget controls which layer receives pointer interaction first:

  • 'content': embedded Lynx content is interactive
  • 'stage': the outer stage container is interactive

onInteraction

Receives the normalized choreography interaction envelope.

type OnInteraction = (interaction: InteractionParams) => unknown;

Use this as the primary high-level callback when you want one place to observe both host-side stage-container interactions and embedded-content runtime callbacks. The concrete raw payload is available as interaction.containerEvent or interaction.runtimeCall, depending on interaction.target.

onLynxRuntimeCall

Receives raw runtime callbacks emitted from embedded Lynx content.

type OnLynxRuntimeCall = (call: LynxRuntimeCall) => unknown;

type LynxRuntimeCall = {
  entry: string;
  channel: string;
  name: string;
  data: unknown;
};

Use this only when you need the original Lynx bridge / RPC callback directly, rather than the normalized onInteraction abstraction.

This keeps the package surface runtime-neutral:

  • no low-level moduleName leak
  • channel remains the host-facing source identifier
  • app-specific interpretation stays outside the package

resolveFocusKey

resolveFocusKey maps a normalized interaction back to a stage-level focusKey.

type FocusKeyResolver = (
  interaction: InteractionParams,
) => string | undefined;

This is the app-owned bridge between generic choreography interactions and business-specific focus semantics.

buildStageGlobalProps

buildStageGlobalProps lets the host derive app-specific Lynx global props for each rendered stage.

type StageGlobalPropsBuilder = (params: {
  stage: StudioStage;
  viewMode: StudioViewMode;
  activeFocusKey: string;
  focusKey: string | undefined;
}) => Record<string, unknown> | undefined;

The two focus-related fields serve different roles:

  • activeFocusKey is the current choreography-wide active focus identity.
  • focusKey is the focus identity of the specific stage currently being rendered.

In other words, activeFocusKey answers "which focus target is currently active?", while focusKey answers "which focus target does this particular stage belong to?".

This makes it easy to derive stage-local props such as:

  • whether the current stage is the active/focused stage
  • which shared component or semantic key is currently active elsewhere in the choreography
  • app-specific flags that depend on both the rendered stage and the global focus state

Choreography API

Choreography is the main public component in this package.

Internally, it wraps ChoreographyView, which is the lower-level renderer responsible for layout, focus state, interaction normalization, and Lynx-stage wiring.

type ChoreographyProps = Omit<ChoreographyViewProps, 'mode'> & {
  viewMode?: StudioViewMode;
};

Behavior notes:

  • viewMode defaults to 'compare'
  • themeKey defaults to 'lunaris-dark'
  • interactionTarget defaults to 'content'
  • focusKey is read directly from each resolved stage in layout (i.e., from the StudioResolvedLayout stage items)
  • bundleRoot can provide a choreography-level fallback; per-stage resource roots are taken from each resolved stage in layout
  • layout is required and should already be fully resolved by the host app

Scope

This package covers the reusable choreography layer:

  • public choreography types and data model
  • grid-driven multi-stage layout and focus presentation
  • normalized stage/content interaction handling
  • the internal ChoreographyView renderer
  • the internal Lynx runtime adapter used by Choreography

This package does not provide:

  • a top-level Studio shell component
  • built-in demo layout data
  • app chrome such as MenuBar
  • app-level keyboard bindings
  • demo-specific runtime event parsing
  • a public lynx-stage subpath export

Intended Consumers

Use @dugyu/luna-studio when you need a reusable multi-stage choreography layer but want to keep your app shell, layout data, business semantics, and event handling local:

  • documentation sites embedding multiple Lynx entries
  • design system comparison canvases
  • host apps that need both stage-container interactions and Lynx runtime callbacks
  • future lynx-website integration

版本与发布记录

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

版本发布日期距今
0.2.0latest2026/05/175 个月前
0.1.32026/05/165 个月前
0.1.22026/05/165 个月前
0.1.12026/05/076 个月前
0.1.02026/05/066 个月前
0.0.0-oidc-bootstrap.0oidc-bootstrap2026/05/066 个月前

关于 @dugyu/luna-studio 的常见问题

@dugyu/luna-studio 是什么?

Reusable multi-stage choreography layer for the L.U.N.A project

如何安装 @dugyu/luna-studio?

在项目目录运行 npm install @dugyu/luna-studio,或使用 pnpm add / yarn add / bun add。它还需要宿主项目提供 peer 依赖:@lynx-js/web-core(>=0.20.0)、motion(>=12.23.16)、react(>=18.0.0)。npm 7 及以上与 pnpm 8 及以上默认会自动安装必需的 peer 依赖,Yarn 需要手动安装。

@dugyu/luna-studio 支持 TypeScript 吗?

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

@dugyu/luna-studio 支持 ESM 还是 CommonJS?

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

@dugyu/luna-studio 可以商用吗?

@dugyu/luna-studio 使用 Apache-2.0 许可。宽松许可:允许商业使用、修改和再分发,并附带明确的专利授权;分发时需保留许可与 NOTICE 文件,修改过的文件要注明改动。以上为概括,不构成法律意见,请以许可原文为准。

@dugyu/luna-studio 的下载量有多少?

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

@dugyu/luna-studio 还在维护吗?

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