vue3-router-tab 1.2.5 → 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.
@@ -106,9 +106,9 @@ function insertTab(tabs: TabRecord[], tab: TabRecord, position: 'last' | 'next',
106
106
  if (exists) return
107
107
 
108
108
  if (position === 'next' && referenceId) {
109
- const idx = tabs.findIndex(item => item.id === referenceId)
110
- if (idx > -1) {
111
- tabs.splice(idx + 1, 0, tab)
109
+ const referenceIndex = tabs.findIndex(item => item.id === referenceId)
110
+ if (referenceIndex !== -1) {
111
+ tabs.splice(referenceIndex + 1, 0, tab)
112
112
  return
113
113
  }
114
114
  }
@@ -211,7 +211,14 @@ export function createRouterTabs(
211
211
 
212
212
  function fallbackAfterClose(closedId: string): RouteLocationRaw | null {
213
213
  const idx = tabs.findIndex(item => item.id === closedId)
214
- const candidate = tabs[idx] || tabs[idx - 1] || tabs[0]
214
+ if (idx === -1) return options.defaultRoute
215
+
216
+ // Priority: next tab -> previous tab -> first available tab
217
+ const nextTab = tabs[idx + 1] // Next tab (after the one being closed)
218
+ const prevTab = tabs[idx - 1] // Previous tab
219
+ const firstTab = tabs.find(tab => tab.id !== closedId) // First available tab (excluding the one being closed)
220
+
221
+ const candidate = nextTab || prevTab || firstTab
215
222
  if (candidate) return candidate.to
216
223
  return options.defaultRoute
217
224
  }
@@ -222,15 +229,20 @@ export function createRouterTabs(
222
229
  throw new Error('[RouterTabs] Unable to close the final tab when keepLastTab is true.')
223
230
  }
224
231
 
232
+ // Calculate fallback route BEFORE removing the tab
233
+ const isClosingActiveTab = activeId.value === id
234
+ const shouldRedirect = isClosingActiveTab && closeOptions.redirect !== null
235
+ const fallbackRoute = shouldRedirect ? (closeOptions.redirect ?? fallbackAfterClose(id)) : null
236
+
237
+
238
+
225
239
  await removeTab(id, { force: closeOptions.force })
226
240
 
241
+ // Only skip redirect if explicitly set to null
227
242
  if (closeOptions.redirect === null) return
228
243
 
229
- if (activeId.value === id) {
230
- const redirect = closeOptions.redirect ?? fallbackAfterClose(id)
231
- if (redirect) await router.replace(redirect)
232
- } else if (closeOptions.redirect) {
233
- await router.replace(closeOptions.redirect)
244
+ if (shouldRedirect && fallbackRoute) {
245
+ await router.replace(fallbackRoute)
234
246
  }
235
247
  }
236
248
 
@@ -255,6 +267,16 @@ export function createRouterTabs(
255
267
 
256
268
  async function refreshTab(id: string | undefined = activeId.value ?? undefined, force = false) {
257
269
  if (!id) return
270
+ const tab = tabs.find(item => item.id === id)
271
+ if (!tab) return
272
+
273
+ if (options.keepAlive && tab.alive) {
274
+ tab.alive = false
275
+ await nextTick()
276
+ tab.alive = true
277
+ await nextTick()
278
+ }
279
+
258
280
  refreshingKey.value = id
259
281
  await nextTick()
260
282
  if (!force) await nextTick()
package/lib/index.ts CHANGED
@@ -11,6 +11,7 @@ import {
11
11
  } from './theme'
12
12
 
13
13
  import type { RouterTabsContext } from './core/types'
14
+ import type { RouterTabsThemeOptions } from './theme'
14
15
 
15
16
  export type {
16
17
  TabRecord,
@@ -22,6 +23,28 @@ export type {
22
23
 
23
24
  export type { RouterTabsThemeOptions } from './theme'
24
25
 
26
+ export interface RouterTabsPluginOptions {
27
+ /**
28
+ * Whether to initialise the theme system automatically during install.
29
+ * Defaults to `true`.
30
+ */
31
+ initTheme?: boolean
32
+ /**
33
+ * Theme options passed to `initRouterTabsTheme` when `initTheme` is enabled.
34
+ */
35
+ themeOptions?: RouterTabsThemeOptions
36
+ /**
37
+ * Global component name used when registering `RouterTab`.
38
+ * Defaults to the component's `name` option or `"RouterTab"`.
39
+ */
40
+ componentName?: string
41
+ /**
42
+ * Global component name used when registering `RouterTabs`.
43
+ * Defaults to the component's `name` option or `"RouterTabs"`.
44
+ */
45
+ tabsComponentName?: string
46
+ }
47
+
25
48
  export {
26
49
  routerTabsKey,
27
50
  useRouterTabs,
@@ -33,22 +56,42 @@ export {
33
56
  setRouterTabsPrimary
34
57
  }
35
58
 
36
- import "./scss/index.scss";
59
+ export {
60
+ useReactiveTab,
61
+ useLoadingTab,
62
+ useNotificationTab,
63
+ useStatusTab
64
+ } from './useReactiveTab'
65
+
66
+ export type {
67
+ ReactiveTabState,
68
+ ReactiveTabReturn
69
+ } from './useReactiveTab'
70
+
71
+ import './scss/index.scss'
72
+
73
+ let installed = false
37
74
 
38
75
  const plugin: Plugin = {
39
- install(app: App) {
40
- if ((plugin as any)._installed) return
41
- ; (plugin as any)._installed = true
76
+ install(app: App, options?: RouterTabsPluginOptions) {
77
+ if (installed) return
78
+ installed = true
42
79
 
43
- initRouterTabsTheme()
80
+ const {
81
+ initTheme = true,
82
+ themeOptions,
83
+ componentName = RouterTab.name || 'RouterTab',
84
+ tabsComponentName = RouterTabsComponent.name || 'RouterTabs'
85
+ } = options ?? {}
44
86
 
45
- const componentName = RouterTab.name || 'RouterTab'
46
- const persistenceComponentName = RouterTabsComponent.name || 'RouterTabs'
87
+ if (initTheme) {
88
+ initRouterTabsTheme(themeOptions ?? {})
89
+ }
47
90
 
48
91
  app.component(componentName, RouterTab)
49
- app.component(persistenceComponentName, RouterTabsComponent)
92
+ app.component(tabsComponentName, RouterTabsComponent)
50
93
 
51
- if (persistenceComponentName !== 'router-tabs') {
94
+ if (tabsComponentName.toLowerCase() !== 'router-tabs') {
52
95
  app.component('router-tabs', RouterTabsComponent)
53
96
  }
54
97
 
@@ -119,7 +119,7 @@ export function useRouterTabsPersistence(options: RouterTabsPersistenceOptions =
119
119
  } finally {
120
120
  hydrating.value = false
121
121
  }
122
- } else {
122
+ } else if (Object.prototype.hasOwnProperty.call(options, 'fallbackRoute')) {
123
123
  try {
124
124
  hydrating.value = true
125
125
  const fallback = options.fallbackRoute ?? ctrl.options.defaultRoute
@@ -127,6 +127,8 @@ export function useRouterTabsPersistence(options: RouterTabsPersistenceOptions =
127
127
  } finally {
128
128
  hydrating.value = false
129
129
  }
130
+ } else {
131
+ hydrating.value = false
130
132
  }
131
133
 
132
134
  const snapshot = ctrl.snapshot()
@@ -12,6 +12,34 @@
12
12
  --router-tab-header-height: #{$router-tab-header-height};
13
13
  --router-tab-padding: #{$router-tab-padding};
14
14
  --router-tab-font-size: #{$router-tab-font-size};
15
+ --router-tab-transition: #{$router-tab-transition};
16
+
17
+ // Colors (will be overridden by theme system)
18
+ --router-tab-primary: #{$router-tab-primary};
19
+ --router-tab-background: #{$router-tab-bg-light};
20
+ --router-tab-text: #{$router-tab-text-light};
21
+ --router-tab-border: #{$router-tab-border-light};
22
+ --router-tab-header-bg: #{$router-tab-bg-light};
23
+ --router-tab-active-background: #{$router-tab-primary};
24
+ --router-tab-active-text: #ffffff;
25
+ --router-tab-active-border: #{$router-tab-primary};
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};
15
43
  }
16
44
 
17
45
  // =============================================================================
@@ -87,6 +115,7 @@
87
115
  cursor: pointer;
88
116
  user-select: none;
89
117
  transition: var(--router-tab-transition);
118
+ will-change: background-color, color;
90
119
 
91
120
  &:first-child {
92
121
  border-left: 1px solid var(--router-tab-border);
@@ -153,7 +182,7 @@
153
182
  overflow: hidden;
154
183
  border: none;
155
184
  border-radius: 50%;
156
- background: var(--router-tab-button-bg);
185
+ background: var(--router-tab-button-background);
157
186
  color: var(--router-tab-button-color);
158
187
  cursor: pointer;
159
188
  transition: var(--router-tab-transition);
@@ -176,7 +205,7 @@
176
205
  &::after { transform: translateY(-50%) rotate(45deg); }
177
206
 
178
207
  &:hover {
179
- background: var(--router-tab-active-button-bg);
208
+ background: var(--router-tab-active-button-background);
180
209
  color: var(--router-tab-active-button-color);
181
210
  }
182
211
  }
@@ -184,15 +213,21 @@
184
213
  // Show close button on hover/active
185
214
  &:hover,
186
215
  &.is-active {
187
- &.is-closable {
188
- padding-right: calc(var(--router-tab-padding) - #{($router-tab-close-icon-size + $router-tab-close-icon-margin) * 0.5});
189
- }
190
216
 
191
217
  .router-tab__item-close {
192
218
  width: $router-tab-close-icon-size;
193
219
  margin-left: $router-tab-close-icon-margin;
194
220
  }
195
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
+ }
196
231
  }
197
232
 
198
233
  &__container {
@@ -205,7 +240,29 @@
205
240
  .router-tab__container {
206
241
  padding: 10px;
207
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%;
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);
208
264
  }
265
+
209
266
  // Context Menu using button colors
210
267
  .router-tab__contextmenu {
211
268
  position: fixed;
@@ -225,13 +282,14 @@
225
282
  line-height: $router-tab-contextmenu-item-height;
226
283
  text-align: left;
227
284
  text-decoration: none;
228
- background: var(--router-tab-button-bg);
285
+ background: var(--router-tab-button-background);
229
286
  border: none;
230
287
  color: var(--router-tab-button-color);
231
288
  cursor: pointer;
232
289
  font: inherit;
233
290
  border-radius: calc(#{$router-tab-contextmenu-border-radius} - 4px);
234
291
  transition: all 0.2s ease-in-out;
292
+ outline: none;
235
293
 
236
294
  &[aria-disabled="true"] {
237
295
  color: color-mix(in srgb, var(--router-tab-text) 40%);
@@ -239,10 +297,16 @@
239
297
  cursor: not-allowed;
240
298
  }
241
299
 
242
- &:hover:not([aria-disabled="true"]),
300
+ &:focus,
243
301
  &:focus-visible {
244
- 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);
245
309
  color: var(--router-tab-active-button-color);
246
310
  }
247
311
  }
248
- }
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
@@ -46,7 +46,7 @@ const defaultColors: ColorStyle = {
46
46
  activeText: "#ffffff",
47
47
  activeBorder: "#034960",
48
48
 
49
- headerBackground: "#ffff",
49
+ headerBackground: "#ffffff",
50
50
 
51
51
  buttonBackground: "#f8fafc",
52
52
  buttonColor: "#034960",
@@ -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)
@@ -90,6 +106,7 @@ function applyPrimary(color: ColorStyle) {
90
106
  document.documentElement.style.setProperty('--router-tab-active-button-color', color.activeButtonColor ?? defaultColors.activeButtonColor)
91
107
  document.documentElement.style.setProperty('--router-tab-button-background', color.buttonBackground ?? defaultColors.buttonBackground)
92
108
  document.documentElement.style.setProperty('--router-tab-active-button-background', color.activeButtonBackground ?? defaultColors.activeButtonBackground)
109
+ document.documentElement.style.setProperty('--router-tab-icon-color', color.iconColor ?? defaultColors.iconColor)
93
110
  }
94
111
 
95
112
  function applyStyle(style: 'light' | 'dark' | 'system') {
@@ -121,17 +138,36 @@ export function initRouterTabsTheme(options: RouterTabsThemeOptions = {}) {
121
138
 
122
139
  const {
123
140
  styleKey = STYLE_KEY,
141
+ primaryKey = PRIMARY_KEY,
124
142
  defaultStyle = DEFAULT_STYLE,
143
+ defaultPrimary
125
144
  } = options
126
145
 
127
146
  const storedStyle = (window.localStorage.getItem(styleKey) as 'light' | 'dark' | 'system' | null) ?? defaultStyle
128
147
 
129
148
  applyStyle(storedStyle)
130
-
131
- if (storedStyle === 'dark') {
132
- 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
+ })
133
169
  } else {
134
- applyPrimary(defaultColors)
170
+ applyPrimary(basePalette)
135
171
  }
136
172
  }
137
173
 
@@ -0,0 +1,130 @@
1
+ import {
2
+ ref,
3
+ computed,
4
+ isRef,
5
+ isReadonly,
6
+ type Ref,
7
+ type ComputedRef
8
+ } from 'vue'
9
+
10
+ export interface ReactiveTabState {
11
+ title?: string | Ref<string> | ComputedRef<string>
12
+ icon?: string | Ref<string> | ComputedRef<string>
13
+ closable?: boolean | Ref<boolean> | ComputedRef<boolean>
14
+ meta?: any | Ref<any> | ComputedRef<any>
15
+ }
16
+
17
+ export interface ReactiveTabReturn {
18
+ routeTabTitle: Ref<string> | ComputedRef<string>
19
+ routeTabIcon: Ref<string> | ComputedRef<string>
20
+ routeTabClosable: Ref<boolean> | ComputedRef<boolean>
21
+ routeTabMeta: Ref<any> | ComputedRef<any>
22
+ updateTitle: (title: string) => void
23
+ updateIcon: (icon: string) => void
24
+ updateClosable: (closable: boolean) => void
25
+ updateMeta: (meta: any) => void
26
+ }
27
+
28
+ /**
29
+ * Composable for managing reactive tab properties
30
+ * RouterTab will automatically watch these properties and update the tab accordingly
31
+ */
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 } : () => {}
44
+ }
45
+ }
46
+
47
+ if (typeof source === 'function') {
48
+ const getter = source as () => T
49
+ return {
50
+ value: computed(getter),
51
+ update: () => {}
52
+ }
53
+ }
54
+
55
+ const state = ref(
56
+ (source === undefined ? fallback : source) as T
57
+ ) as Ref<T>
58
+
59
+ return {
60
+ value: state,
61
+ update: (value: T) => {
62
+ state.value = value
63
+ }
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, {})
72
+
73
+ return {
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
82
+ }
83
+ }
84
+
85
+ /**
86
+ * Preset for loading state tabs
87
+ */
88
+ export function useLoadingTab(loadingState: Ref<boolean>, baseTitle = 'Page') {
89
+ return useReactiveTab({
90
+ title: computed(() => loadingState.value ? 'Loading...' : baseTitle),
91
+ icon: computed(() => loadingState.value ? 'mdi-loading mdi-spin' : 'mdi-page'),
92
+ closable: computed(() => !loadingState.value)
93
+ })
94
+ }
95
+
96
+ /**
97
+ * Preset for notification-aware tabs
98
+ */
99
+ export function useNotificationTab(
100
+ count: Ref<number>,
101
+ baseTitle = 'Page',
102
+ baseIcon = 'mdi-page'
103
+ ) {
104
+ return useReactiveTab({
105
+ title: computed(() => count.value > 0 ? `${baseTitle} (${count.value})` : baseTitle),
106
+ icon: computed(() => count.value > 0 ? 'mdi-bell-badge' : baseIcon)
107
+ })
108
+ }
109
+
110
+ /**
111
+ * Preset for status-aware tabs
112
+ */
113
+ export function useStatusTab(
114
+ status: Ref<'normal' | 'loading' | 'error' | 'success'>,
115
+ baseTitle = 'Page'
116
+ ) {
117
+ const statusConfig = {
118
+ normal: { suffix: '', icon: 'mdi-page' },
119
+ loading: { suffix: ' - Loading', icon: 'mdi-loading mdi-spin' },
120
+ error: { suffix: ' - Error', icon: 'mdi-alert' },
121
+ success: { suffix: ' - Success', icon: 'mdi-check-circle' }
122
+ }
123
+
124
+ return useReactiveTab({
125
+ title: computed(() => baseTitle + statusConfig[status.value].suffix),
126
+ icon: computed(() => statusConfig[status.value].icon),
127
+ closable: computed(() => status.value !== 'loading')
128
+ })
129
+ }
130
+
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vue3-router-tab",
3
- "version": "1.2.5",
3
+ "version": "1.2.7",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "dist",
@@ -20,7 +20,8 @@
20
20
  "./package.json": "./package.json"
21
21
  },
22
22
  "scripts": {
23
- "build": "vite build"
23
+ "build": "vite build",
24
+ "prepublishOnly": "npm run build"
24
25
  },
25
26
  "devDependencies": {
26
27
  "@vitejs/plugin-vue": "^6.0.1",
@@ -46,7 +47,11 @@
46
47
  ],
47
48
  "license": "MIT",
48
49
  "homepage": "https://github.com/anilshr25/vue3-router-tab/#readme",
49
- "description": "Simple Confirm Dialog verification plugin with Vue 3.",
50
+ "description": "Vue 3 Router Tabs component: tabbed navigation for Vue Router with persistence, context menu, drag-and-drop reordering, theming, and reactive page-driven tab titles.",
51
+ "sideEffects": [
52
+ "dist/vue3-router-tab.css",
53
+ "lib/scss/index.scss"
54
+ ],
50
55
  "repository": {
51
56
  "type": "git",
52
57
  "url": "https://github.com/anilshr25/vue3-router-tab.git"