niuma-ui 1.3.8 → 1.3.9
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 +16 -0
- package/NOTICE +12 -9
- package/README.en.md +52 -34
- package/README.md +55 -97
- package/dist/components/RsAnchor.css +65 -0
- package/dist/components/RsAnchor.d.ts +41 -0
- package/dist/components/RsAnchor.impl.js +188 -0
- package/dist/components/RsAnchor.js +8 -0
- package/dist/components/RsTerminal.css +23 -23
- package/dist/components/RsTerminal.d.ts +9 -0
- package/dist/components/RsTerminal.impl.js +21 -2
- package/dist/components/RsTerminal.js +1 -1
- package/dist/components/anchor-utils.d.ts +26 -0
- package/dist/components/anchor-utils.js +63 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +4 -0
- package/dist/locale/messages.js +40 -38
- package/package.json +7 -3
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import { useRsI18n } from "../composables/useRsI18n.js";
|
|
2
|
+
import { computeScrollTopForTarget, flattenAnchorItems, hrefToAnchorId, pickActiveAnchorHref, resolveAnchorContainer, scrollContainerTo, targetTopInContainer } from "./anchor-utils.js";
|
|
3
|
+
import { Fragment, computed, createCommentVNode, createElementBlock, createElementVNode, defineComponent, mergeModels, nextTick, normalizeClass, normalizeStyle, onMounted, onUnmounted, openBlock, ref, renderList, toDisplayString, unref, useModel, watch } from "vue";
|
|
4
|
+
//#region src/components/RsAnchor.vue?vue&type=script&setup=true&lang.ts
|
|
5
|
+
var _hoisted_1 = ["aria-label"];
|
|
6
|
+
var _hoisted_2 = {
|
|
7
|
+
key: 0,
|
|
8
|
+
class: "rs-anchor__rail",
|
|
9
|
+
"aria-hidden": "true"
|
|
10
|
+
};
|
|
11
|
+
var _hoisted_3 = { class: "rs-anchor__list" };
|
|
12
|
+
var _hoisted_4 = [
|
|
13
|
+
"href",
|
|
14
|
+
"data-rs-anchor-href",
|
|
15
|
+
"onClick"
|
|
16
|
+
];
|
|
17
|
+
var RsAnchor_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ defineComponent({
|
|
18
|
+
name: "RsAnchor",
|
|
19
|
+
__name: "RsAnchor",
|
|
20
|
+
props: /*@__PURE__*/ mergeModels({
|
|
21
|
+
items: {},
|
|
22
|
+
offset: { default: 12 },
|
|
23
|
+
targetOffset: {},
|
|
24
|
+
bounds: { default: 8 },
|
|
25
|
+
affix: {
|
|
26
|
+
type: Boolean,
|
|
27
|
+
default: true
|
|
28
|
+
},
|
|
29
|
+
lineless: {
|
|
30
|
+
type: Boolean,
|
|
31
|
+
default: false
|
|
32
|
+
},
|
|
33
|
+
changeHash: {
|
|
34
|
+
type: Boolean,
|
|
35
|
+
default: true
|
|
36
|
+
},
|
|
37
|
+
getContainer: {}
|
|
38
|
+
}, {
|
|
39
|
+
"modelValue": { default: "" },
|
|
40
|
+
"modelModifiers": {}
|
|
41
|
+
}),
|
|
42
|
+
emits: /*@__PURE__*/ mergeModels(["change", "click"], ["update:modelValue"]),
|
|
43
|
+
setup(__props, { emit: __emit }) {
|
|
44
|
+
const props = __props;
|
|
45
|
+
const activeHref = useModel(__props, "modelValue");
|
|
46
|
+
const emit = __emit;
|
|
47
|
+
const { t } = useRsI18n();
|
|
48
|
+
const rootRef = ref(null);
|
|
49
|
+
const inkStyle = ref({});
|
|
50
|
+
const flatItems = computed(() => flattenAnchorItems(props.items));
|
|
51
|
+
const resolvedTargetOffset = computed(() => props.targetOffset ?? props.offset);
|
|
52
|
+
let scrollTarget = null;
|
|
53
|
+
let ticking = false;
|
|
54
|
+
function findTarget(href) {
|
|
55
|
+
const id = hrefToAnchorId(href);
|
|
56
|
+
if (!id || typeof document === "undefined") return null;
|
|
57
|
+
return document.getElementById(id);
|
|
58
|
+
}
|
|
59
|
+
function findLinkByHref(root, href) {
|
|
60
|
+
const links = root.querySelectorAll("[data-rs-anchor-href]");
|
|
61
|
+
for (const link of links) if (link.dataset.rsAnchorHref === href) return link;
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
function collectTops(container) {
|
|
65
|
+
return flatItems.value.map((item) => {
|
|
66
|
+
const el = findTarget(item.href);
|
|
67
|
+
if (!el) return null;
|
|
68
|
+
return {
|
|
69
|
+
href: item.href,
|
|
70
|
+
top: targetTopInContainer(el, container)
|
|
71
|
+
};
|
|
72
|
+
}).filter((entry) => Boolean(entry));
|
|
73
|
+
}
|
|
74
|
+
function setActive(href) {
|
|
75
|
+
if (!href || href === activeHref.value) return;
|
|
76
|
+
activeHref.value = href;
|
|
77
|
+
emit("change", href);
|
|
78
|
+
}
|
|
79
|
+
function syncInk() {
|
|
80
|
+
const root = rootRef.value;
|
|
81
|
+
if (!root || props.lineless) return;
|
|
82
|
+
const current = activeHref.value || flatItems.value[0]?.href;
|
|
83
|
+
if (!current) {
|
|
84
|
+
inkStyle.value = { opacity: "0" };
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
const link = findLinkByHref(root, current);
|
|
88
|
+
if (!link) {
|
|
89
|
+
inkStyle.value = { opacity: "0" };
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
inkStyle.value = {
|
|
93
|
+
opacity: "1",
|
|
94
|
+
transform: `translateY(${link.offsetTop}px)`,
|
|
95
|
+
height: `${link.offsetHeight}px`
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
function syncFromScroll() {
|
|
99
|
+
if (!scrollTarget) return;
|
|
100
|
+
const next = pickActiveAnchorHref(collectTops(scrollTarget), props.offset + props.bounds);
|
|
101
|
+
if (next) setActive(next);
|
|
102
|
+
nextTick(syncInk);
|
|
103
|
+
}
|
|
104
|
+
function onScroll() {
|
|
105
|
+
if (ticking) return;
|
|
106
|
+
ticking = true;
|
|
107
|
+
requestAnimationFrame(() => {
|
|
108
|
+
ticking = false;
|
|
109
|
+
syncFromScroll();
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
function bindContainer() {
|
|
113
|
+
unbindContainer();
|
|
114
|
+
try {
|
|
115
|
+
scrollTarget = resolveAnchorContainer(props.getContainer);
|
|
116
|
+
} catch {
|
|
117
|
+
scrollTarget = null;
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
scrollTarget.addEventListener("scroll", onScroll, { passive: true });
|
|
121
|
+
syncFromScroll();
|
|
122
|
+
}
|
|
123
|
+
function unbindContainer() {
|
|
124
|
+
scrollTarget?.removeEventListener("scroll", onScroll);
|
|
125
|
+
scrollTarget = null;
|
|
126
|
+
}
|
|
127
|
+
function onSelect(href, event) {
|
|
128
|
+
event.preventDefault();
|
|
129
|
+
emit("click", href, event);
|
|
130
|
+
const target = findTarget(href);
|
|
131
|
+
const container = scrollTarget ?? resolveAnchorContainer(props.getContainer);
|
|
132
|
+
if (target) scrollContainerTo(container, computeScrollTopForTarget(target, container, resolvedTargetOffset.value));
|
|
133
|
+
setActive(href);
|
|
134
|
+
if (props.changeHash && typeof history !== "undefined") {
|
|
135
|
+
const id = hrefToAnchorId(href);
|
|
136
|
+
const url = new URL(window.location.href);
|
|
137
|
+
url.hash = id;
|
|
138
|
+
history.replaceState(history.state, "", `${url.pathname}${url.search}#${id}`);
|
|
139
|
+
}
|
|
140
|
+
nextTick(syncInk);
|
|
141
|
+
}
|
|
142
|
+
onMounted(() => {
|
|
143
|
+
bindContainer();
|
|
144
|
+
window.addEventListener("resize", onScroll, { passive: true });
|
|
145
|
+
});
|
|
146
|
+
onUnmounted(() => {
|
|
147
|
+
unbindContainer();
|
|
148
|
+
window.removeEventListener("resize", onScroll);
|
|
149
|
+
});
|
|
150
|
+
watch(() => [
|
|
151
|
+
props.items,
|
|
152
|
+
props.getContainer,
|
|
153
|
+
props.offset,
|
|
154
|
+
props.bounds
|
|
155
|
+
], () => {
|
|
156
|
+
nextTick(bindContainer);
|
|
157
|
+
}, { deep: true });
|
|
158
|
+
watch(activeHref, () => {
|
|
159
|
+
nextTick(syncInk);
|
|
160
|
+
});
|
|
161
|
+
return (_ctx, _cache) => {
|
|
162
|
+
return flatItems.value.length ? (openBlock(), createElementBlock("nav", {
|
|
163
|
+
key: 0,
|
|
164
|
+
ref_key: "rootRef",
|
|
165
|
+
ref: rootRef,
|
|
166
|
+
class: normalizeClass(["rs-anchor", {
|
|
167
|
+
"rs-anchor--affix": __props.affix,
|
|
168
|
+
"rs-anchor--lineless": __props.lineless
|
|
169
|
+
}]),
|
|
170
|
+
"aria-label": unref(t)("anchor.label")
|
|
171
|
+
}, [!__props.lineless ? (openBlock(), createElementBlock("div", _hoisted_2, [createElementVNode("span", {
|
|
172
|
+
class: "rs-anchor__ink",
|
|
173
|
+
style: normalizeStyle(inkStyle.value)
|
|
174
|
+
}, null, 4)])) : createCommentVNode("", true), createElementVNode("div", _hoisted_3, [(openBlock(true), createElementBlock(Fragment, null, renderList(flatItems.value, (item) => {
|
|
175
|
+
return openBlock(), createElementBlock("a", {
|
|
176
|
+
key: item.href,
|
|
177
|
+
class: normalizeClass(["rs-anchor__link", { "rs-anchor__link--active": item.href === activeHref.value }]),
|
|
178
|
+
style: normalizeStyle({ paddingInlineStart: `${.7 + item.depth * .75}rem` }),
|
|
179
|
+
href: item.href,
|
|
180
|
+
"data-rs-anchor-href": item.href,
|
|
181
|
+
onClick: ($event) => onSelect(item.href, $event)
|
|
182
|
+
}, toDisplayString(item.title), 15, _hoisted_4);
|
|
183
|
+
}), 128))])], 10, _hoisted_1)) : createCommentVNode("", true);
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
});
|
|
187
|
+
//#endregion
|
|
188
|
+
export { RsAnchor_vue_vue_type_script_setup_true_lang_default as default };
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import './RsAnchor.css'
|
|
2
|
+
import _plugin_vue_export_helper_default from "../_virtual/_plugin-vue_export-helper.js";
|
|
3
|
+
import RsAnchor_vue_vue_type_script_setup_true_lang_default from "./RsAnchor.impl.js";
|
|
4
|
+
|
|
5
|
+
//#region src/components/RsAnchor.vue
|
|
6
|
+
var RsAnchor_default = /*#__PURE__*/ _plugin_vue_export_helper_default(RsAnchor_vue_vue_type_script_setup_true_lang_default, [["__scopeId", "data-v-d8c4280b"]]);
|
|
7
|
+
//#endregion
|
|
8
|
+
export { RsAnchor_default as default };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
.rs-terminal[data-v-
|
|
2
|
+
.rs-terminal[data-v-27605f60] {
|
|
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-
|
|
19
|
+
[data-rs-theme='light'] .rs-terminal[data-v-27605f60] {
|
|
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-
|
|
25
|
+
.rs-terminal__host[data-v-27605f60] {
|
|
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-
|
|
34
|
-
.rs-terminal__overlay[data-v-
|
|
33
|
+
.rs-terminal__loading[data-v-27605f60],
|
|
34
|
+
.rs-terminal__overlay[data-v-27605f60] {
|
|
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-
|
|
41
|
+
.rs-terminal__overlay[data-v-27605f60] {
|
|
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-
|
|
54
|
+
.rs-terminal__overlay--interactive[data-v-27605f60] {
|
|
55
55
|
pointer-events: auto;
|
|
56
56
|
}
|
|
57
|
-
.rs-terminal__overlay-action[data-v-
|
|
57
|
+
.rs-terminal__overlay-action[data-v-27605f60] {
|
|
58
58
|
pointer-events: auto;
|
|
59
59
|
}
|
|
60
|
-
.rs-terminal__search[data-v-
|
|
60
|
+
.rs-terminal__search[data-v-27605f60] {
|
|
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-
|
|
73
|
+
.rs-terminal__search-input[data-v-27605f60] {
|
|
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-
|
|
84
|
+
.rs-terminal__search-btn[data-v-27605f60] {
|
|
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-
|
|
101
|
+
.rs-terminal[data-v-27605f60] .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-
|
|
112
|
+
.rs-terminal[data-v-27605f60] .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-
|
|
122
|
+
.rs-terminal[data-v-27605f60] .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-
|
|
130
|
+
.rs-terminal[data-v-27605f60] .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-
|
|
133
|
+
.rs-terminal[data-v-27605f60] .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-
|
|
137
|
-
.rs-terminal--zebra[data-v-
|
|
138
|
-
.rs-terminal--zebra[data-v-
|
|
136
|
+
.rs-terminal--zebra[data-v-27605f60] .xterm-rows,
|
|
137
|
+
.rs-terminal--zebra[data-v-27605f60] .xterm-rows > div,
|
|
138
|
+
.rs-terminal--zebra[data-v-27605f60] .xterm-row {
|
|
139
139
|
background-color: transparent !important;
|
|
140
140
|
}
|
|
141
|
-
.rs-terminal--zebra[data-v-
|
|
141
|
+
.rs-terminal--zebra[data-v-27605f60] .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-
|
|
152
|
+
.rs-terminal--zebra[data-v-27605f60] .xterm-screen {
|
|
153
153
|
background-color: transparent !important;
|
|
154
154
|
}
|
|
155
|
-
.rs-terminal[data-v-
|
|
155
|
+
.rs-terminal[data-v-27605f60] .xterm-fg-257 {
|
|
156
156
|
color: var(--rs-terminal-bg) !important;
|
|
157
157
|
}
|
|
158
|
-
.rs-terminal[data-v-
|
|
158
|
+
.rs-terminal[data-v-27605f60] .xterm-bg-257 {
|
|
159
159
|
background-color: var(--rs-terminal-fg) !important;
|
|
160
160
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { ITheme } from '@xterm/xterm';
|
|
2
2
|
import '@xterm/xterm/css/xterm.css';
|
|
3
|
+
import type { RsContextMenuItem } from './context-menu-utils';
|
|
3
4
|
import { type RsTerminalAction, type RsTerminalExpose, type RsTerminalThemeMode } from './terminal-utils';
|
|
4
5
|
import { type RsTerminalWheelScrollModifier } from './terminal-wheel';
|
|
5
6
|
type RsTerminalFontWeight = 'normal' | 'bold' | '100' | '200' | '300' | '400' | '500' | '600' | '700' | '800' | '900';
|
|
@@ -24,6 +25,11 @@ type __VLS_Props = {
|
|
|
24
25
|
contextMenu?: boolean;
|
|
25
26
|
/** 右键菜单是否展示「询问 AI」(仅 emit,业务侧自行处理) */
|
|
26
27
|
showAskAi?: boolean;
|
|
28
|
+
/**
|
|
29
|
+
* 追加到内置右键菜单(复制/粘贴等)与「清空」之间。
|
|
30
|
+
* `key` 勿与 copy / paste / selectAll / search / askAi / clear 冲突。
|
|
31
|
+
*/
|
|
32
|
+
extraContextMenuItems?: RsContextMenuItem[];
|
|
27
33
|
/**
|
|
28
34
|
* 右键是否自动选中光标下单词。
|
|
29
35
|
* SSH/vim/less 等 TUI 场景建议 false,避免冲掉用户已拖选的大段文本。
|
|
@@ -67,6 +73,7 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, RsTerminalE
|
|
|
67
73
|
ready: () => any;
|
|
68
74
|
askAi: (text: string) => any;
|
|
69
75
|
action: (action: RsTerminalAction) => any;
|
|
76
|
+
extraSelect: (key: string) => any;
|
|
70
77
|
}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
|
|
71
78
|
onData?: ((data: string) => any) | undefined;
|
|
72
79
|
onResize?: ((payload: {
|
|
@@ -77,6 +84,7 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, RsTerminalE
|
|
|
77
84
|
onReady?: (() => any) | undefined;
|
|
78
85
|
onAskAi?: ((text: string) => any) | undefined;
|
|
79
86
|
onAction?: ((action: RsTerminalAction) => any) | undefined;
|
|
87
|
+
onExtraSelect?: ((key: string) => any) | undefined;
|
|
80
88
|
}>, {
|
|
81
89
|
theme: Partial<ITheme>;
|
|
82
90
|
loading: boolean;
|
|
@@ -88,6 +96,7 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, RsTerminalE
|
|
|
88
96
|
allowTransparency: boolean;
|
|
89
97
|
themeMode: RsTerminalThemeMode;
|
|
90
98
|
showAskAi: boolean;
|
|
99
|
+
extraContextMenuItems: RsContextMenuItem[];
|
|
91
100
|
rightClickSelectsWord: boolean;
|
|
92
101
|
scrollback: number;
|
|
93
102
|
convertEol: boolean;
|
|
@@ -56,6 +56,7 @@ var RsTerminal_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ defin
|
|
|
56
56
|
type: Boolean,
|
|
57
57
|
default: false
|
|
58
58
|
},
|
|
59
|
+
extraContextMenuItems: { default: () => [] },
|
|
59
60
|
rightClickSelectsWord: {
|
|
60
61
|
type: Boolean,
|
|
61
62
|
default: true
|
|
@@ -94,7 +95,8 @@ var RsTerminal_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ defin
|
|
|
94
95
|
"resize",
|
|
95
96
|
"action",
|
|
96
97
|
"askAi",
|
|
97
|
-
"selectionChange"
|
|
98
|
+
"selectionChange",
|
|
99
|
+
"extraSelect"
|
|
98
100
|
],
|
|
99
101
|
setup(__props, { expose: __expose, emit: __emit }) {
|
|
100
102
|
const props = __props;
|
|
@@ -177,6 +179,11 @@ var RsTerminal_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ defin
|
|
|
177
179
|
icon: "bot",
|
|
178
180
|
disabled: !hasSelection.value && !menuSelectionSnapshot.value
|
|
179
181
|
});
|
|
182
|
+
if (props.extraContextMenuItems?.length) items.push({
|
|
183
|
+
key: "sep-extra",
|
|
184
|
+
label: "",
|
|
185
|
+
separator: true
|
|
186
|
+
}, ...props.extraContextMenuItems);
|
|
180
187
|
items.push({
|
|
181
188
|
key: "sep-1",
|
|
182
189
|
label: "",
|
|
@@ -438,8 +445,20 @@ var RsTerminal_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ defin
|
|
|
438
445
|
emit("action", "askAi");
|
|
439
446
|
}
|
|
440
447
|
}
|
|
448
|
+
const BUILTIN_TERMINAL_ACTIONS = /* @__PURE__ */ new Set([
|
|
449
|
+
"copy",
|
|
450
|
+
"paste",
|
|
451
|
+
"selectAll",
|
|
452
|
+
"clear",
|
|
453
|
+
"askAi",
|
|
454
|
+
"search"
|
|
455
|
+
]);
|
|
441
456
|
function onContextMenuSelect(key) {
|
|
442
|
-
|
|
457
|
+
if (BUILTIN_TERMINAL_ACTIONS.has(key)) {
|
|
458
|
+
runTerminalAction(key);
|
|
459
|
+
return;
|
|
460
|
+
}
|
|
461
|
+
emit("extraSelect", key);
|
|
443
462
|
}
|
|
444
463
|
function attachShortcuts() {
|
|
445
464
|
if (!terminal) return;
|
|
@@ -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-
|
|
6
|
+
var RsTerminal_default = /*#__PURE__*/ _plugin_vue_export_helper_default(RsTerminal_vue_vue_type_script_setup_true_lang_default, [["__scopeId", "data-v-27605f60"]]);
|
|
7
7
|
//#endregion
|
|
8
8
|
export { RsTerminal_default as default };
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export interface RsAnchorItem {
|
|
2
|
+
href: string;
|
|
3
|
+
title: string;
|
|
4
|
+
children?: RsAnchorItem[];
|
|
5
|
+
}
|
|
6
|
+
export interface RsAnchorFlatItem {
|
|
7
|
+
href: string;
|
|
8
|
+
title: string;
|
|
9
|
+
depth: number;
|
|
10
|
+
}
|
|
11
|
+
/** `#overview` / `path#overview` → `overview` */
|
|
12
|
+
export declare function hrefToAnchorId(href: string): string;
|
|
13
|
+
export declare function flattenAnchorItems(items: RsAnchorItem[], depth?: number): RsAnchorFlatItem[];
|
|
14
|
+
/**
|
|
15
|
+
* 滚动监听:取最后一个 top <= offset 的标题。
|
|
16
|
+
* entries 须按文档顺序(与 DOM 一致),top 为相对滚动容器顶部的距离。
|
|
17
|
+
*/
|
|
18
|
+
export declare function pickActiveAnchorHref(entries: ReadonlyArray<{
|
|
19
|
+
href: string;
|
|
20
|
+
top: number;
|
|
21
|
+
}>, offset: number): string;
|
|
22
|
+
export declare function resolveAnchorContainer(getContainer?: () => HTMLElement | Window | null | undefined): HTMLElement | Window;
|
|
23
|
+
export declare function readContainerScrollTop(container: HTMLElement | Window): number;
|
|
24
|
+
export declare function targetTopInContainer(target: HTMLElement, container: HTMLElement | Window): number;
|
|
25
|
+
export declare function scrollContainerTo(container: HTMLElement | Window, top: number): void;
|
|
26
|
+
export declare function computeScrollTopForTarget(target: HTMLElement, container: HTMLElement | Window, targetOffset: number): number;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
//#region src/components/anchor-utils.ts
|
|
2
|
+
/** `#overview` / `path#overview` → `overview` */
|
|
3
|
+
function hrefToAnchorId(href) {
|
|
4
|
+
const raw = href.trim();
|
|
5
|
+
return (raw.includes("#") ? raw.slice(raw.indexOf("#") + 1) : raw).replace(/^#/, "");
|
|
6
|
+
}
|
|
7
|
+
function flattenAnchorItems(items, depth = 0) {
|
|
8
|
+
const out = [];
|
|
9
|
+
for (const item of items) {
|
|
10
|
+
out.push({
|
|
11
|
+
href: item.href,
|
|
12
|
+
title: item.title,
|
|
13
|
+
depth
|
|
14
|
+
});
|
|
15
|
+
if (item.children?.length) out.push(...flattenAnchorItems(item.children, depth + 1));
|
|
16
|
+
}
|
|
17
|
+
return out;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* 滚动监听:取最后一个 top <= offset 的标题。
|
|
21
|
+
* entries 须按文档顺序(与 DOM 一致),top 为相对滚动容器顶部的距离。
|
|
22
|
+
*/
|
|
23
|
+
function pickActiveAnchorHref(entries, offset) {
|
|
24
|
+
if (entries.length === 0) return "";
|
|
25
|
+
let active = entries[0]?.href ?? "";
|
|
26
|
+
for (const entry of entries) if (entry.top <= offset) active = entry.href;
|
|
27
|
+
return active;
|
|
28
|
+
}
|
|
29
|
+
function resolveAnchorContainer(getContainer) {
|
|
30
|
+
const resolved = getContainer?.();
|
|
31
|
+
if (resolved) return resolved;
|
|
32
|
+
if (typeof window !== "undefined") return window;
|
|
33
|
+
throw new Error("RsAnchor: no scroll container");
|
|
34
|
+
}
|
|
35
|
+
function readContainerScrollTop(container) {
|
|
36
|
+
if (container instanceof Window) return container.scrollY;
|
|
37
|
+
return container.scrollTop;
|
|
38
|
+
}
|
|
39
|
+
function targetTopInContainer(target, container) {
|
|
40
|
+
const targetRect = target.getBoundingClientRect();
|
|
41
|
+
if (container instanceof Window) return targetRect.top;
|
|
42
|
+
return targetRect.top - container.getBoundingClientRect().top;
|
|
43
|
+
}
|
|
44
|
+
function scrollContainerTo(container, top) {
|
|
45
|
+
if (container instanceof Window) {
|
|
46
|
+
container.scrollTo({
|
|
47
|
+
top,
|
|
48
|
+
behavior: "smooth"
|
|
49
|
+
});
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
container.scrollTo({
|
|
53
|
+
top,
|
|
54
|
+
behavior: "smooth"
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
function computeScrollTopForTarget(target, container, targetOffset) {
|
|
58
|
+
const current = readContainerScrollTop(container);
|
|
59
|
+
const relativeTop = targetTopInContainer(target, container);
|
|
60
|
+
return Math.max(0, current + relativeTop - targetOffset);
|
|
61
|
+
}
|
|
62
|
+
//#endregion
|
|
63
|
+
export { computeScrollTopForTarget, flattenAnchorItems, hrefToAnchorId, pickActiveAnchorHref, readContainerScrollTop, resolveAnchorContainer, scrollContainerTo, targetTopInContainer };
|
package/dist/index.d.ts
CHANGED
|
@@ -2,6 +2,9 @@ export { default as RsConfigProvider } from './components/RsConfigProvider.js';
|
|
|
2
2
|
export { default as RsBadge } from './components/RsBadge.js';
|
|
3
3
|
export { default as RsContainer } from './components/RsContainer.js';
|
|
4
4
|
export { default as RsBreadcrumb } from './components/RsBreadcrumb.js';
|
|
5
|
+
export { default as RsAnchor } from './components/RsAnchor.js';
|
|
6
|
+
export type { RsAnchorFlatItem, RsAnchorItem } from './components/anchor-utils';
|
|
7
|
+
export { flattenAnchorItems, hrefToAnchorId, pickActiveAnchorHref, } from './components/anchor-utils';
|
|
5
8
|
export { default as RsToolbar } from './components/RsToolbar.js';
|
|
6
9
|
export { default as RsButton } from './components/RsButton.js';
|
|
7
10
|
export type { RsButtonTone, RsButtonVariant } from './components/button-utils';
|
package/dist/index.js
CHANGED
|
@@ -49,6 +49,7 @@ export { RS_TIME_SECONDS_FORMAT } from './lib/rs-dayjs.js'
|
|
|
49
49
|
export { RS_TOAST_DEFAULT_GAP } from './components/overlay-utils.js'
|
|
50
50
|
export { RS_TOAST_DEFAULT_POSITION } from './components/overlay-utils.js'
|
|
51
51
|
export { default as RsAlert } from './components/RsAlert.js'
|
|
52
|
+
export { default as RsAnchor } from './components/RsAnchor.js'
|
|
52
53
|
export { default as RsAutoComplete } from './components/RsAutoComplete.js'
|
|
53
54
|
export { default as RsAvatar } from './components/RsAvatar.js'
|
|
54
55
|
export { default as RsBadge } from './components/RsBadge.js'
|
|
@@ -191,6 +192,7 @@ export { filterTableRows } from './components/table-utils.js'
|
|
|
191
192
|
export { filterTableTreeRows } from './components/table-utils.js'
|
|
192
193
|
export { filterTreeNodes } from './components/tree-utils.js'
|
|
193
194
|
export { fixedCellStyle } from './components/table-utils.js'
|
|
195
|
+
export { flattenAnchorItems } from './components/anchor-utils.js'
|
|
194
196
|
export { flattenTreeNodeIds } from './components/tree-utils.js'
|
|
195
197
|
export { flattenVisibleCountRough } from './composables/useRsTableVirtual.js'
|
|
196
198
|
export { flattenVisibleTableTreeEntries } from './components/table-utils.js'
|
|
@@ -235,6 +237,7 @@ export { hasStableTableTreeRowKey } from './components/table-utils.js'
|
|
|
235
237
|
export { hasTableSummaryConfig } from './components/table/table-summary-utils.js'
|
|
236
238
|
export { hasTableTreeChildren } from './components/table-utils.js'
|
|
237
239
|
export { hasTreeChildren } from './components/tree-utils.js'
|
|
240
|
+
export { hrefToAnchorId } from './components/anchor-utils.js'
|
|
238
241
|
export { inferLogLevel } from './components/log-utils.js'
|
|
239
242
|
export { injectExpandRows } from './components/table-utils.js'
|
|
240
243
|
export { inputRuleMessageKeys } from './components/input-rules.js'
|
|
@@ -300,6 +303,7 @@ export { parseLocalDateTimeToUtcIso } from './lib/iso-local-datetime.js'
|
|
|
300
303
|
export { parseNumberInput } from './components/input-number-utils.js'
|
|
301
304
|
export { parseRsLogLevel } from './components/log-utils.js'
|
|
302
305
|
export { parseTimeValue } from './components/time-picker-utils.js'
|
|
306
|
+
export { pickActiveAnchorHref } from './components/anchor-utils.js'
|
|
303
307
|
export { placeAnchoredPopup } from './components/overlay-utils.js'
|
|
304
308
|
export { prefetchClipboardText } from './utils/rs-clipboard.js'
|
|
305
309
|
export { prewarmCodeMirrorEditor } from './components/code-mirror-lang.js'
|