vue3-router-tab 1.1.9 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/lib/core/createRouterTabs.ts +2 -1
- package/lib/persistence.ts +9 -1
- package/lib/scss/index.scss +110 -116
- package/lib/scss/variables.scss +1 -1
- package/lib/theme.ts +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -115,7 +115,7 @@ The composable also exposes `serialize` / `deserialize` options so you can encry
|
|
|
115
115
|
The plugin initialises a lightweight theme layer on install:
|
|
116
116
|
|
|
117
117
|
- Reads `tab-theme-style` (`'light'`, `'dark'`, or `'system'`; defaults to `'system'`).
|
|
118
|
-
- Reads `tab-theme-primary-color` (defaults to `#
|
|
118
|
+
- Reads `tab-theme-primary-color` (defaults to `#0f172a`).
|
|
119
119
|
- Applies the choice via `data-theme` and `--theme-primary` CSS variables, keeping “system” in sync with OS changes.
|
|
120
120
|
|
|
121
121
|
Override the theme at runtime:
|
|
@@ -3,6 +3,7 @@ import type {
|
|
|
3
3
|
CloseTabOptions,
|
|
4
4
|
RouteMatchResult,
|
|
5
5
|
RouterTabsContext,
|
|
6
|
+
RouterTabsMenuPreset,
|
|
6
7
|
RouterTabsOptions,
|
|
7
8
|
RouterTabsSnapshot,
|
|
8
9
|
RouterTabsSnapshotTab,
|
|
@@ -215,7 +216,7 @@ export function createRouterTabs(
|
|
|
215
216
|
return options.defaultRoute
|
|
216
217
|
}
|
|
217
218
|
|
|
218
|
-
async function closeTab(id: string |
|
|
219
|
+
async function closeTab(id: string | null = activeId.value, closeOptions: CloseTabOptions = {}) {
|
|
219
220
|
if (!id) return
|
|
220
221
|
if (!closeOptions.force && options.keepLastTab && tabs.length === 1) {
|
|
221
222
|
throw new Error('[RouterTabs] Unable to close the final tab when keepLastTab is true.')
|
package/lib/persistence.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { onMounted, ref, watch } from 'vue'
|
|
1
|
+
import { nextTick, onMounted, ref, watch } from 'vue'
|
|
2
2
|
import type { RouteLocationRaw } from 'vue-router'
|
|
3
3
|
import { useRouterTabs } from './useRouterTabs'
|
|
4
4
|
import type { RouterTabsSnapshot } from './core/types'
|
|
@@ -108,6 +108,14 @@ export function useRouterTabsPersistence(options: RouterTabsPersistenceOptions =
|
|
|
108
108
|
try {
|
|
109
109
|
hydrating.value = true
|
|
110
110
|
await ctrl.hydrate(initialSnapshot)
|
|
111
|
+
if (initialSnapshot.active) {
|
|
112
|
+
await nextTick()
|
|
113
|
+
const activeTab = ctrl.tabs.find(t => t.to === initialSnapshot.active)
|
|
114
|
+
if (activeTab) {
|
|
115
|
+
ctrl.activeId.value = activeTab.id
|
|
116
|
+
ctrl.current.value = activeTab
|
|
117
|
+
}
|
|
118
|
+
}
|
|
111
119
|
} finally {
|
|
112
120
|
hydrating.value = false
|
|
113
121
|
}
|
package/lib/scss/index.scss
CHANGED
|
@@ -1,36 +1,57 @@
|
|
|
1
1
|
@use "sass:math";
|
|
2
2
|
@use "sass:color";
|
|
3
|
-
@use "variables" as *;
|
|
4
3
|
|
|
5
|
-
|
|
4
|
+
$default-primary: #0f172a;
|
|
5
|
+
$default-border: #e2e8f0;
|
|
6
|
+
$default-text: #1e293b;
|
|
7
|
+
$default-bg: #f8fafc;
|
|
8
|
+
$default-dark-bg: #0f172a;
|
|
9
|
+
$default-dark-text: #e2e8f0;
|
|
10
|
+
$default-dark-border: rgba(148, 163, 184, 0.35);
|
|
11
|
+
|
|
12
|
+
$font-size: 14px;
|
|
13
|
+
$tab-trans: all 0.3s ease-in-out;
|
|
14
|
+
$hd-height: 40px;
|
|
15
|
+
$tab-padding: 20px;
|
|
16
|
+
$close-icon-margin: 4px;
|
|
17
|
+
$close-icon-size: 13px;
|
|
18
|
+
|
|
19
|
+
:root {
|
|
20
|
+
--theme-primary: #{$default-primary};
|
|
21
|
+
--router-tab-primary: var(--theme-primary);
|
|
22
|
+
--router-tab-background: #{$default-bg};
|
|
23
|
+
--router-tab-foreground: #{$default-text};
|
|
24
|
+
--router-tab-border: #{$default-border};
|
|
25
|
+
--router-tab-header-bg: #{$default-bg};
|
|
26
|
+
color-scheme: light;
|
|
27
|
+
}
|
|
28
|
+
|
|
6
29
|
:root[data-theme="light"] {
|
|
7
30
|
color-scheme: light;
|
|
8
|
-
--router-tab-background: #{$
|
|
9
|
-
--router-tab-foreground: #{$
|
|
10
|
-
--router-tab-border: #{$
|
|
11
|
-
--router-tab-header-bg: #{$
|
|
31
|
+
--router-tab-background: #{$default-bg};
|
|
32
|
+
--router-tab-foreground: #{$default-text};
|
|
33
|
+
--router-tab-border: #{$default-border};
|
|
34
|
+
--router-tab-header-bg: #{$default-bg};
|
|
12
35
|
}
|
|
13
36
|
|
|
14
37
|
:root[data-theme="dark"] {
|
|
15
38
|
color-scheme: dark;
|
|
16
|
-
--router-tab-
|
|
17
|
-
--router-tab-
|
|
18
|
-
--router-tab-
|
|
19
|
-
--router-tab-
|
|
20
|
-
--router-tab-header-bg: #{$router-tab-bg-dark};
|
|
39
|
+
--router-tab-background: #{$default-dark-bg};
|
|
40
|
+
--router-tab-foreground: #{$default-dark-text};
|
|
41
|
+
--router-tab-border: #{$default-dark-border};
|
|
42
|
+
--router-tab-header-bg: #{$default-dark-bg};
|
|
21
43
|
}
|
|
22
44
|
|
|
23
45
|
@media (prefers-color-scheme: dark) {
|
|
24
46
|
:root:not([data-theme]) {
|
|
25
47
|
color-scheme: dark;
|
|
26
|
-
--router-tab-background: #{$
|
|
27
|
-
--router-tab-foreground: #{$
|
|
28
|
-
--router-tab-border: #{$
|
|
29
|
-
--router-tab-header-bg: #{$
|
|
48
|
+
--router-tab-background: #{$default-dark-bg};
|
|
49
|
+
--router-tab-foreground: #{$default-dark-text};
|
|
50
|
+
--router-tab-border: #{$default-dark-border};
|
|
51
|
+
--router-tab-header-bg: #{$default-dark-bg};
|
|
30
52
|
}
|
|
31
53
|
}
|
|
32
54
|
|
|
33
|
-
// Main Component Styles
|
|
34
55
|
.router-tab {
|
|
35
56
|
display: flex;
|
|
36
57
|
flex-direction: column;
|
|
@@ -44,22 +65,16 @@
|
|
|
44
65
|
display: flex;
|
|
45
66
|
flex: none;
|
|
46
67
|
box-sizing: border-box;
|
|
47
|
-
height:
|
|
68
|
+
height: $hd-height;
|
|
48
69
|
background-color: var(--router-tab-header-bg);
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
&__slot-start,
|
|
54
|
-
&__slot-end {
|
|
55
|
-
display: flex;
|
|
56
|
-
align-items: center;
|
|
70
|
+
border-bottom: 1px solid var(--router-tab-border); // FIXED: Added border
|
|
71
|
+
transition: all 0.2s ease-in-out;
|
|
57
72
|
}
|
|
58
73
|
|
|
59
74
|
&__scroll {
|
|
60
75
|
position: relative;
|
|
61
76
|
flex: 1 1 0px;
|
|
62
|
-
height:
|
|
77
|
+
height: $hd-height;
|
|
63
78
|
overflow: hidden;
|
|
64
79
|
|
|
65
80
|
&-container {
|
|
@@ -123,15 +138,19 @@
|
|
|
123
138
|
display: flex;
|
|
124
139
|
flex: none;
|
|
125
140
|
align-items: center;
|
|
126
|
-
padding: 0
|
|
141
|
+
padding: 0 $tab-padding;
|
|
127
142
|
color: inherit;
|
|
128
|
-
font-size:
|
|
143
|
+
font-size: $font-size;
|
|
129
144
|
transform-origin: left bottom;
|
|
130
145
|
cursor: pointer;
|
|
131
|
-
transition:
|
|
146
|
+
transition: $tab-trans;
|
|
132
147
|
user-select: none;
|
|
133
148
|
background-color: transparent;
|
|
149
|
+
|
|
150
|
+
// FIXED: Added proper borders
|
|
134
151
|
border-right: 1px solid var(--router-tab-border);
|
|
152
|
+
border-top: 1px solid transparent;
|
|
153
|
+
border-bottom: 2px solid transparent;
|
|
135
154
|
|
|
136
155
|
&:first-child {
|
|
137
156
|
border-left: 1px solid var(--router-tab-border);
|
|
@@ -141,59 +160,43 @@
|
|
|
141
160
|
color: var(--router-tab-primary);
|
|
142
161
|
}
|
|
143
162
|
|
|
144
|
-
// Drag
|
|
163
|
+
// NEW: Drag states
|
|
145
164
|
&.is-dragging {
|
|
146
|
-
opacity:
|
|
147
|
-
cursor:
|
|
165
|
+
opacity: 0.5;
|
|
166
|
+
cursor: move;
|
|
148
167
|
}
|
|
149
168
|
|
|
150
169
|
&.is-drag-over {
|
|
151
|
-
background:
|
|
152
|
-
|
|
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;
|
|
170
|
+
background: color-mix(in srgb, var(--router-tab-primary) 10%, transparent 90%);
|
|
171
|
+
border-left: 2px solid var(--router-tab-primary);
|
|
172
|
+
transition: background 0.15s ease, border-left 0.15s ease;
|
|
167
173
|
}
|
|
168
174
|
|
|
169
175
|
&-title {
|
|
170
|
-
min-width:
|
|
171
|
-
max-width:
|
|
176
|
+
min-width: 30px;
|
|
177
|
+
max-width: 120px;
|
|
172
178
|
overflow: hidden;
|
|
173
179
|
white-space: nowrap;
|
|
174
180
|
text-overflow: ellipsis;
|
|
175
181
|
}
|
|
176
182
|
|
|
177
183
|
&-icon {
|
|
178
|
-
margin-right:
|
|
179
|
-
font-size:
|
|
184
|
+
margin-right: 5px;
|
|
185
|
+
font-size: 16px;
|
|
180
186
|
}
|
|
181
187
|
|
|
182
188
|
&:hover,
|
|
183
189
|
&.is-active {
|
|
184
|
-
color: var(--router-tab-
|
|
190
|
+
color: var(--router-tab-primary);
|
|
191
|
+
background-color: color-mix(in srgb, var(--router-tab-primary) 5%, transparent 95%);
|
|
185
192
|
|
|
186
193
|
&.is-closable {
|
|
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
|
-
);
|
|
194
|
+
padding: 0 ($tab-padding - math.div($close-icon-size + $close-icon-margin, 2));
|
|
192
195
|
}
|
|
193
196
|
|
|
194
197
|
.router-tab__item-close {
|
|
195
|
-
width: $
|
|
196
|
-
margin-left: $
|
|
198
|
+
width: $close-icon-size;
|
|
199
|
+
margin-left: $close-icon-margin;
|
|
197
200
|
|
|
198
201
|
&::before,
|
|
199
202
|
&::after {
|
|
@@ -202,11 +205,15 @@
|
|
|
202
205
|
}
|
|
203
206
|
}
|
|
204
207
|
|
|
208
|
+
// FIXED: Enhanced active state
|
|
205
209
|
&.is-active {
|
|
210
|
+
border-bottom: 2px solid var(--router-tab-primary);
|
|
206
211
|
background-color: var(--router-tab-background);
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
212
|
+
font-weight: 500;
|
|
213
|
+
|
|
214
|
+
&:not(:first-child) {
|
|
215
|
+
border-left-color: var(--router-tab-border);
|
|
216
|
+
}
|
|
210
217
|
}
|
|
211
218
|
|
|
212
219
|
&-close {
|
|
@@ -215,12 +222,12 @@
|
|
|
215
222
|
position: relative;
|
|
216
223
|
display: block;
|
|
217
224
|
width: 0;
|
|
218
|
-
height: $
|
|
225
|
+
height: $close-icon-size;
|
|
219
226
|
margin-left: 0;
|
|
220
227
|
overflow: hidden;
|
|
221
228
|
border-radius: 50%;
|
|
222
229
|
cursor: pointer;
|
|
223
|
-
transition:
|
|
230
|
+
transition: $tab-trans;
|
|
224
231
|
background: transparent;
|
|
225
232
|
border: none;
|
|
226
233
|
|
|
@@ -247,7 +254,7 @@
|
|
|
247
254
|
}
|
|
248
255
|
|
|
249
256
|
&:hover {
|
|
250
|
-
background-color: color-mix(in srgb, var(--router-tab-primary) 40%,
|
|
257
|
+
background-color: color-mix(in srgb, var(--router-tab-primary) 40%, #ffffff 60%);
|
|
251
258
|
|
|
252
259
|
&::before,
|
|
253
260
|
&::after {
|
|
@@ -257,31 +264,32 @@
|
|
|
257
264
|
}
|
|
258
265
|
}
|
|
259
266
|
|
|
267
|
+
// FIXED: Improved context menu z-index
|
|
260
268
|
&__contextmenu {
|
|
261
269
|
position: fixed;
|
|
262
|
-
z-index: 1000
|
|
263
|
-
min-width:
|
|
264
|
-
padding:
|
|
265
|
-
font-size:
|
|
270
|
+
z-index: 9999; // Increased from 1000
|
|
271
|
+
min-width: 140px;
|
|
272
|
+
padding: 8px 0;
|
|
273
|
+
font-size: $font-size;
|
|
266
274
|
background: var(--router-tab-background);
|
|
267
275
|
border: 1px solid var(--router-tab-border);
|
|
268
|
-
box-shadow:
|
|
269
|
-
border-radius:
|
|
276
|
+
box-shadow: 0 10px 30px rgba(15, 23, 42, 0.15);
|
|
277
|
+
border-radius: 8px;
|
|
270
278
|
|
|
271
|
-
|
|
279
|
+
a,
|
|
280
|
+
button {
|
|
272
281
|
display: block;
|
|
273
282
|
width: 100%;
|
|
274
|
-
padding:
|
|
275
|
-
line-height:
|
|
283
|
+
padding: 0 16px;
|
|
284
|
+
line-height: 30px;
|
|
276
285
|
text-align: left;
|
|
277
|
-
text-decoration: none;
|
|
278
286
|
background: transparent;
|
|
279
287
|
border: none;
|
|
280
288
|
color: inherit;
|
|
281
289
|
cursor: pointer;
|
|
282
290
|
font: inherit;
|
|
291
|
+
text-decoration: none;
|
|
283
292
|
transition: background-color 0.2s ease-in-out;
|
|
284
|
-
border-radius: calc(#{$router-tab-contextmenu-border-radius} - 4px);
|
|
285
293
|
|
|
286
294
|
&[aria-disabled="true"] {
|
|
287
295
|
color: rgba(148, 163, 184, 0.6);
|
|
@@ -289,10 +297,11 @@
|
|
|
289
297
|
cursor: not-allowed;
|
|
290
298
|
}
|
|
291
299
|
|
|
292
|
-
&:
|
|
293
|
-
&:focus-visible {
|
|
294
|
-
background: color-mix(in srgb, var(--router-tab-primary)
|
|
300
|
+
&:not([aria-disabled="true"]):hover,
|
|
301
|
+
&:not([aria-disabled="true"]):focus-visible {
|
|
302
|
+
background: color-mix(in srgb, var(--router-tab-primary) 15%, transparent 85%);
|
|
295
303
|
color: var(--router-tab-primary);
|
|
304
|
+
outline: none;
|
|
296
305
|
}
|
|
297
306
|
}
|
|
298
307
|
}
|
|
@@ -301,47 +310,32 @@
|
|
|
301
310
|
position: relative;
|
|
302
311
|
flex: 1 1 auto;
|
|
303
312
|
background-color: var(--router-tab-background);
|
|
304
|
-
|
|
313
|
+
border: 1px solid var(--router-tab-border);
|
|
314
|
+
border-top: none; // FIXED: Avoid double border with header
|
|
305
315
|
}
|
|
306
316
|
}
|
|
307
|
-
.router-tab__item.is-active:after {
|
|
308
|
-
display: none !important;
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
.router-tab__item.is-active + .router-tab__item {
|
|
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
317
|
|
|
321
|
-
|
|
322
|
-
.router-
|
|
323
|
-
|
|
324
|
-
|
|
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
|
-
}
|
|
318
|
+
// FIXED: Proper adjacent tab borders
|
|
319
|
+
.router-tab__item {
|
|
320
|
+
+ .router-tab__item {
|
|
321
|
+
border-left-color: var(--router-tab-border);
|
|
322
|
+
}
|
|
333
323
|
|
|
334
|
-
.router-
|
|
335
|
-
|
|
336
|
-
|
|
324
|
+
&.is-active + .router-tab__item {
|
|
325
|
+
border-left-color: var(--router-tab-border);
|
|
326
|
+
}
|
|
337
327
|
}
|
|
338
328
|
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
329
|
+
// NEW: Smooth drag animations
|
|
330
|
+
@keyframes router-tab-drag-hint {
|
|
331
|
+
0%, 100% {
|
|
332
|
+
transform: translateY(0);
|
|
333
|
+
}
|
|
334
|
+
50% {
|
|
335
|
+
transform: translateY(-2px);
|
|
336
|
+
}
|
|
342
337
|
}
|
|
343
338
|
|
|
344
|
-
.router-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
}
|
|
339
|
+
.router-tab__item.is-dragging {
|
|
340
|
+
animation: router-tab-drag-hint 0.3s ease-in-out;
|
|
341
|
+
}
|
package/lib/scss/variables.scss
CHANGED
|
@@ -26,7 +26,7 @@ $router-tab-icon-size: 14px !default;
|
|
|
26
26
|
$router-tab-icon-margin: 6px !default;
|
|
27
27
|
|
|
28
28
|
// Close icon
|
|
29
|
-
$router-tab-close-icon-size:
|
|
29
|
+
$router-tab-close-icon-size: 12px !default;
|
|
30
30
|
$router-tab-close-icon-margin: 4px !default;
|
|
31
31
|
|
|
32
32
|
// Context menu
|
package/lib/theme.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
const STYLE_KEY = 'tab-theme-style'
|
|
2
2
|
const PRIMARY_KEY = 'tab-theme-primary-color'
|
|
3
3
|
const DEFAULT_STYLE: 'light' | 'dark' | 'system' = 'system'
|
|
4
|
-
const DEFAULT_PRIMARY = '#
|
|
4
|
+
const DEFAULT_PRIMARY = '#0f172a'
|
|
5
5
|
const MEDIA_QUERY = '(prefers-color-scheme: dark)'
|
|
6
6
|
|
|
7
7
|
let mediaListener: ((event: MediaQueryListEvent) => void) | null = null
|