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.
- package/README.en.md +526 -0
- package/README.md +141 -39
- package/README.ru.md +526 -0
- package/dist/iframe-bridge.d.ts +8 -0
- package/dist/iframe-bridge.html +77 -0
- package/dist/iframe-bridge.mjs +20 -0
- package/dist/iframe-child.html +19 -6
- package/dist/nuxt/favicon.svg +10 -0
- package/dist/nuxt/iframe-bridge.html +77 -0
- package/dist/nuxt/iframe-child.html +88 -0
- package/dist/nuxt/module.d.ts +6 -0
- package/dist/nuxt/module.mjs +28 -0
- package/dist/nuxt/plugin.mjs +9 -0
- package/dist/nuxt/runtime/plugin.d.ts +2 -0
- package/dist/nuxt/runtime/plugin.mjs +9 -0
- package/dist/src/lib/StackTabs.vue.d.ts +30 -16
- package/dist/src/lib/components/ContextMenu/ContextMenuItem.vue.d.ts +7 -1
- package/dist/src/lib/components/ContextMenu/index.vue.d.ts +5 -4
- package/dist/src/lib/components/PageLoading.vue.d.ts +2 -1
- package/dist/src/lib/components/StackKeepAlive/StackCacheRenderer.vue.d.ts +25 -0
- package/dist/src/lib/components/StackKeepAlive/StackKeepAlive.vue.d.ts +12 -0
- package/dist/src/lib/components/TabHeader/TabHeaderButton.vue.d.ts +11 -12
- package/dist/src/lib/components/TabHeader/TabHeaderItem.vue.d.ts +5 -4
- package/dist/src/lib/components/TabHeader/TabHeaderScroll.vue.d.ts +8 -22
- package/dist/src/lib/components/TabHeader/index.vue.d.ts +11 -164
- package/dist/src/lib/hooks/stackTabsContext.d.ts +49 -0
- package/dist/src/lib/hooks/tabPanel/evict.d.ts +21 -61
- package/dist/src/lib/hooks/tabPanel/navigationTransaction.d.ts +35 -0
- package/dist/src/lib/hooks/tabPanel/refresh.d.ts +17 -0
- package/dist/src/lib/hooks/tabPanel/scroll.d.ts +8 -8
- package/dist/src/lib/hooks/tabPanel/session.d.ts +9 -8
- package/dist/src/lib/hooks/tabPanel/state.d.ts +20 -72
- package/dist/src/lib/hooks/useIframeManager.d.ts +35 -0
- package/dist/src/lib/hooks/useTabActions.d.ts +5 -46
- package/dist/src/lib/hooks/useTabEventBus.d.ts +22 -9
- package/dist/src/lib/hooks/useTabPanel.d.ts +14 -73
- package/dist/src/lib/i18n/index.d.ts +6 -4
- package/dist/src/lib/i18n/lang/en.d.ts +5 -0
- package/dist/src/lib/i18n/lang/zh-CN.d.ts +5 -0
- package/dist/src/lib/i18n/stackTabsLocale.d.ts +2 -0
- package/dist/src/lib/iframe-bridge.d.ts +8 -0
- package/dist/src/lib/iframe.vue.d.ts +2 -1
- package/dist/src/lib/index.d.ts +4 -5
- package/dist/src/lib/model/TabModel.d.ts +11 -40
- package/dist/src/lib/nuxt/module.d.ts +6 -0
- package/dist/src/lib/nuxt/runtime/plugin.client.d.ts +2 -0
- package/dist/src/lib/nuxt/runtime/plugin.d.ts +2 -0
- package/dist/src/lib/utils/iframeBridge.d.ts +11 -3
- package/dist/src/lib/utils/stackTabsA11y.d.ts +8 -0
- package/dist/src/lib/utils/stackTabsMessage.d.ts +7 -0
- package/dist/src/lib/utils/tabInfoEncoder.d.ts +0 -15
- package/dist/src/lib/utils/typeGuards.d.ts +2 -0
- package/dist/src/lib/utils/urlParser.d.ts +24 -3
- package/dist/src/lib/versionLogger.d.ts +1 -0
- package/dist/style.css +2 -1
- package/dist/utils/iframeBridge.d.ts +39 -0
- package/dist/vue-stack-tabs.css +2 -1
- package/dist/vue-stack-tabs.es.js +1 -2220
- package/package.json +71 -50
- package/src/lib/StackTabs.vue +223 -196
- package/src/lib/assets/style/stackTab.scss +110 -13
- package/src/lib/components/ContextMenu/ContextMenuItem.vue +23 -11
- package/src/lib/components/ContextMenu/index.vue +94 -10
- package/src/lib/components/PageLoading.vue +9 -7
- package/src/lib/components/StackKeepAlive/StackCacheRenderer.vue +70 -0
- package/src/lib/components/StackKeepAlive/StackKeepAlive.vue +57 -0
- package/src/lib/components/TabHeader/TabHeaderButton.vue +19 -8
- package/src/lib/components/TabHeader/TabHeaderItem.vue +74 -17
- package/src/lib/components/TabHeader/TabHeaderScroll.vue +47 -25
- package/src/lib/components/TabHeader/index.vue +181 -13
- package/src/lib/hooks/stackTabsContext.ts +135 -0
- package/src/lib/hooks/tabPanel/evict.ts +122 -87
- package/src/lib/hooks/tabPanel/navigationTransaction.ts +61 -0
- package/src/lib/hooks/tabPanel/refresh.ts +109 -0
- package/src/lib/hooks/tabPanel/scroll.ts +57 -40
- package/src/lib/hooks/tabPanel/session.ts +84 -22
- package/src/lib/hooks/tabPanel/state.ts +41 -44
- package/src/lib/hooks/useIframeManager.ts +253 -0
- package/src/lib/hooks/useTabActions.ts +64 -30
- package/src/lib/hooks/useTabEventBus.ts +29 -19
- package/src/lib/hooks/useTabLoading.ts +3 -2
- package/src/lib/hooks/useTabPanel.tsx +290 -313
- package/src/lib/hooks/useTabRouter.ts +111 -97
- package/src/lib/i18n/index.ts +53 -45
- package/src/lib/i18n/lang/en.ts +6 -1
- package/src/lib/i18n/lang/zh-CN.ts +6 -1
- package/src/lib/i18n/stackTabsLocale.ts +8 -0
- package/src/lib/iframe-bridge.ts +12 -0
- package/src/lib/index.ts +14 -19
- package/src/lib/model/TabModel.ts +27 -82
- package/src/lib/nuxt/module.d.ts +4 -0
- package/src/lib/nuxt/module.ts +8 -3
- package/src/lib/nuxt/runtime/plugin.client.ts +1 -0
- package/src/lib/nuxt/runtime/plugin.ts +11 -0
- package/src/lib/utils/iframeBridge.ts +29 -5
- package/src/lib/utils/scrollUtils.ts +9 -4
- package/src/lib/utils/stackTabsA11y.ts +12 -0
- package/src/lib/utils/stackTabsMessage.ts +99 -0
- package/src/lib/utils/tabInfoEncoder.ts +70 -21
- package/src/lib/utils/typeGuards.ts +3 -0
- package/src/lib/utils/urlParser.ts +144 -14
- package/src/lib/versionLogger.ts +7 -19
- package/dist/src/lib/hooks/tabPanel/evict.spec.d.ts +0 -1
- package/dist/src/lib/hooks/useTabPanel.spec.d.ts +0 -1
- package/dist/src/lib/model/TabModel.spec.d.ts +0 -1
- package/dist/src/lib/utils/iframeBridge.spec.d.ts +0 -1
- package/dist/src/lib/utils/tabInfoEncoder.spec.d.ts +0 -1
- package/dist/src/lib/utils/urlParser.spec.d.ts +0 -1
- package/dist/vue-stack-tabs.umd.js +0 -1
- package/src/lib/hooks/tabPanel/evict.spec.ts +0 -85
- package/src/lib/hooks/useTabPanel.spec.ts +0 -34
- package/src/lib/model/TabModel.spec.ts +0 -40
- package/src/lib/utils/iframeBridge.spec.ts +0 -37
- package/src/lib/utils/tabInfoEncoder.spec.ts +0 -32
- 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)
|