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,
}; });
},
computed: { const touch = useTouch();
computedLeftWidth() { const { deltaX, direction } = touch;
return +this.leftWidth || this.getWidthByRef('left');
},
computedRightWidth() { const getWidthByRef = (ref) => (ref.value ? useRect(ref).width : 0);
return +this.rightWidth || this.getWidthByRef('right');
},
},
mounted() { const leftWidth = computed(
this.bindTouchEvent(this.$el); () => +props.leftWidth || getWidthByRef(leftRef)
}, );
methods: { const rightWidth = computed(
getWidthByRef(ref) { () => +props.rightWidth || getWidthByRef(rightRef)
if (this.$refs[ref]) { );
const rect = this.$refs[ref].getBoundingClientRect();
return rect.width;
}
return 0; const open = (position) => {
}, opened = true;
state.offset = position === 'left' ? leftWidth.value : -rightWidth.value;
// @exposed-api emit('open', {
open(position) { name: props.name,
const offset =
position === 'left' ? this.computedLeftWidth : -this.computedRightWidth;
this.opened = true;
this.offset = offset;
this.$emit('open', {
position, position,
name: this.name,
}); });
}, };
// @exposed-api const close = (position) => {
close(position) { state.offset = 0;
this.offset = 0;
if (this.opened) { if (opened) {
this.opened = false; opened = false;
this.$emit('close', { emit('close', {
name: props.name,
position, 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;
}
this.touchMove(event); const isEdge = !opened || deltaX.value * startOffset < 0;
if (isEdge) {
if (this.direction === 'horizontal') { preventDefault(event, props.stopPropagation);
this.dragging = true;
this.lockClick = true;
const isPrevent = !this.opened || this.deltaX * this.startOffset < 0;
if (isPrevent) {
preventDefault(event, this.stopPropagation);
} }
this.offset = range( state.offset = range(
this.deltaX + this.startOffset, deltaX.value + startOffset,
-this.computedRightWidth, -rightWidth.value,
this.computedLeftWidth leftWidth.value
); );
} }
}, };
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) {
return (event) => {
if (stop) {
event.stopPropagation();
}
this.onClick(position);
};
},
genLeftPart() {
const content = this.$slots.left?.();
if (content) {
return (
<div
ref="left"
class={bem('left')}
onClick={this.getClickHandler('left', true)}
>
{content}
</div>
);
}
},
genRightPart() {
const content = this.$slots.right?.();
if (content) {
return (
<div
ref="right"
class={bem('right')}
onClick={this.getClickHandler('right', true)}
>
{content}
</div>
);
}
},
},
render() {
const wrapperStyle = {
transform: `translate3d(${this.offset}px, 0, 0)`,
transitionDuration: this.dragging ? '0s' : '.6s',
}; };
return ( const getClickHandler = (position, stop) => (event) => {
<div class={bem()} onClick={this.getClickHandler('cell')}> if (stop) {
<div class={bem('wrapper')} style={wrapperStyle}> event.stopPropagation();
{this.genLeftPart()} }
{this.$slots.default?.()} onClick(position);
{this.genRightPart()} };
const renderSideContent = (position, ref) => {
if (slots[position]) {
return (
<div
ref={ref}
class={bem(position)}
onClick={getClickHandler(position, true)}
>
{slots[position]()}
</div>
);
}
};
usePublicApi({
open,
close,
});
useClickOutside({
element: rootRef,
event: 'touchstart',
callback: onClick,
});
return () => {
const wrapperStyle = {
transform: `translate3d(${state.offset}px, 0, 0)`,
transitionDuration: state.dragging ? '0s' : '.6s',
};
return (
<div
ref={rootRef}
class={bem()}
onClick={getClickHandler('cell')}
onTouchstart={onTouchStart}
onTouchmove={onTouchMove}
onTouchend={onTouchEnd}
onTouchcancel={onTouchEnd}
>
<div class={bem('wrapper')} style={wrapperStyle}>
{renderSideContent('left', leftRef)}
{slots.default?.()}
{renderSideContent('right', rightRef)}
</div>
</div> </div>
</div> );
); };
}, },
}); });