vue-stack-tabs 1.1.0 → 1.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (115) hide show
  1. package/README.en.md +526 -0
  2. package/README.md +141 -39
  3. package/README.ru.md +526 -0
  4. package/dist/iframe-bridge.d.ts +8 -0
  5. package/dist/iframe-bridge.html +77 -0
  6. package/dist/iframe-bridge.mjs +20 -0
  7. package/dist/iframe-child.html +19 -6
  8. package/dist/nuxt/favicon.svg +10 -0
  9. package/dist/nuxt/iframe-bridge.html +77 -0
  10. package/dist/nuxt/iframe-child.html +88 -0
  11. package/dist/nuxt/module.d.ts +6 -0
  12. package/dist/nuxt/module.mjs +28 -0
  13. package/dist/nuxt/plugin.mjs +9 -0
  14. package/dist/nuxt/runtime/plugin.d.ts +2 -0
  15. package/dist/nuxt/runtime/plugin.mjs +9 -0
  16. package/dist/src/lib/StackTabs.vue.d.ts +30 -16
  17. package/dist/src/lib/components/ContextMenu/ContextMenuItem.vue.d.ts +7 -1
  18. package/dist/src/lib/components/ContextMenu/index.vue.d.ts +5 -4
  19. package/dist/src/lib/components/PageLoading.vue.d.ts +2 -1
  20. package/dist/src/lib/components/StackKeepAlive/StackCacheRenderer.vue.d.ts +25 -0
  21. package/dist/src/lib/components/StackKeepAlive/StackKeepAlive.vue.d.ts +12 -0
  22. package/dist/src/lib/components/TabHeader/TabHeaderButton.vue.d.ts +11 -12
  23. package/dist/src/lib/components/TabHeader/TabHeaderItem.vue.d.ts +5 -4
  24. package/dist/src/lib/components/TabHeader/TabHeaderScroll.vue.d.ts +8 -22
  25. package/dist/src/lib/components/TabHeader/index.vue.d.ts +11 -164
  26. package/dist/src/lib/hooks/stackTabsContext.d.ts +49 -0
  27. package/dist/src/lib/hooks/tabPanel/evict.d.ts +21 -61
  28. package/dist/src/lib/hooks/tabPanel/navigationTransaction.d.ts +35 -0
  29. package/dist/src/lib/hooks/tabPanel/refresh.d.ts +17 -0
  30. package/dist/src/lib/hooks/tabPanel/scroll.d.ts +8 -8
  31. package/dist/src/lib/hooks/tabPanel/session.d.ts +9 -8
  32. package/dist/src/lib/hooks/tabPanel/state.d.ts +20 -72
  33. package/dist/src/lib/hooks/useIframeManager.d.ts +35 -0
  34. package/dist/src/lib/hooks/useTabActions.d.ts +5 -46
  35. package/dist/src/lib/hooks/useTabEventBus.d.ts +22 -9
  36. package/dist/src/lib/hooks/useTabPanel.d.ts +14 -73
  37. package/dist/src/lib/i18n/index.d.ts +6 -4
  38. package/dist/src/lib/i18n/lang/en.d.ts +5 -0
  39. package/dist/src/lib/i18n/lang/zh-CN.d.ts +5 -0
  40. package/dist/src/lib/i18n/stackTabsLocale.d.ts +2 -0
  41. package/dist/src/lib/iframe-bridge.d.ts +8 -0
  42. package/dist/src/lib/iframe.vue.d.ts +2 -1
  43. package/dist/src/lib/index.d.ts +4 -5
  44. package/dist/src/lib/model/TabModel.d.ts +11 -40
  45. package/dist/src/lib/nuxt/module.d.ts +6 -0
  46. package/dist/src/lib/nuxt/runtime/plugin.client.d.ts +2 -0
  47. package/dist/src/lib/nuxt/runtime/plugin.d.ts +2 -0
  48. package/dist/src/lib/utils/iframeBridge.d.ts +11 -3
  49. package/dist/src/lib/utils/stackTabsA11y.d.ts +8 -0
  50. package/dist/src/lib/utils/stackTabsMessage.d.ts +7 -0
  51. package/dist/src/lib/utils/tabInfoEncoder.d.ts +0 -15
  52. package/dist/src/lib/utils/typeGuards.d.ts +2 -0
  53. package/dist/src/lib/utils/urlParser.d.ts +24 -3
  54. package/dist/src/lib/versionLogger.d.ts +1 -0
  55. package/dist/style.css +2 -1
  56. package/dist/utils/iframeBridge.d.ts +39 -0
  57. package/dist/vue-stack-tabs.css +2 -1
  58. package/dist/vue-stack-tabs.es.js +1 -2220
  59. package/package.json +71 -50
  60. package/src/lib/StackTabs.vue +223 -196
  61. package/src/lib/assets/style/stackTab.scss +110 -13
  62. package/src/lib/components/ContextMenu/ContextMenuItem.vue +23 -11
  63. package/src/lib/components/ContextMenu/index.vue +94 -10
  64. package/src/lib/components/PageLoading.vue +9 -7
  65. package/src/lib/components/StackKeepAlive/StackCacheRenderer.vue +70 -0
  66. package/src/lib/components/StackKeepAlive/StackKeepAlive.vue +57 -0
  67. package/src/lib/components/TabHeader/TabHeaderButton.vue +19 -8
  68. package/src/lib/components/TabHeader/TabHeaderItem.vue +74 -17
  69. package/src/lib/components/TabHeader/TabHeaderScroll.vue +47 -25
  70. package/src/lib/components/TabHeader/index.vue +181 -13
  71. package/src/lib/hooks/stackTabsContext.ts +135 -0
  72. package/src/lib/hooks/tabPanel/evict.ts +122 -87
  73. package/src/lib/hooks/tabPanel/navigationTransaction.ts +61 -0
  74. package/src/lib/hooks/tabPanel/refresh.ts +109 -0
  75. package/src/lib/hooks/tabPanel/scroll.ts +57 -40
  76. package/src/lib/hooks/tabPanel/session.ts +84 -22
  77. package/src/lib/hooks/tabPanel/state.ts +41 -44
  78. package/src/lib/hooks/useIframeManager.ts +253 -0
  79. package/src/lib/hooks/useTabActions.ts +64 -30
  80. package/src/lib/hooks/useTabEventBus.ts +29 -19
  81. package/src/lib/hooks/useTabLoading.ts +3 -2
  82. package/src/lib/hooks/useTabPanel.tsx +290 -313
  83. package/src/lib/hooks/useTabRouter.ts +111 -97
  84. package/src/lib/i18n/index.ts +53 -45
  85. package/src/lib/i18n/lang/en.ts +6 -1
  86. package/src/lib/i18n/lang/zh-CN.ts +6 -1
  87. package/src/lib/i18n/stackTabsLocale.ts +8 -0
  88. package/src/lib/iframe-bridge.ts +12 -0
  89. package/src/lib/index.ts +14 -19
  90. package/src/lib/model/TabModel.ts +27 -82
  91. package/src/lib/nuxt/module.d.ts +4 -0
  92. package/src/lib/nuxt/module.ts +8 -3
  93. package/src/lib/nuxt/runtime/plugin.client.ts +1 -0
  94. package/src/lib/nuxt/runtime/plugin.ts +11 -0
  95. package/src/lib/utils/iframeBridge.ts +29 -5
  96. package/src/lib/utils/scrollUtils.ts +9 -4
  97. package/src/lib/utils/stackTabsA11y.ts +12 -0
  98. package/src/lib/utils/stackTabsMessage.ts +99 -0
  99. package/src/lib/utils/tabInfoEncoder.ts +70 -21
  100. package/src/lib/utils/typeGuards.ts +3 -0
  101. package/src/lib/utils/urlParser.ts +144 -14
  102. package/src/lib/versionLogger.ts +7 -19
  103. package/dist/src/lib/hooks/tabPanel/evict.spec.d.ts +0 -1
  104. package/dist/src/lib/hooks/useTabPanel.spec.d.ts +0 -1
  105. package/dist/src/lib/model/TabModel.spec.d.ts +0 -1
  106. package/dist/src/lib/utils/iframeBridge.spec.d.ts +0 -1
  107. package/dist/src/lib/utils/tabInfoEncoder.spec.d.ts +0 -1
  108. package/dist/src/lib/utils/urlParser.spec.d.ts +0 -1
  109. package/dist/vue-stack-tabs.umd.js +0 -1
  110. package/src/lib/hooks/tabPanel/evict.spec.ts +0 -85
  111. package/src/lib/hooks/useTabPanel.spec.ts +0 -34
  112. package/src/lib/model/TabModel.spec.ts +0 -40
  113. package/src/lib/utils/iframeBridge.spec.ts +0 -37
  114. package/src/lib/utils/tabInfoEncoder.spec.ts +0 -32
  115. package/src/lib/utils/urlParser.spec.ts +0 -45
@@ -1,4 +1,4 @@
1
- import { nextTick, TransitionProps } from 'vue';
1
+ import { TransitionProps } from 'vue';
2
2
  import { TabScrollMode } from '../../model/TabModel';
3
3
  type __VLS_Props = {
4
4
  space: number;
@@ -7,92 +7,13 @@ type __VLS_Props = {
7
7
  contextmenu?: boolean | object;
8
8
  max?: number;
9
9
  };
10
- declare function __VLS_template(): {
11
- attrs: Partial<{}>;
12
- slots: {
13
- leftButton?(_: {}): any;
14
- rightButton?(_: {}): any;
15
- };
16
- refs: {
17
- scrollContainerRef: ({
18
- $: import('vue').ComponentInternalInstance;
19
- $data: {};
20
- $props: {
21
- readonly space?: number | undefined;
22
- readonly isScrollWheel?: boolean | undefined;
23
- readonly isScrollButton?: boolean | undefined;
24
- } & import('vue').VNodeProps & import('vue').AllowedComponentProps & import('vue').ComponentCustomProps;
25
- $attrs: {
26
- [x: string]: unknown;
27
- };
28
- $refs: {
29
- [x: string]: unknown;
30
- } & {
31
- headerScroll: HTMLDivElement;
32
- container: HTMLDivElement;
33
- bar: HTMLDivElement;
34
- thumb: HTMLDivElement;
35
- };
36
- $slots: Readonly<{
37
- [name: string]: import('vue').Slot<any> | undefined;
38
- }>;
39
- $root: import('vue').ComponentPublicInstance | null;
40
- $parent: import('vue').ComponentPublicInstance | null;
41
- $host: Element | null;
42
- $emit: (event: string, ...args: any[]) => void;
43
- $el: any;
44
- $options: import('vue').ComponentOptionsBase<Readonly<{
45
- space?: number;
46
- isScrollWheel?: boolean;
47
- isScrollButton?: boolean;
48
- }> & Readonly<{}>, {
49
- scrollIntoView: (el: HTMLElement) => void;
50
- isInView: (el: HTMLElement) => boolean;
51
- }, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {}, string, {
52
- space: number;
53
- isScrollWheel: boolean;
54
- isScrollButton: boolean;
55
- }, {}, string, {}, import('vue').GlobalComponents, import('vue').GlobalDirectives, string, import('vue').ComponentProvideOptions> & {
56
- beforeCreate?: (() => void) | (() => void)[];
57
- created?: (() => void) | (() => void)[];
58
- beforeMount?: (() => void) | (() => void)[];
59
- mounted?: (() => void) | (() => void)[];
60
- beforeUpdate?: (() => void) | (() => void)[];
61
- updated?: (() => void) | (() => void)[];
62
- activated?: (() => void) | (() => void)[];
63
- deactivated?: (() => void) | (() => void)[];
64
- beforeDestroy?: (() => void) | (() => void)[];
65
- beforeUnmount?: (() => void) | (() => void)[];
66
- destroyed?: (() => void) | (() => void)[];
67
- unmounted?: (() => void) | (() => void)[];
68
- renderTracked?: ((e: import('vue').DebuggerEvent) => void) | ((e: import('vue').DebuggerEvent) => void)[];
69
- renderTriggered?: ((e: import('vue').DebuggerEvent) => void) | ((e: import('vue').DebuggerEvent) => void)[];
70
- errorCaptured?: ((err: unknown, instance: import('vue').ComponentPublicInstance | null, info: string) => boolean | void) | ((err: unknown, instance: import('vue').ComponentPublicInstance | null, info: string) => boolean | void)[];
71
- };
72
- $forceUpdate: () => void;
73
- $nextTick: typeof nextTick;
74
- $watch<T extends string | ((...args: any) => any)>(source: T, cb: T extends (...args: any) => infer R ? (...args: [R, R, import('@vue/reactivity').OnCleanup]) => any : (...args: [any, any, import('@vue/reactivity').OnCleanup]) => any, options?: import('vue').WatchOptions): import('vue').WatchStopHandle;
75
- } & Readonly<{
76
- space: number;
77
- isScrollWheel: boolean;
78
- isScrollButton: boolean;
79
- }> & Omit<Readonly<{
80
- space?: number;
81
- isScrollWheel?: boolean;
82
- isScrollButton?: boolean;
83
- }> & Readonly<{}>, "scrollIntoView" | "isInView" | ("space" | "isScrollWheel" | "isScrollButton")> & import('vue').ShallowUnwrapRef<{
84
- scrollIntoView: (el: HTMLElement) => void;
85
- isInView: (el: HTMLElement) => boolean;
86
- }> & {} & import('vue').ComponentCustomProperties & {} & {
87
- $slots: {
88
- default?(_: {}): any;
89
- };
90
- }) | null;
91
- };
92
- rootEl: HTMLDivElement;
10
+ declare var __VLS_1: {}, __VLS_30: {};
11
+ type __VLS_Slots = {} & {
12
+ leftButton?: (props: typeof __VLS_1) => any;
13
+ } & {
14
+ rightButton?: (props: typeof __VLS_30) => any;
93
15
  };
94
- type __VLS_TemplateResult = ReturnType<typeof __VLS_template>;
95
- declare const __VLS_component: import('vue').DefineComponent<__VLS_Props, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {
16
+ declare const __VLS_base: import('vue').DefineComponent<__VLS_Props, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {
96
17
  active: (...args: any[]) => void;
97
18
  }, string, import('vue').PublicProps, Readonly<__VLS_Props> & Readonly<{
98
19
  onActive?: ((...args: any[]) => any) | undefined;
@@ -101,85 +22,11 @@ declare const __VLS_component: import('vue').DefineComponent<__VLS_Props, {}, {}
101
22
  contextmenu: boolean | object;
102
23
  tabScrollMode: TabScrollMode;
103
24
  tabTransition: string | TransitionProps;
104
- }, {}, {}, {}, string, import('vue').ComponentProvideOptions, false, {
105
- scrollContainerRef: ({
106
- $: import('vue').ComponentInternalInstance;
107
- $data: {};
108
- $props: {
109
- readonly space?: number | undefined;
110
- readonly isScrollWheel?: boolean | undefined;
111
- readonly isScrollButton?: boolean | undefined;
112
- } & import('vue').VNodeProps & import('vue').AllowedComponentProps & import('vue').ComponentCustomProps;
113
- $attrs: {
114
- [x: string]: unknown;
115
- };
116
- $refs: {
117
- [x: string]: unknown;
118
- } & {
119
- headerScroll: HTMLDivElement;
120
- container: HTMLDivElement;
121
- bar: HTMLDivElement;
122
- thumb: HTMLDivElement;
123
- };
124
- $slots: Readonly<{
125
- [name: string]: import('vue').Slot<any> | undefined;
126
- }>;
127
- $root: import('vue').ComponentPublicInstance | null;
128
- $parent: import('vue').ComponentPublicInstance | null;
129
- $host: Element | null;
130
- $emit: (event: string, ...args: any[]) => void;
131
- $el: any;
132
- $options: import('vue').ComponentOptionsBase<Readonly<{
133
- space?: number;
134
- isScrollWheel?: boolean;
135
- isScrollButton?: boolean;
136
- }> & Readonly<{}>, {
137
- scrollIntoView: (el: HTMLElement) => void;
138
- isInView: (el: HTMLElement) => boolean;
139
- }, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {}, string, {
140
- space: number;
141
- isScrollWheel: boolean;
142
- isScrollButton: boolean;
143
- }, {}, string, {}, import('vue').GlobalComponents, import('vue').GlobalDirectives, string, import('vue').ComponentProvideOptions> & {
144
- beforeCreate?: (() => void) | (() => void)[];
145
- created?: (() => void) | (() => void)[];
146
- beforeMount?: (() => void) | (() => void)[];
147
- mounted?: (() => void) | (() => void)[];
148
- beforeUpdate?: (() => void) | (() => void)[];
149
- updated?: (() => void) | (() => void)[];
150
- activated?: (() => void) | (() => void)[];
151
- deactivated?: (() => void) | (() => void)[];
152
- beforeDestroy?: (() => void) | (() => void)[];
153
- beforeUnmount?: (() => void) | (() => void)[];
154
- destroyed?: (() => void) | (() => void)[];
155
- unmounted?: (() => void) | (() => void)[];
156
- renderTracked?: ((e: import('vue').DebuggerEvent) => void) | ((e: import('vue').DebuggerEvent) => void)[];
157
- renderTriggered?: ((e: import('vue').DebuggerEvent) => void) | ((e: import('vue').DebuggerEvent) => void)[];
158
- errorCaptured?: ((err: unknown, instance: import('vue').ComponentPublicInstance | null, info: string) => boolean | void) | ((err: unknown, instance: import('vue').ComponentPublicInstance | null, info: string) => boolean | void)[];
159
- };
160
- $forceUpdate: () => void;
161
- $nextTick: typeof nextTick;
162
- $watch<T extends string | ((...args: any) => any)>(source: T, cb: T extends (...args: any) => infer R ? (...args: [R, R, import('@vue/reactivity').OnCleanup]) => any : (...args: [any, any, import('@vue/reactivity').OnCleanup]) => any, options?: import('vue').WatchOptions): import('vue').WatchStopHandle;
163
- } & Readonly<{
164
- space: number;
165
- isScrollWheel: boolean;
166
- isScrollButton: boolean;
167
- }> & Omit<Readonly<{
168
- space?: number;
169
- isScrollWheel?: boolean;
170
- isScrollButton?: boolean;
171
- }> & Readonly<{}>, "scrollIntoView" | "isInView" | ("space" | "isScrollWheel" | "isScrollButton")> & import('vue').ShallowUnwrapRef<{
172
- scrollIntoView: (el: HTMLElement) => void;
173
- isInView: (el: HTMLElement) => boolean;
174
- }> & {} & import('vue').ComponentCustomProperties & {} & {
175
- $slots: {
176
- default?(_: {}): any;
177
- };
178
- }) | null;
179
- }, HTMLDivElement>;
180
- declare const _default: __VLS_WithTemplateSlots<typeof __VLS_component, __VLS_TemplateResult["slots"]>;
25
+ }, {}, {}, {}, string, import('vue').ComponentProvideOptions, false, {}, any>;
26
+ declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
27
+ declare const _default: typeof __VLS_export;
181
28
  export default _default;
182
- type __VLS_WithTemplateSlots<T, S> = T & {
29
+ type __VLS_WithSlots<T, S> = T & {
183
30
  new (): {
184
31
  $slots: S;
185
32
  };
@@ -0,0 +1,49 @@
1
+ import { App, DefineComponent, InjectionKey, Ref, ShallowRef } from 'vue';
2
+ import { Emitter } from 'mitt';
3
+ import { ITabItem } from '../model/TabModel';
4
+ import { TabEventPayloadMap } from './useTabEventBus';
5
+ import { ScrollPosition } from './tabPanel/state';
6
+ export declare const STACK_TABS_CONTEXT_UNAVAILABLE_MESSAGE = "VueStackTabs runtime context is not available. Render <VueStackTabs> before using vue-stack-tabs composables.";
7
+ export declare const STACK_TABS_DUPLICATE_INSTANCE_MESSAGE = "VueStackTabs only supports one <VueStackTabs> instance per Vue app.";
8
+ export interface StackTabsRuntimeContext {
9
+ tabs: Ref<ITabItem[]>;
10
+ defaultTabs: Ref<ITabItem[]>;
11
+ caches: ShallowRef<string[]>;
12
+ components: Map<string, DefineComponent>;
13
+ cacheIdsToEvict: Set<string>;
14
+ tabIdsToEvict: Set<string>;
15
+ refreshKey: Ref<number>;
16
+ scrollPositionsByPageId: Map<string, Map<string, ScrollPosition>>;
17
+ isInitialized: Ref<boolean>;
18
+ iframeRefreshKeys: Ref<Record<string, number>>;
19
+ maxTabCount: Ref<number>;
20
+ useGlobalScroll: Ref<boolean>;
21
+ sessionPrefix: Ref<string>;
22
+ iframePath: Ref<string>;
23
+ eventBus: Emitter<TabEventPayloadMap>;
24
+ }
25
+ export interface CreateStackTabsRuntimeContextOptions {
26
+ iframePath?: string;
27
+ maxTabCount?: number;
28
+ useGlobalScroll?: boolean;
29
+ sessionPrefix?: string;
30
+ }
31
+ export interface RegisterStackTabsRuntimeContextOptions {
32
+ app?: App;
33
+ isProduction?: boolean;
34
+ }
35
+ export interface UnregisterStackTabsRuntimeContextOptions {
36
+ app?: App;
37
+ }
38
+ export declare const stackTabsContextKey: InjectionKey<StackTabsRuntimeContext>;
39
+ export declare const maximumKey: InjectionKey<Ref<boolean>>;
40
+ /** 创建一个新的运行时上下文实例 */
41
+ export declare const createStackTabsRuntimeContext: (options?: CreateStackTabsRuntimeContextOptions) => StackTabsRuntimeContext;
42
+ /** 获取当前活跃的运行时上下文(模块级单例) */
43
+ export declare const getActiveStackTabsRuntimeContext: () => StackTabsRuntimeContext | null;
44
+ /** 注册运行时上下文,重复注册会在开发环境抛出错误 */
45
+ export declare const registerStackTabsRuntimeContext: (context: StackTabsRuntimeContext, options?: RegisterStackTabsRuntimeContextOptions) => boolean;
46
+ /** 注销运行时上下文,组件卸载时调用 */
47
+ export declare const unregisterStackTabsRuntimeContext: (context: StackTabsRuntimeContext, options?: UnregisterStackTabsRuntimeContextOptions) => void;
48
+ /** 解析当前可用的运行时上下文,优先使用 inject,回退到模块单例 */
49
+ export declare const resolveStackTabsRuntimeContext: () => StackTabsRuntimeContext;
@@ -1,62 +1,22 @@
1
1
  import { ITabPage } from '../../model/TabModel';
2
- /**
3
- * 将指定 cacheName 放入待驱逐集合。
4
- * 不立即清除,等叫到 evictMarkedCaches 时统一处理。
5
- */
6
- export declare const markCacheForEviction: (cacheName: string) => void;
7
- /**
8
- * 标记某个标签下所有页面为待驱逐,并标记该标签本身(用于「关闭标签」场景)。
9
- * tabIdsToEvict 中的标签 ID 会在 updatePageState 中被检测到,
10
- * 阻止被关闭的标签重新路由时意外复活。
11
- */
12
- export declare const markTabPagesForEviction: (tab: {
13
- id: string;
14
- pages: {
15
- list(): ITabPage[];
16
- };
17
- }) => void;
18
- /**
19
- * 仅标记标签下所有页面为待驱逐,不标记标签本身(用于「续签/换页」场景)。
20
- * 适用于刷新标签:旧页面缓存清零,标签本身仍然存活,
21
- * 导航后由 updatePageState 重新创建页面。
22
- */
23
- export declare const markTabPagesForEvictionOnly: (tab: {
24
- pages: {
25
- list(): ITabPage[];
26
- };
27
- }) => void;
28
- /**
29
- * 将 cacheName 加入 <keep-alive> 的 include 列表(caches)。
30
- * 幂等:已存在时不重复添加,避免 include 数组中出现重复项
31
- * 影响 <keep-alive> 缓存范围,决定哪些 cacheComponent 实例会被保留。
32
- */
33
- export declare const addCache: (cacheName: string) => void;
34
- /**
35
- * 将 cacheName 从 include 列表(caches)中移除。
36
- * Vue 的 <keep-alive> 会以 flush:'post' 的 watcher 检测 include 变更,
37
- * 在下次渲染后自动销毁不再被 include 的缓存实例。
38
- */
39
- export declare const removeCache: (cacheName: string) => void;
40
- /**
41
- * 立即、同步、彻底驱逐单个页面的缓存。
42
- *
43
- * 操作步骤:
44
- * 1. 从 cacheIdsToEvict 移除(若存在,防止后续 evictMarkedCaches 二次处理)
45
- * 2. removeCache(cacheName) → 从 <keep-alive> include 列表删除,触发 Vue 销毁缓存组件实例
46
- * 3. components.delete(...) → 从 components Map 删除组件定义,
47
- * 使 resolvePageComponent 下次进入时必须重新创建全新的 cacheComponent 实例
48
- *
49
- * ⚠️ 调用时机要求:
50
- * 必须在 router.push() 完成(.then())之后调用,
51
- * 不能在导航前调用,否则驱逐触发的响应式更新会导致 StackTabs 以旧路由重渲染,
52
- * 进而重建刚被删除的组件(竞态)。详见 useTabRouter.ts backward 的注释。
53
- */
54
- export declare const evictPageCache: (cacheName: string) => void;
55
- /**
56
- * 执行所有已标记缓存的批量驱逐(由 cacheComponent 的 onActivated 调用)。
57
- *
58
- * 调用时机:
59
- * 当 cacheComponent 切换到激活(activated)状态时,说明用户切换到了一个新页面,
60
- * 相关的旧页面已经稳定进入「不活跃/待驱逐」状态,此时批量清理是安全的。
61
- */
62
- export declare const evictMarkedCaches: () => void;
2
+ import { StackTabsRuntimeContext } from '../stackTabsContext';
3
+ export interface TabPanelEvictionApi {
4
+ markCacheForEviction: (cacheName: string) => void;
5
+ markTabPagesForEviction: (tab: {
6
+ id: string;
7
+ pages: {
8
+ list(): ITabPage[];
9
+ };
10
+ }) => void;
11
+ markTabPagesForEvictionOnly: (tab: {
12
+ pages: {
13
+ list(): ITabPage[];
14
+ };
15
+ }) => void;
16
+ addCache: (cacheName: string) => void;
17
+ removeCache: (cacheName: string) => void;
18
+ evictPageCache: (cacheName: string) => void;
19
+ replacePageCaches: (cacheNamesToEvict: Iterable<string>, cacheNamesToAdd: Iterable<string>) => void;
20
+ evictMarkedCaches: () => void;
21
+ }
22
+ export declare const createTabPanelEviction: (context: StackTabsRuntimeContext) => TabPanelEvictionApi;
@@ -0,0 +1,35 @@
1
+ /**
2
+ * 导航事务管理器。
3
+ *
4
+ * 将「乐观 UI 更新 → 路由导航 → 确认/回滚」包装为原子事务:
5
+ * 1. apply() — 先对 UI 状态做乐观修改,返回快照
6
+ * 2. navigate() — 执行 router.push 等异步导航
7
+ * 3. 成功 → commit();失败/异常 → rollback(snapshot) 恢复快照
8
+ *
9
+ * 当连续快速操作导致事务过期时(isCurrent 返回 false),
10
+ * 不回滚而是调用 cleanupStale() 清理残留状态,避免覆盖新事务的结果。
11
+ */
12
+ interface NavigationTransactionOptions<TSnapshot, TResult> {
13
+ /** 乐观应用 UI 变更,返回可用于 rollback 的快照 */
14
+ apply: () => TSnapshot;
15
+ /** 执行异步路由导航 */
16
+ navigate: () => Promise<TResult>;
17
+ /** 导航失败时回滚到快照状态 */
18
+ rollback: (snapshot: TSnapshot) => void;
19
+ /** 导航成功后的确认回调(如驱逐旧缓存) */
20
+ commit?: () => void;
21
+ /** 判断导航结果是否为失败(如 vue-router 返回的 NavigationFailure) */
22
+ isFailureResult?: (result: TResult) => boolean;
23
+ /** 若为失败结果,是否将其作为异常抛出 */
24
+ rejectFailureResult?: boolean;
25
+ /** 检查当前事务是否仍然有效(防止过期事务覆盖新状态) */
26
+ isCurrent?: () => boolean;
27
+ /** 事务已过期时的清理回调(替代 rollback) */
28
+ cleanupStale?: () => void;
29
+ }
30
+ /**
31
+ * 执行一次导航事务:apply → navigate → commit/rollback。
32
+ * 保证乐观更新在导航失败时能安全回滚。
33
+ */
34
+ export declare const runNavigationTransaction: <TSnapshot, TResult>(options: NavigationTransactionOptions<TSnapshot, TResult>) => Promise<TResult>;
35
+ export {};
@@ -0,0 +1,17 @@
1
+ import { Ref } from 'vue';
2
+ import { ITabItem } from '../../model/TabModel';
3
+ export interface TabPanelRefreshApi {
4
+ refreshTab: (id: string) => void;
5
+ refreshAllTabs: () => void;
6
+ }
7
+ export interface CreateTabPanelRefreshOptions {
8
+ tabs: Ref<ITabItem[]>;
9
+ iframeRefreshKeys: Ref<Record<string, number>>;
10
+ getTab: (id: string) => ITabItem | null;
11
+ createPageId: () => string;
12
+ addCache: (cacheName: string) => void;
13
+ evictPageCache: (cacheName: string) => void;
14
+ replacePageCaches: (cacheNamesToEvict: Iterable<string>, cacheNamesToAdd: Iterable<string>) => void;
15
+ refreshIframeByPostMessage: (tabId: string) => void;
16
+ }
17
+ export declare const createTabPanelRefresh: (options: CreateTabPanelRefreshOptions) => TabPanelRefreshApi;
@@ -1,8 +1,8 @@
1
- /** 恢复指定页面的滚动位置 */
2
- export declare const restoreScroller: (pageCacheId: string) => void;
3
- /** 保存指定页面的滚动位置 */
4
- export declare const saveScroller: (pageCacheId: string) => void;
5
- /** 移除指定页面的滚动记录 */
6
- export declare const removeScroller: (pageCacheId: string) => void;
7
- /** 为页面注册需要记录滚动位置的选择器 */
8
- export declare const addPageScroller: (pageCacheId: string, ...selectorIds: string[]) => void;
1
+ import { StackTabsRuntimeContext } from '../stackTabsContext';
2
+ export interface TabPanelScrollApi {
3
+ restoreScroller: (pageCacheId: string) => void;
4
+ saveScroller: (pageCacheId: string) => void;
5
+ removeScroller: (pageCacheId: string) => void;
6
+ addPageScroller: (pageCacheId: string, ...selectorIds: string[]) => void;
7
+ }
8
+ export declare const createTabPanelScroll: (context: StackTabsRuntimeContext) => TabPanelScrollApi;
@@ -1,9 +1,10 @@
1
1
  import { ITabItem } from '../../model/TabModel';
2
- /** 获取 sessionStorage 的 key */
3
- export declare const getSessionKey: () => string;
4
- /** 将当前激活的标签保存到 sessionStorage */
5
- export declare const saveActiveTabToSession: (tab: ITabItem) => void;
6
- /** 清除 sessionStorage 中的激活标签 */
7
- export declare const clearSession: () => void;
8
- /** 从 sessionStorage 解析恢复的标签(含 Stack 反序列化) */
9
- export declare const restoreTabFromSession: (storedJson: string | null) => ITabItem | null;
2
+ import { StackTabsRuntimeContext } from '../stackTabsContext';
3
+ export interface TabPanelSessionApi {
4
+ getSessionKey: () => string;
5
+ saveActiveTabToSession: (tab: ITabItem) => void;
6
+ clearSession: () => void;
7
+ restoreActiveTabSession: (storedJson: string | null) => void;
8
+ restoreTabFromSession: (storedJson: string | null) => ITabItem | null;
9
+ }
10
+ export declare const createTabPanelSession: (context: StackTabsRuntimeContext) => TabPanelSessionApi;
@@ -1,76 +1,24 @@
1
+ import { DefineComponent, Ref, ShallowRef } from 'vue';
1
2
  import { ITabItem } from '../../model/TabModel';
2
- /** 当前打开的标签列表(含每个标签的页面栈) */
3
- export declare const tabs: import('vue').Ref<{
4
- id: string;
5
- closable: boolean;
6
- refreshable: boolean;
7
- iframe: boolean;
8
- iframeRefreshMode?: import('../..').IframeRefreshMode | undefined;
9
- url?: string | undefined;
10
- active: boolean;
11
- pages: {
12
- push: (element: import('../..').ITabPage) => void;
13
- pop: () => import('../..').ITabPage | undefined;
14
- peek: () => import('../..').ITabPage | undefined;
15
- isEmpty: () => boolean;
16
- size: () => number;
17
- clear: () => void;
18
- list: () => import('../..').ITabPage[];
19
- toJSON: () => import('../..').ITabPage[];
20
- toString: () => string;
21
- };
22
- title: string;
23
- }[], ITabItem[] | {
24
- id: string;
25
- closable: boolean;
26
- refreshable: boolean;
27
- iframe: boolean;
28
- iframeRefreshMode?: import('../..').IframeRefreshMode | undefined;
29
- url?: string | undefined;
30
- active: boolean;
31
- pages: {
32
- push: (element: import('../..').ITabPage) => void;
33
- pop: () => import('../..').ITabPage | undefined;
34
- peek: () => import('../..').ITabPage | undefined;
35
- isEmpty: () => boolean;
36
- size: () => number;
37
- clear: () => void;
38
- list: () => import('../..').ITabPage[];
39
- toJSON: () => import('../..').ITabPage[];
40
- toString: () => string;
41
- };
42
- title: string;
43
- }[]>;
44
- /** 初始化时传入的默认标签(用于对比 session 恢复) */
45
- export declare const defaultTabs: ITabItem[];
46
- /** keep-alive include 数组,与 Vue 的 keep-alive :include 绑定 */
47
- export declare const caches: import('vue').Ref<string[], string[]>;
48
- /** 页面缓存 ID -> 包装后的 DefineComponent */
49
- export declare const components: Map<string, import('vue').DefineComponent<{}, {}, {}, import('vue').ComputedOptions, import('vue').MethodOptions, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {}, string, import('vue').PublicProps, Readonly<import('vue').ExtractPropTypes<{}>>, {}, {}, {}, {}, string, import('vue').ComponentProvideOptions, true, {}, any>>;
50
- /** 待驱逐的页面缓存 ID 集合 */
51
- export declare const cacheIdsToEvict: Set<string>;
52
- /** 待驱逐的标签 ID 集合 */
53
- export declare const tabIdsToEvict: Set<string>;
54
- /** 刷新计数器,变更时触发 component :key 变化 */
55
- export declare const refreshKey: import('vue').Ref<number, number>;
56
- /** 刷新时临时加入 keep-alive exclude 的缓存 ID */
57
- /** pageCacheId -> Map<selector, { top, left }> 滚动位置存储 */
58
- export declare const scrollPositionsByPageId: Map<string, Map<string, {
3
+ export interface ScrollPosition {
59
4
  top: number;
60
5
  left: number;
61
- }>>;
62
- /** 是否已完成初始化 */
63
- export declare const isInitialized: import('vue').Ref<boolean, boolean>;
64
- /** sessionStorage 中存储当前激活标签的 key 前缀 */
6
+ }
7
+ export interface TabPanelRuntimeState {
8
+ tabs: Ref<ITabItem[]>;
9
+ defaultTabs: Ref<ITabItem[]>;
10
+ caches: ShallowRef<string[]>;
11
+ components: Map<string, DefineComponent>;
12
+ cacheIdsToEvict: Set<string>;
13
+ tabIdsToEvict: Set<string>;
14
+ refreshKey: Ref<number>;
15
+ scrollPositionsByPageId: Map<string, Map<string, ScrollPosition>>;
16
+ isInitialized: Ref<boolean>;
17
+ iframeRefreshKeys: Ref<Record<string, number>>;
18
+ maxTabCount: Ref<number>;
19
+ useGlobalScroll: Ref<boolean>;
20
+ sessionPrefix: Ref<string>;
21
+ }
22
+ /** sessionStorage 中存储当前激活标签的 key 后缀 */
65
23
  export declare const SESSION_TAB_NAME = "stacktab-active-tab";
66
- /** iframe 标签 tabId -> 刷新序号,变更时触发 iframe 重载 */
67
- export declare const iframeRefreshKeys: import('vue').Ref<Record<string, number>, Record<string, number>>;
68
- /** 最大标签数量,0 表示不限制 */
69
- export declare let maxTabCount: number;
70
- /** 是否使用页面级滚动 */
71
- export declare let useGlobalScroll: boolean;
72
- /** sessionStorage key 前缀 */
73
- export declare let sessionPrefix: string;
74
- export declare const setMaxTabCount: (n: number) => void;
75
- export declare const setUseGlobalScroll: (v: boolean) => void;
76
- export declare const setSessionPrefix: (p: string) => void;
24
+ export declare const createTabPanelRuntimeState: () => TabPanelRuntimeState;
@@ -0,0 +1,35 @@
1
+ import { Ref } from 'vue';
2
+ import { ITabData, ITabItem } from '../model/TabModel';
3
+ type IframeLoadStatus = 'idle' | 'loading' | 'loaded' | 'timeout';
4
+ interface IframeLoadState {
5
+ status: IframeLoadStatus;
6
+ message?: string;
7
+ }
8
+ export interface UseIframeManagerOptions {
9
+ tabs: Ref<ITabItem[]>;
10
+ iframeRefreshKeys: Ref<Record<string, number>>;
11
+ iframeLoadTimeout: number;
12
+ t: (key: string) => string;
13
+ }
14
+ export declare function useIframeManager(options: UseIframeManagerOptions): {
15
+ iframeTabs: import('vue').ComputedRef<ITabItem[]>;
16
+ activeNonIframeTab: import('vue').ComputedRef<ITabItem | undefined>;
17
+ hasActiveNonIframeTab: import('vue').ComputedRef<boolean>;
18
+ iframeSrcMap: import('vue').ComputedRef<Record<string, string>>;
19
+ getIframeRefreshKey: (frameId: string) => number;
20
+ getIframeKey: (frame: {
21
+ id: string;
22
+ }) => string;
23
+ getIframeLoadState: (frameId: string) => IframeLoadState;
24
+ shouldShowIframeLoading: (frameId: string) => boolean;
25
+ shouldShowIframeError: (frameId: string) => boolean;
26
+ retryIframe: (frameId: string) => void;
27
+ setIframeRef: (id: string, el: HTMLIFrameElement | null) => void;
28
+ handleIframeLoad: (frame: {
29
+ id: string;
30
+ url?: string;
31
+ }, event: Event) => void;
32
+ handleRefreshIframePostMessage: (tabId: string) => void;
33
+ createMessageHandler: (allowedOrigins: Ref<string[]>, openTab: (tab: ITabData) => Promise<string | undefined>) => (ev: MessageEvent) => void;
34
+ };
35
+ export {};
@@ -1,5 +1,4 @@
1
- import { throttle } from 'throttle-debounce';
2
- import { ITabData, IframeRefreshMode } from '../model/TabModel';
1
+ import { ITabData } from '../model/TabModel';
3
2
  /**
4
3
  * useTabActions - 标签操作对外 Hook
5
4
  *
@@ -14,55 +13,15 @@ import { ITabData, IframeRefreshMode } from '../model/TabModel';
14
13
  * - reset() 重置(关闭所有)
15
14
  */
16
15
  export default function useTabActions(): {
17
- openTab: throttle<(tab: ITabData, renew?: any) => Promise<string | undefined>>;
16
+ openTab: (tab: ITabData, renew?: boolean) => Promise<string | undefined>;
18
17
  closeTab: (id: string) => string;
19
18
  closeAllTabs: () => void;
20
19
  refreshTab: (id: string) => void;
21
20
  refreshAllTabs: () => void;
22
- activeTab: (id: string, route?: boolean) => void;
21
+ activeTab: (id: string, route?: boolean) => Promise<void | import('vue-router').NavigationFailure | undefined>;
23
22
  reset: () => void;
24
- tabs: import('vue').Ref<{
25
- id: string;
26
- closable: boolean;
27
- refreshable: boolean;
28
- iframe: boolean;
29
- iframeRefreshMode?: IframeRefreshMode | undefined;
30
- url?: string | undefined;
31
- active: boolean;
32
- pages: {
33
- push: (element: import('..').ITabPage) => void;
34
- pop: () => import('..').ITabPage | undefined;
35
- peek: () => import('..').ITabPage | undefined;
36
- isEmpty: () => boolean;
37
- size: () => number;
38
- clear: () => void;
39
- list: () => import('..').ITabPage[];
40
- toJSON: () => import('..').ITabPage[];
41
- toString: () => string;
42
- };
43
- title: string;
44
- }[], import('..').ITabItem[] | {
45
- id: string;
46
- closable: boolean;
47
- refreshable: boolean;
48
- iframe: boolean;
49
- iframeRefreshMode?: IframeRefreshMode | undefined;
50
- url?: string | undefined;
51
- active: boolean;
52
- pages: {
53
- push: (element: import('..').ITabPage) => void;
54
- pop: () => import('..').ITabPage | undefined;
55
- peek: () => import('..').ITabPage | undefined;
56
- isEmpty: () => boolean;
57
- size: () => number;
58
- clear: () => void;
59
- list: () => import('..').ITabPage[];
60
- toJSON: () => import('..').ITabPage[];
61
- toString: () => string;
62
- };
63
- title: string;
64
- }[]>;
23
+ tabs: import('vue').Ref<import('..').ITabItem[], import('..').ITabItem[]>;
65
24
  setIFramePath: (path: string) => void;
66
- getWrapper: () => Element | undefined;
25
+ getWrapper: () => Element;
67
26
  openInNewWindow: (id: string) => void;
68
27
  };