vue-stack-tabs 1.3.0 → 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.
- package/README.en.md +119 -52
- package/README.md +104 -41
- 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 +20 -0
- package/dist/{src/lib/hooks → hooks}/tabPanel/scroll.d.ts +2 -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 +3 -1
- 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 +83 -59
- package/src/lib/StackTabs.vue +16 -3
- package/src/lib/assets/style/common/var.scss +2 -0
- package/src/lib/assets/style/stackTab.scss +43 -4
- 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/TabHeaderItem.vue +30 -2
- package/src/lib/components/TabHeader/index.vue +76 -4
- package/src/lib/hooks/stackTabsContext.ts +23 -18
- package/src/lib/hooks/tabPanel/pageComponentFactory.tsx +142 -0
- package/src/lib/hooks/tabPanel/refresh.ts +3 -0
- package/src/lib/hooks/tabPanel/scroll.ts +110 -7
- package/src/lib/hooks/tabPanel/session.ts +29 -3
- 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 +20 -8
- package/src/lib/hooks/useTabEventBus.ts +5 -3
- package/src/lib/hooks/useTabPanel.tsx +132 -180
- package/src/lib/hooks/useTabRouter.ts +11 -2
- 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/refresh.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
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useScrollShadow - 为可滚动容器动态切换上下阴影类。
|
|
3
|
+
*
|
|
4
|
+
* 职责:在 scroll 事件触发时根据 scrollTop / scrollHeight 边界,
|
|
5
|
+
* 给目标元素动态加上/移除 stack-tab__shadow-inset-top / -bottom 类,
|
|
6
|
+
* 距离边界 > 阈值时显示阴影(提示用户"还能往这个方向滚"),
|
|
7
|
+
* 已贴边(距离边界 ≤ 阈值)时隐藏阴影,避免到顶/到底后阴影残留误导。
|
|
8
|
+
*
|
|
9
|
+
* 设计要点:
|
|
10
|
+
* - 使用 passive scroll 监听,不阻塞滚动
|
|
11
|
+
* - 使用 ResizeObserver 监视子页面缩放/尺寸变化,重新计算阴影边界
|
|
12
|
+
* - 使用 rAF 节流,避免一帧内多次写 class
|
|
13
|
+
* - 阈值(默认 5px)内视为已贴边(≤ 阈值)→ 隐藏阴影;超过阈值视为可继续滚动 → 显示阴影
|
|
14
|
+
* - SSR 安全:在 document 不存在时直接返回
|
|
15
|
+
* - 自动清理:onScopeDispose 移除监听并取消 rAF/disconnect observer
|
|
16
|
+
*/
|
|
17
|
+
import { onScopeDispose, type Ref } from 'vue'
|
|
18
|
+
|
|
19
|
+
const SHADOW_TOP_CLASS = 'stack-tab__shadow-inset-top'
|
|
20
|
+
const SHADOW_BOTTOM_CLASS = 'stack-tab__shadow-inset-bottom'
|
|
21
|
+
const SHADOW_VERTICAL_CLASS = 'stack-tab__shadow-inset-vertical'
|
|
22
|
+
const DEFAULT_EDGE_THRESHOLD = 5
|
|
23
|
+
|
|
24
|
+
export interface UseScrollShadowOptions {
|
|
25
|
+
/** 容器元素的 ref,可以是 Ref<HTMLElement | null> 或 getter 函数 */
|
|
26
|
+
target: Ref<HTMLElement | null> | (() => HTMLElement | null)
|
|
27
|
+
/**
|
|
28
|
+
* 是否监听内容缩放/尺寸变化(默认 true)。
|
|
29
|
+
* 启用后会用 ResizeObserver 监视目标尺寸,容器或子树尺寸变化都会重新计算阴影边界。
|
|
30
|
+
*/
|
|
31
|
+
watchResize?: boolean
|
|
32
|
+
/**
|
|
33
|
+
* 到顶/到底的判定阈值(px,默认 5)。
|
|
34
|
+
* 距离边界 ≤ 阈值视为已贴边(不显示阴影);距离边界 > 阈值视为可继续滚动(显示阴影)。
|
|
35
|
+
*/
|
|
36
|
+
edgeThreshold?: number
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface UseScrollShadowReturn {
|
|
40
|
+
/** 手动同步一次(用于内容变化/resize 后) */
|
|
41
|
+
sync: () => void
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const resolveTarget = (
|
|
45
|
+
target: Ref<HTMLElement | null> | (() => HTMLElement | null)
|
|
46
|
+
): HTMLElement | null => (typeof target === 'function' ? target() : target.value)
|
|
47
|
+
|
|
48
|
+
export const useScrollShadow = (options: UseScrollShadowOptions): UseScrollShadowReturn => {
|
|
49
|
+
if (typeof document === 'undefined') {
|
|
50
|
+
return { sync: () => undefined }
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const watchResize = options.watchResize !== false
|
|
54
|
+
const edgeThreshold = Math.max(0, options.edgeThreshold ?? DEFAULT_EDGE_THRESHOLD)
|
|
55
|
+
let rafId: number | null = null
|
|
56
|
+
let attached: HTMLElement | null = null
|
|
57
|
+
let resizeObserver: ResizeObserver | null = null
|
|
58
|
+
|
|
59
|
+
const recompute = (): void => {
|
|
60
|
+
if (!attached) return
|
|
61
|
+
const target = attached
|
|
62
|
+
const { scrollTop, scrollHeight, clientHeight } = target
|
|
63
|
+
const distanceToTop = scrollTop
|
|
64
|
+
const distanceToBottom = scrollHeight - clientHeight - scrollTop
|
|
65
|
+
// 顶部阴影:表示上方还有内容,离开顶部(距离顶部 > 阈值)时显示。
|
|
66
|
+
// 底部阴影:表示下方还有内容,离开底部(距离底部 > 阈值)时显示。
|
|
67
|
+
const showTopShadow = distanceToTop > edgeThreshold
|
|
68
|
+
const showBottomShadow = distanceToBottom > edgeThreshold
|
|
69
|
+
// 注意:-top / -bottom 是两个独立 CSS 规则、作用在同一个 ::after 上,
|
|
70
|
+
// 同时加会互相覆盖(后定义的 -bottom 胜出)。因此这里互斥地只加一个类:
|
|
71
|
+
// 两侧都有内容用 -vertical,只有一侧用 -top / -bottom,贴边时都不加。
|
|
72
|
+
target.classList.toggle(SHADOW_VERTICAL_CLASS, showTopShadow && showBottomShadow)
|
|
73
|
+
target.classList.toggle(SHADOW_TOP_CLASS, showTopShadow && !showBottomShadow)
|
|
74
|
+
target.classList.toggle(SHADOW_BOTTOM_CLASS, !showTopShadow && showBottomShadow)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const schedule = (): void => {
|
|
78
|
+
if (rafId !== null) return
|
|
79
|
+
rafId = requestAnimationFrame(() => {
|
|
80
|
+
rafId = null
|
|
81
|
+
recompute()
|
|
82
|
+
})
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const attach = (): void => {
|
|
86
|
+
const el = resolveTarget(options.target)
|
|
87
|
+
if (!el || el === attached) return
|
|
88
|
+
if (attached) {
|
|
89
|
+
attached.removeEventListener('scroll', schedule)
|
|
90
|
+
resizeObserver?.disconnect()
|
|
91
|
+
resizeObserver = null
|
|
92
|
+
}
|
|
93
|
+
attached = el
|
|
94
|
+
el.addEventListener('scroll', schedule, { passive: true })
|
|
95
|
+
if (watchResize && typeof ResizeObserver !== 'undefined') {
|
|
96
|
+
resizeObserver = new ResizeObserver(() => schedule())
|
|
97
|
+
resizeObserver.observe(el)
|
|
98
|
+
}
|
|
99
|
+
schedule()
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const detach = (): void => {
|
|
103
|
+
if (attached) {
|
|
104
|
+
attached.removeEventListener('scroll', schedule)
|
|
105
|
+
attached.classList.remove(SHADOW_TOP_CLASS, SHADOW_BOTTOM_CLASS, SHADOW_VERTICAL_CLASS)
|
|
106
|
+
attached = null
|
|
107
|
+
}
|
|
108
|
+
resizeObserver?.disconnect()
|
|
109
|
+
resizeObserver = null
|
|
110
|
+
if (rafId !== null) {
|
|
111
|
+
cancelAnimationFrame(rafId)
|
|
112
|
+
rafId = null
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// 初始挂载(target 可能是异步插入的 DOM,使用 microtask 等一帧)
|
|
117
|
+
queueMicrotask(attach)
|
|
118
|
+
|
|
119
|
+
onScopeDispose(detach)
|
|
120
|
+
|
|
121
|
+
return {
|
|
122
|
+
sync: () => schedule()
|
|
123
|
+
}
|
|
124
|
+
}
|
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
} from '../utils/urlParser'
|
|
12
12
|
import type { ITabData, IframeRefreshMode } from '../model/TabModel'
|
|
13
13
|
import { runNavigationTransaction } from './tabPanel/navigationTransaction'
|
|
14
|
-
import { resolveStackTabsRuntimeContext } from './stackTabsContext'
|
|
14
|
+
import { resolveStackTabsRuntimeContext, type StackTabsRuntimeContext } from './stackTabsContext'
|
|
15
15
|
|
|
16
16
|
/**
|
|
17
17
|
* useTabActions - 标签操作对外 Hook
|
|
@@ -26,9 +26,9 @@ import { resolveStackTabsRuntimeContext } from './stackTabsContext'
|
|
|
26
26
|
* - activeTab(id, isRoute?) 激活标签
|
|
27
27
|
* - reset() 重置(关闭所有)
|
|
28
28
|
*/
|
|
29
|
-
export default function useTabActions() {
|
|
29
|
+
export default function useTabActions(providedRuntimeContext?: StackTabsRuntimeContext) {
|
|
30
30
|
const router = useRouter()
|
|
31
|
-
const runtimeContext = resolveStackTabsRuntimeContext()
|
|
31
|
+
const runtimeContext = providedRuntimeContext ?? resolveStackTabsRuntimeContext()
|
|
32
32
|
const {
|
|
33
33
|
active,
|
|
34
34
|
hasTab,
|
|
@@ -38,11 +38,12 @@ export default function useTabActions() {
|
|
|
38
38
|
renewTab,
|
|
39
39
|
getTab,
|
|
40
40
|
tabs,
|
|
41
|
+
moveTab,
|
|
41
42
|
removeTab,
|
|
42
43
|
removeAllTabs,
|
|
43
44
|
refreshTab,
|
|
44
45
|
refreshAllTabs
|
|
45
|
-
} = useTabPanel()
|
|
46
|
+
} = useTabPanel(runtimeContext)
|
|
46
47
|
|
|
47
48
|
/** 合并默认值,生成完整 tabInfo */
|
|
48
49
|
const prepareTabInfo = (tab: ITabData): ITabData => {
|
|
@@ -79,7 +80,7 @@ export default function useTabActions() {
|
|
|
79
80
|
let refreshKeySnapshot: number | undefined
|
|
80
81
|
|
|
81
82
|
if (tabInfo.id && renew && isExistingTab) {
|
|
82
|
-
const currentTab = getTab(
|
|
83
|
+
const currentTab = getTab(tabInfo.id!)
|
|
83
84
|
rollbackRenew = renewTab(tab)
|
|
84
85
|
if (currentTab?.active) {
|
|
85
86
|
refreshKeySnapshot = refreshKey.value
|
|
@@ -142,9 +143,19 @@ export default function useTabActions() {
|
|
|
142
143
|
runtimeContext.iframePath.value = path
|
|
143
144
|
}
|
|
144
145
|
|
|
145
|
-
/**
|
|
146
|
-
|
|
147
|
-
|
|
146
|
+
/**
|
|
147
|
+
* 获取缓存页面容器。
|
|
148
|
+
* 不传参数时保留旧行为并返回第一个容器;多标签场景应传 tabId 或 pageId,
|
|
149
|
+
* 避免因 DOM 排序而取得错误页面。
|
|
150
|
+
*/
|
|
151
|
+
const getWrapper = (
|
|
152
|
+
target: { tabId?: string; pageId?: string } = {}
|
|
153
|
+
): HTMLElement | undefined => {
|
|
154
|
+
if (typeof document === 'undefined') return undefined
|
|
155
|
+
const pageId =
|
|
156
|
+
target.pageId ?? (target.tabId ? getTab(target.tabId)?.pages.peek()?.id : undefined)
|
|
157
|
+
if (pageId) return document.getElementById(`W-${pageId}`) ?? undefined
|
|
158
|
+
return document.querySelector<HTMLElement>('.cache-page-wrapper') ?? undefined
|
|
148
159
|
}
|
|
149
160
|
|
|
150
161
|
/** iframe 标签在新窗口打开(无法嵌入时降级) */
|
|
@@ -169,6 +180,7 @@ export default function useTabActions() {
|
|
|
169
180
|
|
|
170
181
|
// 激活与重置
|
|
171
182
|
activeTab: active,
|
|
183
|
+
moveTab,
|
|
172
184
|
reset,
|
|
173
185
|
|
|
174
186
|
// 数据与配置
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
import type { InjectionKey } from 'vue'
|
|
8
8
|
import { hasInjectionContext, inject } from 'vue'
|
|
9
9
|
import type { Emitter, EventType } from 'mitt'
|
|
10
|
-
import { resolveStackTabsRuntimeContext } from './stackTabsContext'
|
|
10
|
+
import { resolveStackTabsRuntimeContext, type StackTabsRuntimeContext } from './stackTabsContext'
|
|
11
11
|
|
|
12
12
|
declare module '@vue/runtime-core' {
|
|
13
13
|
export interface ComponentCustomProperties {
|
|
@@ -35,7 +35,9 @@ export interface TabEventPayloadMap extends Record<EventType, unknown> {
|
|
|
35
35
|
export const tabEmitterKey: InjectionKey<Emitter<TabEventPayloadMap>> = Symbol('tabEmitter')
|
|
36
36
|
|
|
37
37
|
/** 获取标签事件总线,用于组件间通信 */
|
|
38
|
-
export const useTabEmitter = (
|
|
38
|
+
export const useTabEmitter = (
|
|
39
|
+
runtimeContext?: StackTabsRuntimeContext
|
|
40
|
+
): Emitter<TabEventPayloadMap> => {
|
|
39
41
|
const injected = hasInjectionContext() ? inject(tabEmitterKey, null) : null
|
|
40
|
-
return injected ?? resolveStackTabsRuntimeContext().eventBus
|
|
42
|
+
return injected ?? runtimeContext?.eventBus ?? resolveStackTabsRuntimeContext().eventBus
|
|
41
43
|
}
|