From 8a656509d11bdec4b3d39fb3d5497ecbb9fde3f0 Mon Sep 17 00:00:00 2001 From: DCloud_LXH <283700113@qq.com> Date: Thu, 10 Feb 2022 18:12:38 +0800 Subject: [PATCH] =?UTF-8?q?feat(nvue):=20wip=20label=E3=80=81button?= =?UTF-8?q?=E3=80=81movable-view=E3=80=81picker-view?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/service/api/device/scanCode.ts | 2 +- .../service/api/location/chooseLocation.ts | 2 +- .../src/service/api/location/openLocation.ts | 2 +- .../src/view/components/picker/index.tsx | 2 +- .../uni-components/src/components/label.ts | 28 + .../src/components/movable-area.ts | 8 + .../src/components/movable-view/index.ts | 68 ++ .../{vue => components}/movable-view/utils.js | 48 +- .../src/components/picker-view.ts | 43 ++ .../uni-components/src/nvue/button/index.tsx | 293 +++++++ .../uni-components/src/nvue/components.ts | 8 + .../uni-components/src/nvue/helpers/common.ts | 32 + .../uni-components/src/nvue/helpers/dom.ts | 9 + .../uni-components/src/nvue/helpers/index.ts | 2 + .../uni-components/src/nvue/label/index.tsx | 45 ++ .../src/nvue/movable-area/index.tsx | 129 ++++ .../src/nvue/movable-view/index.tsx | 730 ++++++++++++++++++ .../src/nvue/movable-view/useTouchtrack.ts | 118 +++ .../src/nvue/picker-view-column/index.tsx | 262 +++++++ .../src/nvue/picker-view/index.tsx | 120 +++ .../uni-components/src/vue/label/index.tsx | 35 +- .../src/vue/movable-area/index.tsx | 20 +- .../src/vue/movable-view/index.tsx | 86 +-- .../src/vue/picker-view/index.tsx | 42 +- packages/uni-components/vite.config.ts | 14 +- packages/uni-core/src/helpers/index.ts | 1 + .../src/helpers/showPage.ts} | 0 pnpm-lock.yaml | 6 +- 28 files changed, 1960 insertions(+), 195 deletions(-) create mode 100644 packages/uni-components/src/components/label.ts create mode 100644 packages/uni-components/src/components/movable-area.ts create mode 100644 packages/uni-components/src/components/movable-view/index.ts rename packages/uni-components/src/{vue => components}/movable-view/utils.js (89%) create mode 100644 packages/uni-components/src/components/picker-view.ts create mode 100644 packages/uni-components/src/nvue/button/index.tsx create mode 100644 packages/uni-components/src/nvue/helpers/common.ts create mode 100644 packages/uni-components/src/nvue/helpers/dom.ts create mode 100644 packages/uni-components/src/nvue/helpers/index.ts create mode 100644 packages/uni-components/src/nvue/label/index.tsx create mode 100644 packages/uni-components/src/nvue/movable-area/index.tsx create mode 100644 packages/uni-components/src/nvue/movable-view/index.tsx create mode 100644 packages/uni-components/src/nvue/movable-view/useTouchtrack.ts create mode 100644 packages/uni-components/src/nvue/picker-view-column/index.tsx create mode 100644 packages/uni-components/src/nvue/picker-view/index.tsx rename packages/{uni-app-plus/src/helpers/page.ts => uni-core/src/helpers/showPage.ts} (100%) diff --git a/packages/uni-app-plus/src/service/api/device/scanCode.ts b/packages/uni-app-plus/src/service/api/device/scanCode.ts index 0fdd432b1..fb945c8bc 100644 --- a/packages/uni-app-plus/src/service/api/device/scanCode.ts +++ b/packages/uni-app-plus/src/service/api/device/scanCode.ts @@ -1,4 +1,4 @@ -import { showPage } from '../../../helpers/page.js' +import { showPage } from '@dcloudio/uni-core' import { defineAsyncApi, API_SCAN_CODE, diff --git a/packages/uni-app-plus/src/service/api/location/chooseLocation.ts b/packages/uni-app-plus/src/service/api/location/chooseLocation.ts index c3a080717..2bef0fdfa 100644 --- a/packages/uni-app-plus/src/service/api/location/chooseLocation.ts +++ b/packages/uni-app-plus/src/service/api/location/chooseLocation.ts @@ -4,7 +4,7 @@ import { defineAsyncApi, ChooseLocationProtocol, } from '@dcloudio/uni-api' -import { showPage } from '../../../helpers/page' +import { showPage } from '@dcloudio/uni-core' import { getStatusBarStyle } from '../../../helpers/statusBar' export const chooseLocation = defineAsyncApi( diff --git a/packages/uni-app-plus/src/service/api/location/openLocation.ts b/packages/uni-app-plus/src/service/api/location/openLocation.ts index e01454183..ce3a42834 100644 --- a/packages/uni-app-plus/src/service/api/location/openLocation.ts +++ b/packages/uni-app-plus/src/service/api/location/openLocation.ts @@ -5,7 +5,7 @@ import { OpenLocationProtocol, OpenLocationOptions, } from '@dcloudio/uni-api' -import { showPage } from '../../../helpers/page.js' +import { showPage } from '@dcloudio/uni-core' export const openLocation = defineAsyncApi( API_OPEN_LOCATION, diff --git a/packages/uni-app-plus/src/view/components/picker/index.tsx b/packages/uni-app-plus/src/view/components/picker/index.tsx index dfd93329a..121094b76 100644 --- a/packages/uni-app-plus/src/view/components/picker/index.tsx +++ b/packages/uni-app-plus/src/view/components/picker/index.tsx @@ -6,7 +6,7 @@ import { } from '@dcloudio/uni-components' import { useI18n, initI18nPickerMsgsOnce } from '@dcloudio/uni-core' import { UniFormCtx, uniFormKey } from '@dcloudio/uni-components' -import { showPage, Page } from '../../../helpers/page' +import { showPage, Page } from '@dcloudio/uni-core' import { getNavigationBarHeight } from '../../../helpers/navigationBar' type Mode = 'selector' | 'multiSelector' | 'time' | 'date' diff --git a/packages/uni-components/src/components/label.ts b/packages/uni-components/src/components/label.ts new file mode 100644 index 000000000..cf7a05ab6 --- /dev/null +++ b/packages/uni-components/src/components/label.ts @@ -0,0 +1,28 @@ +import { provide } from 'vue' +import { PolySymbol } from '@dcloudio/uni-core' +export const props = { + for: { + type: String, + default: '', + }, +} +export const uniLabelKey = PolySymbol(__DEV__ ? 'uniLabel' : 'ul') +export interface UniLabelCtx { + addHandler: (handler: UniLabelHandlerCtx) => void + removeHandler: (handler: UniLabelHandlerCtx) => void +} +export type UniLabelHandlerCtx = ($event: Event, b: boolean) => void +export function useProvideLabel() { + const handlers: UniLabelHandlerCtx[] = [] + + provide(uniLabelKey, { + addHandler(handler: UniLabelHandlerCtx) { + handlers.push(handler) + }, + removeHandler(handler: UniLabelHandlerCtx) { + handlers.splice(handlers.indexOf(handler), 1) + }, + }) + + return handlers +} diff --git a/packages/uni-components/src/components/movable-area.ts b/packages/uni-components/src/components/movable-area.ts new file mode 100644 index 000000000..9bfc949ce --- /dev/null +++ b/packages/uni-components/src/components/movable-area.ts @@ -0,0 +1,8 @@ +import { ExtractPropTypes } from 'vue' +export const props = { + scaleArea: { + type: Boolean, + default: false, + }, +} +export type Props = ExtractPropTypes diff --git a/packages/uni-components/src/components/movable-view/index.ts b/packages/uni-components/src/components/movable-view/index.ts new file mode 100644 index 000000000..c2ecbb63b --- /dev/null +++ b/packages/uni-components/src/components/movable-view/index.ts @@ -0,0 +1,68 @@ +import { ExtractPropTypes } from 'vue' +import { Decline, Friction, STD } from './utils' +export { Decline, Friction, STD } +export const props = { + direction: { + type: String, + default: 'none', + }, + inertia: { + type: [Boolean, String], + default: false, + }, + outOfBounds: { + type: [Boolean, String], + default: false, + }, + x: { + type: [Number, String], + default: 0, + }, + y: { + type: [Number, String], + default: 0, + }, + damping: { + type: [Number, String], + default: 20, + }, + friction: { + type: [Number, String], + default: 2, + }, + disabled: { + type: [Boolean, String], + default: false, + }, + scale: { + type: [Boolean, String], + default: false, + }, + scaleMin: { + type: [Number, String], + default: 0.5, + }, + scaleMax: { + type: [Number, String], + default: 10, + }, + scaleValue: { + type: [Number, String], + default: 1, + }, + animation: { + type: [Boolean, String], + default: true, + }, +} + +export type Props = ExtractPropTypes +export type FrictionCallback = (friction: Friction | STD) => void +export type Record = { + id: number + cancelled: boolean +} + +export function v(a: number, b: number) { + return +((1000 * a - 1000 * b) / 1000).toFixed(1) +} diff --git a/packages/uni-components/src/vue/movable-view/utils.js b/packages/uni-components/src/components/movable-view/utils.js similarity index 89% rename from packages/uni-components/src/vue/movable-view/utils.js rename to packages/uni-components/src/components/movable-view/utils.js index 6e4b8e116..54a2cee8f 100644 --- a/packages/uni-components/src/vue/movable-view/utils.js +++ b/packages/uni-components/src/components/movable-view/utils.js @@ -18,7 +18,7 @@ export function Friction(e, t) { this._v = 0 } Friction.prototype.setV = function (x, y) { - var n = Math.pow(Math.pow(x, 2) + Math.pow(y, 2), 0.5) + const n = Math.pow(Math.pow(x, 2) + Math.pow(y, 2), 0.5) this._x_v = x this._y_v = y this._x_a = (-this._f * this._x_v) / n @@ -39,8 +39,8 @@ Friction.prototype.s = function (t) { t = this._t this._lastDt = t } - var x = this._x_v * t + 0.5 * this._x_a * Math.pow(t, 2) + this._x_s - var y = this._y_v * t + 0.5 * this._y_a * Math.pow(t, 2) + this._y_s + let x = this._x_v * t + 0.5 * this._x_a * Math.pow(t, 2) + this._x_s + let y = this._y_v * t + 0.5 * this._y_a * Math.pow(t, 2) + this._y_s if ( (this._x_a > 0 && x < this._endPositionX) || (this._x_a < 0 && x > this._endPositionX) @@ -80,7 +80,7 @@ Friction.prototype.dt = function () { return -this._x_v / this._x_a } Friction.prototype.done = function () { - var t = + const t = e(this.s().x, this._endPositionX) || e(this.s().y, this._endPositionY) || this._lastDt === this._t @@ -105,10 +105,10 @@ export function Spring(m, k, c) { this._startTime = 0 } Spring.prototype._solve = function (e, t) { - var n = this._c - var i = this._m - var r = this._k - var o = n * n - 4 * i * r + const n = this._c + const i = this._m + const r = this._k + const o = n * n - 4 * i * r if (o === 0) { const a = -n / (2 * i) const s = e @@ -118,7 +118,7 @@ Spring.prototype._solve = function (e, t) { return (s + l * e) * Math.pow(Math.E, a * e) }, dx: function (e) { - var t = Math.pow(Math.E, a * e) + const t = Math.pow(Math.E, a * e) return a * (s + l * e) * t + l * t }, } @@ -130,8 +130,8 @@ Spring.prototype._solve = function (e, t) { const h = e - d return { x: function (e) { - var t - var n + let t + let n if (e === this._t) { t = this._powER1T n = this._powER2T @@ -146,8 +146,8 @@ Spring.prototype._solve = function (e, t) { return h * t + d * n }, dx: function (e) { - var t - var n + let t + let n if (e === this._t) { t = this._powER1T n = this._powER2T @@ -163,10 +163,10 @@ Spring.prototype._solve = function (e, t) { }, } } - var p = Math.sqrt(4 * i * r - n * n) / (2 * i) - var f = (-n / 2) * i - var v = e - var g = (t - f * e) / p + const p = Math.sqrt(4 * i * r - n * n) / (2 * i) + const f = (-n / 2) * i + const v = e + const g = (t - f * e) / p return { x: function (e) { return ( @@ -174,9 +174,9 @@ Spring.prototype._solve = function (e, t) { ) }, dx: function (e) { - var t = Math.pow(Math.E, f * e) - var n = Math.cos(p * e) - var i = Math.sin(p * e) + const t = Math.pow(Math.E, f * e) + const n = Math.cos(p * e) + const i = Math.sin(p * e) return t * (g * p * n - v * p * i) + f * t * (g * i + v * n) }, } @@ -199,7 +199,7 @@ Spring.prototype.setEnd = function (e, n, i) { } if (e !== this._endPosition || !t(n, 0.1)) { n = n || 0 - var r = this._endPosition + let r = this._endPosition if (this._solution) { if (t(n, 0.1)) { n = this._solution.dx((i - this._startTime) / 1e3) @@ -286,14 +286,14 @@ export function STD(e, t, n) { this._startTime = 0 } STD.prototype.setEnd = function (e, t, n, i) { - var r = new Date().getTime() + const r = new Date().getTime() this._springX.setEnd(e, i, r) this._springY.setEnd(t, i, r) this._springScale.setEnd(n, i, r) this._startTime = r } STD.prototype.x = function () { - var e = (new Date().getTime() - this._startTime) / 1e3 + const e = (new Date().getTime() - this._startTime) / 1e3 return { x: this._springX.x(e), y: this._springY.x(e), @@ -301,7 +301,7 @@ STD.prototype.x = function () { } } STD.prototype.done = function () { - var e = new Date().getTime() + const e = new Date().getTime() return ( this._springX.done(e) && this._springY.done(e) && this._springScale.done(e) ) diff --git a/packages/uni-components/src/components/picker-view.ts b/packages/uni-components/src/components/picker-view.ts new file mode 100644 index 000000000..2a4b7c20d --- /dev/null +++ b/packages/uni-components/src/components/picker-view.ts @@ -0,0 +1,43 @@ +import { + PropType, + ExtractPropTypes, + ComponentInternalInstance, + WritableComputedRef, +} from 'vue' + +export const props = { + value: { + type: Array as PropType, + default() { + return [] + }, + validator: function (val: any) { + return ( + Array.isArray(val) && + val.filter((val) => typeof val === 'number').length === val.length + ) + }, + }, + indicatorStyle: { + type: String, + default: '', + }, + indicatorClass: { + type: String, + default: '', + }, + maskStyle: { + type: String, + default: '', + }, + maskClass: { + type: String, + default: '', + }, +} + +export type Props = ExtractPropTypes + +export type GetPickerViewColumn = ( + columnInstance: ComponentInternalInstance +) => WritableComputedRef diff --git a/packages/uni-components/src/nvue/button/index.tsx b/packages/uni-components/src/nvue/button/index.tsx new file mode 100644 index 000000000..b00cf4f73 --- /dev/null +++ b/packages/uni-components/src/nvue/button/index.tsx @@ -0,0 +1,293 @@ +import { inject, onBeforeUnmount, ref, defineComponent, Text } from 'vue' +import { uniLabelKey, UniLabelCtx } from '../label' +import { useListeners } from '../../helpers/useListeners' +import { useHoverClass } from '../utils' +import { buttonProps } from '../../components/button' +import { extend } from '@vue/shared' + +const buttonStyle = [ + { + ub: { + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'center', + position: 'relative', + paddingLeft: '5', + paddingRight: '5', + overflow: 'hidden', + color: '#000000', + backgroundColor: '#f8f8f8', + borderRadius: '5', + borderStyle: 'solid', + borderWidth: '1', + borderColor: '#dbdbdb', + }, + 'ub-t': { + color: '#000000', + fontSize: '18', + textDecoration: 'none', + lineHeight: '46', + }, + 'ub-d': { + backgroundColor: '#f8f8f8', + }, + 'ub-p': { + backgroundColor: '#007aff', + borderColor: '#0062cc', + }, + 'ub-w': { + backgroundColor: '#e64340', + borderColor: '#b83633', + }, + 'ub-d-t': { + color: '#000000', + }, + 'ub-p-t': { + color: '#ffffff', + }, + 'ub-w-t': { + color: '#ffffff', + }, + 'ub-d-d': { + backgroundColor: '#f7f7f7', + }, + 'ub-p-d': { + backgroundColor: '#63acfc', + borderColor: '#4f8aca', + }, + 'ub-w-d': { + backgroundColor: '#ec8b89', + borderColor: '#bd6f6e', + }, + 'ub-d-t-d': { + color: '#cccccc', + }, + 'ub-p-t-d': { + color: 'rgba(255,255,255,0.6)', + }, + 'ub-w-t-d': { + color: 'rgba(255,255,255,0.6)', + }, + 'ub-d-plain': { + borderColor: '#353535', + backgroundColor: 'rgba(0,0,0,0)', + }, + 'ub-p-plain': { + borderColor: '#007aff', + backgroundColor: 'rgba(0,0,0,0)', + }, + 'ub-w-plain': { + borderColor: '#e64340', + backgroundColor: 'rgba(0,0,0,0)', + }, + 'ub-d-t-plain': { + color: '#353535', + }, + 'ub-p-t-plain': { + color: '#007aff', + }, + 'ub-w-t-plain': { + color: '#e64340', + }, + 'ub-d-d-plain': { + borderColor: '#c6c6c6', + backgroundColor: 'rgba(0,0,0,0)', + }, + 'ub-p-d-plain': { + borderColor: '#c6c6c6', + backgroundColor: 'rgba(0,0,0,0)', + }, + 'ub-w-d-plain': { + borderColor: '#c6c6c6', + backgroundColor: 'rgba(0,0,0,0)', + }, + 'ub-d-t-d-plain': { + color: 'rgba(0,0,0,0.2)', + }, + 'ub-p-t-d-plain': { + color: 'rgba(0,0,0,0.2)', + }, + 'ub-w-t-d-plain': { + color: 'rgba(0,0,0,0.2)', + }, + 'ub-mini': { + lineHeight: '30', + fontSize: '13', + paddingTop: 0, + paddingRight: '17.5', + paddingBottom: 0, + paddingLeft: '17.5', + }, + 'ub-loading': { + width: '18', + height: '18', + marginRight: '10', + }, + 'ub-d-loading': { + color: 'rgba(255,255,255,0.6)', + backgroundColor: 'rgba(0,0,0,0)', + }, + 'ub-p-loading': { + color: 'rgba(255,255,255,0.6)', + backgroundColor: 'rgba(0,0,0,0)', + }, + 'ub-w-loading': { + color: 'rgba(255,255,255,0.6)', + backgroundColor: 'rgba(0,0,0,0)', + }, + 'ub-d-loading-plain': { + color: '#353535', + }, + 'ub-p-loading-plain': { + color: '#007aff', + backgroundColor: '#0062cc', + }, + 'ub-w-loading-plain': { + color: '#e64340', + backgroundColor: 'rgba(0,0,0,0)', + }, + 'ub-d-hover': { + opacity: 0.8, + backgroundColor: '#dedede', + }, + 'ub-p-hover': { + opacity: 0.8, + backgroundColor: '#0062cc', + }, + 'ub-w-hover': { + opacity: 0.8, + backgroundColor: '#ce3c39', + }, + 'ub-d-t-hover': { + color: 'rgba(0,0,0,0.6)', + }, + 'ub-p-t-hover': { + color: 'rgba(255,255,255,0.6)', + }, + 'ub-w-t-hover': { + color: 'rgba(255,255,255,0.6)', + }, + 'ub-d-hover-plain': { + color: 'rgba(53,53,53,0.6)', + borderColor: 'rgba(53,53,53,0.6)', + backgroundColor: 'rgba(0,0,0,0)', + }, + 'ub-p-hover-plain': { + color: 'rgba(26,173,25,0.6)', + borderColor: 'rgba(0,122,255,0.6)', + backgroundColor: 'rgba(0,0,0,0)', + }, + 'ub-w-hover-plain': { + color: 'rgba(230,67,64,0.6)', + borderColor: 'rgba(230,67,64,0.6)', + backgroundColor: 'rgba(0,0,0,0)', + }, + }, +] +const TYPES = { + default: 'd', + primary: 'p', + warn: 'w', +} + +export default defineComponent({ + name: 'Button', + props: extend(buttonProps, { + type: { + type: String, + default: 'default', + }, + size: { + type: String, + default: 'default', + }, + }), + styles: buttonStyle, + setup(props, { slots, attrs }) { + const type = props.type as keyof typeof TYPES + const rootRef = ref(null) + const onClick = (e: Event, isLabelClick?: boolean) => { + if (props.disabled) { + return + } + if (isLabelClick) { + rootRef.value!.click() + } + /* const formType = props.formType + if (formType) { + if (!uniForm) { + return + } + if (formType === 'submit') { + uniForm.submit(e) + } else if (formType === 'reset') { + uniForm.reset(e) + } + } */ + } + + const _getClass = (t: string) => { + let cl = 'ub-' + TYPES[type] + t + props.disabled && (cl += '-d') + props.plain && (cl += '-plain') + props.size === 'mini' && t === '-t' && (cl += ' ub-mini') + return cl + } + const _getHoverClass = (t: string) => { + if (props.disabled) { + return '' + } + let cl = 'ub-' + TYPES[type] + t + '-hover' + props.plain && (cl += '-plain') + return cl + } + + const uniLabel = inject( + uniLabelKey, + false as unknown as UniLabelCtx + ) + if (uniLabel) { + uniLabel.addHandler(onClick) + onBeforeUnmount(() => { + uniLabel.removeHandler(onClick) + }) + } + useListeners(props, { 'label-click': onClick }) + + const wrapSlots = () => { + if (!slots.default) return [] + const _slots = slots.default() + return _slots.map((slot) => { + if (slot.type === Text) { + return {slot} + } + return slot + }) + } + + return () => { + return ( +
+ {props.loading ? ( + + ) : null} + {...wrapSlots()} +
+ ) + } + }, +}) diff --git a/packages/uni-components/src/nvue/components.ts b/packages/uni-components/src/nvue/components.ts index a100dfa8e..01abb7adc 100644 --- a/packages/uni-components/src/nvue/components.ts +++ b/packages/uni-components/src/nvue/components.ts @@ -1,4 +1,12 @@ import Navigator from './navigator' +import Label from './label' +import Button from './button' +import MovableArea from './movable-area' +import MovableView from './movable-view' export default { Navigator, + /* Label, + Button, + MovableArea, + MovableView */ } diff --git a/packages/uni-components/src/nvue/helpers/common.ts b/packages/uni-components/src/nvue/helpers/common.ts new file mode 100644 index 000000000..9e84500cd --- /dev/null +++ b/packages/uni-components/src/nvue/helpers/common.ts @@ -0,0 +1,32 @@ +export function cached(fn: Function) { + const cache = Object.create(null) + return function cachedFn(str: string) { + const hit = cache[str] + return hit || (cache[str] = fn(str)) + } +} + +export const parseStyleText = cached(function (cssText: string) { + const res: Record = {} + const listDelimiter = /;(?![^(]*\))/g + const propertyDelimiter = /:(.+)/ + cssText.split(listDelimiter).forEach(function (item) { + if (item) { + const tmp = item.split(propertyDelimiter) + tmp.length > 1 && (res[tmp[0].trim()] = tmp[1].trim()) + } + }) + return res +}) + +export const firstLetterToLowerCase = cached((str: string) => { + return str.charAt(0).toLowerCase() + str.slice(1) +}) + +export function isDef(v: any) { + return typeof v !== 'undefined' +} + +export function isFn(fn: any) { + return typeof fn === 'function' +} diff --git a/packages/uni-components/src/nvue/helpers/dom.ts b/packages/uni-components/src/nvue/helpers/dom.ts new file mode 100644 index 000000000..61ec51578 --- /dev/null +++ b/packages/uni-components/src/nvue/helpers/dom.ts @@ -0,0 +1,9 @@ +export type Size = { width: number; height: number; top: number; left: number } +export const getComponentSize = (el: HTMLElement) => { + const dom = weex.requireModule('dom') + return new Promise((resolve) => { + dom.getComponentRect(el, ({ size }: { size: Size }) => { + resolve(size) + }) + }) +} diff --git a/packages/uni-components/src/nvue/helpers/index.ts b/packages/uni-components/src/nvue/helpers/index.ts new file mode 100644 index 000000000..725c498d6 --- /dev/null +++ b/packages/uni-components/src/nvue/helpers/index.ts @@ -0,0 +1,2 @@ +export * from './common' +export * from './dom' diff --git a/packages/uni-components/src/nvue/label/index.tsx b/packages/uni-components/src/nvue/label/index.tsx new file mode 100644 index 000000000..8cbce429e --- /dev/null +++ b/packages/uni-components/src/nvue/label/index.tsx @@ -0,0 +1,45 @@ +import { defineComponent } from 'vue' +import { useCurrentPageId } from '@dcloudio/uni-core' +import { props, useProvideLabel } from '../../components/label' + +export { UniLabelCtx, uniLabelKey } from '../../components/label' + +type LabelTarget = HTMLElement & { + attr: { dataUncType: string } +} + +export default defineComponent({ + name: 'Label', + props, + styles: [], + setup(props, { slots }) { + const pageId = useCurrentPageId() + const handlers = useProvideLabel() + + const _onClick = ($event: Event) => { + const EventTarget = $event.target as LabelTarget + const dataType = EventTarget.attr.dataUncType || '' + let stopPropagation = /^uni-(checkbox|radio|switch)-/.test(dataType) + if (!stopPropagation) { + stopPropagation = /^uni-(checkbox|radio|switch|button)$/i.test(dataType) + } + if (stopPropagation) { + return + } + + if (props.for) { + UniViewJSBridge.emit( + `uni-label-click-${pageId}-${props.for}`, + $event, + true + ) + } else { + handlers.length && handlers[0]($event, true) + } + } + + return () => ( +
{slots.default && slots.default()}
+ ) + }, +}) diff --git a/packages/uni-components/src/nvue/movable-area/index.tsx b/packages/uni-components/src/nvue/movable-area/index.tsx new file mode 100644 index 000000000..b041c3a6b --- /dev/null +++ b/packages/uni-components/src/nvue/movable-area/index.tsx @@ -0,0 +1,129 @@ +import { defineComponent, Ref, ref, onMounted, provide } from 'vue' +import { props } from '../../components/movable-area' +import { flatVNode } from '../../helpers/flatVNode' +import { TouchtrackEvent } from '../movable-view/useTouchtrack' +import { getComponentSize } from '../helpers' +export interface MovableViewContext { + setParent: Function +} +export type AddMovableViewContext = (context: MovableViewContext) => void +export type RemoveMovableViewContext = (context: MovableViewContext) => void + +interface TouchMovableViewContext { + touchstart: (e: TouchtrackEvent) => void + touchmove: (e: TouchtrackEvent) => void + touchend: (e: TouchtrackEvent) => void +} +export type SetTouchMovableViewContext = ( + context: TouchMovableViewContext | null +) => void + +type RootRef = Ref +export interface parentSize { + width: Ref + height: Ref + top: Ref + left: Ref +} + +export default defineComponent({ + name: 'MovableArea', + props, + styles: [ + { + 'uni-movable-area': { + width: '10px', + height: '10px', + }, + }, + ], + setup(props, { slots }) { + const width = ref(0) + const height = ref(0) + const top = ref(0) + const left = ref(0) + const _isMounted = ref(false) + const rootRef: RootRef = ref(null) + const originMovableViewContexts: MovableViewContext[] = [] + let touchMovableView: null | TouchMovableViewContext = null + + const setTouchMovableViewContext: SetTouchMovableViewContext = ( + movableview + ) => { + touchMovableView = movableview + } + + const _getWH = () => { + return getComponentSize(rootRef.value!).then( + ({ width: _width, height: _height, top: _top, left: _left }) => { + width.value = _width + height.value = _height + top.value = _top + left.value = _left + } + ) + } + + const _resize = () => { + _getWH().then(() => { + originMovableViewContexts.forEach(function (item) { + item.setParent() + }) + }) + } + + onMounted(() => { + // 由于weex在mounted后渲染是异步的不能确保元素渲染完成,需要延迟执行 + setTimeout(() => { + _isMounted.value = true + _resize() + }, 200) + }) + + const listeners = { + onPanstart(e: TouchtrackEvent) { + touchMovableView && touchMovableView.touchstart(e) + }, + onPanmove(e: TouchtrackEvent) { + e.stopPropagation() + touchMovableView && touchMovableView.touchmove(e) + }, + onPanend(e: TouchtrackEvent) { + touchMovableView && touchMovableView.touchend(e) + }, + } + const addMovableViewContext: AddMovableViewContext = ( + movableViewContext + ) => { + originMovableViewContexts.push(movableViewContext) + } + const removeMovableViewContext: RemoveMovableViewContext = ( + movableViewContext + ) => { + const index = originMovableViewContexts.indexOf(movableViewContext) + if (index >= 0) { + originMovableViewContexts.splice(index, 1) + } + } + provide('_isMounted', _isMounted) + provide('parentSize', { + width: width, + height: height, + top: top, + left: left, + }) + provide('addMovableViewContext', addMovableViewContext) + provide('removeMovableViewContext', removeMovableViewContext) + provide('setTouchMovableViewContext', setTouchMovableViewContext) + + return () => { + const defaultSlots = slots.default && slots.default() + const movableViewItems = flatVNode(defaultSlots) + return ( +
+ {movableViewItems} +
+ ) + } + }, +}) diff --git a/packages/uni-components/src/nvue/movable-view/index.tsx b/packages/uni-components/src/nvue/movable-view/index.tsx new file mode 100644 index 000000000..44e97f0f6 --- /dev/null +++ b/packages/uni-components/src/nvue/movable-view/index.tsx @@ -0,0 +1,730 @@ +import { + defineComponent, + onMounted, + onUnmounted, + ref, + Ref, + computed, + inject, + watch, +} from 'vue' +import { + useTouchtrack, + touchstart, + touchmove, + touchend, + TouchtrackEvent, +} from './useTouchtrack' +import { + CustomEventTrigger, + EmitEvent, + useCustomEvent, +} from '../../helpers/useNvueEvent' +import { + MovableViewContext, + AddMovableViewContext, + RemoveMovableViewContext, + SetTouchMovableViewContext, + parentSize, +} from '../movable-area' +import { + Decline, + Friction, + STD, + props, + Props, + FrictionCallback, + Record, + v, +} from '../../components/movable-view' +import { getComponentSize } from '../helpers' + +function g( + friction: Friction | STD, + execute: FrictionCallback, + endCallback: FrictionCallback +) { + let record: Record = { + id: 0, + cancelled: false, + } + let cancel = function (record: Record) { + if (record && record.id) { + cancelAnimationFrame(record.id) + } + if (record) { + record.cancelled = true + } + } + function fn( + record: Record, + friction: Friction | STD, + execute: FrictionCallback, + endCallback: FrictionCallback + ) { + if (!record || !record.cancelled) { + execute(friction) + let isDone = friction.done() + if (!isDone) { + if (!record.cancelled) { + record.id = requestAnimationFrame( + fn.bind(null, record, friction, execute, endCallback) + ) + } + } + if (isDone && endCallback) { + endCallback(friction) + } + } + } + fn(record, friction, execute, endCallback) + return { + cancel: cancel.bind(null, record), + model: friction, + } +} +let requesting = false +function _requestAnimationFrame(e: Function) { + if (!requesting) { + requesting = true + requestAnimationFrame(function () { + e() + requesting = false + }) + } +} +function requestAnimationFrame(callback: Function) { + return setTimeout(callback, 16) +} +function cancelAnimationFrame(id: number) { + clearTimeout(id) +} + +type ReturnType_g = ReturnType | null +type ScaleOffset = { + x: number + y: number +} +type MoveDirection = 'htouchmove' | 'vtouchmove' +type Size = { width: number; height: number; top: number; left: number } +type RootRef = Ref + +const animation = weex.requireModule('animation') + +export default defineComponent({ + name: 'MovableView', + props, + emits: ['change', 'scale'], + styles: [ + { + 'uni-movable-view': { + position: 'absolute', + top: '0px', + left: '0px', + width: '10px', + height: '10px', + }, + }, + ], + setup(props, { emit, slots }) { + const rootRef: RootRef = ref(null) + const trigger = useCustomEvent>(rootRef, emit) + const setTouchMovableViewContext: SetTouchMovableViewContext = inject( + 'setTouchMovableViewContext', + () => {} + ) + useMovableViewState(props, trigger, rootRef) + + const touchStart = () => { + setTouchMovableViewContext({ + touchstart, + touchmove, + touchend, + }) + } + + return () => { + const attrs = { + preventGesture: true, + } + return ( +
+ {slots.default && slots.default()} +
+ ) + } + }, +}) + +function useMovableViewState( + props: Props, + trigger: CustomEventTrigger, + rootRef: RootRef +) { + const _isMounted: Ref = inject('_isMounted', ref(false)) + const parentSize: parentSize = inject('parentSize', { + width: ref(0), + height: ref(0), + top: ref(0), + left: ref(0), + }) + const addMovableViewContext: AddMovableViewContext = inject( + 'addMovableViewContext', + () => {} + ) + const removeMovableViewContext: RemoveMovableViewContext = inject( + 'removeMovableViewContext', + () => {} + ) + function _getPx(val: string | number) { + // if (/\d+[ur]px$/i.test(val)) { + // return uni.upx2px(parseFloat(val)) + // } + return Number(val) || 0 + } + function _getScaleNumber(val: number) { + val = Number(val) + return isNaN(val) ? 1 : val + } + const xSync = ref(_getPx(props.x)) + const ySync = ref(_getPx(props.y)) + const scaleValueSync = ref(_getScaleNumber(Number(props.scaleValue))) + const width = ref(0) + const height = ref(0) + const minX = ref(0) + const minY = ref(0) + const maxX = ref(0) + const maxY = ref(0) + + let _SFA: ReturnType_g = null + let _FA: ReturnType_g = null + const _offset: ScaleOffset = { + x: 0, + y: 0, + } + const _scaleOffset: ScaleOffset = { + x: 0, + y: 0, + } + let _scale = 1 + let _oldScale = 1 + let _translateX = 0 + let _translateY = 0 + let _isScaling = false + let _isTouching = false + let __baseX: number + let __baseY: number + let _checkCanMove: boolean | null = null + let _firstMoveDirection: MoveDirection | null = null + let _rect: Size = { + top: 0, + left: 0, + width: 0, + height: 0, + } + const _declineX = new Decline() + const _declineY = new Decline() + const __touchInfo = { + historyX: [0, 0], + historyY: [0, 0], + historyT: [0, 0], + } + + const dampingNumber = computed(() => { + let val = Number(props.damping) + return isNaN(val) ? 20 : val + }) + const frictionNumber = computed(() => { + let val = Number(props.friction) + return isNaN(val) || val <= 0 ? 2 : val + }) + const scaleMinNumber = computed(() => { + let val = Number(props.scaleMin) + return isNaN(val) ? 0.5 : val + }) + const scaleMaxNumber = computed(() => { + let val = Number(props.scaleMax) + return isNaN(val) ? 10 : val + }) + const xMove = computed( + () => props.direction === 'all' || props.direction === 'horizontal' + ) + const yMove = computed( + () => props.direction === 'all' || props.direction === 'vertical' + ) + + const _STD = new STD( + 1, + (9 * Math.pow(dampingNumber.value, 2)) / 40, + dampingNumber.value + ) + const _friction = new Friction(1, frictionNumber.value) + + watch( + () => props.x, + (val) => { + xSync.value = _getPx(val) + } + ) + watch( + () => props.y, + (val) => { + ySync.value = _getPx(val) + } + ) + watch( + () => props.scaleValue, + (val) => { + scaleValueSync.value = _getScaleNumber(Number(val)) + } + ) + watch(xSync, _setX) + watch(ySync, _setY) + watch(scaleValueSync, _setScaleValue) + watch(scaleMinNumber, _setScaleMinOrMax) + watch(scaleMaxNumber, _setScaleMinOrMax) + + function FAandSFACancel() { + if (_FA) { + _FA.cancel() + } + if (_SFA) { + _SFA.cancel() + } + } + + function _setX(val: number) { + if (xMove.value) { + if (val + _scaleOffset.x === _translateX) { + return _translateX + } else { + if (_SFA) { + _SFA.cancel() + } + _animationTo(val + _scaleOffset.x, ySync.value + _scaleOffset.y, _scale) + } + } + return val + } + function _setY(val: number) { + if (yMove.value) { + if (val + _scaleOffset.y === _translateY) { + return _translateY + } else { + if (_SFA) { + _SFA.cancel() + } + _animationTo(xSync.value + _scaleOffset.x, val + _scaleOffset.y, _scale) + } + } + return val + } + function _setScaleMinOrMax() { + if (!props.scale) { + return false + } + _updateScale(_scale, true) + _updateOldScale(_scale) + } + function _setScaleValue(scale: number) { + if (!props.scale) { + return false + } + scale = _adjustScale(scale) + _updateScale(scale, true) + _updateOldScale(scale) + return scale + } + function __handleTouchStart() { + if (!_isScaling) { + if (!props.disabled) { + FAandSFACancel() + __touchInfo.historyX = [0, 0] + __touchInfo.historyY = [0, 0] + __touchInfo.historyT = [0, 0] + if (xMove.value) { + __baseX = _translateX + } + if (yMove.value) { + __baseY = _translateY + } + _checkCanMove = null + _firstMoveDirection = null + _isTouching = true + } + } + } + function __handleTouchMove(event: TouchtrackEvent) { + if (!_isScaling && !props.disabled && _isTouching) { + let x = _translateX + let y = _translateY + if (_firstMoveDirection === null) { + _firstMoveDirection = + Math.abs(event.detail.dx / event.detail.dy) > 1 + ? 'htouchmove' + : 'vtouchmove' + } + if (xMove.value) { + x = event.detail.dx + __baseX + __touchInfo.historyX.shift() + __touchInfo.historyX.push(x) + if (!yMove.value && _checkCanMove === null) { + _checkCanMove = Math.abs(event.detail.dx / event.detail.dy) < 1 + } + } + if (yMove.value) { + y = event.detail.dy + __baseY + __touchInfo.historyY.shift() + __touchInfo.historyY.push(y) + if (!xMove.value && _checkCanMove === null) { + _checkCanMove = Math.abs(event.detail.dy / event.detail.dx) < 1 + } + } + __touchInfo.historyT.shift() + __touchInfo.historyT.push(event.detail.timeStamp) + + if (!_checkCanMove) { + // event.preventDefault() + let source = 'touch' + if (x < minX.value) { + if (props.outOfBounds) { + source = 'touch-out-of-bounds' + x = minX.value - _declineX.x(minX.value - x) + } else { + x = minX.value + } + } else if (x > maxX.value) { + if (props.outOfBounds) { + source = 'touch-out-of-bounds' + x = maxX.value + _declineX.x(x - maxX.value) + } else { + x = maxX.value + } + } + if (y < minY.value) { + if (props.outOfBounds) { + source = 'touch-out-of-bounds' + y = minY.value - _declineY.x(minY.value - y) + } else { + y = minY.value + } + } else { + if (y > maxY.value) { + if (props.outOfBounds) { + source = 'touch-out-of-bounds' + y = maxY.value + _declineY.x(y - maxY.value) + } else { + y = maxY.value + } + } + } + _requestAnimationFrame(function () { + _setTransform(x, y, _scale, source) + }) + } + } + } + function __handleTouchEnd() { + if (!_isScaling && !props.disabled && _isTouching) { + _isTouching = false + if (!_checkCanMove && !_revise('out-of-bounds') && props.inertia) { + const xv = + (1000 * (__touchInfo.historyX[1] - __touchInfo.historyX[0])) / + (__touchInfo.historyT[1] - __touchInfo.historyT[0]) + const yv = + (1000 * (__touchInfo.historyY[1] - __touchInfo.historyY[0])) / + (__touchInfo.historyT[1] - __touchInfo.historyT[0]) + _friction.setV(xv, yv) + _friction.setS(_translateX, _translateY) + const x0 = _friction.delta().x + const y0 = _friction.delta().y + let x = x0 + _translateX + let y = y0 + _translateY + if (x < minX.value) { + x = minX.value + y = _translateY + ((minX.value - _translateX) * y0) / x0 + } else { + if (x > maxX.value) { + x = maxX.value + y = _translateY + ((maxX.value - _translateX) * y0) / x0 + } + } + if (y < minY.value) { + y = minY.value + x = _translateX + ((minY.value - _translateY) * x0) / y0 + } else { + if (y > maxY.value) { + y = maxY.value + x = _translateX + ((maxY.value - _translateY) * x0) / y0 + } + } + _friction.setEnd(x, y) + _FA = g( + _friction, + function () { + let t = _friction.s() + let x = t.x + let y = t.y + _setTransform(x, y, _scale, 'friction') + }, + function () { + _FA!.cancel() + } + ) + } + } + } + function _getLimitXY(x: number, y: number) { + let outOfBounds = false + if (x > maxX.value) { + x = maxX.value + outOfBounds = true + } else { + if (x < minX.value) { + x = minX.value + outOfBounds = true + } + } + if (y > maxY.value) { + y = maxY.value + outOfBounds = true + } else { + if (y < minY.value) { + y = minY.value + outOfBounds = true + } + } + return { + x, + y, + outOfBounds, + } + } + function _updateOffset() { + _offset.x = _rect.left - parentSize.left.value + _offset.y = _rect.top - parentSize.top.value + } + function _updateWH(scale: number) { + scale = scale || _scale + scale = _adjustScale(scale) + height.value = _rect.height / _scale + width.value = _rect.width / _scale + let _height = height.value * scale + let _width = width.value * scale + _scaleOffset.x = (_width - width.value) / 2 + _scaleOffset.y = (_height - height.value) / 2 + } + function _updateBoundary() { + let x = 0 - _offset.x + _scaleOffset.x + let _width = + parentSize.width.value - width.value - _offset.x - _scaleOffset.x + minX.value = Math.min(x, _width) + maxX.value = Math.max(x, _width) + let y = 0 - _offset.y + _scaleOffset.y + let _height = + parentSize.height.value - height.value - _offset.y - _scaleOffset.y + minY.value = Math.min(y, _height) + maxY.value = Math.max(y, _height) + } + function _beginScale() { + _isScaling = true + } + function _updateScale(scale: number, animat?: boolean) { + if (props.scale) { + scale = _adjustScale(scale) + _updateWH(scale) + _updateBoundary() + const limitXY = _getLimitXY(_translateX, _translateY) + const x = limitXY.x + const y = limitXY.y + if (animat) { + _animationTo(x, y, scale, '', true, true) + } else { + _requestAnimationFrame(function () { + _setTransform(x, y, scale, '', true, true) + }) + } + } + } + function _updateOldScale(scale: number) { + _oldScale = scale + } + function _adjustScale(scale: number) { + scale = Math.max(0.5, scaleMinNumber.value, scale) + scale = Math.min(10, scaleMaxNumber.value, scale) + return scale + } + function _animationTo( + x: number, + y: number, + scale: number, + source?: number | string, + r?: boolean, + o?: boolean + ) { + FAandSFACancel() + if (!xMove.value) { + x = _translateX + } + if (!yMove.value) { + y = _translateY + } + if (!props.scale) { + scale = _scale + } + let limitXY = _getLimitXY(x, y) + x = limitXY.x + y = limitXY.y + if (!props.animation) { + _setTransform(x, y, scale, source, r, o) + return + } + _STD._springX._solution = null + _STD._springY._solution = null + _STD._springScale._solution = null + _STD._springX._endPosition = _translateX + _STD._springY._endPosition = _translateY + _STD._springScale._endPosition = _scale + _STD.setEnd(x, y, scale, 1) + _SFA = g( + _STD, + function () { + let data = _STD.x() + let x = data.x + let y = data.y + let scale = data.scale + _setTransform(x, y, scale, source, r, o) + }, + function () { + _SFA!.cancel() + } + ) + } + function _revise(source: number | string) { + let limitXY = _getLimitXY(_translateX, _translateY) + let x = limitXY.x + let y = limitXY.y + let outOfBounds = limitXY.outOfBounds + if (outOfBounds) { + _animationTo(x, y, _scale, source) + } + return outOfBounds + } + function _setTransform( + x: number, + y: number, + scale: number, + source: string | number = '', + r?: boolean, + o?: boolean + ) { + if (!(x !== null && x.toString() !== 'NaN' && typeof x === 'number')) { + x = _translateX || 0 + } + if (!(y !== null && y.toString() !== 'NaN' && typeof y === 'number')) { + y = _translateY || 0 + } + x = Number(x.toFixed(1)) + y = Number(y.toFixed(1)) + scale = Number(scale.toFixed(1)) + if (!(_translateX === x && _translateY === y)) { + if (!r) { + trigger('change', { + x: v(x, _scaleOffset.x), + y: v(y, _scaleOffset.y), + source: source, + }) + } + } + if (!props.scale) { + scale = _scale + } + scale = _adjustScale(scale) + scale = +scale.toFixed(3) + if (o && scale !== _scale) { + trigger('scale', { + x: x, + y: y, + scale: scale, + }) + } + const transform = `translate(${x}px, ${y}px) scale(${scale})` + animation.transition(rootRef.value, { + styles: { + transform, + }, + duration: 0, + delay: 0, + }) + _translateX = x + _translateY = y + _scale = scale + } + + function _updateRect() { + return getComponentSize(rootRef.value!).then((rect: Size) => { + _rect = rect + }) + } + function setParent() { + if (!_isMounted.value) { + return + } + FAandSFACancel() + let scale = props.scale ? scaleValueSync.value : 1 + _updateOffset() + _updateWH(scale) + _updateBoundary() + _translateX = xSync.value + _scaleOffset.x + _translateY = ySync.value + _scaleOffset.y + let limitXY = _getLimitXY(_translateX, _translateY) + let x = limitXY.x + let y = limitXY.y + _setTransform(x, y, scale, '', true) + _updateOldScale(scale) + } + + onMounted(() => { + useTouchtrack((event) => { + switch (event.detail.state) { + case 'start': + __handleTouchStart() + break + case 'move': + __handleTouchMove(event) + break + case 'end': + __handleTouchEnd() + } + }) + setTimeout(() => { + _updateRect().then(() => { + setParent() + }) + }, 100) + _friction.reconfigure(1, frictionNumber.value) + _STD.reconfigure( + 1, + (9 * Math.pow(dampingNumber.value, 2)) / 40, + dampingNumber.value + ) + + const context: MovableViewContext = { + setParent, + } + addMovableViewContext(context) + + onUnmounted(() => { + removeMovableViewContext(context) + }) + }) + onUnmounted(() => { + FAandSFACancel() + }) +} diff --git a/packages/uni-components/src/nvue/movable-view/useTouchtrack.ts b/packages/uni-components/src/nvue/movable-view/useTouchtrack.ts new file mode 100644 index 000000000..14320af38 --- /dev/null +++ b/packages/uni-components/src/nvue/movable-view/useTouchtrack.ts @@ -0,0 +1,118 @@ +type State = 'start' | 'move' | 'end' | 'cancel' +type TouchOrMouseEvent = TouchEvent | MouseEvent +type Detail = { + state: State + x: number + y: number + dx: number + dy: number + ddx: number + ddy: number + timeStamp: Event['timeStamp'] +} +type CallbackType = 'touchstart' | 'touchmove' | 'touchend' +export interface TouchtrackEvent { + target: Event['target'] + currentTarget: Event['currentTarget'] + preventDefault?: Event['preventDefault'] + stopPropagation: Event['stopPropagation'] + touches: TouchEvent['touches'] + changedTouches: TouchEvent['changedTouches'] + detail: Detail +} + +const __event: Record = {} +function callback(type: CallbackType, $event: TouchtrackEvent) { + if (__event[type]) { + __event[type]($event) + } +} +function addListener(type: CallbackType, callback: Function) { + __event[type] = function ($event: TouchtrackEvent) { + if (typeof callback === 'function') { + $event.touches = $event.changedTouches + if (callback($event) === false) { + $event.stopPropagation() + } + } + } +} +export function touchstart($event: TouchtrackEvent) { + callback('touchstart', $event) +} +export function touchmove($event: TouchtrackEvent) { + callback('touchmove', $event) +} +export function touchend($event: TouchtrackEvent) { + callback('touchend', $event) +} +export function useTouchtrack( + method: (event: TouchtrackEvent) => boolean | void +) { + let x0 = 0 + let y0 = 0 + let x1 = 0 + let y1 = 0 + const fn = function ( + $event: TouchOrMouseEvent, + state: State, + x: number, + y: number + ) { + if ( + method({ + target: $event.target, + currentTarget: $event.currentTarget, + stopPropagation: $event.stopPropagation.bind($event), + touches: ($event as TouchEvent).touches, + changedTouches: ($event as TouchEvent).changedTouches, + detail: { + state, + x, + y, + dx: x - x0, + dy: y - y0, + ddx: x - x1, + ddy: y - y1, + timeStamp: $event.timeStamp || Date.now(), + }, + }) === false + ) { + return false + } + } + + let $eventOld: TouchOrMouseEvent | null = null + addListener('touchstart', function ($event: TouchEvent) { + if (!$eventOld) { + $eventOld = $event + x0 = x1 = $event.touches[0].pageX + y0 = y1 = $event.touches[0].pageY + return fn($event, 'start', x0, y0) + } + }) + addListener('touchmove', function ($event: TouchEvent) { + if ($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 + } + }) + addListener('touchend', function ($event: TouchEvent) { + if ($eventOld) { + $eventOld = null + return fn( + $event, + 'end', + $event.changedTouches[0].pageX, + $event.changedTouches[0].pageY + ) + } + }) +} diff --git a/packages/uni-components/src/nvue/picker-view-column/index.tsx b/packages/uni-components/src/nvue/picker-view-column/index.tsx new file mode 100644 index 000000000..3901eb62e --- /dev/null +++ b/packages/uni-components/src/nvue/picker-view-column/index.tsx @@ -0,0 +1,262 @@ +import { + defineComponent, + inject, + ref, + Ref, + watch, + VNode, + computed, + getCurrentInstance, + onMounted, +} from 'vue' +import { extend } from '@vue/shared' +import { Props, GetPickerViewColumn } from '../picker-view' +import { parseStyleText, getComponentSize } from '../helpers' + +type ScrollOptions = { + showScrollbar: boolean + scrollToBegin: boolean + decelerationRate: number + scrollY: boolean + scrollTop?: number +} + +const dom = weex.requireModule('dom') +const isAndroid = weex.config.env.platform.toLowerCase() === 'android' +function getStyle(val: string) { + return extend({}, typeof val === 'string' ? parseStyleText(val) : val) +} + +export default defineComponent({ + name: 'PickerViewColumn', + props: { + length: { + type: [Number, String], + default: 0, + }, + }, + data: () => ({ + _isMounted: false, + }), + setup(props, { slots }) { + const instance = getCurrentInstance()! + + const rootRef: Ref = ref(null) + const contentRef: Ref = ref(null) + const scrollViewItemRef: Ref = ref(null) + const indicatorRef: Ref = ref(null) + + const pickerViewProps = inject('pickerViewProps')! + const getPickerViewColumn = inject( + 'getPickerViewColumn', + () => computed({ set: () => {}, get: () => 0 }) + ) + + const current = getPickerViewColumn(instance) + const indicatorStyle = computed(() => + getStyle(pickerViewProps.indicatorStyle) + ) + const maskStyle = computed(() => getStyle(pickerViewProps.maskStyle)) + let indicatorHeight = getHeight(indicatorStyle) + let pickerViewHeight = parseFloat(pickerViewProps.height as string) + + watch( + () => props.length, + () => { + setTimeout(() => { + setCurrent(current.value, true, true) + }, 150) + } + ) + + let scrollToElementTime: number + const setCurrent = (_current: number, animated = true, force: Boolean) => { + if (current.value === _current && !force) { + return + } + dom.scrollToElement(contentRef.value, { + offset: _current * indicatorHeight, + animated, + }) + current.value = _current + if (animated) { + scrollToElementTime = Date.now() + } + } + const onScrollend = (event: { detail: Record }) => { + if (Date.now() - scrollToElementTime < 340) { + return + } + const y = event.detail.contentOffset.y + const _current = Math.round(y / indicatorHeight) + if (y % indicatorHeight) { + setCurrent(_current, true, true) + } else { + current.value = _current + } + } + const checkMounted = () => { + let height_: number + let indicatorHeight_: number + setTimeout(() => { + Promise.all([ + getComponentSize(rootRef.value!).then(({ height }) => { + height_ = pickerViewHeight = height + }), + isAndroid && props.length + ? getComponentSize(scrollViewItemRef.value!).then(({ height }) => { + indicatorHeight_ = indicatorHeight = + height / parseFloat(props.length as string) + }) + : getComponentSize(indicatorRef.value!).then(({ height }) => { + indicatorHeight_ = indicatorHeight = height + }), + ]).then(() => { + if (height_ && indicatorHeight_) { + // 初始化时iOS直接滚动经常出错 + setTimeout(() => { + setCurrent(current.value, false, true) + instance.data._isMounted = true + }, 50) + } else { + checkMounted() + } + }) + }, 50) + } + + onMounted(checkMounted) + + const createScrollViewChild = (item?: VNode[]) => { + if (!item) return null + return isAndroid ? ( +
+ {item} +
+ ) : ( + item + ) + } + + return () => { + const children = slots.default && slots.default() + let padding = (pickerViewHeight - indicatorHeight) / 2 + const maskPosition = `${pickerViewHeight - padding}px` + const scrollOptions: ScrollOptions = { + showScrollbar: false, + scrollToBegin: false, + decelerationRate: 0.3, + scrollY: true, + } + if (!isAndroid) { + scrollOptions.scrollTop = current.value * indicatorHeight + } + + return ( +
+ +
+ {createScrollViewChild(children)} +
+
+ + + + + +
+ ) + } + }, + styles: [ + { + 'uni-picker-view-column': { + flex: 1, + position: 'relative', + alignItems: 'stretch', + overflow: 'hidden', + }, + 'uni-picker-view-mask': { + position: 'absolute', + top: 0, + left: 0, + right: 0, + bottom: 0, + pointerEvents: 'none', + }, + 'uni-picker-view-mask-top': { + bottom: 0, + backgroundImage: + 'linear-gradient(to bottom,rgba(255, 255, 255, 0.95),rgba(255, 255, 255, 0.6))', + }, + 'uni-picker-view-mask-bottom': { + top: 0, + backgroundImage: + 'linear-gradient(to top,rgba(255, 255, 255, 0.95),rgba(255, 255, 255, 0.6))', + }, + 'uni-picker-view-group': { + position: 'absolute', + top: 0, + left: 0, + right: 0, + bottom: 0, + }, + 'uni-picker-view-content': { + paddingTop: 0, + paddingRight: 0, + paddingBottom: 0, + paddingLeft: 0, + }, + 'uni-picker-view-indicator': { + position: 'absolute', + left: 0, + right: 0, + top: 0, + height: '34px', + pointerEvents: 'none', + borderColor: '#e5e5e5', + borderTopWidth: '1px', + borderBottomWidth: '1px', + }, + }, + ], +}) + +function getHeight(style: Record) { + const height = style.height || style.lineHeight || '' + const res = height.match(/(-?[\d\.]+)px/) + let value = 0 + if (res) { + value = parseFloat(res[1]) + } + return value +} diff --git a/packages/uni-components/src/nvue/picker-view/index.tsx b/packages/uni-components/src/nvue/picker-view/index.tsx new file mode 100644 index 000000000..39cce5cf7 --- /dev/null +++ b/packages/uni-components/src/nvue/picker-view/index.tsx @@ -0,0 +1,120 @@ +import { + defineComponent, + computed, + watch, + ref, + Ref, + reactive, + VNode, + provide, + ExtractPropTypes, +} from 'vue' +import { extend } from '@vue/shared' +import { props, GetPickerViewColumn } from '../../components/picker-view' +import { flatVNode } from '../../helpers/flatVNode' +import { useCustomEvent, EmitEvent } from '../../helpers/useNvueEvent' + +export { Props, GetPickerViewColumn } +const pickerViewProps = extend({}, props, { + height: { + type: [Number, String], + default: 0, + }, +}) +type Props = ExtractPropTypes +export default defineComponent({ + name: 'PickerView', + props: pickerViewProps, + emits: ['change', 'update:value'], + setup(props, { slots, emit }) { + const rootRef: Ref = ref(null) + const state = useState(props) + const trigger = useCustomEvent>(rootRef, emit) + + let columnVNodes: VNode[] = [] + const getItemIndex = (vnode: VNode) => + Array.prototype.indexOf.call(columnVNodes, vnode.el) + const getPickerViewColumn: GetPickerViewColumn = (columnInstance) => { + return computed({ + get() { + const index = getItemIndex(columnInstance.vnode) + return state.value[index] || 0 + }, + set(current: number) { + if (!columnInstance.data._isMounted) return + const index = getItemIndex(columnInstance.vnode) + if (index < 0) { + return + } + const oldCurrent = state.value[index] + if (oldCurrent !== current) { + state.value[index] = current + // 避免外部直接对此值进行修改 + const value = state.value.map((val) => val) + emit('update:value', value) + trigger('change', { + value, + }) + } + }, + }) + } + + provide('getPickerViewColumn', getPickerViewColumn) + provide('pickerViewProps', props) + + return () => { + const defaultSlots = slots.default && slots.default() + columnVNodes = flatVNode(defaultSlots) + return ( +
+
{defaultSlots}
+
+ ) + } + }, + styles: [ + { + 'uni-picker-view': { + position: 'relative', + }, + 'uni-picker-view-wrapper': { + display: 'flex', + flexDirection: 'row', + position: 'absolute', + top: 0, + left: 0, + right: 0, + bottom: 0, + overflow: 'hidden', + }, + }, + ], +}) + +function useState(props: Props) { + const value: number[] = reactive([...props.value]) + const state = reactive({ + value, + }) + + watch( + () => props.value, + (val) => { + state.value.length = val.length + val.forEach((val, index) => { + if (val !== state.value[index]) { + state.value.splice(index, 1, val) + } + }) + } + ) + + return state +} diff --git a/packages/uni-components/src/vue/label/index.tsx b/packages/uni-components/src/vue/label/index.tsx index d187a6fad..63dddd5a3 100644 --- a/packages/uni-components/src/vue/label/index.tsx +++ b/packages/uni-components/src/vue/label/index.tsx @@ -1,14 +1,10 @@ -import { provide, computed } from 'vue' -import { PolySymbol, useCurrentPageId } from '@dcloudio/uni-core' +import { computed } from 'vue' +import { useCurrentPageId } from '@dcloudio/uni-core' import { withWebEvent } from '../../helpers/useEvent' import { defineBuiltInComponent } from '../../helpers/component' -export const uniLabelKey = PolySymbol(__DEV__ ? 'uniLabel' : 'ul') -const props = { - for: { - type: String, - default: '', - }, -} +import { props, useProvideLabel } from '../../components/label' + +export { UniLabelCtx, uniLabelKey } from '../../components/label' export default /*#__PURE__*/ defineBuiltInComponent({ name: 'Label', @@ -55,24 +51,3 @@ export default /*#__PURE__*/ defineBuiltInComponent({ ) }, }) - -export interface UniLabelCtx { - addHandler: (handler: UniLabelHandlerCtx) => void - removeHandler: (handler: UniLabelHandlerCtx) => void -} -type UniLabelHandlerCtx = ($event: Event, b: boolean) => void - -function useProvideLabel() { - const handlers: UniLabelHandlerCtx[] = [] - - provide(uniLabelKey, { - addHandler(handler: UniLabelHandlerCtx) { - handlers.push(handler) - }, - removeHandler(handler: UniLabelHandlerCtx) { - handlers.splice(handlers.indexOf(handler), 1) - }, - }) - - return handlers -} diff --git a/packages/uni-components/src/vue/movable-area/index.tsx b/packages/uni-components/src/vue/movable-area/index.tsx index 28283652b..5f9246dcf 100644 --- a/packages/uni-components/src/vue/movable-area/index.tsx +++ b/packages/uni-components/src/vue/movable-area/index.tsx @@ -1,13 +1,4 @@ -import { - ref, - ExtractPropTypes, - reactive, - Ref, - onMounted, - VNode, - markRaw, - provide, -} from 'vue' +import { ref, reactive, Ref, onMounted, VNode, markRaw, provide } from 'vue' import { defineBuiltInComponent } from '../../helpers/component' import { withWebEvent } from '../../helpers/useEvent' import { useAttrs } from '../../helpers/useAttrs' @@ -15,15 +6,8 @@ import { initScrollBounce, disableScrollBounce } from '../../helpers/scroll' import ResizeSensor from '../resize-sensor/index' import { flatVNode } from '../../helpers/flatVNode' import { useRebuild } from '../../helpers/useRebuild' +import { props, Props } from '../../components/movable-area' -const props = { - scaleArea: { - type: Boolean, - default: false, - }, -} - -type Props = ExtractPropTypes type _TouchEvent = '_onTouchstart' | '_onTouchmove' | '_onTouchend' export interface MovableViewContext { rootRef: Ref diff --git a/packages/uni-components/src/vue/movable-view/index.tsx b/packages/uni-components/src/vue/movable-view/index.tsx index 9a045241e..8acb32acf 100644 --- a/packages/uni-components/src/vue/movable-view/index.tsx +++ b/packages/uni-components/src/vue/movable-view/index.tsx @@ -1,13 +1,4 @@ -import { - ref, - ExtractPropTypes, - Ref, - onMounted, - inject, - computed, - watch, - onUnmounted, -} from 'vue' +import { ref, Ref, onMounted, inject, computed, watch, onUnmounted } from 'vue' import { defineBuiltInComponent } from '../../helpers/component' import { initScrollBounce, disableScrollBounce } from '../../helpers/scroll' import { useTouchtrack, TouchtrackEvent } from '../../helpers/useTouchtrack' @@ -22,64 +13,17 @@ import type { AddMovableViewContext, RemoveMovableViewContext, } from '../movable-area/index' -import { Decline, Friction, STD } from './utils' - -const props = { - direction: { - type: String, - default: 'none', - }, - inertia: { - type: [Boolean, String], - default: false, - }, - outOfBounds: { - type: [Boolean, String], - default: false, - }, - x: { - type: [Number, String], - default: 0, - }, - y: { - type: [Number, String], - default: 0, - }, - damping: { - type: [Number, String], - default: 20, - }, - friction: { - type: [Number, String], - default: 2, - }, - disabled: { - type: [Boolean, String], - default: false, - }, - scale: { - type: [Boolean, String], - default: false, - }, - scaleMin: { - type: [Number, String], - default: 0.5, - }, - scaleMax: { - type: [Number, String], - default: 10, - }, - scaleValue: { - type: [Number, String], - default: 1, - }, - animation: { - type: [Boolean, String], - default: true, - }, -} +import { + Decline, + Friction, + STD, + props, + Props, + FrictionCallback, + Record, + v, +} from '../../components/movable-view' -type Props = ExtractPropTypes type RootRef = Ref export default /*#__PURE__*/ defineBuiltInComponent({ @@ -127,14 +71,6 @@ function f(t: HTMLElement, n: HTMLElement): number { let i = t.offsetTop return t.offsetParent ? (i += f(t.offsetParent as HTMLElement, n)) : 0 } -function v(a: number, b: number) { - return +((1000 * a - 1000 * b) / 1000).toFixed(1) -} -type FrictionCallback = (friction: Friction | STD) => void -type Record = { - id: number - cancelled: boolean -} function g( friction: Friction | STD, execute: FrictionCallback, diff --git a/packages/uni-components/src/vue/picker-view/index.tsx b/packages/uni-components/src/vue/picker-view/index.tsx index e8ed43a34..525805b99 100644 --- a/packages/uni-components/src/vue/picker-view/index.tsx +++ b/packages/uni-components/src/vue/picker-view/index.tsx @@ -8,8 +8,6 @@ import { reactive, VNode, SetupContext, - PropType, - ComponentInternalInstance, onMounted, ComponentPublicInstance, nextTick, @@ -20,39 +18,9 @@ import { flatVNode } from '../../helpers/flatVNode' import { useRebuild } from '../../helpers/useRebuild' import ResizeSensor from '../resize-sensor/index' import { useCustomEvent } from '../../helpers/useEvent' - -const props = { - value: { - type: Array as PropType, - default() { - return [] - }, - validator: function (val: any) { - return ( - Array.isArray(val) && - val.filter((val) => typeof val === 'number').length === val.length - ) - }, - }, - indicatorStyle: { - type: String, - default: '', - }, - indicatorClass: { - type: String, - default: '', - }, - maskStyle: { - type: String, - default: '', - }, - maskClass: { - type: String, - default: '', - }, -} - -export type Props = Record +import { props } from '../../components/picker-view' +import type { Props, GetPickerViewColumn } from '../../components/picker-view' +export { Props, GetPickerViewColumn } export interface State { value: number[] height: number @@ -84,10 +52,6 @@ function useState(props: Props): State { return state } -export type GetPickerViewColumn = ( - columnInstance: ComponentInternalInstance -) => WritableComputedRef - export default /*#__PURE__*/ defineBuiltInComponent({ name: 'PickerView', props, diff --git a/packages/uni-components/vite.config.ts b/packages/uni-components/vite.config.ts index 20332819b..bfb9c81e7 100644 --- a/packages/uni-components/vite.config.ts +++ b/packages/uni-components/vite.config.ts @@ -11,6 +11,7 @@ export default defineConfig({ root: __dirname, define: { global: 'window', + __DEV__: `(process.env.NODE_ENV !== 'production')`, }, resolve: { alias: [ @@ -25,12 +26,21 @@ export default defineConfig({ lib: { name: 'components', entry: path.resolve(__dirname, 'src/nvue/components.ts'), - formats: ['es'], + formats: ['iife'], }, rollupOptions: { - external: ['uni', 'vue', 'weex', '@vue/shared', '@dcloudio/uni-shared'], + external: ['uni', 'vue', 'weex', '@vue/shared'], output: { + banner: + 'export function initComponents({uni,Vue,weex,plus,BroadcastChannel,UniViewJSBridge,VueShared}) {', + footer: 'return components\n}', entryFileNames: 'components.js', + globals: { + uni: 'uni', + vue: 'Vue', + weex: 'weex', + '@vue/shared': 'VueShared', + }, }, }, }, diff --git a/packages/uni-core/src/helpers/index.ts b/packages/uni-core/src/helpers/index.ts index a8ebdfeb7..e947c169b 100644 --- a/packages/uni-core/src/helpers/index.ts +++ b/packages/uni-core/src/helpers/index.ts @@ -6,3 +6,4 @@ export * from './hook' export * from './scroll' export * from './route' export * from './callbacks' +export * from './showPage' diff --git a/packages/uni-app-plus/src/helpers/page.ts b/packages/uni-core/src/helpers/showPage.ts similarity index 100% rename from packages/uni-app-plus/src/helpers/page.ts rename to packages/uni-core/src/helpers/showPage.ts diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 324af3598..887f88a4e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -3664,7 +3664,6 @@ packages: /at-least-node/1.0.0: resolution: {integrity: sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==} engines: {node: '>= 4.0.0'} - dev: true /atob/2.1.2: resolution: {integrity: sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==} @@ -5539,9 +5538,10 @@ packages: engines: {node: '>= 0.6'} /fs-extra/10.0.0: - resolution: {integrity: sha512-C5owb14u9eJwizKGdchcDUQeFtlSHHthBk8pbX9Vc1PFZrLombudjDnNns88aYslCyF6IY5SUw3Roz6xShcEIQ==} - engines: {node: '>=12'} + resolution: {integrity: sha512-C5owb14u9eJwizKGdchcDUQeFtlSHHthBk8pbX9Vc1PFZrLombudjDnNns88aYslCyF6IY5SUw3Roz6xShcEIQ==, tarball: fs-extra/-/fs-extra-10.0.0.tgz} + engines: {node: '>=10'} dependencies: + at-least-node: 1.0.0 graceful-fs: 4.2.8 jsonfile: 6.1.0 universalify: 2.0.0 -- GitLab