vue-stack-tabs 1.3.1 → 1.4.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 (95) hide show
  1. package/README.en.md +119 -52
  2. package/README.md +103 -42
  3. package/README.ru.md +125 -54
  4. package/dist/{src/lib/StackTabs.vue.d.ts → StackTabs.vue.d.ts} +5 -5
  5. package/dist/{src/lib/components → components}/ContextMenu/index.vue.d.ts +1 -1
  6. package/dist/components/StackKeepAlive/emptyPlaceholder.d.ts +3 -0
  7. package/dist/{src/lib/components → components}/TabHeader/TabHeaderItem.vue.d.ts +9 -0
  8. package/dist/{src/lib/components → components}/TabHeader/index.vue.d.ts +3 -3
  9. package/dist/{src/lib/hooks → hooks}/stackTabsContext.d.ts +5 -2
  10. package/dist/hooks/tabPanel/pageComponentFactory.d.ts +20 -0
  11. package/dist/{src/lib/hooks → hooks}/tabPanel/scroll.d.ts +2 -0
  12. package/dist/hooks/useScrollShadow.d.ts +20 -0
  13. package/dist/{src/lib/hooks → hooks}/useTabActions.d.ts +7 -2
  14. package/dist/{src/lib/hooks → hooks}/useTabEventBus.d.ts +2 -1
  15. package/dist/{src/lib/hooks → hooks}/useTabPanel.d.ts +3 -1
  16. package/dist/iframe-bridge.mjs +1 -1
  17. package/dist/index.d.ts +22 -6
  18. package/dist/{src/lib/model → model}/TabModel.d.ts +21 -0
  19. package/dist/nuxt/module.mjs +10 -6
  20. package/dist/nuxt/runtime/plugin.mjs +1600 -4
  21. package/dist/style.css +1 -1
  22. package/dist/utils/iframeBridge.d.ts +7 -1
  23. package/dist/{src/lib/utils → utils}/scrollUtils.d.ts +7 -1
  24. package/dist/{src/lib/versionLogger.d.ts → versionLogger.d.ts} +1 -1
  25. package/dist/vue-stack-tabs.css +1 -1
  26. package/dist/vue-stack-tabs.es.js +1 -1
  27. package/package.json +64 -40
  28. package/src/lib/StackTabs.vue +16 -3
  29. package/src/lib/assets/style/common/var.scss +2 -0
  30. package/src/lib/assets/style/stackTab.scss +17 -3
  31. package/src/lib/components/ContextMenu/index.vue +12 -3
  32. package/src/lib/components/PageLoading.vue +10 -2
  33. package/src/lib/components/StackKeepAlive/StackCacheRenderer.vue +3 -9
  34. package/src/lib/components/StackKeepAlive/emptyPlaceholder.tsx +18 -0
  35. package/src/lib/components/TabHeader/index.vue +4 -3
  36. package/src/lib/hooks/stackTabsContext.ts +23 -18
  37. package/src/lib/hooks/tabPanel/pageComponentFactory.tsx +142 -0
  38. package/src/lib/hooks/tabPanel/refresh.ts +3 -0
  39. package/src/lib/hooks/tabPanel/scroll.ts +110 -7
  40. package/src/lib/hooks/tabPanel/session.ts +29 -3
  41. package/src/lib/hooks/useContextMenu.ts +57 -11
  42. package/src/lib/hooks/useIframeManager.ts +18 -2
  43. package/src/lib/hooks/useScrollShadow.ts +124 -0
  44. package/src/lib/hooks/useTabActions.ts +18 -8
  45. package/src/lib/hooks/useTabEventBus.ts +5 -3
  46. package/src/lib/hooks/useTabPanel.tsx +108 -180
  47. package/src/lib/hooks/useTabRouter.ts +11 -2
  48. package/src/lib/i18n/index.ts +3 -3
  49. package/src/lib/model/TabModel.ts +40 -1
  50. package/src/lib/nuxt/module.ts +10 -3
  51. package/src/lib/nuxt/runtime/plugin.ts +13 -2
  52. package/src/lib/utils/iframeBridge.ts +9 -1
  53. package/src/lib/utils/scrollUtils.ts +35 -2
  54. package/src/lib/utils/tabInfoEncoder.ts +21 -8
  55. package/src/lib/utils/urlParser.ts +3 -1
  56. package/src/lib/versionLogger.ts +6 -2
  57. package/dist/favicon.svg +0 -10
  58. package/dist/iframe-bridge.html +0 -77
  59. package/dist/iframe-child.html +0 -88
  60. package/dist/nuxt/favicon.svg +0 -10
  61. package/dist/nuxt/iframe-bridge.html +0 -77
  62. package/dist/nuxt/iframe-child.html +0 -88
  63. package/dist/nuxt/plugin.mjs +0 -9
  64. package/dist/src/lib/iframe-bridge.d.ts +0 -8
  65. package/dist/src/lib/index.d.ts +0 -22
  66. package/dist/src/lib/nuxt/module.d.ts +0 -6
  67. package/dist/src/lib/nuxt/runtime/plugin.client.d.ts +0 -2
  68. package/dist/src/lib/nuxt/runtime/plugin.d.ts +0 -2
  69. package/dist/src/lib/utils/iframeBridge.d.ts +0 -39
  70. package/src/lib/nuxt/runtime/plugin.client.ts +0 -10
  71. /package/dist/{src/lib/components → components}/ContextMenu/ContextMenuItem.vue.d.ts +0 -0
  72. /package/dist/{src/lib/components → components}/PageLoading.vue.d.ts +0 -0
  73. /package/dist/{src/lib/components → components}/StackKeepAlive/StackCacheRenderer.vue.d.ts +0 -0
  74. /package/dist/{src/lib/components → components}/StackKeepAlive/StackKeepAlive.vue.d.ts +0 -0
  75. /package/dist/{src/lib/components → components}/TabHeader/TabHeaderButton.vue.d.ts +0 -0
  76. /package/dist/{src/lib/components → components}/TabHeader/TabHeaderScroll.vue.d.ts +0 -0
  77. /package/dist/{src/lib/hooks → hooks}/tabPanel/evict.d.ts +0 -0
  78. /package/dist/{src/lib/hooks → hooks}/tabPanel/navigationTransaction.d.ts +0 -0
  79. /package/dist/{src/lib/hooks → hooks}/tabPanel/refresh.d.ts +0 -0
  80. /package/dist/{src/lib/hooks → hooks}/tabPanel/session.d.ts +0 -0
  81. /package/dist/{src/lib/hooks → hooks}/tabPanel/state.d.ts +0 -0
  82. /package/dist/{src/lib/hooks → hooks}/useContextMenu.d.ts +0 -0
  83. /package/dist/{src/lib/hooks → hooks}/useIframeManager.d.ts +0 -0
  84. /package/dist/{src/lib/hooks → hooks}/useTabLoading.d.ts +0 -0
  85. /package/dist/{src/lib/hooks → hooks}/useTabRouter.d.ts +0 -0
  86. /package/dist/{src/lib/i18n → i18n}/index.d.ts +0 -0
  87. /package/dist/{src/lib/i18n → i18n}/lang/en.d.ts +0 -0
  88. /package/dist/{src/lib/i18n → i18n}/lang/zh-CN.d.ts +0 -0
  89. /package/dist/{src/lib/i18n → i18n}/stackTabsLocale.d.ts +0 -0
  90. /package/dist/{src/lib/iframe.vue.d.ts → iframe.vue.d.ts} +0 -0
  91. /package/dist/{src/lib/utils → utils}/stackTabsA11y.d.ts +0 -0
  92. /package/dist/{src/lib/utils → utils}/stackTabsMessage.d.ts +0 -0
  93. /package/dist/{src/lib/utils → utils}/tabInfoEncoder.d.ts +0 -0
  94. /package/dist/{src/lib/utils → utils}/typeGuards.d.ts +0 -0
  95. /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
+ [![npm version](https://img.shields.io/npm/v/vue-stack-tabs.svg)](https://www.npmjs.com/package/vue-stack-tabs)
4
+ [![npm downloads](https://img.shields.io/npm/dm/vue-stack-tabs.svg)](https://www.npmjs.com/package/vue-stack-tabs)
5
+ [![license](https://img.shields.io/npm/l/vue-stack-tabs.svg)](./LICENSE)
6
+ [![minzipped size](https://img.shields.io/bundlephobia/minzip/vue-stack-tabs)](https://bundlephobia.com/package/vue-stack-tabs)
7
+ [![docs](https://img.shields.io/badge/docs-GitHub%20Pages-377dff)](https://jktantan.github.io/vue-stack-tabs/)
8
+
3
9
  [中文](./README.md) | [English](./README.en.md) | **Русский**
4
10
 
5
- > Библиотека управления вкладками для Vue 3 на основе Vue Router. Реализует поведение TabPanel, аналогичное iframe, с использованием областей видимости Vue — **каждая вкладка имеет изолированную область компонентов**.
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/vue-stack-tabs.css'
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: 'en-US'
153
+ locale: 'zh-CN' // встроенные варианты: 'zh-CN' | 'en'
137
154
  },
138
- css: ['vue-stack-tabs/dist/vue-stack-tabs.css']
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[] = [{ title: 'Главная', path: '/', closable: false, refreshable: true }]
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' })">Открыть страницу «О приложении»</button>
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/vue-stack-tabs.css'
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?)` | Открыть новую вкладку. При `renew=true` очищает стек страниц и открывает заново |
250
- | `closeTab(id)` | Закрыть указанную вкладку, возвращает ID новой активной вкладки |
251
- | `closeAllTabs()` | Закрыть все закрываемые вкладки |
252
- | `refreshTab(id)` | Обновить указанную вкладку (заменяет ID кэша, пересоздаёт экземпляр компонента) |
253
- | `refreshAllTabs()` | Обновить все вкладки |
254
- | `activeTab(id)` | Активировать указанную вкладку (переключить вкладку) |
255
- | `reset()` | Закрыть все вкладки и сбросить состояние |
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` | `string` | **Обязательно** | Путь маршрута-заглушки для iframe |
339
- | `iframeSandbox` | `string` | `allow-scripts allow-forms allow-popups allow-downloads allow-same-origin` | Политика sandbox для iframe; по умолчанию совместимость, не строгая изоляция — удалите `allow-same-origin` для строгой изоляции; пустая строка отключает sandbox (не рекомендуется) |
340
- | `iframeReferrerPolicy` | `ReferrerPolicy` | `strict-origin-when-cross-origin` | Атрибут referrerpolicy для iframe |
341
- | `iframeAllow` | `string` | `''` | Атрибут allow для iframe, например `fullscreen` |
342
- | `iframeLoadTimeout` | `number` | `15000` | Тайм-аут загрузки iframe в мс |
343
- | `defaultTabs` | `ITabData[]` | `[]` | Начальный список вкладок |
344
- | `max` | `number` | `20` | Максимальное количество вкладок |
345
- | `contextmenu` | `boolean \| object` | `true` | Включить контекстное меню |
346
- | `pageTransition` | `string` | `'stack-tab-swap'` | Имя анимации перехода при навигации вперёд |
347
- | `pageTransitionBack` | `string` | `'stack-tab-swap-back'` | Имя анимации перехода при навигации назад |
348
- | `tabTransition` | `string \| TransitionProps` | `'stack-tab-zoom'` | Эффект перехода при добавлении/удалении вкладок |
349
- | `tabScrollMode` | `TabScrollMode` | `'both'` | Режим прокрутки панели вкладок: `'wheel'` / `'button'` / `'both'` |
350
- | `width` | `string` | `'100%'` | Ширина контейнера |
351
- | `height` | `string` | `'100%'` | Высота контейнера |
352
- | `i18n` | `string` | `'zh-CN'` | Локаль интернационализации |
353
- | `globalScroll` | `boolean` | `false` | Включить запоминание прокрутки на уровне страницы |
354
- | `sessionPrefix` | `string` | `''` | Префикс ключа sessionStorage |
355
- | `iframeAllowedOrigins` | `string[]` | Тот же источник | Разрешённые источники для iframe postMessage |
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-US` (английский).
517
+ Встроенные языки: `zh-CN` (китайский), `en` (английский).
455
518
 
456
519
  ```ts
457
- // Переключить язык
458
- app.use(VueStackTabs, [{ locale: 'en-US' }])
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
- 'VueStackTab.close': 'Закрыть',
469
- 'VueStackTab.refresh': 'Обновить'
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?: Record<string, string> // Параметры маршрута
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
- [LGPL-2.1](./LICENSE)
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: {}, __VLS_34: {
25
+ declare var __VLS_10: {}, __VLS_13: {}, __VLS_35: {
26
26
  tab: import('.').ITabItem;
27
- }, __VLS_36: {
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 __VLS_34) => any;
36
+ iframeLoading?: (props: typeof __VLS_35) => any;
37
37
  } & {
38
- iframeError?: (props: typeof __VLS_36) => any;
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: Array<IContextMenu>;
18
+ contextMenu: IContextMenu[];
19
19
  restoreFocusElement: HTMLElement | null;
20
20
  }, {}, {}, {}, string, import('vue').ComponentProvideOptions, false, {}, any>;
21
21
  declare const _default: typeof __VLS_export;
@@ -0,0 +1,3 @@
1
+ import { DefineComponent } from 'vue';
2
+ export declare const EmptyPlaceholderComponent: DefineComponent;
3
+ export declare const getEmptyPlaceholderComponent: () => DefineComponent;
@@ -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: {}, __VLS_30: {};
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 __VLS_30) => any;
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,20 @@
1
+ import { DefineComponent } from 'vue';
2
+ import { ITabBase, ITabItem } from '../../model/TabModel';
3
+ import { StackTabsRuntimeContext } from '../stackTabsContext';
4
+ interface PageComponentFactoryOptions {
5
+ runtimeContext: StackTabsRuntimeContext;
6
+ getTab: (id: string) => ITabItem | null;
7
+ addCache: (cacheName: string) => void;
8
+ evictMarkedCaches: () => void;
9
+ addPageScroller: (pageId: string, ...selectors: string[]) => void;
10
+ saveScroller: (pageId: string) => void;
11
+ restoreScroller: (pageId: string) => void;
12
+ removeScroller: (pageId: string) => void;
13
+ }
14
+ export declare const createPageComponentFactory: (options: PageComponentFactoryOptions) => {
15
+ resolvePageComponent: (ctx: {
16
+ cacheName: string;
17
+ tabInfo: ITabBase;
18
+ }) => DefineComponent;
19
+ };
20
+ export {};
@@ -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,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: () => Element;
27
+ getWrapper: (target?: {
28
+ tabId?: string;
29
+ pageId?: string;
30
+ }) => HTMLElement | undefined;
26
31
  openInNewWindow: (id: string) => void;
27
32
  };
@@ -1,5 +1,6 @@
1
1
  import { InjectionKey } from 'vue';
2
2
  import { Emitter, EventType } from 'mitt';
3
+ import { StackTabsRuntimeContext } from './stackTabsContext';
3
4
  declare module '@vue/runtime-core' {
4
5
  interface ComponentCustomProperties {
5
6
  $layerEmitter: Emitter<TabEventPayloadMap>;
@@ -28,4 +29,4 @@ export interface TabEventPayloadMap extends Record<EventType, unknown> {
28
29
  }
29
30
  export declare const tabEmitterKey: InjectionKey<Emitter<TabEventPayloadMap>>;
30
31
  /** 获取标签事件总线,用于组件间通信 */
31
- export declare const useTabEmitter: () => Emitter<TabEventPayloadMap>;
32
+ export declare const useTabEmitter: (runtimeContext?: StackTabsRuntimeContext) => Emitter<TabEventPayloadMap>;
@@ -1,9 +1,10 @@
1
1
  import { ITabData, ITabItem } from '../model/TabModel';
2
2
  import { DefineComponent, VNode } from 'vue';
3
3
  import { RouteLocationNormalizedLoaded } from 'vue-router';
4
+ import { StackTabsRuntimeContext } from './stackTabsContext';
4
5
  /** 规范化路径(nginx 等可能加尾部斜杠) */
5
6
  export declare const normalizePathForCache: (route: RouteLocationNormalizedLoaded) => string;
6
- declare const _default: () => {
7
+ declare const _default: (providedRuntimeContext?: StackTabsRuntimeContext) => {
7
8
  tabs: import('vue').Ref<ITabItem[], ITabItem[]>;
8
9
  caches: import('vue').ShallowRef<string[]>;
9
10
  refreshKey: import('vue').Ref<number, number>;
@@ -21,6 +22,7 @@ declare const _default: () => {
21
22
  addCache: (cacheName: string) => void;
22
23
  removeCache: (cacheName: string) => void;
23
24
  getTab: (id: string) => ITabItem | null;
25
+ moveTab: (id: string, targetIndex: number) => boolean;
24
26
  addTab: (tab: ITabItem) => void;
25
27
  removeTab: (id: string) => string;
26
28
  removeAllTabs: () => void;
@@ -4,7 +4,7 @@ function n() {
4
4
  return window.location.origin;
5
5
  }
6
6
  function r(e, r = {}) {
7
- typeof window > "u" || !window.parent || window.parent.postMessage({
7
+ typeof window < "u" && window.parent && window.parent.postMessage({
8
8
  type: t,
9
9
  payload: e
10
10
  }, r.targetOrigin ?? n());
package/dist/index.d.ts CHANGED
@@ -1,6 +1,22 @@
1
- export * from './src/lib/index'
2
- export {}
3
- import VueStackTabs from './src/lib/index'
4
- export default VueStackTabs
5
- export * from './src/lib/index'
6
- export {}
1
+ import { App } from 'vue';
2
+ import { LocaleMessageOption } from './i18n';
3
+ import { default as StackTab } from './StackTabs.vue';
4
+ import { default as IFrame } from './iframe.vue';
5
+ import { default as useTabLoading } from './hooks/useTabLoading';
6
+ import { default as useTabRouter } from './hooks/useTabRouter';
7
+ import { default as useTabActions } from './hooks/useTabActions';
8
+ import { default as TabHeaderButton } from './components/TabHeader/TabHeaderButton.vue';
9
+ export type VueStackTabsPluginOptions = LocaleMessageOption[];
10
+ export * from './model/TabModel';
11
+ declare module '@vue/runtime-core' {
12
+ interface GlobalComponents {
13
+ VueStackTabs: typeof StackTab;
14
+ }
15
+ }
16
+ export { IFrame, useTabActions, useTabLoading, useTabRouter, TabHeaderButton, StackTab, StackTab as VueStackTabs };
17
+ export { postOpenTab, onRefreshRequest, MSG_REFRESH, MSG_OPEN_TAB } from './utils/iframeBridge';
18
+ export type { IframeOpenTabPayload } from './utils/iframeBridge';
19
+ declare const _default: {
20
+ install(Vue: App, options?: VueStackTabsPluginOptions): void;
21
+ };
22
+ export default _default;