refactor(SwipeCell): refactor with composition api

This commit is contained in:
chenjiahan 2020-09-08 17:30:18 +08:00
parent 3cd15b6124
commit b17c67ab53
2 changed files with 151 additions and 172 deletions

View File

@ -1,6 +1,8 @@
import { on, off } from '../utils/dom/event'; import { on, off } from '../utils/dom/event';
import { import {
Ref, Ref,
ref,
isRef,
watch, watch,
onMounted, onMounted,
onActivated, onActivated,
@ -17,6 +19,10 @@ export function useGlobalEvent(
) { ) {
let binded: boolean; let binded: boolean;
if (!isRef(target)) {
target = ref(target);
}
function add() { function add() {
if (binded || (flag && !flag.value) || !target.value) { if (binded || (flag && !flag.value) || !target.value) {
return; return;

View File

@ -1,25 +1,20 @@
import { ref, reactive, computed } from 'vue';
// Utils // Utils
import { createNamespace } from '../utils'; import { createNamespace } from '../utils';
import { range } from '../utils/format/number'; import { range } from '../utils/format/number';
import { preventDefault } from '../utils/dom/event'; import { preventDefault } from '../utils/dom/event';
import { callInterceptor } from '../utils/interceptor'; import { callInterceptor } from '../utils/interceptor';
// Mixins // Composition
import { TouchMixin } from '../mixins/touch'; import { useRect } from '../composition/use-rect';
import { ClickOutsideMixin } from '../mixins/click-outside'; import { useTouch } from '../composition/use-touch';
import { usePublicApi } from '../composition/use-public-api';
import { useClickOutside } from '../composition/use-click-outside';
const [createComponent, bem] = createNamespace('swipe-cell'); const [createComponent, bem] = createNamespace('swipe-cell');
const THRESHOLD = 0.15;
export default createComponent({ export default createComponent({
mixins: [
TouchMixin,
ClickOutsideMixin({
event: 'touchstart',
method: 'onClick',
}),
],
props: { props: {
disabled: Boolean, disabled: Boolean,
leftWidth: [Number, String], leftWidth: [Number, String],
@ -34,211 +29,189 @@ export default createComponent({
emits: ['open', 'close', 'click'], emits: ['open', 'close', 'click'],
data() { setup(props, { emit, slots }) {
return { let opened;
let lockClick;
let startOffset;
const rootRef = ref();
const leftRef = ref();
const rightRef = ref();
const state = reactive({
offset: 0, offset: 0,
dragging: false, dragging: false,
});
const touch = useTouch();
const { deltaX, direction } = touch;
const getWidthByRef = (ref) => (ref.value ? useRect(ref).width : 0);
const leftWidth = computed(
() => +props.leftWidth || getWidthByRef(leftRef)
);
const rightWidth = computed(
() => +props.rightWidth || getWidthByRef(rightRef)
);
const open = (position) => {
opened = true;
state.offset = position === 'left' ? leftWidth.value : -rightWidth.value;
emit('open', {
name: props.name,
position,
});
}; };
},
computed: { const close = (position) => {
computedLeftWidth() { state.offset = 0;
return +this.leftWidth || this.getWidthByRef('left');
},
computedRightWidth() { if (opened) {
return +this.rightWidth || this.getWidthByRef('right'); opened = false;
}, emit('close', {
}, name: props.name,
mounted() {
this.bindTouchEvent(this.$el);
},
methods: {
getWidthByRef(ref) {
if (this.$refs[ref]) {
const rect = this.$refs[ref].getBoundingClientRect();
return rect.width;
}
return 0;
},
// @exposed-api
open(position) {
const offset =
position === 'left' ? this.computedLeftWidth : -this.computedRightWidth;
this.opened = true;
this.offset = offset;
this.$emit('open', {
position, position,
name: this.name,
});
},
// @exposed-api
close(position) {
this.offset = 0;
if (this.opened) {
this.opened = false;
this.$emit('close', {
position,
name: this.name,
}); });
} }
}, };
onTouchStart(event) { const toggle = (position) => {
if (this.disabled) { const offset = Math.abs(state.offset);
const THRESHOLD = 0.15;
const threshold = opened ? 1 - THRESHOLD : THRESHOLD;
if (position === 'left' || position === 'right') {
const width = position === 'left' ? leftWidth.value : rightWidth.value;
if (width && offset > width * threshold) {
open(position);
return;
}
}
close();
};
const onTouchStart = (event) => {
if (!props.disabled) {
startOffset = state.offset;
touch.start(event);
}
};
const onTouchMove = (event) => {
if (props.disabled) {
return; return;
} }
this.startOffset = this.offset; touch.move(event);
this.touchStart(event);
},
onTouchMove(event) { if (direction.value === 'horizontal') {
if (this.disabled) { lockClick = true;
return; state.dragging = true;
const isEdge = !opened || deltaX.value * startOffset < 0;
if (isEdge) {
preventDefault(event, props.stopPropagation);
} }
this.touchMove(event); state.offset = range(
deltaX.value + startOffset,
if (this.direction === 'horizontal') { -rightWidth.value,
this.dragging = true; leftWidth.value
this.lockClick = true;
const isPrevent = !this.opened || this.deltaX * this.startOffset < 0;
if (isPrevent) {
preventDefault(event, this.stopPropagation);
}
this.offset = range(
this.deltaX + this.startOffset,
-this.computedRightWidth,
this.computedLeftWidth
); );
} }
}, };
onTouchEnd() { const onTouchEnd = () => {
if (this.disabled) { if (state.dragging) {
return; state.dragging = false;
} toggle(state.offset > 0 ? 'left' : 'right');
if (this.dragging) {
this.toggle(this.offset > 0 ? 'left' : 'right');
this.dragging = false;
// compatible with desktop scenario // compatible with desktop scenario
setTimeout(() => { setTimeout(() => {
this.lockClick = false; lockClick = false;
}, 0); }, 0);
} }
}, };
toggle(direction) { const onClick = (position = 'outside') => {
const offset = Math.abs(this.offset); emit('click', position);
const threshold = this.opened ? 1 - THRESHOLD : THRESHOLD;
const { computedLeftWidth, computedRightWidth } = this;
if ( if (opened && !lockClick) {
computedRightWidth &&
direction === 'right' &&
offset > computedRightWidth * threshold
) {
this.open('right');
} else if (
computedLeftWidth &&
direction === 'left' &&
offset > computedLeftWidth * threshold
) {
this.open('left');
} else {
this.close();
}
},
onClick(position = 'outside') {
this.$emit('click', position);
if (this.opened && !this.lockClick) {
callInterceptor({ callInterceptor({
interceptor: this.beforeClose, interceptor: props.beforeClose,
args: [ args: [
{ {
name: props.name,
position, position,
name: this.name,
}, },
], ],
done: () => { done: () => {
this.close(position); close(position);
}, },
}); });
} }
}, };
getClickHandler(position, stop) { const getClickHandler = (position, stop) => (event) => {
return (event) => {
if (stop) { if (stop) {
event.stopPropagation(); event.stopPropagation();
} }
this.onClick(position); onClick(position);
}; };
},
genLeftPart() { const renderSideContent = (position, ref) => {
const content = this.$slots.left?.(); if (slots[position]) {
if (content) {
return ( return (
<div <div
ref="left" ref={ref}
class={bem('left')} class={bem(position)}
onClick={this.getClickHandler('left', true)} onClick={getClickHandler(position, true)}
> >
{content} {slots[position]()}
</div> </div>
); );
} }
}, };
genRightPart() { usePublicApi({
const content = this.$slots.right?.(); open,
close,
});
if (content) { useClickOutside({
return ( element: rootRef,
<div event: 'touchstart',
ref="right" callback: onClick,
class={bem('right')} });
onClick={this.getClickHandler('right', true)}
>
{content}
</div>
);
}
},
},
render() { return () => {
const wrapperStyle = { const wrapperStyle = {
transform: `translate3d(${this.offset}px, 0, 0)`, transform: `translate3d(${state.offset}px, 0, 0)`,
transitionDuration: this.dragging ? '0s' : '.6s', transitionDuration: state.dragging ? '0s' : '.6s',
}; };
return ( return (
<div class={bem()} onClick={this.getClickHandler('cell')}> <div
ref={rootRef}
class={bem()}
onClick={getClickHandler('cell')}
onTouchstart={onTouchStart}
onTouchmove={onTouchMove}
onTouchend={onTouchEnd}
onTouchcancel={onTouchEnd}
>
<div class={bem('wrapper')} style={wrapperStyle}> <div class={bem('wrapper')} style={wrapperStyle}>
{this.genLeftPart()} {renderSideContent('left', leftRef)}
{this.$slots.default?.()} {slots.default?.()}
{this.genRightPart()} {renderSideContent('right', rightRef)}
</div> </div>
</div> </div>
); );
};
}, },
}); });