vue-stack-tabs 1.1.0 → 1.3.1

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.
Files changed (115) hide show
  1. package/README.en.md +526 -0
  2. package/README.md +141 -39
  3. package/README.ru.md +526 -0
  4. package/dist/iframe-bridge.d.ts +8 -0
  5. package/dist/iframe-bridge.html +77 -0
  6. package/dist/iframe-bridge.mjs +20 -0
  7. package/dist/iframe-child.html +19 -6
  8. package/dist/nuxt/favicon.svg +10 -0
  9. package/dist/nuxt/iframe-bridge.html +77 -0
  10. package/dist/nuxt/iframe-child.html +88 -0
  11. package/dist/nuxt/module.d.ts +6 -0
  12. package/dist/nuxt/module.mjs +28 -0
  13. package/dist/nuxt/plugin.mjs +9 -0
  14. package/dist/nuxt/runtime/plugin.d.ts +2 -0
  15. package/dist/nuxt/runtime/plugin.mjs +9 -0
  16. package/dist/src/lib/StackTabs.vue.d.ts +30 -16
  17. package/dist/src/lib/components/ContextMenu/ContextMenuItem.vue.d.ts +7 -1
  18. package/dist/src/lib/components/ContextMenu/index.vue.d.ts +5 -4
  19. package/dist/src/lib/components/PageLoading.vue.d.ts +2 -1
  20. package/dist/src/lib/components/StackKeepAlive/StackCacheRenderer.vue.d.ts +25 -0
  21. package/dist/src/lib/components/StackKeepAlive/StackKeepAlive.vue.d.ts +12 -0
  22. package/dist/src/lib/components/TabHeader/TabHeaderButton.vue.d.ts +11 -12
  23. package/dist/src/lib/components/TabHeader/TabHeaderItem.vue.d.ts +5 -4
  24. package/dist/src/lib/components/TabHeader/TabHeaderScroll.vue.d.ts +8 -22
  25. package/dist/src/lib/components/TabHeader/index.vue.d.ts +11 -164
  26. package/dist/src/lib/hooks/stackTabsContext.d.ts +49 -0
  27. package/dist/src/lib/hooks/tabPanel/evict.d.ts +21 -61
  28. package/dist/src/lib/hooks/tabPanel/navigationTransaction.d.ts +35 -0
  29. package/dist/src/lib/hooks/tabPanel/refresh.d.ts +17 -0
  30. package/dist/src/lib/hooks/tabPanel/scroll.d.ts +8 -8
  31. package/dist/src/lib/hooks/tabPanel/session.d.ts +9 -8
  32. package/dist/src/lib/hooks/tabPanel/state.d.ts +20 -72
  33. package/dist/src/lib/hooks/useIframeManager.d.ts +35 -0
  34. package/dist/src/lib/hooks/useTabActions.d.ts +5 -46
  35. package/dist/src/lib/hooks/useTabEventBus.d.ts +22 -9
  36. package/dist/src/lib/hooks/useTabPanel.d.ts +14 -73
  37. package/dist/src/lib/i18n/index.d.ts +6 -4
  38. package/dist/src/lib/i18n/lang/en.d.ts +5 -0
  39. package/dist/src/lib/i18n/lang/zh-CN.d.ts +5 -0
  40. package/dist/src/lib/i18n/stackTabsLocale.d.ts +2 -0
  41. package/dist/src/lib/iframe-bridge.d.ts +8 -0
  42. package/dist/src/lib/iframe.vue.d.ts +2 -1
  43. package/dist/src/lib/index.d.ts +4 -5
  44. package/dist/src/lib/model/TabModel.d.ts +11 -40
  45. package/dist/src/lib/nuxt/module.d.ts +6 -0
  46. package/dist/src/lib/nuxt/runtime/plugin.client.d.ts +2 -0
  47. package/dist/src/lib/nuxt/runtime/plugin.d.ts +2 -0
  48. package/dist/src/lib/utils/iframeBridge.d.ts +11 -3
  49. package/dist/src/lib/utils/stackTabsA11y.d.ts +8 -0
  50. package/dist/src/lib/utils/stackTabsMessage.d.ts +7 -0
  51. package/dist/src/lib/utils/tabInfoEncoder.d.ts +0 -15
  52. package/dist/src/lib/utils/typeGuards.d.ts +2 -0
  53. package/dist/src/lib/utils/urlParser.d.ts +24 -3
  54. package/dist/src/lib/versionLogger.d.ts +1 -0
  55. package/dist/style.css +2 -1
  56. package/dist/utils/iframeBridge.d.ts +39 -0
  57. package/dist/vue-stack-tabs.css +2 -1
  58. package/dist/vue-stack-tabs.es.js +1 -2220
  59. package/package.json +71 -50
  60. package/src/lib/StackTabs.vue +223 -196
  61. package/src/lib/assets/style/stackTab.scss +110 -13
  62. package/src/lib/components/ContextMenu/ContextMenuItem.vue +23 -11
  63. package/src/lib/components/ContextMenu/index.vue +94 -10
  64. package/src/lib/components/PageLoading.vue +9 -7
  65. package/src/lib/components/StackKeepAlive/StackCacheRenderer.vue +70 -0
  66. package/src/lib/components/StackKeepAlive/StackKeepAlive.vue +57 -0
  67. package/src/lib/components/TabHeader/TabHeaderButton.vue +19 -8
  68. package/src/lib/components/TabHeader/TabHeaderItem.vue +74 -17
  69. package/src/lib/components/TabHeader/TabHeaderScroll.vue +47 -25
  70. package/src/lib/components/TabHeader/index.vue +181 -13
  71. package/src/lib/hooks/stackTabsContext.ts +135 -0
  72. package/src/lib/hooks/tabPanel/evict.ts +122 -87
  73. package/src/lib/hooks/tabPanel/navigationTransaction.ts +61 -0
  74. package/src/lib/hooks/tabPanel/refresh.ts +109 -0
  75. package/src/lib/hooks/tabPanel/scroll.ts +57 -40
  76. package/src/lib/hooks/tabPanel/session.ts +84 -22
  77. package/src/lib/hooks/tabPanel/state.ts +41 -44
  78. package/src/lib/hooks/useIframeManager.ts +253 -0
  79. package/src/lib/hooks/useTabActions.ts +64 -30
  80. package/src/lib/hooks/useTabEventBus.ts +29 -19
  81. package/src/lib/hooks/useTabLoading.ts +3 -2
  82. package/src/lib/hooks/useTabPanel.tsx +290 -313
  83. package/src/lib/hooks/useTabRouter.ts +111 -97
  84. package/src/lib/i18n/index.ts +53 -45
  85. package/src/lib/i18n/lang/en.ts +6 -1
  86. package/src/lib/i18n/lang/zh-CN.ts +6 -1
  87. package/src/lib/i18n/stackTabsLocale.ts +8 -0
  88. package/src/lib/iframe-bridge.ts +12 -0
  89. package/src/lib/index.ts +14 -19
  90. package/src/lib/model/TabModel.ts +27 -82
  91. package/src/lib/nuxt/module.d.ts +4 -0
  92. package/src/lib/nuxt/module.ts +8 -3
  93. package/src/lib/nuxt/runtime/plugin.client.ts +1 -0
  94. package/src/lib/nuxt/runtime/plugin.ts +11 -0
  95. package/src/lib/utils/iframeBridge.ts +29 -5
  96. package/src/lib/utils/scrollUtils.ts +9 -4
  97. package/src/lib/utils/stackTabsA11y.ts +12 -0
  98. package/src/lib/utils/stackTabsMessage.ts +99 -0
  99. package/src/lib/utils/tabInfoEncoder.ts +70 -21
  100. package/src/lib/utils/typeGuards.ts +3 -0
  101. package/src/lib/utils/urlParser.ts +144 -14
  102. package/src/lib/versionLogger.ts +7 -19
  103. package/dist/src/lib/hooks/tabPanel/evict.spec.d.ts +0 -1
  104. package/dist/src/lib/hooks/useTabPanel.spec.d.ts +0 -1
  105. package/dist/src/lib/model/TabModel.spec.d.ts +0 -1
  106. package/dist/src/lib/utils/iframeBridge.spec.d.ts +0 -1
  107. package/dist/src/lib/utils/tabInfoEncoder.spec.d.ts +0 -1
  108. package/dist/src/lib/utils/urlParser.spec.d.ts +0 -1
  109. package/dist/vue-stack-tabs.umd.js +0 -1
  110. package/src/lib/hooks/tabPanel/evict.spec.ts +0 -85
  111. package/src/lib/hooks/useTabPanel.spec.ts +0 -34
  112. package/src/lib/model/TabModel.spec.ts +0 -40
  113. package/src/lib/utils/iframeBridge.spec.ts +0 -37
  114. package/src/lib/utils/tabInfoEncoder.spec.ts +0 -32
  115. package/src/lib/utils/urlParser.spec.ts +0 -45
package/README.en.md ADDED
@@ -0,0 +1,526 @@
1
+ # vue-stack-tabs
2
+
3
+ [中文](./README.md) | **English** | [Русский](./README.ru.md)
4
+
5
+ > A Vue 3 multi-tab management library based on Vue Router. Achieves iframe-like TabPanel behavior using Vue scopes — **each tab has its own isolated component scope**.
6
+
7
+ ## Features
8
+
9
+ - **Route-level tabs** — each tab owns independent components and cache
10
+ - **Stack-based in-tab navigation** — forward / backward within a tab, similar to browser history stack
11
+ - **Tab refresh** — refresh single or all tabs with full component instance rebuild
12
+ - **iframe tabs** — embed iframe pages with postMessage communication
13
+ - **Session persistence** — restore last active tabs after browser refresh
14
+ - **Scroll position memory** — automatically restore scroll position when switching tabs
15
+ - **Context menu** — built-in close/refresh actions
16
+ - **i18n** — built-in Chinese & English, extensible
17
+ - **Nuxt 3/4 module** — works out of the box
18
+
19
+ ---
20
+
21
+ ## Installation
22
+
23
+ ```bash
24
+ # npm
25
+ npm install vue-stack-tabs
26
+
27
+ # pnpm
28
+ pnpm add vue-stack-tabs
29
+ ```
30
+
31
+ ---
32
+
33
+ ## Quick Start (Vue 3)
34
+
35
+ ### 1. Register Plugin
36
+
37
+ ```ts
38
+ // main.ts
39
+ import { createApp } from 'vue'
40
+ import VueStackTabs from 'vue-stack-tabs'
41
+ import 'vue-stack-tabs/dist/vue-stack-tabs.css'
42
+ import App from './App.vue'
43
+ import router from './router'
44
+
45
+ const app = createApp(App)
46
+ app.use(router)
47
+ app.use(VueStackTabs)
48
+ app.mount('#app')
49
+ ```
50
+
51
+ ### 2. Configure Routes
52
+
53
+ Tabs depend on Vue Router. You need a parent route to host `<VueStackTabs>`:
54
+
55
+ ```ts
56
+ // router.ts
57
+ import { createRouter, createWebHistory } from 'vue-router'
58
+
59
+ const router = createRouter({
60
+ history: createWebHistory(),
61
+ routes: [
62
+ {
63
+ path: '/',
64
+ component: () => import('./Layout.vue'),
65
+ children: [
66
+ { path: '', component: () => import('./pages/Home.vue') },
67
+ { path: 'about', component: () => import('./pages/About.vue') },
68
+ { path: 'settings', component: () => import('./pages/Settings.vue') },
69
+ // iframe placeholder route (required)
70
+ { path: 'iframe', component: () => import('vue-stack-tabs').then((m) => m.IFrame) }
71
+ ]
72
+ }
73
+ ]
74
+ })
75
+
76
+ export default router
77
+ ```
78
+
79
+ ### 3. Use Component
80
+
81
+ ```vue
82
+ <!-- Layout.vue -->
83
+ <template>
84
+ <div style="width: 100%; height: 100vh">
85
+ <VueStackTabs iframe-path="/iframe" :default-tabs="defaultTabs" :max="20" :contextmenu="true" />
86
+ </div>
87
+ </template>
88
+
89
+ <script setup lang="ts">
90
+ import type { ITabData } from 'vue-stack-tabs'
91
+
92
+ const defaultTabs: ITabData[] = [
93
+ {
94
+ title: 'Home',
95
+ path: '/',
96
+ closable: false,
97
+ refreshable: true
98
+ }
99
+ ]
100
+ </script>
101
+ ```
102
+
103
+ ### 4. Open Tabs
104
+
105
+ ```ts
106
+ import { useTabActions } from 'vue-stack-tabs'
107
+
108
+ const { openTab, closeTab, refreshTab } = useTabActions()
109
+
110
+ // Open a new tab
111
+ openTab({
112
+ id: 'about', // optional, auto-generated if omitted
113
+ title: 'About',
114
+ path: '/about',
115
+ query: { id: '1' } // optional
116
+ })
117
+
118
+ // Close a tab
119
+ closeTab('about')
120
+
121
+ // Refresh a tab
122
+ refreshTab('about')
123
+ ```
124
+
125
+ ---
126
+
127
+ ## Quick Start (Nuxt 3/4)
128
+
129
+ ### 1. Register Module
130
+
131
+ ```ts
132
+ // nuxt.config.ts
133
+ export default defineNuxtConfig({
134
+ modules: ['vue-stack-tabs/nuxt'],
135
+ vueStackTabs: {
136
+ locale: 'en-US'
137
+ },
138
+ css: ['vue-stack-tabs/dist/vue-stack-tabs.css']
139
+ })
140
+ ```
141
+
142
+ ### 2. Use in Layout
143
+
144
+ ```vue
145
+ <!-- layouts/default.vue -->
146
+ <template>
147
+ <div style="width: 100%; height: 100vh">
148
+ <VueStackTabs iframe-path="/iframe" :default-tabs="defaultTabs" />
149
+ </div>
150
+ </template>
151
+
152
+ <script setup lang="ts">
153
+ import type { ITabData } from 'vue-stack-tabs'
154
+
155
+ const defaultTabs: ITabData[] = [{ title: 'Home', path: '/', closable: false, refreshable: true }]
156
+ </script>
157
+ ```
158
+
159
+ ### 3. Use in Pages
160
+
161
+ ```vue
162
+ <!-- pages/index.vue -->
163
+ <template>
164
+ <div>
165
+ <h1>Home</h1>
166
+ <button @click="openTab({ title: 'About', path: '/about' })">Open About Page</button>
167
+ </div>
168
+ </template>
169
+
170
+ <script setup>
171
+ import { useTabActions } from 'vue-stack-tabs'
172
+ const { openTab } = useTabActions()
173
+ </script>
174
+ ```
175
+
176
+ ---
177
+
178
+ ## ESM-only and iframe bridge sub-entry
179
+
180
+ `vue-stack-tabs` only supports ESM import from the current version onward; `require('vue-stack-tabs')` is no longer supported.
181
+
182
+ Main application continues to use the root entry:
183
+
184
+ ```ts
185
+ import VueStackTabs from 'vue-stack-tabs'
186
+ import 'vue-stack-tabs/dist/vue-stack-tabs.css'
187
+ ```
188
+
189
+ For iframe inner pages, use the side-effect-free sub-entry:
190
+
191
+ ```ts
192
+ import { postOpenTab, onRefreshRequest } from 'vue-stack-tabs/iframe-bridge'
193
+
194
+ postOpenTab(
195
+ { title: 'Order Detail', path: '/orders/1' },
196
+ { targetOrigin: 'https://parent.example.com' }
197
+ )
198
+
199
+ const off = onRefreshRequest(() => window.location.reload(), {
200
+ allowedOrigins: ['https://parent.example.com']
201
+ })
202
+ ```
203
+
204
+ When `targetOrigin` is not provided, the iframe page's own `window.location.origin` is used by default, suitable for same-origin parent pages; cross-origin parent pages must explicitly pass `targetOrigin`. In production, it is recommended to explicitly pass both `targetOrigin` and `allowedOrigins`.
205
+
206
+ ---
207
+
208
+ ## iframe Security Policy
209
+
210
+ `VueStackTabs` supports configuring iframe security attributes:
211
+
212
+ ```vue
213
+ <VueStackTabs
214
+ iframe-path="/__iframe"
215
+ iframe-sandbox="allow-scripts allow-forms allow-popups allow-downloads allow-same-origin"
216
+ iframe-referrer-policy="strict-origin-when-cross-origin"
217
+ iframe-allow="fullscreen"
218
+ :iframe-load-timeout="15000"
219
+ />
220
+ ```
221
+
222
+ The default sandbox retains common business page capabilities and is compatibility-first, not strict isolation. For strict isolation, remove `allow-same-origin` and continue reducing sandbox tokens per business needs. If your same-origin business pages cannot work under sandbox, you can pass an empty string to disable sandbox, but this reduces isolation strength.
223
+
224
+ ---
225
+
226
+ ## API Reference
227
+
228
+ ### useTabActions
229
+
230
+ Tab-level operation composable.
231
+
232
+ ```ts
233
+ import { useTabActions } from 'vue-stack-tabs'
234
+
235
+ const {
236
+ openTab, // (tab: ITabData, renew?: boolean) => Promise<string>
237
+ closeTab, // (id: string) => string
238
+ closeAllTabs, // () => void
239
+ refreshTab, // (id: string) => void
240
+ refreshAllTabs, // () => void
241
+ activeTab, // (id: string, isRoute?: boolean) => void
242
+ reset, // () => void
243
+ tabs // Ref<ITabItem[]>
244
+ } = useTabActions()
245
+ ```
246
+
247
+ | Method | Description |
248
+ | ---------------------- | ------------------------------------------------------------------------ |
249
+ | `openTab(tab, renew?)` | Open a new tab. When `renew=true`, clears page stack and reopens if exists |
250
+ | `closeTab(id)` | Close the specified tab, returns newly activated tab ID |
251
+ | `closeAllTabs()` | Close all closable tabs |
252
+ | `refreshTab(id)` | Refresh the specified tab (replaces cache ID, rebuilds component instance) |
253
+ | `refreshAllTabs()` | Refresh all tabs |
254
+ | `activeTab(id)` | Activate the specified tab (switch tab) |
255
+ | `reset()` | Close all tabs and reset state |
256
+
257
+ ---
258
+
259
+ ### useTabRouter
260
+
261
+ Stack-based navigation within a tab. **Can only be used inside page components within a tab**.
262
+
263
+ ```ts
264
+ import { useTabRouter } from 'vue-stack-tabs'
265
+
266
+ const { forward, backward, addScrollTarget } = useTabRouter()
267
+ ```
268
+
269
+ | Method | Description |
270
+ | ----------------------------- | --------------------------------------------------- |
271
+ | `forward(to)` | Navigate forward to a new page within current tab |
272
+ | `backward(to, backQuery?)` | Navigate backward within current tab |
273
+ | `addScrollTarget(selector)` | Register a DOM selector for scroll position memory |
274
+
275
+ #### forward
276
+
277
+ ```ts
278
+ // Navigate to /detail page
279
+ forward({ path: '/detail', query: { id: '123' } })
280
+
281
+ // Push same route onto stack (same route can be pushed multiple times, each with independent cache)
282
+ forward({ path: '/list', query: { page: '2' } })
283
+ ```
284
+
285
+ #### backward
286
+
287
+ ```ts
288
+ // Go back 1 step (default)
289
+ backward(1)
290
+
291
+ // Go back N steps
292
+ backward(3)
293
+
294
+ // Go back to a specific path (auto-lookup from stack)
295
+ backward('/list')
296
+
297
+ // Go back with params (target page receives via props._back)
298
+ backward('/list', { result: 'success', data: { id: 1 } })
299
+ ```
300
+
301
+ **Receiving params in target page:**
302
+
303
+ ```vue
304
+ <script setup>
305
+ // Declare props to receive backward params
306
+ defineProps<{ _back?: { result: string; data: any } }>()
307
+ </script>
308
+ ```
309
+
310
+ ---
311
+
312
+ ### useTabLoading
313
+
314
+ Page loading state control. **Can only be used inside page components within a tab**.
315
+
316
+ ```ts
317
+ import { useTabLoading } from 'vue-stack-tabs'
318
+
319
+ const { openTabLoading, closeTabLoading } = useTabLoading()
320
+
321
+ // Show loading overlay
322
+ openTabLoading()
323
+
324
+ // Close after async operation completes
325
+ fetchData().finally(() => closeTabLoading())
326
+ ```
327
+
328
+ > Loading is automatically closed when the component unmounts — no manual cleanup needed.
329
+
330
+ ---
331
+
332
+ ## Props
333
+
334
+ `<VueStackTabs>` component properties:
335
+
336
+ | Prop | Type | Default | Description |
337
+ | ---------------------- | --------------------------- | ----------------------- | -------------------------------------------------------- |
338
+ | `iframePath` | `string` | **Required** | Path of the iframe placeholder route |
339
+ | `iframeSandbox` | `string` | `allow-scripts allow-forms allow-popups allow-downloads allow-same-origin` | iframe sandbox policy; default is compatibility-first, not strict isolation — remove `allow-same-origin` for strict isolation; pass empty string to disable sandbox (not recommended) |
340
+ | `iframeReferrerPolicy` | `ReferrerPolicy` | `strict-origin-when-cross-origin` | iframe referrerpolicy attribute |
341
+ | `iframeAllow` | `string` | `''` | iframe allow attribute, e.g. `fullscreen` |
342
+ | `iframeLoadTimeout` | `number` | `15000` | iframe load timeout in ms |
343
+ | `defaultTabs` | `ITabData[]` | `[]` | Initial tab list |
344
+ | `max` | `number` | `20` | Maximum number of tabs |
345
+ | `contextmenu` | `boolean \| object` | `true` | Enable context menu |
346
+ | `pageTransition` | `string` | `'stack-tab-swap'` | Page transition animation name for forward navigation |
347
+ | `pageTransitionBack` | `string` | `'stack-tab-swap-back'` | Page transition animation name for backward navigation |
348
+ | `tabTransition` | `string \| TransitionProps` | `'stack-tab-zoom'` | Transition effect for tab add/remove |
349
+ | `tabScrollMode` | `TabScrollMode` | `'both'` | Tab bar scroll mode: `'wheel'` / `'button'` / `'both'` |
350
+ | `width` | `string` | `'100%'` | Container width |
351
+ | `height` | `string` | `'100%'` | Container height |
352
+ | `i18n` | `string` | `'zh-CN'` | i18n locale |
353
+ | `globalScroll` | `boolean` | `false` | Enable page-level scroll memory |
354
+ | `sessionPrefix` | `string` | `''` | sessionStorage key prefix |
355
+ | `iframeAllowedOrigins` | `string[]` | Same origin | Allowed origins for iframe postMessage |
356
+
357
+ ---
358
+
359
+ ## Events
360
+
361
+ | Event | Params | Description |
362
+ | -------------- | -------------- | ---------------------------------- |
363
+ | `onActive` | `(id: string)` | Triggered when a tab is activated |
364
+ | `onPageLoaded` | — | Triggered when page component loads |
365
+
366
+ ---
367
+
368
+ ## iframe Tabs
369
+
370
+ ### Open an iframe Tab
371
+
372
+ ```ts
373
+ openTab({
374
+ title: 'External Page',
375
+ path: 'https://example.com',
376
+ iframe: true
377
+ })
378
+ ```
379
+
380
+ ### Operations Inside iframe Pages
381
+
382
+ Within an iframe tab, you can communicate with the host container via `postMessage` to open new tabs or respond to refresh actions.
383
+
384
+ #### 1. Using Bridge Utilities (Recommended)
385
+
386
+ If the iframe page can reference the library code (or import the tools exported from `iframeBridge.ts`), this approach is recommended:
387
+
388
+ ```ts
389
+ import { postOpenTab, onRefreshRequest } from 'vue-stack-tabs/iframe-bridge'
390
+
391
+ const parentOrigin = 'https://parent.example.com'
392
+
393
+ // Open a tab
394
+ postOpenTab(
395
+ {
396
+ title: 'New Page',
397
+ path: '/detail',
398
+ query: { id: '1' }
399
+ },
400
+ { targetOrigin: parentOrigin }
401
+ )
402
+
403
+ // Listen for refresh requests
404
+ const off = onRefreshRequest(
405
+ () => {
406
+ // Custom refresh logic
407
+ location.reload()
408
+ },
409
+ { allowedOrigins: [parentOrigin] }
410
+ )
411
+ ```
412
+
413
+ #### 2. Native Integration
414
+
415
+ If you cannot import the library utilities or want zero dependencies, use the native API:
416
+
417
+ ```ts
418
+ const parentOrigin = 'https://parent.example.com'
419
+
420
+ // Open a tab
421
+ window.parent.postMessage(
422
+ {
423
+ type: 'vue-stack-tabs:openTab',
424
+ payload: {
425
+ title: 'Native New Page',
426
+ path: '/detail'
427
+ }
428
+ },
429
+ parentOrigin
430
+ )
431
+
432
+ // Listen for refresh requests
433
+ window.addEventListener('message', (ev) => {
434
+ if (ev.origin !== parentOrigin) return
435
+ if (ev.source !== window.parent) return
436
+ if (ev.data?.type === 'vue-stack-tabs:refresh') {
437
+ // Perform refresh
438
+ location.reload()
439
+ }
440
+ })
441
+ ```
442
+
443
+ ### iframe Refresh Modes
444
+
445
+ You can specify refresh behavior via `iframeRefreshMode` when calling `openTab`:
446
+
447
+ - `postMessage` (default): Host sends a `vue-stack-tabs:refresh` message to the iframe; the inner page decides how to refresh (smoother animation).
448
+ - `reload`: Host directly resets the iframe's `src` or `key` to trigger a browser-forced reload (suitable for cross-origin or unmodifiable pages).
449
+
450
+ ---
451
+
452
+ ## i18n
453
+
454
+ Built-in languages: `zh-CN` (Chinese), `en-US` (English).
455
+
456
+ ```ts
457
+ // Switch language
458
+ app.use(VueStackTabs, [{ locale: 'en-US' }])
459
+ ```
460
+
461
+ Custom language pack:
462
+
463
+ ```ts
464
+ app.use(VueStackTabs, [
465
+ {
466
+ locale: 'ja-JP',
467
+ messages: {
468
+ 'VueStackTab.close': 'Close',
469
+ 'VueStackTab.refresh': 'Refresh'
470
+ // ...
471
+ }
472
+ }
473
+ ])
474
+ ```
475
+
476
+ ---
477
+
478
+ ## Type Definitions
479
+
480
+ ```ts
481
+ /** Data passed when opening a tab */
482
+ interface ITabData {
483
+ id?: string // Tab ID (auto-generated UUID if omitted)
484
+ title: string // Tab title
485
+ path: string // Route path or iframe URL
486
+ query?: Record<string, string> // Route params
487
+ closable?: boolean // Whether closable (default true)
488
+ refreshable?: boolean // Whether refreshable (default true)
489
+ iframe?: boolean // Whether it's an iframe tab (default false)
490
+ iframeRefreshMode?: 'postMessage' | 'reload' // iframe refresh method
491
+ }
492
+
493
+ /** Tab bar scroll mode */
494
+ enum TabScrollMode {
495
+ WHEEL = 'wheel',
496
+ BUTTON = 'button',
497
+ BOTH = 'both'
498
+ }
499
+ ```
500
+
501
+ ---
502
+
503
+ ## Project Structure
504
+
505
+ ```
506
+ src/lib/
507
+ ├── StackTabs.vue # Main component
508
+ ├── index.ts # Entry & exports
509
+ ├── hooks/ # Core logic
510
+ │ ├── useTabActions.ts # Public API
511
+ │ ├── useTabRouter.ts # In-tab navigation
512
+ │ ├── useTabLoading.ts # Loading state
513
+ │ └── useTabPanel.tsx # Core engine
514
+ ├── model/TabModel.ts # Type definitions
515
+ ├── components/ # UI components
516
+ ├── nuxt/ # Nuxt module
517
+ └── assets/style/ # Styles
518
+ ```
519
+
520
+ For detailed architecture design, see [`ARCHITECTURE.md`](./ARCHITECTURE.md).
521
+
522
+ ---
523
+
524
+ ## License
525
+
526
+ [LGPL-2.1](./LICENSE)