@cut-workbench/workbench 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -5,22 +5,55 @@
5
5
 
6
6
  ## 安装
7
7
 
8
+ **只需要装这一个包**:
9
+
8
10
  ```bash
9
- npm i @cut-workbench/workbench @cut-workbench/core @cut-workbench/react react react-dom
10
- # 引擎依赖(预览 / 导出必需)
11
- npm i @webav/av-cliper @webav/av-canvas @webav/av-recorder
11
+ npm i @cut-workbench/workbench
12
12
  ```
13
13
 
14
- `@webav/*`、`react`、`react-dom`、`zustand` 是 peer 依赖,由宿主提供。
15
- `@webav/*` 必须与 core 用同一份实例,否则 clip 状态会互相破坏。
14
+ `@cut-workbench/core`、`@cut-workbench/react` 以及引擎依赖
15
+ `@webav/av-cliper`、`@webav/av-canvas`、`@webav/av-recorder` 都是它的
16
+ **dependencies**,会一起装上 —— 不需要你再显式安装,也不需要知道有这些东西。
17
+
18
+ 选装(按需):
19
+
20
+ | 想要什么 | 装什么 |
21
+ |---|---|
22
+ | 只取纯逻辑,不渲染 UI | `npm i @cut-workbench/core` |
23
+ | 只要 React hooks,自己写 UI | `npm i @cut-workbench/core @cut-workbench/react` |
24
+ | Electron 宿主桥(「下载」→「保存到指定路径」) | `npm i @cut-workbench/host-electron` |
25
+ | 需要控制 React 实例 | 宿主自己提供 `react` / `react-dom` / `zustand`(见下) |
26
+
27
+ ### 关于 `react` / `react-dom` / `zustand`
28
+
29
+ 这三个是 **peer 依赖**,由宿主提供,库不会替你装 —— 一旦出现两份 React,
30
+ 宿主会碰到 `Cannot read properties of null (reading 'useContext')`。项目已有
31
+ React 时无需任何额外操作。
32
+
33
+ `zustand` 同理:`@cut-workbench/react` 直接 import 它,两份实例会让订阅错乱,
34
+ 所以必须由宿主提供(React 项目通常已有)。
35
+
36
+ ### 为什么 WebAV 是 dependencies 而不是 peer
37
+
38
+ `@webav/*` 是**运行时必需**的:没有 `av-cliper` / `av-canvas` 就没有预览和
39
+ 导出,功能直接不可用。所以这个包选择把它们带上,让「装一个包就能用」成立。
40
+
41
+ `@cut-workbench/core` 则相反 —— 它的定位是「只取纯逻辑」,有人只要 timeline
42
+ 模型和工程序列化,不该被迫装 WebAV,所以 core 那边是 `optional` peer。
43
+
44
+ > ⚠️ 0.1.0 的 WebAV 是 peer,只靠 npm 7+ 自动安装 peer 的隐式行为碰巧能跑;
45
+ > **pnpm / yarn 不会自动装 peer**,在它们下面构建会报
46
+ > `[MISSING_EXPORT] "Combinator" is not exported by "__vite-optional-peer-dep:…"`。
47
+ > 0.2.0 起改为 dependencies,与包管理器无关。
16
48
 
17
49
  ## React 项目
18
50
 
51
+ **推荐:用 `/auto` 子入口**,一行自带样式:
52
+
19
53
  ```tsx
20
54
  import { createEditor } from '@cut-workbench/core/engine'
21
55
  import { CutEditorProvider } from '@cut-workbench/react'
22
- import { CutWorkbench, CutWorkbenchProvider } from '@cut-workbench/workbench'
23
- import '@cut-workbench/workbench/style.css'
56
+ import { CutWorkbench, CutWorkbenchProvider } from '@cut-workbench/workbench/auto'
24
57
 
25
58
  const editor = createEditor()
26
59
 
@@ -33,11 +66,77 @@ createRoot(el).render(
33
66
  )
34
67
  ```
35
68
 
69
+ **根入口不自带样式** —— 给已经有自己的构建流程、需要控制 CSS 顺序
70
+ (例如要压过某条全局重置)的场景:
71
+
72
+ ```tsx
73
+ import { CutWorkbench, CutWorkbenchProvider } from '@cut-workbench/workbench'
74
+ import '@cut-workbench/workbench/style.css' // ← 必须自己引,忘了就是无样式
75
+ ```
76
+
77
+ 两者的 API 完全一致,`/auto` 只是多了一行 `import './cut-workbench.css'`。
78
+ **不要同时引两个入口** —— 样式会被引入两次(作用域化之后是幂等的,但没必要)。
79
+
80
+ ## 布局
81
+
82
+ ```
83
+ ┌───────────────────────────────────────────┐
84
+ │ 顶栏 │
85
+ ├──────────┬──────────────┬─────────────────┤
86
+ │ 素材库 │ 预览 │ 属性 │ ← 上半区,可纵向滚动
87
+ ├──────────┴──────────────┴─────────────────┤
88
+ │ 时间轴(横跨整行) │ ← 下栏
89
+ └───────────────────────────────────────────┘
90
+ ```
91
+
92
+ 时间轴**通栏占满底部一行**,不随左右面板宽度变化。左上「素材」、中「预览」、
93
+ 右「属性」三栏可拖拽调整,面板内的内容各自滚动。
94
+
95
+ > 0.2.0 之前时间轴被塞在中间列里,左右面板一拉宽就把它挤窄,工具条按钮会
96
+ > 溢出到属性面板上;窄列下播放控制条的时间码也会被裁掉。现在布局与控件都有
97
+ > 容器级自适应(按容器宽度分档收起次要控件,而不是按视口宽度),
98
+ > `scripts/layout-test.mjs` 守着这两条。
99
+
100
+ ## 顶栏定制
101
+
102
+ 内置顶栏分三层可定制,由浅到深:
103
+
104
+ **1. 改文案** —— `labels`:
105
+
106
+ ```tsx
107
+ <CutWorkbench toolbar={{ labels: { export: '导出成片', untitledProject: '新建项目' } }} />
108
+ ```
109
+
110
+ **2. 改局部** —— `brand` / `title` / `status` / `actions`:
111
+
112
+ ```tsx
113
+ <CutWorkbench
114
+ toolbar={{
115
+ brand: <MyLogo />, // 默认**没有**品牌区,避免露出本库名字
116
+ title: null, // 不要工程名区
117
+ status: <AutoSaveBadge />, // 默认「空工程 · 16:9」
118
+ actions: <MyShareButton />, // 追加在右侧按钮组末尾
119
+ editableTitle: true, // 双击工程名重命名(走撤销栈)
120
+ showCapabilityBadge: false, // 隐藏引擎能力徽标
121
+ }}
122
+ />
123
+ ```
124
+
125
+ **3. 整条替换** —— `slots.topBar`(优先级最高):
126
+
127
+ ```tsx
128
+ <CutWorkbench slots={{ topBar: <MyHeader /> }} /> // 传 null 表示完全不要顶栏
129
+ ```
130
+
131
+ > `slots.topBar` 会替换**整条**顶栏,撤销/导出等内置动作一并由宿主负责;
132
+ > `toolbar` 只换其中一块,其余保留。可运行示例见 playground 的
133
+ > `/toolbar-demo.html`。
134
+
135
+
36
136
  ## 非 React 项目
37
137
 
38
138
  ```js
39
- import { initCutWorkbench } from '@cut-workbench/workbench'
40
- import '@cut-workbench/workbench/style.css'
139
+ import { initCutWorkbench } from '@cut-workbench/workbench/auto'
41
140
 
42
141
  const wb = initCutWorkbench('#app', { theme: 'dark', namespace: 'my-project' })
43
142
  await wb.whenReady()
@@ -58,6 +157,51 @@ UMD 单文件已把 React、WebAV、Worker 与样式全部内联,暴露 `windo
58
157
  > ⚠️ React 19 不再发布 UMD 产物,因此 UMD 版本内置了一份 React。有任何构建工具时
59
158
  > 都优先选 ESM。
60
159
 
160
+ ## 类型与依赖缺失
161
+
162
+ 如果引擎依赖没解析到(例如被 npm 的 hoisting 拔掉、或在 pnpm 下版本冲突),
163
+ 工作台不会白屏:能力探测会把「缺依赖」和「浏览器不支持」分开报告,
164
+ 阻断页直接给出可执行的安装命令:
165
+
166
+ ```
167
+ 缺少 @webav/av-cliper,请执行 npm i @webav/av-cliper @webav/av-canvas @webav/av-recorder
168
+ ```
169
+
170
+ 注意缺失依赖只在**构建期**解析 —— 装完必须重新跑一次构建,刷新浏览器没用。
171
+
172
+ ## 关掉能力实验室(可选)
173
+
174
+ `config.features.lab` 是**运行时**开关,默认 `false`。lab 本体是独立子入口
175
+ `@cut-workbench/workbench/lab`,主包对它做动态 import —— 所以默认配置下
176
+ lab 的代码(含 `@webav/av-recorder`)不会加载,但打包器仍会产出一个约 33 KB 的
177
+ lab chunk。
178
+
179
+ 要在产物里**彻底删掉**它,在宿主的构建配置里把子入口指到空实现:
180
+
181
+ ```ts
182
+ // vite.config.ts
183
+ export default {
184
+ resolve: {
185
+ alias: [
186
+ {
187
+ find: /^@cut-workbench\/workbench\/lab$/,
188
+ replacement: '@cut-workbench/workbench/lab-disabled',
189
+ },
190
+ ],
191
+ },
192
+ }
193
+ ```
194
+
195
+ 验证方式:
196
+
197
+ ```bash
198
+ grep -c "FrameCounterClip" dist/assets/*.js # 应为 0
199
+ ```
200
+
201
+ > 为什么不是 `define: { __CUT_WORKBENCH_LAB__: 'false' }`:实测(Vite 8 /
202
+ > Rolldown)动态 import 只要出现在模块图里,chunk 就会被写进 dist,即便所在
203
+ > 分支已被折成死代码。只有把模块解析本身换掉才真正生效。
204
+
61
205
  ## 主题定制
62
206
 
63
207
  ```tsx
@@ -8,6 +8,7 @@
8
8
  * - 不强制能力阻断页(宿主可自行决定)。
9
9
  */
10
10
  import { type ReactNode } from 'react';
11
+ import { type WorkbenchToolbarLabels } from './components/layout/WorkbenchToolbar';
11
12
  export interface CutWorkbenchSlots {
12
13
  /** 替换内置顶栏;传 `null` 表示不要顶栏 */
13
14
  topBar?: ReactNode | null;
@@ -16,8 +17,32 @@ export interface CutWorkbenchSlots {
16
17
  inspector?: ReactNode;
17
18
  timeline?: ReactNode;
18
19
  }
20
+ /**
21
+ * 顶栏的局部自定义。
22
+ *
23
+ * 与 `slots.topBar` 的区别:`slots.topBar` 会替换**整条**顶栏(撤销、导出等
24
+ * 内置动作一并消失,宿主自己负责);`toolbar` 只替换其中某一块,其余保留。
25
+ */
26
+ export interface CutWorkbenchToolbarOptions {
27
+ /** 左侧品牌区;传 `null` 表示不要(默认就没有,避免在宿主产品里露出本库品牌) */
28
+ brand?: ReactNode | null;
29
+ /** 工程名区;传 `null` 表示不要 */
30
+ title?: ReactNode | null;
31
+ /** 工程名右侧状态(默认「空工程 · 16:9」);传 `null` 表示不要 */
32
+ status?: ReactNode | null;
33
+ /** 在右侧按钮组末尾追加宿主动作 */
34
+ actions?: ReactNode;
35
+ /** 是否显示引擎能力徽标,默认 true */
36
+ showCapabilityBadge?: boolean;
37
+ /** 是否允许双击工程名重命名,默认 false */
38
+ editableTitle?: boolean;
39
+ /** 覆盖内置文案 */
40
+ labels?: WorkbenchToolbarLabels;
41
+ }
19
42
  export interface CutWorkbenchProps {
20
43
  slots?: CutWorkbenchSlots;
44
+ /** 顶栏的局部自定义(`slots.topBar` 优先级更高) */
45
+ toolbar?: CutWorkbenchToolbarOptions;
21
46
  className?: string;
22
47
  /**
23
48
  * 工程持久化回调。
@@ -34,4 +59,4 @@ export interface CutWorkbenchProps {
34
59
  *
35
60
  * 必须在 `<WorkbenchProvider>` + `<CutEditorProvider>` 之内使用。
36
61
  */
37
- export declare function CutWorkbench({ slots, className, onSaveProject, onOpenProject, enforceCapability, }: CutWorkbenchProps): import("react").JSX.Element;
62
+ export declare function CutWorkbench({ slots, toolbar: toolbarOptions, className, onSaveProject, onOpenProject, enforceCapability, }: CutWorkbenchProps): import("react").JSX.Element;
package/dist/auto.cjs ADDED
@@ -0,0 +1,4 @@
1
+ // 由 build.mjs 生成:自带样式的入口,见 src/auto.ts
2
+ // ⚠️ 同 auto.js:需在 package.json 的 "sideEffects" 白名单里,否则样式会被裁掉。
3
+ require('./cut-workbench.css')
4
+ module.exports = require('./index.cjs')
package/dist/auto.d.ts ADDED
@@ -0,0 +1,19 @@
1
+ /**
2
+ * 自带样式的接入入口。
3
+ *
4
+ * 根入口(`@cut-workbench/workbench`)刻意**不带**样式:宿主若已有自己的构建
5
+ * 流程、需要控制 CSS 顺序(例如要压过某条全局重置),可以自己决定何时引入
6
+ * `@cut-workbench/workbench/style.css`。
7
+ *
8
+ * 这个入口把「引入样式 + 导出 API」合成一步:
9
+ *
10
+ * ```ts
11
+ * import { CutWorkbench, CutWorkbenchProvider } from '@cut-workbench/workbench/auto'
12
+ * ```
13
+ *
14
+ * ⚠️ 下面这行 import 是 Vite 产出 CSS 的唯一依据(`build.mjs` 会把产物写进
15
+ * `dist/cut-workbench.css`)。它会被 tsc 原样写进 `dist/auto.d.ts`,而 dist 下
16
+ * 没有 `styles/` 目录,接入方关掉 skipLibCheck 会报 TS2882 ——
17
+ * 由 `scripts/fix-dts-paths.mjs` 在构建后剥离。
18
+ */
19
+ export * from './index';
package/dist/auto.js ADDED
@@ -0,0 +1,8 @@
1
+ // 由 build.mjs 生成:自带样式的入口,见 src/auto.ts
2
+ //
3
+ // ⚠️ 本文件必须留在 package.json 的 "sideEffects" 白名单里。打包器会按
4
+ // sideEffects 判定「整个模块可丢弃」,而 auto 的唯一副作用就是下面这行
5
+ // CSS 导入 —— 一旦被判为无副作用,宿主 bundle 里不会报错、日志里没有痕迹,
6
+ // 只是页面彻底没有样式(实测 Vite/Rolldown 行为)。
7
+ import './cut-workbench.css'
8
+ export * from './index.js'
@@ -4,6 +4,11 @@ interface UnsupportedBrowserProps {
4
4
  loading: boolean;
5
5
  error?: string | null;
6
6
  }
7
- /** 不满足运行条件时的阻断页:逐项说明缺失能力,不做静默降级 */
7
+ /**
8
+ * 不满足运行条件时的阻断页:逐项说明缺失能力,不做静默降级。
9
+ *
10
+ * 「依赖没装」与「浏览器不支持」要分开呈现 —— 前者是宿主的构建问题(可执行
11
+ * 安装命令解决),后者只能换浏览器。混在一起会让人对着一条装包报错去升浏览器。
12
+ */
8
13
  export declare function UnsupportedBrowser({ report, loading, error }: UnsupportedBrowserProps): import("react").JSX.Element;
9
14
  export {};
@@ -17,10 +17,24 @@ interface AppShellProps {
17
17
  className?: string;
18
18
  }
19
19
  /**
20
- * 三栏 + 底部时间轴骨架。
20
+ * 工作台骨架:顶栏 + 上半区(素材 / 预览 / 属性)+ 底部通栏时间轴。
21
21
  *
22
- * 布局用 inline style 的像素宽度做「受控」尺寸,配合 PanelResizer 的拖拽回写;
23
- * 这样不会因为 tailwind 的动态 class 被 purge 掉而失效。
22
+ * ```
23
+ * ┌───────────────────────────────────────────┐
24
+ * │ 顶栏 │
25
+ * ├──────────┬──────────────┬─────────────────┤
26
+ * │ 素材库 │ 预览 │ 属性 │ 上半区
27
+ * ├──────────┴──────────────┴─────────────────┤
28
+ * │ 时间轴(横跨整行) │ 下栏
29
+ * └───────────────────────────────────────────┘
30
+ * ```
31
+ *
32
+ * 时间轴必须是**通栏**的:它曾被塞进中间列,于是左右面板一拉宽就把时间轴
33
+ * 挤窄,工具条上的按钮直接溢出到属性面板上(实测右栏拉宽时溢出 203px)。
34
+ * 与此同时,几乎所有 NLE 都是「轨道占满底部一条」,通栏也更符合预期。
35
+ *
36
+ * 尺寸一律用 inline style 的像素值(受控),配合 PanelResizer 的拖拽回写;
37
+ * 不用 tailwind 动态 class,避免被 purge 掉导致尺寸失效。
24
38
  */
25
39
  export declare function AppShell({ topBar, left, center, right, timeline, leftWidth, rightWidth, timelineHeight, leftResizer, rightResizer, timelineResizer, className, }: AppShellProps): import("react").JSX.Element;
26
40
  export {};
@@ -5,9 +5,30 @@
5
5
  * - 品牌区可替换(`brand`),不强制显示项目名;
6
6
  * - 保存/打开是可选回调,宿主不提供就不渲染按钮;
7
7
  * - 「视图切换(编辑器/实验室)」只在开启 lab 特性时显示。
8
+ *
9
+ * 可自定义的层次(由浅到深):
10
+ * 1. **文案**:`labels` 覆盖工程名占位、能力徽标等;
11
+ * 2. **局部**:`brand` / `title` / `status` / `actions` / `showCapabilityBadge`
12
+ * 逐块替换;
13
+ * 3. **整条**:`slots.topBar`(传 `null` 表示完全不要顶栏)。
8
14
  */
9
- import type { ReactNode } from 'react';
15
+ import { type ReactNode } from 'react';
10
16
  import type { CapabilityInfo } from './use-capability-info';
17
+ /** 可覆盖的文案;缺省用内置中文 */
18
+ export interface WorkbenchToolbarLabels {
19
+ /** 空工程的占位名(工程名称为空时显示) */
20
+ untitledProject?: string;
21
+ /** 「空工程」/「N 个片段」里的占位 */
22
+ emptyProject?: string;
23
+ export?: string;
24
+ canvasSettings?: string;
25
+ showLab?: string;
26
+ backToEditor?: string;
27
+ save?: string;
28
+ open?: string;
29
+ undo?: string;
30
+ redo?: string;
31
+ }
11
32
  export interface WorkbenchToolbarProps {
12
33
  capability: CapabilityInfo;
13
34
  onExport: () => void;
@@ -17,8 +38,24 @@ export interface WorkbenchToolbarProps {
17
38
  onOpenProject?: () => void;
18
39
  /** 替换左侧品牌区;传 null 表示不要 */
19
40
  brand?: ReactNode | null;
41
+ /**
42
+ * 替换「工程名」区;传 null 表示不要。
43
+ *
44
+ * 不传时用内置的只读展示(工程名 + 未保存圆点)。
45
+ */
46
+ title?: ReactNode | null;
47
+ /** 替换工程名右侧的状态文案区(默认「空工程 · 16:9」);传 null 表示不要 */
48
+ status?: ReactNode | null;
49
+ /** 在右侧按钮组**末尾**追加宿主自己的动作 */
50
+ actions?: ReactNode;
51
+ /** 是否显示引擎能力徽标,默认 true */
52
+ showCapabilityBadge?: boolean;
53
+ /** 是否允许双击工程名重命名,默认 false(宿主可能用自己的工程体系) */
54
+ editableTitle?: boolean;
20
55
  /** 是否显示「实验室」入口 */
21
56
  showLab?: boolean;
57
+ /** 覆盖内置文案 */
58
+ labels?: WorkbenchToolbarLabels;
22
59
  }
23
60
  /** 顶栏:品牌 / 工程名 / 撤销重做 / 保存 / 导出 / 能力徽标 / 视图切换 */
24
- export declare function WorkbenchToolbar({ capability, onExport, onSaveProject, onOpenProject, brand, showLab, }: WorkbenchToolbarProps): import("react").JSX.Element;
61
+ export declare function WorkbenchToolbar({ capability, onExport, onSaveProject, onOpenProject, brand, title, status, actions, showCapabilityBadge, showLab, editableTitle, labels, }: WorkbenchToolbarProps): import("react").JSX.Element;
@@ -112,7 +112,6 @@
112
112
  --font-weight-normal: 400;
113
113
  --font-weight-medium: 500;
114
114
  --font-weight-semibold: 600;
115
- --tracking-tight: -.025em;
116
115
  --tracking-wide: .025em;
117
116
  --tracking-widest: .1em;
118
117
  --leading-tight: 1.25;
@@ -132,6 +131,10 @@
132
131
  container: card-header / inline-size;
133
132
  }
134
133
 
134
+ .cw-root .\@container {
135
+ container-type: inline-size;
136
+ }
137
+
135
138
  .cw-root .pointer-events-auto {
136
139
  pointer-events: auto;
137
140
  }
@@ -636,6 +639,10 @@
636
639
  min-height: calc(var(--spacing) * 16);
637
640
  }
638
641
 
642
+ .cw-root .min-h-\[220px\] {
643
+ min-height: 220px;
644
+ }
645
+
639
646
  .cw-root .min-h-screen {
640
647
  min-height: 100vh;
641
648
  }
@@ -676,6 +683,10 @@
676
683
  width: calc(var(--spacing) * 44);
677
684
  }
678
685
 
686
+ .cw-root .w-48 {
687
+ width: calc(var(--spacing) * 48);
688
+ }
689
+
679
690
  .cw-root .w-72 {
680
691
  width: calc(var(--spacing) * 72);
681
692
  }
@@ -853,6 +864,10 @@
853
864
  cursor: row-resize;
854
865
  }
855
866
 
867
+ .cw-root .cursor-text {
868
+ cursor: text;
869
+ }
870
+
856
871
  .cw-root .touch-none {
857
872
  touch-action: none;
858
873
  }
@@ -1051,6 +1066,10 @@
1051
1066
  overflow-y: auto;
1052
1067
  }
1053
1068
 
1069
+ .cw-root .overflow-y-hidden {
1070
+ overflow-y: hidden;
1071
+ }
1072
+
1054
1073
  .cw-root .rounded {
1055
1074
  border-radius: .25rem;
1056
1075
  }
@@ -1185,6 +1204,16 @@
1185
1204
  border-color: currentColor;
1186
1205
  }
1187
1206
 
1207
+ .cw-root .border-destructive\/40 {
1208
+ border-color: var(--cw-destructive);
1209
+ }
1210
+
1211
+ @supports (color: color-mix(in lab, red, red)) {
1212
+ .cw-root .border-destructive\/40 {
1213
+ border-color: color-mix(in oklab, var(--cw-destructive) 40%, transparent);
1214
+ }
1215
+ }
1216
+
1188
1217
  .cw-root .border-destructive\/50 {
1189
1218
  border-color: var(--cw-destructive);
1190
1219
  }
@@ -1895,11 +1924,6 @@
1895
1924
  font-weight: var(--font-weight-semibold);
1896
1925
  }
1897
1926
 
1898
- .cw-root .tracking-tight {
1899
- --tw-tracking: var(--tracking-tight);
1900
- letter-spacing: var(--tracking-tight);
1901
- }
1902
-
1903
1927
  .cw-root .tracking-wide {
1904
1928
  --tw-tracking: var(--tracking-wide);
1905
1929
  letter-spacing: var(--tracking-wide);
@@ -2566,6 +2590,11 @@
2566
2590
  color: var(--cw-accent-foreground);
2567
2591
  }
2568
2592
 
2593
+ .cw-root .focus\:ring-1:focus {
2594
+ --tw-ring-shadow: var(--tw-ring-inset, ) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color, currentcolor);
2595
+ box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
2596
+ }
2597
+
2569
2598
  .cw-root .focus\:ring-2:focus {
2570
2599
  --tw-ring-shadow: var(--tw-ring-inset, ) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color, currentcolor);
2571
2600
  box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
@@ -3044,6 +3073,12 @@
3044
3073
  border-radius: 0;
3045
3074
  }
3046
3075
 
3076
+ @media not all and (min-width: 560px) {
3077
+ .cw-root .max-\[560px\]\:hidden {
3078
+ display: none;
3079
+ }
3080
+ }
3081
+
3047
3082
  @media (min-width: 40rem) {
3048
3083
  .cw-root .sm\:inline {
3049
3084
  display: inline;
@@ -3119,6 +3154,12 @@
3119
3154
  }
3120
3155
  }
3121
3156
 
3157
+ @container (min-width: 720px) {
3158
+ .cw-root .\@\[720px\]\:flex {
3159
+ display: flex;
3160
+ }
3161
+ }
3162
+
3122
3163
  .dark\:border-input:where([data-cw-theme="dark"], .cw-root[data-cw-theme="dark"] *) {
3123
3164
  border-color: var(--cw-input);
3124
3165
  }
@@ -1,4 +1,8 @@
1
1
  /**
2
- * Lab feature 的公开 API。
2
+ * Lab feature 的公开 API(内部模块,不对宿主暴露)。
3
+ *
4
+ * 对外入口是 `@cut-workbench/workbench/lab`(见 src/lab.ts);这里只做转发,
5
+ * 避免 `CutWorkbench` 直接触达 `components/LabPage`(那样会把 lab 及其
6
+ * `@webav/av-recorder` 拉回主包的模块图)。
3
7
  */
4
8
  export { LabPage } from './components/LabPage';