gi-component 0.0.44 → 0.0.46
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/dist/components/descriptions/src/descriptions.vue.d.ts +1 -1
- package/dist/components/drawer/src/drawer.vue.d.ts +1 -1
- package/dist/components/flex/src/flex.vue.d.ts +2 -2
- package/dist/components/flex/src/type.d.ts +2 -2
- package/dist/components/nav-tabs/index.d.ts +4 -0
- package/dist/components/nav-tabs/src/nav-tabs.vue.d.ts +53 -0
- package/dist/components/nav-tabs/src/type.d.ts +12 -0
- package/dist/components/plus-tabs/index.d.ts +8 -0
- package/dist/components/plus-tabs/src/context.d.ts +26 -0
- package/dist/components/plus-tabs/src/plus-tab-pane.vue.d.ts +39 -0
- package/dist/components/plus-tabs/src/plus-tabs.vue.d.ts +78 -0
- package/dist/components/plus-tabs/src/type.d.ts +5 -0
- package/dist/components/tab/index.d.ts +2 -0
- package/dist/components/tab/src/context.d.ts +10 -0
- package/dist/components/tab/src/tab-item.vue.d.ts +26 -0
- package/dist/components/tab/src/tab.vue.d.ts +44 -0
- package/dist/components/tab/src/type.d.ts +21 -0
- package/dist/components/tab/src/use-nav-tabs.d.ts +2 -0
- package/dist/components/tab/src/use-tabs.d.ts +13 -0
- package/dist/components/tabs/src/tabs.vue.d.ts +2 -2
- package/dist/gi.css +1 -1
- package/dist/hooks/index.d.ts +1 -0
- package/dist/hooks/useNavTabs.d.ts +20 -0
- package/dist/index.d.ts +3 -3
- package/dist/index.es.js +318 -2023
- package/dist/index.es.js.map +1 -1
- package/dist/index.umd.js +1 -1
- package/dist/index.umd.js.map +1 -1
- package/package.json +1 -1
- package/packages/components/nav-tabs/index.ts +5 -0
- package/packages/components/nav-tabs/src/nav-tabs.vue +164 -0
- package/packages/components/nav-tabs/src/type.ts +13 -0
- package/packages/components.d.ts +34 -33
- package/packages/hooks/index.ts +1 -0
- package/packages/hooks/useNavTabs.ts +275 -0
- package/packages/index.ts +4 -0
package/package.json
CHANGED
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div ref="rootRef" :class="[b('nav-tabs'), { [b('nav-tabs--custom')]: props.custom }]">
|
|
3
|
+
<div v-if="slots['left-extra']" :class="b('nav-tabs__left')">
|
|
4
|
+
<slot name="left-extra" />
|
|
5
|
+
</div>
|
|
6
|
+
<div ref="scrollRef" :class="b('nav-tabs__scroll')">
|
|
7
|
+
<div v-for="item in props.data" :key="item.value" :class="[
|
|
8
|
+
b('nav-tabs-item'),
|
|
9
|
+
props.custom
|
|
10
|
+
? b('nav-tabs-item--custom')
|
|
11
|
+
: {
|
|
12
|
+
[b('nav-tabs-item--active')]: model === item.value,
|
|
13
|
+
[b('nav-tabs-item--disabled')]: item.disabled,
|
|
14
|
+
},
|
|
15
|
+
]" :data-value="item.value" @click="handleItemClick(item)">
|
|
16
|
+
<slot :item="item" :active="model === item.value" :disabled="!!item.disabled">
|
|
17
|
+
{{ item.label }}
|
|
18
|
+
</slot>
|
|
19
|
+
</div>
|
|
20
|
+
</div>
|
|
21
|
+
<div v-if="slots['right-extra']" :class="b('nav-tabs__right')">
|
|
22
|
+
<slot name="right-extra" />
|
|
23
|
+
</div>
|
|
24
|
+
</div>
|
|
25
|
+
</template>
|
|
26
|
+
|
|
27
|
+
<script setup lang="ts">
|
|
28
|
+
import type { NavTabItem, NavTabsProps } from './type.ts'
|
|
29
|
+
import { ref, useSlots } from 'vue'
|
|
30
|
+
import { useBemClass, useNavTabs } from '../../../hooks'
|
|
31
|
+
|
|
32
|
+
const model = defineModel<string | number>()
|
|
33
|
+
|
|
34
|
+
const props = withDefaults(defineProps<NavTabsProps>(), {
|
|
35
|
+
data: () => [],
|
|
36
|
+
wheelSpeed: 1,
|
|
37
|
+
custom: false
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
const emits = defineEmits<{
|
|
41
|
+
(e: 'change', value: string | number): void
|
|
42
|
+
}>()
|
|
43
|
+
|
|
44
|
+
defineSlots<{
|
|
45
|
+
'default': (props: {
|
|
46
|
+
item: NavTabItem
|
|
47
|
+
active: boolean
|
|
48
|
+
disabled: boolean
|
|
49
|
+
}) => void
|
|
50
|
+
'left-extra': () => void
|
|
51
|
+
'right-extra': () => void
|
|
52
|
+
}>()
|
|
53
|
+
|
|
54
|
+
const slots = useSlots()
|
|
55
|
+
const { b } = useBemClass()
|
|
56
|
+
|
|
57
|
+
const rootRef = ref<HTMLElement | null>(null)
|
|
58
|
+
const scrollRef = ref<HTMLElement | null>(null)
|
|
59
|
+
|
|
60
|
+
const tabItemClassName = b('nav-tabs-item')
|
|
61
|
+
|
|
62
|
+
useNavTabs({
|
|
63
|
+
tabEl: rootRef,
|
|
64
|
+
tabScrollEl: scrollRef,
|
|
65
|
+
tabItemClassName,
|
|
66
|
+
activeValue: model,
|
|
67
|
+
wheelSpeed: props.wheelSpeed
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
function handleItemClick(item: NavTabItem) {
|
|
71
|
+
if (item.disabled) {
|
|
72
|
+
return
|
|
73
|
+
}
|
|
74
|
+
model.value = item.value
|
|
75
|
+
emits('change', item.value)
|
|
76
|
+
}
|
|
77
|
+
</script>
|
|
78
|
+
|
|
79
|
+
<style lang="scss" scoped>
|
|
80
|
+
@use '../../../styles/var.scss' as a;
|
|
81
|
+
|
|
82
|
+
.#{a.$prefix}-nav-tabs {
|
|
83
|
+
display: flex;
|
|
84
|
+
align-items: center;
|
|
85
|
+
width: 100%;
|
|
86
|
+
height: 40px;
|
|
87
|
+
box-sizing: border-box;
|
|
88
|
+
|
|
89
|
+
&__left {
|
|
90
|
+
flex-shrink: 0;
|
|
91
|
+
margin-right: 10px;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
&__right {
|
|
95
|
+
flex-shrink: 0;
|
|
96
|
+
margin-left: 10px;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
&__scroll {
|
|
100
|
+
flex: 1;
|
|
101
|
+
display: flex;
|
|
102
|
+
overflow-x: auto;
|
|
103
|
+
overflow-y: hidden;
|
|
104
|
+
scrollbar-width: none;
|
|
105
|
+
height: 100%;
|
|
106
|
+
|
|
107
|
+
&::-webkit-scrollbar {
|
|
108
|
+
display: none;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
&:not(.#{a.$prefix}-nav-tabs--custom) {
|
|
113
|
+
.#{a.$prefix}-nav-tabs-item {
|
|
114
|
+
padding: 0 16px;
|
|
115
|
+
color: var(--el-text-color-regular);
|
|
116
|
+
position: relative;
|
|
117
|
+
font-size: 14px;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
.#{a.$prefix}-nav-tabs-item--active {
|
|
121
|
+
color: var(--el-color-primary);
|
|
122
|
+
font-weight: 500;
|
|
123
|
+
|
|
124
|
+
&::after {
|
|
125
|
+
content: '';
|
|
126
|
+
position: absolute;
|
|
127
|
+
left: 16px;
|
|
128
|
+
right: 16px;
|
|
129
|
+
bottom: 0;
|
|
130
|
+
height: 2px;
|
|
131
|
+
background: var(--el-color-primary);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
.#{a.$prefix}-nav-tabs-item--disabled {
|
|
136
|
+
color: var(--el-text-color-disabled);
|
|
137
|
+
cursor: not-allowed;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
.#{a.$prefix}-nav-tabs--custom {
|
|
143
|
+
.#{a.$prefix}-nav-tabs__scroll {
|
|
144
|
+
gap: 8px;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
.#{a.$prefix}-nav-tabs-item {
|
|
148
|
+
padding: 0;
|
|
149
|
+
height: auto;
|
|
150
|
+
line-height: inherit;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
.#{a.$prefix}-nav-tabs-item {
|
|
155
|
+
flex-shrink: 0;
|
|
156
|
+
cursor: pointer;
|
|
157
|
+
user-select: none;
|
|
158
|
+
height: 100%;
|
|
159
|
+
display: flex;
|
|
160
|
+
justify-content: center;
|
|
161
|
+
align-items: center;
|
|
162
|
+
position: relative;
|
|
163
|
+
}
|
|
164
|
+
</style>
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export type NavTabItem = {
|
|
2
|
+
label: string
|
|
3
|
+
value: string | number
|
|
4
|
+
disabled?: boolean
|
|
5
|
+
[key: string]: unknown
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export interface NavTabsProps {
|
|
9
|
+
data?: NavTabItem[]
|
|
10
|
+
wheelSpeed?: number
|
|
11
|
+
/** 自定义项样式:无 padding,不应用 --active / --disabled 修饰类 */
|
|
12
|
+
custom?: boolean
|
|
13
|
+
}
|
package/packages/components.d.ts
CHANGED
|
@@ -1,33 +1,34 @@
|
|
|
1
|
-
/* eslint-disable */
|
|
2
|
-
// @ts-nocheck
|
|
3
|
-
// Generated by unplugin-vue-components
|
|
4
|
-
// Read more: https://github.com/vuejs/core/pull/3399
|
|
5
|
-
// biome-ignore lint: disable
|
|
6
|
-
export {}
|
|
7
|
-
|
|
8
|
-
/* prettier-ignore */
|
|
9
|
-
declare module 'vue' {
|
|
10
|
-
export interface GlobalComponents {
|
|
11
|
-
GiButton: typeof import('./components/button/src/button.vue')['default']
|
|
12
|
-
GiCard: typeof import('./components/card/src/card.vue')['default']
|
|
13
|
-
GiDescriptions: typeof import('./components/descriptions/src/descriptions.vue')['default']
|
|
14
|
-
GiDialog: typeof import('./components/dialog/src/dialog.vue')['default']
|
|
15
|
-
GiDialogContent: typeof import('./components/dialog/src/dialog-content.vue')['default']
|
|
16
|
-
GiDot: typeof import('./components/dot/src/dot.vue')['default']
|
|
17
|
-
GiDrawer: typeof import('./components/drawer/src/drawer.vue')['default']
|
|
18
|
-
GiEditTable: typeof import('./components/edit-table/src/edit-table.vue')['default']
|
|
19
|
-
GiFlex: typeof import('./components/flex/src/flex.vue')['default']
|
|
20
|
-
GiForm: typeof import('./components/form/src/form.vue')['default']
|
|
21
|
-
GiGrid: typeof import('./components/grid/src/grid.vue')['default']
|
|
22
|
-
GiGridItem: typeof import('./components/grid/src/grid-item.vue')['default']
|
|
23
|
-
GiInputGroup: typeof import('./components/input-group/src/input-group.vue')['default']
|
|
24
|
-
GiInputSearch: typeof import('./components/input-search/src/input-search.vue')['default']
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
}
|
|
1
|
+
/* eslint-disable */
|
|
2
|
+
// @ts-nocheck
|
|
3
|
+
// Generated by unplugin-vue-components
|
|
4
|
+
// Read more: https://github.com/vuejs/core/pull/3399
|
|
5
|
+
// biome-ignore lint: disable
|
|
6
|
+
export {}
|
|
7
|
+
|
|
8
|
+
/* prettier-ignore */
|
|
9
|
+
declare module 'vue' {
|
|
10
|
+
export interface GlobalComponents {
|
|
11
|
+
GiButton: typeof import('./components/button/src/button.vue')['default']
|
|
12
|
+
GiCard: typeof import('./components/card/src/card.vue')['default']
|
|
13
|
+
GiDescriptions: typeof import('./components/descriptions/src/descriptions.vue')['default']
|
|
14
|
+
GiDialog: typeof import('./components/dialog/src/dialog.vue')['default']
|
|
15
|
+
GiDialogContent: typeof import('./components/dialog/src/dialog-content.vue')['default']
|
|
16
|
+
GiDot: typeof import('./components/dot/src/dot.vue')['default']
|
|
17
|
+
GiDrawer: typeof import('./components/drawer/src/drawer.vue')['default']
|
|
18
|
+
GiEditTable: typeof import('./components/edit-table/src/edit-table.vue')['default']
|
|
19
|
+
GiFlex: typeof import('./components/flex/src/flex.vue')['default']
|
|
20
|
+
GiForm: typeof import('./components/form/src/form.vue')['default']
|
|
21
|
+
GiGrid: typeof import('./components/grid/src/grid.vue')['default']
|
|
22
|
+
GiGridItem: typeof import('./components/grid/src/grid-item.vue')['default']
|
|
23
|
+
GiInputGroup: typeof import('./components/input-group/src/input-group.vue')['default']
|
|
24
|
+
GiInputSearch: typeof import('./components/input-search/src/input-search.vue')['default']
|
|
25
|
+
GiNavTabs: typeof import('./components/nav-tabs/src/nav-tabs.vue')['default']
|
|
26
|
+
GiPageLayout: typeof import('./components/page-layout/src/page-layout.vue')['default']
|
|
27
|
+
GiSplitButton: typeof import('./components/page-layout/src/split-button.vue')['default']
|
|
28
|
+
GiTable: typeof import('./components/table/src/table.vue')['default']
|
|
29
|
+
GiTableColumn: typeof import('./components/table/src/TableColumn.vue')['default']
|
|
30
|
+
GiTabs: typeof import('./components/tabs/src/tabs.vue')['default']
|
|
31
|
+
GiTag: typeof import('./components/tag/src/tag.vue')['default']
|
|
32
|
+
GiTreeTransfer: typeof import('./components/tree-transfer/src/tree-transfer.vue')['default']
|
|
33
|
+
}
|
|
34
|
+
}
|
package/packages/hooks/index.ts
CHANGED
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
import type { MaybeRefOrGetter } from 'vue'
|
|
2
|
+
import {
|
|
3
|
+
getCurrentInstance,
|
|
4
|
+
nextTick,
|
|
5
|
+
onMounted,
|
|
6
|
+
onUnmounted,
|
|
7
|
+
toRef,
|
|
8
|
+
toValue,
|
|
9
|
+
watch
|
|
10
|
+
} from 'vue'
|
|
11
|
+
|
|
12
|
+
export interface UseNavTabsOptions {
|
|
13
|
+
/** 根容器:选择器(.tab)或 HTMLElement / Ref */
|
|
14
|
+
tabEl: MaybeRefOrGetter<string | HTMLElement | null>
|
|
15
|
+
/** 滚动容器:类名 tab__scroll 或 .tab__scroll 或 HTMLElement */
|
|
16
|
+
tabScrollEl: MaybeRefOrGetter<string | HTMLElement | null>
|
|
17
|
+
/** tab 项 class,如 tab-item */
|
|
18
|
+
tabItemClassName: string
|
|
19
|
+
/** 当前选中值;变化时自动居中(推荐传入) */
|
|
20
|
+
activeValue?: MaybeRefOrGetter<string | number | undefined>
|
|
21
|
+
/** 滚轮换算系数,默认 1 */
|
|
22
|
+
wheelSpeed?: number
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface UseNavTabsReturn {
|
|
26
|
+
/** 滚动到当前选中项并居中 */
|
|
27
|
+
scrollToActive: (behavior?: ScrollBehavior) => void
|
|
28
|
+
/** 获取解析后的滚动容器 */
|
|
29
|
+
getScrollEl: () => HTMLElement | null
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function normalizeSelector(value: string): string {
|
|
33
|
+
if (value.startsWith('.') || value.startsWith('#')) {
|
|
34
|
+
return value
|
|
35
|
+
}
|
|
36
|
+
return `.${value}`
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function resolveElement(
|
|
40
|
+
target: string | HTMLElement | null | undefined,
|
|
41
|
+
root?: HTMLElement | Document | null
|
|
42
|
+
): HTMLElement | null {
|
|
43
|
+
if (!target) {
|
|
44
|
+
return null
|
|
45
|
+
}
|
|
46
|
+
if (typeof target !== 'string') {
|
|
47
|
+
return target
|
|
48
|
+
}
|
|
49
|
+
const scope = root ?? document
|
|
50
|
+
return scope.querySelector(normalizeSelector(target))
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** 滚轮平滑插值系数,越大跟手越快 */
|
|
54
|
+
const WHEEL_SCROLL_LERP = 0.4
|
|
55
|
+
const WHEEL_LINE_HEIGHT = 16
|
|
56
|
+
|
|
57
|
+
function getWheelPixelDelta(event: WheelEvent, scrollEl: HTMLElement): number {
|
|
58
|
+
let delta = Math.abs(event.deltaX) > Math.abs(event.deltaY)
|
|
59
|
+
? event.deltaX
|
|
60
|
+
: event.deltaY
|
|
61
|
+
|
|
62
|
+
if (event.deltaMode === WheelEvent.DOM_DELTA_LINE) {
|
|
63
|
+
delta *= WHEEL_LINE_HEIGHT
|
|
64
|
+
} else if (event.deltaMode === WheelEvent.DOM_DELTA_PAGE) {
|
|
65
|
+
delta *= scrollEl.clientWidth
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return delta
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function clampScrollLeft(scrollEl: HTMLElement, left: number): number {
|
|
72
|
+
const maxScroll = Math.max(0, scrollEl.scrollWidth - scrollEl.clientWidth)
|
|
73
|
+
return Math.max(0, Math.min(left, maxScroll))
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function scrollItemToCenter(
|
|
77
|
+
scrollEl: HTMLElement,
|
|
78
|
+
activeEl: HTMLElement,
|
|
79
|
+
behavior: ScrollBehavior = 'smooth'
|
|
80
|
+
) {
|
|
81
|
+
const maxScroll = scrollEl.scrollWidth - scrollEl.clientWidth
|
|
82
|
+
if (maxScroll <= 0) {
|
|
83
|
+
return
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// 使用视口坐标计算,兼容 flex gap / margin 等间距,避免 offsetLeft 偏差
|
|
87
|
+
const scrollRect = scrollEl.getBoundingClientRect()
|
|
88
|
+
const activeRect = activeEl.getBoundingClientRect()
|
|
89
|
+
const activeLeftInContent = activeRect.left - scrollRect.left + scrollEl.scrollLeft
|
|
90
|
+
const target = activeLeftInContent - (scrollEl.clientWidth - activeRect.width) / 2
|
|
91
|
+
|
|
92
|
+
scrollEl.scrollTo({
|
|
93
|
+
left: Math.max(0, Math.min(target, maxScroll)),
|
|
94
|
+
behavior
|
|
95
|
+
})
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export function useNavTabs(options: UseNavTabsOptions): UseNavTabsReturn {
|
|
99
|
+
const {
|
|
100
|
+
tabEl,
|
|
101
|
+
tabScrollEl,
|
|
102
|
+
tabItemClassName,
|
|
103
|
+
activeValue,
|
|
104
|
+
wheelSpeed = 1
|
|
105
|
+
} = options
|
|
106
|
+
|
|
107
|
+
const activeClassName = `${tabItemClassName}--active`
|
|
108
|
+
const itemSelector = normalizeSelector(tabItemClassName)
|
|
109
|
+
|
|
110
|
+
let rootEl: HTMLElement | null = null
|
|
111
|
+
let scrollEl: HTMLElement | null = null
|
|
112
|
+
let resizeObserver: ResizeObserver | null = null
|
|
113
|
+
let wheelRafId: number | null = null
|
|
114
|
+
let wheelTargetScrollLeft = 0
|
|
115
|
+
|
|
116
|
+
const cancelWheelAnimation = () => {
|
|
117
|
+
if (wheelRafId !== null) {
|
|
118
|
+
cancelAnimationFrame(wheelRafId)
|
|
119
|
+
wheelRafId = null
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const runWheelAnimation = () => {
|
|
124
|
+
if (!scrollEl) {
|
|
125
|
+
cancelWheelAnimation()
|
|
126
|
+
return
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const current = scrollEl.scrollLeft
|
|
130
|
+
const diff = wheelTargetScrollLeft - current
|
|
131
|
+
|
|
132
|
+
if (Math.abs(diff) < 0.5) {
|
|
133
|
+
scrollEl.scrollLeft = wheelTargetScrollLeft
|
|
134
|
+
cancelWheelAnimation()
|
|
135
|
+
return
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
scrollEl.scrollLeft = current + diff * WHEEL_SCROLL_LERP
|
|
139
|
+
wheelRafId = requestAnimationFrame(runWheelAnimation)
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const resolveElements = () => {
|
|
143
|
+
const instanceRoot = getCurrentInstance()?.proxy?.$el as HTMLElement | undefined
|
|
144
|
+
const fallbackRoot = instanceRoot instanceof HTMLElement ? instanceRoot : document
|
|
145
|
+
|
|
146
|
+
rootEl = resolveElement(toValue(tabEl), fallbackRoot)
|
|
147
|
+
scrollEl = resolveElement(toValue(tabScrollEl), rootEl ?? fallbackRoot)
|
|
148
|
+
|
|
149
|
+
return Boolean(rootEl && scrollEl)
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const findActiveItem = (): HTMLElement | null => {
|
|
153
|
+
if (!scrollEl) {
|
|
154
|
+
return null
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const activeByClass = scrollEl.querySelector(normalizeSelector(activeClassName))
|
|
158
|
+
if (activeByClass instanceof HTMLElement) {
|
|
159
|
+
return activeByClass
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const value = activeValue !== undefined ? toValue(activeValue) : undefined
|
|
163
|
+
if (value === undefined) {
|
|
164
|
+
return null
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const items = scrollEl.querySelectorAll<HTMLElement>(itemSelector)
|
|
168
|
+
const matched = Array.from(items).find((item) => item.dataset.value === String(value))
|
|
169
|
+
return matched ?? null
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const scrollToActive = (behavior: ScrollBehavior = 'smooth') => {
|
|
173
|
+
if (!scrollEl) {
|
|
174
|
+
return
|
|
175
|
+
}
|
|
176
|
+
cancelWheelAnimation()
|
|
177
|
+
const activeItem = findActiveItem()
|
|
178
|
+
if (activeItem) {
|
|
179
|
+
scrollItemToCenter(scrollEl, activeItem, behavior)
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const handleWheel = (event: WheelEvent) => {
|
|
184
|
+
if (!scrollEl) {
|
|
185
|
+
return
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const delta = getWheelPixelDelta(event, scrollEl)
|
|
189
|
+
if (delta === 0) {
|
|
190
|
+
return
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
event.preventDefault()
|
|
194
|
+
|
|
195
|
+
if (wheelRafId === null) {
|
|
196
|
+
wheelTargetScrollLeft = scrollEl.scrollLeft
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
wheelTargetScrollLeft = clampScrollLeft(
|
|
200
|
+
scrollEl,
|
|
201
|
+
wheelTargetScrollLeft + delta * wheelSpeed
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
if (wheelRafId === null) {
|
|
205
|
+
wheelRafId = requestAnimationFrame(runWheelAnimation)
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
const bindWheel = () => {
|
|
210
|
+
scrollEl?.addEventListener('wheel', handleWheel, { passive: false })
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
const unbindWheel = () => {
|
|
214
|
+
scrollEl?.removeEventListener('wheel', handleWheel)
|
|
215
|
+
cancelWheelAnimation()
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
const bindResizeObserver = () => {
|
|
219
|
+
if (!scrollEl || typeof ResizeObserver === 'undefined') {
|
|
220
|
+
return
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
resizeObserver = new ResizeObserver(() => {
|
|
224
|
+
scrollToActive('auto')
|
|
225
|
+
})
|
|
226
|
+
resizeObserver.observe(scrollEl)
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
const unbindResizeObserver = () => {
|
|
230
|
+
resizeObserver?.disconnect()
|
|
231
|
+
resizeObserver = null
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
const setup = async () => {
|
|
235
|
+
await nextTick()
|
|
236
|
+
if (!resolveElements()) {
|
|
237
|
+
return
|
|
238
|
+
}
|
|
239
|
+
bindWheel()
|
|
240
|
+
bindResizeObserver()
|
|
241
|
+
scrollToActive('auto')
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
onMounted(() => {
|
|
245
|
+
setup()
|
|
246
|
+
})
|
|
247
|
+
|
|
248
|
+
watch(
|
|
249
|
+
() => [toValue(tabEl), toValue(tabScrollEl)] as const,
|
|
250
|
+
() => {
|
|
251
|
+
unbindWheel()
|
|
252
|
+
unbindResizeObserver()
|
|
253
|
+
setup()
|
|
254
|
+
}
|
|
255
|
+
)
|
|
256
|
+
|
|
257
|
+
if (activeValue !== undefined) {
|
|
258
|
+
watch(toRef(activeValue), () => {
|
|
259
|
+
nextTick(() => scrollToActive())
|
|
260
|
+
})
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
onUnmounted(() => {
|
|
264
|
+
unbindWheel()
|
|
265
|
+
unbindResizeObserver()
|
|
266
|
+
cancelWheelAnimation()
|
|
267
|
+
rootEl = null
|
|
268
|
+
scrollEl = null
|
|
269
|
+
})
|
|
270
|
+
|
|
271
|
+
return {
|
|
272
|
+
scrollToActive,
|
|
273
|
+
getScrollEl: () => scrollEl
|
|
274
|
+
}
|
|
275
|
+
}
|
package/packages/index.ts
CHANGED
|
@@ -13,6 +13,7 @@ import GridItem from './components/grid/src/grid-item.vue'
|
|
|
13
13
|
import Grid from './components/grid/src/grid.vue'
|
|
14
14
|
import InputGroup from './components/input-group'
|
|
15
15
|
import InputSearch from './components/input-search'
|
|
16
|
+
import NavTabs from './components/nav-tabs'
|
|
16
17
|
import PageLayout from './components/page-layout'
|
|
17
18
|
import Table from './components/table'
|
|
18
19
|
import Tabs from './components/tabs'
|
|
@@ -28,6 +29,7 @@ export * from './components/dialog'
|
|
|
28
29
|
export * from './components/drawer'
|
|
29
30
|
export * from './components/edit-table'
|
|
30
31
|
export * from './components/form'
|
|
32
|
+
export * from './components/nav-tabs'
|
|
31
33
|
export * from './components/table'
|
|
32
34
|
export * from './components/tabs'
|
|
33
35
|
export * from './components/tag'
|
|
@@ -43,6 +45,7 @@ const components = {
|
|
|
43
45
|
Tabs,
|
|
44
46
|
InputGroup,
|
|
45
47
|
InputSearch,
|
|
48
|
+
NavTabs,
|
|
46
49
|
Flex,
|
|
47
50
|
Grid,
|
|
48
51
|
GridItem,
|
|
@@ -64,6 +67,7 @@ export const GiDot: typeof Dot = Dot
|
|
|
64
67
|
export const GiTabs: typeof Tabs = Tabs
|
|
65
68
|
export const GiInputGroup: typeof InputGroup = InputGroup
|
|
66
69
|
export const GiInputSearch: typeof InputSearch = InputSearch
|
|
70
|
+
export const GiNavTabs: typeof NavTabs = NavTabs
|
|
67
71
|
export const GiFlex: typeof Flex = Flex
|
|
68
72
|
export const GiGrid: typeof Grid = Grid
|
|
69
73
|
export const GiGridItem: typeof GridItem = GridItem
|