niuma-ui 1.3.4 → 1.3.7

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,26 @@
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [1.3.7] - 2026-09-15
10
+
11
+ ### 新增
12
+
13
+ - `RsDropdown`:`size`、`contentWidth="fit"`、菜单项 `hint`。工具条动作菜单走公开档位,不要 `:deep` 改菜单内边距。
14
+
15
+ ## [1.3.6] - 2026-09-14
16
+
17
+ ### 新增
18
+
19
+ - `RsSplitPane`:`fill`(默认关)铺满父级并拉高插槽根节点,业务不要 `:deep` 改 `.rs-split__pane`。
20
+ - `RsSplitPane`:`collapsible` 只表示可吸附折叠(拖过阈值 / Enter / `collapse()`)。分隔条不画折叠钮,与 Allotment、react-resizable-panels 一致。
21
+
22
+ ## [1.3.5] - 2026-09-12
23
+
24
+ ### 修复
25
+
26
+ - `RsTerminal`:默认 `minimumContrastRatio=4.5`。浅色主题下 `ls --color` 软链接(黑底叠深色字)不再看不清;传 `1` 可关闭。
27
+ - `RsTerminal`:`fit` 按宿主 `clientWidth/Height` 纠正行列,避免 FitAddon 把 `height: 100%` 算成内容高、PTY 停在默认 80×24。画布仍高出一行再减行。全屏清屏 / 进备用屏才回视口底部。
28
+
9
29
  ## [1.3.4] - 2026-09-11
10
30
 
11
31
  ### 修复
@@ -155,3 +155,38 @@
155
155
  opacity: 0.38;
156
156
  cursor: not-allowed;
157
157
  }
158
+ .rs-dropdown__item-copy {
159
+ display: flex;
160
+ flex-direction: column;
161
+ gap: 1px;
162
+ min-width: 0;
163
+ }
164
+ .rs-dropdown__item-hint {
165
+ font-size: var(--rs-font-size-xs);
166
+ color: var(--rs-muted);
167
+ line-height: var(--rs-line-height-tight);
168
+ white-space: normal;
169
+ display: -webkit-box;
170
+ -webkit-line-clamp: 2;
171
+ -webkit-box-orient: vertical;
172
+ overflow: hidden;
173
+ }
174
+ .rs-dropdown__content--fit {
175
+ min-width: 10.5rem;
176
+ width: max-content;
177
+ }
178
+ .rs-dropdown__content--ssm .rs-dropdown__item {
179
+ min-height: 26px;
180
+ padding: 5px 8px;
181
+ gap: var(--rs-space-xs);
182
+ font-size: var(--rs-font-size-xs);
183
+ align-items: flex-start;
184
+ }
185
+ .rs-dropdown__content--ssm .rs-dropdown__item-icon {
186
+ margin-top: 2px;
187
+ }
188
+ .rs-dropdown__content--sm .rs-dropdown__item {
189
+ min-height: 28px;
190
+ padding: 6px 10px;
191
+ font-size: var(--rs-font-size-sm);
192
+ }
@@ -1,10 +1,15 @@
1
- import { type RsDropdownItems as RsDropdownData } from './dropdown-utils';
1
+ import { type RsDropdownContentWidth, type RsDropdownItems as RsDropdownData } from './dropdown-utils';
2
+ import type { RsComponentSize } from '../theme/types';
2
3
  type __VLS_Props = {
3
4
  items: RsDropdownData;
4
5
  placeholder?: string;
5
6
  disabled?: boolean;
6
7
  /** 选中后是否在触发器回显选项 label;false 时固定显示 placeholder(操作菜单) */
7
8
  showSelected?: boolean;
9
+ /** 菜单项密度,与控件 size 对齐 */
10
+ size?: RsComponentSize;
11
+ /** 菜单宽度;工具条图标按钮用 fit,避免被触发器宽度压扁 */
12
+ contentWidth?: RsDropdownContentWidth;
8
13
  };
9
14
  type __VLS_ModelProps = {
10
15
  modelValue?: string;
@@ -21,8 +26,10 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_PublicProps, {}, {
21
26
  onSelect?: ((value: string) => any) | undefined;
22
27
  "onUpdate:modelValue"?: ((value: string | undefined) => any) | undefined;
23
28
  }>, {
29
+ size: RsComponentSize;
24
30
  disabled: boolean;
25
31
  showSelected: boolean;
32
+ contentWidth: RsDropdownContentWidth;
26
33
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
27
34
  declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
28
35
  declare const _default: typeof __VLS_export;
@@ -18,7 +18,9 @@ var RsDropdown_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ defin
18
18
  showSelected: {
19
19
  type: Boolean,
20
20
  default: true
21
- }
21
+ },
22
+ size: { default: "md" },
23
+ contentWidth: { default: "trigger" }
22
24
  }, {
23
25
  "modelValue": {},
24
26
  "modelModifiers": {}
@@ -69,7 +71,7 @@ var RsDropdown_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ defin
69
71
  _: 1
70
72
  }, 8, ["disabled"])), createVNode(unref(DropdownMenuPortal), null, {
71
73
  default: withCtx(() => [createVNode(unref(DropdownMenuContent), {
72
- class: "rs-dropdown__content",
74
+ class: normalizeClass(["rs-dropdown__content", [`rs-dropdown__content--${__props.size}`, `rs-dropdown__content--${__props.contentWidth}`]]),
73
75
  "side-offset": 4
74
76
  }, {
75
77
  default: withCtx(() => [__props.showSelected ? (openBlock(), createBlock(unref(DropdownMenuRadioGroup), {
@@ -79,16 +81,18 @@ var RsDropdown_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ defin
79
81
  }, {
80
82
  default: withCtx(() => [createVNode(RsDropdownItems_default, {
81
83
  items: __props.items,
84
+ size: __props.size,
82
85
  selectable: ""
83
- }, null, 8, ["items"])]),
86
+ }, null, 8, ["items", "size"])]),
84
87
  _: 1
85
88
  }, 8, ["modelValue"])) : (openBlock(), createBlock(RsDropdownItems_default, {
86
89
  key: 1,
87
90
  items: __props.items,
91
+ size: __props.size,
88
92
  onSelect
89
- }, null, 8, ["items"]))]),
93
+ }, null, 8, ["items", "size"]))]),
90
94
  _: 1
91
- })]),
95
+ }, 8, ["class"])]),
92
96
  _: 1
93
97
  })]),
94
98
  _: 3
@@ -0,0 +1,11 @@
1
+ import type { RsDropdownItem } from './dropdown-utils';
2
+ import { type RsComponentSize } from '../theme/types';
3
+ type __VLS_Props = {
4
+ item: RsDropdownItem;
5
+ size?: RsComponentSize;
6
+ };
7
+ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {
8
+ size: RsComponentSize;
9
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
10
+ declare const _default: typeof __VLS_export;
11
+ export default _default;
@@ -0,0 +1,32 @@
1
+ import RsIcon_default from "./RsIcon.js";
2
+ import { RS_COMPONENT_SIZE_ICON_PX } from "../theme/types.js";
3
+ import { Fragment, computed, createBlock, createCommentVNode, createElementBlock, createElementVNode, defineComponent, openBlock, toDisplayString } from "vue";
4
+ //#region src/components/RsDropdownItemView.vue?vue&type=script&setup=true&lang.ts
5
+ var _hoisted_1 = { class: "rs-dropdown__item-copy" };
6
+ var _hoisted_2 = { class: "rs-dropdown__item-label" };
7
+ var _hoisted_3 = {
8
+ key: 0,
9
+ class: "rs-dropdown__item-hint"
10
+ };
11
+ var RsDropdownItemView_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ defineComponent({
12
+ __name: "RsDropdownItemView",
13
+ props: {
14
+ item: {},
15
+ size: { default: "md" }
16
+ },
17
+ setup(__props) {
18
+ /** 下拉项内容:图标 + 标题 + 可选说明,密度由 size 控制。 */
19
+ const props = __props;
20
+ const iconSize = computed(() => RS_COMPONENT_SIZE_ICON_PX[props.size]);
21
+ return (_ctx, _cache) => {
22
+ return openBlock(), createElementBlock(Fragment, null, [__props.item.icon ? (openBlock(), createBlock(RsIcon_default, {
23
+ key: 0,
24
+ name: __props.item.icon,
25
+ size: iconSize.value,
26
+ class: "rs-dropdown__item-icon"
27
+ }, null, 8, ["name", "size"])) : createCommentVNode("", true), createElementVNode("span", _hoisted_1, [createElementVNode("span", _hoisted_2, toDisplayString(__props.item.label), 1), __props.item.hint ? (openBlock(), createElementBlock("span", _hoisted_3, toDisplayString(__props.item.hint), 1)) : createCommentVNode("", true)])], 64);
28
+ };
29
+ }
30
+ });
31
+ //#endregion
32
+ export { RsDropdownItemView_vue_vue_type_script_setup_true_lang_default as default };
@@ -0,0 +1,5 @@
1
+ import RsDropdownItemView_vue_vue_type_script_setup_true_lang_default from "./RsDropdownItemView.impl.js";
2
+ //#region src/components/RsDropdownItemView.vue
3
+ var RsDropdownItemView_default = RsDropdownItemView_vue_vue_type_script_setup_true_lang_default;
4
+ //#endregion
5
+ export { RsDropdownItemView_default as default };
@@ -1,14 +1,17 @@
1
1
  import { type RsDropdownItems } from './dropdown-utils';
2
+ import type { RsComponentSize } from '../theme/types';
2
3
  type __VLS_Props = {
3
4
  items: RsDropdownItems;
4
5
  /** true:单选互斥;false:操作项 */
5
6
  selectable?: boolean;
7
+ size?: RsComponentSize;
6
8
  };
7
9
  declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
8
10
  select: (value: string) => any;
9
11
  }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
10
12
  onSelect?: ((value: string) => any) | undefined;
11
13
  }>, {
14
+ size: RsComponentSize;
12
15
  selectable: boolean;
13
16
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
14
17
  declare const _default: typeof __VLS_export;
@@ -1,12 +1,8 @@
1
- import RsIcon_default from "./RsIcon.js";
2
1
  import { DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuRadioItem } from "./reka.js";
3
2
  import { isDropdownItemGroup } from "./dropdown-utils.js";
4
- import { Fragment, createBlock, createCommentVNode, createElementBlock, createElementVNode, createTextVNode, createVNode, defineComponent, openBlock, renderList, toDisplayString, unref, withCtx } from "vue";
3
+ import RsDropdownItemView_default from "./RsDropdownItemView.js";
4
+ import { Fragment, createBlock, createElementBlock, createTextVNode, createVNode, defineComponent, openBlock, renderList, toDisplayString, unref, withCtx } from "vue";
5
5
  //#region src/components/RsDropdownItems.vue?vue&type=script&setup=true&lang.ts
6
- var _hoisted_1 = { class: "rs-dropdown__item-label" };
7
- var _hoisted_2 = { class: "rs-dropdown__item-label" };
8
- var _hoisted_3 = { class: "rs-dropdown__item-label" };
9
- var _hoisted_4 = { class: "rs-dropdown__item-label" };
10
6
  var RsDropdownItems_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ defineComponent({
11
7
  __name: "RsDropdownItems",
12
8
  props: {
@@ -14,7 +10,8 @@ var RsDropdownItems_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/
14
10
  selectable: {
15
11
  type: Boolean,
16
12
  default: false
17
- }
13
+ },
14
+ size: { default: "md" }
18
15
  },
19
16
  emits: ["select"],
20
17
  setup(__props, { emit: __emit }) {
@@ -26,7 +23,7 @@ var RsDropdownItems_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/
26
23
  return (_ctx, _cache) => {
27
24
  return openBlock(true), createElementBlock(Fragment, null, renderList(__props.items, (entry, index) => {
28
25
  return openBlock(), createElementBlock(Fragment, { key: index }, [unref(isDropdownItemGroup)(entry) ? (openBlock(), createBlock(unref(DropdownMenuGroup), {
29
- key: 0,
26
+ key: `group-${index}`,
30
27
  class: "rs-dropdown__group"
31
28
  }, {
32
29
  default: withCtx(() => [createVNode(unref(DropdownMenuLabel), { class: "rs-dropdown__group-label" }, {
@@ -39,12 +36,10 @@ var RsDropdownItems_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/
39
36
  disabled: item.disabled,
40
37
  class: "rs-dropdown__item"
41
38
  }, {
42
- default: withCtx(() => [item.icon ? (openBlock(), createBlock(RsIcon_default, {
43
- key: 0,
44
- name: item.icon,
45
- size: 16,
46
- class: "rs-dropdown__item-icon"
47
- }, null, 8, ["name"])) : createCommentVNode("", true), createElementVNode("span", _hoisted_1, toDisplayString(item.label), 1)]),
39
+ default: withCtx(() => [createVNode(RsDropdownItemView_default, {
40
+ item,
41
+ size: __props.size
42
+ }, null, 8, ["item", "size"])]),
48
43
  _: 2
49
44
  }, 1032, ["value", "disabled"]);
50
45
  }), 128)) : (openBlock(true), createElementBlock(Fragment, { key: 1 }, renderList(entry.options, (item) => {
@@ -54,41 +49,35 @@ var RsDropdownItems_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/
54
49
  class: "rs-dropdown__item",
55
50
  onSelect: ($event) => onActionSelect(item)
56
51
  }, {
57
- default: withCtx(() => [item.icon ? (openBlock(), createBlock(RsIcon_default, {
58
- key: 0,
59
- name: item.icon,
60
- size: 16,
61
- class: "rs-dropdown__item-icon"
62
- }, null, 8, ["name"])) : createCommentVNode("", true), createElementVNode("span", _hoisted_2, toDisplayString(item.label), 1)]),
52
+ default: withCtx(() => [createVNode(RsDropdownItemView_default, {
53
+ item,
54
+ size: __props.size
55
+ }, null, 8, ["item", "size"])]),
63
56
  _: 2
64
57
  }, 1032, ["disabled", "onSelect"]);
65
58
  }), 128))]),
66
59
  _: 2
67
60
  }, 1024)) : __props.selectable ? (openBlock(), createBlock(unref(DropdownMenuRadioItem), {
68
- key: entry.value,
61
+ key: `radio-${entry.value}`,
69
62
  value: entry.value,
70
63
  disabled: entry.disabled,
71
64
  class: "rs-dropdown__item"
72
65
  }, {
73
- default: withCtx(() => [entry.icon ? (openBlock(), createBlock(RsIcon_default, {
74
- key: 0,
75
- name: entry.icon,
76
- size: 16,
77
- class: "rs-dropdown__item-icon"
78
- }, null, 8, ["name"])) : createCommentVNode("", true), createElementVNode("span", _hoisted_3, toDisplayString(entry.label), 1)]),
66
+ default: withCtx(() => [createVNode(RsDropdownItemView_default, {
67
+ item: entry,
68
+ size: __props.size
69
+ }, null, 8, ["item", "size"])]),
79
70
  _: 2
80
71
  }, 1032, ["value", "disabled"])) : (openBlock(), createBlock(unref(DropdownMenuItem), {
81
- key: entry.value,
72
+ key: `action-${entry.value}`,
82
73
  disabled: entry.disabled,
83
74
  class: "rs-dropdown__item",
84
75
  onSelect: ($event) => onActionSelect(entry)
85
76
  }, {
86
- default: withCtx(() => [entry.icon ? (openBlock(), createBlock(RsIcon_default, {
87
- key: 0,
88
- name: entry.icon,
89
- size: 16,
90
- class: "rs-dropdown__item-icon"
91
- }, null, 8, ["name"])) : createCommentVNode("", true), createElementVNode("span", _hoisted_4, toDisplayString(entry.label), 1)]),
77
+ default: withCtx(() => [createVNode(RsDropdownItemView_default, {
78
+ item: entry,
79
+ size: __props.size
80
+ }, null, 8, ["item", "size"])]),
92
81
  _: 2
93
82
  }, 1032, ["disabled", "onSelect"]))], 64);
94
83
  }), 128);
@@ -22,6 +22,18 @@
22
22
  min-height: 0;
23
23
  overflow: hidden;
24
24
  }
25
+
26
+ /* fill:面板当弹性列,插槽根节点吃满。业务改自己的内容,不要 :deep 本组件。 */
27
+ .rs-split--fill > .rs-split__pane:not(.rs-split__pane--auto) {
28
+ display: flex;
29
+ flex-direction: column;
30
+ }
31
+ .rs-split--fill > .rs-split__pane:not(.rs-split__pane--auto) > * {
32
+ flex: 1 1 auto;
33
+ min-width: 0;
34
+ min-height: 0;
35
+ width: 100%;
36
+ }
25
37
  .rs-split__pane--auto {
26
38
  /* 内容撑开;超出 max 时在面板内滚动 */
27
39
  overflow: auto;
@@ -10,6 +10,11 @@ type __VLS_Props = {
10
10
  keyboardStep?: number;
11
11
  /** 在分隔条中央展示抓手(grip)指示 */
12
12
  withHandle?: boolean;
13
+ /**
14
+ * 面板铺满父级,并把插槽根节点拉高。
15
+ * 默认关,不改变原有内容尺寸;工作台 / 设置页显式打开,避免 :deep。
16
+ */
17
+ fill?: boolean;
13
18
  };
14
19
  type __VLS_ModelProps = {
15
20
  /** 各面板尺寸(百分比,总和 100) */
@@ -36,6 +41,7 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_PublicProps, RsSpl
36
41
  onCollapse?: ((key: string) => any) | undefined;
37
42
  "onUpdate:sizes"?: ((value: number[]) => any) | undefined;
38
43
  }>, {
44
+ fill: boolean;
39
45
  disabled: boolean;
40
46
  orientation: RsSplitOrientation;
41
47
  keyboardStep: number;
@@ -40,6 +40,10 @@ var RsSplitPane_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ defi
40
40
  withHandle: {
41
41
  type: Boolean,
42
42
  default: false
43
+ },
44
+ fill: {
45
+ type: Boolean,
46
+ default: false
43
47
  }
44
48
  }, {
45
49
  "sizes": { default: () => [] },
@@ -369,7 +373,10 @@ var RsSplitPane_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ defi
369
373
  return openBlock(), createElementBlock("div", {
370
374
  ref_key: "rootRef",
371
375
  ref: rootRef,
372
- class: normalizeClass(["rs-split", [`rs-split--${__props.orientation}`, { "rs-split--disabled": __props.disabled }]])
376
+ class: normalizeClass(["rs-split", [`rs-split--${__props.orientation}`, {
377
+ "rs-split--disabled": __props.disabled,
378
+ "rs-split--fill": __props.fill
379
+ }]])
373
380
  }, [(openBlock(true), createElementBlock(Fragment, null, renderList(__props.panes, (pane, index) => {
374
381
  return openBlock(), createElementBlock(Fragment, { key: pane.key }, [createElementVNode("div", {
375
382
  class: normalizeClass(["rs-split__pane", {
@@ -1,5 +1,5 @@
1
1
 
2
- .rs-terminal[data-v-fba85f3c] {
2
+ .rs-terminal[data-v-db06bdc1] {
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-fba85f3c] {
19
+ [data-rs-theme='light'] .rs-terminal[data-v-db06bdc1] {
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-fba85f3c] {
25
+ .rs-terminal__host[data-v-db06bdc1] {
26
26
  position: relative;
27
27
  width: 100%;
28
28
  height: 100%;
@@ -30,15 +30,15 @@
30
30
  /* FitAddon 取整余量露底时,与单元格同色(勿用 shell 灰底) */
31
31
  background: var(--rs-terminal-bg);
32
32
  }
33
- .rs-terminal__loading[data-v-fba85f3c],
34
- .rs-terminal__overlay[data-v-fba85f3c] {
33
+ .rs-terminal__loading[data-v-db06bdc1],
34
+ .rs-terminal__overlay[data-v-db06bdc1] {
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-fba85f3c] {
41
+ .rs-terminal__overlay[data-v-db06bdc1] {
42
42
  flex-direction: column;
43
43
  gap: var(--rs-space-sm);
44
44
  padding: var(--rs-space-lg);
@@ -51,13 +51,13 @@
51
51
  font-weight: var(--rs-font-weight-regular);
52
52
  line-height: var(--rs-line-height-normal);
53
53
  }
54
- .rs-terminal__overlay--interactive[data-v-fba85f3c] {
54
+ .rs-terminal__overlay--interactive[data-v-db06bdc1] {
55
55
  pointer-events: auto;
56
56
  }
57
- .rs-terminal__overlay-action[data-v-fba85f3c] {
57
+ .rs-terminal__overlay-action[data-v-db06bdc1] {
58
58
  pointer-events: auto;
59
59
  }
60
- .rs-terminal__search[data-v-fba85f3c] {
60
+ .rs-terminal__search[data-v-db06bdc1] {
61
61
  position: absolute;
62
62
  top: var(--rs-space-xs);
63
63
  right: var(--rs-space-xs);
@@ -70,7 +70,7 @@
70
70
  border-radius: var(--rs-radius-sm);
71
71
  background: color-mix(in srgb, var(--rs-terminal-bg) 92%, transparent);
72
72
  }
73
- .rs-terminal__search-input[data-v-fba85f3c] {
73
+ .rs-terminal__search-input[data-v-db06bdc1] {
74
74
  width: 12rem;
75
75
  padding: 0.2rem 0.4rem;
76
76
  border: 1px solid var(--rs-terminal-border);
@@ -81,7 +81,7 @@
81
81
  font-size: var(--rs-font-size-xs);
82
82
  outline: none;
83
83
  }
84
- .rs-terminal__search-btn[data-v-fba85f3c] {
84
+ .rs-terminal__search-btn[data-v-db06bdc1] {
85
85
  padding: 0.2rem 0.4rem;
86
86
  border: 1px solid var(--rs-terminal-border);
87
87
  border-radius: var(--rs-radius-sm);
@@ -98,7 +98,7 @@
98
98
  * DOM 渲染器于是按 `cellWidth - 测量值` 给每个字符补正字距,行盒却仍是 cols × cellWidth,
99
99
  * 逐列累积后整行溢出十几到几十像素,被行盒的 overflow:hidden 切掉行尾几个字符。
100
100
  */
101
- .rs-terminal[data-v-fba85f3c] .xterm {
101
+ .rs-terminal[data-v-db06bdc1] .xterm {
102
102
  width: 100%;
103
103
  height: 100%;
104
104
  line-height: normal;
@@ -109,7 +109,7 @@
109
109
  * xterm 6 官方仍给空的 .xterm-viewport 写 overflow-y:scroll + 背景 #000。
110
110
  * 真正滚动的是 .xterm-scrollable-element;空层滚动条会挡住最后一列。
111
111
  */
112
- .rs-terminal[data-v-fba85f3c] .xterm .xterm-viewport {
112
+ .rs-terminal[data-v-db06bdc1] .xterm .xterm-viewport {
113
113
  overflow: hidden;
114
114
  background-color: transparent;
115
115
  }
@@ -119,7 +119,7 @@
119
119
  * 的圆角胶囊不一致。轨道宽度由 overviewRuler.width 决定,改它会给每个终端实例多建一块
120
120
  * overview ruler 画布,因此只重绘滑块:热区仍是 14px,靠透明边框内缩出 10px 可见胶囊。
121
121
  */
122
- .rs-terminal[data-v-fba85f3c] .xterm-scrollable-element > .scrollbar > .slider {
122
+ .rs-terminal[data-v-db06bdc1] .xterm-scrollable-element > .scrollbar > .slider {
123
123
  box-sizing: border-box;
124
124
  border: var(--rs-scrollbar-padding, 2px) solid transparent;
125
125
  border-radius: var(--rs-radius-full);
@@ -127,18 +127,18 @@
127
127
  background-clip: padding-box;
128
128
  transition: background-color var(--rs-transition-fast);
129
129
  }
130
- .rs-terminal[data-v-fba85f3c] .xterm-scrollable-element > .scrollbar > .slider:hover {
130
+ .rs-terminal[data-v-db06bdc1] .xterm-scrollable-element > .scrollbar > .slider:hover {
131
131
  background-color: color-mix(in srgb, var(--rs-primary) 40%, var(--rs-muted));
132
132
  }
133
- .rs-terminal[data-v-fba85f3c] .xterm-scrollable-element > .scrollbar > .slider.active {
133
+ .rs-terminal[data-v-db06bdc1] .xterm-scrollable-element > .scrollbar > .slider.active {
134
134
  background-color: color-mix(in srgb, var(--rs-primary) 55%, var(--rs-muted));
135
135
  }
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 {
136
+ .rs-terminal--zebra[data-v-db06bdc1] .xterm-rows,
137
+ .rs-terminal--zebra[data-v-db06bdc1] .xterm-rows > div,
138
+ .rs-terminal--zebra[data-v-db06bdc1] .xterm-row {
139
139
  background-color: transparent !important;
140
140
  }
141
- .rs-terminal--zebra[data-v-fba85f3c] .xterm-scrollable-element {
141
+ .rs-terminal--zebra[data-v-db06bdc1] .xterm-scrollable-element {
142
142
  background-color: transparent !important;
143
143
  background-image: repeating-linear-gradient(
144
144
  to bottom,
@@ -149,12 +149,12 @@
149
149
  );
150
150
  background-attachment: local;
151
151
  }
152
- .rs-terminal--zebra[data-v-fba85f3c] .xterm-screen {
152
+ .rs-terminal--zebra[data-v-db06bdc1] .xterm-screen {
153
153
  background-color: transparent !important;
154
154
  }
155
- .rs-terminal[data-v-fba85f3c] .xterm-fg-257 {
155
+ .rs-terminal[data-v-db06bdc1] .xterm-fg-257 {
156
156
  color: var(--rs-terminal-bg) !important;
157
157
  }
158
- .rs-terminal[data-v-fba85f3c] .xterm-bg-257 {
158
+ .rs-terminal[data-v-db06bdc1] .xterm-bg-257 {
159
159
  background-color: var(--rs-terminal-fg) !important;
160
160
  }
@@ -46,6 +46,12 @@ type __VLS_Props = {
46
46
  copyOnSelect?: boolean;
47
47
  /** 启用 Ctrl/⌘+F 终端内搜索 */
48
48
  searchEnabled?: boolean;
49
+ /**
50
+ * 单元格前景相对背景的最低对比度(xterm `minimumContrastRatio`)。
51
+ * `ls --color` 软链接常用黑底;浅色主题把青/蓝调深后会看不清,4.5 对齐 WCAG AA。
52
+ * 传 1 关闭。
53
+ */
54
+ minimumContrastRatio?: number;
49
55
  };
50
56
  declare var __VLS_15: {};
51
57
  type __VLS_Slots = {} & {
@@ -90,6 +96,7 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, RsTerminalE
90
96
  snapViewportOnTuiWrite: boolean;
91
97
  copyOnSelect: boolean;
92
98
  searchEnabled: boolean;
99
+ minimumContrastRatio: number;
93
100
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
94
101
  declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
95
102
  declare const _default: typeof __VLS_export;
@@ -3,7 +3,7 @@ import RsLoading_default from "./RsLoading.js";
3
3
  import RsContextMenu_default from "./RsContextMenu.js";
4
4
  import { beginClipboardPrefetch, copyTextToClipboard, readClipboardText } from "../utils/rs-clipboard.js";
5
5
  import { readTerminalFontFamily, readTerminalFontSizePx, readTerminalFontWeight, readTerminalFontWeightBold } from "../theme/css-token.js";
6
- import { containsTuiRefreshSequence, mergeTerminalTheme, prepareTerminalForPtyWrite, resolveTerminalTheme, terminalShortcutLabel } from "./terminal-utils.js";
6
+ import { mergeTerminalTheme, needsPtyWriteViewportPrep, prepareTerminalForPtyWrite, proposeTerminalGeometry, readXtermCssCellSize, resolveTerminalTheme, terminalShortcutLabel } from "./terminal-utils.js";
7
7
  import { attachWheelScrollGuard, isAlternateTerminalBuffer } from "./terminal-wheel.js";
8
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";
@@ -85,7 +85,8 @@ var RsTerminal_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ defin
85
85
  searchEnabled: {
86
86
  type: Boolean,
87
87
  default: false
88
- }
88
+ },
89
+ minimumContrastRatio: { default: 4.5 }
89
90
  },
90
91
  emits: [
91
92
  "ready",
@@ -268,14 +269,37 @@ var RsTerminal_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ defin
268
269
  if (!fitAddon || !terminal) return;
269
270
  try {
270
271
  fitAddon.fit();
271
- } catch {
272
- return;
273
- }
272
+ } catch {}
274
273
  didInitialFit = true;
275
- emitResizeIfChanged();
274
+ applyHostBoxGeometry();
275
+ trimOverflowRow();
276
276
  syncZebraRowStepFromDom();
277
+ emitResizeIfChanged();
277
278
  syncResolvedFontSize();
278
279
  }
280
+ /** 以 host client 盒为准,纠正 FitAddon 把 100% 高度 parseInt 成内容高(默认 24 行)的情况。 */
281
+ function applyHostBoxGeometry() {
282
+ if (!terminal || !hostEl.value) return;
283
+ const cell = readXtermCssCellSize(terminal);
284
+ if (!cell) return;
285
+ const proposed = proposeTerminalGeometry(hostEl.value.clientWidth, hostEl.value.clientHeight, cell.width, cell.height);
286
+ if (!proposed) return;
287
+ const cols = Math.max(proposed.cols, 20);
288
+ const rows = Math.max(proposed.rows, 5);
289
+ if (cols !== terminal.cols || rows !== terminal.rows) terminal.resize(cols, rows);
290
+ }
291
+ /** 画布比宿主高出超过一行时再减行,避免裁掉最后一行。 */
292
+ function trimOverflowRow() {
293
+ if (!terminal || !hostEl.value) return;
294
+ const screen = hostEl.value.querySelector(".xterm-screen");
295
+ if (!screen) return;
296
+ const overflow = screen.offsetHeight - hostEl.value.clientHeight;
297
+ if (overflow <= 1) return;
298
+ const cell = readXtermCssCellSize(terminal);
299
+ const cut = cell ? Math.max(1, Math.ceil(overflow / cell.height)) : 1;
300
+ const nextRows = Math.max(5, terminal.rows - cut);
301
+ if (nextRows < terminal.rows) terminal.resize(terminal.cols, nextRows);
302
+ }
279
303
  function scheduleFit() {
280
304
  if (fitRaf) return;
281
305
  fitRaf = requestAnimationFrame(() => {
@@ -286,7 +310,7 @@ var RsTerminal_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ defin
286
310
  function write(data) {
287
311
  const term = terminal;
288
312
  if (!term) return;
289
- if (props.snapViewportOnTuiWrite && containsTuiRefreshSequence(data)) prepareTerminalForPtyWrite(term, data);
313
+ if (props.snapViewportOnTuiWrite && needsPtyWriteViewportPrep(data, term)) prepareTerminalForPtyWrite(term, data);
290
314
  term.write(data);
291
315
  }
292
316
  function clear() {
@@ -488,6 +512,9 @@ var RsTerminal_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ defin
488
512
  });
489
513
  watch(() => [props.themeMode, props.theme], refreshResolvedTheme, { deep: true });
490
514
  watch(() => props.wheelScrollModifier, attachWheelGuard);
515
+ watch(() => props.minimumContrastRatio, (value) => {
516
+ if (terminal) terminal.options.minimumContrastRatio = value;
517
+ });
491
518
  onMounted(async () => {
492
519
  if (!hostEl.value) return;
493
520
  refreshResolvedTheme();
@@ -501,6 +528,7 @@ var RsTerminal_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ defin
501
528
  letterSpacing: 0,
502
529
  allowTransparency: resolveAllowTransparency(),
503
530
  drawBoldTextInBrightColors: true,
531
+ minimumContrastRatio: props.minimumContrastRatio,
504
532
  scrollback: props.scrollback,
505
533
  convertEol: props.convertEol,
506
534
  rightClickSelectsWord: props.rightClickSelectsWord,
@@ -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-fba85f3c"]]);
6
+ var RsTerminal_default = /*#__PURE__*/ _plugin_vue_export_helper_default(RsTerminal_vue_vue_type_script_setup_true_lang_default, [["__scopeId", "data-v-db06bdc1"]]);
7
7
  //#endregion
8
8
  export { RsTerminal_default as default };
@@ -2,6 +2,8 @@ export interface RsDropdownItem {
2
2
  label: string;
3
3
  value: string;
4
4
  icon?: string;
5
+ /** 次要说明,工具条动作菜单等场景使用 */
6
+ hint?: string;
5
7
  disabled?: boolean;
6
8
  }
7
9
  export interface RsDropdownItemGroup {
@@ -9,5 +11,7 @@ export interface RsDropdownItemGroup {
9
11
  options: RsDropdownItem[];
10
12
  }
11
13
  export type RsDropdownItems = RsDropdownItem[] | RsDropdownItemGroup[];
14
+ /** 菜单宽度:trigger 对齐触发器;fit 随内容,不跟窄图标按钮 */
15
+ export type RsDropdownContentWidth = 'trigger' | 'fit';
12
16
  export declare function isDropdownItemGroup(item: RsDropdownItem | RsDropdownItemGroup): item is RsDropdownItemGroup;
13
17
  export declare function flattenDropdownItems(items: RsDropdownItems): RsDropdownItem[];
@@ -40,6 +40,27 @@ export declare function readTerminalRowStripeFromCss(mode: RsResolvedTerminalThe
40
40
  export declare function containsEscapeSequence(data: string): boolean;
41
41
  /** 检测 ncurses/vim/top 等全屏刷新常用的光标定位与清屏序列 */
42
42
  export declare function containsTuiRefreshSequence(data: string): boolean;
43
+ /**
44
+ * 按宿主像素高度与实测行高计算能完整放下的行数。
45
+ * FitAddon 用理论 cellHeight,亚像素取整后行盒往往更高,多报的行会被 overflow 裁掉
46
+ *(top 按 1 展开几十上百核时,最后几行 CPU 就像被高度挡住)。
47
+ */
48
+ export declare function computeFittedTerminalRows(hostHeight: number, rowHeight: number, minRows?: number): number | null;
49
+ /** 读取 xterm 当前 CSS 单元格尺寸(与 FitAddon 同一套私有 dimensions)。 */
50
+ export declare function readXtermCssCellSize(terminal: Terminal): {
51
+ width: number;
52
+ height: number;
53
+ } | null;
54
+ /**
55
+ * 用宿主 client 宽高算行列,避开 FitAddon 对 computed `height: 100%` 走 parseInt 的偏差。
56
+ * 偏差时 PTY 会停在默认 80×24,`top` 按 1 就只画二十来行 CPU。
57
+ */
58
+ export declare function proposeTerminalGeometry(hostWidth: number, hostHeight: number, cellWidth: number, cellHeight: number, scrollbarWidth?: number): {
59
+ cols: number;
60
+ rows: number;
61
+ } | null;
62
+ /** 进入备用屏(smcup),top/vim 全屏时常走这条 */
63
+ export declare function containsAltScreenEnter(data: string): boolean;
43
64
  /** 全屏清屏(top/vim 新一轮绘制常见) */
44
65
  export declare function containsFullScreenClear(data: string): boolean;
45
66
  /** 数据块以光标归位首行开头(top 每轮刷新常见) */
@@ -50,6 +71,11 @@ export declare function startsWithCursorHome(data: string): boolean;
50
71
  */
51
72
  export declare function resetLocalTerminalModes(terminal: Terminal): void;
52
73
  export declare function snapTerminalViewportToBottom(terminal: Terminal): void;
74
+ /**
75
+ * 是否要在写入前校正视口。
76
+ * 只认清屏 / 进备用屏 / 已在备用屏且视口不在底部;普通 `\x1b[H` 不碰,以免把用户翻的 scrollback 拽回去。
77
+ */
78
+ export declare function needsPtyWriteViewportPrep(data: string, terminal: Terminal): boolean;
53
79
  /** 处理 PTY 写入前的视口/模式准备(不移动写入光标,避免输入错位) */
54
80
  export declare function prepareTerminalForPtyWrite(terminal: Terminal, data: string): void;
55
81
  export declare function isMacPlatform(): boolean;
@@ -145,6 +145,32 @@ function containsTuiRefreshSequence(data) {
145
145
  return TUI_REFRESH_PATTERN.test(data);
146
146
  }
147
147
  var FULL_SCREEN_CLEAR_PATTERN = /\x1b\[[0-9;]*2J|\x1b\[[0-9;]*3J/;
148
+ var ALT_SCREEN_ENTER_PATTERN = /\x1b\[\?1049h|\x1b\[\?47h|\x1b\[1049h/;
149
+ var DEFAULT_TERMINAL_SCROLLBAR_PX = 14;
150
+ /** 读取 xterm 当前 CSS 单元格尺寸(与 FitAddon 同一套私有 dimensions)。 */
151
+ function readXtermCssCellSize(terminal) {
152
+ const cell = terminal._core?._renderService?.dimensions?.css?.cell;
153
+ if (!cell || cell.width < 1 || cell.height < 1) return null;
154
+ return {
155
+ width: cell.width,
156
+ height: cell.height
157
+ };
158
+ }
159
+ /**
160
+ * 用宿主 client 宽高算行列,避开 FitAddon 对 computed `height: 100%` 走 parseInt 的偏差。
161
+ * 偏差时 PTY 会停在默认 80×24,`top` 按 1 就只画二十来行 CPU。
162
+ */
163
+ function proposeTerminalGeometry(hostWidth, hostHeight, cellWidth, cellHeight, scrollbarWidth = DEFAULT_TERMINAL_SCROLLBAR_PX) {
164
+ if (hostWidth <= 0 || hostHeight <= 0 || cellWidth < 1 || cellHeight < 1) return null;
165
+ return {
166
+ cols: Math.max(2, Math.floor((hostWidth - scrollbarWidth) / cellWidth)),
167
+ rows: Math.max(1, Math.floor(hostHeight / cellHeight))
168
+ };
169
+ }
170
+ /** 进入备用屏(smcup),top/vim 全屏时常走这条 */
171
+ function containsAltScreenEnter(data) {
172
+ return ALT_SCREEN_ENTER_PATTERN.test(data);
173
+ }
148
174
  /** 全屏清屏(top/vim 新一轮绘制常见) */
149
175
  function containsFullScreenClear(data) {
150
176
  return FULL_SCREEN_CLEAR_PATTERN.test(data);
@@ -159,10 +185,24 @@ function resetLocalTerminalModes(terminal) {
159
185
  function snapTerminalViewportToBottom(terminal) {
160
186
  terminal.scrollToBottom();
161
187
  }
188
+ /**
189
+ * 是否要在写入前校正视口。
190
+ * 只认清屏 / 进备用屏 / 已在备用屏且视口不在底部;普通 `\x1b[H` 不碰,以免把用户翻的 scrollback 拽回去。
191
+ */
192
+ function needsPtyWriteViewportPrep(data, terminal) {
193
+ if (containsFullScreenClear(data) || containsAltScreenEnter(data)) return true;
194
+ if (terminal.buffer.active.type !== "alternate") return false;
195
+ return containsTuiRefreshSequence(data) && terminal.buffer.active.viewportY < terminal.buffer.active.baseY;
196
+ }
162
197
  /** 处理 PTY 写入前的视口/模式准备(不移动写入光标,避免输入错位) */
163
198
  function prepareTerminalForPtyWrite(terminal, data) {
164
199
  if (containsFullScreenClear(data)) {
165
200
  resetLocalTerminalModes(terminal);
201
+ snapTerminalViewportToBottom(terminal);
202
+ return;
203
+ }
204
+ if (containsAltScreenEnter(data)) {
205
+ snapTerminalViewportToBottom(terminal);
166
206
  return;
167
207
  }
168
208
  const buffer = terminal.buffer.active;
@@ -203,8 +243,13 @@ function buildAnsiColorDemo() {
203
243
  "\x1B[1mStyles\x1B[0m \x1B[1mbold\x1B[0m \x1B[3mitalic\x1B[0m \x1B[4munderline\x1B[0m \x1B[9mstrikethrough\x1B[0m",
204
244
  "",
205
245
  "\x1B[38;5;196m256-color\x1B[0m sample:",
206
- gradient
246
+ gradient,
247
+ "",
248
+ "\x1B[1mls --color symlink\x1B[0m",
249
+ ` 23 Apr 1 2024 \x1b[01;36mbusiness-logs -> /wmsdata/business-logs/\x1b[0m`,
250
+ ` 23 Apr 1 2024 \x1b[01;36;40mbusiness-logs -> /wmsdata/business-logs/\x1b[0m`,
251
+ ` 23 Apr 1 2024 \x1b[40;31;01mbusiness-logs -> /wmsdata/business-logs/\x1b[0m`
207
252
  ].join("\r\n")}\r\n`;
208
253
  }
209
254
  //#endregion
210
- export { buildAnsiColorDemo, containsEscapeSequence, containsFullScreenClear, containsTuiRefreshSequence, getTerminalThemePalette, isMacPlatform, mergeTerminalTheme, normalizeTerminalHexColor, prepareTerminalForPtyWrite, readTerminalThemeFromCss, resetLocalTerminalModes, resolveCssColorForXterm, resolveTerminalTheme, snapTerminalViewportToBottom, terminalShortcutLabel };
255
+ export { buildAnsiColorDemo, containsAltScreenEnter, containsEscapeSequence, containsFullScreenClear, containsTuiRefreshSequence, getTerminalThemePalette, isMacPlatform, mergeTerminalTheme, needsPtyWriteViewportPrep, normalizeTerminalHexColor, prepareTerminalForPtyWrite, proposeTerminalGeometry, readTerminalThemeFromCss, readXtermCssCellSize, resetLocalTerminalModes, resolveCssColorForXterm, resolveTerminalTheme, snapTerminalViewportToBottom, terminalShortcutLabel };
package/dist/index.d.ts CHANGED
@@ -109,7 +109,7 @@ export type { RsContainerBreakpoint, RsContainerGap, RsContainerMaxWidth, RsCont
109
109
  export type { RsBreadcrumbItem } from './components/RsBreadcrumb.js';
110
110
  export type { RsToolbarBorder, RsToolbarSize } from './components/RsToolbar.js';
111
111
  export type { RsContextMenuItem } from './components/context-menu-utils';
112
- export type { RsDropdownItem, RsDropdownItemGroup, RsDropdownItems } from './components/dropdown-utils';
112
+ export type { RsDropdownContentWidth, RsDropdownItem, RsDropdownItemGroup, RsDropdownItems } from './components/dropdown-utils';
113
113
  export type { RsMenuItem, RsMenuItemGroup, RsMenuItems } from './components/menu-utils';
114
114
  export type { RsScrollbarOrientation, RsScrollbarType } from './components/scrollbar-utils';
115
115
  export type { RsSelectFieldNames, RsSelectFilterOption, RsSelectFilterSort, RsSelectGetPopupContainer, RsSelectLabeledValue, RsSelectModelValue, RsSelectResolvedModel, RsSelectOption, RsSelectOptionFilterProp, RsSelectOptionGroup, RsSelectOptionInput, RsSelectOptions, RsSelectOptionsInput, RsSelectPlacement, RsSelectStatus, RsSelectMaxTagCount, RsSelectVariant, RsSelectValue, } from './components/select-utils';
@@ -144,6 +144,8 @@ var zhCN = {
144
144
  "tree.drag": "拖拽排序",
145
145
  "split.label": "面板分割",
146
146
  "split.resize": "拖动调整面板尺寸",
147
+ "split.collapse": "收起面板",
148
+ "split.expand": "展开面板",
147
149
  "drawer.resize": "拖动调整抽屉尺寸",
148
150
  "terminal.copy": "复制",
149
151
  "terminal.paste": "粘贴",
@@ -374,6 +376,8 @@ var enUS = {
374
376
  "tree.drag": "Drag to reorder",
375
377
  "split.label": "Split panes",
376
378
  "split.resize": "Drag to resize panes",
379
+ "split.collapse": "Collapse pane",
380
+ "split.expand": "Expand pane",
377
381
  "drawer.resize": "Drag to resize the drawer",
378
382
  "terminal.copy": "Copy",
379
383
  "terminal.paste": "Paste",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "niuma-ui",
3
- "version": "1.3.4",
3
+ "version": "1.3.7",
4
4
  "license": "Apache-2.0",
5
5
  "type": "module",
6
6
  "description": "Vue 3 工作台设计系统(Rs* 组件、--rs-* token;含可选编辑器 / 终端)",