Skip to content
体验新版
项目
组织
正在加载...
登录
切换导航
打开侧边栏
qianlong66
uni-app
提交
8dc9937e
U
uni-app
项目概览
qianlong66
/
uni-app
与 Fork 源项目一致
Fork自
DCloud / uni-app
通知
1
Star
1
Fork
0
代码
文件
提交
分支
Tags
贡献者
分支图
Diff
Issue
0
列表
看板
标记
里程碑
合并请求
0
DevOps
流水线
流水线任务
计划
Wiki
0
Wiki
分析
仓库
DevOps
项目成员
Pages
U
uni-app
项目概览
项目概览
详情
发布
仓库
仓库
文件
提交
分支
标签
贡献者
分支图
比较
Issue
0
Issue
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
Pages
DevOps
DevOps
流水线
流水线任务
计划
分析
分析
仓库分析
DevOps
Wiki
0
Wiki
成员
成员
收起侧边栏
关闭侧边栏
动态
分支图
创建新Issue
流水线任务
提交
Issue看板
体验新版 GitCode,发现更多精彩内容 >>
提交
8dc9937e
编写于
4月 26, 2021
作者:
Q
qiang
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
refactor: input、textarea
上级
5f85117a
变更
11
展开全部
隐藏空白更改
内联
并排
Showing
11 changed file
with
1516 addition
and
1308 deletion
+1516
-1308
packages/uni-components/src/components/index.ts
packages/uni-components/src/components/index.ts
+2
-2
packages/uni-components/src/components/input/index.tsx
packages/uni-components/src/components/input/index.tsx
+151
-0
packages/uni-components/src/components/input/index.vue
packages/uni-components/src/components/input/index.vue
+0
-223
packages/uni-components/src/components/textarea/index.tsx
packages/uni-components/src/components/textarea/index.tsx
+168
-0
packages/uni-components/src/components/textarea/index.vue
packages/uni-components/src/components/textarea/index.vue
+0
-280
packages/uni-components/src/helpers/throttle.ts
packages/uni-components/src/helpers/throttle.ts
+28
-0
packages/uni-components/src/helpers/useField.ts
packages/uni-components/src/helpers/useField.ts
+379
-0
packages/uni-components/src/helpers/useFormField.ts
packages/uni-components/src/helpers/useFormField.ts
+19
-3
packages/uni-components/src/helpers/useKeyboard.ts
packages/uni-components/src/helpers/useKeyboard.ts
+39
-14
packages/uni-components/src/helpers/useScopedAttrs.ts
packages/uni-components/src/helpers/useScopedAttrs.ts
+33
-0
packages/uni-h5/dist/uni-h5.esm.js
packages/uni-h5/dist/uni-h5.esm.js
+697
-786
未找到文件。
packages/uni-components/src/components/index.ts
浏览文件 @
8dc9937e
...
...
@@ -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
,
...
...
packages/uni-components/src/components/input/index.tsx
0 → 100644
浏览文件 @
8dc9937e
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
<
HTMLElement
|
null
>
=
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
?
(
<
input
ref
=
{
fieldRef
}
value
=
{
state
.
value
}
tabindex
=
"-1"
readonly
=
{
!!
props
.
disabled
}
type
=
{
type
.
value
}
maxlength
=
{
state
.
maxlength
}
step
=
{
step
.
value
}
class
=
"uni-input-input"
// fix: 禁止 readonly 状态获取焦点
onFocus
=
{
(
event
:
Event
)
=>
(
event
.
target
as
HTMLInputElement
).
blur
()
}
/>
)
:
(
<
input
ref
=
{
fieldRef
}
value
=
{
state
.
value
}
disabled
=
{
!!
props
.
disabled
}
type
=
{
type
.
value
}
maxlength
=
{
state
.
maxlength
}
step
=
{
step
.
value
}
enterkeyhint
=
{
props
.
confirmType
}
class
=
"uni-input-input"
autocomplete
=
"off"
onKeyup
=
{
onKeyUpEnter
}
/>
)
return
(
<
uni
-
input
ref
=
{
rootRef
}
>
<
div
class
=
"uni-input-wrapper"
>
<
div
v
-
show
=
{
!
(
state
.
value
.
length
||
!
valid
.
value
)
}
{
...
scopedAttrsState
.
attrs
}
style
=
{
props
.
placeholderStyle
}
class
=
{
[
'
uni-input-placeholder
'
,
props
.
placeholderClass
]
}
>
{
props
.
placeholder
}
</
div
>
{
props
.
confirmType
===
'
search
'
?
(
<
form
action
=
""
onSubmit
=
{
()
=>
false
}
class
=
"uni-input-form"
>
{
inputNode
}
</
form
>
)
:
(
inputNode
)
}
</
div
>
</
uni
-
input
>
)
}
},
})
packages/uni-components/src/components/input/index.vue
已删除
100644 → 0
浏览文件 @
5f85117a
<
template
>
<uni-input
@
change.stop
v-bind=
"$attrs"
>
<div
ref=
"wrapper"
class=
"uni-input-wrapper"
>
<div
v-show=
"!(composing || valueSync.length)"
ref=
"placeholder"
:style=
"placeholderStyle"
:class=
"placeholderClass"
class=
"uni-input-placeholder"
v-text=
"placeholder"
/>
<input
ref=
"input"
v-model=
"valueSync"
:disabled=
"disabled"
:type=
"inputType"
:maxlength=
"maxlength"
:step=
"step"
:autofocus=
"focus"
class=
"uni-input-input"
autocomplete=
"off"
@
focus=
"_onFocus"
@
blur=
"_onBlur"
@
input.stop=
"_onInput"
@
compositionstart=
"_onComposition"
@
compositionend=
"_onComposition"
@
keyup.stop=
"_onKeyup"
/>
</div>
</uni-input>
</
template
>
<
script
>
import
{
getCurrentInstance
}
from
'
vue
'
import
{
useFormField
}
from
'
../../helpers/useFormField
'
import
{
baseInput
}
from
'
../../mixins
'
const
INPUT_TYPES
=
[
'
text
'
,
'
number
'
,
'
idcard
'
,
'
digit
'
,
'
password
'
]
const
NUMBER_TYPES
=
[
'
number
'
,
'
digit
'
]
export
default
{
name
:
'
Input
'
,
mixins
:
[
baseInput
],
props
:
{
name
:
{
type
:
String
,
default
:
''
},
type
:
{
type
:
String
,
default
:
'
text
'
},
password
:
{
type
:
[
Boolean
,
String
],
default
:
false
},
placeholder
:
{
type
:
String
,
default
:
''
},
placeholderStyle
:
{
type
:
String
,
default
:
''
},
placeholderClass
:
{
type
:
String
,
default
:
'
input-placeholder
'
},
disabled
:
{
type
:
[
Boolean
,
String
],
default
:
false
},
maxlength
:
{
type
:
[
Number
,
String
],
default
:
140
},
focus
:
{
type
:
[
Boolean
,
String
],
default
:
false
},
confirmType
:
{
type
:
String
,
default
:
'
done
'
}
},
data
()
{
return
{
composing
:
false
,
wrapperHeight
:
0
,
cachedValue
:
''
}
},
computed
:
{
inputType
:
function
()
{
let
type
=
''
switch
(
this
.
type
)
{
case
'
text
'
:
this
.
confirmType
===
'
search
'
&&
(
type
=
'
search
'
)
break
case
'
idcard
'
:
// TODO 可能要根据不同平台进行区分处理
type
=
'
text
'
break
case
'
digit
'
:
type
=
'
number
'
break
default
:
type
=
~
INPUT_TYPES
.
indexOf
(
this
.
type
)
?
this
.
type
:
'
text
'
break
}
return
this
.
password
?
'
password
'
:
type
},
step
()
{
// 处理部分设备中无法输入小数点的问题
return
~
NUMBER_TYPES
.
indexOf
(
this
.
type
)
?
'
0.000000000000000001
'
:
''
}
},
watch
:
{
focus
(
val
)
{
this
.
$refs
.
input
&&
this
.
$refs
.
input
[
val
?
'
focus
'
:
'
blur
'
]()
},
maxlength
(
value
)
{
const
realValue
=
this
.
valueSync
.
slice
(
0
,
parseInt
(
value
,
10
))
realValue
!==
this
.
valueSync
&&
(
this
.
valueSync
=
realValue
)
}
},
setup
()
{
useFormField
(
'
name
'
,
'
valueSync
'
)
},
mounted
()
{
if
(
this
.
confirmType
===
'
search
'
)
{
const
formElem
=
document
.
createElement
(
'
form
'
)
formElem
.
action
=
''
formElem
.
onsubmit
=
function
()
{
return
false
}
formElem
.
className
=
'
uni-input-form
'
formElem
.
appendChild
(
this
.
$refs
.
input
)
this
.
$refs
.
wrapper
.
appendChild
(
formElem
)
}
const
instance
=
getCurrentInstance
()
if
(
instance
&&
instance
.
vnode
.
scopeId
)
{
this
.
$refs
.
placeholder
.
setAttribute
(
instance
.
vnode
.
scopeId
,
''
)
}
// let $vm = this
// while ($vm) {
// const scopeId = $vm.$options._scopeId
// if (scopeId) {
// this.$refs.placeholder.setAttribute(scopeId, '')
// }
// $vm = $vm.$parent
// }
this
.
initKeyboard
(
this
.
$refs
.
input
)
},
methods
:
{
_onKeyup
(
$event
)
{
if
(
$event
.
keyCode
===
13
)
{
this
.
$trigger
(
'
confirm
'
,
$event
,
{
value
:
$event
.
target
.
value
})
}
},
_onInput
(
$event
)
{
if
(
this
.
composing
)
{
return
}
// 处理部分输入法可以输入其它字符的情况
if
(
~
NUMBER_TYPES
.
indexOf
(
this
.
type
))
{
if
(
this
.
$refs
.
input
.
validity
&&
!
this
.
$refs
.
input
.
validity
.
valid
)
{
$event
.
target
.
value
=
this
.
cachedValue
this
.
valueSync
=
$event
.
target
.
value
// 输入非法字符不触发 input 事件
return
}
else
{
this
.
cachedValue
=
this
.
valueSync
}
}
// type="number" 不支持 maxlength 属性,因此需要主动限制长度。
if
(
this
.
inputType
===
'
number
'
)
{
const
maxlength
=
parseInt
(
this
.
maxlength
,
10
)
if
(
maxlength
>
0
&&
$event
.
target
.
value
.
length
>
maxlength
)
{
$event
.
target
.
value
=
$event
.
target
.
value
.
slice
(
0
,
maxlength
)
this
.
valueSync
=
$event
.
target
.
value
// 字符长度超出范围不触发 input 事件
return
}
}
this
.
$triggerInput
(
$event
,
{
value
:
this
.
valueSync
})
},
_onFocus
(
$event
)
{
this
.
$trigger
(
'
focus
'
,
$event
,
{
value
:
$event
.
target
.
value
})
},
_onBlur
(
$event
)
{
this
.
$trigger
(
'
blur
'
,
$event
,
{
value
:
$event
.
target
.
value
})
},
_onComposition
(
$event
)
{
if
(
$event
.
type
===
'
compositionstart
'
)
{
this
.
composing
=
true
}
else
{
this
.
composing
=
false
}
},
_resetFormData
()
{
this
.
valueSync
=
''
},
_getFormData
()
{
return
this
.
name
?
{
value
:
this
.
valueSync
,
key
:
this
.
name
}
:
{}
}
}
}
</
script
>
packages/uni-components/src/components/textarea/index.tsx
0 → 100644
浏览文件 @
8dc9937e
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
<
HTMLElement
|
null
>
=
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
<
HTMLElement
|
null
>
=
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
?
(
<
textarea
ref
=
{
fieldRef
}
value
=
{
state
.
value
}
tabindex
=
"-1"
readonly
=
{
!!
props
.
disabled
}
maxlength
=
{
state
.
maxlength
}
class
=
{
{
'
uni-textarea-textarea
'
:
true
,
'
uni-textarea-textarea-fix-margin
'
:
fixMargin
,
}
}
style
=
{
{
overflowY
:
props
.
autoHeight
?
'
hidden
'
:
'
auto
'
}
}
// fix: 禁止 readonly 状态获取焦点
onFocus
=
{
(
event
:
Event
)
=>
(
event
.
target
as
HTMLInputElement
).
blur
()
}
/>
)
:
(
<
textarea
ref
=
{
fieldRef
}
value
=
{
state
.
value
}
disabled
=
{
!!
props
.
disabled
}
maxlength
=
{
state
.
maxlength
}
enterkeyhint
=
{
props
.
confirmType
}
class
=
{
{
'
uni-textarea-textarea
'
:
true
,
'
uni-textarea-textarea-fix-margin
'
:
fixMargin
,
}
}
style
=
{
{
overflowY
:
props
.
autoHeight
?
'
hidden
'
:
'
auto
'
}
}
onKeydown
=
{
onKeyDownEnter
}
onKeyup
=
{
onKeyUpEnter
}
/>
)
return
(
<
uni
-
textarea
ref
=
{
rootRef
}
>
<
div
class
=
"uni-textarea-wrapper"
>
<
div
v
-
show
=
{
!
state
.
value
.
length
}
{
...
scopedAttrsState
.
attrs
}
style
=
{
props
.
placeholderStyle
}
class
=
{
[
'
uni-textarea-placeholder
'
,
props
.
placeholderClass
]
}
>
{
props
.
placeholder
}
</
div
>
<
div
ref
=
{
lineRef
}
class
=
"uni-textarea-line"
>
{
'
'
}
</
div
>
<
div
class
=
"uni-textarea-compute"
>
{
valueCompute
.
value
.
map
((
item
)
=>
(
<
div
>
{
item
.
trim
()
?
item
:
'
.
'
}
</
div
>
))
}
<
ResizeSensor
initial
onResize
=
{
onResize
}
/>
</
div
>
{
props
.
confirmType
===
'
search
'
?
(
<
form
action
=
""
onSubmit
=
{
()
=>
false
}
class
=
"uni-input-form"
>
{
textareaNode
}
</
form
>
)
:
(
textareaNode
)
}
</
div
>
</
uni
-
textarea
>
)
}
},
})
packages/uni-components/src/components/textarea/index.vue
已删除
100644 → 0
浏览文件 @
5f85117a
<
template
>
<uni-textarea
@
change.stop
v-bind=
"$attrs"
>
<div
class=
"uni-textarea-wrapper"
>
<div
v-show=
"!(composition||valueSync.length)"
ref=
"placeholder"
:style=
"placeholderStyle"
:class=
"placeholderClass"
class=
"uni-textarea-placeholder"
v-text=
"placeholder"
/>
<div
ref=
"line"
class=
"uni-textarea-line"
v-text=
"' '"
/>
<div
class=
"uni-textarea-compute"
>
<div
v-for=
"(item,index) in valueCompute"
:key=
"index"
v-text=
"item.trim() ? item : '.'"
/>
<v-uni-resize-sensor
ref=
"sensor"
@
resize=
"_resize"
/>
</div>
<textarea
ref=
"textarea"
v-model=
"valueSync"
:disabled=
"disabled"
:maxlength=
"maxlengthNumber"
:autofocus=
"autoFocus || focus"
:class=
"
{'uni-textarea-textarea-fix-margin': fixMargin}"
:style="{'overflow-y': autoHeight? 'hidden':'auto'}"
class="uni-textarea-textarea"
@compositionstart="_compositionstart"
@compositionend="_compositionend"
@input.stop="_input"
@focus="_focus"
@blur="_blur"
@touchstart.passive="_touchstart"
/>
</div>
</uni-textarea>
</
template
>
<
script
>
import
{
baseInput
}
from
'
../../mixins
'
const
DARK_TEST_STRING
=
'
(prefers-color-scheme: dark)
'
export
default
{
name
:
'
Textarea
'
,
mixins
:
[
baseInput
],
props
:
{
name
:
{
type
:
String
,
default
:
''
},
maxlength
:
{
type
:
[
Number
,
String
],
default
:
140
},
placeholder
:
{
type
:
String
,
default
:
''
},
disabled
:
{
type
:
[
Boolean
,
String
],
default
:
false
},
focus
:
{
type
:
[
Boolean
,
String
],
default
:
false
},
autoFocus
:
{
type
:
[
Boolean
,
String
],
default
:
false
},
placeholderClass
:
{
type
:
String
,
default
:
'
textarea-placeholder
'
},
placeholderStyle
:
{
type
:
String
,
default
:
''
},
autoHeight
:
{
type
:
[
Boolean
,
String
],
default
:
false
},
cursor
:
{
type
:
[
Number
,
String
],
default
:
-
1
},
selectionStart
:
{
type
:
[
Number
,
String
],
default
:
-
1
},
selectionEnd
:
{
type
:
[
Number
,
String
],
default
:
-
1
}
},
data
()
{
return
{
valueComposition
:
''
,
composition
:
false
,
focusSync
:
this
.
focus
,
height
:
0
,
focusChangeSource
:
''
,
// iOS 13 以下版本需要修正边距
fixMargin
:
String
(
navigator
.
platform
).
indexOf
(
'
iP
'
)
===
0
&&
String
(
navigator
.
vendor
).
indexOf
(
'
Apple
'
)
===
0
&&
window
.
matchMedia
(
DARK_TEST_STRING
).
media
!==
DARK_TEST_STRING
}
},
computed
:
{
maxlengthNumber
()
{
var
maxlength
=
Number
(
this
.
maxlength
)
return
isNaN
(
maxlength
)
?
140
:
maxlength
},
cursorNumber
()
{
var
cursor
=
Number
(
this
.
cursor
)
return
isNaN
(
cursor
)
?
-
1
:
cursor
},
selectionStartNumber
()
{
var
selectionStart
=
Number
(
this
.
selectionStart
)
return
isNaN
(
selectionStart
)
?
-
1
:
selectionStart
},
selectionEndNumber
()
{
var
selectionEnd
=
Number
(
this
.
selectionEnd
)
return
isNaN
(
selectionEnd
)
?
-
1
:
selectionEnd
},
valueCompute
()
{
return
(
this
.
composition
?
this
.
valueComposition
:
this
.
valueSync
).
split
(
'
\n
'
)
}
},
watch
:
{
focus
(
val
)
{
if
(
val
)
{
this
.
focusChangeSource
=
'
focus
'
if
(
this
.
$refs
.
textarea
)
{
this
.
$refs
.
textarea
.
focus
()
}
}
else
{
if
(
this
.
$refs
.
textarea
)
{
this
.
$refs
.
textarea
.
blur
()
}
}
},
focusSync
(
val
)
{
this
.
$emit
(
'
update:focus
'
,
val
)
this
.
_checkSelection
()
this
.
_checkCursor
()
},
cursorNumber
()
{
this
.
_checkCursor
()
},
selectionStartNumber
()
{
this
.
_checkSelection
()
},
selectionEndNumber
()
{
this
.
_checkSelection
()
},
height
(
height
)
{
let
lineHeight
=
parseFloat
(
getComputedStyle
(
this
.
$el
).
lineHeight
)
if
(
isNaN
(
lineHeight
))
{
lineHeight
=
this
.
$refs
.
line
.
offsetHeight
}
var
lineCount
=
Math
.
round
(
height
/
lineHeight
)
this
.
$trigger
(
'
linechange
'
,
{},
{
height
,
heightRpx
:
750
/
window
.
innerWidth
*
height
,
lineCount
})
if
(
this
.
autoHeight
)
{
this
.
$el
.
style
.
height
=
this
.
height
+
'
px
'
}
}
},
created
()
{
this
.
$dispatch
(
'
Form
'
,
'
uni-form-group-update
'
,
{
type
:
'
add
'
,
vm
:
this
})
},
mounted
()
{
// this._resize({
// height: this.$refs.sensor.$el.offsetHeight
// })
let
$vm
=
this
while
(
$vm
)
{
const
scopeId
=
$vm
.
$options
.
_scopeId
if
(
scopeId
)
{
this
.
$refs
.
placeholder
.
setAttribute
(
scopeId
,
''
)
}
$vm
=
$vm
.
$parent
}
this
.
initKeyboard
(
this
.
$refs
.
textarea
)
},
beforeDestroy
()
{
this
.
$dispatch
(
'
Form
'
,
'
uni-form-group-update
'
,
{
type
:
'
remove
'
,
vm
:
this
})
},
methods
:
{
_focus
:
function
(
$event
)
{
this
.
focusSync
=
true
this
.
$trigger
(
'
focus
'
,
$event
,
{
value
:
this
.
valueSync
})
},
_checkSelection
()
{
if
(
this
.
focusSync
&&
(
!
this
.
focusChangeSource
)
&&
this
.
selectionStartNumber
>
-
1
&&
this
.
selectionEndNumber
>
-
1
)
{
this
.
$refs
.
textarea
.
selectionStart
=
this
.
selectionStartNumber
this
.
$refs
.
textarea
.
selectionEnd
=
this
.
selectionEndNumber
}
},
_checkCursor
()
{
if
(
this
.
focusSync
&&
(
this
.
focusChangeSource
===
'
focus
'
||
((
!
this
.
focusChangeSource
)
&&
this
.
selectionStartNumber
<
0
&&
this
.
selectionEndNumber
<
0
))
&&
this
.
cursorNumber
>
-
1
)
{
this
.
$refs
.
textarea
.
selectionEnd
=
this
.
$refs
.
textarea
.
selectionStart
=
this
.
cursorNumber
}
},
_blur
:
function
(
$event
)
{
this
.
focusSync
=
false
this
.
$trigger
(
'
blur
'
,
$event
,
{
value
:
this
.
valueSync
,
cursor
:
this
.
$refs
.
textarea
.
selectionEnd
})
},
_compositionstart
(
$event
)
{
this
.
composition
=
true
},
_compositionend
(
$event
)
{
this
.
composition
=
false
},
// 暂无完成按钮,此功能未实现
_confirm
(
$event
)
{
this
.
$trigger
(
'
confirm
'
,
$event
,
{
value
:
this
.
valueSync
})
},
_linechange
(
$event
)
{
this
.
$trigger
(
'
linechange
'
,
$event
,
{
value
:
this
.
valueSync
})
},
_touchstart
()
{
this
.
focusChangeSource
=
'
touch
'
},
_resize
({
height
})
{
this
.
height
=
height
},
_input
(
$event
)
{
if
(
this
.
composition
)
{
this
.
valueComposition
=
$event
.
target
.
value
return
}
this
.
$triggerInput
(
$event
,
{
value
:
this
.
valueSync
,
cursor
:
this
.
$refs
.
textarea
.
selectionEnd
})
},
_getFormData
()
{
return
{
value
:
this
.
valueSync
,
key
:
this
.
name
}
},
_resetFormData
()
{
this
.
valueSync
=
''
}
}
}
</
script
>
\ No newline at end of file
packages/uni-components/src/helpers/throttle.
j
s
→
packages/uni-components/src/helpers/throttle.
t
s
浏览文件 @
8dc9937e
export
function
throttle
(
fn
,
wait
)
{
export
function
throttle
(
fn
:
Function
,
wait
:
number
)
{
let
last
=
0
let
timeout
const
newFn
=
function
(...
arg
)
{
let
timeout
:
number
let
waitCallback
:
Function
|
null
const
newFn
=
function
(
this
:
any
,
...
arg
:
any
)
{
const
now
=
Date
.
now
()
clearTimeout
(
timeout
)
const
waitCallback
=
()
=>
{
waitCallback
=
()
=>
{
waitCallback
=
null
last
=
now
fn
.
apply
(
this
,
arg
)
}
...
...
@@ -14,8 +16,13 @@ export function throttle(fn, wait) {
}
waitCallback
()
}
newFn
.
cancel
=
function
()
{
newFn
.
cancel
=
function
()
{
clearTimeout
(
timeout
)
waitCallback
=
null
}
newFn
.
flush
=
function
()
{
clearTimeout
(
timeout
)
waitCallback
&&
waitCallback
()
}
return
newFn
}
packages/uni-components/src/helpers/useField.ts
0 → 100644
浏览文件 @
8dc9937e
import
{
Ref
,
ref
,
SetupContext
,
watch
,
onMounted
,
onBeforeMount
,
computed
,
reactive
,
}
from
'
vue
'
import
{
debounce
}
from
'
@dcloudio/uni-shared
'
import
{
throttle
}
from
'
./throttle
'
import
{
useCustomEvent
,
CustomEventTrigger
}
from
'
./useEvent
'
import
{
useUserAction
}
from
'
./useUserAction
'
import
{
props
as
keyboardProps
,
emit
as
keyboardEmit
,
useKeyboard
,
}
from
'
./useKeyboard
'
import
{
useScopedAttrs
}
from
'
./useScopedAttrs
'
import
{
useFormField
}
from
'
./useFormField
'
// App 延迟获取焦点
const
FOCUS_DELAY
=
200
let
startTime
:
number
function
getValueString
(
value
:
any
)
{
return
value
===
null
?
''
:
String
(
value
)
}
interface
InputEventDetail
{
value
:
string
}
type
HTMLFieldElement
=
HTMLInputElement
|
HTMLTextAreaElement
export
const
props
=
/*#__PURE__*/
Object
.
assign
(
{},
{
name
:
{
type
:
String
,
default
:
''
,
},
value
:
{
type
:
[
String
,
Number
],
default
:
''
,
},
disabled
:
{
type
:
[
Boolean
,
String
],
default
:
false
,
},
/**
* 已废弃属性,用于历史兼容
*/
autoFocus
:
{
type
:
[
Boolean
,
String
],
default
:
false
,
},
focus
:
{
type
:
[
Boolean
,
String
],
default
:
false
,
},
cursor
:
{
type
:
[
Number
,
String
],
default
:
-
1
,
},
selectionStart
:
{
type
:
[
Number
,
String
],
default
:
-
1
,
},
selectionEnd
:
{
type
:
[
Number
,
String
],
default
:
-
1
,
},
type
:
{
type
:
String
,
default
:
'
text
'
,
},
password
:
{
type
:
[
Boolean
,
String
],
default
:
false
,
},
placeholder
:
{
type
:
String
,
default
:
''
,
},
placeholderStyle
:
{
type
:
String
,
default
:
''
,
},
placeholderClass
:
{
type
:
String
,
default
:
''
,
},
maxlength
:
{
type
:
[
Number
,
String
],
default
:
140
,
},
confirmType
:
{
type
:
String
,
default
:
'
done
'
,
},
},
keyboardProps
)
export
const
emit
=
[
'
input
'
,
'
focus
'
,
'
blur
'
,
...
keyboardEmit
]
type
Props
=
Record
<
keyof
typeof
props
,
any
>
interface
State
{
value
:
string
maxlength
:
number
focus
:
boolean
composing
:
boolean
selectionStart
:
number
selectionEnd
:
number
cursor
:
number
}
function
useBase
(
props
:
Props
,
rootRef
:
Ref
<
HTMLElement
|
null
>
,
emit
:
SetupContext
[
'
emit
'
]
)
{
const
fieldRef
:
Ref
<
HTMLFieldElement
|
null
>
=
ref
(
null
)
const
trigger
=
useCustomEvent
(
rootRef
,
emit
)
const
selectionStart
=
computed
(()
=>
{
const
selectionStart
=
Number
(
props
.
selectionStart
)
return
isNaN
(
selectionStart
)
?
-
1
:
selectionStart
})
const
selectionEnd
=
computed
(()
=>
{
const
selectionEnd
=
Number
(
props
.
selectionEnd
)
return
isNaN
(
selectionEnd
)
?
-
1
:
selectionEnd
})
const
cursor
=
computed
(()
=>
{
const
cursor
=
Number
(
props
.
cursor
)
return
isNaN
(
cursor
)
?
-
1
:
cursor
})
const
maxlength
=
computed
(()
=>
{
var
maxlength
=
Number
(
props
.
maxlength
)
return
isNaN
(
maxlength
)
?
140
:
maxlength
})
const
value
=
getValueString
(
props
.
value
)
const
state
:
State
=
reactive
({
value
,
valueOrigin
:
value
,
maxlength
,
focus
:
props
.
focus
,
composing
:
false
,
selectionStart
,
selectionEnd
,
cursor
,
})
watch
(
()
=>
state
.
focus
,
(
val
)
=>
emit
(
'
update:focus
'
,
val
)
)
watch
(
()
=>
state
.
maxlength
,
(
val
)
=>
(
state
.
value
=
state
.
value
.
slice
(
0
,
val
))
)
return
{
fieldRef
,
state
,
trigger
,
}
}
function
useValueSync
(
props
:
Props
,
state
:
{
value
:
string
},
emit
:
SetupContext
[
'
emit
'
],
trigger
:
CustomEventTrigger
)
{
const
valueChangeFn
=
debounce
((
val
:
any
)
=>
{
state
.
value
=
getValueString
(
val
)
},
100
)
watch
(()
=>
props
.
value
,
valueChangeFn
)
const
triggerInputFn
=
throttle
((
event
:
Event
,
detail
:
InputEventDetail
)
=>
{
emit
(
'
update:value
'
,
detail
.
value
)
trigger
(
'
input
'
,
event
,
detail
)
},
100
)
const
triggerInput
=
(
event
:
Event
,
detail
:
InputEventDetail
,
force
:
boolean
)
=>
{
valueChangeFn
.
cancel
()
triggerInputFn
(
event
,
detail
)
if
(
force
)
{
triggerInputFn
.
flush
()
}
}
onBeforeMount
(()
=>
{
valueChangeFn
.
cancel
()
triggerInputFn
.
cancel
()
})
return
{
trigger
,
triggerInput
,
}
}
function
useAutoFocus
(
props
:
Props
,
fieldRef
:
Ref
<
HTMLFieldElement
|
null
>
)
{
const
{
state
:
userActionState
}
=
useUserAction
()
const
needFocus
=
computed
(()
=>
props
.
autoFocus
||
props
.
focus
)
function
focus
()
{
if
(
!
needFocus
.
value
)
{
return
}
const
field
=
fieldRef
.
value
if
(
!
field
||
(
__PLATFORM__
===
'
app
'
&&
!
(
'
plus
'
in
window
)))
{
setTimeout
(
focus
,
100
)
return
}
if
(
__PLATFORM__
===
'
h5
'
)
{
field
.
focus
()
}
else
{
const
timeout
=
FOCUS_DELAY
-
(
Date
.
now
()
-
startTime
)
if
(
timeout
>
0
)
{
setTimeout
(
focus
,
timeout
)
return
}
field
.
focus
()
// 无用户交互的 webview 需主动显示键盘(安卓)
if
(
!
userActionState
.
userAction
)
{
plus
.
key
.
showSoftKeybord
()
}
}
}
function
blur
()
{
const
field
=
fieldRef
.
value
if
(
field
)
{
field
.
blur
()
}
}
watch
(
()
=>
props
.
focus
,
(
value
)
=>
{
if
(
value
)
{
focus
()
}
else
{
blur
()
}
}
)
onMounted
(()
=>
{
startTime
=
startTime
||
Date
.
now
()
if
(
needFocus
.
value
)
{
focus
()
}
})
}
function
useEvent
(
fieldRef
:
Ref
<
HTMLFieldElement
|
null
>
,
state
:
State
,
trigger
:
CustomEventTrigger
,
triggerInput
:
Function
,
beforeInput
?:
(
event
:
Event
,
state
:
State
)
=>
any
)
{
function
checkSelection
()
{
const
field
=
fieldRef
.
value
if
(
field
&&
state
.
focus
&&
state
.
selectionStart
>
-
1
&&
state
.
selectionEnd
>
-
1
)
{
field
.
selectionStart
=
state
.
selectionStart
field
.
selectionEnd
=
state
.
selectionEnd
}
}
function
checkCursor
()
{
const
field
=
fieldRef
.
value
if
(
field
&&
state
.
focus
&&
state
.
selectionStart
<
0
&&
state
.
selectionEnd
<
0
&&
state
.
cursor
>
-
1
)
{
field
.
selectionEnd
=
field
.
selectionStart
=
state
.
cursor
}
}
function
initField
()
{
const
field
=
fieldRef
.
value
as
HTMLFieldElement
const
onFocus
=
function
(
event
:
Event
)
{
state
.
focus
=
true
trigger
(
'
focus
'
,
event
,
{
value
:
state
.
value
,
})
// 从 watch:focusSync 中移出到这里。在watcher中如果focus初始值为ture,则不会执行以下逻辑
checkSelection
()
checkCursor
()
}
const
onInput
=
function
(
event
:
Event
,
force
?:
boolean
)
{
event
.
stopPropagation
()
if
(
typeof
beforeInput
===
'
function
'
&&
beforeInput
(
event
,
state
)
===
false
)
{
return
}
state
.
value
=
field
.
value
if
(
!
state
.
composing
)
{
triggerInput
(
event
,
{
value
:
field
.
value
,
cursor
:
field
.
selectionEnd
,
},
force
)
}
}
const
onBlur
=
function
(
event
:
Event
)
{
// iOS 输入法 compositionend 事件可能晚于 blur
if
(
state
.
composing
)
{
state
.
composing
=
false
onInput
(
event
,
true
)
}
state
.
focus
=
false
trigger
(
'
blur
'
,
event
,
{
value
:
state
.
value
,
cursor
:
(
event
.
target
as
HTMLFieldElement
).
selectionEnd
,
})
}
// 避免触发父组件 change 事件
field
.
addEventListener
(
'
change
'
,
(
event
:
Event
)
=>
event
.
stopPropagation
())
field
.
addEventListener
(
'
focus
'
,
onFocus
)
field
.
addEventListener
(
'
blur
'
,
onBlur
)
field
.
addEventListener
(
'
input
'
,
onInput
)
field
.
addEventListener
(
'
compositionstart
'
,
(
event
)
=>
{
event
.
stopPropagation
()
state
.
composing
=
true
})
field
.
addEventListener
(
'
compositionend
'
,
(
event
)
=>
{
event
.
stopPropagation
()
if
(
state
.
composing
)
{
state
.
composing
=
false
// 部分输入法 compositionend 事件可能晚于 input
onInput
(
event
)
}
})
}
watch
([()
=>
state
.
selectionStart
,
()
=>
state
.
selectionEnd
],
checkSelection
)
watch
(()
=>
state
.
cursor
,
checkCursor
)
watch
(()
=>
fieldRef
.
value
,
initField
)
}
export
function
useField
(
props
:
Props
,
rootRef
:
Ref
<
HTMLElement
|
null
>
,
emit
:
SetupContext
[
'
emit
'
],
beforeInput
?:
(
event
:
Event
,
state
:
State
)
=>
any
)
{
const
{
fieldRef
,
state
,
trigger
}
=
useBase
(
props
,
rootRef
,
emit
)
const
{
triggerInput
}
=
useValueSync
(
props
,
state
,
emit
,
trigger
)
useAutoFocus
(
props
,
fieldRef
)
useKeyboard
(
props
,
fieldRef
,
trigger
)
const
{
state
:
scopedAttrsState
}
=
useScopedAttrs
()
useFormField
(
'
name
'
,
state
)
useEvent
(
fieldRef
,
state
,
trigger
,
triggerInput
,
beforeInput
)
// Safari 14 以上修正禁用状态颜色
const
fixDisabledColor
=
String
(
navigator
.
vendor
).
indexOf
(
'
Apple
'
)
===
0
&&
CSS
.
supports
(
'
image-orientation:from-image
'
)
return
{
fieldRef
,
state
,
scopedAttrsState
,
fixDisabledColor
,
trigger
,
}
}
packages/uni-components/src/helpers/useFormField.ts
浏览文件 @
8dc9937e
import
{
getCurrentInstance
,
inject
,
onBeforeUnmount
}
from
'
vue
'
import
{
UniFormCtx
,
uniFormKey
}
from
'
../components/form
'
export
function
useFormField
(
nameKey
:
string
,
valueKey
:
string
)
{
interface
ValueState
{
value
:
string
}
export
function
useFormField
(
nameKey
:
string
,
valueKey
:
string
):
void
export
function
useFormField
(
nameKey
:
string
,
valueState
:
ValueState
):
void
export
function
useFormField
(
nameKey
:
string
,
value
:
string
|
ValueState
):
void
{
const
uniForm
=
inject
<
UniFormCtx
>
(
uniFormKey
,
(
false
as
unknown
)
as
UniFormCtx
// remove warning
...
...
@@ -13,10 +22,17 @@ export function useFormField(nameKey: string, valueKey: string) {
const
ctx
=
{
submit
():
[
string
,
any
]
{
const
proxy
=
instance
.
proxy
return
[(
proxy
as
any
)[
nameKey
],
(
proxy
as
any
)[
valueKey
]]
return
[
(
proxy
as
any
)[
nameKey
],
typeof
value
===
'
string
'
?
(
proxy
as
any
)[
value
]
:
value
.
value
,
]
},
reset
()
{
;(
instance
.
proxy
as
any
)[
valueKey
]
=
''
if
(
typeof
value
===
'
string
'
)
{
;(
instance
.
proxy
as
any
)[
value
]
=
''
}
else
{
value
.
value
=
''
}
},
}
uniForm
.
addField
(
ctx
)
...
...
packages/uni-components/src/helpers/useKeyboard.ts
浏览文件 @
8dc9937e
import
{
Ref
,
onMounted
}
from
'
vue
'
import
{
Ref
,
watch
}
from
'
vue
'
import
{
CustomEventTrigger
}
from
'
./useEvent
'
import
{
plusReady
}
from
'
@dcloudio/uni-shared
'
...
...
@@ -101,14 +101,34 @@ interface KeyboardState {
softinputNavBar
?:
any
}
export
function
useKeyboard
(
props
:
{
disabled
:
any
cursorSpacing
:
number
|
string
autoBlur
:
any
adjustPosition
:
any
showConfirmBar
:
any
export
const
props
=
{
cursorSpacing
:
{
type
:
[
Number
,
String
],
default
:
0
,
},
showConfirmBar
:
{
type
:
[
Boolean
,
String
],
default
:
'
auto
'
,
},
adjustPosition
:
{
type
:
[
Boolean
,
String
],
default
:
true
,
},
autoBlur
:
{
type
:
[
Boolean
,
String
],
default
:
false
,
},
}
export
const
emit
=
[
'
keyboardheightchange
'
]
interface
Props
extends
Record
<
keyof
typeof
props
,
any
>
{
disabled
?:
any
readOnly
?:
any
}
export
function
useKeyboard
(
props
:
Props
,
elRef
:
Ref
<
HTMLElement
|
null
>
,
trigger
:
CustomEventTrigger
)
{
...
...
@@ -157,14 +177,19 @@ export function useKeyboard(
if
(
__PLATFORM__
===
'
app
'
)
{
// 安卓单独隐藏键盘后点击输入框不会触发 focus 事件
el
.
addEventListener
(
'
click
'
,
()
=>
{
if
(
!
props
.
disabled
&&
focus
&&
keyboardHeight
===
0
)
{
if
(
!
props
.
disabled
&&
!
props
.
readOnly
&&
focus
&&
keyboardHeight
===
0
)
{
setSoftinputTemporary
(
props
,
el
)
}
})
if
(
!
isAndroid
&&
parseInt
(
osVersion
)
<
12
)
{
// iOS12 以下系统 focus 事件设置较迟,改在 touchstart 设置
el
.
addEventListener
(
'
touchstart
'
,
()
=>
{
if
(
!
props
.
disabled
&&
!
focus
)
{
if
(
!
props
.
disabled
&&
!
props
.
readOnly
&&
!
focus
)
{
setSoftinputTemporary
(
props
,
el
)
}
})
...
...
@@ -205,8 +230,8 @@ export function useKeyboard(
onKeyboardHide
()
})
}
onMounted
(()
=>
{
const
el
=
elRef
.
value
as
HTMLElement
initKeyboard
(
el
)
}
)
watch
(
()
=>
elRef
.
value
,
(
el
)
=>
initKeyboard
(
el
as
HTMLElement
)
)
}
packages/uni-components/src/helpers/useScopedAttrs.ts
0 → 100644
浏览文件 @
8dc9937e
import
{
onMounted
,
getCurrentInstance
,
ComponentInternalInstance
,
reactive
,
}
from
'
vue
'
interface
State
{
attrs
:
Record
<
string
,
string
>
}
export
function
useScopedAttrs
()
{
const
state
:
State
=
reactive
({
attrs
:
{},
})
onMounted
(()
=>
{
let
vm
=
(
getCurrentInstance
()
as
ComponentInternalInstance
).
proxy
while
(
vm
)
{
const
$options
=
vm
.
$options
const
scopeId
=
$options
.
__scopeId
if
(
scopeId
)
{
const
attrs
:
Record
<
string
,
string
>
=
{}
attrs
[
scopeId
]
=
''
state
.
attrs
=
attrs
}
vm
=
vm
.
$parent
}
})
return
{
state
,
}
}
packages/uni-h5/dist/uni-h5.esm.js
浏览文件 @
8dc9937e
此差异已折叠。
点击以展开。
编辑
预览
Markdown
is supported
0%
请重试
或
添加新附件
.
添加附件
取消
You are about to add
0
people
to the discussion. Proceed with caution.
先完成此消息的编辑!
取消
想要评论请
注册
或
登录