@hy-bricks/editor 0.1.1

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 hy_top
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,423 @@
1
+ # @hy-bricks/editor
2
+
3
+ > 嵌入式低代码组件编辑器:Monaco 三栏(html/js/css)+ 拖 tab 多分栏 + 实时预览 + 草稿持久化 + Monaco 类型注入。
4
+
5
+ [![npm](https://img.shields.io/npm/v/@hy-bricks/editor.svg)](https://www.npmjs.com/package/@hy-bricks/editor)
6
+ [![license](https://img.shields.io/badge/license-MIT-green.svg)](./LICENSE)
7
+ [![vue](https://img.shields.io/badge/vue-%5E3.5-42b883)](https://vuejs.org/)
8
+ [![monaco](https://img.shields.io/badge/monaco-%5E0.55-007acc)](https://microsoft.github.io/monaco-editor/)
9
+ [![tailwind](https://img.shields.io/badge/tailwind-%5E3.4-38bdf8)](https://tailwindcss.com/)
10
+
11
+ ```
12
+ ┌─────────────────────────────────────────────────────────────────────┐
13
+ │ demo-component v3 · 已发布 ☐ 自动预览 ⚙ 保存草稿 推版本 │
14
+ ├──────────────────────────────────┬──────────────────────────────────┤
15
+ │ html │ javascript │ + css │ 预览 ⧉ │
16
+ │ ─────────────────────────────── │ ─────────────────────────────── │
17
+ │ <div class="root"> │ │
18
+ │ <h2>{{ greeting }}</h2> │ Hello World ! │
19
+ │ <el-input v-model="msg" /> │ ┌───────────────┐ │
20
+ │ <el-button @click="add"> │ │ 试试输入 │ │
21
+ │ 点我 +1 │ └───────────────┘ │
22
+ │ </el-button> │ 你输入了: │
23
+ │ </div> │ │
24
+ │ │ [ 点我 +1 ] 点了 0 次 │
25
+ └──────────────────────────────────┴──────────────────────────────────┘
26
+ (拖 tab 头 → 多分栏)
27
+ ```
28
+
29
+ `<HyperCardEditor>` 是一个**受控组件**:你给一份 `{html, js, css}` 源码,它给你一个 IDE 风界面 + 一份能跑的实时预览。所有副作用(保存、推版本、开窗口、设置面板)都通过事件抛回宿主,SDK **不调任何 API**、**不依赖 vue-router**、**不强制宿主目录结构**。
30
+
31
+ ---
32
+
33
+ ## Quickstart(5 分钟,从空白 Vite + Vue 3 项目接进来)
34
+
35
+ ### 1. 安装 SDK + 全部 peerDeps
36
+
37
+ reka-ui / cva / clsx / tailwind-merge / lucide-vue-next 在 SDK 构建时被 vite `external`,**必须由宿主装**(详见下面的 [peerDependencies](#peerdependencies-必装清单))。
38
+
39
+ ```bash
40
+ pnpm add @hy-bricks/editor @hy-bricks/core \
41
+ vue monaco-editor \
42
+ reka-ui class-variance-authority clsx tailwind-merge lucide-vue-next \
43
+ tailwindcss tailwindcss-animate autoprefixer postcss
44
+ ```
45
+
46
+ ### 2. `tailwind.config.js` —— 不要用 SDK 的 preset
47
+
48
+ > 包内 `.vue` 用了大量 Tailwind utility,但 Tailwind v3 默认 ignore `node_modules`,即便 `content` 写上 `node_modules/@hy-bricks/**` 也大概率扫不全(symlink、pnpm hoist 路径变化)。
49
+ >
50
+ > 正确做法:**本包发布时已经 self-compile 出 `dist/style.css`**,把全部需要的 utility 都打进去了。宿主只要 import 这一个 css 就行,不需要让自己的 Tailwind 扫包内源码。
51
+
52
+ ```js
53
+ /** tailwind.config.js — 宿主只配自己的内容,不扫 SDK */
54
+ export default {
55
+ content: [
56
+ './index.html',
57
+ './src/**/*.{vue,ts,tsx,js,jsx}',
58
+ ],
59
+ theme: { extend: {} },
60
+ plugins: [],
61
+ }
62
+ ```
63
+
64
+ ### 3. `src/style.css`
65
+
66
+ ```css
67
+ @tailwind base;
68
+ @tailwind components;
69
+ @tailwind utilities;
70
+ ```
71
+
72
+ > ⚠ **不要**在这个 css 里 `@import '@hy-bricks/editor/style.css'`。PostCSS 的 `@import`
73
+ > 走它自己的解析器,不识别 npm scope alias(`@hy-bricks/...`),通常会 silent 失败:
74
+ > 编译没报错,主题变量却丢失。**唯一推荐**:在 `main.ts` 里用 JS `import`(下一步),
75
+ > 走 bundler 的 ESM 解析,稳定可靠。
76
+
77
+ ### 4. `src/main.ts`
78
+
79
+ ```ts
80
+ import { createApp } from 'vue'
81
+ import { createHyperCard } from '@hy-bricks/core'
82
+
83
+ // 通过 vite ESM resolve 直接拿包内 self-compiled 样式 — 这是引入主题 CSS 的
84
+ // **唯一推荐方式**(不要在 style.css 里 @import,见上一步说明)
85
+ import '@hy-bricks/editor/style.css'
86
+
87
+ import App from './App.vue'
88
+ import './style.css'
89
+
90
+ const app = createApp(App)
91
+
92
+ app.use(
93
+ createHyperCard({
94
+ libs: {
95
+ // 你提供给组件作者用的运行时库,组件代码里通过 __HYPERCARD__.libs.* 拿
96
+ http: /* axios 或自己封装的 fetch */ undefined,
97
+ ui: /* ElementPlus / antdv / 你自己的 UI 库 */ undefined,
98
+ },
99
+ }),
100
+ )
101
+
102
+ app.mount('#app')
103
+ ```
104
+
105
+ ### 5. `App.vue`
106
+
107
+ ```vue
108
+ <script setup lang="ts">
109
+ import { ref } from 'vue'
110
+ import { HyperCardEditor } from '@hy-bricks/editor'
111
+
112
+ const source = ref({ html: '', js: '', css: '' })
113
+ </script>
114
+
115
+ <template>
116
+ <div class="h-screen">
117
+ <HyperCardEditor v-model="source" draft-key="my-component" />
118
+ </div>
119
+ </template>
120
+ ```
121
+
122
+ 跑 `pnpm dev` 就能看到完整 IDE。三栏 Monaco 都能编辑、改了立刻预览、`Cmd+S` 存草稿、刷新页面草稿还在。
123
+
124
+ ---
125
+
126
+ ## peerDependencies(必装清单)
127
+
128
+ | 包 | 版本 | 必装 | 说明 |
129
+ |---|---|---|---|
130
+ | `vue` | `^3.5` | ✅ | Composition API + `<script setup>` |
131
+ | `monaco-editor` | `^0.55` | ✅ | 三栏代码编辑器内核 |
132
+ | `tailwindcss` | `^3.4 \|\| ^4` | ✅ | 宿主自己的 Tailwind(只扫自己,不扫 SDK) |
133
+ | `@hy-bricks/core` | 同 SDK 版本 | ✅ | 运行时(预览盒子、`createHyperCard` plugin) |
134
+ | `reka-ui` | `^1.0.0-alpha.8` | ✅ | shadcn-vue 的 headless 底座(Button 等用了) |
135
+ | `class-variance-authority` | `^0.7` | ✅ | `buttonVariants` 生成器 |
136
+ | `clsx` | `^2.1` | ✅ | className 合并 |
137
+ | `tailwind-merge` | `^2.5` | ✅ | dedupe Tailwind 冲突 class |
138
+ | `lucide-vue-next` | `^0.469` | ✅ | 顶栏图标(Settings / RefreshCw 等) |
139
+ | `tailwindcss-animate` | `^1.0` | 推荐 | shadcn-vue 默认动画(若你启用宿主侧 shadcn 组件) |
140
+
141
+ > SDK `package.json` 里 reka-ui / cva / clsx / tailwind-merge / lucide-vue-next 已经移到 `peerDependencies`(语义就是"宿主项目需要装"):**宿主必须显式安装**;SDK build 时把它们 `external`,避免重复打包。
142
+
143
+ ---
144
+
145
+ ## API
146
+
147
+ ### `<HyperCardEditor>` props
148
+
149
+ | prop | 类型 | 默认 | 说明 |
150
+ |---|---|---|---|
151
+ | `modelValue` | `{ html: string; js: string; css: string }` | — | **必填**。`v-model` 双向绑定;形状写死,宿主必须适配 |
152
+ | `draftKey` | `string` | `undefined` | 启用 localStorage 草稿;实际 key = `hc:draft:<draftKey>`。留空 = 不启用草稿 |
153
+ | `componentId` | `string` | 回退 `draftKey`,再回退实例级唯一 id(`__hc_editor_<uid>__`,用 `getCurrentInstance().uid`) | 给运行时实例的 id。默认回退已自动每实例唯一,不再撞;**多实例仍建议显式传**,方便 runtime / cssScope / 调试定位 |
154
+ | `showBackButton` | `boolean` | `false` | 顶栏左侧返回按钮。点击触发 `go-back` 事件,SDK 自己不路由 |
155
+ | `title` | `string` | `undefined` | 顶栏组件标题 |
156
+ | `statusLabel` | `string` | `undefined` | 顶栏右侧的版本号 / 状态文字(如 `'v3 · 已发布'`) |
157
+ | `autoPreview` | `boolean` | `true` | 改源码后自动 debounce 渲染预览;`false` 时显示「重新渲染」按钮,需手点 |
158
+ | `previewRatio` | `number` | `0.62` | 编辑器与预览的初始宽度比 0–1 |
159
+ | `extraLibs` | `ExtraLib[]` | `undefined` | 给 Monaco 注入的额外类型 lib(自动补全 `__HYPERCARD__.libs.*`),详见 [Monaco 类型注入](#monaco-类型注入) |
160
+
161
+ ```ts
162
+ interface ExtraLib {
163
+ filePath: string // 必须形如 'file:///foo.d.ts'
164
+ content: string // ambient .d.ts 字符串(不能含 export {})
165
+ }
166
+ ```
167
+
168
+ ### emit 事件
169
+
170
+ | 事件 | payload | 触发时机 | 宿主该怎么处理 |
171
+ |---|---|---|---|
172
+ | `update:modelValue` | `{ html, js, css }` | 任意一栏 Monaco 内容变化(throttle) | `v-model` 自动接,无需手写 |
173
+ | `dirty` | `boolean` | 用户改了源码 / 调用 `applyDraft` 后 → `true`;v-model 重置或保存草稿后 → `false` | 顶栏 dot、disable「推版本」按钮等 |
174
+ | `save-draft` | — | 用户点顶栏「保存草稿」或 `Cmd+S`(草稿已写入 localStorage) | toast「已保存」即可,无需自己再写 |
175
+ | `push-version` | — | 用户点顶栏「推版本」 | 弹自家 dialog 让填备注 → 调后端 API |
176
+ | `open-preview-window` | — | 用户点预览面板右上的「⧉ 独立窗口」 | `window.open(routerResolve('/preview/...'), ...)`;开了之后 SDK 自动用 BroadcastChannel 同步源码 |
177
+ | `open-settings` | — | 用户点顶栏齿轮图标 | 弹自家组件元数据 drawer(name / desc / tags 等) |
178
+ | `go-back` | — | `showBackButton=true` 时点击返回按钮 | `router.back()` 之类 |
179
+ | `error` | `{ message, stack? }` | 编译 / 运行时报错 | 写 console、上报 Sentry、Toast 等 |
180
+
181
+ ### expose(`ref` 拿到的 instance)
182
+
183
+ ```ts
184
+ const editor = useTemplateRef<{
185
+ isDirty: Ref<boolean> // ← 注意是 Ref,不是裸 boolean
186
+ confirmLeave(): boolean
187
+ applyDraft(draft: ComponentDraft): void
188
+ manualPreview(): void
189
+ applyPreview(): void
190
+ }>('editor')
191
+ ```
192
+
193
+ | 方法 / 属性 | 签名 | 说明 |
194
+ |---|---|---|
195
+ | `isDirty` | `Ref<boolean>` | **是 ref 不是裸值**,读用 `editor.value.isDirty.value`、模板里直接 `editor?.isDirty` |
196
+ | `confirmLeave()` | `() => boolean` | 同步弹 `confirm`,dirty 时提示「未保存,确定离开?」;返回 `true` 表示放行,`false` 表示用户取消 |
197
+ | `applyDraft(draft)` | `(d: ComponentDraft) => void` | 把外部草稿灌进编辑器(用于「恢复历史版本」),会把 dirty 设为 true |
198
+ | `manualPreview()` | `() => void` | `autoPreview=false` 时手动触发预览 |
199
+ | `applyPreview()` | `() => void` | 用 `v-model` 灌进新 source 后,如果想立刻刷预览(`watch(modelValue)` 已经会调,基本用不到) |
200
+
201
+ #### vue-router 离开守卫
202
+
203
+ ```ts
204
+ import { onBeforeRouteLeave } from 'vue-router'
205
+
206
+ const editor = useTemplateRef<{ confirmLeave(): boolean }>('editor')
207
+
208
+ onBeforeRouteLeave(() => editor.value?.confirmLeave() ?? true)
209
+ ```
210
+
211
+ `beforeunload`(关浏览器、刷新)SDK 自动接,无需宿主写。
212
+
213
+ ---
214
+
215
+ ## Monaco 类型注入
216
+
217
+ 让组件作者写 `__HYPERCARD__.libs.http.` 时 Monaco 自动补全到具体方法。SDK 默认已经注册 `__HYPERCARD__` / `runtime` / `assets` 三个 ambient,**但 `libs` 的具体类型必须由宿主补**(因为只有宿主知道你提供了 axios 还是 ky 还是 ElementPlus)。
218
+
219
+ ```ts
220
+ import { HyperCardEditor, type ExtraLib } from '@hy-bricks/editor'
221
+
222
+ const extraLibs: ExtraLib[] = [
223
+ {
224
+ // 必须 file:/// 协议,Monaco 用这个判断模块身份
225
+ filePath: 'file:///host-libs.d.ts',
226
+ content: `
227
+ // 关键:用 declaration merging 跟 SDK 自带的 HyperCardLibs 接口合并,
228
+ // 不要写 export {},一旦文件里有 import/export,
229
+ // 它就变成 module 而不是 ambient,声明就泄漏不到全局了
230
+ interface HyperCardLibs {
231
+ http: {
232
+ get<T = any>(url: string, config?: any): Promise<{ data: T; status: number }>
233
+ post<T = any>(url: string, data?: any, config?: any): Promise<{ data: T; status: number }>
234
+ }
235
+ ui: { version: string; install: (...args: any[]) => void }
236
+ }
237
+ `,
238
+ },
239
+ ]
240
+ ```
241
+
242
+ ```vue
243
+ <HyperCardEditor v-model="source" :extra-libs="extraLibs" />
244
+ ```
245
+
246
+ > ⚠ **致命陷阱**:`content` 字符串里**不能**出现 `export {}` / `import ...`,否则 .d.ts 自动从 ambient 转成 module,顶层 `declare const __HYPERCARD__` 就不再是 global,组件作者那边补全直接灰掉。
247
+
248
+ ---
249
+
250
+ ## 主题定制
251
+
252
+ SDK 用 shadcn-vue 风的 HSL CSS variables。**不需要重 build**,在宿主自己的 `:root` 覆盖任意一条即可。
253
+
254
+ | 变量 | 默认 (light) | 用途 |
255
+ |---|---|---|
256
+ | `--background` / `--foreground` | `0 0% 100%` / `222.2 84% 4.9%` | 页面底色 / 主文字 |
257
+ | `--primary` / `--primary-foreground` | `222.2 47.4% 11.2%` / `210 40% 98%` | 「推版本」主按钮 |
258
+ | `--secondary` / `--secondary-foreground` | `210 40% 96.1%` / `222.2 47.4% 11.2%` | 次按钮 |
259
+ | `--muted` / `--muted-foreground` | `210 40% 96.1%` / `215.4 16.3% 46.9%` | 弱化区域、placeholder |
260
+ | `--accent` / `--accent-foreground` | `210 40% 96.1%` / `222.2 47.4% 11.2%` | hover 反馈 |
261
+ | `--destructive` / `--destructive-foreground` | `0 84.2% 60.2%` / `210 40% 98%` | 删除 / 警告 |
262
+ | `--border` / `--input` / `--ring` | `214.3 31.8% 91.4%` / 同左 / `222.2 84% 4.9%` | 边框 / input / focus ring |
263
+ | `--radius` | `0.5rem` | 基础圆角(Button、面板) |
264
+
265
+ ```css
266
+ /* 改主色 + dark mode 一起改 */
267
+ :root {
268
+ --primary: 220 80% 50%;
269
+ --radius: 0.25rem;
270
+ }
271
+
272
+ .dark {
273
+ --primary: 220 70% 60%;
274
+ --background: 222.2 84% 4.9%;
275
+ }
276
+ ```
277
+
278
+ > 值要写**纯 HSL 三元**(无 `hsl()` 包裹、无逗号),包内 `hsl(var(--primary) / <alpha>)` 会自己拼。
279
+
280
+ ---
281
+
282
+ ## 草稿持久化 + 离开守卫
283
+
284
+ | 场景 | SDK 做了什么 | 宿主做什么 |
285
+ |---|---|---|
286
+ | 设置 `draft-key="abc"` | 启用 localStorage,key = `hc:draft:abc` | 传 prop |
287
+ | 顶栏「保存草稿」/ `Cmd+S` | 写 localStorage,清除 dirty,触发 `@save-draft` | 接事件 toast 反馈 |
288
+ | 进入页面检测到旧草稿 | 顶栏渲染琥珀色 pill「本地草稿 · 5 分钟前 · [恢复] [丢弃]」 | — |
289
+ | 关浏览器 / 刷新 (`beforeunload`) | dirty 时弹浏览器原生 confirm | — |
290
+ | 切路由 (`onBeforeRouteLeave`) | 暴露 `confirmLeave()` | 在 router guard 中调:`return editor.value?.confirmLeave()` |
291
+ | 推版本成功 | — | 宿主自己 `clearDraft(key)`(从 SDK 导出) |
292
+
293
+ ```ts
294
+ import { clearDraft, loadDraft, saveDraft } from '@hy-bricks/editor'
295
+
296
+ clearDraft('my-component') // 推版本成功后清
297
+ const d = loadDraft('my-component') // SSR / 列表预读
298
+ saveDraft('my-component', { html, javascript, css }) // 自己触发存(很少用)
299
+ ```
300
+
301
+ ---
302
+
303
+ ## 子组件单独导出(高级用户)
304
+
305
+ 不想要默认的「IDE 顶栏 + 三栏 + 预览」布局?可以拿乐高块自己拼:
306
+
307
+ ```ts
308
+ import {
309
+ // 布局
310
+ EditorLayout, EditorGroup, MonacoEditor, Splitter,
311
+ // multi-instance scope(每个独立编辑器一份,model 不串)
312
+ createEditorScope, useEditorScope, EDITOR_SCOPE_KEY,
313
+ // 布局算法(纯函数,immutable)
314
+ defaultLayout, activateTab, closeTab, splitWithTab, moveTab, setSplitRatio,
315
+ // 草稿
316
+ loadDraft, saveDraft, clearDraft,
317
+ // 预览跨窗口总线
318
+ createPreviewBus,
319
+ // shadcn-vue 风 Button(包内 vendor)
320
+ Button, buttonVariants,
321
+ // Monaco 类型钩子
322
+ setupHyperCardMonacoTypes, addMonacoExtraLib,
323
+ } from '@hy-bricks/editor'
324
+ ```
325
+
326
+ - `EditorLayout` / `EditorGroup` / `MonacoEditor` / `Splitter` —— 自拼 IDE 界面
327
+ - `createEditorScope` / `useEditorScope` —— multi-instance 的 monaco model 隔离
328
+ - `Button` / `buttonVariants` —— vendor 自 shadcn-vue,跟 SDK 主题变量一致
329
+
330
+ ---
331
+
332
+ ## Troubleshooting
333
+
334
+ #### ❌ 装了 SDK,顶栏 Button 没样式 / 圆角错 / 颜色全黑
335
+
336
+ **原因**:Tailwind v3 出于性能默认 ignore `node_modules`,所以即便宿主 `tailwind.config.js` 的 `content` 写了 `node_modules/@hy-bricks/**`,也基本扫不到包内 `.vue` 里的 utility class。
337
+
338
+ **解法**:**不要让宿主 Tailwind 扫这个包**。本包发布时已经 self-compile 出 `dist/style.css`,把全部需要的 utility 都打进去。宿主只要:
339
+
340
+ ```ts
341
+ import '@hy-bricks/editor/style.css'
342
+ ```
343
+
344
+ 就拿到完整样式 + 主题变量。Tailwind preset 共享只是给「我也想跟本包用同一套色板」的宿主用,跟样式生效**无关**。
345
+
346
+ #### ❌ `Cannot read file ... ?worker` 报错
347
+
348
+ **原因**:Vite 默认会把 `node_modules` 里的 ESM 包预 bundle(esbuild),但 esbuild 不认识 Vite 的 `?worker` query —— monaco-editor 的 worker import 就这样炸。
349
+
350
+ **解法**:宿主 `vite.config.ts` 把 SDK 排除:
351
+
352
+ ```ts
353
+ export default defineConfig({
354
+ optimizeDeps: {
355
+ exclude: ['@hy-bricks/core', '@hy-bricks/editor'],
356
+ },
357
+ })
358
+ ```
359
+
360
+ #### ❌ PostCSS `Cannot find module 'postcss'` / CJS 加载失败
361
+
362
+ **原因**:Vite 把 SDK 当 ESM 加载时,如果宿主和 SDK 用了不同 PostCSS 版本,Node 模块解析会撞到 CJS/ESM 边界。
363
+
364
+ **解法**:SDK 已经把 `postcss` 处理打进自己的 dist 里了。如果宿主仍然另行 import `postcss`,在 `vite.config.ts` 里加 dedupe:
365
+
366
+ ```ts
367
+ export default defineConfig({
368
+ resolve: {
369
+ dedupe: ['postcss', 'monaco-editor', 'vue'],
370
+ },
371
+ })
372
+ ```
373
+
374
+ #### ❌ `editor.value.isDirty` 永远 truthy
375
+
376
+ **原因**:`isDirty` 暴露的是 `Ref<boolean>` 不是裸 `boolean`,而 `Ref` 对象本身永远 truthy。
377
+
378
+ **解法**:读 `.value`,或在模板里依赖自动 unwrap。TS 类型也要写对:
379
+
380
+ ```ts
381
+ const editor = useTemplateRef<{ isDirty: Ref<boolean> }>('editor')
382
+
383
+ if (editor.value?.isDirty.value) { /* ... */ }
384
+ ```
385
+
386
+ 或干脆听 `@dirty` 事件,**最推荐**:
387
+
388
+ ```vue
389
+ <HyperCardEditor v-model="source" @dirty="d => isDirty = d" />
390
+ ```
391
+
392
+ #### ❌ Monaco 补全 `__HYPERCARD__.libs.` 是空的
393
+
394
+ **原因**:你的 `extraLibs[].content` 里出现了 `export {}` / `import x from ...`,文件被 TS 识别成 module,声明不再 global。
395
+
396
+ **解法**:见 [Monaco 类型注入](#monaco-类型注入)。content 必须是纯 ambient,只有 `declare` / `interface` / `type`,没有任何 `import` / `export`。
397
+
398
+ #### ⚠ 多个 `<HyperCardEditor>` 同页,样式 / 实例 id 区分
399
+
400
+ **默认行为**:不传 `componentId` 时,自动回退成实例级唯一 id(`__hc_editor_<uid>__`,基于 `getCurrentInstance().uid`),每个 `<HyperCardEditor>` 实例自动拿不同 id,**不会撞**。
401
+
402
+ **仍建议**:多实例并存时**显式传** `componentId`(或不同 `draftKey`,会被自动回退用),让 runtime / cssScope / 调试堆栈里看得清是哪个实例:
403
+
404
+ ```vue
405
+ <HyperCardEditor v-model="a" component-id="card-a" />
406
+ <HyperCardEditor v-model="b" component-id="card-b" />
407
+ ```
408
+
409
+ ---
410
+
411
+ ## 配合使用 / Ecosystem
412
+
413
+ - [@hy-bricks/core](https://www.npmjs.com/package/@hy-bricks/core) — 运行时 SDK(editor 的 peerDep)
414
+ - [@hy-bricks/canvas](https://www.npmjs.com/package/@hy-bricks/canvas) — 自由画布 + 多实例编排
415
+ - [@hy-bricks/devtools](https://www.npmjs.com/package/@hy-bricks/devtools) — 运行时诊断浮窗
416
+ - [shadcn-vue](https://www.shadcn-vue.com/) — UI 风格参考
417
+ - [monaco-editor](https://microsoft.github.io/monaco-editor/) — 代码编辑器内核
418
+
419
+ ---
420
+
421
+ ## License
422
+
423
+ [MIT](./LICENSE) © hy_top
@@ -0,0 +1,64 @@
1
+ /**
2
+ * 共享 Tailwind 主题片段 — 给 tailwind-preset.js(宿主用)和 tailwind.config.js
3
+ * (SDK 自 build 用)避免双份 token 同步漂移。
4
+ *
5
+ * 改 shadcn 颜色 / 圆角 / dark mode 时只改这一个文件,两边自动跟随。
6
+ * 这个文件只导出 darkMode / theme / plugins 三件,**不带 content**(content
7
+ * 由调用方各自决定:preset 给宿主,config 给 SDK 内部 build)。
8
+ */
9
+ import animate from 'tailwindcss-animate'
10
+
11
+ /** @type {Pick<import('tailwindcss').Config, 'darkMode' | 'theme' | 'plugins'>} */
12
+ export default {
13
+ darkMode: ['class'],
14
+ theme: {
15
+ container: {
16
+ center: true,
17
+ padding: '1rem',
18
+ screens: { '2xl': '1400px' },
19
+ },
20
+ extend: {
21
+ colors: {
22
+ border: 'hsl(var(--border))',
23
+ input: 'hsl(var(--input))',
24
+ ring: 'hsl(var(--ring))',
25
+ background: 'hsl(var(--background))',
26
+ foreground: 'hsl(var(--foreground))',
27
+ primary: {
28
+ DEFAULT: 'hsl(var(--primary))',
29
+ foreground: 'hsl(var(--primary-foreground))',
30
+ },
31
+ secondary: {
32
+ DEFAULT: 'hsl(var(--secondary))',
33
+ foreground: 'hsl(var(--secondary-foreground))',
34
+ },
35
+ destructive: {
36
+ DEFAULT: 'hsl(var(--destructive))',
37
+ foreground: 'hsl(var(--destructive-foreground))',
38
+ },
39
+ muted: {
40
+ DEFAULT: 'hsl(var(--muted))',
41
+ foreground: 'hsl(var(--muted-foreground))',
42
+ },
43
+ accent: {
44
+ DEFAULT: 'hsl(var(--accent))',
45
+ foreground: 'hsl(var(--accent-foreground))',
46
+ },
47
+ popover: {
48
+ DEFAULT: 'hsl(var(--popover))',
49
+ foreground: 'hsl(var(--popover-foreground))',
50
+ },
51
+ card: {
52
+ DEFAULT: 'hsl(var(--card))',
53
+ foreground: 'hsl(var(--card-foreground))',
54
+ },
55
+ },
56
+ borderRadius: {
57
+ lg: 'var(--radius)',
58
+ md: 'calc(var(--radius) - 2px)',
59
+ sm: 'calc(var(--radius) - 4px)',
60
+ },
61
+ },
62
+ },
63
+ plugins: [animate],
64
+ }
package/dist/index.cjs ADDED
@@ -0,0 +1 @@
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("vue"),t=require("lucide-vue-next"),o=require("@hy-bricks/core"),n=require("monaco-editor"),r=require("monaco-editor/esm/vs/editor/editor.worker?worker"),a=require("monaco-editor/esm/vs/language/json/json.worker?worker"),i=require("monaco-editor/esm/vs/language/css/css.worker?worker"),s=require("monaco-editor/esm/vs/language/html/html.worker?worker"),l=require("monaco-editor/esm/vs/language/typescript/ts.worker?worker"),c=require("reka-ui"),u=require("clsx"),d=require("tailwind-merge"),p=require("class-variance-authority"),f=function(e){const t=Object.create(null,{[Symbol.toStringTag]:{value:"Module"}});if(e)for(const o in e)if("default"!==o){const n=Object.getOwnPropertyDescriptor(e,o);Object.defineProperty(t,o,n.get?n:{enumerable:!0,get:()=>e[o]})}return t.default=e,Object.freeze(t)}(n),v="\n// HyperCard 运行时全局类型 — 由 @hy-bricks/editor 注入到 Monaco\n\ndeclare const __HYPERCARD__: HyperCardInstance | undefined\n\ninterface HyperCardInstance {\n /** 宿主通过 createHyperCard({ libs: { ... } }) 注入的库 / 函数 / 常量 */\n libs: HyperCardLibs\n /** 跨实例运行时 SDK */\n runtime: HyperCardRuntime\n /** 资源 / 短链辅助 */\n assets: HyperCardAssets\n /** SDK 实例版本 */\n version: string\n}\n\n/**\n * 默认 libs 是开放命名空间。宿主补具体类型(axios / EP 等)时,在自己的 d.ts\n * extraLib 里 `interface HyperCardLibs { http: ...; ui: ... }` 声明合并。\n */\ninterface HyperCardLibs {\n [key: string]: any\n}\n\ninterface HyperCardRuntime {\n /** 调另一个组件实例的非钩子方法,同步;实例不存在返 undefined */\n call<T = unknown>(id: string, method: string, ...args: unknown[]): T | undefined\n /** 监听另一个实例的事件,返回 unsubscribe 函数;实例不存在返 undefined */\n on(id: string, event: string, handler: (payload: unknown) => void): (() => void) | undefined\n /** 在另一个实例上发事件 */\n emit(id: string, event: string, payload?: unknown): void\n /** 列出当前页面所有实例,可按 componentId 过滤 */\n listInstances(filter?: { componentId?: string }): HyperCardInstanceHandle[]\n /** 拿到指定 instanceId 的 handle */\n getInstance(id: string): HyperCardInstanceHandle | null\n}\n\ninterface HyperCardInstanceHandle {\n readonly instanceId: string\n readonly componentId: string\n call<T = unknown>(method: string, ...args: unknown[]): T | undefined\n on(event: string, handler: (payload: unknown) => void): () => void\n emit(event: string, payload?: unknown): void\n}\n\ninterface HyperCardAssets {\n /** 拼短链:'/a/<id>' */\n pickerUrl(id: string): string\n}\n",h="file:///hypercard-globals.d.ts";let m=!1;function y(){var e,t;if(m)return;m=!0,n.typescript.javascriptDefaults.addExtraLib(v,h),n.typescript.typescriptDefaults.addExtraLib(v,h),"undefined"!=typeof globalThis&&globalThis.__HYPERCARD_DEBUG__&&console.info("[hypercard][monaco-types] HyperCard globals 注入完成,extra libs =",Object.keys((null==(t=(e=n.typescript.javascriptDefaults).getExtraLibs)?void 0:t.call(e))??{}));const o={target:n.typescript.ScriptTarget.Latest,allowNonTsExtensions:!0,moduleResolution:n.typescript.ModuleResolutionKind.NodeJs,module:n.typescript.ModuleKind.ESNext,noEmit:!0,esModuleInterop:!0,allowJs:!0,checkJs:!1};n.typescript.javascriptDefaults.setCompilerOptions(o),n.typescript.typescriptDefaults.setCompilerOptions(o)}const b=new Map;function g(){const e=[{filePath:h,content:v},...Array.from(b.entries()).map(([e,{content:t}])=>({filePath:e,content:t}))];n.typescript.javascriptDefaults.setExtraLibs(e),n.typescript.typescriptDefaults.setExtraLibs(e)}function x(e){const t=b.get(e.filePath);if(t)return t.count++,void(t.content!==e.content&&console.warn(`[hypercard] extraLib "${e.filePath}" 多次注册但 content 不一致;保留先注册的版本。如需独立内容,请用不同 filePath。`));b.set(e.filePath,{content:e.content,count:1}),g()}function w(e){const t=b.get(e);t&&(t.count--,t.count<=0&&(b.delete(e),g()))}function k(e,t){return Boolean(null==e?void 0:e.showHistory)&&!0===(null==t?void 0:t.hasHistory)}function C(e,t){return Boolean(null==e?void 0:e.showDiff)&&!0===(null==t?void 0:t.hasHistory)&&Boolean(null==t?void 0:t.currentVersionLabel)}function E(e){return!1!==(null==e?void 0:e.enablePublish)}function S(e,t=new Date){if(!e)return null;const o=Date.parse(e);if(Number.isNaN(o))return null;const n=t.getTime()-o;return n<6e4?"刚刚":n<36e5?`${Math.floor(n/6e4)} 分钟前`:n<864e5?`${Math.floor(n/36e5)} 小时前`:n<2592e6?`${Math.floor(n/864e5)} 天前`:new Date(o).toLocaleDateString()}const V=["html","javascript","css"];function B(){let e=null,t=[];function o(){if(e)return e;e={html:f.editor.createModel("","html"),javascript:f.editor.createModel("","javascript"),css:f.editor.createModel("","css")};for(const o of V)e[o].onDidChangeContent(()=>{for(const e of t)e(o)});return e}return{useModels:o,getSource:e=>o()[e].getValue(),setSource(e,t){const n=o()[e];n.getValue()!==t&&n.setValue(t)},snapshotSources(){const e=o();return{html:e.html.getValue(),javascript:e.javascript.getValue(),css:e.css.getValue()}},onSourceChange:e=>(o(),t.push(e),()=>{t=t.filter(t=>t!==e)}),dispose(){if(e){for(const t of V)e[t].dispose();e=null,t=[]}}}}const N=Symbol("hc-editor-scope");function z(){const t=e.inject(N);if(!t)throw new Error("[hypercard] useEditorScope 必须在 <HyperCardEditor> 内使用。组件作者:不要直接挂 MonacoEditor / EditorGroup 等内部组件。");return t}const T={html:"index.html",javascript:"component.js",css:"styles.css"},D=e.defineComponent({__name:"MonacoEditor",props:{sourceKey:{},readOnly:{type:Boolean,default:!1},theme:{default:"vs"},fontSize:{default:13},minimap:{type:Boolean,default:!1}},emits:["save","pushVersion"],setup(t,{emit:o}){globalThis.__HC_MONACO_BOOTSTRAPPED__||(self.MonacoEnvironment={getWorker:(e,t)=>"json"===t?new a:"css"===t||"scss"===t||"less"===t?new i:"html"===t||"handlebars"===t||"razor"===t?new s:"typescript"===t||"javascript"===t?new l:new r},globalThis.__HC_MONACO_BOOTSTRAPPED__=!0),y();const n=t,c=o,u=e.ref(null),d=e.shallowRef(null),p=z();return e.onMounted(()=>{if(!u.value)return;const e=p.useModels(),t=f.editor.create(u.value,{model:e[n.sourceKey],readOnly:n.readOnly,theme:n.theme,fontSize:n.fontSize,fontFamily:"JetBrains Mono, SF Mono, Menlo, Consolas, Liberation Mono, monospace",lineNumbers:"on",minimap:{enabled:n.minimap},automaticLayout:!0,tabSize:2,insertSpaces:!0,scrollBeyondLastLine:!1,smoothScrolling:!0,renderWhitespace:"selection",wordWrap:"on",padding:{top:8,bottom:8}});d.value=t,t.addCommand(f.KeyMod.CtrlCmd|f.KeyCode.KeyS,()=>c("save")),t.addCommand(f.KeyMod.CtrlCmd|f.KeyMod.Shift|f.KeyCode.KeyS,()=>c("pushVersion"))}),e.onBeforeUnmount(()=>{var e;null==(e=d.value)||e.dispose(),d.value=null}),e.watch(()=>n.sourceKey,e=>{const t=d.value;if(!t)return;const o=p.useModels();t.setModel(o[e])}),e.watch(()=>n.readOnly,e=>{var t;return null==(t=d.value)?void 0:t.updateOptions({readOnly:e})}),(t,o)=>(e.openBlock(),e.createElementBlock("div",{ref_key:"containerRef",ref:u,class:"w-full h-full"},null,512))}}),_={class:"hc-editor-tabs h-8 border-b border-zinc-200 flex items-end text-xs shrink-0 overflow-x-auto overflow-y-hidden"},M=["onClick","onDragstart","onDragover","onDrop"],L={key:0,class:"hc-editor-insert-line absolute z-30 left-0 top-0 bottom-0 w-0.5 bg-blue-500"},P={class:"font-mono text-[11px] leading-none truncate"},j={key:0,class:"hc-editor-insert-line absolute z-30 left-0 top-0 bottom-0 w-0.5 bg-blue-500"},I={key:1,class:"absolute inset-0 grid place-items-center text-xs text-zinc-400"},O=e.defineComponent({__name:"EditorGroup",props:{group:{},isOnly:{type:Boolean}},emits:["activate","tab-drop","save","pushVersion"],setup(t,{emit:o}){const n=t,r=o,a=e.ref(null),i=e.ref(null),s=e.ref(null);function l(e){var t;return Array.from((null==(t=e.dataTransfer)?void 0:t.types)??[]).includes("application/x-hc-tab")}function c(e){e.dataTransfer&&(e.dataTransfer.dropEffect=e.altKey||e.metaKey?"copy":"move")}function u(){a.value=null,i.value=null,s.value=null}function d(e){var t;const o=null==(t=e.dataTransfer)?void 0:t.getData("application/x-hc-tab");if(!o)return null;try{const e=JSON.parse(o);return"string"==typeof e.sourceGroupId&&"string"==typeof e.sourceTab?e:null}catch{return null}}function p(e){if(!l(e))return;e.preventDefault(),e.stopPropagation(),c(e);const t=e.currentTarget;a.value=function(e,t){const o=t.getBoundingClientRect(),n=(e.clientX-o.left)/o.width,r=(e.clientY-o.top)/o.height;return r<.22?"top":r>.78?"bottom":n<.5?"left":"right"}(e,t),i.value=null}function f(e){const t=e.currentTarget,o=e.relatedTarget;o&&t.contains(o)||u()}function v(e){if(!l(e))return;e.preventDefault(),e.stopPropagation();const t=d(e),o=a.value;u(),t&&o&&"center"!==o&&r("tab-drop",{sourceGroupId:t.sourceGroupId,sourceTab:t.sourceTab,targetGroupId:n.group.id,edge:o,insertIndex:null,copy:e.altKey||e.metaKey})}function h(e,t){l(e)&&(e.preventDefault(),c(e),a.value="center",i.value=t,e.stopPropagation())}function m(e,t){if(!l(e))return;e.preventDefault(),e.stopPropagation();const o=d(e);u(),o&&r("tab-drop",{sourceGroupId:o.sourceGroupId,sourceTab:o.sourceTab,targetGroupId:n.group.id,edge:"center",insertIndex:t,copy:e.altKey||e.metaKey})}const y=e.computed(()=>{switch(a.value){case"left":return"hc-editor-drop-overlay--left";case"right":return"hc-editor-drop-overlay--right";case"top":return"hc-editor-drop-overlay--top";case"bottom":return"hc-editor-drop-overlay--bottom";default:return""}});return(o,l)=>(e.openBlock(),e.createElementBlock("div",{class:"hc-editor-group relative w-full h-full flex flex-col bg-white border border-zinc-200 overflow-hidden",onDragleave:f,onDragendCapture:u},[e.createElementVNode("div",_,[(e.openBlock(!0),e.createElementBlock(e.Fragment,null,e.renderList(t.group.tabs,(o,l)=>(e.openBlock(),e.createElementBlock("div",{key:o+"-"+l,draggable:!0,class:e.normalizeClass(["hc-editor-tab h-7 min-w-0 px-3 flex items-center gap-1.5 border-r border-zinc-200 transition-colors cursor-pointer select-none relative",[t.group.activeTab===o?"hc-editor-tab--active bg-white text-zinc-950 border-t-2 border-t-zinc-900 border-b border-b-white":"text-zinc-500 hover:text-zinc-900 hover:bg-zinc-50",s.value===o?"hc-editor-tab--dragging opacity-45":""]]),onClick:e=>r("activate",o),onDragstart:e=>function(e,t){e.dataTransfer&&(s.value=t,e.dataTransfer.effectAllowed="copyMove",e.dataTransfer.setData("application/x-hc-tab",JSON.stringify({sourceGroupId:n.group.id,sourceTab:t})),e.dataTransfer.setData("text/plain",T[t]))}(e,o),onDragover:e=>h(e,l),onDrop:e=>m(e,l)},["center"===a.value&&i.value===l?(e.openBlock(),e.createElementBlock("span",L)):e.createCommentVNode("",!0),e.createElementVNode("span",P,e.toDisplayString(e.unref(T)[o]),1)],42,M))),128)),e.createElementVNode("div",{class:"hc-editor-tab-tail flex-1 relative min-w-6",onDragover:l[0]||(l[0]=e=>h(e,t.group.tabs.length)),onDrop:l[1]||(l[1]=e=>m(e,t.group.tabs.length))},["center"===a.value&&i.value===t.group.tabs.length?(e.openBlock(),e.createElementBlock("span",j)):e.createCommentVNode("",!0)],32)]),e.createElementVNode("div",{class:"flex-1 min-h-0 relative",onDragoverCapture:p,onDropCapture:v},[t.group.tabs.length>0?(e.openBlock(),e.createBlock(D,{key:0,"source-key":t.group.activeTab,onSave:l[2]||(l[2]=e=>r("save")),onPushVersion:l[3]||(l[3]=e=>r("pushVersion"))},null,8,["source-key"])):(e.openBlock(),e.createElementBlock("div",I," 把 tab 拖到这里打开 ")),a.value?(e.openBlock(),e.createElementBlock("div",{key:2,class:e.normalizeClass(["hc-editor-drop-overlay pointer-events-none absolute z-50 bg-blue-500/15 border-2 border-blue-500 ring-2 ring-blue-500/20 transition-all",y.value])},null,2)):e.createCommentVNode("",!0)],32)],32))}}),K=["aria-orientation"],H=e.defineComponent({__name:"Splitter",props:{direction:{default:"horizontal"},ratio:{},handleSize:{default:6}},emits:["update:ratio"],setup(t,{emit:o}){const n=t,r=o,a=e.ref(null),i=e.ref(!1);let s="",l="";const c=e.computed(()=>"horizontal"===n.direction),u=e.computed(()=>(c.value,{flexBasis:`calc(${100*n.ratio}% - ${n.handleSize/2}px)`})),d=e.computed(()=>(c.value,{flexBasis:`calc(${100*(1-n.ratio)}% - ${n.handleSize/2}px)`})),p=e.computed(()=>c.value?{flexBasis:`${n.handleSize}px`,width:`${n.handleSize}px`}:{flexBasis:`${n.handleSize}px`,height:`${n.handleSize}px`});function f(e){if(!a.value)return;const t=a.value.getBoundingClientRect(),o=c.value?(e.clientX-t.left)/t.width:(e.clientY-t.top)/t.height;r("update:ratio",Math.max(.08,Math.min(.92,o)))}function v(e){e.preventDefault(),e.stopPropagation(),i.value=!0,f(e);const t=e.currentTarget;try{t.setPointerCapture(e.pointerId)}catch{}s=document.body.style.cursor,l=document.body.style.userSelect,document.body.style.cursor=c.value?"col-resize":"row-resize",document.body.style.userSelect="none",window.addEventListener("pointermove",h),window.addEventListener("pointerup",b,{once:!0}),window.addEventListener("pointercancel",b,{once:!0})}function h(e){i.value&&(e.preventDefault(),f(e))}function m(){i.value&&(i.value=!1,document.body.style.cursor=s,document.body.style.userSelect=l,window.removeEventListener("pointermove",h),window.removeEventListener("pointerup",b),window.removeEventListener("pointercancel",b))}function y(e){const t=e.currentTarget;try{t.hasPointerCapture(e.pointerId)&&t.releasePointerCapture(e.pointerId)}catch{}m()}function b(){m()}return e.onBeforeUnmount(()=>{m()}),(t,o)=>(e.openBlock(),e.createElementBlock("div",{ref_key:"containerRef",ref:a,class:e.normalizeClass(["hc-editor-splitter relative w-full h-full min-w-0 min-h-0",c.value?"hc-editor-splitter--horizontal flex flex-row":"hc-editor-splitter--vertical flex flex-col"])},[e.createElementVNode("div",{class:"hc-editor-splitter-pane min-w-0 min-h-0 overflow-hidden",style:e.normalizeStyle(u.value)},[e.renderSlot(t.$slots,"a")],4),e.createElementVNode("div",{class:e.normalizeClass(["hc-editor-splitter-handle shrink-0 select-none",[c.value?"hc-editor-splitter-handle--horizontal":"hc-editor-splitter-handle--vertical",i.value?"hc-editor-splitter-handle--dragging":""]]),style:e.normalizeStyle(p.value),role:"separator","aria-orientation":c.value?"vertical":"horizontal",onPointerdown:v,onPointerup:y},null,46,K),e.createElementVNode("div",{class:"hc-editor-splitter-pane min-w-0 min-h-0 overflow-hidden",style:e.normalizeStyle(d.value)},[e.renderSlot(t.$slots,"b")],4)],2))}}),R=e.defineComponent({__name:"EditorLayout",props:{node:{},isOnly:{type:Boolean}},emits:["activate","tab-drop","set-ratio","save","pushVersion"],setup(t,{emit:o}){const n=o;return(o,r)=>{const a=e.resolveComponent("EditorLayout",!0);return"group"===t.node.type?(e.openBlock(),e.createBlock(O,{key:0,group:t.node,"is-only":t.isOnly??!1,onActivate:r[0]||(r[0]=e=>n("activate",t.node.id,e)),onTabDrop:r[1]||(r[1]=e=>n("tab-drop",e)),onSave:r[2]||(r[2]=e=>n("save")),onPushVersion:r[3]||(r[3]=e=>n("pushVersion"))},null,8,["group","is-only"])):(e.openBlock(),e.createBlock(H,{key:1,direction:t.node.direction,ratio:t.node.ratio,"onUpdate:ratio":r[14]||(r[14]=e=>n("set-ratio",t.node.id,e))},{a:e.withCtx(()=>[e.createVNode(a,{node:t.node.a,onActivate:r[4]||(r[4]=(e,t)=>n("activate",e,t)),onTabDrop:r[5]||(r[5]=e=>n("tab-drop",e)),onSetRatio:r[6]||(r[6]=(e,t)=>n("set-ratio",e,t)),onSave:r[7]||(r[7]=e=>n("save")),onPushVersion:r[8]||(r[8]=e=>n("pushVersion"))},null,8,["node"])]),b:e.withCtx(()=>[e.createVNode(a,{node:t.node.b,onActivate:r[9]||(r[9]=(e,t)=>n("activate",e,t)),onTabDrop:r[10]||(r[10]=e=>n("tab-drop",e)),onSetRatio:r[11]||(r[11]=(e,t)=>n("set-ratio",e,t)),onSave:r[12]||(r[12]=e=>n("save")),onPushVersion:r[13]||(r[13]=e=>n("pushVersion"))},null,8,["node"])]),_:1},8,["direction","ratio"]))}}});let $=0;function A(e){return`${e}_${++$}_${Date.now().toString(36)}`}function G(e,t){return{type:"group",id:A("g"),tabs:[...e],activeTab:t??e[0]}}function q(e){return JSON.parse(JSON.stringify(e))}function J(e,t){return"group"===e.type?e.id===t?e:null:J(e.a,t)??J(e.b,t)}function U(e,t){return"group"===e.type?null:e.id===t?e:U(e.a,t)??U(e.b,t)}function Y(e,t,o){if("group"===e.type)return e.id===t?o:e;if(e.id===t)return o;const n=Y(e.a,t,o),r=Y(e.b,t,o);return n||r?n?r?{...e,a:n,b:r}:n:r:null}function W(e,t,o){const n=q(e),r=J(n,t);return r&&r.tabs.includes(o)&&(r.activeTab=o),n}function F(e,t,o){const n=q(e),r=J(n,t);if(!r)return n;const a=r.tabs.indexOf(o);return-1===a?n:(r.tabs.splice(a,1),0===r.tabs.length?"group"===n.type&&n.id===t?n:Y(n,t,null)??n:(r.activeTab===o&&(r.activeTab=r.tabs[Math.min(a,r.tabs.length-1)]),n))}function X(e,t,o,n,r,a=!1){let i=q(e);const s=J(i,n);if(!s)return i;if(t===n&&!a){const e=s.tabs.indexOf(o);if(-1===e)return i;s.tabs.splice(e,1);const t=null==r?s.tabs.length:Math.min(r,s.tabs.length);return s.tabs.splice(t,0,o),s.activeTab=o,i}if(!s.tabs.includes(o)){const e=null==r?s.tabs.length:Math.min(r,s.tabs.length);s.tabs.splice(e,0,o)}return s.activeTab=o,a||(i=F(i,t,o)),i}function Q(e,t,o,n,r,a=!1){let i=q(e);const s=J(i,n);if(!s)return i;if(t===n&&1===s.tabs.length&&s.tabs[0]===o&&!a)return i;const l=G([o],o),c="left"===r||"right"===r?"horizontal":"vertical",u={type:"split",id:A("s"),direction:c,ratio:.5,a:"right"===r||"bottom"===r?q(s):l,b:"right"===r||"bottom"===r?l:q(s)},d=Y(i,s.id,u);return d?(i=d,a||(i=F(i,t,o)),i):i}function Z(e,t,o){const n=q(e),r=U(n,t);return r&&(r.ratio=Math.max(.05,Math.min(.95,o))),n}function ee(){return G(["html","javascript","css"],"javascript")}function te(e){return"hc:draft:"+e}function oe(e){if(!e)return null;const t=localStorage.getItem(te(e));if(!t)return null;try{const e=JSON.parse(t);if("string"==typeof e.html&&"string"==typeof e.javascript&&"string"==typeof e.css&&"number"==typeof e.savedAt)return e}catch{}return null}function ne(e,t){if(!e)return null;const o={html:t.html,javascript:t.javascript,css:t.css,savedAt:Date.now()};try{return localStorage.setItem(te(e),JSON.stringify(o)),o}catch(e){return console.warn("[hc-draft] saveDraft failed:",e),null}}function re(e){e&&localStorage.removeItem(te(e))}function ae(e){const t=new BroadcastChannel(`hc-preview-${e}`);return{channel:t,send(e){t.postMessage(e)},close(){t.close()}}}function ie(...e){return d.twMerge(u.clsx(e))}const se=p.cva("inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",{variants:{variant:{default:"bg-primary text-primary-foreground shadow-xs hover:bg-primary/90",destructive:"bg-destructive text-white shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",outline:"border border-input bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50",secondary:"bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80",ghost:"hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",link:"text-primary underline-offset-4 hover:underline"},size:{default:"h-9 px-4 py-2 has-[>svg]:px-3",xs:"h-7 rounded-md px-2 text-xs has-[>svg]:px-1.5",sm:"h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5",lg:"h-10 rounded-md px-6 has-[>svg]:px-4",icon:"size-9","icon-sm":"size-8"}},defaultVariants:{variant:"default",size:"default"}}),le=e.defineComponent({__name:"Button",props:{variant:{},size:{},class:{type:[Boolean,null,String,Object,Array]},asChild:{type:Boolean},as:{default:"button"}},setup(t){const o=t;return(n,r)=>(e.openBlock(),e.createBlock(e.unref(c.Primitive),{"data-slot":"button",as:t.as,"as-child":t.asChild,class:e.normalizeClass(e.unref(ie)(e.unref(se)({variant:t.variant,size:t.size}),o.class))},{default:e.withCtx(()=>[e.renderSlot(n.$slots,"default")]),_:3},8,["as","as-child","class"]))}}),ce={class:"h-full w-full flex flex-col bg-zinc-50 text-zinc-900"},ue={class:"h-11 border-b border-zinc-200 bg-white flex items-center px-4 gap-3 shrink-0"},de={key:1,class:"h-4 w-px bg-zinc-200"},pe={class:"flex items-baseline gap-2 min-w-0"},fe={key:0,class:"text-sm font-medium truncate"},ve={key:1,class:"text-[11px] text-zinc-400 tabular-nums"},he=["title"],me={key:3,class:"text-[11px] text-amber-600"},ye={key:2,class:"hc-editor-draft-pill"},be={class:"hc-editor-draft-pill-text"},ge={class:"ml-auto flex items-center gap-1.5"},xe={class:"flex items-center gap-1.5 text-[11px] text-zinc-500 select-none cursor-pointer"},we={class:"flex-1 min-h-0 p-2 bg-zinc-50"},ke={class:"w-full h-full"},Ce={class:"hc-editor-preview-pane w-full h-full bg-white border border-zinc-200 flex flex-col overflow-hidden"},Ee={class:"hc-editor-preview-header"},Se={class:"hc-editor-preview-tab"},Ve={class:"hc-editor-preview-actions"},Be={class:"flex-1 min-h-0 overflow-auto bg-white"},Ne={class:"hc-editor-preview-body min-h-full px-5 py-4"},ze={key:1,class:"w-full h-full"},Te=e.defineComponent({__name:"HyperCardEditor",props:{modelValue:{},draftKey:{},componentId:{},showBackButton:{type:Boolean,default:!1},title:{},statusLabel:{},versionStatus:{},features:{},autoPreview:{type:Boolean,default:!0},previewRatio:{default:.62},extraLibs:{}},emits:["update:modelValue","dirty","save-draft","push-version","open-preview-window","open-settings","open-history","open-diff","go-back","error"],setup(n,{expose:r,emit:a}){var i;const s=n,l=a,c=B();e.provide(N,c);const u=e.ref(!1),d=e.ref(ee()),p=e.ref(s.previewRatio),f=e.ref(!1),v=e.ref(s.autoPreview),h=e.ref(""),m=e.ref(""),y=e.ref(""),b=e.ref(null),g=(null==(i=e.getCurrentInstance())?void 0:i.uid)??Math.floor(1e9*Math.random()),V=e.computed(()=>s.componentId??s.draftKey??`__hc_editor_${g}__`),z=e.computed(()=>`editor-preview-${V.value}`),T=e.computed(()=>{var e;return(null==(e=s.versionStatus)?void 0:e.currentVersionLabel)??s.statusLabel??""}),D=e.computed(()=>{var e;return S(null==(e=s.versionStatus)?void 0:e.lastPublishedAt)}),_=e.computed(()=>k(s.features,s.versionStatus)),M=e.computed(()=>C(s.features,s.versionStatus)),L=e.computed(()=>E(s.features));let P=!1,j=null,I=null;function O(){const e=c.snapshotSources();l("update:modelValue",{html:e.html,js:e.javascript,css:e.css})}function K(e,t,o){P=!0;try{c.setSource("html",e),c.setSource("javascript",t),c.setSource("css",o)}finally{P=!1}}e.watch(()=>s.modelValue,e=>{if(!e)return;const t=c.snapshotSources();t.html===(e.html??"")&&t.javascript===(e.js??"")&&t.css===(e.css??"")||(K(e.html??"",e.js??"",e.css??""),A(),U(),u.value=!1,l("dirty",!1))},{immediate:!0});let $=null;function A(){const e=c.snapshotSources();h.value=e.html,m.value=e.javascript,y.value=e.css}function G(){$&&clearTimeout($),A()}function q(){s.draftKey&&!j&&(j=ae(s.draftKey),j.channel.addEventListener("message",e=>{const t=e.data;"hello"===t.type?(f.value=!0,J()):"goodbye"===t.type?f.value=!1:"request-sources"===t.type&&J()}))}function J(){if(!j)return;const e=c.snapshotSources();j.send({type:"sources",html:e.html,javascript:e.javascript,css:e.css})}function U(){j&&(I&&clearTimeout(I),I=setTimeout(J,200))}function Y(){s.draftKey?(ne(s.draftKey,c.snapshotSources())&&(u.value=!1,l("dirty",!1),b.value=null),l("save-draft")):l("save-draft")}function F(){const e=b.value;e&&(K(e.html,e.javascript,e.css),A(),O(),U(),b.value=null,u.value=!0,l("dirty",!0))}function te(){s.draftKey&&(re(s.draftKey),b.value=null)}function ie(e,t){d.value=W(d.value,e,t)}function se(e){"center"===e.edge?d.value=X(d.value,e.sourceGroupId,e.sourceTab,e.targetGroupId,e.insertIndex,e.copy):d.value=Q(d.value,e.sourceGroupId,e.sourceTab,e.targetGroupId,e.edge,e.copy)}function Te(e,t){d.value=Z(d.value,e,t)}function De(e){u.value&&(e.preventDefault(),e.returnValue="")}let _e=null;const Me=[];function Le(){q(),l("open-preview-window")}function Pe(e){const t=Date.now()-e;return t<6e4?"刚刚":t<36e5?`${Math.floor(t/6e4)} 分钟前`:t<864e5?`${Math.floor(t/36e5)} 小时前`:new Date(e).toLocaleString()}return e.onMounted(()=>{if(s.extraLibs)for(const e of s.extraLibs)x(e),Me.push(e.filePath);_e=c.onSourceChange(()=>{P||(u.value=!0,l("dirty",!0),O(),U(),v.value&&($&&clearTimeout($),$=setTimeout(A,600)))}),s.draftKey&&(b.value=oe(s.draftKey),q()),window.addEventListener("beforeunload",De)}),e.onBeforeUnmount(()=>{null==_e||_e(),_e=null,I&&clearTimeout(I),$&&clearTimeout($),null==j||j.close(),j=null,window.removeEventListener("beforeunload",De);for(const e of Me)w(e);Me.length=0,c.dispose()}),r({isDirty:u,confirmLeave:function(){return!u.value||window.confirm("当前修改未保存,确定离开?未保存内容将丢失。")},applyDraft:function(e){K(e.html,e.javascript,e.css),A(),O(),U(),u.value=!0,l("dirty",!0)},manualPreview:G,applyPreview:A}),(r,a)=>{var i;return e.openBlock(),e.createElementBlock("div",ce,[e.createElementVNode("header",ue,[n.showBackButton?(e.openBlock(),e.createBlock(e.unref(le),{key:0,variant:"ghost",size:"sm",onClick:a[0]||(a[0]=e=>l("go-back"))},{default:e.withCtx(()=>[e.createVNode(e.unref(t.ArrowLeft),{size:14}),a[9]||(a[9]=e.createTextVNode(" 返回 ",-1))]),_:1})):e.createCommentVNode("",!0),n.showBackButton?(e.openBlock(),e.createElementBlock("span",de)):e.createCommentVNode("",!0),e.createElementVNode("div",pe,[n.title?(e.openBlock(),e.createElementBlock("h1",fe,e.toDisplayString(n.title),1)):e.createCommentVNode("",!0),T.value?(e.openBlock(),e.createElementBlock("span",ve,e.toDisplayString(T.value),1)):e.createCommentVNode("",!0),D.value?(e.openBlock(),e.createElementBlock("span",{key:2,class:"text-[11px] text-zinc-400",title:null==(i=n.versionStatus)?void 0:i.lastPublishedAt}," · 上次发布 "+e.toDisplayString(D.value),9,he)):e.createCommentVNode("",!0),u.value?(e.openBlock(),e.createElementBlock("span",me,"●未保存")):e.createCommentVNode("",!0)]),b.value?(e.openBlock(),e.createElementBlock("div",ye,[a[10]||(a[10]=e.createElementVNode("span",{class:"hc-editor-draft-pill-dot"},null,-1)),e.createElementVNode("span",be," 本地草稿 · "+e.toDisplayString(Pe(b.value.savedAt))+"保存 ",1),e.createElementVNode("button",{type:"button",class:"hc-editor-draft-pill-action hc-editor-draft-pill-action--primary",onClick:F}," 恢复 "),e.createElementVNode("button",{type:"button",class:"hc-editor-draft-pill-action",onClick:te}," 丢弃 ")])):e.createCommentVNode("",!0),e.createElementVNode("div",ge,[e.createElementVNode("label",xe,[e.withDirectives(e.createElementVNode("input",{"onUpdate:modelValue":a[1]||(a[1]=e=>v.value=e),type:"checkbox",class:"h-3 w-3 rounded border-zinc-300 text-zinc-900 focus:ring-0"},null,512),[[e.vModelCheckbox,v.value]]),a[11]||(a[11]=e.createTextVNode(" 自动预览 ",-1))]),M.value?(e.openBlock(),e.createBlock(e.unref(le),{key:0,variant:"ghost",size:"xs",title:"查看当前改动",onClick:a[2]||(a[2]=e=>l("open-diff"))},{default:e.withCtx(()=>[e.createVNode(e.unref(t.GitCompare),{size:13}),a[12]||(a[12]=e.createTextVNode(" 看改动 ",-1))]),_:1})):e.createCommentVNode("",!0),_.value?(e.openBlock(),e.createBlock(e.unref(le),{key:1,variant:"ghost",size:"xs",title:"历史版本",onClick:a[3]||(a[3]=e=>l("open-history"))},{default:e.withCtx(()=>[e.createVNode(e.unref(t.History),{size:13}),a[13]||(a[13]=e.createTextVNode(" 历史版本 ",-1))]),_:1})):e.createCommentVNode("",!0),e.createVNode(e.unref(le),{variant:"ghost",size:"icon-sm",title:"组件设置",onClick:a[4]||(a[4]=e=>l("open-settings"))},{default:e.withCtx(()=>[e.createVNode(e.unref(t.Settings),{size:14})]),_:1}),e.createVNode(e.unref(le),{variant:"outline",size:"xs",onClick:Y},{default:e.withCtx(()=>[...a[14]||(a[14]=[e.createTextVNode(" 保存草稿 ",-1)])]),_:1}),L.value?(e.openBlock(),e.createBlock(e.unref(le),{key:2,variant:"default",size:"xs",onClick:a[5]||(a[5]=e=>l("push-version"))},{default:e.withCtx(()=>[...a[15]||(a[15]=[e.createTextVNode(" 推版本 ",-1)])]),_:1})):e.createCommentVNode("",!0)])]),e.createElementVNode("div",we,[f.value?(e.openBlock(),e.createElementBlock("div",ze,[e.createVNode(R,{node:d.value,"is-only":"group"===d.value.type,onActivate:ie,onTabDrop:se,onSetRatio:Te,onSave:Y,onPushVersion:a[8]||(a[8]=e=>l("push-version"))},null,8,["node","is-only"]),a[18]||(a[18]=e.createElementVNode("div",{class:"fixed bottom-3 right-3 bg-zinc-900 text-white text-[11px] px-3 py-1.5 rounded-md shadow-md flex items-center gap-2"},[e.createElementVNode("span",{class:"h-1.5 w-1.5 rounded-full bg-emerald-400"}),e.createTextVNode(" 预览已脱出 · 关闭独立窗口可恢复 ")],-1))])):(e.openBlock(),e.createBlock(H,{key:0,direction:"horizontal",ratio:p.value,"onUpdate:ratio":a[7]||(a[7]=e=>p.value=e)},{a:e.withCtx(()=>[e.createElementVNode("div",ke,[e.createVNode(R,{node:d.value,"is-only":"group"===d.value.type,onActivate:ie,onTabDrop:se,onSetRatio:Te,onSave:Y,onPushVersion:a[6]||(a[6]=e=>l("push-version"))},null,8,["node","is-only"])])]),b:e.withCtx(()=>[e.createElementVNode("div",Ce,[e.createElementVNode("div",Ee,[e.createElementVNode("div",Se,[a[16]||(a[16]=e.createElementVNode("span",{class:"hc-editor-preview-title"},"预览",-1)),e.createElementVNode("button",{type:"button",class:"hc-editor-icon-button hc-editor-preview-icon-button",title:"独立窗口预览",onClick:Le},[e.createVNode(e.unref(t.ExternalLink),{size:13})])]),e.createElementVNode("div",Ve,[v.value?e.createCommentVNode("",!0):(e.openBlock(),e.createElementBlock("button",{key:0,type:"button",class:"hc-editor-preview-refresh",onClick:G},[e.createVNode(e.unref(t.RefreshCw),{size:12}),a[17]||(a[17]=e.createTextVNode(" 重新渲染 ",-1))]))])]),e.createElementVNode("div",Be,[e.createElementVNode("div",Ne,[e.createVNode(e.unref(o.RuntimeBox),{"instance-id":z.value,"component-id":`user-${V.value}`,"html-source":h.value,"js-source":m.value,"css-source":y.value},null,8,["instance-id","component-id","html-source","js-source","css-source"])])])])]),_:1},8,["ratio"]))])])}}});exports.Button=le,exports.EDITOR_SCOPE_KEY=N,exports.EditorGroup=O,exports.EditorLayout=R,exports.HyperCardEditor=Te,exports.MonacoEditor=D,exports.SOURCE_KEY_LABEL=T,exports.Splitter=H,exports.VERSION="0.0.0-stage3",exports.activateTab=W,exports.addMonacoExtraLib=function(e){x(e)},exports.buttonVariants=se,exports.clearDraft=re,exports.closeTab=F,exports.createEditorScope=B,exports.createPreviewBus=ae,exports.defaultLayout=ee,exports.formatLastPublished=S,exports.formatRelative=function(e){const t=Date.now()-e,o=Math.floor(t/6e4);if(o<1)return"刚刚";if(o<60)return`${o} 分钟前`;const n=Math.floor(o/60);return n<24?`${n} 小时前`:`${Math.floor(n/24)} 天前`},exports.loadDraft=oe,exports.moveTab=X,exports.releaseMonacoExtraLib=w,exports.removeMonacoExtraLib=function(e){w(e)},exports.retainMonacoExtraLib=x,exports.saveDraft=ne,exports.setSplitRatio=Z,exports.setupHyperCardMonacoTypes=y,exports.shouldShowDiffEntry=C,exports.shouldShowHistoryEntry=k,exports.shouldShowPublishEntry=E,exports.splitWithTab=Q,exports.useEditorScope=z;
@@ -0,0 +1,348 @@
1
+ import { AsTag } from 'reka-ui';
2
+ import { ClassProp } from 'class-variance-authority/types';
3
+ import { Component } from 'vue';
4
+ import { ComponentOptionsMixin } from 'vue';
5
+ import { ComponentProvideOptions } from 'vue';
6
+ import { DefineComponent } from 'vue';
7
+ import { HTMLAttributes } from 'vue';
8
+ import { InjectionKey } from 'vue';
9
+ import * as monaco from 'monaco-editor';
10
+ import { PrimitiveProps } from 'reka-ui';
11
+ import { PublicProps } from 'vue';
12
+ import { Ref } from 'vue';
13
+ import { VariantProps } from 'class-variance-authority';
14
+
15
+ declare const __VLS_component: DefineComponent<__VLS_Props_5, {}, {}, {}, {}, ComponentOptionsMixin, ComponentOptionsMixin, {} & {
16
+ "update:ratio": (v: number) => any;
17
+ }, string, PublicProps, Readonly<__VLS_Props_5> & Readonly<{
18
+ "onUpdate:ratio"?: ((v: number) => any) | undefined;
19
+ }>, {
20
+ direction: "horizontal" | "vertical";
21
+ handleSize: number;
22
+ }, {}, {}, {}, string, ComponentProvideOptions, false, {
23
+ containerRef: HTMLDivElement;
24
+ }, HTMLDivElement>;
25
+
26
+ declare const __VLS_component_2: DefineComponent<Props, {}, {}, {}, {}, ComponentOptionsMixin, ComponentOptionsMixin, {}, string, PublicProps, Readonly<Props> & Readonly<{}>, {
27
+ as: AsTag | Component;
28
+ }, {}, {}, {}, string, ComponentProvideOptions, false, {}, any>;
29
+
30
+ declare type __VLS_Props = {
31
+ modelValue: Source;
32
+ draftKey?: string;
33
+ componentId?: string;
34
+ showBackButton?: boolean;
35
+ title?: string;
36
+ statusLabel?: string;
37
+ versionStatus?: VersionStatus;
38
+ features?: EditorFeatures;
39
+ autoPreview?: boolean;
40
+ previewRatio?: number;
41
+ extraLibs?: ExtraLib[];
42
+ };
43
+
44
+ declare type __VLS_Props_2 = {
45
+ node: LayoutNode;
46
+ isOnly?: boolean;
47
+ };
48
+
49
+ declare type __VLS_Props_3 = {
50
+ group: GroupNode;
51
+ isOnly: boolean;
52
+ };
53
+
54
+ declare type __VLS_Props_4 = {
55
+ sourceKey: SourceKey;
56
+ readOnly?: boolean;
57
+ theme?: 'vs' | 'vs-dark';
58
+ fontSize?: number;
59
+ minimap?: boolean;
60
+ };
61
+
62
+ declare type __VLS_Props_5 = {
63
+ direction?: 'horizontal' | 'vertical';
64
+ ratio: number;
65
+ handleSize?: number;
66
+ };
67
+
68
+ declare function __VLS_template(): {
69
+ attrs: Partial<{}>;
70
+ slots: {
71
+ a?(_: {}): any;
72
+ b?(_: {}): any;
73
+ };
74
+ refs: {
75
+ containerRef: HTMLDivElement;
76
+ };
77
+ rootEl: HTMLDivElement;
78
+ };
79
+
80
+ declare function __VLS_template_2(): {
81
+ attrs: Partial<{}>;
82
+ slots: {
83
+ default?(_: {}): any;
84
+ };
85
+ refs: {};
86
+ rootEl: any;
87
+ };
88
+
89
+ declare type __VLS_TemplateResult = ReturnType<typeof __VLS_template>;
90
+
91
+ declare type __VLS_TemplateResult_2 = ReturnType<typeof __VLS_template_2>;
92
+
93
+ declare type __VLS_WithTemplateSlots<T, S> = T & {
94
+ new (): {
95
+ $slots: S;
96
+ };
97
+ };
98
+
99
+ declare type __VLS_WithTemplateSlots_2<T, S> = T & {
100
+ new (): {
101
+ $slots: S;
102
+ };
103
+ };
104
+
105
+ export declare function activateTab(root: LayoutNode, groupId: string, tab: SourceKey): LayoutNode;
106
+
107
+ export declare function addMonacoExtraLib(lib: ExtraLib): void;
108
+
109
+ declare function applyDraft(draft: ComponentDraft): void;
110
+
111
+ declare function applyPreview(): void;
112
+
113
+ export declare const Button: __VLS_WithTemplateSlots_2<typeof __VLS_component_2, __VLS_TemplateResult_2["slots"]>;
114
+
115
+ export declare type ButtonVariants = VariantProps<typeof buttonVariants>;
116
+
117
+ export declare const buttonVariants: (props?: ({
118
+ variant?: "link" | "default" | "destructive" | "outline" | "secondary" | "ghost" | null | undefined;
119
+ size?: "default" | "xs" | "sm" | "lg" | "icon" | "icon-sm" | null | undefined;
120
+ } & ClassProp) | undefined) => string;
121
+
122
+ export declare function clearDraft(componentId: string): void;
123
+
124
+ export declare function closeTab(root: LayoutNode, groupId: string, tab: SourceKey): LayoutNode;
125
+
126
+ export declare interface ComponentDraft {
127
+ html: string;
128
+ javascript: string;
129
+ css: string;
130
+ savedAt: number;
131
+ }
132
+
133
+ declare function confirmLeave(): boolean;
134
+
135
+ export declare function createEditorScope(): EditorScope;
136
+
137
+ export declare function createPreviewBus(componentId: string): PreviewBus;
138
+
139
+ export declare function defaultLayout(): LayoutNode;
140
+
141
+ export declare type DropEdge = 'center' | 'left' | 'right' | 'top' | 'bottom';
142
+
143
+ export declare const EDITOR_SCOPE_KEY: InjectionKey<EditorScope>;
144
+
145
+ export declare interface EditorFeatures {
146
+ showHistory?: boolean;
147
+ showDiff?: boolean;
148
+ enablePublish?: boolean;
149
+ }
150
+
151
+ export declare const EditorGroup: DefineComponent<__VLS_Props_3, {}, {}, {}, {}, ComponentOptionsMixin, ComponentOptionsMixin, {} & {
152
+ save: () => any;
153
+ pushVersion: () => any;
154
+ activate: (tab: SourceKey) => any;
155
+ "tab-drop": (payload: TabDropPayload) => any;
156
+ }, string, PublicProps, Readonly<__VLS_Props_3> & Readonly<{
157
+ onSave?: (() => any) | undefined;
158
+ onPushVersion?: (() => any) | undefined;
159
+ onActivate?: ((tab: SourceKey) => any) | undefined;
160
+ "onTab-drop"?: ((payload: TabDropPayload) => any) | undefined;
161
+ }>, {}, {}, {}, {}, string, ComponentProvideOptions, false, {}, HTMLDivElement>;
162
+
163
+ export declare const EditorLayout: DefineComponent<__VLS_Props_2, {}, {}, {}, {}, ComponentOptionsMixin, ComponentOptionsMixin, {} & {
164
+ save: () => any;
165
+ pushVersion: () => any;
166
+ activate: (groupId: string, tab: SourceKey) => any;
167
+ "tab-drop": (payload: TabDropPayload) => any;
168
+ "set-ratio": (splitId: string, ratio: number) => any;
169
+ }, string, PublicProps, Readonly<__VLS_Props_2> & Readonly<{
170
+ onSave?: (() => any) | undefined;
171
+ onPushVersion?: (() => any) | undefined;
172
+ onActivate?: ((groupId: string, tab: SourceKey) => any) | undefined;
173
+ "onTab-drop"?: ((payload: TabDropPayload) => any) | undefined;
174
+ "onSet-ratio"?: ((splitId: string, ratio: number) => any) | undefined;
175
+ }>, {}, {}, {}, {}, string, ComponentProvideOptions, false, {}, any>;
176
+
177
+ export declare interface EditorScope {
178
+ useModels(): ModelMap;
179
+ getSource(key: SourceKey): string;
180
+ setSource(key: SourceKey, value: string): void;
181
+ snapshotSources(): Record<SourceKey, string>;
182
+ onSourceChange(handler: (key: SourceKey) => void): () => void;
183
+ dispose(): void;
184
+ }
185
+
186
+ export declare interface ExtraLib {
187
+ content: string;
188
+ filePath: string;
189
+ }
190
+
191
+ export declare function formatLastPublished(iso?: string, now?: Date): string | null;
192
+
193
+ export declare function formatRelative(ts: number): string;
194
+
195
+ export declare interface GroupNode {
196
+ type: 'group';
197
+ id: string;
198
+ tabs: SourceKey[];
199
+ activeTab: SourceKey;
200
+ }
201
+
202
+ export declare const HyperCardEditor: DefineComponent<__VLS_Props, {
203
+ isDirty: Ref<boolean, boolean>;
204
+ confirmLeave: typeof confirmLeave;
205
+ applyDraft: typeof applyDraft;
206
+ manualPreview: typeof manualPreview;
207
+ applyPreview: typeof applyPreview;
208
+ }, {}, {}, {}, ComponentOptionsMixin, ComponentOptionsMixin, {
209
+ [x: string]: any;
210
+ } & {
211
+ [x: string]: any;
212
+ }, string, PublicProps, Readonly<__VLS_Props> & Readonly<{
213
+ [x: `on${Capitalize<any>}`]: ((...args: any) => any) | undefined;
214
+ }>, {
215
+ showBackButton: boolean;
216
+ autoPreview: boolean;
217
+ previewRatio: number;
218
+ }, {}, {}, {}, string, ComponentProvideOptions, false, {}, HTMLDivElement>;
219
+
220
+ export declare type LayoutNode = GroupNode | SplitNode;
221
+
222
+ export declare function loadDraft(componentId: string): ComponentDraft | null;
223
+
224
+ declare function manualPreview(): void;
225
+
226
+ export declare interface ModelMap {
227
+ html: monaco.editor.ITextModel;
228
+ javascript: monaco.editor.ITextModel;
229
+ css: monaco.editor.ITextModel;
230
+ }
231
+
232
+ export declare const MonacoEditor: DefineComponent<__VLS_Props_4, {}, {}, {}, {}, ComponentOptionsMixin, ComponentOptionsMixin, {} & {
233
+ save: () => any;
234
+ pushVersion: () => any;
235
+ }, string, PublicProps, Readonly<__VLS_Props_4> & Readonly<{
236
+ onSave?: (() => any) | undefined;
237
+ onPushVersion?: (() => any) | undefined;
238
+ }>, {
239
+ readOnly: boolean;
240
+ theme: "vs" | "vs-dark";
241
+ fontSize: number;
242
+ minimap: boolean;
243
+ }, {}, {}, {}, string, ComponentProvideOptions, false, {
244
+ containerRef: HTMLDivElement;
245
+ }, HTMLDivElement>;
246
+
247
+ export declare function moveTab(root: LayoutNode, sourceGroupId: string, tab: SourceKey, targetGroupId: string, insertIndex: number | null, copy?: boolean): LayoutNode;
248
+
249
+ declare interface PreviewBus {
250
+ channel: BroadcastChannel;
251
+ send(msg: PreviewMessage): void;
252
+ close(): void;
253
+ }
254
+
255
+ declare interface PreviewGoodbyeMessage {
256
+ type: 'goodbye';
257
+ windowId: string;
258
+ }
259
+
260
+ declare interface PreviewHelloMessage {
261
+ type: 'hello';
262
+ windowId: string;
263
+ }
264
+
265
+ export declare type PreviewMessage = PreviewSourcesMessage | PreviewHelloMessage | PreviewGoodbyeMessage | PreviewRequestSourcesMessage;
266
+
267
+ declare interface PreviewRequestSourcesMessage {
268
+ type: 'request-sources';
269
+ }
270
+
271
+ declare interface PreviewSourcesMessage {
272
+ type: 'sources';
273
+ html: string;
274
+ javascript: string;
275
+ css: string;
276
+ }
277
+
278
+ declare interface Props extends PrimitiveProps {
279
+ variant?: ButtonVariants['variant'];
280
+ size?: ButtonVariants['size'];
281
+ class?: HTMLAttributes['class'];
282
+ }
283
+
284
+ export declare function releaseMonacoExtraLib(filePath: string): void;
285
+
286
+ export declare function removeMonacoExtraLib(filePath: string): void;
287
+
288
+ export declare function retainMonacoExtraLib(lib: ExtraLib): void;
289
+
290
+ export declare function saveDraft(componentId: string, sources: {
291
+ html: string;
292
+ javascript: string;
293
+ css: string;
294
+ }): ComponentDraft | null;
295
+
296
+ export declare function setSplitRatio(root: LayoutNode, splitId: string, ratio: number): LayoutNode;
297
+
298
+ export declare function setupHyperCardMonacoTypes(): void;
299
+
300
+ export declare function shouldShowDiffEntry(features?: EditorFeatures, status?: VersionStatus): boolean;
301
+
302
+ export declare function shouldShowHistoryEntry(features?: EditorFeatures, status?: VersionStatus): boolean;
303
+
304
+ export declare function shouldShowPublishEntry(features?: EditorFeatures): boolean;
305
+
306
+ export declare interface Source {
307
+ html: string;
308
+ js: string;
309
+ css: string;
310
+ }
311
+
312
+ export declare const SOURCE_KEY_LABEL: Record<SourceKey, string>;
313
+
314
+ export declare type SourceKey = 'html' | 'javascript' | 'css';
315
+
316
+ export declare interface SplitNode {
317
+ type: 'split';
318
+ id: string;
319
+ direction: 'horizontal' | 'vertical';
320
+ ratio: number;
321
+ a: LayoutNode;
322
+ b: LayoutNode;
323
+ }
324
+
325
+ export declare const Splitter: __VLS_WithTemplateSlots<typeof __VLS_component, __VLS_TemplateResult["slots"]>;
326
+
327
+ export declare function splitWithTab(root: LayoutNode, sourceGroupId: string, tab: SourceKey, targetGroupId: string, edge: 'left' | 'right' | 'top' | 'bottom', copy?: boolean): LayoutNode;
328
+
329
+ export declare interface TabDropPayload {
330
+ sourceGroupId: string;
331
+ sourceTab: SourceKey;
332
+ targetGroupId: string;
333
+ edge: DropEdge;
334
+ insertIndex: number | null;
335
+ copy: boolean;
336
+ }
337
+
338
+ export declare function useEditorScope(): EditorScope;
339
+
340
+ export declare const VERSION = "0.0.0-stage3";
341
+
342
+ export declare interface VersionStatus {
343
+ currentVersionLabel?: string;
344
+ hasHistory?: boolean;
345
+ lastPublishedAt?: string;
346
+ }
347
+
348
+ export { }
package/dist/index.mjs ADDED
@@ -0,0 +1 @@
1
+ import{inject as e,defineComponent as t,ref as n,shallowRef as o,onMounted as r,onBeforeUnmount as a,watch as i,openBlock as s,createElementBlock as l,computed as u,createElementVNode as c,Fragment as d,renderList as p,normalizeClass as v,createCommentVNode as f,toDisplayString as h,unref as m,createBlock as g,normalizeStyle as b,renderSlot as y,resolveComponent as w,withCtx as x,createVNode as k,provide as S,getCurrentInstance as z,createTextVNode as C,withDirectives as T,vModelCheckbox as D}from"vue";import{ArrowLeft as _,GitCompare as M,History as E,Settings as L,ExternalLink as P,RefreshCw as j}from"lucide-vue-next";import{RuntimeBox as I}from"@hy-bricks/core";import*as B from"monaco-editor";import{typescript as K}from"monaco-editor";import O from"monaco-editor/esm/vs/editor/editor.worker?worker";import V from"monaco-editor/esm/vs/language/json/json.worker?worker";import H from"monaco-editor/esm/vs/language/css/css.worker?worker";import $ from"monaco-editor/esm/vs/language/html/html.worker?worker";import A from"monaco-editor/esm/vs/language/typescript/ts.worker?worker";import{Primitive as R}from"reka-ui";import{clsx as G}from"clsx";import{twMerge as N}from"tailwind-merge";import{cva as J}from"class-variance-authority";const U="\n// HyperCard 运行时全局类型 — 由 @hy-bricks/editor 注入到 Monaco\n\ndeclare const __HYPERCARD__: HyperCardInstance | undefined\n\ninterface HyperCardInstance {\n /** 宿主通过 createHyperCard({ libs: { ... } }) 注入的库 / 函数 / 常量 */\n libs: HyperCardLibs\n /** 跨实例运行时 SDK */\n runtime: HyperCardRuntime\n /** 资源 / 短链辅助 */\n assets: HyperCardAssets\n /** SDK 实例版本 */\n version: string\n}\n\n/**\n * 默认 libs 是开放命名空间。宿主补具体类型(axios / EP 等)时,在自己的 d.ts\n * extraLib 里 `interface HyperCardLibs { http: ...; ui: ... }` 声明合并。\n */\ninterface HyperCardLibs {\n [key: string]: any\n}\n\ninterface HyperCardRuntime {\n /** 调另一个组件实例的非钩子方法,同步;实例不存在返 undefined */\n call<T = unknown>(id: string, method: string, ...args: unknown[]): T | undefined\n /** 监听另一个实例的事件,返回 unsubscribe 函数;实例不存在返 undefined */\n on(id: string, event: string, handler: (payload: unknown) => void): (() => void) | undefined\n /** 在另一个实例上发事件 */\n emit(id: string, event: string, payload?: unknown): void\n /** 列出当前页面所有实例,可按 componentId 过滤 */\n listInstances(filter?: { componentId?: string }): HyperCardInstanceHandle[]\n /** 拿到指定 instanceId 的 handle */\n getInstance(id: string): HyperCardInstanceHandle | null\n}\n\ninterface HyperCardInstanceHandle {\n readonly instanceId: string\n readonly componentId: string\n call<T = unknown>(method: string, ...args: unknown[]): T | undefined\n on(event: string, handler: (payload: unknown) => void): () => void\n emit(event: string, payload?: unknown): void\n}\n\ninterface HyperCardAssets {\n /** 拼短链:'/a/<id>' */\n pickerUrl(id: string): string\n}\n",Y="file:///hypercard-globals.d.ts";let W=!1;function F(){var e,t;if(W)return;W=!0,K.javascriptDefaults.addExtraLib(U,Y),K.typescriptDefaults.addExtraLib(U,Y),"undefined"!=typeof globalThis&&globalThis.__HYPERCARD_DEBUG__&&console.info("[hypercard][monaco-types] HyperCard globals 注入完成,extra libs =",Object.keys((null==(t=(e=K.javascriptDefaults).getExtraLibs)?void 0:t.call(e))??{}));const n={target:K.ScriptTarget.Latest,allowNonTsExtensions:!0,moduleResolution:K.ModuleResolutionKind.NodeJs,module:K.ModuleKind.ESNext,noEmit:!0,esModuleInterop:!0,allowJs:!0,checkJs:!1};K.javascriptDefaults.setCompilerOptions(n),K.typescriptDefaults.setCompilerOptions(n)}const X=new Map;function q(){const e=[{filePath:Y,content:U},...Array.from(X.entries()).map(([e,{content:t}])=>({filePath:e,content:t}))];K.javascriptDefaults.setExtraLibs(e),K.typescriptDefaults.setExtraLibs(e)}function Q(e){const t=X.get(e.filePath);if(t)return t.count++,void(t.content!==e.content&&console.warn(`[hypercard] extraLib "${e.filePath}" 多次注册但 content 不一致;保留先注册的版本。如需独立内容,请用不同 filePath。`));X.set(e.filePath,{content:e.content,count:1}),q()}function Z(e){const t=X.get(e);t&&(t.count--,t.count<=0&&(X.delete(e),q()))}function ee(e){Q(e)}function te(e){Z(e)}function ne(e,t){return Boolean(null==e?void 0:e.showHistory)&&!0===(null==t?void 0:t.hasHistory)}function oe(e,t){return Boolean(null==e?void 0:e.showDiff)&&!0===(null==t?void 0:t.hasHistory)&&Boolean(null==t?void 0:t.currentVersionLabel)}function re(e){return!1!==(null==e?void 0:e.enablePublish)}function ae(e,t=new Date){if(!e)return null;const n=Date.parse(e);if(Number.isNaN(n))return null;const o=t.getTime()-n;return o<6e4?"刚刚":o<36e5?`${Math.floor(o/6e4)} 分钟前`:o<864e5?`${Math.floor(o/36e5)} 小时前`:o<2592e6?`${Math.floor(o/864e5)} 天前`:new Date(n).toLocaleDateString()}const ie=["html","javascript","css"];function se(){let e=null,t=[];function n(){if(e)return e;e={html:B.editor.createModel("","html"),javascript:B.editor.createModel("","javascript"),css:B.editor.createModel("","css")};for(const n of ie)e[n].onDidChangeContent(()=>{for(const e of t)e(n)});return e}return{useModels:n,getSource:e=>n()[e].getValue(),setSource(e,t){const o=n()[e];o.getValue()!==t&&o.setValue(t)},snapshotSources(){const e=n();return{html:e.html.getValue(),javascript:e.javascript.getValue(),css:e.css.getValue()}},onSourceChange:e=>(n(),t.push(e),()=>{t=t.filter(t=>t!==e)}),dispose(){if(e){for(const t of ie)e[t].dispose();e=null,t=[]}}}}const le=Symbol("hc-editor-scope");function ue(){const t=e(le);if(!t)throw new Error("[hypercard] useEditorScope 必须在 <HyperCardEditor> 内使用。组件作者:不要直接挂 MonacoEditor / EditorGroup 等内部组件。");return t}const ce={html:"index.html",javascript:"component.js",css:"styles.css"},de=t({__name:"MonacoEditor",props:{sourceKey:{},readOnly:{type:Boolean,default:!1},theme:{default:"vs"},fontSize:{default:13},minimap:{type:Boolean,default:!1}},emits:["save","pushVersion"],setup(e,{emit:t}){globalThis.__HC_MONACO_BOOTSTRAPPED__||(self.MonacoEnvironment={getWorker:(e,t)=>"json"===t?new V:"css"===t||"scss"===t||"less"===t?new H:"html"===t||"handlebars"===t||"razor"===t?new $:"typescript"===t||"javascript"===t?new A:new O},globalThis.__HC_MONACO_BOOTSTRAPPED__=!0),F();const u=e,c=t,d=n(null),p=o(null),v=ue();return r(()=>{if(!d.value)return;const e=v.useModels(),t=B.editor.create(d.value,{model:e[u.sourceKey],readOnly:u.readOnly,theme:u.theme,fontSize:u.fontSize,fontFamily:"JetBrains Mono, SF Mono, Menlo, Consolas, Liberation Mono, monospace",lineNumbers:"on",minimap:{enabled:u.minimap},automaticLayout:!0,tabSize:2,insertSpaces:!0,scrollBeyondLastLine:!1,smoothScrolling:!0,renderWhitespace:"selection",wordWrap:"on",padding:{top:8,bottom:8}});p.value=t,t.addCommand(B.KeyMod.CtrlCmd|B.KeyCode.KeyS,()=>c("save")),t.addCommand(B.KeyMod.CtrlCmd|B.KeyMod.Shift|B.KeyCode.KeyS,()=>c("pushVersion"))}),a(()=>{var e;null==(e=p.value)||e.dispose(),p.value=null}),i(()=>u.sourceKey,e=>{const t=p.value;if(!t)return;const n=v.useModels();t.setModel(n[e])}),i(()=>u.readOnly,e=>{var t;return null==(t=p.value)?void 0:t.updateOptions({readOnly:e})}),(e,t)=>(s(),l("div",{ref_key:"containerRef",ref:d,class:"w-full h-full"},null,512))}}),pe={class:"hc-editor-tabs h-8 border-b border-zinc-200 flex items-end text-xs shrink-0 overflow-x-auto overflow-y-hidden"},ve=["onClick","onDragstart","onDragover","onDrop"],fe={key:0,class:"hc-editor-insert-line absolute z-30 left-0 top-0 bottom-0 w-0.5 bg-blue-500"},he={class:"font-mono text-[11px] leading-none truncate"},me={key:0,class:"hc-editor-insert-line absolute z-30 left-0 top-0 bottom-0 w-0.5 bg-blue-500"},ge={key:1,class:"absolute inset-0 grid place-items-center text-xs text-zinc-400"},be=t({__name:"EditorGroup",props:{group:{},isOnly:{type:Boolean}},emits:["activate","tab-drop","save","pushVersion"],setup(e,{emit:t}){const o=e,r=t,a=n(null),i=n(null),b=n(null);function y(e){var t;return Array.from((null==(t=e.dataTransfer)?void 0:t.types)??[]).includes("application/x-hc-tab")}function w(e){e.dataTransfer&&(e.dataTransfer.dropEffect=e.altKey||e.metaKey?"copy":"move")}function x(){a.value=null,i.value=null,b.value=null}function k(e){var t;const n=null==(t=e.dataTransfer)?void 0:t.getData("application/x-hc-tab");if(!n)return null;try{const e=JSON.parse(n);return"string"==typeof e.sourceGroupId&&"string"==typeof e.sourceTab?e:null}catch{return null}}function S(e){if(!y(e))return;e.preventDefault(),e.stopPropagation(),w(e);const t=e.currentTarget;a.value=function(e,t){const n=t.getBoundingClientRect(),o=(e.clientX-n.left)/n.width,r=(e.clientY-n.top)/n.height;return r<.22?"top":r>.78?"bottom":o<.5?"left":"right"}(e,t),i.value=null}function z(e){const t=e.currentTarget,n=e.relatedTarget;n&&t.contains(n)||x()}function C(e){if(!y(e))return;e.preventDefault(),e.stopPropagation();const t=k(e),n=a.value;x(),t&&n&&"center"!==n&&r("tab-drop",{sourceGroupId:t.sourceGroupId,sourceTab:t.sourceTab,targetGroupId:o.group.id,edge:n,insertIndex:null,copy:e.altKey||e.metaKey})}function T(e,t){y(e)&&(e.preventDefault(),w(e),a.value="center",i.value=t,e.stopPropagation())}function D(e,t){if(!y(e))return;e.preventDefault(),e.stopPropagation();const n=k(e);x(),n&&r("tab-drop",{sourceGroupId:n.sourceGroupId,sourceTab:n.sourceTab,targetGroupId:o.group.id,edge:"center",insertIndex:t,copy:e.altKey||e.metaKey})}const _=u(()=>{switch(a.value){case"left":return"hc-editor-drop-overlay--left";case"right":return"hc-editor-drop-overlay--right";case"top":return"hc-editor-drop-overlay--top";case"bottom":return"hc-editor-drop-overlay--bottom";default:return""}});return(t,n)=>(s(),l("div",{class:"hc-editor-group relative w-full h-full flex flex-col bg-white border border-zinc-200 overflow-hidden",onDragleave:z,onDragendCapture:x},[c("div",pe,[(s(!0),l(d,null,p(e.group.tabs,(t,n)=>(s(),l("div",{key:t+"-"+n,draggable:!0,class:v(["hc-editor-tab h-7 min-w-0 px-3 flex items-center gap-1.5 border-r border-zinc-200 transition-colors cursor-pointer select-none relative",[e.group.activeTab===t?"hc-editor-tab--active bg-white text-zinc-950 border-t-2 border-t-zinc-900 border-b border-b-white":"text-zinc-500 hover:text-zinc-900 hover:bg-zinc-50",b.value===t?"hc-editor-tab--dragging opacity-45":""]]),onClick:e=>r("activate",t),onDragstart:e=>function(e,t){e.dataTransfer&&(b.value=t,e.dataTransfer.effectAllowed="copyMove",e.dataTransfer.setData("application/x-hc-tab",JSON.stringify({sourceGroupId:o.group.id,sourceTab:t})),e.dataTransfer.setData("text/plain",ce[t]))}(e,t),onDragover:e=>T(e,n),onDrop:e=>D(e,n)},["center"===a.value&&i.value===n?(s(),l("span",fe)):f("",!0),c("span",he,h(m(ce)[t]),1)],42,ve))),128)),c("div",{class:"hc-editor-tab-tail flex-1 relative min-w-6",onDragover:n[0]||(n[0]=t=>T(t,e.group.tabs.length)),onDrop:n[1]||(n[1]=t=>D(t,e.group.tabs.length))},["center"===a.value&&i.value===e.group.tabs.length?(s(),l("span",me)):f("",!0)],32)]),c("div",{class:"flex-1 min-h-0 relative",onDragoverCapture:S,onDropCapture:C},[e.group.tabs.length>0?(s(),g(de,{key:0,"source-key":e.group.activeTab,onSave:n[2]||(n[2]=e=>r("save")),onPushVersion:n[3]||(n[3]=e=>r("pushVersion"))},null,8,["source-key"])):(s(),l("div",ge," 把 tab 拖到这里打开 ")),a.value?(s(),l("div",{key:2,class:v(["hc-editor-drop-overlay pointer-events-none absolute z-50 bg-blue-500/15 border-2 border-blue-500 ring-2 ring-blue-500/20 transition-all",_.value])},null,2)):f("",!0)],32)],32))}}),ye=["aria-orientation"],we=t({__name:"Splitter",props:{direction:{default:"horizontal"},ratio:{},handleSize:{default:6}},emits:["update:ratio"],setup(e,{emit:t}){const o=e,r=t,i=n(null),d=n(!1);let p="",f="";const h=u(()=>"horizontal"===o.direction),m=u(()=>(h.value,{flexBasis:`calc(${100*o.ratio}% - ${o.handleSize/2}px)`})),g=u(()=>(h.value,{flexBasis:`calc(${100*(1-o.ratio)}% - ${o.handleSize/2}px)`})),w=u(()=>h.value?{flexBasis:`${o.handleSize}px`,width:`${o.handleSize}px`}:{flexBasis:`${o.handleSize}px`,height:`${o.handleSize}px`});function x(e){if(!i.value)return;const t=i.value.getBoundingClientRect(),n=h.value?(e.clientX-t.left)/t.width:(e.clientY-t.top)/t.height;r("update:ratio",Math.max(.08,Math.min(.92,n)))}function k(e){e.preventDefault(),e.stopPropagation(),d.value=!0,x(e);const t=e.currentTarget;try{t.setPointerCapture(e.pointerId)}catch{}p=document.body.style.cursor,f=document.body.style.userSelect,document.body.style.cursor=h.value?"col-resize":"row-resize",document.body.style.userSelect="none",window.addEventListener("pointermove",S),window.addEventListener("pointerup",T,{once:!0}),window.addEventListener("pointercancel",T,{once:!0})}function S(e){d.value&&(e.preventDefault(),x(e))}function z(){d.value&&(d.value=!1,document.body.style.cursor=p,document.body.style.userSelect=f,window.removeEventListener("pointermove",S),window.removeEventListener("pointerup",T),window.removeEventListener("pointercancel",T))}function C(e){const t=e.currentTarget;try{t.hasPointerCapture(e.pointerId)&&t.releasePointerCapture(e.pointerId)}catch{}z()}function T(){z()}return a(()=>{z()}),(e,t)=>(s(),l("div",{ref_key:"containerRef",ref:i,class:v(["hc-editor-splitter relative w-full h-full min-w-0 min-h-0",h.value?"hc-editor-splitter--horizontal flex flex-row":"hc-editor-splitter--vertical flex flex-col"])},[c("div",{class:"hc-editor-splitter-pane min-w-0 min-h-0 overflow-hidden",style:b(m.value)},[y(e.$slots,"a")],4),c("div",{class:v(["hc-editor-splitter-handle shrink-0 select-none",[h.value?"hc-editor-splitter-handle--horizontal":"hc-editor-splitter-handle--vertical",d.value?"hc-editor-splitter-handle--dragging":""]]),style:b(w.value),role:"separator","aria-orientation":h.value?"vertical":"horizontal",onPointerdown:k,onPointerup:C},null,46,ye),c("div",{class:"hc-editor-splitter-pane min-w-0 min-h-0 overflow-hidden",style:b(g.value)},[y(e.$slots,"b")],4)],2))}}),xe=t({__name:"EditorLayout",props:{node:{},isOnly:{type:Boolean}},emits:["activate","tab-drop","set-ratio","save","pushVersion"],setup(e,{emit:t}){const n=t;return(t,o)=>{const r=w("EditorLayout",!0);return"group"===e.node.type?(s(),g(be,{key:0,group:e.node,"is-only":e.isOnly??!1,onActivate:o[0]||(o[0]=t=>n("activate",e.node.id,t)),onTabDrop:o[1]||(o[1]=e=>n("tab-drop",e)),onSave:o[2]||(o[2]=e=>n("save")),onPushVersion:o[3]||(o[3]=e=>n("pushVersion"))},null,8,["group","is-only"])):(s(),g(we,{key:1,direction:e.node.direction,ratio:e.node.ratio,"onUpdate:ratio":o[14]||(o[14]=t=>n("set-ratio",e.node.id,t))},{a:x(()=>[k(r,{node:e.node.a,onActivate:o[4]||(o[4]=(e,t)=>n("activate",e,t)),onTabDrop:o[5]||(o[5]=e=>n("tab-drop",e)),onSetRatio:o[6]||(o[6]=(e,t)=>n("set-ratio",e,t)),onSave:o[7]||(o[7]=e=>n("save")),onPushVersion:o[8]||(o[8]=e=>n("pushVersion"))},null,8,["node"])]),b:x(()=>[k(r,{node:e.node.b,onActivate:o[9]||(o[9]=(e,t)=>n("activate",e,t)),onTabDrop:o[10]||(o[10]=e=>n("tab-drop",e)),onSetRatio:o[11]||(o[11]=(e,t)=>n("set-ratio",e,t)),onSave:o[12]||(o[12]=e=>n("save")),onPushVersion:o[13]||(o[13]=e=>n("pushVersion"))},null,8,["node"])]),_:1},8,["direction","ratio"]))}}});let ke=0;function Se(e){return`${e}_${++ke}_${Date.now().toString(36)}`}function ze(e,t){return{type:"group",id:Se("g"),tabs:[...e],activeTab:t??e[0]}}function Ce(e){return JSON.parse(JSON.stringify(e))}function Te(e,t){return"group"===e.type?e.id===t?e:null:Te(e.a,t)??Te(e.b,t)}function De(e,t){return"group"===e.type?null:e.id===t?e:De(e.a,t)??De(e.b,t)}function _e(e,t,n){if("group"===e.type)return e.id===t?n:e;if(e.id===t)return n;const o=_e(e.a,t,n),r=_e(e.b,t,n);return o||r?o?r?{...e,a:o,b:r}:o:r:null}function Me(e,t,n){const o=Ce(e),r=Te(o,t);return r&&r.tabs.includes(n)&&(r.activeTab=n),o}function Ee(e,t,n){const o=Ce(e),r=Te(o,t);if(!r)return o;const a=r.tabs.indexOf(n);return-1===a?o:(r.tabs.splice(a,1),0===r.tabs.length?"group"===o.type&&o.id===t?o:_e(o,t,null)??o:(r.activeTab===n&&(r.activeTab=r.tabs[Math.min(a,r.tabs.length-1)]),o))}function Le(e,t,n,o,r,a=!1){let i=Ce(e);const s=Te(i,o);if(!s)return i;if(t===o&&!a){const e=s.tabs.indexOf(n);if(-1===e)return i;s.tabs.splice(e,1);const t=null==r?s.tabs.length:Math.min(r,s.tabs.length);return s.tabs.splice(t,0,n),s.activeTab=n,i}if(!s.tabs.includes(n)){const e=null==r?s.tabs.length:Math.min(r,s.tabs.length);s.tabs.splice(e,0,n)}return s.activeTab=n,a||(i=Ee(i,t,n)),i}function Pe(e,t,n,o,r,a=!1){let i=Ce(e);const s=Te(i,o);if(!s)return i;if(t===o&&1===s.tabs.length&&s.tabs[0]===n&&!a)return i;const l=ze([n],n),u="left"===r||"right"===r?"horizontal":"vertical",c={type:"split",id:Se("s"),direction:u,ratio:.5,a:"right"===r||"bottom"===r?Ce(s):l,b:"right"===r||"bottom"===r?l:Ce(s)},d=_e(i,s.id,c);return d?(i=d,a||(i=Ee(i,t,n)),i):i}function je(e,t,n){const o=Ce(e),r=De(o,t);return r&&(r.ratio=Math.max(.05,Math.min(.95,n))),o}function Ie(){return ze(["html","javascript","css"],"javascript")}function Be(e){return"hc:draft:"+e}function Ke(e){if(!e)return null;const t=localStorage.getItem(Be(e));if(!t)return null;try{const e=JSON.parse(t);if("string"==typeof e.html&&"string"==typeof e.javascript&&"string"==typeof e.css&&"number"==typeof e.savedAt)return e}catch{}return null}function Oe(e,t){if(!e)return null;const n={html:t.html,javascript:t.javascript,css:t.css,savedAt:Date.now()};try{return localStorage.setItem(Be(e),JSON.stringify(n)),n}catch(e){return console.warn("[hc-draft] saveDraft failed:",e),null}}function Ve(e){e&&localStorage.removeItem(Be(e))}function He(e){const t=Date.now()-e,n=Math.floor(t/6e4);if(n<1)return"刚刚";if(n<60)return`${n} 分钟前`;const o=Math.floor(n/60);return o<24?`${o} 小时前`:`${Math.floor(o/24)} 天前`}function $e(e){const t=new BroadcastChannel(`hc-preview-${e}`);return{channel:t,send(e){t.postMessage(e)},close(){t.close()}}}function Ae(...e){return N(G(e))}const Re=J("inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",{variants:{variant:{default:"bg-primary text-primary-foreground shadow-xs hover:bg-primary/90",destructive:"bg-destructive text-white shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",outline:"border border-input bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50",secondary:"bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80",ghost:"hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",link:"text-primary underline-offset-4 hover:underline"},size:{default:"h-9 px-4 py-2 has-[>svg]:px-3",xs:"h-7 rounded-md px-2 text-xs has-[>svg]:px-1.5",sm:"h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5",lg:"h-10 rounded-md px-6 has-[>svg]:px-4",icon:"size-9","icon-sm":"size-8"}},defaultVariants:{variant:"default",size:"default"}}),Ge=t({__name:"Button",props:{variant:{},size:{},class:{type:[Boolean,null,String,Object,Array]},asChild:{type:Boolean},as:{default:"button"}},setup(e){const t=e;return(n,o)=>(s(),g(m(R),{"data-slot":"button",as:e.as,"as-child":e.asChild,class:v(m(Ae)(m(Re)({variant:e.variant,size:e.size}),t.class))},{default:x(()=>[y(n.$slots,"default")]),_:3},8,["as","as-child","class"]))}}),Ne={class:"h-full w-full flex flex-col bg-zinc-50 text-zinc-900"},Je={class:"h-11 border-b border-zinc-200 bg-white flex items-center px-4 gap-3 shrink-0"},Ue={key:1,class:"h-4 w-px bg-zinc-200"},Ye={class:"flex items-baseline gap-2 min-w-0"},We={key:0,class:"text-sm font-medium truncate"},Fe={key:1,class:"text-[11px] text-zinc-400 tabular-nums"},Xe=["title"],qe={key:3,class:"text-[11px] text-amber-600"},Qe={key:2,class:"hc-editor-draft-pill"},Ze={class:"hc-editor-draft-pill-text"},et={class:"ml-auto flex items-center gap-1.5"},tt={class:"flex items-center gap-1.5 text-[11px] text-zinc-500 select-none cursor-pointer"},nt={class:"flex-1 min-h-0 p-2 bg-zinc-50"},ot={class:"w-full h-full"},rt={class:"hc-editor-preview-pane w-full h-full bg-white border border-zinc-200 flex flex-col overflow-hidden"},at={class:"hc-editor-preview-header"},it={class:"hc-editor-preview-tab"},st={class:"hc-editor-preview-actions"},lt={class:"flex-1 min-h-0 overflow-auto bg-white"},ut={class:"hc-editor-preview-body min-h-full px-5 py-4"},ct={key:1,class:"w-full h-full"},dt=t({__name:"HyperCardEditor",props:{modelValue:{},draftKey:{},componentId:{},showBackButton:{type:Boolean,default:!1},title:{},statusLabel:{},versionStatus:{},features:{},autoPreview:{type:Boolean,default:!0},previewRatio:{default:.62},extraLibs:{}},emits:["update:modelValue","dirty","save-draft","push-version","open-preview-window","open-settings","open-history","open-diff","go-back","error"],setup(e,{expose:t,emit:o}){var d;const p=e,v=o,b=se();S(le,b);const y=n(!1),w=n(Ie()),B=n(p.previewRatio),K=n(!1),O=n(p.autoPreview),V=n(""),H=n(""),$=n(""),A=n(null),R=(null==(d=z())?void 0:d.uid)??Math.floor(1e9*Math.random()),G=u(()=>p.componentId??p.draftKey??`__hc_editor_${R}__`),N=u(()=>`editor-preview-${G.value}`),J=u(()=>{var e;return(null==(e=p.versionStatus)?void 0:e.currentVersionLabel)??p.statusLabel??""}),U=u(()=>{var e;return ae(null==(e=p.versionStatus)?void 0:e.lastPublishedAt)}),Y=u(()=>ne(p.features,p.versionStatus)),W=u(()=>oe(p.features,p.versionStatus)),F=u(()=>re(p.features));let X=!1,q=null,ee=null;function te(){const e=b.snapshotSources();v("update:modelValue",{html:e.html,js:e.javascript,css:e.css})}function ie(e,t,n){X=!0;try{b.setSource("html",e),b.setSource("javascript",t),b.setSource("css",n)}finally{X=!1}}i(()=>p.modelValue,e=>{if(!e)return;const t=b.snapshotSources();t.html===(e.html??"")&&t.javascript===(e.js??"")&&t.css===(e.css??"")||(ie(e.html??"",e.js??"",e.css??""),ce(),fe(),y.value=!1,v("dirty",!1))},{immediate:!0});let ue=null;function ce(){const e=b.snapshotSources();V.value=e.html,H.value=e.javascript,$.value=e.css}function de(){ue&&clearTimeout(ue),ce()}function pe(){p.draftKey&&!q&&(q=$e(p.draftKey),q.channel.addEventListener("message",e=>{const t=e.data;"hello"===t.type?(K.value=!0,ve()):"goodbye"===t.type?K.value=!1:"request-sources"===t.type&&ve()}))}function ve(){if(!q)return;const e=b.snapshotSources();q.send({type:"sources",html:e.html,javascript:e.javascript,css:e.css})}function fe(){q&&(ee&&clearTimeout(ee),ee=setTimeout(ve,200))}function he(){p.draftKey?(Oe(p.draftKey,b.snapshotSources())&&(y.value=!1,v("dirty",!1),A.value=null),v("save-draft")):v("save-draft")}function me(){const e=A.value;e&&(ie(e.html,e.javascript,e.css),ce(),te(),fe(),A.value=null,y.value=!0,v("dirty",!0))}function ge(){p.draftKey&&(Ve(p.draftKey),A.value=null)}function be(e,t){w.value=Me(w.value,e,t)}function ye(e){"center"===e.edge?w.value=Le(w.value,e.sourceGroupId,e.sourceTab,e.targetGroupId,e.insertIndex,e.copy):w.value=Pe(w.value,e.sourceGroupId,e.sourceTab,e.targetGroupId,e.edge,e.copy)}function ke(e,t){w.value=je(w.value,e,t)}function Se(e){y.value&&(e.preventDefault(),e.returnValue="")}let ze=null;const Ce=[];function Te(){pe(),v("open-preview-window")}function De(e){const t=Date.now()-e;return t<6e4?"刚刚":t<36e5?`${Math.floor(t/6e4)} 分钟前`:t<864e5?`${Math.floor(t/36e5)} 小时前`:new Date(e).toLocaleString()}return r(()=>{if(p.extraLibs)for(const e of p.extraLibs)Q(e),Ce.push(e.filePath);ze=b.onSourceChange(()=>{X||(y.value=!0,v("dirty",!0),te(),fe(),O.value&&(ue&&clearTimeout(ue),ue=setTimeout(ce,600)))}),p.draftKey&&(A.value=Ke(p.draftKey),pe()),window.addEventListener("beforeunload",Se)}),a(()=>{null==ze||ze(),ze=null,ee&&clearTimeout(ee),ue&&clearTimeout(ue),null==q||q.close(),q=null,window.removeEventListener("beforeunload",Se);for(const e of Ce)Z(e);Ce.length=0,b.dispose()}),t({isDirty:y,confirmLeave:function(){return!y.value||window.confirm("当前修改未保存,确定离开?未保存内容将丢失。")},applyDraft:function(e){ie(e.html,e.javascript,e.css),ce(),te(),fe(),y.value=!0,v("dirty",!0)},manualPreview:de,applyPreview:ce}),(t,n)=>{var o;return s(),l("div",Ne,[c("header",Je,[e.showBackButton?(s(),g(m(Ge),{key:0,variant:"ghost",size:"sm",onClick:n[0]||(n[0]=e=>v("go-back"))},{default:x(()=>[k(m(_),{size:14}),n[9]||(n[9]=C(" 返回 ",-1))]),_:1})):f("",!0),e.showBackButton?(s(),l("span",Ue)):f("",!0),c("div",Ye,[e.title?(s(),l("h1",We,h(e.title),1)):f("",!0),J.value?(s(),l("span",Fe,h(J.value),1)):f("",!0),U.value?(s(),l("span",{key:2,class:"text-[11px] text-zinc-400",title:null==(o=e.versionStatus)?void 0:o.lastPublishedAt}," · 上次发布 "+h(U.value),9,Xe)):f("",!0),y.value?(s(),l("span",qe,"●未保存")):f("",!0)]),A.value?(s(),l("div",Qe,[n[10]||(n[10]=c("span",{class:"hc-editor-draft-pill-dot"},null,-1)),c("span",Ze," 本地草稿 · "+h(De(A.value.savedAt))+"保存 ",1),c("button",{type:"button",class:"hc-editor-draft-pill-action hc-editor-draft-pill-action--primary",onClick:me}," 恢复 "),c("button",{type:"button",class:"hc-editor-draft-pill-action",onClick:ge}," 丢弃 ")])):f("",!0),c("div",et,[c("label",tt,[T(c("input",{"onUpdate:modelValue":n[1]||(n[1]=e=>O.value=e),type:"checkbox",class:"h-3 w-3 rounded border-zinc-300 text-zinc-900 focus:ring-0"},null,512),[[D,O.value]]),n[11]||(n[11]=C(" 自动预览 ",-1))]),W.value?(s(),g(m(Ge),{key:0,variant:"ghost",size:"xs",title:"查看当前改动",onClick:n[2]||(n[2]=e=>v("open-diff"))},{default:x(()=>[k(m(M),{size:13}),n[12]||(n[12]=C(" 看改动 ",-1))]),_:1})):f("",!0),Y.value?(s(),g(m(Ge),{key:1,variant:"ghost",size:"xs",title:"历史版本",onClick:n[3]||(n[3]=e=>v("open-history"))},{default:x(()=>[k(m(E),{size:13}),n[13]||(n[13]=C(" 历史版本 ",-1))]),_:1})):f("",!0),k(m(Ge),{variant:"ghost",size:"icon-sm",title:"组件设置",onClick:n[4]||(n[4]=e=>v("open-settings"))},{default:x(()=>[k(m(L),{size:14})]),_:1}),k(m(Ge),{variant:"outline",size:"xs",onClick:he},{default:x(()=>[...n[14]||(n[14]=[C(" 保存草稿 ",-1)])]),_:1}),F.value?(s(),g(m(Ge),{key:2,variant:"default",size:"xs",onClick:n[5]||(n[5]=e=>v("push-version"))},{default:x(()=>[...n[15]||(n[15]=[C(" 推版本 ",-1)])]),_:1})):f("",!0)])]),c("div",nt,[K.value?(s(),l("div",ct,[k(xe,{node:w.value,"is-only":"group"===w.value.type,onActivate:be,onTabDrop:ye,onSetRatio:ke,onSave:he,onPushVersion:n[8]||(n[8]=e=>v("push-version"))},null,8,["node","is-only"]),n[18]||(n[18]=c("div",{class:"fixed bottom-3 right-3 bg-zinc-900 text-white text-[11px] px-3 py-1.5 rounded-md shadow-md flex items-center gap-2"},[c("span",{class:"h-1.5 w-1.5 rounded-full bg-emerald-400"}),C(" 预览已脱出 · 关闭独立窗口可恢复 ")],-1))])):(s(),g(we,{key:0,direction:"horizontal",ratio:B.value,"onUpdate:ratio":n[7]||(n[7]=e=>B.value=e)},{a:x(()=>[c("div",ot,[k(xe,{node:w.value,"is-only":"group"===w.value.type,onActivate:be,onTabDrop:ye,onSetRatio:ke,onSave:he,onPushVersion:n[6]||(n[6]=e=>v("push-version"))},null,8,["node","is-only"])])]),b:x(()=>[c("div",rt,[c("div",at,[c("div",it,[n[16]||(n[16]=c("span",{class:"hc-editor-preview-title"},"预览",-1)),c("button",{type:"button",class:"hc-editor-icon-button hc-editor-preview-icon-button",title:"独立窗口预览",onClick:Te},[k(m(P),{size:13})])]),c("div",st,[O.value?f("",!0):(s(),l("button",{key:0,type:"button",class:"hc-editor-preview-refresh",onClick:de},[k(m(j),{size:12}),n[17]||(n[17]=C(" 重新渲染 ",-1))]))])]),c("div",lt,[c("div",ut,[k(m(I),{"instance-id":N.value,"component-id":`user-${G.value}`,"html-source":V.value,"js-source":H.value,"css-source":$.value},null,8,["instance-id","component-id","html-source","js-source","css-source"])])])])]),_:1},8,["ratio"]))])])}}}),pt="0.0.0-stage3";export{Ge as Button,le as EDITOR_SCOPE_KEY,be as EditorGroup,xe as EditorLayout,dt as HyperCardEditor,de as MonacoEditor,ce as SOURCE_KEY_LABEL,we as Splitter,pt as VERSION,Me as activateTab,ee as addMonacoExtraLib,Re as buttonVariants,Ve as clearDraft,Ee as closeTab,se as createEditorScope,$e as createPreviewBus,Ie as defaultLayout,ae as formatLastPublished,He as formatRelative,Ke as loadDraft,Le as moveTab,Z as releaseMonacoExtraLib,te as removeMonacoExtraLib,Q as retainMonacoExtraLib,Oe as saveDraft,je as setSplitRatio,F as setupHyperCardMonacoTypes,oe as shouldShowDiffEntry,ne as shouldShowHistoryEntry,re as shouldShowPublishEntry,Pe as splitWithTab,ue as useEditorScope};
package/dist/style.css ADDED
@@ -0,0 +1 @@
1
+ :root{--background:0 0% 100%;--foreground:222.2 84% 4.9%;--card:0 0% 100%;--card-foreground:222.2 84% 4.9%;--popover:0 0% 100%;--popover-foreground:222.2 84% 4.9%;--primary:222.2 47.4% 11.2%;--primary-foreground:210 40% 98%;--secondary:210 40% 96.1%;--secondary-foreground:222.2 47.4% 11.2%;--muted:210 40% 96.1%;--muted-foreground:215.4 16.3% 46.9%;--accent:210 40% 96.1%;--accent-foreground:222.2 47.4% 11.2%;--destructive:0 84.2% 60.2%;--destructive-foreground:210 40% 98%;--border:214.3 31.8% 91.4%;--input:214.3 31.8% 91.4%;--ring:222.2 84% 4.9%;--radius:0.5rem}.dark{--background:222.2 84% 4.9%;--foreground:210 40% 98%;--card:222.2 84% 4.9%;--card-foreground:210 40% 98%;--popover:222.2 84% 4.9%;--popover-foreground:210 40% 98%;--primary:210 40% 98%;--primary-foreground:222.2 47.4% 11.2%;--secondary:217.2 32.6% 17.5%;--secondary-foreground:210 40% 98%;--muted:217.2 32.6% 17.5%;--muted-foreground:215 20.2% 65.1%;--accent:217.2 32.6% 17.5%;--accent-foreground:210 40% 98%;--destructive:0 62.8% 30.6%;--destructive-foreground:210 40% 98%;--border:217.2 32.6% 17.5%;--input:217.2 32.6% 17.5%;--ring:212.7 26.8% 83.9%}.hc-editor-group,.hc-editor-preview-pane{border-radius:2px}.hc-editor-preview-header,.hc-editor-tabs{background:hsla(240,5%,96%,.8)}.hc-editor-draft-pill{display:flex;align-items:center;gap:8px;max-width:min(460px,36vw);min-width:0;height:26px;margin-left:6px;padding:0 7px 0 9px;border:1px solid rgba(217,119,6,.22);border-radius:5px;background:rgba(255,251,235,.72);color:#78350f;font-size:12px;line-height:1}.hc-editor-draft-pill-dot{width:6px;height:6px;flex:0 0 auto;border-radius:999px;background:#f59e0b;box-shadow:0 0 0 2px rgba(245,158,11,.12)}.hc-editor-draft-pill-text{min-width:0;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.hc-editor-draft-pill-action{flex:0 0 auto;height:18px;padding:0 6px;border:1px solid transparent;border-radius:3px;background:transparent;color:#78350f;font-size:11px;line-height:16px;font-weight:500;cursor:pointer}.hc-editor-draft-pill-action:hover{border-color:rgba(217,119,6,.22);background:hsla(0,0%,100%,.72);color:#5c2504}.hc-editor-draft-pill-action--primary{border-color:rgba(120,53,15,.18);background:#78350f;color:#fff}.hc-editor-draft-pill-action--primary:hover{border-color:#5c2504;background:#5c2504;color:#fff}.hc-editor-tab{height:28px}.hc-editor-tab--active{background:#fff;border-top-color:#18181b;border-bottom-color:#fff;color:#09090b}.hc-editor-tab--dragging{opacity:.45}.hc-editor-insert-line{width:2px;background:#3b82f6;box-shadow:0 0 0 1px rgba(59,130,246,.25)}.hc-editor-drop-overlay{z-index:50;background:rgba(59,130,246,.15);border:2px solid #3b82f6;box-shadow:0 0 0 2px rgba(59,130,246,.2)}.hc-editor-drop-overlay--left{inset:0 auto 0 0;width:50%}.hc-editor-drop-overlay--right{inset:0 0 0 auto;width:50%}.hc-editor-drop-overlay--top{inset:0 0 auto 0;height:50%}.hc-editor-drop-overlay--bottom{inset:auto 0 0 0;height:50%}.hc-editor-icon-button{border-radius:2px}.hc-editor-icon-button:hover{background:#e4e4e7}.hc-editor-splitter-pane{flex:0 0 auto}.hc-editor-splitter-handle{position:relative;display:block;flex:0 0 auto;touch-action:none;outline:none;background:#e4e4e7;transition:background-color .12s ease,box-shadow .12s ease}.hc-editor-splitter-handle--horizontal{cursor:col-resize}.hc-editor-splitter-handle--vertical{cursor:row-resize}.hc-editor-splitter-handle:after{content:"";position:absolute;border-radius:999px;background:transparent;transition:background-color .12s ease,transform .12s ease}.hc-editor-splitter-handle--horizontal:after{top:50%;left:50%;width:3px;height:28px;transform:translate(-50%,-50%)}.hc-editor-splitter-handle--vertical:after{top:50%;left:50%;width:28px;height:3px;transform:translate(-50%,-50%)}.hc-editor-splitter-handle--dragging,.hc-editor-splitter-handle:hover{background:#a1a1aa;box-shadow:inset 0 0 0 1px hsla(240,4%,46%,.08)}.hc-editor-splitter-handle--dragging:after,.hc-editor-splitter-handle:hover:after{background:#52525b}.hc-editor-splitter-handle--horizontal.hc-editor-splitter-handle--dragging:after,.hc-editor-splitter-handle--horizontal:hover:after{transform:translate(-50%,-50%) scaleX(1.15)}.hc-editor-splitter-handle--vertical.hc-editor-splitter-handle--dragging:after,.hc-editor-splitter-handle--vertical:hover:after{transform:translate(-50%,-50%) scaleY(1.15)}.hc-editor-preview-header{display:flex;align-items:flex-end;justify-content:space-between;height:32px;flex-shrink:0;border-bottom:1px solid #e4e4e7;color:#71717a;font-size:11px}.hc-editor-preview-tab{display:inline-flex;align-items:center;gap:7px;height:28px;padding:0 10px 0 12px;border-top:2px solid #18181b;border-right:1px solid #e4e4e7;border-bottom:1px solid #fff;background:#fff;color:#18181b}.hc-editor-preview-title{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;line-height:1}.hc-editor-preview-icon-button{display:inline-flex;align-items:center;justify-content:center;width:18px;height:18px;padding:0;border:0;background:transparent;color:#71717a;cursor:pointer}.hc-editor-preview-icon-button:hover{color:#18181b}.hc-editor-preview-actions{display:flex;align-items:center;height:100%;padding-right:8px}.hc-editor-preview-refresh{display:inline-flex;align-items:center;gap:5px;height:22px;padding:0 8px;border:1px solid transparent;border-radius:4px;background:transparent;color:#71717a;font-size:11px;cursor:pointer}.hc-editor-preview-refresh:hover{border-color:#e4e4e7;background:#fafafa;color:#18181b}.hc-editor-preview-body{padding:16px 20px}.pointer-events-none{pointer-events:none}.fixed{position:fixed}.absolute{position:absolute}.relative{position:relative}.inset-0{inset:0}.bottom-0{bottom:0}.bottom-3{bottom:.75rem}.left-0{left:0}.right-3{right:.75rem}.top-0{top:0}.z-30{z-index:30}.z-50{z-index:50}.ml-auto{margin-left:auto}.flex{display:flex}.inline-flex{display:inline-flex}.grid{display:grid}.size-4{width:1rem;height:1rem}.size-8{width:2rem;height:2rem}.size-9{width:2.25rem;height:2.25rem}.h-1\.5{height:.375rem}.h-10{height:2.5rem}.h-11{height:2.75rem}.h-3{height:.75rem}.h-4{height:1rem}.h-7{height:1.75rem}.h-8{height:2rem}.h-9{height:2.25rem}.h-full{height:100%}.min-h-0{min-height:0}.min-h-full{min-height:100%}.w-0\.5{width:.125rem}.w-1\.5{width:.375rem}.w-3{width:.75rem}.w-full{width:100%}.w-px{width:1px}.min-w-0{min-width:0}.min-w-6{min-width:1.5rem}.flex-1{flex:1 1 0%}.shrink-0{flex-shrink:0}.cursor-pointer{cursor:pointer}.select-none{-webkit-user-select:none;-moz-user-select:none;user-select:none}.flex-row{flex-direction:row}.flex-col{flex-direction:column}.place-items-center{place-items:center}.items-end{align-items:flex-end}.items-center{align-items:center}.items-baseline{align-items:baseline}.justify-center{justify-content:center}.gap-1\.5{gap:.375rem}.gap-2{gap:.5rem}.gap-3{gap:.75rem}.overflow-auto{overflow:auto}.overflow-hidden{overflow:hidden}.overflow-x-auto{overflow-x:auto}.overflow-y-hidden{overflow-y:hidden}.truncate{overflow:hidden;text-overflow:ellipsis}.truncate,.whitespace-nowrap{white-space:nowrap}.rounded{border-radius:.25rem}.rounded-full{border-radius:9999px}.rounded-md{border-radius:calc(var(--radius) - 2px)}.border{border-width:1px}.border-2{border-width:2px}.border-b{border-bottom-width:1px}.border-r{border-right-width:1px}.border-t-2{border-top-width:2px}.border-blue-500{--tw-border-opacity:1;border-color:rgb(59 130 246/var(--tw-border-opacity,1))}.border-input{border-color:hsl(var(--input))}.border-zinc-200{--tw-border-opacity:1;border-color:rgb(228 228 231/var(--tw-border-opacity,1))}.border-zinc-300{--tw-border-opacity:1;border-color:rgb(212 212 216/var(--tw-border-opacity,1))}.border-b-white{--tw-border-opacity:1;border-bottom-color:rgb(255 255 255/var(--tw-border-opacity,1))}.border-t-zinc-900{--tw-border-opacity:1;border-top-color:rgb(24 24 27/var(--tw-border-opacity,1))}.bg-background{background-color:hsl(var(--background))}.bg-blue-500{--tw-bg-opacity:1;background-color:rgb(59 130 246/var(--tw-bg-opacity,1))}.bg-blue-500\/15{background-color:rgba(59,130,246,.15)}.bg-destructive{background-color:hsl(var(--destructive))}.bg-emerald-400{--tw-bg-opacity:1;background-color:rgb(52 211 153/var(--tw-bg-opacity,1))}.bg-primary{background-color:hsl(var(--primary))}.bg-secondary{background-color:hsl(var(--secondary))}.bg-white{--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity,1))}.bg-zinc-200{--tw-bg-opacity:1;background-color:rgb(228 228 231/var(--tw-bg-opacity,1))}.bg-zinc-50{--tw-bg-opacity:1;background-color:rgb(250 250 250/var(--tw-bg-opacity,1))}.bg-zinc-900{--tw-bg-opacity:1;background-color:rgb(24 24 27/var(--tw-bg-opacity,1))}.p-2{padding:.5rem}.px-2{padding-left:.5rem;padding-right:.5rem}.px-3{padding-left:.75rem;padding-right:.75rem}.px-4{padding-left:1rem;padding-right:1rem}.px-5{padding-left:1.25rem;padding-right:1.25rem}.px-6{padding-left:1.5rem;padding-right:1.5rem}.py-1\.5{padding-top:.375rem;padding-bottom:.375rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.py-4{padding-top:1rem;padding-bottom:1rem}.font-mono{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}.text-\[11px\]{font-size:11px}.text-sm{font-size:.875rem;line-height:1.25rem}.text-xs{font-size:.75rem;line-height:1rem}.font-medium{font-weight:500}.tabular-nums{--tw-numeric-spacing:tabular-nums;font-variant-numeric:var(--tw-ordinal) var(--tw-slashed-zero) var(--tw-numeric-figure) var(--tw-numeric-spacing) var(--tw-numeric-fraction)}.leading-none{line-height:1}.text-amber-600{--tw-text-opacity:1;color:rgb(217 119 6/var(--tw-text-opacity,1))}.text-primary{color:hsl(var(--primary))}.text-primary-foreground{color:hsl(var(--primary-foreground))}.text-secondary-foreground{color:hsl(var(--secondary-foreground))}.text-white{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity,1))}.text-zinc-400{--tw-text-opacity:1;color:rgb(161 161 170/var(--tw-text-opacity,1))}.text-zinc-500{--tw-text-opacity:1;color:rgb(113 113 122/var(--tw-text-opacity,1))}.text-zinc-900{--tw-text-opacity:1;color:rgb(24 24 27/var(--tw-text-opacity,1))}.text-zinc-950{--tw-text-opacity:1;color:rgb(9 9 11/var(--tw-text-opacity,1))}.underline-offset-4{text-underline-offset:4px}.opacity-45{opacity:.45}.shadow-md{--tw-shadow:0 4px 6px -1px rgba(0,0,0,.1),0 2px 4px -2px rgba(0,0,0,.1);--tw-shadow-colored:0 4px 6px -1px var(--tw-shadow-color),0 2px 4px -2px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.outline-none{outline:2px solid transparent;outline-offset:2px}.outline{outline-style:solid}.ring-2{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000)}.ring-blue-500\/20{--tw-ring-color:rgba(59,130,246,.2)}.filter{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.transition-all{transition-property:all;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-colors{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}@keyframes enter{0%{opacity:var(--tw-enter-opacity,1);transform:translate3d(var(--tw-enter-translate-x,0),var(--tw-enter-translate-y,0),0) scale3d(var(--tw-enter-scale,1),var(--tw-enter-scale,1),var(--tw-enter-scale,1)) rotate(var(--tw-enter-rotate,0))}}@keyframes exit{to{opacity:var(--tw-exit-opacity,1);transform:translate3d(var(--tw-exit-translate-x,0),var(--tw-exit-translate-y,0),0) scale3d(var(--tw-exit-scale,1),var(--tw-exit-scale,1),var(--tw-exit-scale,1)) rotate(var(--tw-exit-rotate,0))}}.hover\:bg-accent:hover{background-color:hsl(var(--accent))}.hover\:bg-destructive\/90:hover{background-color:hsl(var(--destructive)/.9)}.hover\:bg-primary\/90:hover{background-color:hsl(var(--primary)/.9)}.hover\:bg-secondary\/80:hover{background-color:hsl(var(--secondary)/.8)}.hover\:bg-zinc-50:hover{--tw-bg-opacity:1;background-color:rgb(250 250 250/var(--tw-bg-opacity,1))}.hover\:text-accent-foreground:hover{color:hsl(var(--accent-foreground))}.hover\:text-zinc-900:hover{--tw-text-opacity:1;color:rgb(24 24 27/var(--tw-text-opacity,1))}.hover\:underline:hover{text-decoration-line:underline}.focus\:ring-0:focus{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000)}.focus-visible\:border-ring:focus-visible{border-color:hsl(var(--ring))}.focus-visible\:ring-\[3px\]:focus-visible{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(3px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000)}.focus-visible\:ring-destructive\/20:focus-visible{--tw-ring-color:hsl(var(--destructive)/0.2)}.focus-visible\:ring-ring\/50:focus-visible{--tw-ring-color:hsl(var(--ring)/0.5)}.disabled\:pointer-events-none:disabled{pointer-events:none}.disabled\:opacity-50:disabled{opacity:.5}.has-\[\>svg\]\:px-1\.5:has(>svg){padding-left:.375rem;padding-right:.375rem}.has-\[\>svg\]\:px-2\.5:has(>svg){padding-left:.625rem;padding-right:.625rem}.has-\[\>svg\]\:px-3:has(>svg){padding-left:.75rem;padding-right:.75rem}.has-\[\>svg\]\:px-4:has(>svg){padding-left:1rem;padding-right:1rem}.dark\:border-input:is(.dark *){border-color:hsl(var(--input))}.dark\:bg-destructive\/60:is(.dark *){background-color:hsl(var(--destructive)/.6)}.dark\:bg-input\/30:is(.dark *){background-color:hsl(var(--input)/.3)}.dark\:hover\:bg-accent\/50:hover:is(.dark *){background-color:hsl(var(--accent)/.5)}.dark\:hover\:bg-input\/50:hover:is(.dark *){background-color:hsl(var(--input)/.5)}.dark\:focus-visible\:ring-destructive\/40:focus-visible:is(.dark *){--tw-ring-color:hsl(var(--destructive)/0.4)}.\[\&_svg\:not\(\[class\*\=\'size-\'\]\)\]\:size-4 svg:not([class*=size-]){width:1rem;height:1rem}.\[\&_svg\]\:pointer-events-none svg{pointer-events:none}.\[\&_svg\]\:shrink-0 svg{flex-shrink:0}
@@ -0,0 +1,60 @@
1
+ /**
2
+ * @hy-bricks/editor 的 Tailwind preset — 让宿主"装 + extend 一行"接入。
3
+ *
4
+ * 用法(宿主项目 tailwind.config.js):
5
+ *
6
+ * import hyBricksPreset from '@hy-bricks/editor/tailwind-preset'
7
+ *
8
+ * export default {
9
+ * presets: [hyBricksPreset],
10
+ * content: ['./src/**\/*.{vue,ts,js,tsx,jsx}'], // 宿主自己的源码
11
+ * // theme / plugins 想再覆盖什么自己加
12
+ * }
13
+ *
14
+ * preset 自带:
15
+ * - content:SDK 包内部源码路径(node_modules/@hy-bricks/{editor,core}/...)
16
+ * - theme / plugins / darkMode:从 _shared-theme.js 复用,跟 SDK 自 build 的
17
+ * tailwind.config.js 同源,不再两份漂移
18
+ *
19
+ * 注意:
20
+ * - 宿主仍要自己提供 CSS variables(shadcn 主题色),建议 import @hy-bricks/editor/style.css
21
+ * - 实际上 SDK 已经 self-compile 出 dist/style.css 包含完整 utility,宿主只需
22
+ * `import '@hy-bricks/editor/style.css'` 就够,不一定要走 preset 路线
23
+ */
24
+
25
+ import { createRequire } from 'node:module'
26
+ import { fileURLToPath } from 'node:url'
27
+ import { dirname, resolve } from 'node:path'
28
+ import shared from './_shared-theme.js'
29
+
30
+ // 用 require.resolve 精确拿到 SDK 包根 — npm 装(node_modules/@hy-bricks/editor)/
31
+ // monorepo workspace symlink / pnpm hoist 全部能 resolve 到真实物理位置。
32
+ const requireFromPreset = createRequire(import.meta.url)
33
+
34
+ function safeResolve(pkg) {
35
+ try {
36
+ return dirname(requireFromPreset.resolve(`${pkg}/package.json`))
37
+ } catch {
38
+ return null
39
+ }
40
+ }
41
+
42
+ const editorRoot =
43
+ safeResolve('@hy-bricks/editor') ??
44
+ // fallback:preset 文件自己旁边(monorepo 直接吃 src/ 时用)
45
+ dirname(fileURLToPath(import.meta.url))
46
+ const coreRoot =
47
+ safeResolve('@hy-bricks/core') ??
48
+ resolve(dirname(fileURLToPath(import.meta.url)), '../core')
49
+
50
+ /** @type {import('tailwindcss').Config} */
51
+ export default {
52
+ ...shared,
53
+ content: [
54
+ // SDK 包内部:editor + core 的源码 / dist 都扫
55
+ `${editorRoot}/src/**/*.{vue,js,ts}`,
56
+ `${editorRoot}/dist/**/*.{js,mjs,cjs}`,
57
+ `${coreRoot}/src/**/*.{vue,js,ts}`,
58
+ `${coreRoot}/dist/**/*.{js,mjs,cjs}`,
59
+ ],
60
+ }
package/package.json ADDED
@@ -0,0 +1,79 @@
1
+ {
2
+ "name": "@hy-bricks/editor",
3
+ "version": "0.1.1",
4
+ "description": "HyperCard 嵌入式编辑器 — Monaco 三栏 + 拖 tab 分栏 + 实时预览 + shadcn-vue 风格 UI",
5
+ "keywords": [
6
+ "hypercard",
7
+ "low-code",
8
+ "vue3",
9
+ "editor",
10
+ "monaco-editor",
11
+ "shadcn-vue"
12
+ ],
13
+ "type": "module",
14
+ "main": "./dist/index.cjs",
15
+ "module": "./dist/index.mjs",
16
+ "types": "./dist/index.d.ts",
17
+ "exports": {
18
+ ".": {
19
+ "types": "./dist/index.d.ts",
20
+ "import": "./dist/index.mjs",
21
+ "require": "./dist/index.cjs"
22
+ },
23
+ "./style.css": "./dist/style.css",
24
+ "./tailwind-preset": "./dist/tailwind-preset.js",
25
+ "./package.json": "./package.json"
26
+ },
27
+ "files": [
28
+ "dist",
29
+ "README.md",
30
+ "LICENSE"
31
+ ],
32
+ "publishConfig": {
33
+ "access": "public"
34
+ },
35
+ "peerDependencies": {
36
+ "class-variance-authority": "^0.7.1",
37
+ "clsx": "^2.1.1",
38
+ "lucide-vue-next": "^0.469.0",
39
+ "monaco-editor": "^0.55.0",
40
+ "reka-ui": "^1.0.0-alpha.8",
41
+ "tailwind-merge": "^2.5.5",
42
+ "tailwindcss": "^3.4.0 || ^4.0.0",
43
+ "tailwindcss-animate": "^1.0.7",
44
+ "vue": "^3.5.0",
45
+ "@hy-bricks/core": "^0.1.1"
46
+ },
47
+ "peerDependenciesMeta": {
48
+ "tailwindcss": {
49
+ "optional": false
50
+ }
51
+ },
52
+ "devDependencies": {
53
+ "@vitejs/plugin-vue": "^5.2.1",
54
+ "@vue/test-utils": "^2.4.10",
55
+ "autoprefixer": "^10.4.20",
56
+ "jsdom": "^29.1.1",
57
+ "postcss": "^8.5.14",
58
+ "rimraf": "^5.0.10",
59
+ "tailwindcss": "^3.4.16",
60
+ "terser": "^5.47.1",
61
+ "typescript": "~5.6.3",
62
+ "vite": "^6.0.3",
63
+ "vite-plugin-css-injected-by-js": "^5.0.1",
64
+ "vite-plugin-dts": "^4.4.0",
65
+ "vitest": "^2.1.9",
66
+ "vue": "^3.5.13",
67
+ "vue-tsc": "^2.1.10"
68
+ },
69
+ "license": "MIT",
70
+ "author": "hy_top",
71
+ "scripts": {
72
+ "build": "vite build",
73
+ "dev": "vite build --watch",
74
+ "test": "vitest run",
75
+ "test:watch": "vitest",
76
+ "type-check": "vue-tsc --noEmit",
77
+ "clean": "rimraf dist .turbo"
78
+ }
79
+ }