niuma-ui 1.2.8 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -6,6 +6,21 @@
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [1.3.0] - 2026-09-07
10
+
11
+ ### 新增
12
+
13
+ - `RsTerminal`:`searchEnabled`(默认 `false`)打开终端内查找。`Ctrl/⌘+F`、右键「搜索」调出浮层;Enter / Shift+Enter 下一个 / 上一个,Esc 关闭。依赖 `@xterm/addon-search`,已列入 `niuma-ui/vite-prebundle/xterm`。
14
+ - `RsTerminal`:`copyOnSelect`(默认 `false`)在鼠标松开且有选区时复制,接近原生终端 copy-on-select。
15
+ - `RsTerminal`:`#overlayAction` 插槽。有插槽时遮罩可点,便于宿主放「重连」等操作;仅 `overlay` 文案时仍拦截点击。
16
+ - `RsTerminalAction` 增加 `search`。中英 locale:`terminal.search` / `searchPlaceholder` / `searchNext` / `searchPrev` / `searchClose`。
17
+
18
+ ## [1.2.9] - 2026-09-01
19
+
20
+ ### 修复
21
+
22
+ - `RsCodeEditor` 切换主题只换配色,不再拆掉重建,正文不会空白
23
+
9
24
  ## [1.2.8] - 2026-09-01
10
25
 
11
26
  ### 新增
@@ -1,5 +1,5 @@
1
1
 
2
- .rs-code-editor[data-v-29cd1019] {
2
+ .rs-code-editor[data-v-edadd50f] {
3
3
  display: flex;
4
4
  flex-direction: column;
5
5
  overflow: hidden;
@@ -8,10 +8,10 @@
8
8
  background: var(--rs-input-bg);
9
9
  color: var(--rs-text);
10
10
  }
11
- .rs-code-editor--square[data-v-29cd1019] {
11
+ .rs-code-editor--square[data-v-edadd50f] {
12
12
  border-radius: 0;
13
13
  }
14
- .rs-code-editor--embedded[data-v-29cd1019] {
14
+ .rs-code-editor--embedded[data-v-edadd50f] {
15
15
  border: 0;
16
16
  border-radius: 0;
17
17
  background: transparent;
@@ -19,7 +19,7 @@
19
19
  min-height: 0;
20
20
  width: 100%;
21
21
  }
22
- .rs-code-editor__toolbar[data-v-29cd1019] {
22
+ .rs-code-editor__toolbar[data-v-edadd50f] {
23
23
  display: flex;
24
24
  align-items: center;
25
25
  justify-content: space-between;
@@ -28,23 +28,23 @@
28
28
  color: var(--rs-muted);
29
29
  font-size: var(--rs-font-size-xs);
30
30
  }
31
- .rs-code-editor__body[data-v-29cd1019] {
31
+ .rs-code-editor__body[data-v-edadd50f] {
32
32
  position: relative;
33
33
  flex: 1;
34
34
  min-height: 0;
35
35
  display: flex;
36
36
  flex-direction: column;
37
37
  }
38
- .rs-code-editor__surface[data-v-29cd1019] {
38
+ .rs-code-editor__surface[data-v-edadd50f] {
39
39
  flex: 1;
40
40
  min-height: 0;
41
41
  overflow: hidden;
42
42
  }
43
- .rs-code-editor--embedded .rs-code-editor__body[data-v-29cd1019],
44
- .rs-code-editor--embedded .rs-code-editor__surface[data-v-29cd1019] {
43
+ .rs-code-editor--embedded .rs-code-editor__body[data-v-edadd50f],
44
+ .rs-code-editor--embedded .rs-code-editor__surface[data-v-edadd50f] {
45
45
  height: 100%;
46
46
  }
47
- .rs-code-editor__inline-edit[data-v-29cd1019] {
47
+ .rs-code-editor__inline-edit[data-v-edadd50f] {
48
48
  position: absolute;
49
49
  left: 50%;
50
50
  top: 12%;
@@ -57,7 +57,7 @@
57
57
  background: var(--rs-surface-elevated);
58
58
  box-shadow: 0 8px 28px rgba(0, 0, 0, 0.14);
59
59
  }
60
- .rs-code-editor__inline-edit-head[data-v-29cd1019] {
60
+ .rs-code-editor__inline-edit-head[data-v-edadd50f] {
61
61
  display: flex;
62
62
  align-items: center;
63
63
  justify-content: space-between;
@@ -66,14 +66,14 @@
66
66
  font-weight: var(--rs-font-weight-semibold);
67
67
  color: var(--rs-foreground);
68
68
  }
69
- .rs-code-editor__inline-edit-head kbd[data-v-29cd1019] {
69
+ .rs-code-editor__inline-edit-head kbd[data-v-edadd50f] {
70
70
  padding: 0.1rem 0.35rem;
71
71
  border-radius: 4px;
72
72
  border: 1px solid var(--rs-border-subtle);
73
73
  font-size: var(--rs-font-size-xs);
74
74
  color: var(--rs-muted);
75
75
  }
76
- .rs-code-editor__inline-preview[data-v-29cd1019] {
76
+ .rs-code-editor__inline-preview[data-v-edadd50f] {
77
77
  margin: 0 0 0.5rem;
78
78
  padding: 0.35rem 0.5rem;
79
79
  border-radius: 4px;
@@ -86,7 +86,7 @@
86
86
  max-height: 4.5rem;
87
87
  overflow: hidden;
88
88
  }
89
- .rs-code-editor__inline-input[data-v-29cd1019] {
89
+ .rs-code-editor__inline-input[data-v-edadd50f] {
90
90
  width: 100%;
91
91
  box-sizing: border-box;
92
92
  margin-bottom: 0.5rem;
@@ -97,21 +97,21 @@
97
97
  color: var(--rs-foreground);
98
98
  font-size: var(--rs-font-size-sm);
99
99
  }
100
- .rs-code-editor__inline-input[data-v-29cd1019]:focus {
100
+ .rs-code-editor__inline-input[data-v-edadd50f]:focus {
101
101
  outline: 2px solid color-mix(in srgb, var(--rs-primary) 35%, transparent);
102
102
  outline-offset: 1px;
103
103
  }
104
- .rs-code-editor__inline-error[data-v-29cd1019] {
104
+ .rs-code-editor__inline-error[data-v-edadd50f] {
105
105
  margin: -0.25rem 0 0.5rem;
106
106
  font-size: var(--rs-font-size-xs);
107
107
  color: var(--rs-danger);
108
108
  }
109
- .rs-code-editor__inline-actions[data-v-29cd1019] {
109
+ .rs-code-editor__inline-actions[data-v-edadd50f] {
110
110
  display: flex;
111
111
  justify-content: flex-end;
112
112
  gap: 0.35rem;
113
113
  }
114
- .rs-code-editor__inline-btn[data-v-29cd1019] {
114
+ .rs-code-editor__inline-btn[data-v-edadd50f] {
115
115
  padding: 0.3rem 0.65rem;
116
116
  border: 1px solid var(--rs-border);
117
117
  border-radius: var(--rs-radius-sm, 4px);
@@ -120,69 +120,69 @@
120
120
  font-size: var(--rs-font-size-xs);
121
121
  cursor: pointer;
122
122
  }
123
- .rs-code-editor__inline-btn--primary[data-v-29cd1019] {
123
+ .rs-code-editor__inline-btn--primary[data-v-edadd50f] {
124
124
  border-color: var(--rs-primary);
125
125
  background: var(--rs-primary);
126
126
  color: var(--rs-primary-foreground, #fff);
127
127
  }
128
- .rs-code-editor__inline-btn[data-v-29cd1019]:disabled {
128
+ .rs-code-editor__inline-btn[data-v-edadd50f]:disabled {
129
129
  opacity: 0.55;
130
130
  cursor: not-allowed;
131
131
  }
132
- .rs-code-editor__surface--disabled[data-v-29cd1019] {
132
+ .rs-code-editor__surface--disabled[data-v-edadd50f] {
133
133
  opacity: 0.6;
134
134
  pointer-events: none;
135
135
  }
136
- .rs-code-editor__surface[data-v-29cd1019] .cm-editor {
136
+ .rs-code-editor__surface[data-v-edadd50f] .cm-editor {
137
137
  height: 100%;
138
138
  background: var(--rs-surface-elevated) !important;
139
139
  }
140
- .rs-code-editor__surface[data-v-29cd1019] .cm-scroller {
140
+ .rs-code-editor__surface[data-v-edadd50f] .cm-scroller {
141
141
  font-family: var(--rs-code-font-family, var(--rs-font-mono));
142
142
  font-size: var(--rs-code-font-size, var(--rs-font-size-sm));
143
143
  line-height: var(--rs-code-line-height, var(--rs-line-height-relaxed));
144
144
  }
145
- .rs-code-editor__surface[data-v-29cd1019] .cm-gutters {
145
+ .rs-code-editor__surface[data-v-edadd50f] .cm-gutters {
146
146
  background: color-mix(in srgb, var(--rs-surface-elevated) 85%, var(--rs-border) 15%) !important;
147
147
  border-right-color: var(--rs-border-subtle) !important;
148
148
  color: var(--rs-muted);
149
149
  }
150
- .rs-code-editor--embedded .rs-code-editor__surface[data-v-29cd1019] .cm-editor {
150
+ .rs-code-editor--embedded .rs-code-editor__surface[data-v-edadd50f] .cm-editor {
151
151
  height: 100%;
152
152
  background: transparent !important;
153
153
  }
154
- .rs-code-editor--no-fold .rs-code-editor__surface[data-v-29cd1019] .cm-foldGutter {
154
+ .rs-code-editor--no-fold .rs-code-editor__surface[data-v-edadd50f] .cm-foldGutter {
155
155
  display: none !important;
156
156
  width: 0 !important;
157
157
  min-width: 0 !important;
158
158
  }
159
- .rs-code-editor--gutter-fixed .rs-code-editor__surface[data-v-29cd1019] .cm-gutters {
159
+ .rs-code-editor--gutter-fixed .rs-code-editor__surface[data-v-edadd50f] .cm-gutters {
160
160
  min-width: var(--rs-code-editor-gutter-width);
161
161
  }
162
- .rs-code-editor--gutter-fixed.rs-code-editor--no-fold .rs-code-editor__surface[data-v-29cd1019] .cm-lineNumbers {
162
+ .rs-code-editor--gutter-fixed.rs-code-editor--no-fold .rs-code-editor__surface[data-v-edadd50f] .cm-lineNumbers {
163
163
  min-width: var(--rs-code-editor-gutter-width);
164
164
  }
165
- .rs-code-editor--gutter-fixed.rs-code-editor--no-fold .rs-code-editor__surface[data-v-29cd1019] .cm-lineNumbers .cm-gutterElement {
165
+ .rs-code-editor--gutter-fixed.rs-code-editor--no-fold .rs-code-editor__surface[data-v-edadd50f] .cm-lineNumbers .cm-gutterElement {
166
166
  min-width: calc(var(--rs-code-editor-gutter-width) - 8px);
167
167
  }
168
- .rs-code-editor__surface[data-v-29cd1019] .cm-content {
168
+ .rs-code-editor__surface[data-v-edadd50f] .cm-content {
169
169
  color: var(--rs-foreground);
170
170
  }
171
- .rs-code-editor__surface[data-v-29cd1019] .cm-lintRange-error {
171
+ .rs-code-editor__surface[data-v-edadd50f] .cm-lintRange-error {
172
172
  background: color-mix(in srgb, var(--rs-danger) 12%, transparent);
173
173
  }
174
- .rs-code-editor__surface[data-v-29cd1019] .cm-lintRange-warning {
174
+ .rs-code-editor__surface[data-v-edadd50f] .cm-lintRange-warning {
175
175
  background: color-mix(in srgb, var(--rs-warning) 12%, transparent);
176
176
  }
177
- .rs-code-editor__surface[data-v-29cd1019] .cm-lintRange-info {
177
+ .rs-code-editor__surface[data-v-edadd50f] .cm-lintRange-info {
178
178
  background: color-mix(in srgb, var(--rs-info) 10%, transparent);
179
179
  }
180
- .rs-code-editor__surface[data-v-29cd1019] .rs-code-editor__ghost {
180
+ .rs-code-editor__surface[data-v-edadd50f] .rs-code-editor__ghost {
181
181
  color: var(--rs-muted);
182
182
  opacity: 0.55;
183
183
  pointer-events: none;
184
184
  }
185
- .rs-code-editor__surface[data-v-29cd1019] .rs-code-editor__hover {
185
+ .rs-code-editor__surface[data-v-edadd50f] .rs-code-editor__hover {
186
186
  max-width: 28rem;
187
187
  padding: 0.45rem 0.65rem;
188
188
  border-radius: var(--rs-radius-sm, 4px);
@@ -195,7 +195,7 @@
195
195
  white-space: pre-wrap;
196
196
  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.12);
197
197
  }
198
- .rs-code-editor__diagnostics[data-v-29cd1019] {
198
+ .rs-code-editor__diagnostics[data-v-edadd50f] {
199
199
  margin: 0;
200
200
  padding: var(--rs-space-sm) var(--rs-space-md);
201
201
  border-top: 1px solid var(--rs-border-subtle);
@@ -203,12 +203,12 @@
203
203
  font-size: var(--rs-font-size-xs);
204
204
  list-style: none;
205
205
  }
206
- .rs-code-editor__diagnostic--error[data-v-29cd1019] {
206
+ .rs-code-editor__diagnostic--error[data-v-edadd50f] {
207
207
  color: var(--rs-danger);
208
208
  }
209
- .rs-code-editor__diagnostic--warning[data-v-29cd1019] {
209
+ .rs-code-editor__diagnostic--warning[data-v-edadd50f] {
210
210
  color: var(--rs-warning);
211
211
  }
212
- .rs-code-editor__diagnostic--info[data-v-29cd1019] {
212
+ .rs-code-editor__diagnostic--info[data-v-edadd50f] {
213
213
  color: var(--rs-info);
214
214
  }
@@ -1,4 +1,4 @@
1
- import { codeEditorLanguageLabel, resolveCodeEditorLanguage, resolveCodeEditorSize, resolveCodeEditorTheme } from "./code-editor-utils.js";
1
+ import { codeEditorLanguageLabel, readDocumentTheme, resolveCodeEditorLanguage, resolveCodeEditorSize, resolveCodeEditorTheme } from "./code-editor-utils.js";
2
2
  import { isCodeMirrorLightTheme, resolveCodeMirrorLanguage } from "./code-mirror-lang.js";
3
3
  import { diagnosticExtensions, setEditorDiagnostics } from "./code-mirror-diagnostics.js";
4
4
  import { codeMirrorIntelExtensions, goToPosition } from "./code-mirror-intel.js";
@@ -89,10 +89,12 @@ var RsCodeEditor_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ def
89
89
  });
90
90
  const resolvedLanguage = computed(() => resolveCodeEditorLanguage(props.language));
91
91
  const resolvedLanguageLabel = computed(() => codeEditorLanguageLabel(props.language));
92
- const resolvedTheme = computed(() => resolveCodeEditorTheme(props.theme));
92
+ const documentTheme = ref(readDocumentTheme());
93
+ const resolvedTheme = computed(() => props.theme === "auto" ? documentTheme.value : resolveCodeEditorTheme(props.theme));
93
94
  const editorEl = ref(null);
94
95
  const view = shallowRef(null);
95
96
  const editableCompartment = new Compartment();
97
+ const themeCompartment = new Compartment();
96
98
  let themeObserver = null;
97
99
  let unmounted = false;
98
100
  let syncingFromModel = false;
@@ -157,6 +159,15 @@ var RsCodeEditor_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ def
157
159
  ghostTimer = null;
158
160
  }
159
161
  }
162
+ function codeMirrorThemeExts() {
163
+ return isCodeMirrorLightTheme() ? [] : [oneDark];
164
+ }
165
+ function applyDocumentTheme() {
166
+ documentTheme.value = readDocumentTheme();
167
+ if (!view.value) return;
168
+ view.value.dispatch({ effects: themeCompartment.reconfigure(codeMirrorThemeExts()) });
169
+ view.value.requestMeasure();
170
+ }
160
171
  async function initEditor() {
161
172
  if (!editorEl.value || unmounted) return;
162
173
  const seq = ++initSeq;
@@ -185,7 +196,7 @@ var RsCodeEditor_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ def
185
196
  ...codeMirrorCompletionExtensions(() => ({ completionRequest: props.completionRequest })),
186
197
  ...codeMirrorGhostExtensions(() => ({ completionRequest: props.completionRequest }), scheduleGhostCompletion, cancelGhostCompletionSchedule),
187
198
  ...props.inlineEditRequest ? [codeMirrorInlineEditExtension(onInlineEditTrigger)] : [],
188
- ...isCodeMirrorLightTheme() ? [] : [oneDark],
199
+ themeCompartment.of(codeMirrorThemeExts()),
189
200
  EditorView.updateListener.of((update) => {
190
201
  if (!update.docChanged || syncingFromModel) return;
191
202
  const next = update.state.doc.toString();
@@ -204,7 +215,7 @@ var RsCodeEditor_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ def
204
215
  unmounted = false;
205
216
  initEditor();
206
217
  themeObserver = new MutationObserver(() => {
207
- initEditor();
218
+ applyDocumentTheme();
208
219
  });
209
220
  themeObserver.observe(document.documentElement, {
210
221
  attributes: true,
@@ -3,6 +3,6 @@ import _plugin_vue_export_helper_default from "../_virtual/_plugin-vue_export-he
3
3
  import RsCodeEditor_vue_vue_type_script_setup_true_lang_default from "./RsCodeEditor.impl.js";
4
4
 
5
5
  //#region src/components/RsCodeEditor.vue
6
- var RsCodeEditor_default = /*#__PURE__*/ _plugin_vue_export_helper_default(RsCodeEditor_vue_vue_type_script_setup_true_lang_default, [["__scopeId", "data-v-29cd1019"]]);
6
+ var RsCodeEditor_default = /*#__PURE__*/ _plugin_vue_export_helper_default(RsCodeEditor_vue_vue_type_script_setup_true_lang_default, [["__scopeId", "data-v-edadd50f"]]);
7
7
  //#endregion
8
8
  export { RsCodeEditor_default as default };
@@ -1,5 +1,5 @@
1
1
 
2
- .rs-terminal[data-v-67905bea] {
2
+ .rs-terminal[data-v-fba85f3c] {
3
3
  position: relative;
4
4
  height: 100%;
5
5
  min-height: 0;
@@ -16,13 +16,13 @@
16
16
  0 1px 3px color-mix(in srgb, #000 32%, transparent);
17
17
  overflow: hidden;
18
18
  }
19
- [data-rs-theme='light'] .rs-terminal[data-v-67905bea] {
19
+ [data-rs-theme='light'] .rs-terminal[data-v-fba85f3c] {
20
20
  box-shadow:
21
21
  inset 0 1px 0 color-mix(in srgb, #fff 80%, transparent),
22
22
  inset 0 0 0 1px color-mix(in srgb, #000 4%, transparent),
23
23
  0 1px 4px color-mix(in srgb, #000 8%, transparent);
24
24
  }
25
- .rs-terminal__host[data-v-67905bea] {
25
+ .rs-terminal__host[data-v-fba85f3c] {
26
26
  position: relative;
27
27
  width: 100%;
28
28
  height: 100%;
@@ -30,15 +30,17 @@
30
30
  /* FitAddon 取整余量露底时,与单元格同色(勿用 shell 灰底) */
31
31
  background: var(--rs-terminal-bg);
32
32
  }
33
- .rs-terminal__loading[data-v-67905bea],
34
- .rs-terminal__overlay[data-v-67905bea] {
33
+ .rs-terminal__loading[data-v-fba85f3c],
34
+ .rs-terminal__overlay[data-v-fba85f3c] {
35
35
  position: absolute;
36
36
  inset: 0;
37
37
  display: flex;
38
38
  align-items: center;
39
39
  justify-content: center;
40
40
  }
41
- .rs-terminal__overlay[data-v-67905bea] {
41
+ .rs-terminal__overlay[data-v-fba85f3c] {
42
+ flex-direction: column;
43
+ gap: var(--rs-space-sm);
42
44
  padding: var(--rs-space-lg);
43
45
  color: color-mix(in srgb, var(--rs-terminal-fg) 92%, #fff 8%);
44
46
  background: color-mix(in srgb, var(--rs-terminal-bg) 84%, transparent);
@@ -49,6 +51,45 @@
49
51
  font-weight: var(--rs-font-weight-regular);
50
52
  line-height: var(--rs-line-height-normal);
51
53
  }
54
+ .rs-terminal__overlay--interactive[data-v-fba85f3c] {
55
+ pointer-events: auto;
56
+ }
57
+ .rs-terminal__overlay-action[data-v-fba85f3c] {
58
+ pointer-events: auto;
59
+ }
60
+ .rs-terminal__search[data-v-fba85f3c] {
61
+ position: absolute;
62
+ top: var(--rs-space-xs);
63
+ right: var(--rs-space-xs);
64
+ z-index: 2;
65
+ display: flex;
66
+ align-items: center;
67
+ gap: var(--rs-space-xs);
68
+ padding: var(--rs-space-xs);
69
+ border: 1px solid var(--rs-terminal-border);
70
+ border-radius: var(--rs-radius-sm);
71
+ background: color-mix(in srgb, var(--rs-terminal-bg) 92%, transparent);
72
+ }
73
+ .rs-terminal__search-input[data-v-fba85f3c] {
74
+ width: 12rem;
75
+ padding: 0.2rem 0.4rem;
76
+ border: 1px solid var(--rs-terminal-border);
77
+ border-radius: var(--rs-radius-sm);
78
+ background: var(--rs-terminal-bg);
79
+ color: var(--rs-terminal-fg);
80
+ font-family: var(--rs-font-mono);
81
+ font-size: var(--rs-font-size-xs);
82
+ outline: none;
83
+ }
84
+ .rs-terminal__search-btn[data-v-fba85f3c] {
85
+ padding: 0.2rem 0.4rem;
86
+ border: 1px solid var(--rs-terminal-border);
87
+ border-radius: var(--rs-radius-sm);
88
+ background: transparent;
89
+ color: var(--rs-terminal-fg);
90
+ font-size: var(--rs-font-size-xs);
91
+ cursor: pointer;
92
+ }
52
93
 
53
94
  /*
54
95
  * letter-spacing 必须在终端内归零:宿主应用常在祖先上设负字距(NiuMa 的 .nm-app 是 -0.011em)。
@@ -57,7 +98,7 @@
57
98
  * DOM 渲染器于是按 `cellWidth - 测量值` 给每个字符补正字距,行盒却仍是 cols × cellWidth,
58
99
  * 逐列累积后整行溢出十几到几十像素,被行盒的 overflow:hidden 切掉行尾几个字符。
59
100
  */
60
- .rs-terminal[data-v-67905bea] .xterm {
101
+ .rs-terminal[data-v-fba85f3c] .xterm {
61
102
  width: 100%;
62
103
  height: 100%;
63
104
  line-height: normal;
@@ -68,7 +109,7 @@
68
109
  * xterm 6 官方仍给空的 .xterm-viewport 写 overflow-y:scroll + 背景 #000。
69
110
  * 真正滚动的是 .xterm-scrollable-element;空层滚动条会挡住最后一列。
70
111
  */
71
- .rs-terminal[data-v-67905bea] .xterm .xterm-viewport {
112
+ .rs-terminal[data-v-fba85f3c] .xterm .xterm-viewport {
72
113
  overflow: hidden;
73
114
  background-color: transparent;
74
115
  }
@@ -78,7 +119,7 @@
78
119
  * 的圆角胶囊不一致。轨道宽度由 overviewRuler.width 决定,改它会给每个终端实例多建一块
79
120
  * overview ruler 画布,因此只重绘滑块:热区仍是 14px,靠透明边框内缩出 10px 可见胶囊。
80
121
  */
81
- .rs-terminal[data-v-67905bea] .xterm-scrollable-element > .scrollbar > .slider {
122
+ .rs-terminal[data-v-fba85f3c] .xterm-scrollable-element > .scrollbar > .slider {
82
123
  box-sizing: border-box;
83
124
  border: var(--rs-scrollbar-padding, 2px) solid transparent;
84
125
  border-radius: var(--rs-radius-full);
@@ -86,18 +127,18 @@
86
127
  background-clip: padding-box;
87
128
  transition: background-color var(--rs-transition-fast);
88
129
  }
89
- .rs-terminal[data-v-67905bea] .xterm-scrollable-element > .scrollbar > .slider:hover {
130
+ .rs-terminal[data-v-fba85f3c] .xterm-scrollable-element > .scrollbar > .slider:hover {
90
131
  background-color: color-mix(in srgb, var(--rs-primary) 40%, var(--rs-muted));
91
132
  }
92
- .rs-terminal[data-v-67905bea] .xterm-scrollable-element > .scrollbar > .slider.active {
133
+ .rs-terminal[data-v-fba85f3c] .xterm-scrollable-element > .scrollbar > .slider.active {
93
134
  background-color: color-mix(in srgb, var(--rs-primary) 55%, var(--rs-muted));
94
135
  }
95
- .rs-terminal--zebra[data-v-67905bea] .xterm-rows,
96
- .rs-terminal--zebra[data-v-67905bea] .xterm-rows > div,
97
- .rs-terminal--zebra[data-v-67905bea] .xterm-row {
136
+ .rs-terminal--zebra[data-v-fba85f3c] .xterm-rows,
137
+ .rs-terminal--zebra[data-v-fba85f3c] .xterm-rows > div,
138
+ .rs-terminal--zebra[data-v-fba85f3c] .xterm-row {
98
139
  background-color: transparent !important;
99
140
  }
100
- .rs-terminal--zebra[data-v-67905bea] .xterm-scrollable-element {
141
+ .rs-terminal--zebra[data-v-fba85f3c] .xterm-scrollable-element {
101
142
  background-color: transparent !important;
102
143
  background-image: repeating-linear-gradient(
103
144
  to bottom,
@@ -108,12 +149,12 @@
108
149
  );
109
150
  background-attachment: local;
110
151
  }
111
- .rs-terminal--zebra[data-v-67905bea] .xterm-screen {
152
+ .rs-terminal--zebra[data-v-fba85f3c] .xterm-screen {
112
153
  background-color: transparent !important;
113
154
  }
114
- .rs-terminal[data-v-67905bea] .xterm-fg-257 {
155
+ .rs-terminal[data-v-fba85f3c] .xterm-fg-257 {
115
156
  color: var(--rs-terminal-bg) !important;
116
157
  }
117
- .rs-terminal[data-v-67905bea] .xterm-bg-257 {
158
+ .rs-terminal[data-v-fba85f3c] .xterm-bg-257 {
118
159
  background-color: var(--rs-terminal-fg) !important;
119
160
  }
@@ -42,8 +42,16 @@ type __VLS_Props = {
42
42
  wheelScrollModifier?: RsTerminalWheelScrollModifier;
43
43
  /** TUI 全屏刷新时若视口不在底部,自动滚回底部(修复 top 表头丢失) */
44
44
  snapViewportOnTuiWrite?: boolean;
45
+ /** 鼠标松开时若有选区则复制(接近原生终端 copy-on-select) */
46
+ copyOnSelect?: boolean;
47
+ /** 启用 Ctrl/⌘+F 终端内搜索 */
48
+ searchEnabled?: boolean;
45
49
  };
46
- declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, RsTerminalExpose, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
50
+ declare var __VLS_15: {};
51
+ type __VLS_Slots = {} & {
52
+ overlayAction?: (props: typeof __VLS_15) => any;
53
+ };
54
+ declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, RsTerminalExpose, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
47
55
  data: (data: string) => any;
48
56
  resize: (payload: {
49
57
  cols: number;
@@ -80,6 +88,14 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, RsTermina
80
88
  zebraStripes: boolean;
81
89
  wheelScrollModifier: RsTerminalWheelScrollModifier;
82
90
  snapViewportOnTuiWrite: boolean;
91
+ copyOnSelect: boolean;
92
+ searchEnabled: boolean;
83
93
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
94
+ declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
84
95
  declare const _default: typeof __VLS_export;
85
96
  export default _default;
97
+ type __VLS_WithSlots<T, S> = T & {
98
+ new (): {
99
+ $slots: S;
100
+ };
101
+ };
@@ -5,14 +5,17 @@ import { beginClipboardPrefetch, copyTextToClipboard, readClipboardText } from "
5
5
  import { readTerminalFontFamily, readTerminalFontSizePx, readTerminalFontWeight, readTerminalFontWeightBold } from "../theme/css-token.js";
6
6
  import { containsTuiRefreshSequence, mergeTerminalTheme, prepareTerminalForPtyWrite, resolveTerminalTheme, terminalShortcutLabel } from "./terminal-utils.js";
7
7
  import { attachWheelScrollGuard, isAlternateTerminalBuffer } from "./terminal-wheel.js";
8
- import { computed, createBlock, createCommentVNode, createElementBlock, createElementVNode, defineComponent, mergeProps, nextTick, onBeforeUnmount, onMounted, openBlock, ref, toDisplayString, watch, withCtx } from "vue";
8
+ import { computed, createBlock, createCommentVNode, createElementBlock, createElementVNode, defineComponent, mergeProps, nextTick, normalizeClass, onBeforeUnmount, onMounted, openBlock, ref, renderSlot, toDisplayString, unref, vModelText, watch, withCtx, withDirectives, withModifiers } from "vue";
9
9
  import { FitAddon } from "@xterm/addon-fit";
10
+ import { SearchAddon } from "@xterm/addon-search";
10
11
  import { Terminal } from "@xterm/xterm";
11
12
  import "@xterm/xterm/css/xterm.css";
12
13
  //#region src/components/RsTerminal.vue?vue&type=script&setup=true&lang.ts
13
- var _hoisted_1 = {
14
+ var _hoisted_1 = ["placeholder", "aria-label"];
15
+ var _hoisted_2 = { key: 0 };
16
+ var _hoisted_3 = {
14
17
  key: 1,
15
- class: "rs-terminal__overlay"
18
+ class: "rs-terminal__overlay-action"
16
19
  };
17
20
  /** 单元格高度倍率(xterm options.lineHeight)。中文比 Canvas 测的西文高,1 会裁到下一行。 */
18
21
  var TERMINAL_LINE_HEIGHT = 1.2;
@@ -74,6 +77,14 @@ var RsTerminal_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ defin
74
77
  snapViewportOnTuiWrite: {
75
78
  type: Boolean,
76
79
  default: true
80
+ },
81
+ copyOnSelect: {
82
+ type: Boolean,
83
+ default: false
84
+ },
85
+ searchEnabled: {
86
+ type: Boolean,
87
+ default: false
77
88
  }
78
89
  },
79
90
  emits: [
@@ -149,6 +160,12 @@ var RsTerminal_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ defin
149
160
  shortcut: terminalShortcutLabel("A")
150
161
  }
151
162
  ];
163
+ if (props.searchEnabled) items.push({
164
+ key: "search",
165
+ label: t("terminal.search", "Search"),
166
+ icon: "search",
167
+ shortcut: terminalShortcutLabel("F")
168
+ });
152
169
  if (props.showAskAi) items.push({
153
170
  key: "sep-ai",
154
171
  label: "",
@@ -172,8 +189,12 @@ var RsTerminal_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ defin
172
189
  });
173
190
  return items;
174
191
  });
192
+ const searchOpen = ref(false);
193
+ const searchQuery = ref("");
194
+ const searchInputEl = ref(null);
175
195
  let terminal = null;
176
196
  let fitAddon = null;
197
+ let searchAddon = null;
177
198
  let resizeObserver = null;
178
199
  let themeObserver = null;
179
200
  let detachWheelGuard = null;
@@ -290,6 +311,43 @@ var RsTerminal_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ defin
290
311
  if (live && (!snap || live.length >= snap.length)) return live;
291
312
  return snap;
292
313
  }
314
+ function openSearch() {
315
+ if (!props.searchEnabled) return;
316
+ searchOpen.value = true;
317
+ nextTick(() => {
318
+ searchInputEl.value?.focus();
319
+ searchInputEl.value?.select();
320
+ });
321
+ }
322
+ function closeSearch() {
323
+ searchOpen.value = false;
324
+ searchAddon?.clearDecorations();
325
+ terminal?.focus();
326
+ }
327
+ function runSearch(direction) {
328
+ const query = searchQuery.value;
329
+ if (!query || !searchAddon) return;
330
+ if (direction === "prev") {
331
+ searchAddon.findPrevious(query);
332
+ return;
333
+ }
334
+ searchAddon.findNext(query);
335
+ }
336
+ function onSearchKeydown(event) {
337
+ if (event.key === "Escape") {
338
+ event.preventDefault();
339
+ closeSearch();
340
+ return;
341
+ }
342
+ if (event.key === "Enter") {
343
+ event.preventDefault();
344
+ runSearch(event.shiftKey ? "prev" : "next");
345
+ }
346
+ }
347
+ function onHostMouseUp() {
348
+ if (!props.copyOnSelect || !terminal?.hasSelection()) return;
349
+ copySelection();
350
+ }
293
351
  async function copySelection() {
294
352
  const text = resolveMenuSelectionText();
295
353
  if (!text) return;
@@ -345,6 +403,11 @@ var RsTerminal_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ defin
345
403
  clearTerminal();
346
404
  return;
347
405
  }
406
+ if (action === "search") {
407
+ openSearch();
408
+ emit("action", "search");
409
+ return;
410
+ }
348
411
  if (action === "askAi") {
349
412
  const text = resolveMenuSelectionText();
350
413
  emit("askAi", text);
@@ -384,6 +447,12 @@ var RsTerminal_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ defin
384
447
  clearTerminal();
385
448
  return false;
386
449
  }
450
+ if (key === "f" && props.searchEnabled) {
451
+ event.preventDefault();
452
+ event.stopPropagation();
453
+ openSearch();
454
+ return false;
455
+ }
387
456
  return true;
388
457
  });
389
458
  }
@@ -439,6 +508,8 @@ var RsTerminal_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ defin
439
508
  });
440
509
  fitAddon = new FitAddon();
441
510
  terminal.loadAddon(fitAddon);
511
+ searchAddon = new SearchAddon();
512
+ terminal.loadAddon(searchAddon);
442
513
  terminal.open(hostEl.value);
443
514
  terminal.onData((data) => {
444
515
  if (props.inputEnabled) emit("data", data);
@@ -478,6 +549,7 @@ var RsTerminal_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ defin
478
549
  terminal?.dispose();
479
550
  terminal = null;
480
551
  fitAddon = null;
552
+ searchAddon = null;
481
553
  didInitialFit = false;
482
554
  });
483
555
  __expose({
@@ -505,18 +577,53 @@ var RsTerminal_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ defin
505
577
  onClick: focus,
506
578
  onContextmenuCapture: onTerminalContextMenu
507
579
  }), [
580
+ __props.searchEnabled && searchOpen.value ? (openBlock(), createElementBlock("div", {
581
+ key: 0,
582
+ class: "rs-terminal__search",
583
+ onClick: _cache[3] || (_cache[3] = withModifiers(() => {}, ["stop"]))
584
+ }, [
585
+ withDirectives(createElementVNode("input", {
586
+ ref_key: "searchInputEl",
587
+ ref: searchInputEl,
588
+ "onUpdate:modelValue": _cache[0] || (_cache[0] = ($event) => searchQuery.value = $event),
589
+ class: "rs-terminal__search-input",
590
+ type: "search",
591
+ placeholder: unref(t)("terminal.searchPlaceholder", "Find in terminal"),
592
+ "aria-label": unref(t)("terminal.search", "Search"),
593
+ onKeydown: onSearchKeydown
594
+ }, null, 40, _hoisted_1), [[vModelText, searchQuery.value]]),
595
+ createElementVNode("button", {
596
+ type: "button",
597
+ class: "rs-terminal__search-btn",
598
+ onClick: _cache[1] || (_cache[1] = ($event) => runSearch("prev"))
599
+ }, toDisplayString(unref(t)("terminal.searchPrev", "Previous")), 1),
600
+ createElementVNode("button", {
601
+ type: "button",
602
+ class: "rs-terminal__search-btn",
603
+ onClick: _cache[2] || (_cache[2] = ($event) => runSearch("next"))
604
+ }, toDisplayString(unref(t)("terminal.searchNext", "Next")), 1),
605
+ createElementVNode("button", {
606
+ type: "button",
607
+ class: "rs-terminal__search-btn",
608
+ onClick: closeSearch
609
+ }, toDisplayString(unref(t)("terminal.searchClose", "Close search")), 1)
610
+ ])) : createCommentVNode("", true),
508
611
  createElementVNode("div", {
509
612
  ref_key: "hostEl",
510
613
  ref: hostEl,
511
- class: "rs-terminal__host"
512
- }, null, 512),
614
+ class: "rs-terminal__host",
615
+ onMouseup: onHostMouseUp
616
+ }, null, 544),
513
617
  showLoading.value ? (openBlock(), createBlock(RsLoading_default, {
514
- key: 0,
618
+ key: 1,
515
619
  class: "rs-terminal__loading"
516
620
  })) : createCommentVNode("", true),
517
- __props.overlay ? (openBlock(), createElementBlock("output", _hoisted_1, toDisplayString(__props.overlay), 1)) : createCommentVNode("", true)
621
+ __props.overlay || _ctx.$slots.overlayAction ? (openBlock(), createElementBlock("output", {
622
+ key: 2,
623
+ class: normalizeClass(["rs-terminal__overlay", { "rs-terminal__overlay--interactive": Boolean(_ctx.$slots.overlayAction) }])
624
+ }, [__props.overlay ? (openBlock(), createElementBlock("span", _hoisted_2, toDisplayString(__props.overlay), 1)) : createCommentVNode("", true), _ctx.$slots.overlayAction ? (openBlock(), createElementBlock("div", _hoisted_3, [renderSlot(_ctx.$slots, "overlayAction", {}, void 0, true)])) : createCommentVNode("", true)], 2)) : createCommentVNode("", true)
518
625
  ], 16)]),
519
- _: 1
626
+ _: 3
520
627
  }, 8, ["disabled", "items"]);
521
628
  };
522
629
  }
@@ -3,6 +3,6 @@ import _plugin_vue_export_helper_default from "../_virtual/_plugin-vue_export-he
3
3
  import RsTerminal_vue_vue_type_script_setup_true_lang_default from "./RsTerminal.impl.js";
4
4
 
5
5
  //#region src/components/RsTerminal.vue
6
- var RsTerminal_default = /*#__PURE__*/ _plugin_vue_export_helper_default(RsTerminal_vue_vue_type_script_setup_true_lang_default, [["__scopeId", "data-v-67905bea"]]);
6
+ var RsTerminal_default = /*#__PURE__*/ _plugin_vue_export_helper_default(RsTerminal_vue_vue_type_script_setup_true_lang_default, [["__scopeId", "data-v-fba85f3c"]]);
7
7
  //#endregion
8
8
  export { RsTerminal_default as default };
@@ -1,7 +1,7 @@
1
1
  import type { ITheme, Terminal } from '@xterm/xterm';
2
2
  export type RsTerminalThemeMode = 'auto' | 'light' | 'dark';
3
3
  export type RsResolvedTerminalTheme = 'light' | 'dark';
4
- export type RsTerminalAction = 'copy' | 'paste' | 'selectAll' | 'clear' | 'askAi';
4
+ export type RsTerminalAction = 'copy' | 'paste' | 'selectAll' | 'clear' | 'askAi' | 'search';
5
5
  export type RsTerminalGeometry = {
6
6
  cols: number;
7
7
  rows: number;
@@ -4,5 +4,6 @@
4
4
  * CSS 一并扫描,防止 JS 预构建后样式副作用丢失。
5
5
  */
6
6
  import '@xterm/addon-fit';
7
+ import '@xterm/addon-search';
7
8
  import '@xterm/xterm';
8
9
  import '@xterm/xterm/css/xterm.css';
@@ -1,3 +1,4 @@
1
1
  import "@xterm/addon-fit";
2
+ import "@xterm/addon-search";
2
3
  import "@xterm/xterm";
3
4
  import "@xterm/xterm/css/xterm.css";
@@ -148,6 +148,11 @@ var zhCN = {
148
148
  "terminal.selectAll": "全选",
149
149
  "terminal.clear": "清空终端",
150
150
  "terminal.askAi": "询问 AI",
151
+ "terminal.search": "搜索",
152
+ "terminal.searchPlaceholder": "在终端中查找",
153
+ "terminal.searchNext": "下一个",
154
+ "terminal.searchPrev": "上一个",
155
+ "terminal.searchClose": "关闭搜索",
151
156
  "playground.brand": "Niuma UI",
152
157
  "playground.subtitle": "Vue 3 组件演示",
153
158
  "playground.nav.label": "组件导航",
@@ -351,6 +356,11 @@ var enUS = {
351
356
  "terminal.selectAll": "Select All",
352
357
  "terminal.clear": "Clear Terminal",
353
358
  "terminal.askAi": "Ask AI",
359
+ "terminal.search": "Search",
360
+ "terminal.searchPlaceholder": "Find in terminal",
361
+ "terminal.searchNext": "Next",
362
+ "terminal.searchPrev": "Previous",
363
+ "terminal.searchClose": "Close search",
354
364
  "playground.brand": "Niuma UI",
355
365
  "playground.subtitle": "Vue 3 component demos",
356
366
  "playground.nav.label": "Component navigation",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "niuma-ui",
3
- "version": "1.2.8",
3
+ "version": "1.3.0",
4
4
  "license": "Apache-2.0",
5
5
  "type": "module",
6
6
  "description": "Vue 3 工作台设计系统(Rs* 组件、--rs-* token;含可选编辑器 / 终端)",
@@ -105,6 +105,7 @@
105
105
  "@codemirror/view": "^6.43.1",
106
106
  "@lucide/vue": "^1.31.0",
107
107
  "@xterm/addon-fit": "^0.11.0",
108
+ "@xterm/addon-search": "^0.16.0",
108
109
  "@xterm/xterm": "^6.0.0",
109
110
  "codemirror": "^6.0.2",
110
111
  "dayjs": "^1.11.21",