vue3-router-tab 1.1.7 → 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 +150 -309
- 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,64 +1,42 @@
|
|
|
1
1
|
@use "sass:math";
|
|
2
|
-
@use "sass:
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
$dark-text: #e2e8f0;
|
|
13
|
-
$dark-border: rgba(226, 232, 240, 0.12);
|
|
14
|
-
|
|
15
|
-
$font-size: 14px;
|
|
16
|
-
$tab-trans: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
|
17
|
-
$hd-height: 40px;
|
|
18
|
-
$tab-padding: 20px;
|
|
19
|
-
$close-icon-margin: 4px;
|
|
20
|
-
$close-icon-size: 13px;
|
|
21
|
-
|
|
22
|
-
/// Utility to fetch a CSS variable with a graceful fallback.
|
|
23
|
-
@function css-var($name, $fallback) {
|
|
24
|
-
@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};
|
|
25
12
|
}
|
|
26
13
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
}
|
|
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};
|
|
32
21
|
}
|
|
33
22
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
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};
|
|
38
30
|
}
|
|
39
31
|
}
|
|
40
32
|
|
|
33
|
+
// Main Component Styles
|
|
41
34
|
.router-tab {
|
|
42
|
-
// Use Vuetify theme colors with fallbacks
|
|
43
|
-
$bg: css-var(--router-tab-background, css-var(--v-theme-surface, css-var(--theme-background, $light-bg)));
|
|
44
|
-
$fg: css-var(--router-tab-foreground, css-var(--v-theme-on-surface, css-var(--theme-foreground, $light-text)));
|
|
45
|
-
$border: css-var(--router-tab-border, css-var(--v-border-color, css-var(--theme-border, $light-border)));
|
|
46
|
-
$primary: css-var(--router-tab-primary, css-var(--v-theme-primary, css-var(--theme-primary, $primary-fallback)));
|
|
47
|
-
$header-bg: css-var(--router-tab-header-bg, $bg);
|
|
48
|
-
$tooltip-bg: css-var(--router-tab-tooltip-background, rgba(var(--v-theme-on-surface), 0.9));
|
|
49
|
-
$tooltip-fg: css-var(--router-tab-tooltip-foreground, css-var(--v-theme-surface, #ffffff));
|
|
50
|
-
$tooltip-shadow: css-var(--router-tab-tooltip-shadow, 0 8px 24px rgba(0, 0, 0, 0.18));
|
|
51
|
-
|
|
52
35
|
display: flex;
|
|
53
36
|
flex-direction: column;
|
|
54
37
|
min-height: 300px;
|
|
55
|
-
background-color:
|
|
56
|
-
color:
|
|
57
|
-
|
|
58
|
-
// Dark theme default values
|
|
59
|
-
@include dark-theme {
|
|
60
|
-
--router-tab-tooltip-shadow: 0 8px 24px rgba(0, 0, 0, 0.5);
|
|
61
|
-
}
|
|
38
|
+
background-color: var(--router-tab-background);
|
|
39
|
+
color: var(--router-tab-foreground);
|
|
62
40
|
|
|
63
41
|
&__header {
|
|
64
42
|
position: relative;
|
|
@@ -66,21 +44,22 @@ $close-icon-size: 13px;
|
|
|
66
44
|
display: flex;
|
|
67
45
|
flex: none;
|
|
68
46
|
box-sizing: border-box;
|
|
69
|
-
height: $
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
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;
|
|
78
57
|
}
|
|
79
58
|
|
|
80
59
|
&__scroll {
|
|
81
60
|
position: relative;
|
|
82
61
|
flex: 1 1 0px;
|
|
83
|
-
height: $
|
|
62
|
+
height: var(--router-tab-header-height, $router-tab-header-height);
|
|
84
63
|
overflow: hidden;
|
|
85
64
|
|
|
86
65
|
&-container {
|
|
@@ -91,7 +70,6 @@ $close-icon-size: 13px;
|
|
|
91
70
|
&.is-mobile {
|
|
92
71
|
overflow-x: auto;
|
|
93
72
|
overflow-y: hidden;
|
|
94
|
-
-webkit-overflow-scrolling: touch;
|
|
95
73
|
}
|
|
96
74
|
}
|
|
97
75
|
}
|
|
@@ -104,14 +82,10 @@ $close-icon-size: 13px;
|
|
|
104
82
|
bottom: 0;
|
|
105
83
|
left: 0;
|
|
106
84
|
height: $h;
|
|
107
|
-
background-color: rgba(
|
|
85
|
+
background-color: rgba(0, 0, 0, 0.1);
|
|
108
86
|
border-radius: $h;
|
|
109
87
|
opacity: 0;
|
|
110
|
-
transition: opacity 0.3s
|
|
111
|
-
|
|
112
|
-
@include reduced-motion {
|
|
113
|
-
transition: none;
|
|
114
|
-
}
|
|
88
|
+
transition: opacity 0.3s ease-in-out;
|
|
115
89
|
|
|
116
90
|
.router-tab__scroll:hover &,
|
|
117
91
|
&.is-dragging {
|
|
@@ -123,17 +97,13 @@ $close-icon-size: 13px;
|
|
|
123
97
|
top: 0;
|
|
124
98
|
left: 0;
|
|
125
99
|
height: 100%;
|
|
126
|
-
background-color: rgba(
|
|
100
|
+
background-color: rgba(0, 0, 0, 0.1);
|
|
127
101
|
border-radius: $h;
|
|
128
|
-
transition: background-color 0.3s
|
|
129
|
-
|
|
130
|
-
@include reduced-motion {
|
|
131
|
-
transition: none;
|
|
132
|
-
}
|
|
102
|
+
transition: background-color 0.3s ease-in-out;
|
|
133
103
|
|
|
134
104
|
&:hover,
|
|
135
105
|
.router-tab__scrollbar.is-dragging & {
|
|
136
|
-
background-color: rgba(
|
|
106
|
+
background-color: rgba(0, 0, 0, 0.2);
|
|
137
107
|
}
|
|
138
108
|
}
|
|
139
109
|
}
|
|
@@ -153,180 +123,90 @@ $close-icon-size: 13px;
|
|
|
153
123
|
display: flex;
|
|
154
124
|
flex: none;
|
|
155
125
|
align-items: center;
|
|
156
|
-
padding: 0 $tab-padding;
|
|
126
|
+
padding: 0 var(--router-tab-padding, $router-tab-padding);
|
|
157
127
|
color: inherit;
|
|
158
|
-
font-size: $font-size;
|
|
159
|
-
border: 1px solid $border;
|
|
160
|
-
border-left: none;
|
|
128
|
+
font-size: var(--router-tab-font-size, $router-tab-font-size);
|
|
161
129
|
transform-origin: left bottom;
|
|
162
130
|
cursor: pointer;
|
|
163
|
-
transition: $tab-
|
|
131
|
+
transition: var(--router-tab-transition, $router-tab-transition);
|
|
164
132
|
user-select: none;
|
|
165
133
|
background-color: transparent;
|
|
166
|
-
|
|
167
|
-
@include reduced-motion {
|
|
168
|
-
transition: none;
|
|
169
|
-
}
|
|
134
|
+
border-right: 1px solid var(--router-tab-border);
|
|
170
135
|
|
|
171
136
|
&:first-child {
|
|
172
|
-
border-left: 1px solid
|
|
137
|
+
border-left: 1px solid var(--router-tab-border);
|
|
173
138
|
}
|
|
174
139
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
outline: 2px solid $primary;
|
|
178
|
-
outline-offset: -2px;
|
|
179
|
-
z-index: 1;
|
|
140
|
+
&.is-contextmenu {
|
|
141
|
+
color: var(--router-tab-primary);
|
|
180
142
|
}
|
|
181
143
|
|
|
182
|
-
|
|
183
|
-
|
|
144
|
+
// Drag and drop states
|
|
145
|
+
&.is-dragging {
|
|
146
|
+
opacity: $router-tab-drag-opacity;
|
|
147
|
+
cursor: $router-tab-drag-cursor;
|
|
184
148
|
}
|
|
185
149
|
|
|
186
150
|
&.is-drag-over {
|
|
187
|
-
background:
|
|
188
|
-
transition: background 0.15s
|
|
151
|
+
background: $router-tab-drag-over-bg;
|
|
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;
|
|
189
167
|
}
|
|
190
168
|
|
|
191
169
|
&-title {
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
align-items: center;
|
|
195
|
-
gap: var(--router-tab-title-gap, 0);
|
|
196
|
-
min-width: var(--router-tab-title-min-width, 30px);
|
|
197
|
-
max-width: var(--router-tab-title-max-width, 120px);
|
|
198
|
-
color: var(--router-tab-title-color, inherit);
|
|
199
|
-
font-weight: var(--router-tab-title-font-weight, inherit);
|
|
200
|
-
letter-spacing: var(--router-tab-title-letter-spacing, normal);
|
|
201
|
-
text-transform: var(--router-tab-title-transform, none);
|
|
170
|
+
min-width: $router-tab-title-min-width;
|
|
171
|
+
max-width: $router-tab-title-max-width;
|
|
202
172
|
overflow: hidden;
|
|
203
173
|
white-space: nowrap;
|
|
204
174
|
text-overflow: ellipsis;
|
|
205
|
-
transition: max-width 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
|
206
|
-
|
|
207
|
-
@include reduced-motion {
|
|
208
|
-
transition: none;
|
|
209
|
-
}
|
|
210
175
|
}
|
|
211
176
|
|
|
212
177
|
&-icon {
|
|
213
|
-
margin-right:
|
|
214
|
-
font-size:
|
|
215
|
-
flex-shrink: 0;
|
|
178
|
+
margin-right: $router-tab-icon-margin;
|
|
179
|
+
font-size: $router-tab-icon-size;
|
|
216
180
|
}
|
|
217
181
|
|
|
218
182
|
&:hover,
|
|
219
183
|
&.is-active {
|
|
220
|
-
color:
|
|
184
|
+
color: var(--router-tab-active-border);
|
|
221
185
|
|
|
222
186
|
&.is-closable {
|
|
223
|
-
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
|
+
);
|
|
224
192
|
}
|
|
225
193
|
|
|
226
194
|
.router-tab__item-close {
|
|
227
|
-
width: $close-icon-size;
|
|
228
|
-
margin-left: $close-icon-margin;
|
|
195
|
+
width: $router-tab-close-icon-size;
|
|
196
|
+
margin-left: $router-tab-close-icon-margin;
|
|
229
197
|
|
|
230
198
|
&::before,
|
|
231
199
|
&::after {
|
|
232
|
-
background-color:
|
|
200
|
+
background-color: currentColor;
|
|
233
201
|
}
|
|
234
202
|
}
|
|
235
203
|
}
|
|
236
204
|
|
|
237
|
-
&:hover {
|
|
238
|
-
.router-tab__item-title {
|
|
239
|
-
max-width: var(--router-tab-title-hover-max-width, 220px);
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
|
|
243
205
|
&.is-active {
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
// Fallback for browsers with color-mix support
|
|
250
|
-
@supports (background: color-mix(in srgb, red 50%, blue)) {
|
|
251
|
-
background: color-mix(in srgb, $primary 8%, $bg 92%);
|
|
252
|
-
box-shadow: inset 0 -2px 0 color-mix(in srgb, $primary 50%, transparent);
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
&::after {
|
|
256
|
-
content: '';
|
|
257
|
-
position: absolute;
|
|
258
|
-
left: 16px;
|
|
259
|
-
right: 16px;
|
|
260
|
-
bottom: 6px;
|
|
261
|
-
height: 2px;
|
|
262
|
-
border-radius: 999px;
|
|
263
|
-
background: $primary;
|
|
264
|
-
opacity: 0.7;
|
|
265
|
-
pointer-events: none;
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
&[data-title] {
|
|
270
|
-
&::after {
|
|
271
|
-
content: attr(data-title);
|
|
272
|
-
position: absolute;
|
|
273
|
-
left: 50%;
|
|
274
|
-
bottom: calc(100% + 8px);
|
|
275
|
-
display: inline-block;
|
|
276
|
-
max-width: 320px;
|
|
277
|
-
padding: 6px 12px;
|
|
278
|
-
border-radius: 6px;
|
|
279
|
-
background: $tooltip-bg;
|
|
280
|
-
color: $tooltip-fg;
|
|
281
|
-
font-size: 12px;
|
|
282
|
-
line-height: 1.4;
|
|
283
|
-
text-align: center;
|
|
284
|
-
white-space: normal;
|
|
285
|
-
word-break: break-word;
|
|
286
|
-
transform: translate(-50%, 4px);
|
|
287
|
-
box-shadow: $tooltip-shadow;
|
|
288
|
-
opacity: 0;
|
|
289
|
-
pointer-events: none;
|
|
290
|
-
transition: opacity 0.2s cubic-bezier(0.4, 0, 0.2, 1),
|
|
291
|
-
transform 0.2s cubic-bezier(0.4, 0, 0.2, 1);
|
|
292
|
-
z-index: 10;
|
|
293
|
-
|
|
294
|
-
@include reduced-motion {
|
|
295
|
-
transition: opacity 0.2s cubic-bezier(0.4, 0, 0.2, 1);
|
|
296
|
-
transform: translate(-50%, 0);
|
|
297
|
-
}
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
&::before {
|
|
301
|
-
content: '';
|
|
302
|
-
position: absolute;
|
|
303
|
-
left: 50%;
|
|
304
|
-
bottom: calc(100% + 4px);
|
|
305
|
-
width: 8px;
|
|
306
|
-
height: 8px;
|
|
307
|
-
background: $tooltip-bg;
|
|
308
|
-
transform: translate(-50%, 6px) rotate(45deg);
|
|
309
|
-
opacity: 0;
|
|
310
|
-
pointer-events: none;
|
|
311
|
-
transition: opacity 0.2s cubic-bezier(0.4, 0, 0.2, 1),
|
|
312
|
-
transform 0.2s cubic-bezier(0.4, 0, 0.2, 1);
|
|
313
|
-
z-index: 9;
|
|
314
|
-
|
|
315
|
-
@include reduced-motion {
|
|
316
|
-
transition: opacity 0.2s cubic-bezier(0.4, 0, 0.2, 1);
|
|
317
|
-
transform: translate(-50%, 0) rotate(45deg);
|
|
318
|
-
}
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
&:hover::after {
|
|
322
|
-
opacity: 1;
|
|
323
|
-
transform: translate(-50%, 0);
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
&:hover::before {
|
|
327
|
-
opacity: 1;
|
|
328
|
-
transform: translate(-50%, 0) rotate(45deg);
|
|
329
|
-
}
|
|
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;
|
|
330
210
|
}
|
|
331
211
|
|
|
332
212
|
&-close {
|
|
@@ -335,25 +215,14 @@ $close-icon-size: 13px;
|
|
|
335
215
|
position: relative;
|
|
336
216
|
display: block;
|
|
337
217
|
width: 0;
|
|
338
|
-
height: $close-icon-size;
|
|
218
|
+
height: $router-tab-close-icon-size;
|
|
339
219
|
margin-left: 0;
|
|
340
220
|
overflow: hidden;
|
|
341
221
|
border-radius: 50%;
|
|
342
222
|
cursor: pointer;
|
|
343
|
-
transition: $tab-
|
|
223
|
+
transition: var(--router-tab-transition, $router-tab-transition);
|
|
344
224
|
background: transparent;
|
|
345
225
|
border: none;
|
|
346
|
-
flex-shrink: 0;
|
|
347
|
-
|
|
348
|
-
@include reduced-motion {
|
|
349
|
-
transition: none;
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
// Keyboard focus
|
|
353
|
-
&:focus-visible {
|
|
354
|
-
outline: 2px solid $primary;
|
|
355
|
-
outline-offset: 2px;
|
|
356
|
-
}
|
|
357
226
|
|
|
358
227
|
&::before,
|
|
359
228
|
&::after {
|
|
@@ -365,12 +234,8 @@ $close-icon-size: 13px;
|
|
|
365
234
|
height: 1px;
|
|
366
235
|
margin-left: math.div(-$inner, 2);
|
|
367
236
|
background-color: currentColor;
|
|
368
|
-
transition: background-color 0.2s
|
|
369
|
-
content:
|
|
370
|
-
|
|
371
|
-
@include reduced-motion {
|
|
372
|
-
transition: none;
|
|
373
|
-
}
|
|
237
|
+
transition: background-color 0.2s ease-in-out;
|
|
238
|
+
content: "";
|
|
374
239
|
}
|
|
375
240
|
|
|
376
241
|
&::before {
|
|
@@ -382,17 +247,11 @@ $close-icon-size: 13px;
|
|
|
382
247
|
}
|
|
383
248
|
|
|
384
249
|
&:hover {
|
|
385
|
-
|
|
386
|
-
background-color: rgba(var(--v-theme-primary, 99, 91, 255), 0.15);
|
|
387
|
-
|
|
388
|
-
// Fallback for browsers with color-mix support
|
|
389
|
-
@supports (background: color-mix(in srgb, red 50%, blue)) {
|
|
390
|
-
background-color: color-mix(in srgb, $primary 15%, transparent);
|
|
391
|
-
}
|
|
250
|
+
background-color: color-mix(in srgb, var(--router-tab-primary) 40%, transparent 60%);
|
|
392
251
|
|
|
393
252
|
&::before,
|
|
394
253
|
&::after {
|
|
395
|
-
background-color:
|
|
254
|
+
background-color: #fff;
|
|
396
255
|
}
|
|
397
256
|
}
|
|
398
257
|
}
|
|
@@ -401,106 +260,88 @@ $close-icon-size: 13px;
|
|
|
401
260
|
&__contextmenu {
|
|
402
261
|
position: fixed;
|
|
403
262
|
z-index: 1000;
|
|
404
|
-
min-width:
|
|
405
|
-
padding:
|
|
406
|
-
font-size: $font-size;
|
|
407
|
-
background:
|
|
408
|
-
border: 1px solid
|
|
409
|
-
box-shadow:
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
a,
|
|
415
|
-
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 {
|
|
416
272
|
display: block;
|
|
417
273
|
width: 100%;
|
|
418
|
-
padding:
|
|
419
|
-
line-height:
|
|
274
|
+
padding: $router-tab-contextmenu-item-padding;
|
|
275
|
+
line-height: $router-tab-contextmenu-item-height;
|
|
420
276
|
text-align: left;
|
|
277
|
+
text-decoration: none;
|
|
421
278
|
background: transparent;
|
|
422
279
|
border: none;
|
|
423
280
|
color: inherit;
|
|
424
281
|
cursor: pointer;
|
|
425
282
|
font: inherit;
|
|
426
|
-
transition: background-color 0.2s
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
@include reduced-motion {
|
|
430
|
-
transition: none;
|
|
431
|
-
}
|
|
432
|
-
|
|
433
|
-
// Keyboard focus
|
|
434
|
-
&:focus-visible {
|
|
435
|
-
outline: 2px solid $primary;
|
|
436
|
-
outline-offset: -2px;
|
|
437
|
-
}
|
|
283
|
+
transition: background-color 0.2s ease-in-out;
|
|
284
|
+
border-radius: calc(#{$router-tab-contextmenu-border-radius} - 4px);
|
|
438
285
|
|
|
439
|
-
&[aria-disabled=
|
|
440
|
-
color: rgba(
|
|
286
|
+
&[aria-disabled="true"] {
|
|
287
|
+
color: rgba(148, 163, 184, 0.6);
|
|
441
288
|
pointer-events: none;
|
|
442
289
|
cursor: not-allowed;
|
|
443
290
|
}
|
|
444
291
|
|
|
445
|
-
&:hover:not([aria-disabled=
|
|
446
|
-
&:focus-visible
|
|
447
|
-
background:
|
|
448
|
-
color:
|
|
449
|
-
|
|
450
|
-
@supports (background: color-mix(in srgb, red 50%, blue)) {
|
|
451
|
-
background: color-mix(in srgb, $primary 8%, transparent);
|
|
452
|
-
}
|
|
292
|
+
&:hover:not([aria-disabled="true"]),
|
|
293
|
+
&:focus-visible {
|
|
294
|
+
background: color-mix(in srgb, var(--router-tab-primary) 10%, transparent 90%);
|
|
295
|
+
color: var(--router-tab-primary);
|
|
453
296
|
}
|
|
454
297
|
}
|
|
455
298
|
}
|
|
456
|
-
}
|
|
457
299
|
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
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;
|
|
463
309
|
}
|
|
464
310
|
|
|
465
311
|
.router-tab__item.is-active + .router-tab__item {
|
|
466
|
-
border-left-color:
|
|
312
|
+
border-left-color: transparent;
|
|
467
313
|
}
|
|
468
314
|
|
|
469
|
-
//
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
}
|
|
475
|
-
|
|
476
|
-
@container router-tab (max-width: 600px) {
|
|
477
|
-
.router-tab__item {
|
|
478
|
-
padding: 0 12px;
|
|
315
|
+
// Transition animations
|
|
316
|
+
.router-tab-zoom-enter-active,
|
|
317
|
+
.router-tab-zoom-leave-active {
|
|
318
|
+
transition: all 0.3s ease;
|
|
319
|
+
}
|
|
479
320
|
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
321
|
+
.router-tab-zoom-enter-from,
|
|
322
|
+
.router-tab-zoom-leave-to {
|
|
323
|
+
opacity: 0;
|
|
324
|
+
transform: scale(0.8);
|
|
325
|
+
}
|
|
483
326
|
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
327
|
+
.router-tab-swap-enter-active,
|
|
328
|
+
.router-tab-swap-leave-active {
|
|
329
|
+
transition:
|
|
330
|
+
opacity 0.2s ease,
|
|
331
|
+
transform 0.2s ease;
|
|
489
332
|
}
|
|
490
333
|
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
border-width: 2px;
|
|
334
|
+
.router-tab-swap-enter-from {
|
|
335
|
+
opacity: 0;
|
|
336
|
+
transform: translateX(10px);
|
|
337
|
+
}
|
|
496
338
|
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
339
|
+
.router-tab-swap-leave-to {
|
|
340
|
+
opacity: 0;
|
|
341
|
+
transform: translateX(-10px);
|
|
342
|
+
}
|
|
500
343
|
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
}
|
|
506
|
-
}
|
|
344
|
+
.router-tab-page {
|
|
345
|
+
width: 100%;
|
|
346
|
+
height: 100%;
|
|
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;
|