mirror of
https://gitee.com/element-plus/element-plus.git
synced 2024-12-01 10:47:57 +08:00
feat(tabs): Remove setupState
This commit is contained in:
parent
72daa4359d
commit
423feb1051
@ -55,7 +55,7 @@ describe('Tabs.vue', () => {
|
||||
},
|
||||
methods: {
|
||||
handleClick(tab) {
|
||||
this.activeName = tab.setupState.paneName
|
||||
this.activeName = tab.paneName
|
||||
},
|
||||
},
|
||||
template: `
|
||||
|
@ -5,19 +5,20 @@
|
||||
></div>
|
||||
</template>
|
||||
<script lang='ts'>
|
||||
import { defineComponent, inject, getCurrentInstance, watch, nextTick, ref, ComponentInternalInstance } from 'vue'
|
||||
import { defineComponent, inject, getCurrentInstance, watch, nextTick, ref, PropType } from 'vue'
|
||||
import { capitalize } from '@vue/shared'
|
||||
import { Pane, RootTabs } from './tabs.vue'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'ElTabBar',
|
||||
props: {
|
||||
tabs: {
|
||||
type: Array as PropType<ComponentInternalInstance[]>,
|
||||
default: () => ([] as ComponentInternalInstance[]),
|
||||
type: Array as PropType<Pane[]>,
|
||||
default: () => ([] as Pane[]),
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
const rootTabs = inject('rootTabs')
|
||||
const rootTabs = inject<RootTabs>('rootTabs')
|
||||
if (!rootTabs) {
|
||||
throw new Error(`ElTabBar must use with ElTabs`)
|
||||
}
|
||||
@ -32,9 +33,9 @@ export default defineComponent({
|
||||
const sizeDir = sizeName === 'width' ? 'x' : 'y'
|
||||
|
||||
props.tabs.every(tab => {
|
||||
let $el = instance.parent.refs?.[`tab-${tab.setupState.paneName}`]
|
||||
let $el = instance.parent.refs?.[`tab-${tab.paneName}`] as Element
|
||||
if (!$el) { return false }
|
||||
if (!tab.setupState.active) {
|
||||
if (!tab.active) {
|
||||
offset += $el[`client${capitalize(sizeName)}`]
|
||||
return true
|
||||
} else {
|
||||
|
@ -1,13 +1,19 @@
|
||||
<script lang='ts'>
|
||||
import { h, defineComponent, ref, inject, computed, onUpdated, onMounted, onBeforeUnmount } from 'vue'
|
||||
import { addResizeListener, removeResizeListener } from '@element-plus/utils/resize-event'
|
||||
import { h, defineComponent, ref, inject, computed, onUpdated, onMounted, onBeforeUnmount, PropType } from 'vue'
|
||||
import { addResizeListener, removeResizeListener, ResizableElement } from '@element-plus/utils/resize-event'
|
||||
import { eventKeys } from '@element-plus/utils/aria'
|
||||
import { on, off } from '@element-plus/utils/dom'
|
||||
import TabBar from './tab-bar.vue'
|
||||
import { NOOP, capitalize } from '@vue/shared'
|
||||
import { RootTabs, Pane } from './tabs.vue'
|
||||
|
||||
type RefElement = Nullable<HTMLElement>
|
||||
|
||||
interface Scrollable {
|
||||
next?: boolean
|
||||
prev?: number
|
||||
}
|
||||
|
||||
export default defineComponent({
|
||||
name: 'ElTabNav',
|
||||
components: {
|
||||
@ -15,8 +21,8 @@ export default defineComponent({
|
||||
},
|
||||
props: {
|
||||
panes: {
|
||||
type: Array as PropType<ComponentInternalInstance[]>,
|
||||
default: () => ([] as ComponentInternalInstance[]),
|
||||
type: Array as PropType<Pane[]>,
|
||||
default: () => ([] as Pane[]),
|
||||
},
|
||||
currentName: {
|
||||
type: String,
|
||||
@ -24,11 +30,11 @@ export default defineComponent({
|
||||
},
|
||||
editable: Boolean,
|
||||
onTabClick: {
|
||||
type: Function as PropType<(tab: ComponentInternalInstance, tabName: string, ev: Event) => void>,
|
||||
type: Function as PropType<(tab: Pane, tabName: string, ev: Event) => void>,
|
||||
default: NOOP,
|
||||
},
|
||||
onTabRemove: {
|
||||
type: Function as PropType<(tab: ComponentInternalInstance, ev: Event) => void>,
|
||||
type: Function as PropType<(tab: Pane, ev: Event) => void>,
|
||||
default: NOOP,
|
||||
},
|
||||
type: {
|
||||
@ -38,12 +44,12 @@ export default defineComponent({
|
||||
stretch: Boolean,
|
||||
},
|
||||
setup() {
|
||||
const rootTabs = inject('rootTabs')
|
||||
const rootTabs = inject<RootTabs>('rootTabs')
|
||||
if (!rootTabs) {
|
||||
throw new Error(`ElTabNav must use with ElTabs`)
|
||||
}
|
||||
|
||||
const scrollable = ref(false)
|
||||
const scrollable = ref<boolean | Scrollable>(false)
|
||||
const navOffset = ref(0)
|
||||
const isFocus = ref(false)
|
||||
const focusable = ref(true)
|
||||
@ -131,7 +137,7 @@ export default defineComponent({
|
||||
|
||||
if (containerSize < navSize) {
|
||||
const currentOffset = navOffset.value
|
||||
scrollable.value = scrollable.value || {}
|
||||
scrollable.value = (scrollable.value || {}) as Scrollable
|
||||
scrollable.value.prev = currentOffset
|
||||
scrollable.value.next = currentOffset + containerSize < navSize
|
||||
if (navSize - currentOffset < containerSize) {
|
||||
@ -211,7 +217,7 @@ export default defineComponent({
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
addResizeListener(el$.value, update)
|
||||
addResizeListener(el$.value as ResizableElement, update)
|
||||
on(document, 'visibilitychange', visibilityChangeHandler)
|
||||
on(window, 'blur', windowBlurHandler)
|
||||
on(window, 'focus', windowFocusHandler)
|
||||
@ -222,7 +228,7 @@ export default defineComponent({
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
if(el$.value) {
|
||||
removeResizeListener(el$.value, update)
|
||||
removeResizeListener(el$.value as ResizableElement, update)
|
||||
}
|
||||
off(document, 'visibilitychange', visibilityChangeHandler)
|
||||
off(window, 'blur', windowBlurHandler)
|
||||
@ -295,10 +301,10 @@ export default defineComponent({
|
||||
] : null
|
||||
|
||||
const tabs = panes.map((pane, index) => {
|
||||
let tabName = pane.props.name || pane.setupState.index || `${index}`
|
||||
const closable = pane.setupState.isClosable || editable
|
||||
let tabName = pane.props.name || pane.index || `${index}`
|
||||
const closable = pane.isClosable || editable
|
||||
|
||||
pane.setupState.index = `${index}`
|
||||
pane.index = `${index}`
|
||||
|
||||
const btnClose = closable ?
|
||||
h(
|
||||
@ -309,8 +315,8 @@ export default defineComponent({
|
||||
},
|
||||
) : null
|
||||
|
||||
const tabLabelContent = pane.slots.label?.() || pane.props.label
|
||||
const tabindex = pane.setupState.active ? 0 : -1
|
||||
const tabLabelContent = pane.instance.slots.label?.() || pane.props.label
|
||||
const tabindex = pane.active ? 0 : -1
|
||||
|
||||
return h(
|
||||
'div',
|
||||
@ -318,7 +324,7 @@ export default defineComponent({
|
||||
class: {
|
||||
'el-tabs__item': true,
|
||||
[`is-${ rootTabs.props.tabPosition }`]: true,
|
||||
'is-active': pane.setupState.active,
|
||||
'is-active': pane.active,
|
||||
'is-disabled': pane.props.disabled,
|
||||
'is-closable': closable,
|
||||
'is-focus': isFocus,
|
||||
@ -327,7 +333,7 @@ export default defineComponent({
|
||||
key: `tab-${tabName}`,
|
||||
'aria-controls': `pane-${tabName}`,
|
||||
role: 'tab',
|
||||
'aria-selected': pane.setupState.active ,
|
||||
'aria-selected': pane.active ,
|
||||
ref: `tab-${tabName}`,
|
||||
tabindex: tabindex,
|
||||
onFocus: () => { setFocus() },
|
||||
|
@ -12,7 +12,9 @@
|
||||
</div>
|
||||
</template>
|
||||
<script lang='ts'>
|
||||
import { defineComponent, ref, computed, inject } from 'vue'
|
||||
import { defineComponent, ref, computed, inject, getCurrentInstance } from 'vue'
|
||||
import { RootTabs, UpdatePaneStateCallback, IEPaneProps } from './tabs.vue'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'ElTabPane',
|
||||
props: {
|
||||
@ -28,12 +30,13 @@ export default defineComponent({
|
||||
disabled: Boolean,
|
||||
lazy: Boolean,
|
||||
},
|
||||
setup(props) {
|
||||
const index = ref(null)
|
||||
setup(props: IEPaneProps) {
|
||||
const index = ref<string>(null)
|
||||
const loaded = ref(false)
|
||||
const rootTabs = inject('rootTabs')
|
||||
const rootTabs = inject<RootTabs>('rootTabs')
|
||||
const updatePaneState = inject<UpdatePaneStateCallback>('updatePaneState')
|
||||
|
||||
if (!rootTabs) {
|
||||
if (!rootTabs || !updatePaneState) {
|
||||
throw new Error(`ElTabPane must use with ElTabs`)
|
||||
}
|
||||
|
||||
@ -42,14 +45,14 @@ export default defineComponent({
|
||||
})
|
||||
|
||||
const active = computed(() => {
|
||||
const active = rootTabs.setupState.currentName === (props.name || index.value)
|
||||
const active = rootTabs.currentName.value === (props.name || index.value)
|
||||
if (active) {
|
||||
loaded.value = true
|
||||
}
|
||||
return active
|
||||
})
|
||||
|
||||
const paneName = computed(() => {
|
||||
const paneName = computed((): string => {
|
||||
return props.name || index.value
|
||||
})
|
||||
|
||||
@ -57,6 +60,17 @@ export default defineComponent({
|
||||
return (!props.lazy || loaded.value) || active.value
|
||||
})
|
||||
|
||||
const instance = getCurrentInstance()
|
||||
updatePaneState({
|
||||
uid: instance.uid,
|
||||
instance,
|
||||
props,
|
||||
paneName,
|
||||
active,
|
||||
index,
|
||||
isClosable,
|
||||
})
|
||||
|
||||
return {
|
||||
index,
|
||||
loaded,
|
||||
|
@ -1,10 +1,49 @@
|
||||
<script lang='ts'>
|
||||
import { h, defineComponent, ref, onMounted, onUpdated, provide, watch, nextTick, getCurrentInstance, ComponentInternalInstance } from 'vue'
|
||||
import { h, defineComponent, ref, onMounted, onUpdated, provide, watch, nextTick, getCurrentInstance, ComputedRef, PropType, Ref, ComponentInternalInstance, VNode, Component } from 'vue'
|
||||
import { Fragment } from '@vue/runtime-core'
|
||||
import TabNav from './tab-nav.vue'
|
||||
|
||||
type RefElement = Nullable<HTMLElement>
|
||||
|
||||
type BeforeLeave = (newTabName: string, oldTabName: string) => void | Promise<void> | boolean
|
||||
|
||||
export interface IETabsProps {
|
||||
type: string
|
||||
activeName: string
|
||||
closable: boolean
|
||||
addable: boolean
|
||||
modelValue: string
|
||||
editable: boolean
|
||||
tabPosition: string
|
||||
beforeLeave: BeforeLeave
|
||||
stretch: boolean
|
||||
}
|
||||
|
||||
export interface RootTabs {
|
||||
props: IETabsProps
|
||||
currentName: Ref<string>
|
||||
}
|
||||
|
||||
export interface IEPaneProps {
|
||||
label: string
|
||||
name: string
|
||||
closable: boolean
|
||||
disabled: boolean
|
||||
lazy: boolean
|
||||
}
|
||||
|
||||
export interface Pane {
|
||||
uid: number
|
||||
instance: ComponentInternalInstance
|
||||
props: IEPaneProps
|
||||
paneName: ComputedRef<string>
|
||||
active: ComputedRef<boolean>
|
||||
index: Ref<string>
|
||||
isClosable: ComputedRef<boolean>
|
||||
}
|
||||
|
||||
export type UpdatePaneStateCallback = (pane: Pane) => void
|
||||
|
||||
export default defineComponent({
|
||||
name: 'ElTabs',
|
||||
components: { TabNav },
|
||||
@ -29,17 +68,27 @@ export default defineComponent({
|
||||
default: 'top',
|
||||
},
|
||||
beforeLeave: {
|
||||
type: Function as PropType<(newTabName: string, oldTabName: string) => void>,
|
||||
type: Function as PropType<BeforeLeave>,
|
||||
default: null,
|
||||
},
|
||||
stretch: Boolean,
|
||||
},
|
||||
emits: ['tab-click', 'edit', 'tab-remove', 'tab-add', 'input', 'update:modelValue'],
|
||||
setup(props, ctx) {
|
||||
const nav$ = ref<RefElement>(null)
|
||||
setup(props: IETabsProps, ctx) {
|
||||
const nav$ = ref<typeof TabNav>(null)
|
||||
const currentName = ref(props.modelValue || props.activeName || '0')
|
||||
const panes = ref<ComponentInternalInstance[]>([])
|
||||
const panes = ref([])
|
||||
const instance = getCurrentInstance()
|
||||
const paneStatesMap = {}
|
||||
|
||||
provide<RootTabs>('rootTabs', {
|
||||
props,
|
||||
currentName,
|
||||
})
|
||||
|
||||
provide<UpdatePaneStateCallback>('updatePaneState', (pane: Pane) => {
|
||||
paneStatesMap[pane.uid] = pane
|
||||
})
|
||||
|
||||
watch(() => props.activeName, modelValue => {
|
||||
setCurrentName(modelValue)
|
||||
@ -60,11 +109,11 @@ export default defineComponent({
|
||||
setPaneInstances(true)
|
||||
})
|
||||
|
||||
const getPaneInstanceFromSlot = (vnode, paneInstanceList = []) => {
|
||||
const getPaneInstanceFromSlot = (vnode: VNode, paneInstanceList: ComponentInternalInstance[] = []) => {
|
||||
|
||||
Array.from(vnode.children || []).forEach(node => {
|
||||
Array.from((vnode.children || []) as ArrayLike<VNode>).forEach(node => {
|
||||
let type = node.type
|
||||
type = type.name || type
|
||||
type = (type as Component).name || type
|
||||
if (type === 'ElTabPane' && node.component) {
|
||||
paneInstanceList.push(node.component)
|
||||
} else if(type === Fragment || type === 'template') {
|
||||
@ -78,13 +127,15 @@ export default defineComponent({
|
||||
if(ctx.slots.default) {
|
||||
const children = instance.subTree.children
|
||||
|
||||
const content = Array.from(children).find(({ props }) => {
|
||||
const content = Array.from(children as ArrayLike<VNode>).find(({ props }) => {
|
||||
return props.class === 'el-tabs__content'
|
||||
})
|
||||
|
||||
if(!content) return
|
||||
|
||||
const paneInstanceList = getPaneInstanceFromSlot(content)
|
||||
const paneInstanceList: Pane[] = getPaneInstanceFromSlot(content).map(paneComponent => {
|
||||
return paneStatesMap[paneComponent.uid]
|
||||
})
|
||||
const panesChanged = !(paneInstanceList.length === panes.value.length && paneInstanceList.every((pane, index) => pane.uid === panes.value[index].uid))
|
||||
|
||||
if (isForceUpdate || panesChanged) {
|
||||
@ -104,8 +155,8 @@ export default defineComponent({
|
||||
const setCurrentName = value => {
|
||||
if(currentName.value !== value && props.beforeLeave) {
|
||||
const before = props.beforeLeave(value, currentName.value)
|
||||
if(before && before.then) {
|
||||
before.then(() => {
|
||||
if(before && (before as Promise<void>).then) {
|
||||
(before as Promise<void>).then(() => {
|
||||
changeCurrentName(value)
|
||||
nav$.value && nav$.value.removeFocus()
|
||||
}, () => {
|
||||
@ -137,8 +188,6 @@ export default defineComponent({
|
||||
ctx.emit('tab-add')
|
||||
}
|
||||
|
||||
provide('rootTabs', getCurrentInstance())
|
||||
|
||||
onUpdated(() => {
|
||||
setPaneInstances()
|
||||
})
|
||||
|
@ -1,7 +1,7 @@
|
||||
import ResizeObserver from 'resize-observer-polyfill'
|
||||
import isServer from './isServer'
|
||||
|
||||
type ResizableElement = CustomizedHTMLElement<{
|
||||
export type ResizableElement = CustomizedHTMLElement<{
|
||||
__resizeListeners__: Array<(...args: unknown[]) => unknown>
|
||||
__ro__: ResizeObserver
|
||||
}>;
|
||||
|
Loading…
Reference in New Issue
Block a user