vue3-router-tab 1.1.8 → 1.1.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/lib/components/RouterTab.vue +106 -13
- package/lib/scss/index.scss +153 -158
- package/lib/scss/variables.scss +47 -0
- package/package.json +1 -1
|
@@ -12,22 +12,28 @@
|
|
|
12
12
|
v-bind="tabTransitionProps"
|
|
13
13
|
>
|
|
14
14
|
<li
|
|
15
|
-
v-for="tab in tabs"
|
|
15
|
+
v-for="(tab, index) in tabs"
|
|
16
16
|
:key="tab.id"
|
|
17
17
|
:class="buildTabClass(tab)"
|
|
18
|
-
:data-title="
|
|
18
|
+
:data-title="getTabTitle(tab)"
|
|
19
|
+
:draggable="sortable"
|
|
19
20
|
@click="activate(tab)"
|
|
20
21
|
@auxclick.middle.prevent="close(tab)"
|
|
21
22
|
@contextmenu.prevent="showContextMenu(tab, $event)"
|
|
23
|
+
@dragstart="onDragStart(tab, index, $event)"
|
|
24
|
+
@dragover="onDragOver(index, $event)"
|
|
25
|
+
@dragenter="onDragEnter(index)"
|
|
26
|
+
@dragleave="onDragLeave"
|
|
27
|
+
@drop="onDrop(index, $event)"
|
|
28
|
+
@dragend="onDragEnd"
|
|
22
29
|
>
|
|
23
|
-
<span class="router-tab__item-title" :title="
|
|
30
|
+
<span class="router-tab__item-title" :title="getTabTitle(tab)">
|
|
24
31
|
<i v-if="tab.icon" :class="['router-tab__item-icon', tab.icon]" />
|
|
25
|
-
{{
|
|
32
|
+
{{ getTabTitle(tab) }}
|
|
26
33
|
</span>
|
|
27
34
|
<a
|
|
28
35
|
v-if="isClosable(tab)"
|
|
29
36
|
class="router-tab__item-close"
|
|
30
|
-
type="button"
|
|
31
37
|
@click.stop="close(tab)"
|
|
32
38
|
/>
|
|
33
39
|
</li>
|
|
@@ -125,7 +131,6 @@ import { getTransOpt } from '../util/index'
|
|
|
125
131
|
import { routerTabsKey, routerTabsCookie } from '../constants'
|
|
126
132
|
import { useRouterTabsPersistence } from '../persistence'
|
|
127
133
|
|
|
128
|
-
|
|
129
134
|
interface ResolvedMenuItem {
|
|
130
135
|
id: string
|
|
131
136
|
label: string
|
|
@@ -180,9 +185,18 @@ export default defineComponent({
|
|
|
180
185
|
persistence: {
|
|
181
186
|
type: Object as PropType<RouterTabsPersistenceOptions | null>,
|
|
182
187
|
default: null
|
|
188
|
+
},
|
|
189
|
+
sortable: {
|
|
190
|
+
type: Boolean,
|
|
191
|
+
default: true
|
|
192
|
+
},
|
|
193
|
+
titleResolver: {
|
|
194
|
+
type: Function as PropType<(tab: TabRecord) => string>,
|
|
195
|
+
default: null
|
|
183
196
|
}
|
|
184
197
|
},
|
|
185
|
-
|
|
198
|
+
emits: ['tab-sort', 'tab-sorted'],
|
|
199
|
+
setup(props, { emit }) {
|
|
186
200
|
const instance = getCurrentInstance()
|
|
187
201
|
if (!instance) {
|
|
188
202
|
throw new Error('[RouterTab] component must be used within a Vue application context.')
|
|
@@ -199,7 +213,7 @@ export default defineComponent({
|
|
|
199
213
|
maxAlive: props.maxAlive,
|
|
200
214
|
keepLastTab: props.keepLastTab,
|
|
201
215
|
appendPosition: props.append,
|
|
202
|
-
defaultRoute: props.defaultPage
|
|
216
|
+
defaultRoute: props.defaultPage,
|
|
203
217
|
})
|
|
204
218
|
|
|
205
219
|
provide(routerTabsKey, controller)
|
|
@@ -228,6 +242,14 @@ export default defineComponent({
|
|
|
228
242
|
position: { x: 0, y: 0 }
|
|
229
243
|
})
|
|
230
244
|
|
|
245
|
+
// Drag and drop state
|
|
246
|
+
const dragState = reactive({
|
|
247
|
+
dragging: false,
|
|
248
|
+
dragIndex: -1,
|
|
249
|
+
dropIndex: -1,
|
|
250
|
+
dragTab: null as TabRecord | null
|
|
251
|
+
})
|
|
252
|
+
|
|
231
253
|
type MenuConfig = RouterTabsMenuConfig
|
|
232
254
|
type MenuActionContext = RouterTabsMenuContext
|
|
233
255
|
type CustomMenuOption = RouterTabsMenuItem
|
|
@@ -385,7 +407,10 @@ export default defineComponent({
|
|
|
385
407
|
await item.action()
|
|
386
408
|
}
|
|
387
409
|
|
|
388
|
-
function
|
|
410
|
+
function getTabTitle(tab: TabRecord): string {
|
|
411
|
+
if (props.titleResolver) {
|
|
412
|
+
return props.titleResolver(tab)
|
|
413
|
+
}
|
|
389
414
|
if (typeof tab.title === 'string') return tab.title
|
|
390
415
|
if (Array.isArray(tab.title) && tab.title.length) return String(tab.title[0])
|
|
391
416
|
return tab.fullPath
|
|
@@ -411,7 +436,9 @@ export default defineComponent({
|
|
|
411
436
|
'router-tab__item',
|
|
412
437
|
{
|
|
413
438
|
'is-active': controller.activeId.value === tab.id,
|
|
414
|
-
'is-closable': isClosable(tab)
|
|
439
|
+
'is-closable': isClosable(tab),
|
|
440
|
+
'is-dragging': dragState.dragging && dragState.dragTab?.id === tab.id,
|
|
441
|
+
'is-drag-over': dragState.dropIndex === getTabIndex(tab.id)
|
|
415
442
|
},
|
|
416
443
|
tab.tabClass
|
|
417
444
|
]
|
|
@@ -421,6 +448,66 @@ export default defineComponent({
|
|
|
421
448
|
return controller.refreshingKey.value === controller.getRouteKey(route)
|
|
422
449
|
}
|
|
423
450
|
|
|
451
|
+
// Drag and drop handlers
|
|
452
|
+
function onDragStart(tab: TabRecord, index: number, event: DragEvent) {
|
|
453
|
+
if (!props.sortable) return
|
|
454
|
+
|
|
455
|
+
dragState.dragging = true
|
|
456
|
+
dragState.dragIndex = index
|
|
457
|
+
dragState.dragTab = tab
|
|
458
|
+
|
|
459
|
+
if (event.dataTransfer) {
|
|
460
|
+
event.dataTransfer.effectAllowed = 'move'
|
|
461
|
+
event.dataTransfer.setData('text/plain', tab.id)
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
emit('tab-sort', { tab, index })
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
function onDragOver(index: number, event: DragEvent) {
|
|
468
|
+
if (!props.sortable || !dragState.dragging) return
|
|
469
|
+
event.preventDefault()
|
|
470
|
+
if (event.dataTransfer) {
|
|
471
|
+
event.dataTransfer.dropEffect = 'move'
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
function onDragEnter(index: number) {
|
|
476
|
+
if (!props.sortable || !dragState.dragging) return
|
|
477
|
+
dragState.dropIndex = index
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
function onDragLeave() {
|
|
481
|
+
if (!props.sortable || !dragState.dragging) return
|
|
482
|
+
// Don't reset dropIndex immediately to prevent flicker
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
function onDrop(index: number, event: DragEvent) {
|
|
486
|
+
if (!props.sortable || !dragState.dragging) return
|
|
487
|
+
|
|
488
|
+
event.preventDefault()
|
|
489
|
+
|
|
490
|
+
if (dragState.dragIndex !== -1 && dragState.dragIndex !== index) {
|
|
491
|
+
const movedTab = controller.tabs.splice(dragState.dragIndex, 1)[0]
|
|
492
|
+
controller.tabs.splice(index, 0, movedTab)
|
|
493
|
+
|
|
494
|
+
emit('tab-sorted', {
|
|
495
|
+
tab: movedTab,
|
|
496
|
+
fromIndex: dragState.dragIndex,
|
|
497
|
+
toIndex: index
|
|
498
|
+
})
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
onDragEnd()
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
function onDragEnd() {
|
|
505
|
+
dragState.dragging = false
|
|
506
|
+
dragState.dragIndex = -1
|
|
507
|
+
dragState.dropIndex = -1
|
|
508
|
+
dragState.dragTab = null
|
|
509
|
+
}
|
|
510
|
+
|
|
424
511
|
onMounted(() => {
|
|
425
512
|
document.addEventListener('keydown', hideContextMenu)
|
|
426
513
|
})
|
|
@@ -474,11 +561,17 @@ export default defineComponent({
|
|
|
474
561
|
handleMenuAction,
|
|
475
562
|
showContextMenu,
|
|
476
563
|
hideContextMenu,
|
|
477
|
-
|
|
564
|
+
getTabTitle,
|
|
478
565
|
isClosable,
|
|
479
566
|
isRefreshing,
|
|
480
|
-
hasCustomSlot
|
|
567
|
+
hasCustomSlot,
|
|
568
|
+
onDragStart,
|
|
569
|
+
onDragOver,
|
|
570
|
+
onDragEnter,
|
|
571
|
+
onDragLeave,
|
|
572
|
+
onDrop,
|
|
573
|
+
onDragEnd
|
|
481
574
|
}
|
|
482
575
|
}
|
|
483
576
|
})
|
|
484
|
-
</script>
|
|
577
|
+
</script>
|
package/lib/scss/index.scss
CHANGED
|
@@ -1,39 +1,42 @@
|
|
|
1
1
|
@use "sass:math";
|
|
2
|
-
@use "sass:
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
$hd-height: 40px;
|
|
13
|
-
$tab-padding: 20px;
|
|
14
|
-
$close-icon-margin: 4px;
|
|
15
|
-
$close-icon-size: 13px;
|
|
16
|
-
|
|
17
|
-
/// Utility to fetch a CSS variable with a graceful fallback.
|
|
18
|
-
@function css-var($name, $fallback) {
|
|
19
|
-
@return string.unquote("var(#{$name}, #{$fallback})");
|
|
2
|
+
@use "sass:color";
|
|
3
|
+
@use "variables" as *;
|
|
4
|
+
|
|
5
|
+
// Theme Setup
|
|
6
|
+
:root[data-theme="light"] {
|
|
7
|
+
color-scheme: light;
|
|
8
|
+
--router-tab-background: #{$router-tab-bg-light};
|
|
9
|
+
--router-tab-foreground: #{$router-tab-text-light};
|
|
10
|
+
--router-tab-border: #{$router-tab-border-light};
|
|
11
|
+
--router-tab-header-bg: #{$router-tab-bg-light};
|
|
20
12
|
}
|
|
21
13
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
14
|
+
:root[data-theme="dark"] {
|
|
15
|
+
color-scheme: dark;
|
|
16
|
+
--router-tab-active-border: #ffff;
|
|
17
|
+
--router-tab-background: #{$router-tab-bg-dark};
|
|
18
|
+
--router-tab-foreground: #{$router-tab-text-dark};
|
|
19
|
+
--router-tab-border: #{$router-tab-border-dark};
|
|
20
|
+
--router-tab-header-bg: #{$router-tab-bg-dark};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
@media (prefers-color-scheme: dark) {
|
|
24
|
+
:root:not([data-theme]) {
|
|
25
|
+
color-scheme: dark;
|
|
26
|
+
--router-tab-background: #{$router-tab-bg-dark};
|
|
27
|
+
--router-tab-foreground: #{$router-tab-text-dark};
|
|
28
|
+
--router-tab-border: #{$router-tab-border-dark};
|
|
29
|
+
--router-tab-header-bg: #{$router-tab-bg-dark};
|
|
30
|
+
}
|
|
31
|
+
}
|
|
31
32
|
|
|
33
|
+
// Main Component Styles
|
|
34
|
+
.router-tab {
|
|
32
35
|
display: flex;
|
|
33
36
|
flex-direction: column;
|
|
34
37
|
min-height: 300px;
|
|
35
|
-
background-color:
|
|
36
|
-
color:
|
|
38
|
+
background-color: var(--router-tab-background);
|
|
39
|
+
color: var(--router-tab-foreground);
|
|
37
40
|
|
|
38
41
|
&__header {
|
|
39
42
|
position: relative;
|
|
@@ -41,16 +44,22 @@ $close-icon-size: 13px;
|
|
|
41
44
|
display: flex;
|
|
42
45
|
flex: none;
|
|
43
46
|
box-sizing: border-box;
|
|
44
|
-
height: $
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
47
|
+
height: var(--router-tab-header-height, $router-tab-header-height);
|
|
48
|
+
background-color: var(--router-tab-header-bg);
|
|
49
|
+
transition: $router-tab-transition-fast;
|
|
50
|
+
border-bottom: 1px solid var(--router-tab-border);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
&__slot-start,
|
|
54
|
+
&__slot-end {
|
|
55
|
+
display: flex;
|
|
56
|
+
align-items: center;
|
|
48
57
|
}
|
|
49
58
|
|
|
50
59
|
&__scroll {
|
|
51
60
|
position: relative;
|
|
52
61
|
flex: 1 1 0px;
|
|
53
|
-
height: $
|
|
62
|
+
height: var(--router-tab-header-height, $router-tab-header-height);
|
|
54
63
|
overflow: hidden;
|
|
55
64
|
|
|
56
65
|
&-container {
|
|
@@ -114,143 +123,90 @@ $close-icon-size: 13px;
|
|
|
114
123
|
display: flex;
|
|
115
124
|
flex: none;
|
|
116
125
|
align-items: center;
|
|
117
|
-
padding: 0 $tab-padding;
|
|
126
|
+
padding: 0 var(--router-tab-padding, $router-tab-padding);
|
|
118
127
|
color: inherit;
|
|
119
|
-
font-size: $font-size;
|
|
120
|
-
border: 1px solid $border;
|
|
121
|
-
border-left: none;
|
|
128
|
+
font-size: var(--router-tab-font-size, $router-tab-font-size);
|
|
122
129
|
transform-origin: left bottom;
|
|
123
130
|
cursor: pointer;
|
|
124
|
-
transition: $tab-
|
|
131
|
+
transition: var(--router-tab-transition, $router-tab-transition);
|
|
125
132
|
user-select: none;
|
|
126
133
|
background-color: transparent;
|
|
134
|
+
border-right: 1px solid var(--router-tab-border);
|
|
127
135
|
|
|
128
136
|
&:first-child {
|
|
129
|
-
border-left: 1px solid
|
|
137
|
+
border-left: 1px solid var(--router-tab-border);
|
|
130
138
|
}
|
|
131
139
|
|
|
132
140
|
&.is-contextmenu {
|
|
133
|
-
color:
|
|
141
|
+
color: var(--router-tab-primary);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Drag and drop states
|
|
145
|
+
&.is-dragging {
|
|
146
|
+
opacity: $router-tab-drag-opacity;
|
|
147
|
+
cursor: $router-tab-drag-cursor;
|
|
134
148
|
}
|
|
135
149
|
|
|
136
150
|
&.is-drag-over {
|
|
137
|
-
background:
|
|
151
|
+
background: $router-tab-drag-over-bg;
|
|
138
152
|
transition: background 0.15s ease;
|
|
153
|
+
|
|
154
|
+
&::before {
|
|
155
|
+
content: "";
|
|
156
|
+
position: absolute;
|
|
157
|
+
left: -2px;
|
|
158
|
+
top: 0;
|
|
159
|
+
bottom: 0;
|
|
160
|
+
width: 2px;
|
|
161
|
+
background: var(--router-tab-primary);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
&[draggable="true"] {
|
|
166
|
+
cursor: $router-tab-drag-cursor;
|
|
139
167
|
}
|
|
140
168
|
|
|
141
169
|
&-title {
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
align-items: center;
|
|
145
|
-
gap: var(--router-tab-title-gap, 0);
|
|
146
|
-
min-width: var(--router-tab-title-min-width, 30px);
|
|
147
|
-
max-width: var(--router-tab-title-max-width, 120px);
|
|
148
|
-
color: var(--router-tab-title-color, inherit);
|
|
149
|
-
font-weight: var(--router-tab-title-font-weight, inherit);
|
|
150
|
-
letter-spacing: var(--router-tab-title-letter-spacing, normal);
|
|
151
|
-
text-transform: var(--router-tab-title-transform, none);
|
|
170
|
+
min-width: $router-tab-title-min-width;
|
|
171
|
+
max-width: $router-tab-title-max-width;
|
|
152
172
|
overflow: hidden;
|
|
153
173
|
white-space: nowrap;
|
|
154
174
|
text-overflow: ellipsis;
|
|
155
175
|
}
|
|
156
176
|
|
|
157
177
|
&-icon {
|
|
158
|
-
margin-right:
|
|
159
|
-
font-size:
|
|
178
|
+
margin-right: $router-tab-icon-margin;
|
|
179
|
+
font-size: $router-tab-icon-size;
|
|
160
180
|
}
|
|
161
181
|
|
|
162
182
|
&:hover,
|
|
163
183
|
&.is-active {
|
|
164
|
-
color:
|
|
184
|
+
color: var(--router-tab-active-border);
|
|
165
185
|
|
|
166
186
|
&.is-closable {
|
|
167
|
-
padding: 0
|
|
187
|
+
padding: 0
|
|
188
|
+
calc(
|
|
189
|
+
var(--router-tab-padding, #{$router-tab-padding}) -
|
|
190
|
+
(#{$router-tab-close-icon-size} + #{$router-tab-close-icon-margin}) / 2
|
|
191
|
+
);
|
|
168
192
|
}
|
|
169
193
|
|
|
170
194
|
.router-tab__item-close {
|
|
171
|
-
width: $close-icon-size;
|
|
172
|
-
margin-left: $close-icon-margin;
|
|
195
|
+
width: $router-tab-close-icon-size;
|
|
196
|
+
margin-left: $router-tab-close-icon-margin;
|
|
173
197
|
|
|
174
198
|
&::before,
|
|
175
199
|
&::after {
|
|
176
|
-
background-color:
|
|
200
|
+
background-color: currentColor;
|
|
177
201
|
}
|
|
178
202
|
}
|
|
179
203
|
}
|
|
180
204
|
|
|
181
|
-
&:hover {
|
|
182
|
-
.router-tab__item-title {
|
|
183
|
-
max-width: var(--router-tab-title-hover-max-width, 220px);
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
|
|
187
205
|
&.is-active {
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
&::after {
|
|
194
|
-
content: '';
|
|
195
|
-
position: absolute;
|
|
196
|
-
left: 16px;
|
|
197
|
-
right: 16px;
|
|
198
|
-
bottom: 6px;
|
|
199
|
-
height: 2px;
|
|
200
|
-
border-radius: 999px;
|
|
201
|
-
background: $primary;
|
|
202
|
-
opacity: 0.6;
|
|
203
|
-
pointer-events: none;
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
&[data-title] {
|
|
208
|
-
&::after {
|
|
209
|
-
content: attr(data-title);
|
|
210
|
-
position: absolute;
|
|
211
|
-
left: 50%;
|
|
212
|
-
bottom: calc(100% + 8px);
|
|
213
|
-
display: inline-block;
|
|
214
|
-
max-width: 320px;
|
|
215
|
-
padding: 4px 10px;
|
|
216
|
-
border-radius: 6px;
|
|
217
|
-
background: $tooltip-bg;
|
|
218
|
-
color: $tooltip-fg;
|
|
219
|
-
font-size: 12px;
|
|
220
|
-
line-height: 1.3;
|
|
221
|
-
text-align: center;
|
|
222
|
-
white-space: normal;
|
|
223
|
-
word-break: break-word;
|
|
224
|
-
transform: translate(-50%, 4px);
|
|
225
|
-
box-shadow: $tooltip-shadow;
|
|
226
|
-
opacity: 0;
|
|
227
|
-
pointer-events: none;
|
|
228
|
-
transition: opacity 0.2s ease, transform 0.2s ease;
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
&::before {
|
|
232
|
-
content: '';
|
|
233
|
-
position: absolute;
|
|
234
|
-
left: 50%;
|
|
235
|
-
bottom: calc(100% + 4px);
|
|
236
|
-
width: 8px;
|
|
237
|
-
height: 8px;
|
|
238
|
-
background: $tooltip-bg;
|
|
239
|
-
transform: translate(-50%, 6px) rotate(45deg);
|
|
240
|
-
opacity: 0;
|
|
241
|
-
pointer-events: none;
|
|
242
|
-
transition: opacity 0.2s ease, transform 0.2s ease;
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
&:hover::after {
|
|
246
|
-
opacity: 1;
|
|
247
|
-
transform: translate(-50%, 0);
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
&:hover::before {
|
|
251
|
-
opacity: 1;
|
|
252
|
-
transform: translate(-50%, 0) rotate(45deg);
|
|
253
|
-
}
|
|
206
|
+
background-color: var(--router-tab-background);
|
|
207
|
+
color: var(--router-tab-active-border);
|
|
208
|
+
border-bottom: 2px solid var(--router-tab-active-border);
|
|
209
|
+
font-weight: 510;
|
|
254
210
|
}
|
|
255
211
|
|
|
256
212
|
&-close {
|
|
@@ -259,12 +215,12 @@ $close-icon-size: 13px;
|
|
|
259
215
|
position: relative;
|
|
260
216
|
display: block;
|
|
261
217
|
width: 0;
|
|
262
|
-
height: $close-icon-size;
|
|
218
|
+
height: $router-tab-close-icon-size;
|
|
263
219
|
margin-left: 0;
|
|
264
220
|
overflow: hidden;
|
|
265
221
|
border-radius: 50%;
|
|
266
222
|
cursor: pointer;
|
|
267
|
-
transition: $tab-
|
|
223
|
+
transition: var(--router-tab-transition, $router-tab-transition);
|
|
268
224
|
background: transparent;
|
|
269
225
|
border: none;
|
|
270
226
|
|
|
@@ -279,7 +235,7 @@ $close-icon-size: 13px;
|
|
|
279
235
|
margin-left: math.div(-$inner, 2);
|
|
280
236
|
background-color: currentColor;
|
|
281
237
|
transition: background-color 0.2s ease-in-out;
|
|
282
|
-
content:
|
|
238
|
+
content: "";
|
|
283
239
|
}
|
|
284
240
|
|
|
285
241
|
&::before {
|
|
@@ -291,7 +247,7 @@ $close-icon-size: 13px;
|
|
|
291
247
|
}
|
|
292
248
|
|
|
293
249
|
&:hover {
|
|
294
|
-
background-color: color-mix(in srgb,
|
|
250
|
+
background-color: color-mix(in srgb, var(--router-tab-primary) 40%, transparent 60%);
|
|
295
251
|
|
|
296
252
|
&::before,
|
|
297
253
|
&::after {
|
|
@@ -304,49 +260,88 @@ $close-icon-size: 13px;
|
|
|
304
260
|
&__contextmenu {
|
|
305
261
|
position: fixed;
|
|
306
262
|
z-index: 1000;
|
|
307
|
-
min-width:
|
|
308
|
-
padding:
|
|
309
|
-
font-size: $font-size;
|
|
310
|
-
background:
|
|
311
|
-
border: 1px solid
|
|
312
|
-
box-shadow:
|
|
313
|
-
border-radius:
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
button {
|
|
263
|
+
min-width: $router-tab-contextmenu-min-width;
|
|
264
|
+
padding: $router-tab-contextmenu-padding;
|
|
265
|
+
font-size: var(--router-tab-font-size, $router-tab-font-size);
|
|
266
|
+
background: var(--router-tab-background);
|
|
267
|
+
border: 1px solid var(--router-tab-border);
|
|
268
|
+
box-shadow: $router-tab-contextmenu-shadow;
|
|
269
|
+
border-radius: $router-tab-contextmenu-border-radius;
|
|
270
|
+
|
|
271
|
+
&-item {
|
|
317
272
|
display: block;
|
|
318
273
|
width: 100%;
|
|
319
|
-
padding:
|
|
320
|
-
line-height:
|
|
274
|
+
padding: $router-tab-contextmenu-item-padding;
|
|
275
|
+
line-height: $router-tab-contextmenu-item-height;
|
|
321
276
|
text-align: left;
|
|
277
|
+
text-decoration: none;
|
|
322
278
|
background: transparent;
|
|
323
279
|
border: none;
|
|
324
280
|
color: inherit;
|
|
325
281
|
cursor: pointer;
|
|
326
282
|
font: inherit;
|
|
327
283
|
transition: background-color 0.2s ease-in-out;
|
|
284
|
+
border-radius: calc(#{$router-tab-contextmenu-border-radius} - 4px);
|
|
328
285
|
|
|
329
|
-
&[aria-disabled=
|
|
286
|
+
&[aria-disabled="true"] {
|
|
330
287
|
color: rgba(148, 163, 184, 0.6);
|
|
331
288
|
pointer-events: none;
|
|
289
|
+
cursor: not-allowed;
|
|
332
290
|
}
|
|
333
291
|
|
|
334
|
-
&:hover,
|
|
292
|
+
&:hover:not([aria-disabled="true"]),
|
|
335
293
|
&:focus-visible {
|
|
336
|
-
background: color-mix(in srgb,
|
|
337
|
-
color:
|
|
294
|
+
background: color-mix(in srgb, var(--router-tab-primary) 10%, transparent 90%);
|
|
295
|
+
color: var(--router-tab-primary);
|
|
338
296
|
}
|
|
339
297
|
}
|
|
340
298
|
}
|
|
341
|
-
}
|
|
342
299
|
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
300
|
+
&__container {
|
|
301
|
+
position: relative;
|
|
302
|
+
flex: 1 1 auto;
|
|
303
|
+
background-color: var(--router-tab-background);
|
|
304
|
+
overflow: hidden;
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
.router-tab__item.is-active:after {
|
|
308
|
+
display: none !important;
|
|
348
309
|
}
|
|
349
310
|
|
|
350
311
|
.router-tab__item.is-active + .router-tab__item {
|
|
351
|
-
border-left-color:
|
|
312
|
+
border-left-color: transparent;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// Transition animations
|
|
316
|
+
.router-tab-zoom-enter-active,
|
|
317
|
+
.router-tab-zoom-leave-active {
|
|
318
|
+
transition: all 0.3s ease;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
.router-tab-zoom-enter-from,
|
|
322
|
+
.router-tab-zoom-leave-to {
|
|
323
|
+
opacity: 0;
|
|
324
|
+
transform: scale(0.8);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
.router-tab-swap-enter-active,
|
|
328
|
+
.router-tab-swap-leave-active {
|
|
329
|
+
transition:
|
|
330
|
+
opacity 0.2s ease,
|
|
331
|
+
transform 0.2s ease;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
.router-tab-swap-enter-from {
|
|
335
|
+
opacity: 0;
|
|
336
|
+
transform: translateX(10px);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
.router-tab-swap-leave-to {
|
|
340
|
+
opacity: 0;
|
|
341
|
+
transform: translateX(-10px);
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
.router-tab-page {
|
|
345
|
+
width: 100%;
|
|
346
|
+
height: 100%;
|
|
352
347
|
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
// variables.scss
|
|
2
|
+
|
|
3
|
+
// Tab colors (light + dark theme overrides in :root)
|
|
4
|
+
$router-tab-bg-light: #ffffff !default;
|
|
5
|
+
$router-tab-bg-dark: #1e293b !default;
|
|
6
|
+
|
|
7
|
+
$router-tab-text-light: #0f172a !default;
|
|
8
|
+
$router-tab-text-dark: #f1f5f9 !default;
|
|
9
|
+
|
|
10
|
+
$router-tab-border-light: #e2e8f0 !default;
|
|
11
|
+
$router-tab-border-dark: #334155 !default;
|
|
12
|
+
|
|
13
|
+
$router-tab-primary: #3b82f6 !default; // default primary color
|
|
14
|
+
|
|
15
|
+
// Sizes
|
|
16
|
+
$router-tab-header-height: 42px !default;
|
|
17
|
+
$router-tab-padding: 12px !default;
|
|
18
|
+
$router-tab-font-size: 14px !default;
|
|
19
|
+
|
|
20
|
+
// Title
|
|
21
|
+
$router-tab-title-min-width: 60px !default;
|
|
22
|
+
$router-tab-title-max-width: 180px !default;
|
|
23
|
+
|
|
24
|
+
// Icons
|
|
25
|
+
$router-tab-icon-size: 14px !default;
|
|
26
|
+
$router-tab-icon-margin: 6px !default;
|
|
27
|
+
|
|
28
|
+
// Close icon
|
|
29
|
+
$router-tab-close-icon-size: 16px !default;
|
|
30
|
+
$router-tab-close-icon-margin: 4px !default;
|
|
31
|
+
|
|
32
|
+
// Context menu
|
|
33
|
+
$router-tab-contextmenu-min-width: 160px !default;
|
|
34
|
+
$router-tab-contextmenu-padding: 6px 0 !default;
|
|
35
|
+
$router-tab-contextmenu-item-height: 32px !default;
|
|
36
|
+
$router-tab-contextmenu-item-padding: 0 12px !default;
|
|
37
|
+
$router-tab-contextmenu-border-radius: 8px !default;
|
|
38
|
+
$router-tab-contextmenu-shadow: 0 2px 8px rgba(0, 0, 0, 0.1) !default;
|
|
39
|
+
|
|
40
|
+
// Dragging
|
|
41
|
+
$router-tab-drag-opacity: 0.6 !default;
|
|
42
|
+
$router-tab-drag-cursor: grabbing !default;
|
|
43
|
+
$router-tab-drag-over-bg: rgba(0, 0, 0, 0.05) !default;
|
|
44
|
+
|
|
45
|
+
// Transitions
|
|
46
|
+
$router-tab-transition-fast: all 0.2s ease !default;
|
|
47
|
+
$router-tab-transition: all 0.3s ease !default;
|