vue-stack-tabs 1.3.1 → 1.4.2
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 +119 -52
- package/README.md +103 -42
- package/README.ru.md +125 -54
- package/dist/{src/lib/StackTabs.vue.d.ts → StackTabs.vue.d.ts} +5 -5
- package/dist/{src/lib/components → components}/ContextMenu/index.vue.d.ts +1 -1
- package/dist/components/StackKeepAlive/emptyPlaceholder.d.ts +3 -0
- package/dist/{src/lib/components → components}/TabHeader/TabHeaderItem.vue.d.ts +9 -0
- package/dist/{src/lib/components → components}/TabHeader/index.vue.d.ts +3 -3
- package/dist/{src/lib/hooks → hooks}/stackTabsContext.d.ts +5 -2
- package/dist/hooks/tabPanel/pageComponentFactory.d.ts +23 -0
- package/dist/{src/lib/hooks → hooks}/tabPanel/refresh.d.ts +6 -3
- package/dist/{src/lib/hooks → hooks}/tabPanel/scroll.d.ts +2 -0
- package/dist/hooks/tabPanel/tabStore.d.ts +69 -0
- package/dist/hooks/useScrollShadow.d.ts +20 -0
- package/dist/{src/lib/hooks → hooks}/useTabActions.d.ts +7 -2
- package/dist/{src/lib/hooks → hooks}/useTabEventBus.d.ts +2 -1
- package/dist/{src/lib/hooks → hooks}/useTabPanel.d.ts +16 -4
- package/dist/iframe-bridge.mjs +1 -1
- package/dist/index.d.ts +22 -6
- package/dist/{src/lib/model → model}/TabModel.d.ts +21 -0
- package/dist/nuxt/module.mjs +10 -6
- package/dist/nuxt/runtime/plugin.mjs +1600 -4
- package/dist/style.css +1 -1
- package/dist/utils/iframeBridge.d.ts +7 -1
- package/dist/{src/lib/utils → utils}/scrollUtils.d.ts +7 -1
- package/dist/{src/lib/versionLogger.d.ts → versionLogger.d.ts} +1 -1
- package/dist/vue-stack-tabs.css +1 -1
- package/dist/vue-stack-tabs.es.js +1 -1
- package/package.json +64 -40
- package/src/lib/StackTabs.vue +16 -3
- package/src/lib/assets/style/common/var.scss +2 -0
- package/src/lib/assets/style/stackTab.scss +17 -3
- package/src/lib/components/ContextMenu/index.vue +12 -3
- package/src/lib/components/PageLoading.vue +10 -2
- package/src/lib/components/StackKeepAlive/StackCacheRenderer.vue +3 -9
- package/src/lib/components/StackKeepAlive/emptyPlaceholder.tsx +18 -0
- package/src/lib/components/TabHeader/index.vue +4 -3
- package/src/lib/hooks/stackTabsContext.ts +23 -18
- package/src/lib/hooks/tabPanel/pageComponentFactory.tsx +136 -0
- package/src/lib/hooks/tabPanel/refresh.ts +20 -13
- package/src/lib/hooks/tabPanel/scroll.ts +110 -7
- package/src/lib/hooks/tabPanel/session.ts +29 -3
- package/src/lib/hooks/tabPanel/tabStore.ts +595 -0
- package/src/lib/hooks/useContextMenu.ts +57 -11
- package/src/lib/hooks/useIframeManager.ts +18 -2
- package/src/lib/hooks/useScrollShadow.ts +124 -0
- package/src/lib/hooks/useTabActions.ts +18 -8
- package/src/lib/hooks/useTabEventBus.ts +5 -3
- package/src/lib/hooks/useTabPanel.tsx +183 -302
- package/src/lib/hooks/useTabRouter.ts +42 -38
- package/src/lib/i18n/index.ts +3 -3
- package/src/lib/model/TabModel.ts +40 -1
- package/src/lib/nuxt/module.ts +10 -3
- package/src/lib/nuxt/runtime/plugin.ts +13 -2
- package/src/lib/utils/iframeBridge.ts +9 -1
- package/src/lib/utils/scrollUtils.ts +35 -2
- package/src/lib/utils/tabInfoEncoder.ts +21 -8
- package/src/lib/utils/urlParser.ts +3 -1
- package/src/lib/versionLogger.ts +6 -2
- package/dist/favicon.svg +0 -10
- package/dist/iframe-bridge.html +0 -77
- package/dist/iframe-child.html +0 -88
- package/dist/nuxt/favicon.svg +0 -10
- package/dist/nuxt/iframe-bridge.html +0 -77
- package/dist/nuxt/iframe-child.html +0 -88
- package/dist/nuxt/plugin.mjs +0 -9
- package/dist/src/lib/iframe-bridge.d.ts +0 -8
- package/dist/src/lib/index.d.ts +0 -22
- package/dist/src/lib/nuxt/module.d.ts +0 -6
- package/dist/src/lib/nuxt/runtime/plugin.client.d.ts +0 -2
- package/dist/src/lib/nuxt/runtime/plugin.d.ts +0 -2
- package/dist/src/lib/utils/iframeBridge.d.ts +0 -39
- package/src/lib/nuxt/runtime/plugin.client.ts +0 -10
- /package/dist/{src/lib/components → components}/ContextMenu/ContextMenuItem.vue.d.ts +0 -0
- /package/dist/{src/lib/components → components}/PageLoading.vue.d.ts +0 -0
- /package/dist/{src/lib/components → components}/StackKeepAlive/StackCacheRenderer.vue.d.ts +0 -0
- /package/dist/{src/lib/components → components}/StackKeepAlive/StackKeepAlive.vue.d.ts +0 -0
- /package/dist/{src/lib/components → components}/TabHeader/TabHeaderButton.vue.d.ts +0 -0
- /package/dist/{src/lib/components → components}/TabHeader/TabHeaderScroll.vue.d.ts +0 -0
- /package/dist/{src/lib/hooks → hooks}/tabPanel/evict.d.ts +0 -0
- /package/dist/{src/lib/hooks → hooks}/tabPanel/navigationTransaction.d.ts +0 -0
- /package/dist/{src/lib/hooks → hooks}/tabPanel/session.d.ts +0 -0
- /package/dist/{src/lib/hooks → hooks}/tabPanel/state.d.ts +0 -0
- /package/dist/{src/lib/hooks → hooks}/useContextMenu.d.ts +0 -0
- /package/dist/{src/lib/hooks → hooks}/useIframeManager.d.ts +0 -0
- /package/dist/{src/lib/hooks → hooks}/useTabLoading.d.ts +0 -0
- /package/dist/{src/lib/hooks → hooks}/useTabRouter.d.ts +0 -0
- /package/dist/{src/lib/i18n → i18n}/index.d.ts +0 -0
- /package/dist/{src/lib/i18n → i18n}/lang/en.d.ts +0 -0
- /package/dist/{src/lib/i18n → i18n}/lang/zh-CN.d.ts +0 -0
- /package/dist/{src/lib/i18n → i18n}/stackTabsLocale.d.ts +0 -0
- /package/dist/{src/lib/iframe.vue.d.ts → iframe.vue.d.ts} +0 -0
- /package/dist/{src/lib/utils → utils}/stackTabsA11y.d.ts +0 -0
- /package/dist/{src/lib/utils → utils}/stackTabsMessage.d.ts +0 -0
- /package/dist/{src/lib/utils → utils}/tabInfoEncoder.d.ts +0 -0
- /package/dist/{src/lib/utils → utils}/typeGuards.d.ts +0 -0
- /package/dist/{src/lib/utils → utils}/urlParser.d.ts +0 -0
package/README.ru.md
CHANGED
|
@@ -1,8 +1,16 @@
|
|
|
1
1
|
# vue-stack-tabs
|
|
2
2
|
|
|
3
|
+
[](https://www.npmjs.com/package/vue-stack-tabs)
|
|
4
|
+
[](https://www.npmjs.com/package/vue-stack-tabs)
|
|
5
|
+
[](./LICENSE)
|
|
6
|
+
[](https://bundlephobia.com/package/vue-stack-tabs)
|
|
7
|
+
[](https://jktantan.github.io/vue-stack-tabs/)
|
|
8
|
+
|
|
3
9
|
[中文](./README.md) | [English](./README.en.md) | **Русский**
|
|
4
10
|
|
|
5
|
-
|
|
11
|
+
**📖 [Документация](https://jktantan.github.io/vue-stack-tabs/) · 🎮 [Демо](https://jktantan.github.io/vue-stack-tabs/demo.html)**
|
|
12
|
+
|
|
13
|
+
> Библиотека управления вкладками для Vue 3 на основе Vue Router. Она обеспечивает изоляцию вкладок, подобную iframe, через независимые области видимости Vue: каждая вкладка сохраняет собственный экземпляр страницы, кэш и стек навигации.
|
|
6
14
|
|
|
7
15
|
## Возможности
|
|
8
16
|
|
|
@@ -13,6 +21,7 @@
|
|
|
13
21
|
- **Сохранение сессии** — восстановление последних активных вкладок после обновления браузера
|
|
14
22
|
- **Запоминание позиции прокрутки** — автоматическое восстановление позиции прокрутки при переключении вкладок
|
|
15
23
|
- **Контекстное меню** — встроенные действия закрытия/обновления
|
|
24
|
+
- **Слоты** — кнопки слева/справа на панели вкладок, свои плейсхолдеры загрузки/ошибки iframe
|
|
16
25
|
- **Интернационализация** — встроенные китайский и английский языки, расширяемо
|
|
17
26
|
- **Модуль Nuxt 3/4** — работает из коробки
|
|
18
27
|
|
|
@@ -38,7 +47,7 @@ pnpm add vue-stack-tabs
|
|
|
38
47
|
// main.ts
|
|
39
48
|
import { createApp } from 'vue'
|
|
40
49
|
import VueStackTabs from 'vue-stack-tabs'
|
|
41
|
-
import 'vue-stack-tabs/dist/
|
|
50
|
+
import 'vue-stack-tabs/dist/style.css'
|
|
42
51
|
import App from './App.vue'
|
|
43
52
|
import router from './router'
|
|
44
53
|
|
|
@@ -48,6 +57,8 @@ app.use(VueStackTabs)
|
|
|
48
57
|
app.mount('#app')
|
|
49
58
|
```
|
|
50
59
|
|
|
60
|
+
> `vue-stack-tabs` поддерживает только один экземпляр `<VueStackTabs>` на приложение Vue. Экспорт по умолчанию — плагин Vue для `app.use(VueStackTabs)`; если нужен локальный импорт компонента, используйте именованный экспорт ниже.
|
|
61
|
+
|
|
51
62
|
### 2. Настройка маршрутов
|
|
52
63
|
|
|
53
64
|
Вкладки зависят от Vue Router. Необходим родительский маршрут для размещения `<VueStackTabs>`:
|
|
@@ -100,6 +111,12 @@ const defaultTabs: ITabData[] = [
|
|
|
100
111
|
</script>
|
|
101
112
|
```
|
|
102
113
|
|
|
114
|
+
Если нужен локальный импорт компонента, используйте именованный экспорт `VueStackTabs`; именованный экспорт `StackTab` сохранён как алиас совместимости:
|
|
115
|
+
|
|
116
|
+
```ts
|
|
117
|
+
import { VueStackTabs as VueStackTabsComponent } from 'vue-stack-tabs'
|
|
118
|
+
```
|
|
119
|
+
|
|
103
120
|
### 4. Открытие вкладок
|
|
104
121
|
|
|
105
122
|
```ts
|
|
@@ -133,9 +150,9 @@ refreshTab('about')
|
|
|
133
150
|
export default defineNuxtConfig({
|
|
134
151
|
modules: ['vue-stack-tabs/nuxt'],
|
|
135
152
|
vueStackTabs: {
|
|
136
|
-
locale: '
|
|
153
|
+
locale: 'zh-CN' // встроенные варианты: 'zh-CN' | 'en'
|
|
137
154
|
},
|
|
138
|
-
css: ['vue-stack-tabs/dist/
|
|
155
|
+
css: ['vue-stack-tabs/dist/style.css']
|
|
139
156
|
})
|
|
140
157
|
```
|
|
141
158
|
|
|
@@ -152,7 +169,9 @@ export default defineNuxtConfig({
|
|
|
152
169
|
<script setup lang="ts">
|
|
153
170
|
import type { ITabData } from 'vue-stack-tabs'
|
|
154
171
|
|
|
155
|
-
const defaultTabs: ITabData[] = [
|
|
172
|
+
const defaultTabs: ITabData[] = [
|
|
173
|
+
{ title: 'Главная', path: '/', closable: false, refreshable: true }
|
|
174
|
+
]
|
|
156
175
|
</script>
|
|
157
176
|
```
|
|
158
177
|
|
|
@@ -163,7 +182,9 @@ const defaultTabs: ITabData[] = [{ title: 'Главная', path: '/', closable:
|
|
|
163
182
|
<template>
|
|
164
183
|
<div>
|
|
165
184
|
<h1>Главная</h1>
|
|
166
|
-
<button @click="openTab({ title: 'О приложении', path: '/about' })"
|
|
185
|
+
<button @click="openTab({ title: 'О приложении', path: '/about' })">
|
|
186
|
+
Открыть страницу «О приложении»
|
|
187
|
+
</button>
|
|
167
188
|
</div>
|
|
168
189
|
</template>
|
|
169
190
|
|
|
@@ -183,7 +204,7 @@ const { openTab } = useTabActions()
|
|
|
183
204
|
|
|
184
205
|
```ts
|
|
185
206
|
import VueStackTabs from 'vue-stack-tabs'
|
|
186
|
-
import 'vue-stack-tabs/dist/
|
|
207
|
+
import 'vue-stack-tabs/dist/style.css'
|
|
187
208
|
```
|
|
188
209
|
|
|
189
210
|
Для iframe-страниц используйте суб-вход без побочных эффектов стилей:
|
|
@@ -219,7 +240,7 @@ const off = onRefreshRequest(() => window.location.reload(), {
|
|
|
219
240
|
/>
|
|
220
241
|
```
|
|
221
242
|
|
|
222
|
-
Sandbox по умолчанию сохраняет типичные возможности бизнес-страниц и ориентирован на совместимость, не на строгую изоляцию. Для строгой изоляции удалите `allow-same-origin` и продолжайте уменьшать токены sandbox по потребностям бизнеса.
|
|
243
|
+
Sandbox по умолчанию сохраняет типичные возможности бизнес-страниц и ориентирован на совместимость, не на строгую изоляцию. Для строгой изоляции удалите `allow-same-origin` и продолжайте уменьшать токены sandbox по потребностям бизнеса. Если ваши страницы того же источника не могут работать под sandbox, можно передать пустую строку для отключения sandbox, но это снижает уровень изоляции.
|
|
223
244
|
|
|
224
245
|
---
|
|
225
246
|
|
|
@@ -233,26 +254,36 @@ Sandbox по умолчанию сохраняет типичные возмож
|
|
|
233
254
|
import { useTabActions } from 'vue-stack-tabs'
|
|
234
255
|
|
|
235
256
|
const {
|
|
236
|
-
openTab, // (tab: ITabData, renew?: boolean) => Promise<string>
|
|
257
|
+
openTab, // (tab: ITabData, renew?: boolean) => Promise<string | undefined>
|
|
237
258
|
closeTab, // (id: string) => string
|
|
238
259
|
closeAllTabs, // () => void
|
|
239
260
|
refreshTab, // (id: string) => void
|
|
240
261
|
refreshAllTabs, // () => void
|
|
241
262
|
activeTab, // (id: string, isRoute?: boolean) => void
|
|
263
|
+
moveTab, // (id: string, targetIndex: number) => boolean
|
|
242
264
|
reset, // () => void
|
|
243
|
-
tabs // Ref<ITabItem[]>
|
|
265
|
+
tabs, // Ref<ITabItem[]>
|
|
266
|
+
setIFramePath, // (path: string) => void
|
|
267
|
+
getWrapper, // ({ tabId?, pageId? }) => HTMLElement | undefined
|
|
268
|
+
openInNewWindow // (id: string) => void
|
|
244
269
|
} = useTabActions()
|
|
245
270
|
```
|
|
246
271
|
|
|
247
|
-
| Метод
|
|
248
|
-
|
|
|
249
|
-
| `openTab(tab, renew?)`
|
|
250
|
-
| `closeTab(id)`
|
|
251
|
-
| `closeAllTabs()`
|
|
252
|
-
| `refreshTab(id)`
|
|
253
|
-
| `refreshAllTabs()`
|
|
254
|
-
| `activeTab(id)`
|
|
255
|
-
| `
|
|
272
|
+
| Метод | Описание |
|
|
273
|
+
| --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ |
|
|
274
|
+
| `openTab(tab, renew?)` | Открыть новую вкладку. При `renew=true` очищает стек страниц и открывает заново |
|
|
275
|
+
| `closeTab(id)` | Закрыть указанную вкладку, возвращает ID новой активной вкладки |
|
|
276
|
+
| `closeAllTabs()` | Закрыть все закрываемые вкладки |
|
|
277
|
+
| `refreshTab(id)` | Обновить указанную вкладку (заменяет ID кэша, пересоздаёт экземпляр компонента) |
|
|
278
|
+
| `refreshAllTabs()` | Обновить все вкладки |
|
|
279
|
+
| `activeTab(id)` | Активировать указанную вкладку (переключить вкладку) |
|
|
280
|
+
| `moveTab(id, targetIndex)` | Переместить вкладку на указанный индекс; панель вкладок также поддерживает перетаскивание |
|
|
281
|
+
| `reset()` | Закрыть все вкладки и сбросить состояние |
|
|
282
|
+
| `setIFramePath(path)` | Установить путь маршрута-заглушки iframe (вызывается внутри компонента) |
|
|
283
|
+
| `getWrapper({ tabId?, pageId? })` | Получить DOM-контейнер содержимого; без параметров вернёт первый контейнер, при нескольких вкладках передавайте `tabId` или `pageId` |
|
|
284
|
+
| `openInNewWindow(id)` | Открыть вкладку iframe в новом окне (fallback, если встраивание невозможно) |
|
|
285
|
+
|
|
286
|
+
> Закрытие слева / справа / остальных — операции только контекстного меню, выполняются внутри `TabHeader`/`ContextMenu` через `useTabPanel`.
|
|
256
287
|
|
|
257
288
|
---
|
|
258
289
|
|
|
@@ -268,9 +299,9 @@ const { forward, backward, addScrollTarget } = useTabRouter()
|
|
|
268
299
|
|
|
269
300
|
| Метод | Описание |
|
|
270
301
|
| --------------------------- | ----------------------------------------------------------- |
|
|
271
|
-
| `forward(to)` | Перейти вперёд к новой странице внутри текущей вкладки
|
|
302
|
+
| `forward(to)` | Перейти вперёд к новой странице внутри текущей вкладки |
|
|
272
303
|
| `backward(to, backQuery?)` | Вернуться назад внутри текущей вкладки |
|
|
273
|
-
| `addScrollTarget(selector)` | Зарегистрировать CSS-селектор DOM для запоминания прокрутки
|
|
304
|
+
| `addScrollTarget(selector)` | Зарегистрировать CSS-селектор DOM для запоминания прокрутки |
|
|
274
305
|
|
|
275
306
|
#### forward
|
|
276
307
|
|
|
@@ -333,35 +364,67 @@ fetchData().finally(() => closeTabLoading())
|
|
|
333
364
|
|
|
334
365
|
Свойства компонента `<VueStackTabs>`:
|
|
335
366
|
|
|
336
|
-
| Свойство | Тип | По умолчанию
|
|
337
|
-
|
|
|
338
|
-
| `iframePath`
|
|
339
|
-
| `iframeSandbox`
|
|
340
|
-
| `iframeReferrerPolicy`
|
|
341
|
-
| `iframeAllow`
|
|
342
|
-
| `iframeLoadTimeout`
|
|
343
|
-
| `defaultTabs`
|
|
344
|
-
| `max`
|
|
345
|
-
| `contextmenu`
|
|
346
|
-
| `pageTransition`
|
|
347
|
-
| `pageTransitionBack`
|
|
348
|
-
| `tabTransition`
|
|
349
|
-
| `tabScrollMode`
|
|
350
|
-
| `
|
|
351
|
-
| `
|
|
352
|
-
| `
|
|
353
|
-
| `
|
|
354
|
-
| `
|
|
355
|
-
| `
|
|
367
|
+
| Свойство | Тип | По умолчанию | Описание |
|
|
368
|
+
| ---------------------- | --------------------------- | -------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
369
|
+
| `iframePath` | `string` | **Обязательно** | Путь маршрута-заглушки для iframe |
|
|
370
|
+
| `iframeSandbox` | `string` | `allow-scripts allow-forms allow-popups allow-downloads allow-same-origin` | Политика sandbox для iframe; по умолчанию совместимость, не строгая изоляция — удалите `allow-same-origin` для строгой изоляции; пустая строка отключает sandbox (не рекомендуется) |
|
|
371
|
+
| `iframeReferrerPolicy` | `ReferrerPolicy` | `strict-origin-when-cross-origin` | Атрибут referrerpolicy для iframe |
|
|
372
|
+
| `iframeAllow` | `string` | `''` | Атрибут allow для iframe, например `fullscreen` |
|
|
373
|
+
| `iframeLoadTimeout` | `number` | `15000` | Тайм-аут загрузки iframe в мс |
|
|
374
|
+
| `defaultTabs` | `ITabData[]` | `[]` | Начальный список вкладок |
|
|
375
|
+
| `max` | `number` | `20` | Максимальное количество вкладок |
|
|
376
|
+
| `contextmenu` | `boolean \| object` | `true` | Включить контекстное меню |
|
|
377
|
+
| `pageTransition` | `string` | `'stack-tab-swap'` | Имя анимации перехода при навигации вперёд |
|
|
378
|
+
| `pageTransitionBack` | `string` | `'stack-tab-swap-back'` | Имя анимации перехода при навигации назад |
|
|
379
|
+
| `tabTransition` | `string \| TransitionProps` | `'stack-tab-zoom'` | Эффект перехода при добавлении/удалении вкладок |
|
|
380
|
+
| `tabScrollMode` | `TabScrollMode` | `'both'` | Режим прокрутки панели вкладок: `'wheel'` / `'button'` / `'both'` |
|
|
381
|
+
| `space` | `number` | `300` | Шаг прокрутки панели вкладок (px) |
|
|
382
|
+
| `width` | `string` | `'100%'` | Ширина контейнера |
|
|
383
|
+
| `height` | `string` | `'100%'` | Высота контейнера |
|
|
384
|
+
| `i18n` | `string` | `'zh-CN'` | Локаль интернационализации, встроенные `'zh-CN'` / `'en'` |
|
|
385
|
+
| `globalScroll` | `boolean` | `false` | Включить запоминание прокрутки на уровне страницы |
|
|
386
|
+
| `sessionPrefix` | `string` | `''` | Префикс ключа sessionStorage; полезно для нескольких экземпляров на одном источнике |
|
|
387
|
+
| `iframeAllowedOrigins` | `string[]` | `[]` (плюс текущий источник) | Разрешённые источники для iframe postMessage |
|
|
356
388
|
|
|
357
389
|
---
|
|
358
390
|
|
|
359
391
|
## События (Events)
|
|
360
392
|
|
|
361
|
-
| Событие | Параметры | Описание
|
|
362
|
-
| -------------- | -------------- |
|
|
363
|
-
| `onActive` | `(id: string)` | Срабатывает при активации вкладки
|
|
364
|
-
| `onPageLoaded` | — | Срабатывает при загрузке компонента страницы
|
|
393
|
+
| Событие | Параметры | Описание |
|
|
394
|
+
| -------------- | -------------- | -------------------------------------------- |
|
|
395
|
+
| `onActive` | `(id: string)` | Срабатывает при активации вкладки |
|
|
396
|
+
| `onPageLoaded` | — | Срабатывает при загрузке компонента страницы |
|
|
397
|
+
|
|
398
|
+
```vue
|
|
399
|
+
<VueStackTabs
|
|
400
|
+
iframe-path="/iframe"
|
|
401
|
+
@on-active="(id) => console.log('active:', id)"
|
|
402
|
+
@on-page-loaded="() => console.log('page loaded')"
|
|
403
|
+
/>
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
---
|
|
407
|
+
|
|
408
|
+
## Слоты
|
|
409
|
+
|
|
410
|
+
| Слот | Параметры слота | Описание |
|
|
411
|
+
| --------------- | ---------------- | -------------------------------------------------------------------- |
|
|
412
|
+
| `leftButton` | — | Левая область панели вкладок, обычно с `TabHeaderButton` |
|
|
413
|
+
| `rightButton` | — | Правая область панели вкладок |
|
|
414
|
+
| `iframeLoading` | `{ tab }` | Свой плейсхолдер загрузки для вкладки iframe |
|
|
415
|
+
| `iframeError` | `{ tab, retry }` | Свой плейсхолдер ошибки для вкладки iframe; `retry()` — перезагрузка |
|
|
416
|
+
|
|
417
|
+
```vue
|
|
418
|
+
<VueStackTabs iframe-path="/iframe">
|
|
419
|
+
<template #leftButton>
|
|
420
|
+
<TabHeaderButton title="Развернуть" @click="collapse = !collapse" />
|
|
421
|
+
</template>
|
|
422
|
+
<template #iframeError="{ tab, retry }">
|
|
423
|
+
<span>{{ tab.title }} не загрузилась</span>
|
|
424
|
+
<button @click="retry">Повторить</button>
|
|
425
|
+
</template>
|
|
426
|
+
</VueStackTabs>
|
|
427
|
+
```
|
|
365
428
|
|
|
366
429
|
---
|
|
367
430
|
|
|
@@ -451,23 +514,29 @@ window.addEventListener('message', (ev) => {
|
|
|
451
514
|
|
|
452
515
|
## Интернационализация
|
|
453
516
|
|
|
454
|
-
Встроенные языки: `zh-CN` (китайский), `en
|
|
517
|
+
Встроенные языки: `zh-CN` (китайский), `en` (английский).
|
|
455
518
|
|
|
456
519
|
```ts
|
|
457
|
-
// Переключить язык
|
|
458
|
-
|
|
520
|
+
// Переключить язык через prop компонента
|
|
521
|
+
// <VueStackTabs iframe-path="/iframe" i18n="en" />
|
|
522
|
+
|
|
523
|
+
// Или при установке плагина
|
|
524
|
+
app.use(VueStackTabs, [{ locale: 'en' }])
|
|
459
525
|
```
|
|
460
526
|
|
|
461
|
-
Пользовательский языковой
|
|
527
|
+
Пользовательский языковой пакет (`messages` — вложенный объект, глубоко сливается со встроенными языками):
|
|
462
528
|
|
|
463
529
|
```ts
|
|
464
530
|
app.use(VueStackTabs, [
|
|
465
531
|
{
|
|
466
532
|
locale: 'ru-RU',
|
|
467
533
|
messages: {
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
534
|
+
VueStackTab: {
|
|
535
|
+
close: 'Закрыть',
|
|
536
|
+
reload: 'Обновить',
|
|
537
|
+
loading: 'Загрузка'
|
|
538
|
+
// ...
|
|
539
|
+
}
|
|
471
540
|
}
|
|
472
541
|
}
|
|
473
542
|
])
|
|
@@ -478,12 +547,14 @@ app.use(VueStackTabs, [
|
|
|
478
547
|
## Определения типов
|
|
479
548
|
|
|
480
549
|
```ts
|
|
550
|
+
import type { LocationQueryRaw } from 'vue-router'
|
|
551
|
+
|
|
481
552
|
/** Данные, передаваемые при открытии вкладки */
|
|
482
553
|
interface ITabData {
|
|
483
554
|
id?: string // ID вкладки (автоматически генерируемый UUID, если не указан)
|
|
484
555
|
title: string // Заголовок вкладки
|
|
485
556
|
path: string // Путь маршрута или URL iframe
|
|
486
|
-
query?:
|
|
557
|
+
query?: LocationQueryRaw // Параметры маршрута
|
|
487
558
|
closable?: boolean // Можно ли закрыть (по умолчанию true)
|
|
488
559
|
refreshable?: boolean // Можно ли обновить (по умолчанию true)
|
|
489
560
|
iframe?: boolean // Является ли вкладкой iframe (по умолчанию false)
|
|
@@ -523,4 +594,4 @@ src/lib/
|
|
|
523
594
|
|
|
524
595
|
## Лицензия
|
|
525
596
|
|
|
526
|
-
[
|
|
597
|
+
[Apache-2.0](./LICENSE)
|
|
@@ -22,9 +22,9 @@ type __VLS_Props = {
|
|
|
22
22
|
iframeAllow?: string;
|
|
23
23
|
iframeLoadTimeout?: number;
|
|
24
24
|
};
|
|
25
|
-
declare var __VLS_10: {}, __VLS_13: {},
|
|
25
|
+
declare var __VLS_10: {}, __VLS_13: {}, __VLS_35: {
|
|
26
26
|
tab: import('.').ITabItem;
|
|
27
|
-
},
|
|
27
|
+
}, __VLS_37: {
|
|
28
28
|
tab: import('.').ITabItem;
|
|
29
29
|
retry: (frameId: string) => void;
|
|
30
30
|
};
|
|
@@ -33,9 +33,9 @@ type __VLS_Slots = {} & {
|
|
|
33
33
|
} & {
|
|
34
34
|
rightButton?: (props: typeof __VLS_13) => any;
|
|
35
35
|
} & {
|
|
36
|
-
iframeLoading?: (props: typeof
|
|
36
|
+
iframeLoading?: (props: typeof __VLS_35) => any;
|
|
37
37
|
} & {
|
|
38
|
-
iframeError?: (props: typeof
|
|
38
|
+
iframeError?: (props: typeof __VLS_37) => any;
|
|
39
39
|
};
|
|
40
40
|
declare const __VLS_base: import('vue').DefineComponent<__VLS_Props, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {
|
|
41
41
|
onActive: (...args: any[]) => void;
|
|
@@ -44,9 +44,9 @@ declare const __VLS_base: import('vue').DefineComponent<__VLS_Props, {}, {}, {},
|
|
|
44
44
|
onOnActive?: ((...args: any[]) => any) | undefined;
|
|
45
45
|
onOnPageLoaded?: ((...args: any[]) => any) | undefined;
|
|
46
46
|
}>, {
|
|
47
|
+
contextmenu: boolean | object;
|
|
47
48
|
defaultTabs: ITabData[];
|
|
48
49
|
max: number;
|
|
49
|
-
contextmenu: boolean | object;
|
|
50
50
|
space: number;
|
|
51
51
|
width: string;
|
|
52
52
|
tabScrollMode: TabScrollMode;
|
|
@@ -15,7 +15,7 @@ declare const __VLS_export: import('vue').DefineComponent<__VLS_Props, {}, {}, {
|
|
|
15
15
|
}>, {
|
|
16
16
|
left: number;
|
|
17
17
|
top: number;
|
|
18
|
-
contextMenu:
|
|
18
|
+
contextMenu: IContextMenu[];
|
|
19
19
|
restoreFocusElement: HTMLElement | null;
|
|
20
20
|
}, {}, {}, {}, string, import('vue').ComponentProvideOptions, false, {}, any>;
|
|
21
21
|
declare const _default: typeof __VLS_export;
|
|
@@ -1,15 +1,24 @@
|
|
|
1
1
|
import { ITabItem } from '../../model/TabModel';
|
|
2
2
|
type __VLS_Props = {
|
|
3
3
|
item: ITabItem;
|
|
4
|
+
dropPosition?: 'before' | 'after';
|
|
4
5
|
};
|
|
5
6
|
declare const __VLS_export: import('vue').DefineComponent<__VLS_Props, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {
|
|
6
7
|
active: (...args: any[]) => void;
|
|
7
8
|
close: (...args: any[]) => void;
|
|
8
9
|
contextmenu: (...args: any[]) => void;
|
|
10
|
+
dragend: (...args: any[]) => void;
|
|
11
|
+
dragover: (...args: any[]) => void;
|
|
12
|
+
dragstart: (...args: any[]) => void;
|
|
13
|
+
drop: (...args: any[]) => void;
|
|
9
14
|
}, string, import('vue').PublicProps, Readonly<__VLS_Props> & Readonly<{
|
|
10
15
|
onActive?: ((...args: any[]) => any) | undefined;
|
|
11
16
|
onClose?: ((...args: any[]) => any) | undefined;
|
|
12
17
|
onContextmenu?: ((...args: any[]) => any) | undefined;
|
|
18
|
+
onDragend?: ((...args: any[]) => any) | undefined;
|
|
19
|
+
onDragover?: ((...args: any[]) => any) | undefined;
|
|
20
|
+
onDragstart?: ((...args: any[]) => any) | undefined;
|
|
21
|
+
onDrop?: ((...args: any[]) => any) | undefined;
|
|
13
22
|
}>, {}, {}, {}, {}, string, import('vue').ComponentProvideOptions, false, {}, any>;
|
|
14
23
|
declare const _default: typeof __VLS_export;
|
|
15
24
|
export default _default;
|
|
@@ -7,19 +7,19 @@ type __VLS_Props = {
|
|
|
7
7
|
contextmenu?: boolean | object;
|
|
8
8
|
max?: number;
|
|
9
9
|
};
|
|
10
|
-
declare var __VLS_1: {},
|
|
10
|
+
declare var __VLS_1: {}, __VLS_35: {};
|
|
11
11
|
type __VLS_Slots = {} & {
|
|
12
12
|
leftButton?: (props: typeof __VLS_1) => any;
|
|
13
13
|
} & {
|
|
14
|
-
rightButton?: (props: typeof
|
|
14
|
+
rightButton?: (props: typeof __VLS_35) => any;
|
|
15
15
|
};
|
|
16
16
|
declare const __VLS_base: import('vue').DefineComponent<__VLS_Props, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {
|
|
17
17
|
active: (...args: any[]) => void;
|
|
18
18
|
}, string, import('vue').PublicProps, Readonly<__VLS_Props> & Readonly<{
|
|
19
19
|
onActive?: ((...args: any[]) => any) | undefined;
|
|
20
20
|
}>, {
|
|
21
|
-
max: number;
|
|
22
21
|
contextmenu: boolean | object;
|
|
22
|
+
max: number;
|
|
23
23
|
tabScrollMode: TabScrollMode;
|
|
24
24
|
tabTransition: string | TransitionProps;
|
|
25
25
|
}, {}, {}, {}, string, import('vue').ComponentProvideOptions, false, {}, any>;
|
|
@@ -39,11 +39,14 @@ export declare const stackTabsContextKey: InjectionKey<StackTabsRuntimeContext>;
|
|
|
39
39
|
export declare const maximumKey: InjectionKey<Ref<boolean>>;
|
|
40
40
|
/** 创建一个新的运行时上下文实例 */
|
|
41
41
|
export declare const createStackTabsRuntimeContext: (options?: CreateStackTabsRuntimeContextOptions) => StackTabsRuntimeContext;
|
|
42
|
-
/**
|
|
42
|
+
/**
|
|
43
|
+
* 获取非 App 容器下注册的兼容上下文。
|
|
44
|
+
* @deprecated App 内的 composable 必须依赖 provide/inject,不应使用这个回退。
|
|
45
|
+
*/
|
|
43
46
|
export declare const getActiveStackTabsRuntimeContext: () => StackTabsRuntimeContext | null;
|
|
44
47
|
/** 注册运行时上下文,重复注册会在开发环境抛出错误 */
|
|
45
48
|
export declare const registerStackTabsRuntimeContext: (context: StackTabsRuntimeContext, options?: RegisterStackTabsRuntimeContextOptions) => boolean;
|
|
46
49
|
/** 注销运行时上下文,组件卸载时调用 */
|
|
47
50
|
export declare const unregisterStackTabsRuntimeContext: (context: StackTabsRuntimeContext, options?: UnregisterStackTabsRuntimeContextOptions) => void;
|
|
48
|
-
/** 解析当前可用的运行时上下文,优先使用 inject
|
|
51
|
+
/** 解析当前可用的运行时上下文,优先使用 inject,兼容回退仅限非 App 容器。 */
|
|
49
52
|
export declare const resolveStackTabsRuntimeContext: () => StackTabsRuntimeContext;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { DefineComponent } from 'vue';
|
|
2
|
+
import { ITabBase } from '../../model/TabModel';
|
|
3
|
+
import { StackTabsRuntimeContext } from '../stackTabsContext';
|
|
4
|
+
interface PageComponentFactoryOptions {
|
|
5
|
+
runtimeContext: StackTabsRuntimeContext;
|
|
6
|
+
consumeBackParams: (tabId: string, pageId: string) => Record<string, unknown> | null;
|
|
7
|
+
getComponent: (id: string) => DefineComponent | undefined;
|
|
8
|
+
setComponent: (id: string, component: DefineComponent) => void;
|
|
9
|
+
addCache: (cacheName: string) => void;
|
|
10
|
+
evictMarkedCaches: () => void;
|
|
11
|
+
clearTabEvictionMarks: () => void;
|
|
12
|
+
addPageScroller: (pageId: string, ...selectors: string[]) => void;
|
|
13
|
+
saveScroller: (pageId: string) => void;
|
|
14
|
+
restoreScroller: (pageId: string) => void;
|
|
15
|
+
removeScroller: (pageId: string) => void;
|
|
16
|
+
}
|
|
17
|
+
export declare const createPageComponentFactory: (options: PageComponentFactoryOptions) => {
|
|
18
|
+
resolvePageComponent: (ctx: {
|
|
19
|
+
cacheName: string;
|
|
20
|
+
tabInfo: ITabBase;
|
|
21
|
+
}) => DefineComponent;
|
|
22
|
+
};
|
|
23
|
+
export {};
|
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
import { Ref } from 'vue';
|
|
2
|
-
import { ITabItem } from '../../model/TabModel';
|
|
2
|
+
import { ITabItem, ITabPage } from '../../model/TabModel';
|
|
3
|
+
import { PagePatch } from './tabStore';
|
|
3
4
|
export interface TabPanelRefreshApi {
|
|
4
5
|
refreshTab: (id: string) => void;
|
|
5
6
|
refreshAllTabs: () => void;
|
|
6
7
|
}
|
|
7
8
|
export interface CreateTabPanelRefreshOptions {
|
|
8
|
-
|
|
9
|
+
getTabs: () => readonly ITabItem[];
|
|
10
|
+
getTopPage: (tabId: string) => ITabPage | null;
|
|
11
|
+
/** 修改页面字段(刷新版本 / 页面 id)并触发 tabs 更新 */
|
|
12
|
+
updatePage: (tabId: string, pageId: string, patch: PagePatch) => boolean;
|
|
9
13
|
iframeRefreshKeys: Ref<Record<string, number>>;
|
|
10
|
-
getTab: (id: string) => ITabItem | null;
|
|
11
14
|
createPageId: () => string;
|
|
12
15
|
addCache: (cacheName: string) => void;
|
|
13
16
|
evictPageCache: (cacheName: string) => void;
|
|
@@ -4,5 +4,7 @@ export interface TabPanelScrollApi {
|
|
|
4
4
|
saveScroller: (pageCacheId: string) => void;
|
|
5
5
|
removeScroller: (pageCacheId: string) => void;
|
|
6
6
|
addPageScroller: (pageCacheId: string, ...selectorIds: string[]) => void;
|
|
7
|
+
/** 主动失效某页面的滚动元素缓存,例如外部强制刷新缓存页时 */
|
|
8
|
+
invalidateAutoScrollCache: (pageCacheId: string) => void;
|
|
7
9
|
}
|
|
8
10
|
export declare const createTabPanelScroll: (context: StackTabsRuntimeContext) => TabPanelScrollApi;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { DefineComponent } from 'vue';
|
|
2
|
+
import { ITabItem, ITabPage } from '../../model/TabModel';
|
|
3
|
+
import { StackTabsRuntimeContext } from '../stackTabsContext';
|
|
4
|
+
import { TabPanelEvictionApi } from './evict';
|
|
5
|
+
export interface RemoveTabResult {
|
|
6
|
+
status: 'not-found' | 'not-closable' | 'removed';
|
|
7
|
+
removed: ITabItem | null;
|
|
8
|
+
/** 关闭后应激活的相邻标签;没有可激活标签时为空字符串 */
|
|
9
|
+
nextActiveId: string;
|
|
10
|
+
}
|
|
11
|
+
/** updatePage 允许修改的字段;页面身份与刷新版本必须通过 store 写入 */
|
|
12
|
+
export interface PagePatch {
|
|
13
|
+
id?: string;
|
|
14
|
+
refreshVersion?: number;
|
|
15
|
+
_backParams?: Record<string, unknown>;
|
|
16
|
+
}
|
|
17
|
+
export interface TabStoreApi extends TabPanelEvictionApi {
|
|
18
|
+
/** 返回标签列表的只读视图(运行时为同一个响应式数组) */
|
|
19
|
+
getTabs: () => readonly ITabItem[];
|
|
20
|
+
getTab: (id: string) => ITabItem | null;
|
|
21
|
+
getActiveTab: () => ITabItem | null;
|
|
22
|
+
/** 查找栈顶页面 path 与给定 path 相同的标签 */
|
|
23
|
+
findTabByTopPagePath: (path: string) => ITabItem | null;
|
|
24
|
+
hasTab: (id: string) => boolean;
|
|
25
|
+
canAddTab: () => boolean;
|
|
26
|
+
getPages: (tabId: string) => readonly ITabPage[];
|
|
27
|
+
getTopPage: (tabId: string) => ITabPage | null;
|
|
28
|
+
/** 返回深拷贝的页面栈快照,可直接用于 rollback */
|
|
29
|
+
getPageSnapshot: (tabId: string) => ITabPage[];
|
|
30
|
+
addTab: (tab: ITabItem) => void;
|
|
31
|
+
addTabs: (tabs: readonly ITabItem[]) => void;
|
|
32
|
+
/** 唯一 active 写入口 */
|
|
33
|
+
activateTab: (id: string) => ITabItem | null;
|
|
34
|
+
moveTab: (id: string, targetIndex: number) => boolean;
|
|
35
|
+
removeTab: (id: string) => RemoveTabResult;
|
|
36
|
+
removeTabs: (shouldRemove: (tab: ITabItem, index: number) => boolean) => ITabItem[];
|
|
37
|
+
appendPage: (tabId: string, page: ITabPage) => void;
|
|
38
|
+
/** 从栈顶弹出若干页,返回被弹出的页面(由调用方决定驱逐/回滚) */
|
|
39
|
+
popPages: (tabId: string, steps: number) => ITabPage[];
|
|
40
|
+
/** 用给定页面替换整个栈(内部深拷贝页面) */
|
|
41
|
+
replacePages: (tabId: string, pages: readonly ITabPage[]) => void;
|
|
42
|
+
clearPages: (tabId: string) => void;
|
|
43
|
+
/** 修改页面字段(id / refreshVersion / _backParams),并触发 tabs 更新 */
|
|
44
|
+
updatePage: (tabId: string, pageId: string, patch: PagePatch) => boolean;
|
|
45
|
+
/** 取出并清除栈顶页面的 _backParams;pageId 不是当前栈顶时返回 null */
|
|
46
|
+
consumeBackParams: (tabId: string, pageId: string) => Record<string, unknown> | null;
|
|
47
|
+
isTabMarkedForEviction: (id: string) => boolean;
|
|
48
|
+
isCacheMarkedForEviction: (id: string) => boolean;
|
|
49
|
+
unmarkCacheForEviction: (id: string) => void;
|
|
50
|
+
clearTabEvictionMarks: () => void;
|
|
51
|
+
getComponent: (id: string) => DefineComponent | undefined;
|
|
52
|
+
setComponent: (id: string, component: DefineComponent) => void;
|
|
53
|
+
deleteComponent: (id: string) => void;
|
|
54
|
+
snapshotComponents: () => Map<string, DefineComponent>;
|
|
55
|
+
restoreComponents: (snapshot: Map<string, DefineComponent>) => void;
|
|
56
|
+
snapshotCaches: () => string[];
|
|
57
|
+
restoreCaches: (cacheNames: readonly string[]) => void;
|
|
58
|
+
/** 通知依赖 tabs 的计算属性重新求值(如 activePageRefreshVersion) */
|
|
59
|
+
touchTabs: () => void;
|
|
60
|
+
/** 清空 tabs / caches / components / 驱逐标记,供 destroy 使用 */
|
|
61
|
+
reset: () => void;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* 每个运行时上下文只创建一个 store。
|
|
65
|
+
*
|
|
66
|
+
* useTabPanel 会被 StackKeepAlive、StackCacheRenderer、页面内的 useTabRouter 多次调用;
|
|
67
|
+
* 复用同一实例可避免 eviction 的 Set/数组出现多份互相不同步的状态。
|
|
68
|
+
*/
|
|
69
|
+
export declare const getTabStore: (context: StackTabsRuntimeContext) => TabStoreApi;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { Ref } from 'vue';
|
|
2
|
+
export interface UseScrollShadowOptions {
|
|
3
|
+
/** 容器元素的 ref,可以是 Ref<HTMLElement | null> 或 getter 函数 */
|
|
4
|
+
target: Ref<HTMLElement | null> | (() => HTMLElement | null);
|
|
5
|
+
/**
|
|
6
|
+
* 是否监听内容缩放/尺寸变化(默认 true)。
|
|
7
|
+
* 启用后会用 ResizeObserver 监视目标尺寸,容器或子树尺寸变化都会重新计算阴影边界。
|
|
8
|
+
*/
|
|
9
|
+
watchResize?: boolean;
|
|
10
|
+
/**
|
|
11
|
+
* 到顶/到底的判定阈值(px,默认 5)。
|
|
12
|
+
* 距离边界 ≤ 阈值视为已贴边(不显示阴影);距离边界 > 阈值视为可继续滚动(显示阴影)。
|
|
13
|
+
*/
|
|
14
|
+
edgeThreshold?: number;
|
|
15
|
+
}
|
|
16
|
+
export interface UseScrollShadowReturn {
|
|
17
|
+
/** 手动同步一次(用于内容变化/resize 后) */
|
|
18
|
+
sync: () => void;
|
|
19
|
+
}
|
|
20
|
+
export declare const useScrollShadow: (options: UseScrollShadowOptions) => UseScrollShadowReturn;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { ITabData } from '../model/TabModel';
|
|
2
|
+
import { StackTabsRuntimeContext } from './stackTabsContext';
|
|
2
3
|
/**
|
|
3
4
|
* useTabActions - 标签操作对外 Hook
|
|
4
5
|
*
|
|
@@ -12,16 +13,20 @@ import { ITabData } from '../model/TabModel';
|
|
|
12
13
|
* - activeTab(id, isRoute?) 激活标签
|
|
13
14
|
* - reset() 重置(关闭所有)
|
|
14
15
|
*/
|
|
15
|
-
export default function useTabActions(): {
|
|
16
|
+
export default function useTabActions(providedRuntimeContext?: StackTabsRuntimeContext): {
|
|
16
17
|
openTab: (tab: ITabData, renew?: boolean) => Promise<string | undefined>;
|
|
17
18
|
closeTab: (id: string) => string;
|
|
18
19
|
closeAllTabs: () => void;
|
|
19
20
|
refreshTab: (id: string) => void;
|
|
20
21
|
refreshAllTabs: () => void;
|
|
21
22
|
activeTab: (id: string, route?: boolean) => Promise<void | import('vue-router').NavigationFailure | undefined>;
|
|
23
|
+
moveTab: (id: string, targetIndex: number) => boolean;
|
|
22
24
|
reset: () => void;
|
|
23
25
|
tabs: import('vue').Ref<import('..').ITabItem[], import('..').ITabItem[]>;
|
|
24
26
|
setIFramePath: (path: string) => void;
|
|
25
|
-
getWrapper: (
|
|
27
|
+
getWrapper: (target?: {
|
|
28
|
+
tabId?: string;
|
|
29
|
+
pageId?: string;
|
|
30
|
+
}) => HTMLElement | undefined;
|
|
26
31
|
openInNewWindow: (id: string) => void;
|
|
27
32
|
};
|