diff --git a/src/calendar/components/Month.js b/src/calendar/components/Month.js index 184075556..509121634 100644 --- a/src/calendar/components/Month.js +++ b/src/calendar/components/Month.js @@ -48,6 +48,7 @@ export default createComponent({ const [visible, setVisible] = useToggle(); const daysRef = ref(); const monthRef = ref(); + const height = useHeight(monthRef); const title = computed(() => formatMonthTitle(props.date)); @@ -82,14 +83,6 @@ export default createComponent({ } }); - let height; - const getHeight = () => { - if (!height) { - height = useHeight(monthRef); - } - return height; - }; - const getDate = () => props.data; const getTitle = () => title.value; @@ -255,9 +248,9 @@ export default createComponent({ // @exposed-api const vm = getCurrentInstance().proxy; + vm.height = height; vm.getDate = getDate; vm.getTitle = getTitle; - vm.getHeight = getHeight; vm.setVisible = setVisible; vm.scrollIntoView = scrollIntoView; diff --git a/src/calendar/index.js b/src/calendar/index.js index 7140a9fd3..fb9cba4f7 100644 --- a/src/calendar/index.js +++ b/src/calendar/index.js @@ -251,7 +251,9 @@ export default createComponent({ const { body } = this.$refs; const { months, monthRefs } = this; const top = getScrollTop(body); - const heights = months.map((item, index) => monthRefs[index].getHeight()); + const heights = months.map( + (item, index) => monthRefs[index].height.value + ); const heightSum = heights.reduce((a, b) => a + b, 0); // iOS scroll bounce may exceed the range diff --git a/src/composition/use-rect.ts b/src/composition/use-rect.ts index f4e4d75cd..1dedbe4d1 100644 --- a/src/composition/use-rect.ts +++ b/src/composition/use-rect.ts @@ -1,7 +1,13 @@ -import type { Ref } from 'vue'; +import { Ref, ref, onMounted } from 'vue'; export const useRect = (el: Ref) => el.value.getBoundingClientRect(); -export const useWidth = (el: Ref) => useRect(el).width; +export const useHeight = (el: Ref) => { + const height = ref(); -export const useHeight = (el: Ref) => useRect(el).height; + onMounted(() => { + height.value = useRect(el).height; + }); + + return height; +}; diff --git a/src/nav-bar/index.js b/src/nav-bar/index.js index 097079306..4ee9034fe 100644 --- a/src/nav-bar/index.js +++ b/src/nav-bar/index.js @@ -1,4 +1,4 @@ -import { ref, onMounted } from 'vue'; +import { ref } from 'vue'; // Utils import { createNamespace } from '../utils'; @@ -30,14 +30,8 @@ export default createComponent({ emits: ['click-left', 'click-right'], setup(props, { emit, slots }) { - const height = ref(); const navBarRef = ref(); - - onMounted(() => { - if (props.placeholder && props.fixed) { - height.value = useHeight(navBarRef); - } - }); + const height = useHeight(navBarRef); const onClickLeft = (event) => { emit('click-left', event); @@ -90,7 +84,7 @@ export default createComponent({ }; return () => { - if (props.placeholder && props.fixed) { + if (props.fixed && props.placeholder) { return (
); } - return renderNavBar(); }; }, diff --git a/src/notice-bar/index.js b/src/notice-bar/index.js index 8b3f9c080..841b6e473 100644 --- a/src/notice-bar/index.js +++ b/src/notice-bar/index.js @@ -1,7 +1,7 @@ import { ref, reactive, nextTick, onActivated, watch } from 'vue'; import { createNamespace, isDef } from '../utils'; import { doubleRaf } from '../utils/dom/raf'; -import { useWidth } from '../composition/use-rect'; +import { useRect } from '../composition/use-rect'; import Icon from '../icon'; const [createComponent, bem] = createNamespace('notice-bar'); @@ -139,8 +139,8 @@ export default createComponent({ return; } - const wrapRefWidth = useWidth(wrapRef); - const contentRefWidth = useWidth(contentRef); + const wrapRefWidth = useRect(wrapRef).width; + const contentRefWidth = useRect(contentRef).width; if (scrollable || contentRefWidth > wrapRefWidth) { doubleRaf(() => { diff --git a/src/tabbar-item/index.js b/src/tabbar-item/index.js index 211da719b..9a88a9b49 100644 --- a/src/tabbar-item/index.js +++ b/src/tabbar-item/index.js @@ -1,9 +1,12 @@ +import { computed, getCurrentInstance } from 'vue'; +import { TABBAR_KEY } from '../tabbar'; + // Utils import { createNamespace, isObject, isDef } from '../utils'; -import { route, routeProps } from '../composition/use-route'; -// Mixins -import { ChildrenMixin } from '../mixins/relation'; +// Composition +import { useParent } from '../composition/use-relation'; +import { routeProps, useRoute } from '../composition/use-route'; // Components import Icon from '../icon'; @@ -12,8 +15,6 @@ import Badge from '../badge'; const [createComponent, bem] = createNamespace('tabbar-item'); export default createComponent({ - mixins: [ChildrenMixin('vanTabbar')], - props: { ...routeProps, dot: Boolean, @@ -25,57 +26,62 @@ export default createComponent({ emits: ['click'], - data() { - return { - active: false, - }; - }, + setup(props, { emit, slots }) { + const route = useRoute(); + const vm = getCurrentInstance().proxy; + const { parent, index } = useParent(TABBAR_KEY); - computed: { - routeActive() { - const { to, $route } = this; - if (to && $route) { + const active = computed(() => { + const { $route } = vm; + const { route, modelValue } = parent.props; + + if (route && $route) { + const { to } = props; const config = isObject(to) ? to : { path: to }; const pathMatched = config.path === $route.path; const nameMatched = isDef(config.name) && config.name === $route.name; return pathMatched || nameMatched; } - }, - }, - methods: { - onClick(event) { - this.parent.onChange(this.name || this.index); - this.$emit('click', event); - route(this.$router, this); - }, + return (props.name || index.value) === modelValue; + }); - genIcon(active) { - if (this.$slots.icon) { - return this.$slots.icon({ active }); + const onClick = (event) => { + parent.setActive(props.name || index.value); + emit('click', event); + route(); + }; + + const renderIcon = () => { + if (slots.icon) { + return slots.icon({ active: active.value }); } - - if (this.icon) { - return ; + if (props.icon) { + return ; } - }, - }, + }; - render() { - const active = this.parent.route ? this.routeActive : this.active; - const color = this.parent[active ? 'activeColor' : 'inactiveColor']; + return () => { + const { dot, badge } = props; + const { activeColor, inactiveColor } = parent.props; + const color = active.value ? activeColor : inactiveColor; - return ( -
-
- {this.genIcon(active)} - + return ( +
+
+ {renderIcon()} + +
+
+ {slots.default?.({ active: active.value })} +
-
- {this.$slots.default ? this.$slots.default({ active }) : null} -
-
- ); + ); + }; }, }); diff --git a/src/tabbar/index.js b/src/tabbar/index.js index d0bbfefbf..537a5fbf0 100644 --- a/src/tabbar/index.js +++ b/src/tabbar/index.js @@ -1,12 +1,13 @@ -import { createNamespace } from '../utils'; -import { ParentMixin } from '../mixins/relation'; +import { ref, reactive, provide } from 'vue'; +import { createNamespace, isDef } from '../utils'; import { BORDER_TOP_BOTTOM } from '../utils/constant'; +import { useHeight } from '../composition/use-rect'; const [createComponent, bem] = createNamespace('tabbar'); -export default createComponent({ - mixins: [ParentMixin('vanTabbar')], +export const TABBAR_KEY = 'vanTabbar'; +export default createComponent({ props: { route: Boolean, zIndex: [Number, String], @@ -33,75 +34,59 @@ export default createComponent({ emits: ['change', 'update:modelValue'], - data() { - return { - height: null, - }; - }, + setup(props, { emit, slots }) { + const tabbarRef = ref(); + const height = useHeight(tabbarRef); + const children = reactive([]); - computed: { - fit() { - if (this.safeAreaInsetBottom !== null) { - return this.safeAreaInsetBottom; + const isUnfit = () => { + if (isDef(props.safeAreaInsetBottom)) { + return !props.safeAreaInsetBottom; } // enable safe-area-inset-bottom by default when fixed - return this.fixed; - }, - }, + return !props.fixed; + }; - watch: { - children: 'setActiveItem', - modelValue: 'setActiveItem', - }, - - mounted() { - if (this.placeholder && this.fixed) { - this.height = this.$refs.tabbar.getBoundingClientRect().height; - } - }, - - methods: { - setActiveItem() { - this.children.forEach((item, index) => { - item.active = (item.name || index) === this.modelValue; - }); - }, - - onChange(active) { - if (active !== this.modelValue) { - this.$emit('update:modelValue', active); - this.$emit('change', active); - } - }, - - genTabbar() { + const renderTabbar = () => { + const { fixed, zIndex, border } = props; + const unfit = isUnfit(); return (
- {this.$slots.default?.()} + {slots.default?.()}
); - }, - }, + }; - render() { - if (this.placeholder && this.fixed) { - return ( -
- {this.genTabbar()} -
- ); - } + const setActive = (active) => { + if (active !== props.modelValue) { + emit('update:modelValue', active); + emit('change', active); + } + }; - return this.genTabbar(); + provide(TABBAR_KEY, { + props, + children, + setActive, + }); + + return () => { + if (props.fixed && props.placeholder) { + return ( +
+ {renderTabbar()} +
+ ); + } + + return renderTabbar(); + }; }, });