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 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 `#635bff`).
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 | undefined = activeId.value, closeOptions: CloseTabOptions = {}) {
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.')
@@ -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
  }
@@ -1,36 +1,57 @@
1
1
  @use "sass:math";
2
2
  @use "sass:color";
3
- @use "variables" as *;
4
3
 
5
- // Theme Setup
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: #{$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};
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-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};
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: #{$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};
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: var(--router-tab-header-height, $router-tab-header-height);
68
+ height: $hd-height;
48
69
  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;
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: var(--router-tab-header-height, $router-tab-header-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 var(--router-tab-padding, $router-tab-padding);
141
+ padding: 0 $tab-padding;
127
142
  color: inherit;
128
- font-size: var(--router-tab-font-size, $router-tab-font-size);
143
+ font-size: $font-size;
129
144
  transform-origin: left bottom;
130
145
  cursor: pointer;
131
- transition: var(--router-tab-transition, $router-tab-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 and drop states
163
+ // NEW: Drag states
145
164
  &.is-dragging {
146
- opacity: $router-tab-drag-opacity;
147
- cursor: $router-tab-drag-cursor;
165
+ opacity: 0.5;
166
+ cursor: move;
148
167
  }
149
168
 
150
169
  &.is-drag-over {
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;
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: $router-tab-title-min-width;
171
- max-width: $router-tab-title-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: $router-tab-icon-margin;
179
- font-size: $router-tab-icon-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-active-border);
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: $router-tab-close-icon-size;
196
- margin-left: $router-tab-close-icon-margin;
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
- color: var(--router-tab-active-border);
208
- border-bottom: 2px solid var(--router-tab-active-border);
209
- font-weight: 510;
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: $router-tab-close-icon-size;
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: var(--router-tab-transition, $router-tab-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%, transparent 60%);
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: $router-tab-contextmenu-min-width;
264
- padding: $router-tab-contextmenu-padding;
265
- font-size: var(--router-tab-font-size, $router-tab-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: $router-tab-contextmenu-shadow;
269
- border-radius: $router-tab-contextmenu-border-radius;
276
+ box-shadow: 0 10px 30px rgba(15, 23, 42, 0.15);
277
+ border-radius: 8px;
270
278
 
271
- &-item {
279
+ a,
280
+ button {
272
281
  display: block;
273
282
  width: 100%;
274
- padding: $router-tab-contextmenu-item-padding;
275
- line-height: $router-tab-contextmenu-item-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
- &:hover:not([aria-disabled="true"]),
293
- &:focus-visible {
294
- background: color-mix(in srgb, var(--router-tab-primary) 10%, transparent 90%);
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
- overflow: hidden;
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
- .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
- }
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-tab-swap-enter-from {
335
- opacity: 0;
336
- transform: translateX(10px);
324
+ &.is-active + .router-tab__item {
325
+ border-left-color: var(--router-tab-border);
326
+ }
337
327
  }
338
328
 
339
- .router-tab-swap-leave-to {
340
- opacity: 0;
341
- transform: translateX(-10px);
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-tab-page {
345
- width: 100%;
346
- height: 100%;
347
- }
339
+ .router-tab__item.is-dragging {
340
+ animation: router-tab-drag-hint 0.3s ease-in-out;
341
+ }
@@ -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: 16px !default;
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 = '#635bff'
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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vue3-router-tab",
3
- "version": "1.1.9",
3
+ "version": "1.2.0",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "dist",