提交 2680cafb 编写于 作者: D DCloud_LXH

feat(h5): showActionSheet

上级 b25232d3
......@@ -5,9 +5,9 @@ const validator: ProtocolOptions<String>[] = [
required: true,
},
]
export const API_CREATE_AUDIO_CONTEXT = 'createAudioContext'
/* export const API_CREATE_AUDIO_CONTEXT = 'createAudioContext'
export type API_TYPE_CREATE_AUDIO_CONTEXT = typeof uni.createAudioContext
export const CreateAudioContextProtocol = validator
export const CreateAudioContextProtocol = validator */
export const API_CREATE_VIDEO_CONTEXT = 'createVideoContext'
export type API_TYPE_CREATE_VIDEO_CONTEXT = typeof uni.createVideoContext
......
......@@ -5,7 +5,7 @@ Object.defineProperty(exports, '__esModule', { value: true });
var vue = require('vue');
var shared = require('@vue/shared');
const sanitise = (val) => (val && JSON.parse(JSON.stringify(val))) || val;
const sanitise = (val) => (val && JSON.parse(JSON.stringify(val))) || val;
const UNI_SSR = '__uniSSR';
const UNI_SSR_DATA = 'data';
......
import { shallowRef, ref, getCurrentInstance, isInSSRComponentSetup, injectHook } from 'vue';
import { hasOwn } from '@vue/shared';
const sanitise = (val) => (val && JSON.parse(JSON.stringify(val))) || val;
const sanitise = (val) => (val && JSON.parse(JSON.stringify(val))) || val;
const UNI_SSR = '__uniSSR';
const UNI_SSR_DATA = 'data';
const UNI_SSR_GLOBAL_DATA = 'globalData';
......
......@@ -312,7 +312,7 @@ export class Scroller {
this._options.onScroll(event)
}
}
update(height: number, scrollHeight?: number, itemSize?: number) {
update(height?: number, scrollHeight?: number, itemSize?: number) {
let extent = 0
const position = this._position
if (this._enableX) {
......
......@@ -12,3 +12,5 @@ export type {
} from './helpers/useEvent'
export { useUserAction } from './helpers/useUserAction'
export { useAttrs } from './helpers/useAttrs'
export { useBooleanAttr } from './helpers/useBooleanAttr'
export { useTouchtrack } from './helpers/useTouchtrack'
......@@ -3514,6 +3514,109 @@ function useMovableAreaState(props2, rootRef) {
}
};
}
const addListenerToElement = function(element, type, callback, capture) {
element.addEventListener(type, ($event) => {
if (typeof callback === "function") {
if (callback($event) === false) {
if (typeof $event.cancelable !== "undefined" ? $event.cancelable : true) {
$event.preventDefault();
}
$event.stopPropagation();
}
}
}, {
passive: false
});
};
function useTouchtrack(element, method, useCancel) {
let x0 = 0;
let y0 = 0;
let x1 = 0;
let y1 = 0;
const fn = function($event, state, x, y) {
if (method({
target: $event.target,
currentTarget: $event.currentTarget,
preventDefault: $event.preventDefault.bind($event),
stopPropagation: $event.stopPropagation.bind($event),
touches: $event.touches,
changedTouches: $event.changedTouches,
detail: {
state,
x,
y,
dx: x - x0,
dy: y - y0,
ddx: x - x1,
ddy: y - y1,
timeStamp: $event.timeStamp
}
}) === false) {
return false;
}
};
let $eventOld = null;
let hasTouchStart;
let hasMouseDown;
addListenerToElement(element, "touchstart", function($event) {
hasTouchStart = true;
if ($event.touches.length === 1 && !$eventOld) {
$eventOld = $event;
x0 = x1 = $event.touches[0].pageX;
y0 = y1 = $event.touches[0].pageY;
return fn($event, "start", x0, y0);
}
});
addListenerToElement(element, "mousedown", function($event) {
hasMouseDown = true;
if (!hasTouchStart && !$eventOld) {
$eventOld = $event;
x0 = x1 = $event.pageX;
y0 = y1 = $event.pageY;
return fn($event, "start", x0, y0);
}
});
addListenerToElement(element, "touchmove", function($event) {
if ($event.touches.length === 1 && $eventOld) {
const res = fn($event, "move", $event.touches[0].pageX, $event.touches[0].pageY);
x1 = $event.touches[0].pageX;
y1 = $event.touches[0].pageY;
return res;
}
});
const mouseMoveEventListener = function($event) {
if (!hasTouchStart && hasMouseDown && $eventOld) {
const res = fn($event, "move", $event.pageX, $event.pageY);
x1 = $event.pageX;
y1 = $event.pageY;
return res;
}
};
document.addEventListener("mousemove", mouseMoveEventListener);
addListenerToElement(element, "touchend", function($event) {
if ($event.touches.length === 0 && $eventOld) {
hasTouchStart = false;
$eventOld = null;
return fn($event, "end", $event.changedTouches[0].pageX, $event.changedTouches[0].pageY);
}
});
const mouseUpEventListener = function($event) {
hasMouseDown = false;
if (!hasTouchStart && $eventOld) {
$eventOld = null;
return fn($event, "end", $event.pageX, $event.pageY);
}
};
document.addEventListener("mouseup", mouseUpEventListener);
addListenerToElement(element, "touchcancel", function($event) {
if ($eventOld) {
hasTouchStart = false;
const $eventTemp = $eventOld;
$eventOld = null;
return fn($event, useCancel ? "cancel" : "end", $eventTemp.touches[0].pageX, $eventTemp.touches[0].pageY);
}
});
}
function e(e2, t2, n) {
return e2 > t2 - n && e2 < t2 + n;
}
......@@ -10108,9 +10211,11 @@ exports.setupApp = setupApp;
exports.setupPage = setupPage;
exports.uni = uni$1;
exports.useAttrs = useAttrs;
exports.useBooleanAttr = useBooleanAttr;
exports.useCustomEvent = useCustomEvent;
exports.useNativeEvent = useNativeEvent;
exports.useOn = useOn;
exports.useSubscribe = useSubscribe;
exports.useTouchtrack = useTouchtrack;
exports.useUserAction = useUserAction;
exports.withWebEvent = withWebEvent;
此差异已折叠。
import { onMounted, computed, ref, onUnmounted } from 'vue'
type Popover = {
left: number
width: number
top: number
height: number
}
export type popupStyleType = {
content: {
transform: string
left: string
top: string
bottom: string
}
triangle: {
'border-width': string
'border-color': string
left: string
top: string
bottom: string
}
}
export function usePopupStyle(props: Data) {
const popupWidth = ref(0)
const popupHeight = ref(0)
const isDesktop = computed(
() => popupWidth.value >= 500 && popupHeight.value >= 500
)
const popupStyle = computed(() => {
const style: popupStyleType = {
content: {
transform: '',
left: '',
top: '',
bottom: '',
},
triangle: {
left: '',
top: '',
bottom: '',
'border-width': '',
'border-color': '',
},
}
const contentStyle = style.content
const triangleStyle = style.triangle
const popover: Popover = props.popover as Popover
function getNumber(value: number | string) {
return Number(value) || 0
}
if (isDesktop.value && popover) {
Object.assign(triangleStyle, {
position: 'absolute',
width: '0',
height: '0',
'margin-left': '-6px',
'border-style': 'solid',
})
const popoverLeft = getNumber(popover.left)
const popoverWidth = getNumber(popover.width)
const popoverTop = getNumber(popover.top)
const popoverHeight = getNumber(popover.height)
const center = popoverLeft + popoverWidth / 2
contentStyle.transform = 'none !important'
const contentLeft = Math.max(0, center - 300 / 2)
contentStyle.left = `${contentLeft}px`
let triangleLeft = Math.max(12, center - contentLeft)
triangleLeft = Math.min(300 - 12, triangleLeft)
triangleStyle.left = `${triangleLeft}px`
const vcl = popupHeight.value / 2
if (popoverTop + popoverHeight - vcl > vcl - popoverTop) {
contentStyle.top = 'auto'
contentStyle.bottom = `${popupHeight.value - popoverTop + 6}px`
triangleStyle.bottom = '-6px'
triangleStyle['border-width'] = '6px 6px 0 6px'
triangleStyle['border-color'] =
'#fcfcfd transparent transparent transparent'
} else {
contentStyle.top = `${popoverTop + popoverHeight + 6}px`
triangleStyle.top = '-6px'
triangleStyle['border-width'] = '0 6px 6px 6px'
triangleStyle['border-color'] =
'transparent transparent #fcfcfd transparent'
}
}
return style
})
onMounted(() => {
const fixSize = () => {
const { windowWidth, windowHeight, windowTop } = uni.getSystemInfoSync()
popupWidth.value = windowWidth
popupHeight.value = windowHeight + windowTop
}
window.addEventListener('resize', fixSize)
fixSize()
onUnmounted(() => {
window.removeEventListener('resize', fixSize)
})
})
return {
isDesktop,
popupStyle,
}
}
......@@ -50,6 +50,7 @@ export * from './route/switchTab'
export * from './ui/popup/showModal'
export * from './ui/popup/showToast'
export * from './ui/popup/showActionSheet'
export * from './ui/loadFontFace'
export * from './ui/navigationBar'
export * from './ui/pageScrollTo'
......
import {
defineComponent,
ExtractPropTypes,
onMounted,
Ref,
ref,
SetupContext,
watch,
watchEffect,
nextTick,
Transition,
} from 'vue'
import { usePopupStyle } from '../../../../helpers/usePopupStyle'
import { useKeyboard } from '../../../../helpers/useKeyboard'
import { initI18nShowActionSheetMsgsOnce, useI18n } from '@dcloudio/uni-core'
import { useTouchtrack } from '@dcloudio/uni-components'
// @ts-ignore
import { Friction } from '@dcloudio/uni-components/../helpers/scroller/Friction'
// @ts-ignore
import { Spring } from '@dcloudio/uni-components/../helpers/scroller/Spring'
// @ts-ignore
import { Scroller } from '@dcloudio/uni-components/../helpers/scroller/Scroller'
// @ts-ignore
import { useScroller } from '@dcloudio/uni-components/../helpers/scroller'
import {
initScrollBounce,
disableScrollBounce,
// @ts-ignore
} from '@dcloudio/uni-components/../helpers/scroll'
import { onEventPrevent } from '@dcloudio/uni-core'
const props = {
title: {
type: String,
default: '',
},
itemList: {
type: Array,
default() {
return []
},
},
itemColor: {
type: String,
default: '#000000',
},
popover: {
type: Object,
default: null,
},
visible: {
type: Boolean,
default: false,
},
}
export type Props = ExtractPropTypes<typeof props>
export default /*#__PURE__*/ defineComponent({
name: 'ActionSheet',
props,
emits: ['close'],
setup(props, { emit }) {
initI18nShowActionSheetMsgsOnce()
const HEIGHT = ref(260)
const contentHeight = ref(0)
const titleHeight = ref(0)
const deltaY = ref(0)
const scrollTop = ref(0)
const content: Ref<HTMLElement | null> = ref(null)
const main: Ref<HTMLElement | null> = ref(null)
const { t } = useI18n()
const { _close } = useActionSheetLoader(props, emit as SetupContext['emit'])
const { popupStyle } = usePopupStyle(props)
let scroller: Scroller
onMounted(() => {
const {
scroller: _scroller,
handleTouchStart,
handleTouchMove,
handleTouchEnd,
} = useScroller(content.value!, {
enableY: true,
friction: new Friction(0.0001),
spring: new Spring(2, 90, 20),
onScroll: (e: Event) => {
scrollTop.value = (e.target as HTMLElement).scrollTop
},
})
scroller = _scroller
// 模拟滚动使用
useTouchtrack(
content.value!,
(e) => {
if (_scroller) {
switch (e.detail.state) {
case 'start':
handleTouchStart(e)
disableScrollBounce({
disable: true,
})
break
case 'move':
handleTouchMove(e)
break
case 'end':
case 'cancel':
handleTouchEnd(e)
disableScrollBounce({
disable: false,
})
}
}
},
true
)
initScrollBounce()
})
function _handleWheel($event: WheelEvent) {
const _deltaY = deltaY.value + $event.deltaY
if (Math.abs(_deltaY) > 10) {
scrollTop.value += _deltaY / 3
scrollTop.value =
scrollTop.value >= contentHeight.value
? contentHeight.value
: scrollTop.value <= 0
? 0
: scrollTop.value
scroller.scrollTo(scrollTop.value)
} else {
deltaY.value = _deltaY
}
$event.preventDefault()
}
watch(
() => props.visible,
() => {
nextTick(() => {
// title 占位
if (props.title) {
titleHeight.value = (
document.querySelector('.uni-actionsheet__title') as HTMLElement
).offsetHeight
}
// 滚动条更新
scroller.update()
// 获取contentHeight 滚动时使用
if (content.value)
contentHeight.value = content.value.clientHeight - HEIGHT.value
// 给每一个项添加点击事件
document
.querySelectorAll('.uni-actionsheet__cell')
.forEach((item) => {
initClick(item as HTMLElement)
})
})
}
)
return () => {
return (
<uni-actionsheet onTouchmove={onEventPrevent}>
<Transition name="uni-fade">
<div
v-show={props.visible}
class="uni-mask uni-actionsheet__mask"
onClick={() => _close(-1)}
/>
</Transition>
<div
class="uni-actionsheet"
// @ts-ignore
class={{ 'uni-actionsheet_toggle': props.visible }}
style={popupStyle.value.content}
>
<div
ref={main}
class="uni-actionsheet__menu"
onWheel={_handleWheel}
>
{/* title占位 */}
{props.title ? (
<>
<div
class="uni-actionsheet__cell"
style={{ height: `${titleHeight.value}px` }}
/>
<div class="uni-actionsheet__title">{props.title}</div>
</>
) : (
''
)}
<div
style={{ maxHeight: `${HEIGHT.value}px`, overflow: 'hidden' }}
>
<div ref={content}>
{props.itemList.map((itemTitle, index) => (
<div
key={index}
style={{ color: props.itemColor }}
class="uni-actionsheet__cell"
onClick={() => _close(index)}
>
{itemTitle}
</div>
))}
</div>
</div>
</div>
<div class="uni-actionsheet__action">
<div
style={{ color: props.itemColor }}
class="uni-actionsheet__cell"
onClick={() => _close(-1)}
>
{t('uni.showActionSheet.cancel')}
</div>
</div>
<div style={popupStyle.value.triangle} />
</div>
</uni-actionsheet>
)
}
},
})
function useActionSheetLoader(props: Props, emit: SetupContext['emit']) {
function _close(tapIndex: number) {
emit('close', tapIndex)
}
const { key, disable } = useKeyboard()
watch(
() => props.visible,
(value) => (disable.value = !value)
)
watchEffect(() => {
const { value } = key
if (value === 'esc') {
_close && _close(-1)
}
})
return {
_close,
}
}
// 由于模拟滚动阻止了点击,使用自定义事件来触发点击事件
function initClick(dom: HTMLElement) {
const MAX_MOVE = 20
let x = 0
let y = 0
dom.addEventListener('touchstart', (event) => {
const info = event.changedTouches[0]
x = info.clientX
y = info.clientY
})
dom.addEventListener('touchend', (event) => {
const info = event.changedTouches[0]
if (
Math.abs(info.clientX - x) < MAX_MOVE &&
Math.abs(info.clientY - y) < MAX_MOVE
) {
const target = event.target as HTMLElement
const currentTarget = event.currentTarget as HTMLElement
const customEvent = new CustomEvent('click', {
bubbles: true,
cancelable: true,
target,
currentTarget,
} as any)
;['screenX', 'screenY', 'clientX', 'clientY', 'pageX', 'pageY'].forEach(
(key) => {
;(customEvent as any)[key] = (info as any)[key]
}
)
event.target!.dispatchEvent(customEvent)
}
})
}
import {
API_TYPE_SHOW_ACTION_SHEET,
API_SHOW_ACTION_SHEET,
ShowActionSheetProtocol,
ShowActionSheetOptions,
defineAsyncApi,
} from '@dcloudio/uni-api'
import { extend } from '@vue/shared'
import { nextTick, reactive } from 'vue'
import actionSheet, { Props } from './actionSheet'
import { ensureRoot, createRootApp } from './utils'
let resolveAction: UniApp.ShowActionSheetOptions['success']
let rejectAction: UniApp.ShowActionSheetOptions['fail']
let showActionSheetState: Props
function onActionSheetClose(tapIndex: number) {
if (tapIndex === -1) {
rejectAction && rejectAction('cancel')
} else {
resolveAction && resolveAction({ tapIndex })
}
}
export const showActionSheet = defineAsyncApi<API_TYPE_SHOW_ACTION_SHEET>(
API_SHOW_ACTION_SHEET,
(args, { resolve, reject }) => {
resolveAction = resolve
rejectAction = reject
if (!showActionSheetState) {
showActionSheetState = reactive(args as Props)
// 异步执行,避免干扰 getCurrentInstance
nextTick(
() => (
createRootApp(
actionSheet,
showActionSheetState,
onActionSheetClose
).mount(ensureRoot('u-s-a-s')), //下一帧执行,确保首次显示时有动画效果
nextTick(() => (showActionSheetState.visible = true))
)
)
} else {
extend(showActionSheetState, args)
showActionSheetState.visible = true
}
},
ShowActionSheetProtocol,
ShowActionSheetOptions
)
......@@ -100,6 +100,7 @@ function useToastIcon(props: ToastProps) {
class: ToastIconClassName,
})
) : props.icon === 'loading' ? (
// @ts-ignore
<i class={ToastIconClassName} class="uni-loading"></i>
) : null
)
......
......@@ -214,7 +214,20 @@ function once(fn, ctx = null) {
return res;
});
}
const sanitise = (val) => (val && JSON.parse(JSON.stringify(val))) || val;
const sanitise = (val) => (val && JSON.parse(JSON.stringify(val))) || val;
const _completeValue = (value) => (value > 9 ? value : '0' + value);
function formatDateTime({ date = new Date(), mode = 'date' }) {
if (mode === 'time') {
return (_completeValue(date.getHours()) + ':' + _completeValue(date.getMinutes()));
}
else {
return (date.getFullYear() +
'-' +
_completeValue(date.getMonth() + 1) +
'-' +
_completeValue(date.getDate()));
}
}
const encode = encodeURIComponent;
function stringifyQuery(obj, encodeStr = encode) {
......@@ -350,6 +363,7 @@ exports.debounce = debounce;
exports.decode = decode;
exports.decodedQuery = decodedQuery;
exports.defaultRpx2Unit = defaultRpx2Unit;
exports.formatDateTime = formatDateTime;
exports.getEnvLocale = getEnvLocale;
exports.getLen = getLen;
exports.invokeArrayFns = invokeArrayFns;
......
......@@ -34,6 +34,11 @@ export declare const defaultRpx2Unit: {
unitPrecision: number;
};
export declare function formatDateTime({ date, mode }: {
date?: Date | undefined;
mode?: string | undefined;
}): string;
export declare function getEnvLocale(): string;
export declare function getLen(str?: string): number;
......
......@@ -210,7 +210,20 @@ function once(fn, ctx = null) {
return res;
});
}
const sanitise = (val) => (val && JSON.parse(JSON.stringify(val))) || val;
const sanitise = (val) => (val && JSON.parse(JSON.stringify(val))) || val;
const _completeValue = (value) => (value > 9 ? value : '0' + value);
function formatDateTime({ date = new Date(), mode = 'date' }) {
if (mode === 'time') {
return (_completeValue(date.getHours()) + ':' + _completeValue(date.getMinutes()));
}
else {
return (date.getFullYear() +
'-' +
_completeValue(date.getMonth() + 1) +
'-' +
_completeValue(date.getDate()));
}
}
const encode = encodeURIComponent;
function stringifyQuery(obj, encodeStr = encode) {
......@@ -325,4 +338,4 @@ function getEnvLocale() {
return (lang && lang.replace(/[.:].*/, '')) || 'en';
}
export { BUILT_IN_TAGS, COMPONENT_NAME_PREFIX, COMPONENT_PREFIX, COMPONENT_SELECTOR_PREFIX, NAVBAR_HEIGHT, ON_REACH_BOTTOM_DISTANCE, PLUS_RE, PRIMARY_COLOR, RESPONSIVE_MIN_WIDTH, TABBAR_HEIGHT, TAGS, UNI_SSR, UNI_SSR_DATA, UNI_SSR_GLOBAL_DATA, UNI_SSR_STORE, addFont, createRpx2Unit, debounce, decode, decodedQuery, defaultRpx2Unit, getEnvLocale, getLen, invokeArrayFns, isBuiltInComponent, isCustomElement, isNativeTag, normalizeDataset, normalizeTarget, once, parseQuery, passive, plusReady, removeLeadingSlash, sanitise, scrollTo, stringifyQuery, updateElementStyle };
export { BUILT_IN_TAGS, COMPONENT_NAME_PREFIX, COMPONENT_PREFIX, COMPONENT_SELECTOR_PREFIX, NAVBAR_HEIGHT, ON_REACH_BOTTOM_DISTANCE, PLUS_RE, PRIMARY_COLOR, RESPONSIVE_MIN_WIDTH, TABBAR_HEIGHT, TAGS, UNI_SSR, UNI_SSR_DATA, UNI_SSR_GLOBAL_DATA, UNI_SSR_STORE, addFont, createRpx2Unit, debounce, decode, decodedQuery, defaultRpx2Unit, formatDateTime, getEnvLocale, getLen, invokeArrayFns, isBuiltInComponent, isCustomElement, isNativeTag, normalizeDataset, normalizeTarget, once, parseQuery, passive, plusReady, removeLeadingSlash, sanitise, scrollTo, stringifyQuery, updateElementStyle };
......@@ -39,3 +39,21 @@ export function once<T extends (...args: any[]) => any>(
export const sanitise = (val: unknown) =>
(val && JSON.parse(JSON.stringify(val))) || val
const _completeValue = (value: number) => (value > 9 ? value : '0' + value)
export function formatDateTime({ date = new Date(), mode = 'date' }) {
if (mode === 'time') {
return (
_completeValue(date.getHours()) + ':' + _completeValue(date.getMinutes())
)
} else {
return (
date.getFullYear() +
'-' +
_completeValue(date.getMonth() + 1) +
'-' +
_completeValue(date.getDate())
)
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册