文件
OmniCard/mpuniapp/uni_modules/uni-collapse-x/components/uni-collapse-item/uni-collapse-item.uvue

692 行
16 KiB
Plaintext

<template>
<view>
<view class="uni-collapse-item__title" :class="props.titleWrapClass" @click="openCollapse(!is_open)">
<view class="uni-collapse-item__title-main">
<slot name="title" :open="is_open" :disabled="props.disabled">
<text class="uni-collapse-item__title-text"
:class="[{'open--active': is_open, 'is-disabled': props.disabled}, props.titleClass, is_open ? props.titleOpenClass : '', props.disabled ? props.titleDisabledClass : '']">{{ props.title }}</text>
</slot>
</view>
<view class="down_arrow"
:class="[arrowPlatformClass, arrowStateClass, arrowDisabledClass, props.arrowClass, is_open ? props.arrowOpenClass : '', props.disabled ? props.arrowDisabledClass : '']"></view>
</view>
<view ref="contentWrapRef" class="uni-collapse-item__content"
:class="[transition_enabled ? 'content-transition--active' : '', box_is_open ? 'content-state-open' : 'content-state-close', contentWrapClass]">
<view ref="contentRef" class="uni-collapse-item__content-box"
:class="[transition_enabled ? 'content-transition--active' : '', box_is_open ? 'content-box-open' : 'content-box-close']">
<slot></slot>
</view>
</view>
</view>
</template>
<script lang="uts" setup>
/**
* 折叠面板子项
* @description 折叠面板子项组件
* @uniPlatform {
* "app": {
* "android": {
* "uniVer": "x",
* "unixVer": "5.07",
* "unixVaporVer": "5.21"
* },
* "ios": {
* "uniVer": "x",
* "unixVer": "5.07",
* "unixVaporVer": "5.11"
* },
* "harmony": {
* "uniVer": "x",
* "unixVer": "5.07",
* "unixVaporVer": "5.07"
* }
* },
* "mp": {
* "weixin": {
* "uniVer": "x",
* "unixVer": "5.07"
* }
* },
* "web": {
* "uniVer": "x",
* "unixVer": "5.07"
* }
* }
*/
import type { ItemChildType } from '../type.uts'
import {
UNI_COLLAPSE_REGISTER_CHILD,
UNI_COLLAPSE_UNREGISTER_CHILD,
UNI_COLLAPSE_CHILD_TOGGLE
} from '../common.uts'
defineOptions({
name: 'UniCollapseItem',
styleIsolation: 'app',
externalClasses: ['title-wrap-class', 'title-class', 'title-open-class', 'title-disabled-class', 'content-wrap-class', 'arrow-class', 'arrow-open-class', 'arrow-disabled-class']
})
type Props = {
/**
* @description 折叠项标题文字
* @default ""
* @uniPlatform {
* "app": {
* "android": {
* "uniVer": "x",
* "unixVer": "5.07",
* "unixVaporVer": "5.21"
* },
* "ios": {
* "uniVer": "x",
* "unixVer": "5.07",
* "unixVaporVer": "5.11"
* },
* "harmony": {
* "uniVer": "x",
* "unixVer": "5.07",
* "unixVaporVer": "5.07"
* }
* },
* "mp": {
* "weixin": {
* "uniVer": "x",
* "unixVer": "5.07"
* }
* },
* "web": {
* "uniVer": "x",
* "unixVer": "5.07"
* }
* }
*/
title? : string,
/**
* @description 是否默认展开
* @default false
* @uniPlatform {
* "app": {
* "android": {
* "uniVer": "x",
* "unixVer": "5.07",
* "unixVaporVer": "5.21"
* },
* "ios": {
* "uniVer": "x",
* "unixVer": "5.07",
* "unixVaporVer": "5.11"
* },
* "harmony": {
* "uniVer": "x",
* "unixVer": "5.07",
* "unixVaporVer": "5.07"
* }
* },
* "mp": {
* "weixin": {
* "uniVer": "x",
* "unixVer": "5.07"
* }
* },
* "web": {
* "uniVer": "x",
* "unixVer": "5.07"
* }
* }
*/
open? : boolean,
/**
* @description 是否禁用,禁用后点击标题不会展开或收起
* @default false
* @uniPlatform {
* "app": {
* "android": {
* "uniVer": "x",
* "unixVer": "5.07",
* "unixVaporVer": "5.21"
* },
* "ios": {
* "uniVer": "x",
* "unixVer": "5.07",
* "unixVaporVer": "5.11"
* },
* "harmony": {
* "uniVer": "x",
* "unixVer": "5.07",
* "unixVaporVer": "5.07"
* }
* },
* "mp": {
* "weixin": {
* "uniVer": "x",
* "unixVer": "5.07"
* }
* },
* "web": {
* "uniVer": "x",
* "unixVer": "5.07"
* }
* }
*/
disabled? : boolean,
/**
* @description 标题行(整行容器)的样式类
* @default ""
* @uniPlatform {
* "app": {
* "android": {
* "uniVer": "x",
* "unixVer": "5.07",
* "unixVaporVer": "5.21"
* },
* "ios": {
* "uniVer": "x",
* "unixVer": "5.07",
* "unixVaporVer": "5.11"
* },
* "harmony": {
* "uniVer": "x",
* "unixVer": "5.07",
* "unixVaporVer": "5.07"
* }
* },
* "mp": {
* "weixin": {
* "uniVer": "x",
* "unixVer": "5.07"
* }
* },
* "web": {
* "uniVer": "x",
* "unixVer": "5.07"
* }
* }
*/
titleWrapClass? : string.ClassString,
/**
* @description 标题文字的基础样式类
* @default ""
* @uniPlatform {
* "app": {
* "android": {
* "uniVer": "x",
* "unixVer": "5.07",
* "unixVaporVer": "5.21"
* },
* "ios": {
* "uniVer": "x",
* "unixVer": "5.07",
* "unixVaporVer": "5.11"
* },
* "harmony": {
* "uniVer": "x",
* "unixVer": "5.07",
* "unixVaporVer": "5.07"
* }
* },
* "mp": {
* "weixin": {
* "uniVer": "x",
* "unixVer": "5.07"
* }
* },
* "web": {
* "uniVer": "x",
* "unixVer": "5.07"
* }
* }
*/
titleClass? : string.ClassString,
/**
* @description 展开状态下叠加到标题文字的样式类
* @default ""
* @uniPlatform {
* "app": {
* "android": {
* "uniVer": "x",
* "unixVer": "5.07",
* "unixVaporVer": "5.21"
* },
* "ios": {
* "uniVer": "x",
* "unixVer": "5.07",
* "unixVaporVer": "5.11"
* },
* "harmony": {
* "uniVer": "x",
* "unixVer": "5.07",
* "unixVaporVer": "5.07"
* }
* },
* "mp": {
* "weixin": {
* "uniVer": "x",
* "unixVer": "5.07"
* }
* },
* "web": {
* "uniVer": "x",
* "unixVer": "5.07"
* }
* }
*/
titleOpenClass? : string.ClassString,
/**
* @description 禁用状态下叠加到标题文字的样式类
* @default ""
* @uniPlatform {
* "app": {
* "android": {
* "uniVer": "x",
* "unixVer": "5.07",
* "unixVaporVer": "5.21"
* },
* "ios": {
* "uniVer": "x",
* "unixVer": "5.07",
* "unixVaporVer": "5.11"
* },
* "harmony": {
* "uniVer": "x",
* "unixVer": "5.07",
* "unixVaporVer": "5.07"
* }
* },
* "mp": {
* "weixin": {
* "uniVer": "x",
* "unixVer": "5.07"
* }
* },
* "web": {
* "uniVer": "x",
* "unixVer": "5.07"
* }
* }
*/
titleDisabledClass? : string.ClassString,
/**
* @description 内容外层容器的样式类
* @default ""
* @uniPlatform {
* "app": {
* "android": {
* "uniVer": "x",
* "unixVer": "5.07",
* "unixVaporVer": "5.21"
* },
* "ios": {
* "uniVer": "x",
* "unixVer": "5.07",
* "unixVaporVer": "5.11"
* },
* "harmony": {
* "uniVer": "x",
* "unixVer": "5.07",
* "unixVaporVer": "5.07"
* }
* },
* "mp": {
* "weixin": {
* "uniVer": "x",
* "unixVer": "5.07"
* }
* },
* "web": {
* "uniVer": "x",
* "unixVer": "5.07"
* }
* }
*/
contentWrapClass? : string.ClassString,
/**
* @description 箭头图标的基础样式类
* @default ""
* @uniPlatform {
* "app": {
* "android": {
* "uniVer": "x",
* "unixVer": "5.07",
* "unixVaporVer": "5.21"
* },
* "ios": {
* "uniVer": "x",
* "unixVer": "5.07",
* "unixVaporVer": "5.11"
* },
* "harmony": {
* "uniVer": "x",
* "unixVer": "5.07",
* "unixVaporVer": "5.07"
* }
* },
* "mp": {
* "weixin": {
* "uniVer": "x",
* "unixVer": "5.07"
* }
* },
* "web": {
* "uniVer": "x",
* "unixVer": "5.07"
* }
* }
*/
arrowClass? : string.ClassString,
/**
* @description 展开状态下叠加到箭头图标的样式类
* @default ""
* @uniPlatform {
* "app": {
* "android": {
* "uniVer": "x",
* "unixVer": "5.07",
* "unixVaporVer": "5.21"
* },
* "ios": {
* "uniVer": "x",
* "unixVer": "5.07",
* "unixVaporVer": "5.11"
* },
* "harmony": {
* "uniVer": "x",
* "unixVer": "5.07",
* "unixVaporVer": "5.07"
* }
* },
* "mp": {
* "weixin": {
* "uniVer": "x",
* "unixVer": "5.07"
* }
* },
* "web": {
* "uniVer": "x",
* "unixVer": "5.07"
* }
* }
*/
arrowOpenClass? : string.ClassString,
/**
* @description 禁用状态下叠加到箭头图标的样式类
* @default ""
* @uniPlatform {
* "app": {
* "android": {
* "uniVer": "x",
* "unixVer": "5.07",
* "unixVaporVer": "5.21"
* },
* "ios": {
* "uniVer": "x",
* "unixVer": "5.07",
* "unixVaporVer": "5.11"
* },
* "harmony": {
* "uniVer": "x",
* "unixVer": "5.07",
* "unixVaporVer": "5.07"
* }
* },
* "mp": {
* "weixin": {
* "uniVer": "x",
* "unixVer": "5.07"
* }
* },
* "web": {
* "uniVer": "x",
* "unixVer": "5.07"
* }
* }
*/
arrowDisabledClass? : string.ClassString
}
const props = withDefaults(defineProps<Props>(), {
title: '',
open: false,
disabled: false,
titleWrapClass: '',
titleClass: '',
titleOpenClass: '',
titleDisabledClass: '',
contentWrapClass: '',
arrowClass: '',
arrowOpenClass: '',
arrowDisabledClass: ''
})
const emit = defineEmits<{
change: [open: boolean]
}>()
defineSlots<{
default(props : {}) : any,
title(props : { open : boolean, disabled : boolean }) : any
}>()
let is_open = ref<boolean>(props.open)
let box_is_open = ref<boolean>(props.open)
let transition_enabled = ref<boolean>(false)
let contentWrapRef = ref<UniViewElement | null>(null)
let contentRef = ref<UniViewElement | null>(null)
const elId = ref(`uni_collapse_item_${Math.ceil(Math.random() * 10e5).toString(36)}`)
const contentTransitionDelay = 16
let enableTransitionTimer = 0
let isAndroidArrowRenderFix = false
// #ifdef APP-ANDROID
// Android arrow fix:
// keep transform rotation, but avoid animating border colors for border-drawn arrows.
// Keep split border-* properties here. Do not use border shorthand with var().
isAndroidArrowRenderFix = true
// #endif
const registerChild = inject<((child : ItemChildType) => void) | null>(UNI_COLLAPSE_REGISTER_CHILD, null)
const unregisterChild = inject<((elId : string) => void) | null>(UNI_COLLAPSE_UNREGISTER_CHILD, null)
const collapseToggle = inject<((elId : string) => void) | null>(UNI_COLLAPSE_CHILD_TOGGLE, null)
const arrowPlatformClass = computed(() : string => {
return isAndroidArrowRenderFix ? 'down_arrow--android-fix' : ''
})
const arrowStateClass = computed(() : string => {
if (!is_open.value) {
return ''
}
return isAndroidArrowRenderFix ? 'down_arrow--android-active' : 'down_arrow--active'
})
const arrowDisabledClass = computed(() : string => {
return props.disabled ? 'down_arrow--disabled' : ''
})
function clearEnableTransitionTimer() {
if (enableTransitionTimer != 0) {
clearTimeout(enableTransitionTimer)
enableTransitionTimer = 0
}
}
function openOrClose(open : boolean) {
box_is_open.value = open
}
function applyOpenState(open : boolean, syncSiblings : boolean, triggerEmit : boolean) {
if (is_open.value == open && box_is_open.value == open) {
return
}
if (open && syncSiblings && collapseToggle != null) {
collapseToggle(elId.value)
}
is_open.value = open
openOrClose(open)
if (triggerEmit) {
emit('change', open)
}
}
function openCollapse(open : boolean) {
if (props.disabled) {
return
}
applyOpenState(open, true, true)
}
onMounted(() => {
if (registerChild != null) {
const child : ItemChildType = {
is_open,
elId: elId.value,
openOrClose
}
registerChild(child)
}
if (is_open.value && collapseToggle != null) {
collapseToggle(elId.value)
}
nextTick(() => {
enableTransitionTimer = setTimeout(() => {
enableTransitionTimer = 0
transition_enabled.value = true
}, contentTransitionDelay)
})
})
onUnmounted(() => {
clearEnableTransitionTimer()
if (unregisterChild != null) {
unregisterChild(elId.value)
}
})
watch(() : boolean => props.open, (value : boolean) => {
applyOpenState(value, false, false)
})
defineExpose({
is_open,
openOrClose,
openCollapse
})
</script>
<style>
.uni-collapse-item__title {
flex-direction: row;
align-items: center;
padding: 12px 18px;
background-color: #ffffff;
}
.uni-collapse-item__title-main {
flex: 1;
flex-direction: row;
align-items: center;
}
.uni-collapse-item__title-text {
flex: 1;
font-size: 14px;
font-weight: 400;
color: #333333;
}
.open--active {
opacity: 0.78;
}
.is-disabled {
opacity: 0.45;
}
.down_arrow {
width: 8px;
height: 8px;
margin-left: 12px;
margin-top: -3px;
transform: rotate(45deg);
border-right-width: 1px;
border-right-style: solid;
border-right-color: #999999;
border-bottom-width: 1px;
border-bottom-style: solid;
border-bottom-color: #999999;
transition-property: transform, border-right-color, border-bottom-color;
transition-duration: 150ms;
}
.down_arrow--active {
margin-top: 0px;
transform: rotate(-135deg);
}
.down_arrow--android-fix {
transition-property: transform;
}
.down_arrow--android-active {
margin-top: 0px;
transform: rotate(-135deg);
}
.down_arrow--disabled {
opacity: 0.45;
}
.uni-collapse-item__content {
overflow: hidden;
width: 100%;
max-height: 0px;
background-color: #ffffff;
}
.uni-collapse-item__content-box {
width: 100%;
opacity: 0;
transform: translateY(-8px);
}
.content-state-open {
max-height: 2000px;
}
.content-state-close {
max-height: 0px;
}
.content-box-open {
opacity: 1;
transform: translateY(0px);
}
.content-box-close {
opacity: 0;
transform: translateY(-8px);
}
.content-transition--active {
transition-duration: 180ms;
transition-timing-function: ease;
}
/* 不要恢复旧的“运行时测量高度 + 内联 style.height”方案。
该方案在 HarmonyOS 上会出现默认展开时外层容器仍为 0 高度,导致内容不显示。 */
.uni-collapse-item__content {
transition-property: max-height;
}
.uni-collapse-item__content-box {
transition-property: opacity, transform;
}
</style>