From 8dc9937ead677021399d51a9300e5818eb7263e3 Mon Sep 17 00:00:00 2001 From: qiang Date: Mon, 26 Apr 2021 20:47:57 +0800 Subject: [PATCH] =?UTF-8?q?refactor:=20input=E3=80=81textarea?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../uni-components/src/components/index.ts | 4 +- .../src/components/input/index.tsx | 151 ++ .../src/components/input/index.vue | 223 --- .../src/components/textarea/index.tsx | 168 ++ .../src/components/textarea/index.vue | 280 ---- .../uni-components/src/helpers/throttle.js | 21 - .../uni-components/src/helpers/throttle.ts | 28 + .../uni-components/src/helpers/useField.ts | 379 +++++ .../src/helpers/useFormField.ts | 22 +- .../uni-components/src/helpers/useKeyboard.ts | 53 +- .../src/helpers/useScopedAttrs.ts | 33 + packages/uni-h5/dist/uni-h5.esm.js | 1483 ++++++++--------- 12 files changed, 1516 insertions(+), 1329 deletions(-) create mode 100644 packages/uni-components/src/components/input/index.tsx delete mode 100644 packages/uni-components/src/components/input/index.vue create mode 100644 packages/uni-components/src/components/textarea/index.tsx delete mode 100644 packages/uni-components/src/components/textarea/index.vue delete mode 100644 packages/uni-components/src/helpers/throttle.js create mode 100644 packages/uni-components/src/helpers/throttle.ts create mode 100644 packages/uni-components/src/helpers/useField.ts create mode 100644 packages/uni-components/src/helpers/useScopedAttrs.ts diff --git a/packages/uni-components/src/components/index.ts b/packages/uni-components/src/components/index.ts index de311a5e0..1058dab52 100644 --- a/packages/uni-components/src/components/index.ts +++ b/packages/uni-components/src/components/index.ts @@ -7,7 +7,7 @@ import Editor from './editor/index' import Form from './form/index' import Icon from './icon/index' import Image from './image/index' -import Input from './input/index.vue' +import Input from './input/index' import Label from './label/index' // import MovableArea from './movable-area/index.vue' import MovableView from './movable-view/index.vue' @@ -25,7 +25,7 @@ import Slider from './slider/index.vue' import SwiperItem from './swiper-item/index.vue' import Switch from './switch/index.vue' import Text from './text/index' -import Textarea from './textarea/index.vue' +import Textarea from './textarea/index' import View from './view/index' export { Audio, diff --git a/packages/uni-components/src/components/input/index.tsx b/packages/uni-components/src/components/input/index.tsx new file mode 100644 index 000000000..c4c616e51 --- /dev/null +++ b/packages/uni-components/src/components/input/index.tsx @@ -0,0 +1,151 @@ +import { defineComponent, Ref, ref, computed } from 'vue' +import { + props as fieldProps, + emit as fieldEmit, + emit, + useField, +} from '../../helpers/useField' + +const props = /*#__PURE__*/ Object.assign({}, fieldProps, { + placeholderClass: { + type: String, + default: 'input-placeholder', + }, +}) + +export default /*#__PURE__*/ defineComponent({ + name: 'Input', + props, + emit: ['confirm', ...fieldEmit], + setup(props, { emit }) { + const INPUT_TYPES = ['text', 'number', 'idcard', 'digit', 'password'] + const type = computed(() => { + let type = '' + switch (props.type) { + case 'text': + if (props.confirmType === 'search') { + type = 'search' + } + break + case 'idcard': + // TODO 可能要根据不同平台进行区分处理 + type = 'text' + break + case 'digit': + type = 'number' + break + default: + type = ~INPUT_TYPES.includes(props.type) ? props.type : 'text' + break + } + return props.password ? 'password' : type + }) + const valid = ref(true) + let cachedValue = '' + const rootRef: Ref = ref(null) + const { + fieldRef, + state, + scopedAttrsState, + fixDisabledColor, + trigger, + } = useField(props, rootRef, emit, (event, state) => { + const input = event.target as HTMLInputElement + + if (NUMBER_TYPES.includes(props.type)) { + // 在输入 - 负号 的情况下,event.target.value没有值,但是会触发校验 false,因此做此处理 + valid.value = input.validity && input.validity.valid + cachedValue = state.value + + // 处理部分输入法可以输入其它字符的情况 + // 上一处理导致无法输入 - ,因此去除 + // if (input.validity && !input.validity.valid) { + // input.value = cachedValue + // state.value = input.value + // // 输入非法字符不触发 input 事件 + // return false + // } else { + // cachedValue = state.value + // } + } + + // type="number" 不支持 maxlength 属性,因此需要主动限制长度。 + if (type.value === 'number') { + const maxlength = state.maxlength + if (maxlength > 0 && input.value.length > maxlength) { + input.value = input.value.slice(0, maxlength) + state.value = input.value + // 字符长度超出范围不触发 input 事件 + return false + } + } + }) + const NUMBER_TYPES = ['number', 'digit'] + const step = computed(() => + NUMBER_TYPES.includes(props.type) ? '0.000000000000000001' : '' + ) + + function onKeyUpEnter(event: Event) { + if ((event as KeyboardEvent).key !== 'Enter') { + return + } + event.stopPropagation() + trigger('confirm', event, { + value: (event.target as HTMLInputElement).value, + }) + } + return () => { + let inputNode = + props.disabled && fixDisabledColor ? ( + + (event.target as HTMLInputElement).blur() + } + /> + ) : ( + + ) + return ( + +
+
+ {props.placeholder} +
+ {props.confirmType === 'search' ? ( +
false} class="uni-input-form"> + {inputNode} +
+ ) : ( + inputNode + )} +
+
+ ) + } + }, +}) diff --git a/packages/uni-components/src/components/input/index.vue b/packages/uni-components/src/components/input/index.vue deleted file mode 100644 index 85687d7d8..000000000 --- a/packages/uni-components/src/components/input/index.vue +++ /dev/null @@ -1,223 +0,0 @@ - - diff --git a/packages/uni-components/src/components/textarea/index.tsx b/packages/uni-components/src/components/textarea/index.tsx new file mode 100644 index 000000000..b4492a08a --- /dev/null +++ b/packages/uni-components/src/components/textarea/index.tsx @@ -0,0 +1,168 @@ +import { defineComponent, Ref, ref, computed, watch } from 'vue' +import { + props as fieldProps, + emit as fieldEmit, + useField, +} from '../../helpers/useField' +import ResizeSensor from '../resize-sensor/index' + +const props = /*#__PURE__*/ Object.assign({}, fieldProps, { + placeholderClass: { + type: String, + default: 'input-placeholder', + }, + autoHeight: { + type: [Boolean, String], + default: false, + }, + confirmType: { + type: String, + default: '', + }, +}) + +export default /*#__PURE__*/ defineComponent({ + name: 'Textarea', + props, + emit: ['confirm', 'linechange', ...fieldEmit], + setup(props, { emit }) { + const rootRef: Ref = ref(null) + const { + fieldRef, + state, + scopedAttrsState, + fixDisabledColor, + trigger, + } = useField(props, rootRef, emit) + const valueCompute = computed(() => state.value.split('\n')) + const isDone = computed(() => + ['done', 'go', 'next', 'search', 'send'].includes(props.confirmType) + ) + const heightRef = ref(0) + const lineRef: Ref = ref(null) + watch( + () => heightRef.value, + (height) => { + const el = rootRef.value as HTMLElement + const lineEl = lineRef.value as HTMLElement + let lineHeight = parseFloat(getComputedStyle(el).lineHeight) + if (isNaN(lineHeight)) { + lineHeight = lineEl.offsetHeight + } + var lineCount = Math.round(height / lineHeight) + trigger('linechange', {} as Event, { + height, + heightRpx: (750 / window.innerWidth) * height, + lineCount, + }) + if (props.autoHeight) { + el.style.height = height + 'px' + } + } + ) + + function onResize({ height }: { height: number }) { + heightRef.value = height + } + + function confirm(event: Event) { + trigger('confirm', event, { + value: state.value, + }) + } + + function onKeyDownEnter(event: Event) { + if ((event as KeyboardEvent).key !== 'Enter') { + return + } + if (isDone.value) { + event.preventDefault() + } + } + + function onKeyUpEnter(event: Event) { + if ((event as KeyboardEvent).key !== 'Enter') { + return + } + if (isDone.value) { + confirm(event) + const textarea = event.target as HTMLTextAreaElement + textarea.blur() + } + } + + // iOS 13 以下版本需要修正边距 + const DARK_TEST_STRING = '(prefers-color-scheme: dark)' + const fixMargin = + String(navigator.platform).indexOf('iP') === 0 && + String(navigator.vendor).indexOf('Apple') === 0 && + window.matchMedia(DARK_TEST_STRING).media !== DARK_TEST_STRING + + return () => { + let textareaNode = + props.disabled && fixDisabledColor ? ( +