This commit is contained in:
2026-01-05 12:47:14 +08:00
commit 1fc846fae3
1614 changed files with 162035 additions and 0 deletions

View File

@@ -0,0 +1,27 @@
.nut-swipe {
display: block;
overflow: hidden;
&__inner {
position: relative;
cursor: grab;
transition: transform 0.3s cubic-bezier(0.19, 1, 0.22, 1);
}
&__left,
&__right {
position: absolute;
top: 0;
height: 100%;
}
&__left {
left: 0;
transform: translate3d(-100%, 0, 0);
}
&__right {
right: 0;
transform: translate3d(100%, 0, 0);
}
}

View File

@@ -0,0 +1,2 @@
export * from './swipe'
export * from './types'

View File

@@ -0,0 +1,38 @@
import type { ExtractPropTypes } from 'vue'
import { CLICK_EVENT } from '../_constants'
import { commonProps, isString, makeArrayProp } from '../_utils'
import type { SwipePosition, SwipeToggleEvent } from './types'
export const swipeProps = {
...commonProps,
/**
* @description 唯一标识
*/
name: String,
/**
* @description 是否阻止滑动事件冒泡
*/
touchMoveStopPropagation: Boolean,
/**
* @description 是否阻止滑动事件行为
*/
touchMovePreventDefault: Boolean,
/**
* @description 是否禁用滑动
*/
disabled: Boolean,
/**
* @description 点击自动关闭的部分
*/
closeOnClick: makeArrayProp<SwipePosition>(['left', 'content', 'right']),
}
export type SwipeProps = ExtractPropTypes<typeof swipeProps>
export const swipeEmits = {
open: (event: SwipeToggleEvent) => event instanceof Object,
close: (event: SwipeToggleEvent) => event instanceof Object,
[CLICK_EVENT]: (val: string) => isString(val),
}
export type SwipeEmits = typeof swipeEmits

View File

@@ -0,0 +1,247 @@
<script lang="ts" setup>
import { computed, defineComponent, getCurrentInstance, inject, onMounted, reactive, ref, watch } from 'vue'
import { CLICK_EVENT, PREFIX } from '../_constants'
import { useRect, useTouch } from '../_hooks'
import { getMainClass, getRandomId } from '../_utils'
import { swipeEmits, swipeProps } from './swipe'
import type { SwipeDirection, SwipePosition } from './types'
const props = defineProps(swipeProps)
const emit = defineEmits(swipeEmits)
const instance = getCurrentInstance()!
const parent = inject('swipeGroup', null) as any
const classes = computed(() => {
return getMainClass(props, componentName)
})
const randomId = getRandomId()
// eslint-disable-next-line ts/no-use-before-define
const leftElId = `${componentName}-left-${randomId}`
const leftElWidth = ref(0)
// eslint-disable-next-line ts/no-use-before-define
const rightElId = `${componentName}-right-${randomId}`
const rightElWidth = ref(0)
async function getElementWidth(elementId: string) {
const rect = await useRect(elementId, instance)
return rect.width || 0
}
async function initWidth() {
const [leftWidth, rightWidth] = await Promise.all([
getElementWidth(leftElId),
getElementWidth(rightElId),
])
leftElWidth.value = leftWidth
rightElWidth.value = rightWidth
}
watch(() => parent?.name.value, (value) => {
if (props.name !== value && parent && parent.lock.value)
close()
})
const opened = ref(false)
let direction: SwipeDirection = ''
let oldDirection: SwipeDirection = ''
const state = reactive({
offset: 0,
moving: false,
})
const innerStyles = computed(() => {
return {
transform: `translate3d(${state.offset}px, 0, 0)`,
}
})
function open(dir: SwipeDirection = '') {
parent && parent.update(props.name)
if (opened.value)
return
if (dir)
state.offset = dir === 'left' ? -rightElWidth.value : leftElWidth.value
opened.value = true
const finalDirection = direction || dir
emit('open', {
name: props.name!,
direction: finalDirection,
position: finalDirection,
})
}
function close() {
if (!opened.value)
return
state.offset = 0
opened.value = false
emit('close', {
name: props.name!,
direction,
position: direction,
})
}
function handleClick(position: SwipePosition) {
if (props.closeOnClick.includes(position))
close()
emit(CLICK_EVENT, position)
}
function updateOffset(deltaX: number) {
direction = deltaX > 0 ? 'right' : 'left'
let offset = deltaX
switch (direction) {
case 'left': {
if (opened.value && oldDirection === direction)
offset = -rightElWidth.value
else
offset = Math.abs(deltaX) > rightElWidth.value ? -rightElWidth.value : deltaX
break
}
case 'right': {
if (opened.value && oldDirection === direction)
offset = leftElWidth.value
else
offset = Math.abs(deltaX) > leftElWidth.value ? leftElWidth.value : deltaX
break
}
}
state.offset = offset
}
const touch = useTouch()
function onTouchStart(event: any) {
if (props.disabled)
return
touch.start(event)
}
async function onTouchMove(event: any) {
if (props.disabled)
return
touch.move(event)
if (touch.isHorizontal()) {
state.moving = true
updateOffset(touch.deltaX.value)
if (props.touchMovePreventDefault)
event.preventDefault()
if (props.touchMoveStopPropagation)
event.stopPropagation()
}
}
function onTouchEnd() {
if (!state.moving)
return
state.moving = false
oldDirection = direction
switch (direction) {
case 'left': {
if (Math.abs(state.offset) <= rightElWidth.value / 2) {
close()
}
else {
state.offset = -rightElWidth.value
open()
}
break
}
case 'right': {
if (Math.abs(state.offset) <= leftElWidth.value / 2) {
close()
}
else {
state.offset = leftElWidth.value
open()
}
break
}
}
}
onMounted(() => {
setTimeout(() => {
initWidth()
}, 100)
})
defineExpose({
open,
close,
})
</script>
<script lang="ts">
const componentName = `${PREFIX}-swipe`
export default defineComponent({
name: componentName,
options: {
virtualHost: true,
addGlobalClass: true,
// #ifndef H5
styleIsolation: 'shared',
// #endif
},
})
</script>
<template>
<view :class="classes" :style="props.customStyle">
<view
class="nut-swipe__inner"
:style="innerStyles"
@touchstart="onTouchStart"
@touchmove="onTouchMove"
@touchend="onTouchEnd"
@touchcancel="onTouchEnd"
>
<view :id="leftElId" class="nut-swipe__left" @click="handleClick('left')">
<slot name="left" />
</view>
<view class="nut-swipe__content" @click="handleClick('content')">
<slot name="default" />
</view>
<view :id="rightElId" class="nut-swipe__right" @click="handleClick('right')">
<slot name="right" />
</view>
</view>
</view>
</template>
<style lang="scss">
@import "./index";
</style>

View File

@@ -0,0 +1,33 @@
export const swipeDirection = ['left', 'right', ''] as const
export type SwipeDirection = (typeof swipeDirection)[number]
export const swipePosition = ['left', 'content', 'right'] as const
export type SwipePosition = (typeof swipePosition)[number]
export interface SwipeToggleEvent {
/**
* swipe名称
*/
name: string
/**
* 滑动方向
*/
direction: SwipeDirection
/**
* @deprecated 使用 `direction` 代替
* 滑动方向
*/
position: SwipeDirection
}
export interface SwipeInst {
/**
* 滑动单元格侧边栏left 指向左滑right 指向右滑
* @param position
*/
open: (direction?: SwipeDirection) => void
/**
* 收起单元格侧边栏
*/
close: () => void
}