Skip to content

Commit e9e5699

Browse files
committed
wip(mp): input auto close
1 parent 50e2c47 commit e9e5699

File tree

3 files changed

+154
-1
lines changed

3 files changed

+154
-1
lines changed
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
import { formatInputTag } from '../src/plugins/input'
2+
3+
describe('test input tag', () => {
4+
test('with end input tag', () => {
5+
expect(formatInputTag(`<template><input type="text" /></template>`)).toBe(
6+
`<template><input type="text" /></template>`
7+
)
8+
9+
expect(
10+
formatInputTag(`
11+
<template>
12+
<input class="hidden-input" hold-keyboard type="digit" >
13+
<keyboard-accessory>
14+
<cover-view class="custom__list">
15+
<cover-view class="custom__item" v-for="i in 4" :key="i" @click="change(i)">{{i * 5}}</cover-view>
16+
</cover-view>
17+
</keyboard-accessory>
18+
</input>
19+
</template>`)
20+
).toBe(`
21+
<template>
22+
<input class="hidden-input" hold-keyboard type="digit" >
23+
<keyboard-accessory>
24+
<cover-view class="custom__list">
25+
<cover-view class="custom__item" v-for="i in 4" :key="i" @click="change(i)">{{i * 5}}</cover-view>
26+
</cover-view>
27+
</keyboard-accessory>
28+
</input>
29+
</template>`)
30+
})
31+
32+
test('no end input tag', () => {
33+
expect(formatInputTag(`<template><input ></template>`)).toBe(
34+
`<template><input /></template>`
35+
)
36+
37+
expect(
38+
formatInputTag(
39+
`<template><input id="input1" class="test" type="text" ></template>`
40+
)
41+
).toBe(
42+
`<template><input id="input1" class="test" type="text" /></template>`
43+
)
44+
45+
expect(
46+
formatInputTag(
47+
`<template><input id="input1" class="test" type="text" ><input type="text" /></template>`
48+
)
49+
).toBe(
50+
`<template><input id="input1" class="test" type="text" /><input type="text" /></template>`
51+
)
52+
53+
expect(
54+
formatInputTag(`
55+
<template>
56+
<input type="text">
57+
<input type="checkbox"></input>
58+
<input type="radio" />
59+
<input type="file">Some content</input>
60+
<input type="text">xxx</input>
61+
<input>
62+
<keyboard-accessory>
63+
234
64+
</keyboard-accessory>
65+
</input>
66+
<input type="text" disabled></template>`)
67+
).toBe(`
68+
<template>
69+
<input type="text" />
70+
<input type="checkbox"></input>
71+
<input type="radio" />
72+
<input type="file">Some content</input>
73+
<input type="text">xxx</input>
74+
<input>
75+
<keyboard-accessory>
76+
234
77+
</keyboard-accessory>
78+
</input>
79+
<input type="text" disabled /></template>`)
80+
})
81+
})

packages/uni-mp-vite/src/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ import { uniMainJsPlugin } from './plugins/mainJs'
2525
import { uniManifestJsonPlugin } from './plugins/manifestJson'
2626
import { uniPagesJsonPlugin } from './plugins/pagesJson'
2727
import { uniEntryPlugin } from './plugins/entry'
28-
28+
import { uniInputAutoClosePlugin } from './plugins/input'
2929
import { uniRenderjsPlugin } from './plugins/renderjs'
3030
import { uniRuntimeHooksPlugin } from './plugins/runtimeHooks'
3131
import { uniSubpackagePlugin } from './plugins/subpackage'
@@ -52,6 +52,7 @@ export default (options: UniMiniProgramPluginOptions) => {
5252
)
5353

5454
return [
55+
uniInputAutoClosePlugin(),
5556
...(isEnableConsole() ? [uniHBuilderXConsolePlugin('uni.__f__')] : []),
5657
...(process.env.UNI_APP_X === 'true'
5758
? [
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
export function formatInputTag(code: string) {
2+
const inputStartRegex = /<input(\s+[^>]*)?>/g
3+
let result = code
4+
let match
5+
6+
// 第一次遍历:收集所有<input>标签的位置
7+
const inputStarts: Array<{
8+
index: number
9+
length: number
10+
fullMatch: string
11+
}> = []
12+
while ((match = inputStartRegex.exec(code)) !== null) {
13+
inputStarts.push({
14+
index: match.index,
15+
length: match[0].length,
16+
fullMatch: match[0],
17+
})
18+
}
19+
20+
// 第二次遍历:检查每个<input>是否有对应的</input>
21+
const toReplace: Array<{
22+
index: number
23+
length: number
24+
replacement: string
25+
}> = []
26+
for (let i = 0; i < inputStarts.length; i++) {
27+
const start = inputStarts[i]
28+
const nextStart = inputStarts[i + 1]?.index || code.length
29+
const substring = code.slice(start.index + start.length, nextStart)
30+
31+
// 检查是否有对应的</input>
32+
const hasClosingTag = substring.includes('</input>')
33+
34+
// 检查是否是自闭合形式(以/>结尾)
35+
const isSelfClosing = start.fullMatch.endsWith('/>')
36+
37+
if (!hasClosingTag && !isSelfClosing) {
38+
// 需要转换的标签
39+
toReplace.push({
40+
index: start.index,
41+
length: start.length,
42+
replacement: start.fullMatch.replace(/>$/, ' />'),
43+
})
44+
}
45+
}
46+
47+
// 从后往前替换,避免影响索引
48+
for (let i = toReplace.length - 1; i >= 0; i--) {
49+
const item = toReplace[i]
50+
result =
51+
result.slice(0, item.index) +
52+
item.replacement +
53+
result.slice(item.index + item.length)
54+
}
55+
56+
return result
57+
}
58+
59+
export function uniInputAutoClosePlugin() {
60+
return {
61+
name: 'uni:mp-input-auto-close',
62+
enforce: 'pre',
63+
transform(code, id) {
64+
if (!id.endsWith('.vue')) {
65+
return
66+
}
67+
68+
return formatInputTag(code)
69+
},
70+
}
71+
}

0 commit comments

Comments
 (0)