diff --git a/packages/uni-core/src/helpers/event.ts b/packages/uni-core/src/helpers/event.ts new file mode 100644 index 0000000000000000000000000000000000000000..3f6b3cb6c6c2db5aa8bcf585551a430b4c6b2201 --- /dev/null +++ b/packages/uni-core/src/helpers/event.ts @@ -0,0 +1,3 @@ +import { withModifiers } from 'vue' +export const onTouchmovePrevent = withModifiers(() => {}, ['prevent']) +export const onTouchmoveStop = withModifiers(() => {}, ['stop']) diff --git a/packages/uni-core/src/helpers/index.ts b/packages/uni-core/src/helpers/index.ts index ba218d6f2b779de578bde2cfc78b0832332c6a17..d806af1d3013ec1a84e0cd092f9977cde6d4e0be 100644 --- a/packages/uni-core/src/helpers/index.ts +++ b/packages/uni-core/src/helpers/index.ts @@ -1,5 +1,6 @@ export * from './util' export * from './icon' +export * from './event' export * from './scroll' export * from './getRealRoute' export * from './updateCssVar' diff --git a/packages/uni-h5/dist/uni-h5.esm.js b/packages/uni-h5/dist/uni-h5.esm.js index 8b642bba1ddbf20962f69d25e0bc14556c004b2c..e5db18b0a2b0dc81ef687835926c6b08391ec489 100644 --- a/packages/uni-h5/dist/uni-h5.esm.js +++ b/packages/uni-h5/dist/uni-h5.esm.js @@ -1,5 +1,5 @@ import {isFunction, extend, isPlainObject, isString, isArray, hasOwn as hasOwn$1, isObject as isObject$1, capitalize, toRawType, makeMap as makeMap$1, isPromise, invokeArrayFns as invokeArrayFns$1, hyphenate} from "@vue/shared"; -import {injectHook, createVNode, inject, provide, reactive, computed, nextTick, getCurrentInstance, onBeforeMount, onMounted, onBeforeActivate, onBeforeDeactivate, openBlock, createBlock, mergeProps, toDisplayString, ref, defineComponent, resolveComponent, toHandlers, renderSlot, watch, onActivated, onBeforeUnmount, withModifiers, withDirectives, vShow, vModelDynamic, createTextVNode, createCommentVNode, Fragment, renderList, vModelText, onDeactivated, onUnmounted, watchEffect, withCtx, KeepAlive, resolveDynamicComponent} from "vue"; +import {injectHook, createVNode, withModifiers, inject, provide, reactive, computed, nextTick, getCurrentInstance, onBeforeMount, onMounted, onBeforeActivate, onBeforeDeactivate, openBlock, createBlock, mergeProps, toDisplayString, ref, defineComponent, resolveComponent, toHandlers, renderSlot, watch, onActivated, onBeforeUnmount, withDirectives, vShow, vModelDynamic, createTextVNode, createCommentVNode, Fragment, renderList, vModelText, onDeactivated, onUnmounted, createApp, watchEffect, Transition, withCtx, KeepAlive, resolveDynamicComponent} from "vue"; import {once, passive, normalizeTarget, invokeArrayFns, NAVBAR_HEIGHT, parseQuery, PRIMARY_COLOR, removeLeadingSlash, getLen, ON_REACH_BOTTOM_DISTANCE, decodedQuery, plusReady, debounce, updateElementStyle, addFont, scrollTo} from "@dcloudio/uni-shared"; import {useRoute, createRouter, createWebHistory, createWebHashHistory, useRouter, isNavigationFailure, RouterView} from "vue-router"; function applyOptions(options, instance2, publicThis) { @@ -1064,6 +1064,10 @@ function createSvgIconVNode(path, color = "#000", size = 27) { }, null, 8, ["d", "fill"]) ], 8, ["width", "height"]); } +const onTouchmovePrevent = withModifiers(() => { +}, ["prevent"]); +const onTouchmoveStop = withModifiers(() => { +}, ["stop"]); function disableScrollListener(evt) { evt.preventDefault(); } @@ -2627,8 +2631,6 @@ function createNormalizeUrl(type) { } }; } -const API_HIDE_LOADING = "hideLoading"; -const API_HIDE_TOAST = "hideToast"; const API_LOAD_FONT_FACE = "loadFontFace"; const LoadFontFaceProtocol = { family: { @@ -2695,30 +2697,6 @@ const PageScrollToOptions = { } } }; -const API_SHOW_ACTION_SHEET = "showActionSheet"; -const ShowActionSheetProtocol = { - itemList: { - type: Array, - required: true - }, - itemColor: String -}; -const ShowActionSheetOptions = { - formatArgs: { - itemColor: "#000" - } -}; -const API_SHOW_LOADING = "showLoading"; -const ShowLoadingProtocol = { - title: String, - mask: Boolean -}; -const ShowLoadingOptions = { - formatArgs: { - title: "", - mask: false - } -}; const API_SHOW_MODAL = "showModal"; const ShowModalProtocol = { title: String, @@ -2753,31 +2731,6 @@ const ShowModalOptions = { confirmColor: PRIMARY_COLOR } }; -const API_SHOW_TOAST = "showToast"; -const ShowToastProtocol = { - title: String, - icon: String, - image: String, - duration: Number, - mask: Boolean -}; -const ShowToastOptions = { - formatArgs: { - title: "", - icon(value, params) { - if (["success", "loading", "none"].indexOf(value) === -1) { - params.icon = "success"; - } - }, - image(value, params) { - if (value) { - params.image = getRealPath(value); - } - }, - duration: 1500, - mask: false - } -}; const API_START_PULL_DOWN_REFRESH = "startPullDownRefresh"; const API_STOP_PULL_DOWN_REFRESH = "stopPullDownRefresh"; const IndexProtocol = { @@ -6121,7 +6074,7 @@ function useResizeSensorLifecycle(rootRef, props2, update, reset) { } }); } -const props$3 = { +const props$4 = { src: { type: String, default: "" @@ -6160,7 +6113,7 @@ const IMAGE_MODES = { }; var index$6 = /* @__PURE__ */ defineComponent({ name: "Image", - props: props$3, + props: props$4, setup(props2, { emit }) { @@ -7737,7 +7690,7 @@ const VALUES = { backgroundColor: "#EBEBEB", activeMode: "backwards" }; -const props$2 = { +const props$3 = { percent: { type: [Number, String], default: 0, @@ -7786,7 +7739,7 @@ const props$2 = { }; var index$5 = /* @__PURE__ */ defineComponent({ name: "Progress", - props: props$2, + props: props$3, setup(props2) { const state = useProgressState(props2); _activeAnimation(state, props2); @@ -10787,7 +10740,7 @@ function useContext(play, pause, seek, sendDanmu, playbackRate, requestFullScree } }); } -const props$1 = { +const props$2 = { id: { type: String, default: "" @@ -10873,7 +10826,7 @@ const props$1 = { }; var index$2 = /* @__PURE__ */ defineComponent({ name: "Video", - props: props$1, + props: props$2, emits: ["fullscreenchange", "progress", "loadedmetadata", "waiting", "error", "play", "pause", "ended", "timeupdate"], setup(props2, { emit, @@ -11082,7 +11035,7 @@ var index$2 = /* @__PURE__ */ defineComponent({ }; } }); -const props = { +const props$1 = { src: { type: String, default: "" @@ -11092,7 +11045,7 @@ const props = { }; var index$1 = /* @__PURE__ */ defineComponent({ name: "WebView", - props, + props: props$1, setup(props2) { const rootRef = ref(null); const iframe = document.createElement("iframe"); @@ -12778,6 +12731,178 @@ function getTabBarPageId(url) { const switchTab = defineAsyncApi(API_SWITCH_TAB, ({url}, {resolve, reject}) => { return removeNonTabBarPages(), navigate(API_SWITCH_TAB, url, getTabBarPageId(url)).then(resolve).catch(reject); }, SwitchTabProtocol, SwitchTabOptions); +const KEY_MAPS = { + esc: ["Esc", "Escape"], + enter: ["Enter"] +}; +const KEYS = Object.keys(KEY_MAPS); +function useKeyboard() { + const key = ref(""); + const disable = ref(false); + const onKeyup = (evt) => { + if (disable.value) { + return; + } + const res = KEYS.find((key2) => KEY_MAPS[key2].indexOf(evt.key) !== -1); + if (res) { + key.value = res; + } + }; + onMounted(() => { + document.addEventListener("keyup", onKeyup); + }); + onBeforeUnmount(() => { + document.removeEventListener("keyup", onKeyup); + }); + return { + key, + disable + }; +} +const VNODE_MASK = /* @__PURE__ */ createVNode("div", {class: "uni-mask"}, null, -1); +function createRootApp(component, rootState, callback) { + return createApp(defineComponent({ + setup() { + const onClose = (...args) => (rootState.visible = false, callback.apply(null, args)); + return () => (openBlock(), createBlock(component, mergeProps({ + onClose + }, rootState))); + } + })); +} +function ensureRoot(id2) { + let rootEl = document.getElementById(id2); + if (!rootEl) { + rootEl = document.createElement("div"); + rootEl.id = id2; + document.body.append(rootEl); + } + return rootEl; +} +function usePopup(props2, { + onEsc, + onEnter +}) { + const visible = ref(props2.visible); + const {key, disable} = useKeyboard(); + watch(() => props2.visible, (value) => visible.value = value); + watch(() => visible.value, (value) => disable.value = !value); + watchEffect(() => { + const {value} = key; + if (value === "esc") { + onEsc && onEsc(); + } else if (value === "enter") { + onEnter && onEnter(); + } + }); + return visible; +} +const props = { + title: { + type: String, + default: "" + }, + content: { + type: String, + default: "" + }, + showCancel: { + type: Boolean, + default: true + }, + cancelText: { + type: String, + default: "Cancel" + }, + cancelColor: { + type: String, + default: "#000000" + }, + confirmText: { + type: String, + default: "OK" + }, + confirmColor: { + type: String, + default: "#007aff" + }, + visible: { + type: Boolean + } +}; +var modal = /* @__PURE__ */ defineComponent({ + props, + setup(props2, { + emit + }) { + const close = () => visible.value = false; + const cancel = () => (close(), emit("close", "cancel")); + const confirm = () => (close(), emit("close", "confirm")); + const visible = usePopup(props2, { + onEsc: cancel, + onEnter: confirm + }); + return () => { + const { + title, + content, + showCancel, + confirmText, + confirmColor + } = props2; + return createVNode(Transition, { + name: "uni-fade" + }, { + default: () => [withDirectives(createVNode("uni-modal", { + onTouchmove: onTouchmovePrevent + }, [VNODE_MASK, createVNode("div", { + class: "uni-modal" + }, [title && createVNode("div", { + class: "uni-modal__hd" + }, [createVNode("strong", { + class: "uni-modal__title", + textContent: title + }, null, 8, ["textContent"])]), createVNode("div", { + class: "uni-modal__bd", + onTouchmove: onTouchmoveStop, + textContent: content + }, null, 40, ["onTouchmove", "textContent"]), createVNode("div", { + class: "uni-modal__ft" + }, [showCancel && createVNode("div", { + style: { + color: props2.cancelColor + }, + class: "uni-modal__btn uni-modal__btn_default", + onClick: cancel + }, [props2.cancelText], 12, ["onClick"]), createVNode("div", { + style: { + color: confirmColor + }, + class: "uni-modal__btn uni-modal__btn_primary", + onClick: confirm + }, [confirmText], 12, ["onClick"])])])], 40, ["onTouchmove"]), [[vShow, visible.value]])] + }); + }; + } +}); +let showModalState; +let currentShowModalResolve; +function onModalClose(type) { + currentShowModalResolve && currentShowModalResolve({ + confirm: type === "confirm", + cancel: type === "cancel" + }); +} +const showModal = defineAsyncApi(API_SHOW_MODAL, (args, {resolve}) => { + currentShowModalResolve = resolve; + if (!showModalState) { + showModalState = reactive(args); + nextTick(() => createRootApp(modal, showModalState, onModalClose).mount(ensureRoot("u-a-m"))); + } else { + extend(showModalState, args); + } + showModalState.visible = true; +}, ShowModalProtocol, ShowModalOptions); const loadFontFace = defineAsyncApi(API_LOAD_FONT_FACE, ({family, source, desc}, {resolve, reject}) => { addFont(family, source, desc).then(() => { resolve(); @@ -12832,18 +12957,6 @@ const pageScrollTo = defineAsyncApi(API_PAGE_SCROLL_TO, ({scrollTop, selector, d scrollTo(selector || scrollTop || 0, duration); resolve(); }, PageScrollToProtocol, PageScrollToOptions); -const showModal = defineAsyncApi(API_SHOW_MODAL, () => { -}, ShowModalProtocol, ShowModalOptions); -const showToast = defineAsyncApi(API_SHOW_TOAST, () => { -}, ShowToastProtocol, ShowToastOptions); -const hideToast = defineAsyncApi(API_HIDE_TOAST, () => { -}); -const showLoading = defineAsyncApi(API_SHOW_LOADING, () => { -}, ShowLoadingProtocol, ShowLoadingOptions); -const hideLoading = defineAsyncApi(API_HIDE_LOADING, () => { -}); -const showActionSheet = defineAsyncApi(API_SHOW_ACTION_SHEET, () => { -}, ShowActionSheetProtocol, ShowActionSheetOptions); const startPullDownRefresh = defineAsyncApi(API_START_PULL_DOWN_REFRESH, (_args, {resolve}) => { UniServiceJSBridge.publishHandler(API_START_PULL_DOWN_REFRESH, {}, getCurrentPageId()); resolve(); @@ -13018,18 +13131,13 @@ var api = /* @__PURE__ */ Object.freeze({ redirectTo, reLaunch, switchTab, + showModal, loadFontFace, setNavigationBarColor, showNavigationBarLoading, hideNavigationBarLoading, setNavigationBarTitle, pageScrollTo, - showModal, - showToast, - hideToast, - showLoading, - hideLoading, - showActionSheet, startPullDownRefresh, stopPullDownRefresh, setTabBarItem, @@ -14144,4 +14252,4 @@ function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) { ]); } _sfc_main.render = _sfc_render; -export {_sfc_main$1 as AsyncErrorComponent, _sfc_main as AsyncLoadingComponent, _sfc_main$j as Audio, index$8 as Button, _sfc_main$i as Canvas, _sfc_main$h as Checkbox, _sfc_main$g as CheckboxGroup, _sfc_main$f as Editor, index$9 as Form, index$7 as Icon, index$6 as Image, _sfc_main$e as Input, _sfc_main$d as Label, LayoutComponent, _sfc_main$c as MovableView, _sfc_main$b as Navigator, index as PageComponent, index$5 as Progress, _sfc_main$a as Radio, _sfc_main$9 as RadioGroup, ResizeSensor, _sfc_main$8 as RichText, _sfc_main$7 as ScrollView, _sfc_main$6 as Slider, _sfc_main$5 as SwiperItem, _sfc_main$4 as Switch, index$4 as Text, _sfc_main$3 as Textarea, UniServiceJSBridge$1 as UniServiceJSBridge, UniViewJSBridge$1 as UniViewJSBridge, index$2 as Video, index$3 as View, index$1 as WebView, addInterceptor, arrayBufferToBase64, base64ToArrayBuffer, canIUse, chooseFile, chooseImage, chooseVideo, clearStorage, clearStorageSync, closeSocket, connectSocket, createInnerAudioContext, createIntersectionObserver, createSelectorQuery, createVideoContext, cssBackdropFilter, cssConstant, cssEnv, cssVar, downloadFile, getApp$1 as getApp, getCurrentPages$1 as getCurrentPages, getFileInfo, getImageInfo, getLocation, getNetworkType, getStorage, getStorageInfo, getStorageInfoSync, getStorageSync, getSystemInfo, getSystemInfoSync, getVideoInfo, hideKeyboard, hideLoading, hideNavigationBarLoading, hideTabBar, hideTabBarRedDot, hideToast, loadFontFace, makePhoneCall, navigateBack, navigateTo, offAccelerometerChange, offCompassChange, offNetworkStatusChange, onAccelerometerChange, onCompassChange, onNetworkStatusChange, onSocketClose, onSocketError, onSocketMessage, onSocketOpen, onTabBarMidButtonTap, openDocument, pageScrollTo, index$a as plugin, promiseInterceptor, reLaunch, redirectTo, removeInterceptor, removeStorage, removeStorageSync, removeTabBarBadge, request, sendSocketMessage, setNavigationBarColor, setNavigationBarTitle, setStorage, setStorageSync, setTabBarBadge, setTabBarItem, setTabBarStyle, setupApp, setupPage, showActionSheet, showLoading, showModal, showNavigationBarLoading, showTabBar, showTabBarRedDot, showToast, startAccelerometer, startCompass, startPullDownRefresh, stopAccelerometer, stopCompass, stopPullDownRefresh, switchTab, uni$1 as uni, uploadFile, upx2px, useCustomEvent, useOn, useSubscribe, useUserAction, vibrateLong, vibrateShort}; +export {_sfc_main$1 as AsyncErrorComponent, _sfc_main as AsyncLoadingComponent, _sfc_main$j as Audio, index$8 as Button, _sfc_main$i as Canvas, _sfc_main$h as Checkbox, _sfc_main$g as CheckboxGroup, _sfc_main$f as Editor, index$9 as Form, index$7 as Icon, index$6 as Image, _sfc_main$e as Input, _sfc_main$d as Label, LayoutComponent, _sfc_main$c as MovableView, _sfc_main$b as Navigator, index as PageComponent, index$5 as Progress, _sfc_main$a as Radio, _sfc_main$9 as RadioGroup, ResizeSensor, _sfc_main$8 as RichText, _sfc_main$7 as ScrollView, _sfc_main$6 as Slider, _sfc_main$5 as SwiperItem, _sfc_main$4 as Switch, index$4 as Text, _sfc_main$3 as Textarea, UniServiceJSBridge$1 as UniServiceJSBridge, UniViewJSBridge$1 as UniViewJSBridge, index$2 as Video, index$3 as View, index$1 as WebView, addInterceptor, arrayBufferToBase64, base64ToArrayBuffer, canIUse, chooseFile, chooseImage, chooseVideo, clearStorage, clearStorageSync, closeSocket, connectSocket, createInnerAudioContext, createIntersectionObserver, createSelectorQuery, createVideoContext, cssBackdropFilter, cssConstant, cssEnv, cssVar, downloadFile, getApp$1 as getApp, getCurrentPages$1 as getCurrentPages, getFileInfo, getImageInfo, getLocation, getNetworkType, getStorage, getStorageInfo, getStorageInfoSync, getStorageSync, getSystemInfo, getSystemInfoSync, getVideoInfo, hideKeyboard, hideNavigationBarLoading, hideTabBar, hideTabBarRedDot, loadFontFace, makePhoneCall, navigateBack, navigateTo, offAccelerometerChange, offCompassChange, offNetworkStatusChange, onAccelerometerChange, onCompassChange, onNetworkStatusChange, onSocketClose, onSocketError, onSocketMessage, onSocketOpen, onTabBarMidButtonTap, openDocument, pageScrollTo, index$a as plugin, promiseInterceptor, reLaunch, redirectTo, removeInterceptor, removeStorage, removeStorageSync, removeTabBarBadge, request, sendSocketMessage, setNavigationBarColor, setNavigationBarTitle, setStorage, setStorageSync, setTabBarBadge, setTabBarItem, setTabBarStyle, setupApp, setupPage, showModal, showNavigationBarLoading, showTabBar, showTabBarRedDot, startAccelerometer, startCompass, startPullDownRefresh, stopAccelerometer, stopCompass, stopPullDownRefresh, switchTab, uni$1 as uni, uploadFile, upx2px, useCustomEvent, useOn, useSubscribe, useUserAction, vibrateLong, vibrateShort}; diff --git a/packages/uni-h5/src/helpers/useKeyboard.ts b/packages/uni-h5/src/helpers/useKeyboard.ts new file mode 100644 index 0000000000000000000000000000000000000000..ae86fe10ab11fc1d38afaec3a11419e5aa627e1d --- /dev/null +++ b/packages/uni-h5/src/helpers/useKeyboard.ts @@ -0,0 +1,40 @@ +import { onBeforeUnmount, onMounted, ref } from 'vue' + +const KEY_MAPS = { + esc: ['Esc', 'Escape'], + // tab: ['Tab'], + enter: ['Enter'], + // space: [' ', 'Spacebar'], + // up: ['Up', 'ArrowUp'], + // left: ['Left', 'ArrowLeft'], + // right: ['Right', 'ArrowRight'], + // down: ['Down', 'ArrowDown'], + // delete: ['Backspace', 'Delete', 'Del'], +} + +const KEYS = Object.keys(KEY_MAPS) +export function useKeyboard() { + const key = ref('') + const disable = ref(false) + const onKeyup = (evt: KeyboardEvent) => { + if (disable.value) { + return + } + const res = KEYS.find( + (key) => KEY_MAPS[key as keyof typeof KEY_MAPS].indexOf(evt.key) !== -1 + ) + if (res) { + key.value = res + } + } + onMounted(() => { + document.addEventListener('keyup', onKeyup) + }) + onBeforeUnmount(() => { + document.removeEventListener('keyup', onKeyup) + }) + return { + key, + disable, + } +} diff --git a/packages/uni-h5/src/service/api/index.ts b/packages/uni-h5/src/service/api/index.ts index 0d13348beed4377052f2831a4ef417a83e2b613a..21b914a37bb12df7f516f058dcef880c3ed90914 100644 --- a/packages/uni-h5/src/service/api/index.ts +++ b/packages/uni-h5/src/service/api/index.ts @@ -36,10 +36,10 @@ export * from './route/redirectTo' export * from './route/reLaunch' export * from './route/switchTab' +export * from './ui/popup/showModal' export * from './ui/loadFontFace' export * from './ui/navigationBar' export * from './ui/pageScrollTo' -export * from './ui/popup' export * from './ui/startPullDownRefresh' export * from './ui/stopPullDownRefresh' export * from './ui/tabBar' diff --git a/packages/uni-h5/src/service/api/ui/popup/modal.tsx b/packages/uni-h5/src/service/api/ui/popup/modal.tsx new file mode 100644 index 0000000000000000000000000000000000000000..78c82bcf6d4ac9ceb153368ae964ac2b887c00e9 --- /dev/null +++ b/packages/uni-h5/src/service/api/ui/popup/modal.tsx @@ -0,0 +1,91 @@ +import { onTouchmovePrevent, onTouchmoveStop } from '@dcloudio/uni-core' +import { Transition, defineComponent, ExtractPropTypes } from 'vue' +import { usePopup, VNODE_MASK } from './utils' + +const props = { + title: { + type: String, + default: '', + }, + content: { + type: String, + default: '', + }, + showCancel: { + type: Boolean, + default: true, + }, + cancelText: { + type: String, + default: 'Cancel', + }, + cancelColor: { + type: String, + default: '#000000', + }, + confirmText: { + type: String, + default: 'OK', + }, + confirmColor: { + type: String, + default: '#007aff', + }, + visible: { + type: Boolean, + }, +} +export type ModalProps = ExtractPropTypes + +export default /*#__PURE__*/ defineComponent({ + props, + setup(props, { emit }) { + const close = () => (visible.value = false) + const cancel = () => (close(), emit('close', 'cancel')) + const confirm = () => (close(), emit('close', 'confirm')) + const visible = usePopup(props, { + onEsc: cancel, + onEnter: confirm, + }) + return () => { + const { title, content, showCancel, confirmText, confirmColor } = props + return ( + + + {VNODE_MASK} +
+ {title && ( +
+ +
+ )} +
+
+ {showCancel && ( +
+ {props.cancelText} +
+ )} +
+ {confirmText} +
+
+
+
+
+ ) + } + }, +}) diff --git a/packages/uni-h5/src/service/api/ui/popup/showModal.ts b/packages/uni-h5/src/service/api/ui/popup/showModal.ts new file mode 100644 index 0000000000000000000000000000000000000000..5ee06060ef542ada4c2e9e559ee932d603327fd0 --- /dev/null +++ b/packages/uni-h5/src/service/api/ui/popup/showModal.ts @@ -0,0 +1,46 @@ +import { extend } from '@vue/shared' +import { nextTick, reactive } from 'vue' +import { + API_SHOW_MODAL, + API_TYPE_SHOW_MODAL, + defineAsyncApi, + ShowModalOptions, + ShowModalProtocol, +} from '@dcloudio/uni-api' + +import modal, { ModalProps } from './modal' + +import { ensureRoot, createRootApp } from './utils' + +let showModalState: ModalProps + +let currentShowModalResolve: UniApp.ShowModalOptions['success'] + +function onModalClose(type: 'cancel' | 'confirm') { + currentShowModalResolve && + currentShowModalResolve!({ + confirm: type === 'confirm', + cancel: type === 'cancel', + }) +} + +export const showModal = defineAsyncApi( + API_SHOW_MODAL, + (args, { resolve }) => { + currentShowModalResolve = resolve + if (!showModalState) { + showModalState = reactive(args as ModalProps) + // 异步执行,避免干扰 getCurrentInstance + nextTick(() => + createRootApp(modal, showModalState, onModalClose).mount( + ensureRoot('u-a-m') + ) + ) + } else { + extend(showModalState, args) + } + showModalState.visible = true + }, + ShowModalProtocol, + ShowModalOptions +) diff --git a/packages/uni-h5/src/service/api/ui/popup/utils.ts b/packages/uni-h5/src/service/api/ui/popup/utils.ts new file mode 100644 index 0000000000000000000000000000000000000000..e391b290db89527d3fca11f468eb3daa1b3bc6a0 --- /dev/null +++ b/packages/uni-h5/src/service/api/ui/popup/utils.ts @@ -0,0 +1,90 @@ +import { + ref, + watch, + watchEffect, + createVNode, + Component, + defineComponent, + createApp, + openBlock, + createBlock, + mergeProps, +} from 'vue' + +import { useKeyboard } from '../../../../helpers/useKeyboard' + +export const VNODE_MASK = /*#__PURE__*/ createVNode( + 'div', + { class: 'uni-mask' }, + null, + -1 /* HOISTED */ +) + +export function createRootApp( + component: Component, + rootState: Record, + callback: (...args: any[]) => void +) { + return createApp( + defineComponent({ + setup() { + const onClose = (...args: any[]) => ( + (rootState.visible = false), callback.apply(null, args) + ) + return () => ( + openBlock(), + createBlock( + component, + mergeProps( + { + onClose, + }, + rootState + ) + ) + ) + }, + }) + ) +} + +export function ensureRoot(id: string) { + let rootEl = document.getElementById(id) + if (!rootEl) { + rootEl = document.createElement('div') + rootEl.id = id + document.body.append(rootEl) + } + return rootEl +} + +export function usePopup( + props: { visible: boolean }, + { + onEsc, + onEnter, + }: { + onEsc?: () => void + onEnter?: () => void + } +) { + const visible = ref(props.visible) + const { key, disable } = useKeyboard() + watch( + () => props.visible, + (value) => (visible.value = value) + ) + watch( + () => visible.value, + (value) => (disable.value = !value) + ) + watchEffect(() => { + const { value } = key + if (value === 'esc') { + onEsc && onEsc() + } else if (value === 'enter') { + onEnter && onEnter() + } + }) + return visible +} diff --git a/packages/uni-h5/style/api/action-sheet.css b/packages/uni-h5/style/api/action-sheet.css new file mode 100644 index 0000000000000000000000000000000000000000..cf47f9fc83c8c7818d85d7fdb57670fc9a46f16e --- /dev/null +++ b/packages/uni-h5/style/api/action-sheet.css @@ -0,0 +1,108 @@ +uni-actionsheet { + display: block; + box-sizing: border-box; +} + +.uni-actionsheet { + position: fixed; + left: 6px; + right: 6px; + bottom: 6px; + transform: translate(0, 100%); + backface-visibility: hidden; + z-index: 999; + visibility: hidden; + transition: transform 0.3s, visibility 0.3s; +} + +.uni-actionsheet.uni-actionsheet_toggle { + visibility: visible; + transform: translate(0, 0); +} + +.uni-actionsheet * { + box-sizing: border-box; +} + +.uni-actionsheet__menu, +.uni-actionsheet__action { + border-radius: 5px; + background-color: #fcfcfd; +} + +.uni-actionsheet__action { + margin-top: 6px; +} + +.uni-actionsheet__cell, +.uni-actionsheet__title { + position: relative; + padding: 10px 6px; + text-align: center; + font-size: 18px; + text-overflow: ellipsis; + overflow: hidden; + cursor: pointer; +} + +.uni-actionsheet__title { + position: absolute; + top: 0; + right: 0; + left: 0; + z-index: 1; + background-color: #fff; + border-radius: 5px 5px 0 0; + border-bottom: 1px solid #e5e5e5; +} + +.uni-actionsheet__cell:before { + content: ' '; + position: absolute; + left: 0; + top: 0; + right: 0; + height: 1px; + border-top: 1px solid #e5e5e5; + color: #e5e5e5; + transform-origin: 0 0; + transform: scaleY(0.5); +} + +.uni-actionsheet__cell:active { + background-color: #ececec; +} + +.uni-actionsheet__cell:first-child:before { + display: none; +} + +@media screen and (min-width: 500px) and (min-height: 500px) { + .uni-mask.uni-actionsheet__mask { + background: none; + } + + .uni-actionsheet { + width: 300px; + left: 50%; + right: auto; + top: 50%; + bottom: auto; + transform: translate(-50%, -50%); + opacity: 0; + transition: opacity 0.3s, visibility 0.3s; + } + + .uni-actionsheet.uni-actionsheet_toggle { + opacity: 1; + transform: translate(-50%, -50%); + } + + .uni-actionsheet__menu { + box-shadow: 0px 0 20px 5px rgba(0, 0, 0, 0.3); + } + + .uni-actionsheet__action { + display: none; + } +} diff --git a/packages/uni-h5/style/api/modal.css b/packages/uni-h5/style/api/modal.css new file mode 100644 index 0000000000000000000000000000000000000000..9a6e1b6f4587c9d520b7e18e595a15541eb06e1a --- /dev/null +++ b/packages/uni-h5/style/api/modal.css @@ -0,0 +1,118 @@ +uni-modal { + position: fixed; + top: 0; + right: 0; + bottom: 0; + left: 0; + z-index: 999; + display: block; + box-sizing: border-box; +} + +.uni-modal { + position: fixed; + z-index: 999; + width: 80%; + max-width: 300px; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + background-color: #ffffff; + text-align: center; + border-radius: 3px; + overflow: hidden; +} + +.uni-modal * { + box-sizing: border-box; +} + +.uni-modal__hd { + padding: 1em 1.6em 0.3em; +} + +.uni-modal__title { + font-weight: 400; + font-size: 18px; + word-wrap: break-word; + word-break: break-all; + white-space: pre-wrap; + overflow: hidden; + text-overflow: ellipsis; + display: -webkit-box; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; +} + +.uni-modal__bd { + padding: 1.3em 1.6em 1.3em; + min-height: 40px; + font-size: 15px; + line-height: 1.4; + word-wrap: break-word; + word-break: break-all; + white-space: pre-wrap; + color: #999999; + max-height: 400px; + overflow-x: hidden; + overflow-y: auto; +} + +.uni-modal__ft { + position: relative; + line-height: 48px; + font-size: 18px; + display: flex; +} + +.uni-modal__ft:after { + content: ' '; + position: absolute; + left: 0; + top: 0; + right: 0; + height: 1px; + border-top: 1px solid #d5d5d6; + color: #d5d5d6; + transform-origin: 0 0; + transform: scaleY(0.5); +} + +.uni-modal__btn { + display: block; + flex: 1; + color: #3cc51f; + text-decoration: none; + -webkit-tap-highlight-color: rgba(0, 0, 0, 0); + position: relative; + cursor: pointer; +} + +.uni-modal__btn:active { + background-color: #eeeeee; +} + +.uni-modal__btn:after { + content: ' '; + position: absolute; + left: 0; + top: 0; + width: 1px; + bottom: 0; + border-left: 1px solid #d5d5d6; + color: #d5d5d6; + transform-origin: 0 0; + transform: scaleX(0.5); +} + +.uni-modal__btn:first-child:after { + display: none; +} + +.uni-modal__btn_default { + color: #353535; +} + +.uni-modal__btn_primary { + color: #007aff; +} diff --git a/packages/uni-h5/style/api/toast.css b/packages/uni-h5/style/api/toast.css new file mode 100644 index 0000000000000000000000000000000000000000..724d66bd78609fc62e65021c39a8cbc607185105 --- /dev/null +++ b/packages/uni-h5/style/api/toast.css @@ -0,0 +1,84 @@ +uni-toast { + position: fixed; + top: 0; + right: 0; + bottom: 0; + left: 0; + z-index: 999; + display: block; + box-sizing: border-box; + pointer-events: none; + font-size: 16px; +} + +.uni-sample-toast { + position: fixed; + z-index: 999; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + text-align: center; + max-width: 80%; +} + +.uni-simple-toast__text { + display: inline-block; + vertical-align: middle; + color: #ffffff; + background-color: rgba(17, 17, 17, 0.7); + padding: 10px 20px; + border-radius: 5px; + font-size: 13px; + text-align: center; + max-width: 100%; + word-break: break-all; + white-space: normal; +} + +uni-toast .uni-mask { + pointer-events: auto; +} + +.uni-toast { + position: fixed; + z-index: 999; + width: 8em; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + background: rgba(17, 17, 17, 0.7); + text-align: center; + border-radius: 5px; + color: #ffffff; +} + +.uni-toast * { + box-sizing: border-box; +} + +.uni-toast__icon { + margin: 20px 0 0; + width: 38px; + height: 38px; + vertical-align: baseline; +} + +.uni-icon_toast { + margin: 15px 0 0; +} + +.uni-icon_toast.uni-icon-success-no-circle:before { + color: #ffffff; + font-size: 55px; +} + +.uni-icon_toast.uni-loading { + margin: 20px 0 0; + width: 38px; + height: 38px; + vertical-align: baseline; +} + +.uni-toast__content { + margin: 0 0 15px; +} diff --git a/packages/uni-h5/style/framework/base.css b/packages/uni-h5/style/framework/base.css index 6831d78496bd5433e381cd01b397b4812b3612b4..0c5d738eda64240c8bdc8c79ad6b4852c3b9eafe 100644 --- a/packages/uni-h5/style/framework/base.css +++ b/packages/uni-h5/style/framework/base.css @@ -30,3 +30,25 @@ uni-page, uni-page-wrapper { height: 100%; } + +/* toast,modal,actionSheet,picker,layout */ +.uni-mask { + position: fixed; + z-index: 999; + top: 0; + right: 0; + left: 0; + bottom: 0; + background: rgba(0, 0, 0, 0.5); +} +/* toast,modal,actionSheet,picker */ +.uni-fade-enter-active, +.uni-fade-leave-active { + transition-duration: .25s; + transition-property: opacity; + transition-timing-function: ease; +} +.uni-fade-enter-from, +.uni-fade-leave-active { + opacity: 0; +} diff --git a/packages/vite-plugin-uni/src/configResolved/plugins/easycom.ts b/packages/vite-plugin-uni/src/configResolved/plugins/easycom.ts index 5813f41d44f057d54eb62ce2fc080c2aa0418ada..060fcb85c9ba279ee29a8434c51c633f5b71e4b8 100644 --- a/packages/vite-plugin-uni/src/configResolved/plugins/easycom.ts +++ b/packages/vite-plugin-uni/src/configResolved/plugins/easycom.ts @@ -7,11 +7,14 @@ import { isBuiltInComponent } from '@dcloudio/uni-shared' import { EXTNAME_VUE, parseVueRequest } from '@dcloudio/uni-cli-shared' import { UniPluginFilterOptions } from '.' -import { debugEasycom, matchEasycom } from '../../utils' +import { + H5_COMPONENTS_STYLE_PATH, + BASE_COMPONENTS_STYLE_PATH, + debugEasycom, + matchEasycom, +} from '../../utils' -const BASE_COMPONENTS_STYLE_PATH = '@dcloudio/uni-components/style/' const H5_COMPONENTS_PATH = '@dcloudio/uni-h5/dist/uni-h5.esm.js' -const H5_COMPONENTS_STYLE_PATH = '@dcloudio/uni-h5/style/' const baseComponents = [ 'audio', diff --git a/packages/vite-plugin-uni/src/configResolved/plugins/index.ts b/packages/vite-plugin-uni/src/configResolved/plugins/index.ts index c4f81c31b00e54cc112337078bc28ecd80ce4437..a6321a26cc21532e652e85fda3a67bfe2c5866b6 100644 --- a/packages/vite-plugin-uni/src/configResolved/plugins/index.ts +++ b/packages/vite-plugin-uni/src/configResolved/plugins/index.ts @@ -1,6 +1,6 @@ -import { FilterPattern } from '@rollup/pluginutils' import debug from 'debug' import { Plugin, ResolvedConfig } from 'vite' +import { FilterPattern } from '@rollup/pluginutils' import { VitePluginUniResolvedOptions } from '../..' import { uniPrePlugin } from './pre' import { uniJsonPlugin } from './json' @@ -55,6 +55,12 @@ const uniEasycomPluginOptions: Partial = { exclude: [APP_VUE_RE, UNI_H5_RE], } +const API_STYLES = { + showModal: 'modal', + showToast: 'toast', + showActionSheet: 'action-sheet', +} + const uniInjectPluginOptions: Partial = { exclude: [...COMMON_EXCLUDE], 'uni.': '@dcloudio/uni-h5', @@ -62,6 +68,18 @@ const uniInjectPluginOptions: Partial = { getCurrentPages: ['@dcloudio/uni-h5', 'getCurrentPages'], UniServiceJSBridge: ['@dcloudio/uni-h5', 'UniServiceJSBridge'], UniViewJSBridge: ['@dcloudio/uni-h5', 'UniViewJSBridge'], + callback(imports, mod) { + const style = + mod[0] === '@dcloudio/uni-h5' && + API_STYLES[mod[1] as keyof typeof API_STYLES] + if (!style) { + return + } + const hash = `${mod[0]}.${mod[1]}` + if (!imports.has(hash)) { + imports.set(hash, `import '@dcloudio/uni-h5/style/api/${style}.css';`) + } + }, } export function resolvePlugins( diff --git a/packages/vite-plugin-uni/src/configResolved/plugins/inject.ts b/packages/vite-plugin-uni/src/configResolved/plugins/inject.ts index adc9cc4413f46f7f2a99e59b38e985dee4bdc020..7686bf0ae76febd05415f7fa44b1ee6ba811fcfd 100644 --- a/packages/vite-plugin-uni/src/configResolved/plugins/inject.ts +++ b/packages/vite-plugin-uni/src/configResolved/plugins/inject.ts @@ -40,6 +40,7 @@ type Injectment = string | [string, string] export interface InjectOptions extends UniPluginFilterOptions { sourceMap?: boolean + callback?: (imports: Map, mod: [string, string]) => void [str: string]: | Injectment | InjectOptions['include'] @@ -60,6 +61,7 @@ export function uniInjectPlugin(options: InjectOptions): Plugin { delete modules.exclude delete modules.sourceMap delete modules.devServer + delete modules.callback const modulesMap = new Map() const namespaceModulesMap = new Map() @@ -83,6 +85,7 @@ export function uniInjectPlugin(options: InjectOptions): Plugin { ) const EXTNAMES = EXTNAME_JS.concat(EXTNAME_VUE) const sourceMap = options.sourceMap !== false + const callback = options.callback return { name: 'vite:uni-inject', transform(code, id) { @@ -154,6 +157,7 @@ export function uniInjectPlugin(options: InjectOptions): Plugin { hash, `import { ${mod[1]} as ${importLocalName} } from '${mod[0]}';` ) + callback && callback(newImports, mod) } } diff --git a/packages/vite-plugin-uni/src/configResolved/plugins/pagesJson.ts b/packages/vite-plugin-uni/src/configResolved/plugins/pagesJson.ts index 5d3512b1cdd5f523ddd9dfa60b4f174be4d7c742..a93b91f27675405ff6ea2c9685442a652b92aab4 100644 --- a/packages/vite-plugin-uni/src/configResolved/plugins/pagesJson.ts +++ b/packages/vite-plugin-uni/src/configResolved/plugins/pagesJson.ts @@ -5,7 +5,13 @@ import { Plugin, ResolvedConfig } from 'vite' import { parse } from 'jsonc-parser' import { camelize, capitalize } from '@vue/shared' import { VitePluginUniResolvedOptions } from '../..' -import { FEATURE_DEFINES, normalizePagesJson } from '../../utils' +import { + BASE_COMPONENTS_STYLE_PATH, + FEATURE_DEFINES, + H5_API_STYLE_PATH, + H5_FRAMEWORK_STYLE_PATH, + normalizePagesJson, +} from '../../utils' const pkg = require('@dcloudio/vite-plugin-uni/package.json') @@ -60,7 +66,7 @@ function parsePagesJson( const manifestJsonPath = slash( path.resolve(options.inputDir, 'manifest.json.js') ) - const cssCode = generateCssCode(config) + const cssCode = generateCssCode(config, options) return ` import { extend } from '@vue/shared' @@ -99,26 +105,34 @@ function normalizePageIdentifier(path: string) { return capitalize(camelize(path.replace(/\//g, '-'))) } -function generateCssCode(config: ResolvedConfig) { +function generateCssCode( + config: ResolvedConfig, + options: VitePluginUniResolvedOptions +) { const define = config.define! as FEATURE_DEFINES - const cssFiles = ['@dcloudio/uni-h5/style/framework/base.css'] + const cssFiles = [H5_FRAMEWORK_STYLE_PATH + 'base.css'] if (define.__UNI_FEATURE_PAGES__) { - cssFiles.push('@dcloudio/uni-h5/style/framework/layout.css') + cssFiles.push(H5_FRAMEWORK_STYLE_PATH + 'layout.css') } if (define.__UNI_FEATURE_NAVIGATIONBAR__) { - cssFiles.push('@dcloudio/uni-h5/style/framework/pageHead.css') + cssFiles.push(H5_FRAMEWORK_STYLE_PATH + 'pageHead.css') } if (define.__UNI_FEATURE_TABBAR__) { - cssFiles.push('@dcloudio/uni-h5/style/framework/tabBar.css') + cssFiles.push(H5_FRAMEWORK_STYLE_PATH + 'tabBar.css') } if (define.__UNI_FEATURE_NVUE__) { - cssFiles.push('@dcloudio/uni-h5/style/framework/nvue.css') + cssFiles.push(H5_FRAMEWORK_STYLE_PATH + 'nvue.css') } if (define.__UNI_FEATURE_PULL_DOWN_REFRESH__) { - cssFiles.push('@dcloudio/uni-h5/style/framework/pageRefresh.css') + cssFiles.push(H5_FRAMEWORK_STYLE_PATH + 'pageRefresh.css') } if (define.__UNI_FEATURE_NAVIGATIONBAR_SEARCHINPUT__) { - cssFiles.push('@dcloudio/uni-components/style/input.css') + cssFiles.push(BASE_COMPONENTS_STYLE_PATH + 'input.css') + } + if (options.command === 'serve') { + cssFiles.push(H5_API_STYLE_PATH + 'modal.css') + cssFiles.push(H5_API_STYLE_PATH + 'toast.css') + cssFiles.push(H5_API_STYLE_PATH + 'action-sheet.css') } return cssFiles.map((file) => `import '${file}'`).join('\n') } diff --git a/packages/vite-plugin-uni/src/utils/constants.ts b/packages/vite-plugin-uni/src/utils/constants.ts new file mode 100644 index 0000000000000000000000000000000000000000..974b58b936ec5efe8bde73a9cd10701dec6da28e --- /dev/null +++ b/packages/vite-plugin-uni/src/utils/constants.ts @@ -0,0 +1,4 @@ +export const H5_API_STYLE_PATH = '@dcloudio/uni-h5/style/api/' +export const H5_FRAMEWORK_STYLE_PATH = '@dcloudio/uni-h5/style/framework/' +export const H5_COMPONENTS_STYLE_PATH = '@dcloudio/uni-h5/style/' +export const BASE_COMPONENTS_STYLE_PATH = '@dcloudio/uni-components/style/' diff --git a/packages/vite-plugin-uni/src/utils/index.ts b/packages/vite-plugin-uni/src/utils/index.ts index 6ea1c4ccf8f95cbb2838b22ab663462610dd6e86..177be90360758a646570c584677a2b37c03c7e80 100644 --- a/packages/vite-plugin-uni/src/utils/index.ts +++ b/packages/vite-plugin-uni/src/utils/index.ts @@ -3,3 +3,4 @@ export * from './define' export * from './easycom' export * from './postcss' export * from './pagesJson' +export * from './constants'