vue3-router-tab 1.2.6 → 1.2.7

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.
@@ -23,7 +23,23 @@
23
23
  --router-tab-active-background: #{$router-tab-primary};
24
24
  --router-tab-active-text: #ffffff;
25
25
  --router-tab-active-border: #{$router-tab-primary};
26
- --router-tab-icon-color: #475569;
26
+ --router-tab-icon-color: #{$router-tab-icon-color-light};
27
+ --router-tab-button-background: #{$router-tab-button-bg-light};
28
+ --router-tab-button-color: #{$router-tab-button-color-light};
29
+ --router-tab-active-button-background: #{$router-tab-active-button-bg-light};
30
+ --router-tab-active-button-color: #{$router-tab-active-button-color-light};
31
+ }
32
+
33
+ :root[data-theme="dark"] {
34
+ --router-tab-background: #{$router-tab-bg-dark};
35
+ --router-tab-text: #{$router-tab-text-dark};
36
+ --router-tab-border: #{$router-tab-border-dark};
37
+ --router-tab-header-bg: #{$router-tab-bg-dark};
38
+ --router-tab-icon-color: #{$router-tab-icon-color-dark};
39
+ --router-tab-button-background: #{$router-tab-button-bg-dark};
40
+ --router-tab-button-color: #{$router-tab-button-color-dark};
41
+ --router-tab-active-button-background: #{$router-tab-active-button-bg-dark};
42
+ --router-tab-active-button-color: #{$router-tab-active-button-color-dark};
27
43
  }
28
44
 
29
45
  // =============================================================================
@@ -99,6 +115,7 @@
99
115
  cursor: pointer;
100
116
  user-select: none;
101
117
  transition: var(--router-tab-transition);
118
+ will-change: background-color, color;
102
119
 
103
120
  &:first-child {
104
121
  border-left: 1px solid var(--router-tab-border);
@@ -165,7 +182,7 @@
165
182
  overflow: hidden;
166
183
  border: none;
167
184
  border-radius: 50%;
168
- background: var(--router-tab-button-bg);
185
+ background: var(--router-tab-button-background);
169
186
  color: var(--router-tab-button-color);
170
187
  cursor: pointer;
171
188
  transition: var(--router-tab-transition);
@@ -188,7 +205,7 @@
188
205
  &::after { transform: translateY(-50%) rotate(45deg); }
189
206
 
190
207
  &:hover {
191
- background: var(--router-tab-active-button-bg);
208
+ background: var(--router-tab-active-button-background);
192
209
  color: var(--router-tab-active-button-color);
193
210
  }
194
211
  }
@@ -196,15 +213,21 @@
196
213
  // Show close button on hover/active
197
214
  &:hover,
198
215
  &.is-active {
199
- &.is-closable {
200
- padding-right: calc(var(--router-tab-padding) - #{($router-tab-close-icon-size + $router-tab-close-icon-margin) * 0.5});
201
- }
202
216
 
203
217
  .router-tab__item-close {
204
218
  width: $router-tab-close-icon-size;
205
219
  margin-left: $router-tab-close-icon-margin;
206
220
  }
207
221
  }
222
+
223
+ &.is-dragging {
224
+ opacity: $router-tab-drag-opacity;
225
+ cursor: $router-tab-drag-cursor;
226
+ }
227
+
228
+ &.is-drag-over {
229
+ background-color: color-mix(in srgb, var(--router-tab-primary) 12%, transparent);
230
+ }
208
231
  }
209
232
 
210
233
  &__container {
@@ -217,7 +240,29 @@
217
240
  .router-tab__container {
218
241
  padding: 10px;
219
242
  border: 1px solid var(--router-tab-border);
243
+ transition: var(--router-tab-transition);
244
+ will-change: background-color, border-color;
245
+ }
246
+
247
+ .router-tab__slot-start,
248
+ .router-tab__slot-end {
249
+ display: none;
250
+ align-items: center;
251
+ gap: 0.25rem;
252
+ padding: 0 0.5rem;
253
+ height: 100%;
220
254
  }
255
+
256
+ .router-tab__slot-start.has-content {
257
+ display: flex;
258
+ border-right: 1px solid var(--router-tab-border);
259
+ }
260
+
261
+ .router-tab__slot-end.has-content {
262
+ display: flex;
263
+ border-left: 1px solid var(--router-tab-border);
264
+ }
265
+
221
266
  // Context Menu using button colors
222
267
  .router-tab__contextmenu {
223
268
  position: fixed;
@@ -237,13 +282,14 @@
237
282
  line-height: $router-tab-contextmenu-item-height;
238
283
  text-align: left;
239
284
  text-decoration: none;
240
- background: var(--router-tab-button-bg);
285
+ background: var(--router-tab-button-background);
241
286
  border: none;
242
287
  color: var(--router-tab-button-color);
243
288
  cursor: pointer;
244
289
  font: inherit;
245
290
  border-radius: calc(#{$router-tab-contextmenu-border-radius} - 4px);
246
291
  transition: all 0.2s ease-in-out;
292
+ outline: none;
247
293
 
248
294
  &[aria-disabled="true"] {
249
295
  color: color-mix(in srgb, var(--router-tab-text) 40%);
@@ -251,10 +297,16 @@
251
297
  cursor: not-allowed;
252
298
  }
253
299
 
254
- &:hover:not([aria-disabled="true"]),
300
+ &:focus,
255
301
  &:focus-visible {
256
- background: var(--router-tab-active-button-bg);
302
+ outline: none;
303
+ }
304
+
305
+ &:hover:not([aria-disabled="true"]),
306
+ &:focus-visible,
307
+ &.is-focused {
308
+ background: var(--router-tab-active-button-background);
257
309
  color: var(--router-tab-active-button-color);
258
310
  }
259
311
  }
260
- }
312
+ }
@@ -18,12 +18,24 @@ $router-tab-padding: 12px !default;
18
18
  $router-tab-font-size: 14px !default;
19
19
 
20
20
  // Title
21
- $router-tab-title-min-width: 60px !default;
21
+ $router-tab-title-min-width: auto !default;
22
22
  $router-tab-title-max-width: 180px !default;
23
23
 
24
24
  // Icons
25
25
  $router-tab-icon-size: 14px !default;
26
26
  $router-tab-icon-margin: 6px !default;
27
+ $router-tab-icon-color-light: #475569 !default;
28
+ $router-tab-icon-color-dark: #cbd5e1 !default;
29
+
30
+ // Buttons
31
+ $router-tab-button-bg-light: #f8fafc !default;
32
+ $router-tab-button-bg-dark: #1f2937 !default;
33
+ $router-tab-button-color-light: #0f172a !default;
34
+ $router-tab-button-color-dark: #f8fafc !default;
35
+ $router-tab-active-button-bg-light: $router-tab-primary !default;
36
+ $router-tab-active-button-bg-dark: #38bdf8 !default;
37
+ $router-tab-active-button-color-light: #ffffff !default;
38
+ $router-tab-active-button-color-dark: #0f172a !default;
27
39
 
28
40
  // Close icon
29
41
  $router-tab-close-icon-size: 12px !default;
@@ -33,7 +45,7 @@ $router-tab-close-icon-margin: 4px !default;
33
45
  $router-tab-contextmenu-min-width: 160px !default;
34
46
  $router-tab-contextmenu-padding: 6px 0 !default;
35
47
  $router-tab-contextmenu-item-height: 32px !default;
36
- $router-tab-contextmenu-item-padding: 0 12px !default;
48
+ $router-tab-contextmenu-item-padding: 5px 12px !default;
37
49
  $router-tab-contextmenu-border-radius: 8px !default;
38
50
  $router-tab-contextmenu-shadow: 0 2px 8px rgba(0, 0, 0, 0.1) !default;
39
51
 
package/lib/theme.ts CHANGED
@@ -76,6 +76,22 @@ const defaultDarkColor: ColorStyle = {
76
76
  iconColor: "#cbd5e1",
77
77
  }
78
78
 
79
+ function readStoredPrimary(key: string): Partial<ColorStyle> | null {
80
+ if (typeof window === 'undefined') return null
81
+ const raw = window.localStorage.getItem(key)
82
+ if (!raw) return null
83
+
84
+ try {
85
+ const parsed = JSON.parse(raw) as Partial<ColorStyle>
86
+ return parsed && typeof parsed === 'object' ? parsed : null
87
+ } catch (error) {
88
+ if (import.meta.env?.DEV) {
89
+ console.warn('[RouterTabs] Failed to parse stored primary color palette', error)
90
+ }
91
+ return null
92
+ }
93
+ }
94
+
79
95
  function applyPrimary(color: ColorStyle) {
80
96
  if (typeof document === 'undefined') return
81
97
  document.documentElement.style.setProperty('--router-tab-primary', color.primary ?? defaultColors.primary)
@@ -122,17 +138,36 @@ export function initRouterTabsTheme(options: RouterTabsThemeOptions = {}) {
122
138
 
123
139
  const {
124
140
  styleKey = STYLE_KEY,
141
+ primaryKey = PRIMARY_KEY,
125
142
  defaultStyle = DEFAULT_STYLE,
143
+ defaultPrimary
126
144
  } = options
127
145
 
128
146
  const storedStyle = (window.localStorage.getItem(styleKey) as 'light' | 'dark' | 'system' | null) ?? defaultStyle
129
147
 
130
148
  applyStyle(storedStyle)
131
-
132
- if (storedStyle === 'dark') {
133
- applyPrimary(defaultDarkColor)
149
+
150
+ const prefersDark =
151
+ storedStyle === 'dark' ||
152
+ (storedStyle === 'system' && window.matchMedia(MEDIA_QUERY).matches)
153
+
154
+ const basePalette = prefersDark
155
+ ? { ...defaultDarkColor }
156
+ : { ...defaultColors }
157
+
158
+ if (defaultPrimary) {
159
+ basePalette.primary = defaultPrimary
160
+ }
161
+
162
+ const storedPrimary = readStoredPrimary(primaryKey)
163
+
164
+ if (storedPrimary) {
165
+ applyPrimary({
166
+ ...basePalette,
167
+ ...storedPrimary
168
+ })
134
169
  } else {
135
- applyPrimary(defaultColors)
170
+ applyPrimary(basePalette)
136
171
  }
137
172
  }
138
173
 
@@ -1,4 +1,11 @@
1
- import { ref, computed, type Ref, type ComputedRef } from 'vue'
1
+ import {
2
+ ref,
3
+ computed,
4
+ isRef,
5
+ isReadonly,
6
+ type Ref,
7
+ type ComputedRef
8
+ } from 'vue'
2
9
 
3
10
  export interface ReactiveTabState {
4
11
  title?: string | Ref<string> | ComputedRef<string>
@@ -22,64 +29,56 @@ export interface ReactiveTabReturn {
22
29
  * Composable for managing reactive tab properties
23
30
  * RouterTab will automatically watch these properties and update the tab accordingly
24
31
  */
25
- export function useReactiveTab(initialState: ReactiveTabState = {}): ReactiveTabReturn {
26
- // Create reactive references
27
- const tabTitle = ref(initialState.title || 'Untitled')
28
- const tabIcon = ref(initialState.icon || 'mdi-tab')
29
- const tabClosable = ref(initialState.closable !== false)
30
- const tabMeta = ref(initialState.meta || {})
31
-
32
- // Convert to computed if needed
33
- const routeTabTitle = typeof initialState.title === 'function'
34
- ? computed(initialState.title as () => string)
35
- : tabTitle
36
-
37
- const routeTabIcon = typeof initialState.icon === 'function'
38
- ? computed(initialState.icon as () => string)
39
- : tabIcon
40
-
41
- const routeTabClosable = typeof initialState.closable === 'function'
42
- ? computed(initialState.closable as () => boolean)
43
- : tabClosable
44
-
45
- const routeTabMeta = typeof initialState.meta === 'function'
46
- ? computed(initialState.meta as () => any)
47
- : tabMeta
48
-
49
- // Update functions
50
- const updateTitle = (title: string) => {
51
- if ('value' in tabTitle) {
52
- tabTitle.value = title
32
+ function resolveReactiveProp<T>(
33
+ source: T | Ref<T> | ComputedRef<T> | (() => T) | undefined,
34
+ fallback: T
35
+ ): {
36
+ value: Ref<T> | ComputedRef<T>
37
+ update: (value: T) => void
38
+ } {
39
+ if (isRef(source)) {
40
+ const writable = !isReadonly(source)
41
+ return {
42
+ value: source,
43
+ update: writable ? (value: T) => { (source as Ref<T>).value = value } : () => {}
53
44
  }
54
45
  }
55
46
 
56
- const updateIcon = (icon: string) => {
57
- if ('value' in tabIcon) {
58
- tabIcon.value = icon
47
+ if (typeof source === 'function') {
48
+ const getter = source as () => T
49
+ return {
50
+ value: computed(getter),
51
+ update: () => {}
59
52
  }
60
53
  }
61
54
 
62
- const updateClosable = (closable: boolean) => {
63
- if ('value' in tabClosable) {
64
- tabClosable.value = closable
65
- }
66
- }
55
+ const state = ref(
56
+ (source === undefined ? fallback : source) as T
57
+ ) as Ref<T>
67
58
 
68
- const updateMeta = (meta: any) => {
69
- if ('value' in tabMeta) {
70
- tabMeta.value = meta
59
+ return {
60
+ value: state,
61
+ update: (value: T) => {
62
+ state.value = value
71
63
  }
72
64
  }
65
+ }
66
+
67
+ export function useReactiveTab(initialState: ReactiveTabState = {}): ReactiveTabReturn {
68
+ const title = resolveReactiveProp(initialState.title, 'Untitled')
69
+ const icon = resolveReactiveProp(initialState.icon, '')
70
+ const closable = resolveReactiveProp(initialState.closable, true)
71
+ const meta = resolveReactiveProp(initialState.meta, {})
73
72
 
74
73
  return {
75
- routeTabTitle,
76
- routeTabIcon,
77
- routeTabClosable,
78
- routeTabMeta,
79
- updateTitle,
80
- updateIcon,
81
- updateClosable,
82
- updateMeta
74
+ routeTabTitle: title.value,
75
+ routeTabIcon: icon.value,
76
+ routeTabClosable: closable.value,
77
+ routeTabMeta: meta.value,
78
+ updateTitle: title.update,
79
+ updateIcon: icon.update,
80
+ updateClosable: closable.update,
81
+ updateMeta: meta.update
83
82
  }
84
83
  }
85
84
 
@@ -127,4 +126,5 @@ export function useStatusTab(
127
126
  icon: computed(() => statusConfig[status.value].icon),
128
127
  closable: computed(() => status.value !== 'loading')
129
128
  })
130
- }
129
+ }
130
+
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vue3-router-tab",
3
- "version": "1.2.6",
3
+ "version": "1.2.7",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "dist",