@manohub/ui 0.8.1 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,216 @@
1
+ /**
2
+ * CodeBlock —— 代码块族(只读面 + 编辑面 + 语言角标 + 复制位)。
3
+ *
4
+ * 一个**族根** `.mh-code-block` 承载两形态共用的部分(底 / 圆角 / 等宽字族 / 字号 / 行高 / 定位上下文),
5
+ * 形态差异只在**槽位**上:只读面是 `__view`(`<pre>`,横向滚动、缺省不折行),
6
+ * 编辑面是 `__editor`(`<textarea>`,软换行)。两形态共用同一个根 ⇒ 互切时块体不跳,
7
+ * 且"同源"是**一处定义**(`code-block.tokens.css`),不是两处巧合。
8
+ *
9
+ * 观感基准:**无边框浅底块 + 方圆角 + 右上角语言角标 + 悬浮/聚焦让位后出现的方形图标复制按钮**。
10
+ *
11
+ * ## 三件结构决定(改前先读)
12
+ *
13
+ * - **复制按钮的面仍归按钮族**(`.mh-button--icon`),本族只补「浮在代码面上需要自己的底与定位」;
14
+ * 故标记是 `class="mh-button mh-button--icon mh-code-block__copy"`。
15
+ * - **复制按钮与语言角标都不能放进代码面里**:只读面是 `overflow-x: auto` 的滚动容器,会裁掉
16
+ * 外挂的绝对定位件 —— 故两者都挂在**族根**上(族根只做 `position: relative`)。
17
+ * - **隐藏用 `opacity` + `pointer-events`**(不是 `visibility` / `display`):键盘仍能 Tab 到它,
18
+ * `:focus-within` 会把它显出来,且隐藏时不残留一块看不见的可点区。
19
+ * - **编辑面画框的是根**(圆角归根、面自身透明无框):原生 `textarea` 的框模型(内距 / 滚动条 /
20
+ * 聚焦轮廓)会把框画歪,收归根才能与只读面对齐(旧实现同此口径)。
21
+ *
22
+ * ## 本族与按钮族的层序
23
+ *
24
+ * `styles/index.css` 里 `code-block.css` 在 `button.css` **之前**导入,故 `background` / `color` /
25
+ * `box-shadow` 这些**同特异性的声明会被按钮族的档位规则压掉**(历史 bug:复制位的白底被
26
+ * `.mh-button--text { background: transparent }` 压成透明,看起来像一段悬空文字)。
27
+ * 修法不是调层序,而是让本族的声明**升一档特异性**(`.mh-code-block > .mh-code-block__copy`,
28
+ * 悬停 / 成功态再各叠一层)—— 按钮族日后换皮肤也压不掉它。
29
+ *
30
+ * 零 JS 可表达:全部皮肤 + 角标让位 + 复制位的显隐。**真正的复制动作归组件层**(要 `navigator.clipboard`)。
31
+ */
32
+
33
+ /* ===== 族根:两形态共用的底 / 圆角 / 字族,以及角标与复制位的定位上下文 ===== */
34
+ .mh-code-block {
35
+ position: relative;
36
+ min-width: 0; /* flex / grid 里能被压到容器宽(否则长行的 min-content 会把轨道撑宽、页面横向溢出) */
37
+ border-radius: var(--ui-code-block-radius);
38
+ background: var(--ui-code-block-bg);
39
+ font-family: var(--ui-font-mono);
40
+ font-size: var(--ui-code-block-font-size);
41
+ line-height: var(--ui-code-block-line-height);
42
+ color: var(--ui-base-content);
43
+ }
44
+ /* 聚焦环:与输入框族同一句(主色弱底当环,不新造阴影令牌)。**只在编辑档**给 ——
45
+ 只读面本身不可聚焦,给它加环会在「键盘聚焦复制位」时冒出一圈(只读块从前没有这一圈,保持原样)。 */
46
+ .mh-code-block--editable {
47
+ transition: box-shadow 0.15s ease, background-color 0.15s ease, color 0.15s ease;
48
+ }
49
+ .mh-code-block--editable:focus-within {
50
+ box-shadow: 0 0 0 2px var(--ui-primary-weak);
51
+ }
52
+
53
+ /* ===== 只读面(`__view`):`<pre>` —— 缺省横向滚动(缩进与列对齐不被破坏),`--wrap` 档才软换行 ===== */
54
+ .mh-code-block__view {
55
+ display: block;
56
+ margin: var(--ui-space-2) 0 0;
57
+ padding: var(--ui-code-block-padding);
58
+ border: 0;
59
+ border-radius: inherit;
60
+ font: inherit;
61
+ color: inherit;
62
+ overflow-x: auto;
63
+ white-space: pre;
64
+ overflow-wrap: normal;
65
+ }
66
+ .mh-code-block--wrap > .mh-code-block__view {
67
+ white-space: pre-wrap;
68
+ overflow-wrap: anywhere;
69
+ }
70
+
71
+ /* ===== 编辑面(`__editor`):`<textarea>` 透明底 + 无框 + 无 outline(框与聚焦环都画在族根上) ===== */
72
+ .mh-code-block__editor {
73
+ display: block;
74
+ width: 100%;
75
+ margin: 0;
76
+ padding: var(--ui-code-block-editor-padding-y) var(--ui-code-block-editor-padding-x);
77
+ border: 0;
78
+ border-radius: inherit;
79
+ background: transparent;
80
+ font: inherit;
81
+ line-height: inherit;
82
+ color: inherit;
83
+ outline: none;
84
+ resize: none; /* 高度由 autoSize / maxHeight 决定,不留拖拽把手(同只读面:不被用户拖变形) */
85
+ overflow: auto;
86
+ tab-size: 2;
87
+ }
88
+ .mh-code-block__editor::placeholder {
89
+ color: var(--ui-base-content-subtle);
90
+ }
91
+
92
+ /* 只读档(原生 `readonly`:仍可选中、可滚动、可复制,只是不可改)——**不弱化字色**,
93
+ 代码是拿来读的;只是光标不再是文本输入那一支(与只读面观感一致) */
94
+ .mh-code-block--editable:has(> .mh-code-block__editor[readonly]) {
95
+ cursor: default;
96
+ }
97
+
98
+ /* 禁用档:底与字色弱化 + 内描边(本族没有外描边,靠内描边表达"锁住") */
99
+ .mh-code-block--editable:has(> .mh-code-block__editor:disabled) {
100
+ background: var(--ui-base-100);
101
+ color: var(--ui-base-content-subtle);
102
+ box-shadow: inset 0 0 0 1px var(--ui-base-300);
103
+ }
104
+ .mh-code-block--editable:has(> .mh-code-block__editor:disabled) > .mh-code-block__editor {
105
+ cursor: not-allowed;
106
+ }
107
+
108
+ /* ===== 语言角标(可选):两形态的槽位**有意不同** =====
109
+ * 共用部分(绝对定位 + 等宽小字 + 不吃指针)写在基类;
110
+ * 只读面与复制位**同槽位**(等高 32),悬停 / 聚焦时淡出让位(零 JS 互斥);
111
+ * 编辑面**不叠在文字上**(内容会折行,压住就看不见了),而是让首行右端**预留**出它的宽度。 */
112
+ .mh-code-block__lang {
113
+ position: absolute;
114
+ display: flex;
115
+ align-items: center;
116
+ font-family: var(--ui-font-mono);
117
+ font-size: var(--ui-code-block-font-size);
118
+ line-height: 1;
119
+ color: var(--ui-base-content-subtle);
120
+ user-select: none;
121
+ pointer-events: none; /* 装饰位不吃指针事件,否则会挡住复制位的悬停热区 */
122
+ }
123
+ /* 只读档:与复制位同槽位(让位互换时基线不跳) */
124
+ .mh-code-block:not(.mh-code-block--editable) > .mh-code-block__lang {
125
+ top: var(--ui-code-block-copy-inset);
126
+ right: var(--ui-code-block-copy-inset);
127
+ height: var(--ui-code-block-copy-size);
128
+ transition: opacity 0.15s ease;
129
+ }
130
+ .mh-code-block:not(.mh-code-block--editable):hover > .mh-code-block__lang,
131
+ .mh-code-block:not(.mh-code-block--editable):focus-within > .mh-code-block__lang,
132
+ .mh-code-block.is-hover > .mh-code-block__lang {
133
+ opacity: 0;
134
+ }
135
+ /* 编辑档:贴编辑面的右上角,并给编辑面首行右端预留出它的宽度 */
136
+ .mh-code-block--editable > .mh-code-block__lang {
137
+ top: var(--ui-code-block-editor-padding-y);
138
+ right: var(--ui-code-block-editor-padding-x);
139
+ height: var(--ui-code-block-line-height);
140
+ }
141
+ .mh-code-block--editable:has(> .mh-code-block__lang) > .mh-code-block__editor {
142
+ padding-right: calc(var(--ui-code-block-editor-padding-x) + var(--ui-code-block-lang-reserve));
143
+ }
144
+
145
+ /* ===== 行内 chip(`--inline`,挂在 `<code>` 上即自身为根;`--plain` 是它的中性档) ===== */
146
+ .mh-code-block--inline {
147
+ display: inline-flex;
148
+ align-items: center;
149
+ padding: 1px var(--ui-space-3);
150
+ border-radius: var(--ui-radius-full);
151
+ background: var(--ui-code-block-chip-bg);
152
+ color: var(--ui-primary);
153
+ /* 行内 chip 回到「继承外部行高」(改动前它不声明行高,族根那枚等宽行高是给代码面的)——
154
+ 同一元素上两条规则,后者胜出,故这里写 inherit 即等价于旧行为 */
155
+ line-height: inherit;
156
+ }
157
+ /* 中性档(规范 `.chip--plain`:规则 ID 这类不强调的短标识) */
158
+ .mh-code-block--plain {
159
+ background: var(--ui-code-block-bg);
160
+ color: var(--ui-base-content);
161
+ border-radius: var(--ui-code-block-radius);
162
+ }
163
+
164
+ /* ===== 复制位(面归按钮族,本族只给定位、浮底、方形几何、显隐) =====
165
+ * 选择器**刻意升一档**(`.mh-code-block >`):同特异性下按钮族的档位规则会压掉这几条(见文件头) */
166
+ .mh-code-block > .mh-code-block__copy {
167
+ position: absolute;
168
+ top: var(--ui-code-block-copy-inset);
169
+ right: var(--ui-code-block-copy-inset);
170
+ width: var(--ui-code-block-copy-size);
171
+ height: var(--ui-code-block-copy-size);
172
+ padding: 0;
173
+ border: 1px solid var(--ui-code-block-line);
174
+ border-radius: var(--ui-radius-sm);
175
+ background: var(--ui-base-100);
176
+ color: var(--ui-base-content);
177
+ box-shadow: none; /* 压掉图标按钮 hover 档的卡片光晕(本件浮在块上,再加影会脏) */
178
+ opacity: 0;
179
+ pointer-events: none;
180
+ transition: opacity 0.15s ease, border-color 0.15s ease, color 0.15s ease;
181
+ }
182
+ /* 显形:族根悬停 / 键盘聚焦进入(`.is-hover` 是**静态档**,供静态页与读图对状态,与全库同款三源等价) */
183
+ .mh-code-block:hover > .mh-code-block__copy,
184
+ .mh-code-block:focus-within > .mh-code-block__copy,
185
+ .mh-code-block.is-hover > .mh-code-block__copy {
186
+ opacity: 1;
187
+ pointer-events: auto;
188
+ }
189
+ /* 悬停 / 键盘聚焦的加深档(再叠一层,压过 `.mh-button--icon:hover` 的字色与光晕) */
190
+ .mh-code-block > .mh-code-block__copy:hover,
191
+ .mh-code-block > .mh-code-block__copy:focus-visible,
192
+ .mh-code-block.is-hover > .mh-code-block__copy {
193
+ border-color: var(--ui-base-content-subtle);
194
+ color: var(--ui-primary);
195
+ box-shadow: none;
196
+ }
197
+ /* 复制成功态(组件下发 `data-state`;原生无此态,静态档 `.is-copied` 供读图) */
198
+ .mh-code-block > .mh-code-block__copy.is-copied,
199
+ .mh-code-block > .mh-code-block__copy[data-state='copied'] {
200
+ border-color: var(--ui-success);
201
+ color: var(--ui-success);
202
+ opacity: 1;
203
+ pointer-events: auto;
204
+ }
205
+
206
+ /* 无悬停能力的设备(触屏):复制位常显;角标让位(`display: none` 而非透明 —— 它只是装饰,
207
+ 不占焦点序列,隐藏掉才不会与常显的复制位叠在一起) */
208
+ @media (hover: none) {
209
+ .mh-code-block > .mh-code-block__copy {
210
+ opacity: 1;
211
+ pointer-events: auto;
212
+ }
213
+ .mh-code-block:not(.mh-code-block--editable) > .mh-code-block__lang {
214
+ display: none;
215
+ }
216
+ }
@@ -0,0 +1,40 @@
1
+ /**
2
+ * code-block —— 单件令牌(**基础值**)。
3
+ *
4
+ * 本族自己的几何 / 尺寸 / 色彩槽位;只服务本族,故不随主题进 `@manohub/theme`。
5
+ *
6
+ * ## 两形态共用同一份令牌
7
+ *
8
+ * 只读面(`__view`)与编辑面(`__editor`)**共用**底 / 圆角 / 字族 / 字号 / 行高这几枚 ——
9
+ * 这正是「视觉同源、互切不跳」的落点:一处定义,两形态一起变。
10
+ * 从前是两套单件令牌各写一份同值(`--ui-code-*` 与 `--ui-code-editor-*`),
11
+ * 靠注释互相提醒"要一致";0.9.0 收成一族(见 `docs/guide/migration-0-9.md`)。
12
+ *
13
+ * 两形态**有意不同**的只有内距:只读面上下 12(与左右齐)、编辑面上下 8
14
+ * (`textarea` 的行高自带行距,上下再给 12 会显得空)—— 它们各自保留一枚令牌。
15
+ *
16
+ * ## 抽出说明
17
+ *
18
+ * 这些是**基础值**,锚在 `[data-manohub-ui]` —— 跨包唯一作用域锚,由入口层无条件写在应用容器上,
19
+ * 因此**任何落在容器里的分件都取得到值**(历史症状:跨件消费的 `--ui-*-group-*` 取不到值,组内间距变 0)。
20
+ *
21
+ * ⚠️ `.mh-<件>--<档>` 这类**档位改写**留在面文件的修饰符块里 —— 它们靠「同一元素上两个
22
+ * 选择器改写同一个令牌」生效,搬到本文件会让档位失效。
23
+ * ⚠️ 新增组件要登记两处:本文件 + `src/styles/index.css`(见其文件头「五处齐备」)。
24
+ */
25
+ [data-manohub-ui] {
26
+ /* 单件令牌(挂在容器上:族根 / 只读面 / 编辑面 / 复制位都在其下) */
27
+ --ui-code-block-bg: var(--ui-base-200); /* 代码面底(只读块与编辑框同底 —— 互切的"不跳"靠它) */
28
+ --ui-code-block-chip-bg: var(--ui-primary-weak); /* 行内 chip 底(规范 Au02) */
29
+ --ui-code-block-line: var(--ui-base-300); /* 描边色:面自身无边框,本令牌现服务复制位按钮 */
30
+ --ui-code-block-radius: var(--ui-radius-control);
31
+ --ui-code-block-font-size: var(--ui-font-caption); /* 规范等宽字号档对位 */
32
+ --ui-code-block-line-height: var(--ui-line-caption); /* 等宽行高:角标与复制位也吃它 */
33
+ --ui-code-block-padding: var(--ui-space-3); /* 12:只读面四边内距 */
34
+ --ui-code-block-editor-padding-x: var(--ui-space-3); /* 12:编辑面左右内距 */
35
+ --ui-code-block-editor-padding-y: var(--ui-space-2); /* 8:编辑面上下内距(比只读面小,见文件头) */
36
+ --ui-code-block-lang-reserve: 56px; /* 编辑面:首行右端给角标预留的宽度(只影响折行位置) */
37
+ --ui-code-block-copy-size: var(--ui-control-height-md); /* 32:复制位方形热区(与按钮族标准档同高) */
38
+ /* 复制位是 `Button`(`mh-button__icon`):字形尺寸归按钮族的图标槽(常规图标档),本族不另设令牌 */
39
+ --ui-code-block-copy-inset: var(--ui-space-2); /* 8:复制位 / 角标距块边 */
40
+ }
@@ -58,9 +58,9 @@
58
58
  font-weight: var(--ui-weight-medium);
59
59
  color: var(--ui-base-content);
60
60
  }
61
- /* 语义档(`tone`):标题左侧一个**裸符号**,颜色按档取语义色 ——
62
- 缺省(不传 tone)不渲染,故不影响既有外观。取色口径与 `Notice` 同一张表
63
- (success / warning 取 -strong 档,否则浅底上对比度不足)。 */
61
+ /* 语义档(`tone`):标题左侧一个**带圈语义图标**(图标位没有底色圆,故取带圈族 —— 见
62
+ `@manohub/icon` 的「两族」口径),颜色按档取语义色 —— 缺省(不传 tone)不渲染,故不影响既有外观。
63
+ 取色口径与 `Notice` 同一张表(success / warning 取 -strong 档,否则浅底上对比度不足)。 */
64
64
  .mh-dialog__tone-icon {
65
65
  flex: none;
66
66
  width: var(--ui-font-icon);
@@ -59,8 +59,7 @@
59
59
  @import './components/tag.tokens.css';
60
60
  @import './components/avatar.tokens.css';
61
61
  @import './components/skeleton.tokens.css';
62
- @import './components/code.tokens.css';
63
- @import './components/code-editor.tokens.css';
62
+ @import './components/code-block.tokens.css';
64
63
 
65
64
  /* 表单与选择控件 */
66
65
  @import './components/button.tokens.css';
@@ -116,8 +115,7 @@
116
115
  @import './components/tag.css';
117
116
  @import './components/avatar.css';
118
117
  @import './components/skeleton.css';
119
- @import './components/code.css'; /* 行内 chip / 代码块(复制按钮的面归按钮族,故在其后);面里靠升一档特异性压过按钮族,见其文件头 */
120
- @import './components/code-editor.css'; /* 可编辑代码区(与只读块视觉同源;无按钮,与按钮族无层序关系) */
118
+ @import './components/code-block.css'; /* 代码块族(只读面 / 编辑面 / 角标 / 复制位;复制按钮的面归按钮族,故在其后);面里靠升一档特异性压过按钮族,见其文件头 */
121
119
 
122
120
  /* 表单与选择控件(按钮 → 输入 → 选择 → 开关 → 勾选 → 单选(两形态同面)→ 胶囊) */
123
121
  @import './components/button.css';
@@ -203,3 +203,7 @@
203
203
  :where([data-manohub-ui]) summary {
204
204
  display: list-item;
205
205
  }
206
+
207
+ :where([data-manohub-ui]) label {
208
+ margin: 0;
209
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@manohub/ui",
3
- "version": "0.8.1",
3
+ "version": "0.9.0",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "AIHub 自绘组件层:不依赖 farris 的手写 Vue TSX 组件 + 自带令牌层(Farris 规范视觉 · daisyUI 式语义令牌 · shadcn 式 API 词汇)。",
@@ -46,7 +46,7 @@
46
46
  "vue": "^3.5.0"
47
47
  },
48
48
  "dependencies": {
49
- "@manohub/icon": "^0.8.1"
49
+ "@manohub/icon": "^0.9.0"
50
50
  },
51
51
  "devDependencies": {
52
52
  "@types/node": "^24.0.0",
@@ -1,134 +0,0 @@
1
- import { type PropType, type SlotsType, type VNodeChild } from 'vue';
2
- /**
3
- * 代码(**自绘**;行内 chip / 代码块 / 语言角标 / 复制位)—— **只读展示件**。
4
- *
5
- * 三种形态:`inline`(行内 chip,如版本号、规则 ID)、`inline + plain`(中性档)、
6
- * 缺省(代码块 + 右上角 hover / 聚焦显形的方形图标复制按钮 + 可选语言角标)。
7
- * 代码块**缺省不折行**(横向滚动,缩进与列对齐不被破坏),`wrap` 才软换行。
8
- *
9
- * **编辑态用 `CodeEditor`**(本件只管展示):要「只读看 + 就地改」,把两件组合起来用
10
- * ——只读时用本件渲染(可接已高亮节点),切到编辑时换成 `CodeEditor`。
11
- *
12
- * ## 高亮由消费方声明(本件不做语法高亮)
13
- *
14
- * 本件不认识任何语言:`language` 只画右上角的**角标**,**不做**分词着色。
15
- * 需要高亮时走**默认插槽**传入已高亮过的节点(VNode / 已转义好的 HTML 片段皆可),
16
- * 此时 `code` 仍应给**纯文本**——它是复制进剪贴板的内容(插槽内容是渲染结果,不能拿去复制)。
17
- *
18
- * ## 复制按钮的面归按钮族(`.mh-button--icon`)
19
- *
20
- * 本件只补定位与显隐(见 `styles/components/code.css`);按钮挂在宿主 `.mh-code__wrap` 上
21
- * 而**不是代码块里** —— 代码块是 `overflow-x: auto` 的滚动容器,放进去会被裁掉。
22
- *
23
- * ## 复制动作与降级
24
- *
25
- * 走 `navigator.clipboard.writeText`(**唯一需要 JS 的部分**,按 copied 状态二选一图标)。
26
- * 环境没有该 API(旧浏览器、非安全上下文)或写入被拒时:不谎报成功,`onCopy(false)` 交回结果
27
- * 并 `console.warn` 一次 —— 消费方据此提示「请手动复制」(本件不自己弹提示,避免与页面的提示口径两套)。
28
- *
29
- * @example 行内
30
- * <Code inline>v1.2.0</Code>
31
- * <Code inline plain>RULE-1024</Code>
32
- *
33
- * @example 代码块 + 语言角标 + 复制
34
- * <Code :code="sql" language="sql" copyable @copy="(ok) => ok || showManualTip()" />
35
- *
36
- * @example 高亮交给消费方(`code` 仍是复制用的纯文本)
37
- * <Code :code="sql" language="sql" copyable>
38
- * <span v-html="highlightedSql" />
39
- * </Code>
40
- */
41
- export declare const Code: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
42
- /** 代码文本(也可用默认插槽给复杂内容);**复制进剪贴板的就是它** */
43
- code: {
44
- type: StringConstructor;
45
- default: string;
46
- };
47
- /** 行内 chip(缺省是代码块) */
48
- inline: {
49
- type: BooleanConstructor;
50
- default: boolean;
51
- };
52
- /** 行内中性档(不强调的短标识) */
53
- plain: {
54
- type: BooleanConstructor;
55
- default: boolean;
56
- };
57
- /** 右上角语言角标(如 `sql` / `json` / `vue`);只做标注,**不做语法高亮** */
58
- language: {
59
- type: StringConstructor;
60
- default: string;
61
- };
62
- /** 代码块右上角的复制按钮 */
63
- copyable: {
64
- type: BooleanConstructor;
65
- default: boolean;
66
- };
67
- /** 最大高度(数字按 px):超出后**块内滚动**,用于长日志/长配置 */
68
- maxHeight: {
69
- type: PropType<string | number>;
70
- default: undefined;
71
- };
72
- /** 允许软换行(缺省保持 `pre` 的横向不换行 + 横向滚动) */
73
- wrap: {
74
- type: BooleanConstructor;
75
- default: boolean;
76
- };
77
- /** 复制结果回调:`true` = 真的写进剪贴板了 */
78
- onCopy: {
79
- type: PropType<(ok: boolean) => void>;
80
- default: undefined;
81
- };
82
- }>, () => import("vue/jsx-runtime").JSX.Element, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
83
- /** 代码文本(也可用默认插槽给复杂内容);**复制进剪贴板的就是它** */
84
- code: {
85
- type: StringConstructor;
86
- default: string;
87
- };
88
- /** 行内 chip(缺省是代码块) */
89
- inline: {
90
- type: BooleanConstructor;
91
- default: boolean;
92
- };
93
- /** 行内中性档(不强调的短标识) */
94
- plain: {
95
- type: BooleanConstructor;
96
- default: boolean;
97
- };
98
- /** 右上角语言角标(如 `sql` / `json` / `vue`);只做标注,**不做语法高亮** */
99
- language: {
100
- type: StringConstructor;
101
- default: string;
102
- };
103
- /** 代码块右上角的复制按钮 */
104
- copyable: {
105
- type: BooleanConstructor;
106
- default: boolean;
107
- };
108
- /** 最大高度(数字按 px):超出后**块内滚动**,用于长日志/长配置 */
109
- maxHeight: {
110
- type: PropType<string | number>;
111
- default: undefined;
112
- };
113
- /** 允许软换行(缺省保持 `pre` 的横向不换行 + 横向滚动) */
114
- wrap: {
115
- type: BooleanConstructor;
116
- default: boolean;
117
- };
118
- /** 复制结果回调:`true` = 真的写进剪贴板了 */
119
- onCopy: {
120
- type: PropType<(ok: boolean) => void>;
121
- default: undefined;
122
- };
123
- }>> & Readonly<{}>, {
124
- inline: boolean;
125
- wrap: boolean;
126
- plain: boolean;
127
- code: string;
128
- onCopy: (ok: boolean) => void;
129
- language: string;
130
- copyable: boolean;
131
- maxHeight: string | number;
132
- }, SlotsType<{
133
- default?: () => VNodeChild;
134
- }>, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
@@ -1,117 +0,0 @@
1
- import { defineComponent, ref, onBeforeUnmount, createVNode } from "vue";
2
- import { Icon } from "@manohub/icon";
3
- const Code = /* @__PURE__ */ defineComponent({
4
- name: "Code",
5
- inheritAttrs: false,
6
- props: {
7
- /** 代码文本(也可用默认插槽给复杂内容);**复制进剪贴板的就是它** */
8
- code: {
9
- type: String,
10
- default: ""
11
- },
12
- /** 行内 chip(缺省是代码块) */
13
- inline: {
14
- type: Boolean,
15
- default: false
16
- },
17
- /** 行内中性档(不强调的短标识) */
18
- plain: {
19
- type: Boolean,
20
- default: false
21
- },
22
- /** 右上角语言角标(如 `sql` / `json` / `vue`);只做标注,**不做语法高亮** */
23
- language: {
24
- type: String,
25
- default: ""
26
- },
27
- /** 代码块右上角的复制按钮 */
28
- copyable: {
29
- type: Boolean,
30
- default: false
31
- },
32
- /** 最大高度(数字按 px):超出后**块内滚动**,用于长日志/长配置 */
33
- maxHeight: {
34
- type: [String, Number],
35
- default: void 0
36
- },
37
- /** 允许软换行(缺省保持 `pre` 的横向不换行 + 横向滚动) */
38
- wrap: {
39
- type: Boolean,
40
- default: false
41
- },
42
- /** 复制结果回调:`true` = 真的写进剪贴板了 */
43
- onCopy: {
44
- type: Function,
45
- default: void 0
46
- }
47
- },
48
- slots: Object,
49
- setup(props, {
50
- slots,
51
- attrs
52
- }) {
53
- const copied = ref(false);
54
- let timer;
55
- onBeforeUnmount(() => {
56
- if (timer) clearTimeout(timer);
57
- });
58
- const copy = async () => {
59
- const text = props.code;
60
- let ok = false;
61
- try {
62
- if (typeof navigator !== "undefined" && navigator.clipboard?.writeText) {
63
- await navigator.clipboard.writeText(text);
64
- ok = true;
65
- }
66
- } catch {
67
- ok = false;
68
- }
69
- if (!ok) {
70
- console.warn("[ui] Code:当前环境无法写入剪贴板,复制未生效");
71
- }
72
- copied.value = ok;
73
- if (ok) {
74
- if (timer) clearTimeout(timer);
75
- timer = setTimeout(() => copied.value = false, 1500);
76
- }
77
- props.onCopy?.(ok);
78
- };
79
- return () => {
80
- const extra = attrs.class;
81
- const style = attrs.style;
82
- const content = slots.default ? slots.default() : props.code;
83
- if (props.inline) {
84
- return createVNode("code", {
85
- "class": ["mh-code", props.plain ? "mh-code--plain" : "", extra].filter(Boolean).join(" "),
86
- "style": style
87
- }, [content]);
88
- }
89
- const copyLabel = copied.value ? "已复制" : "复制代码";
90
- return createVNode("div", {
91
- "class": ["mh-code__wrap", extra].filter(Boolean).join(" "),
92
- "style": style
93
- }, [createVNode("pre", {
94
- "class": ["mh-code--block", props.wrap ? "mh-code--wrap" : ""].filter(Boolean).join(" "),
95
- "style": props.maxHeight === void 0 ? void 0 : {
96
- maxHeight: typeof props.maxHeight === "number" ? `${props.maxHeight}px` : props.maxHeight,
97
- overflow: "auto"
98
- }
99
- }, [content]), props.language ? createVNode("span", {
100
- "class": "mh-code__lang"
101
- }, [props.language]) : null, props.copyable ? createVNode("button", {
102
- "type": "button",
103
- "class": ["mh-button", "mh-button--icon", "mh-code__copy", copied.value ? "is-copied" : ""].filter(Boolean).join(" "),
104
- "data-state": copied.value ? "copied" : "idle",
105
- "aria-label": copyLabel,
106
- "title": copyLabel,
107
- "onClick": copy
108
- }, [createVNode(Icon, {
109
- "name": copied.value ? "check" : "copy",
110
- "class": "mh-button__icon"
111
- }, null)]) : null]);
112
- };
113
- }
114
- });
115
- export {
116
- Code
117
- };