diff --git a/docs/examples-docs/zh-CN/search.md b/docs/examples-docs/zh-CN/search.md index 2ec150355..6008f2fe5 100644 --- a/docs/examples-docs/zh-CN/search.md +++ b/docs/examples-docs/zh-CN/search.md @@ -1,11 +1,19 @@ + + ## Search 搜索 ### 使用指南 @@ -27,55 +41,47 @@ Vue.component(Search.name, Search); #### 基础用法 -如果你只需要在搜索时有个回调,只要监听一个`search`事件。 +`van-search` 中,v-model 用于控制搜索框中的文字。background 可以自定义搜索框外部背景色。 :::demo 基础用法 ```html - -``` - -```javascript -export default { - methods: { - goSearch(value) { - alert(value) - } - } -}; -``` -::: - -#### 微杂志页搜索样式 - -:::demo 基础用法 -```html - + ``` ::: #### 监听对应事件 -除了`search`事件,还有`change`和`cancel`事件,`change`事件在`input`输入框每次`change`时触发,适用于实时搜索等,`cancel`在取消按钮点击时触发。 +`van-search` 提供了 search 和 cancel 事件。search 事件在用户点击键盘上的 搜索/回车 按钮触发。cancel 事件在用户点击搜索框右侧取消按钮时触发 + +Tips: 在 `van-search` 外层增加 form 标签,并且 action 不为空,即可在 IOS 弹出的输入法中显示搜索按钮 :::demo 监听对应事件 ```html - +
+ +
``` +::: -```javascript -export default { - methods: { - goSearch(value) { - alert(value) - }, - handleChange(value) { - console.log(value); - }, - handleCancel() { - alert('cancel'); - } - } -}; +#### 自定义行动按钮 + +`van-search` 支持自定义右侧取消按钮,使用名字为 action 的 slot 即可。使用此 slot 以后,原有的 cancel 事件不再生效。 + +:::demo 自定义行动按钮 +```html + + + ``` ::: @@ -84,12 +90,18 @@ export default { | 参数 | 说明 | 类型 | 默认值 | 可选值 | |-----------|-----------|-----------|-------------|-------------| | placeholder | `input`的`placeholder`文案 | `String` | | | -| type | 搜索样式类型 | `String` | `normal` | `normal`:普通样式,`showcase`:微杂志页样式 | +| background | 搜索框背景色 | `String` | `#f2f2f2` | 所有浏览器支持的颜色描述 | +| showAction | 是否在搜索框右侧显示取消按钮 | `Boolean` | false | | ### Event | 事件名 | 说明 | 参数 | |-----------|-----------|-----------| -| change | `input`输入框每次`change`时触发,适用于实时搜索等 | value:当前`input`输入框的值 | -| cancel | 取消搜索 | | -| search | 确定搜索 | value:当前`input`输入框的值 | +| cancel | 取消搜索 | - | +| search | 确定搜索 | - | + +### Slot + +| name | 描述 | +|-----------|-----------| +| action | 自定义搜索框右侧按钮,需要在`showAction`为 true 时才会显示 | diff --git a/packages/search/index.vue b/packages/search/index.vue index 64a291602..aeaedab50 100644 --- a/packages/search/index.vue +++ b/packages/search/index.vue @@ -1,21 +1,26 @@ @@ -32,21 +37,19 @@ export default { props: { placeholder: String, - type: { + value: String, + showAction: { + type: Boolean, + default: false + }, + background: { type: String, - default: 'normal' - } - }, - - watch: { - value(val) { - this.$emit('change', val); + default: '#f2f2f2' } }, data() { return { - value: '', focusStatus: false, isFocus: false }; @@ -71,29 +74,38 @@ export default { this.isFocus = true; }, + handleInput(event) { + this.$emit('input', event.target.value); + }, + /** - * 点击close后清空vlaue后,再聚焦input框 + * 点击close后清空value后,再聚焦input框 */ handleClean() { - this.value = ''; + this.$emit('input', ''); this.focusStatus = true; + + // 保证多次点击 clean 后,仍能触发 refocus + this.$nextTick(() => { + this.focusStatus = false; + }); }, /** * 点击取消后,清空所有回复最初状态 */ handleBack() { - this.value = ''; - this.focusStatus = false; - this.isFocus = false; + this.$emit('input', ''); this.$emit('cancel'); }, /** * input输入回车后,发送回调 */ - handleSearch() { - this.$emit('search', this.value); + handleSearch(e) { + e.preventDefault(); + this.$emit('search'); + return false; }, handleClickoutside() { diff --git a/packages/vant-css/src/search.css b/packages/vant-css/src/search.css index 08df57948..d4238ed38 100644 --- a/packages/vant-css/src/search.css +++ b/packages/vant-css/src/search.css @@ -1,26 +1,19 @@ @import './common/var.css'; .van-search { - position: relative; + display: flex; + align-items: center; box-sizing: border-box; padding: 4px 15px; - background-color: #F2F2F2; - &--focus { - padding-right: 50px; - } - - &--showcase { - padding: 10px; - background-color: $background-color; - - .van-search__input-wrap { - border-color: $gray-light; - } + &--show-action { + padding-right: 0; } &__input-wrap { position: relative; + flex: 1; + height: 16px; padding: 8px 24px 8px 35px; border: 1px solid $gray-light; border-radius: 4px; @@ -49,13 +42,14 @@ } } - &__cancel { - position: absolute; + &__action { line-height: 34px; - padding: 4px 0; - top: 0; - right: 10px; font-size: 14px; + letter-spacing: 1px; + } + + &__action-text { + padding: 0 10px; color: $green; } @@ -73,9 +67,11 @@ font-size: 14px; line-height: 16px; position: absolute; - right: 5px; + right: 0; top: 50%; transform: translateY(-50%); + /* 增加 clear 点击区域 */ + padding: 5px; color: #888; } } diff --git a/test/unit/specs/search.spec.js b/test/unit/specs/search.spec.js index 4a63a0389..b066b6bc7 100644 --- a/test/unit/specs/search.spec.js +++ b/test/unit/specs/search.spec.js @@ -8,7 +8,7 @@ describe('Search', () => { wrapper && wrapper.destroy(); }); - it('create a stepper', () => { + it('create a search', () => { wrapper = mount(Search); expect(wrapper.hasClass('van-search')).to.be.true; @@ -25,54 +25,65 @@ describe('Search', () => { expect(wrapper.data().isFocus).to.be.true; }); - it('emit change event', (done) => { - wrapper = mount(Search); + it('create a search with searchText', (done) => { + wrapper = mount(Search, { + propsData: { + value: 'search text' + } + }); - const eventStub = sinon.stub(wrapper.vm, '$emit'); - wrapper.setData({ value: 'test' }); - wrapper.update(); wrapper.vm.$nextTick(() => { - expect(wrapper.data().value).to.be.equal('test'); - expect(eventStub.calledOnce).to.be.true; - expect(eventStub.calledWith('change')); + const input = wrapper.find('.van-search__input')[0]; + expect(input.element.value === 'search text').to.be.true; done(); }); }); - it('handle clean click', () => { + it('emit input event', () => { wrapper = mount(Search); - wrapper.setData({ value: 'test' }); - expect(wrapper.data().value).to.be.equal('test'); + const input = wrapper.find('.van-search__input')[0]; + const eventStub = sinon.stub(wrapper.vm, '$emit'); + input.trigger('input', { target: { value: 'search' }}); + + expect(eventStub.calledOnce).to.be.true; + expect(eventStub.calledWith('input')).to.be.true; + }); + + it('handle clean click and refocus', (done) => { + wrapper = mount(Search); + wrapper.setProps({ value: 'test' }); + const eventStub = sinon.stub(wrapper.vm, '$emit'); const input = wrapper.find('.van-search__input')[0]; input.trigger('focus'); const cleanBtn = wrapper.find('.van-icon-clear')[0]; cleanBtn.trigger('click'); - expect(wrapper.data().value).to.equal(''); - expect(wrapper.data().focusStatus).to.be.true; + + wrapper.vm.$nextTick(() => { + expect(eventStub.calledOnce).to.be.true; + expect(eventStub.calledWith('input')).to.be.true; + done(); + }); }); it('handle cancel click', (done) => { wrapper = mount(Search); - wrapper.setData({ value: 'test' }); - expect(wrapper.data().value).to.be.equal('test'); + wrapper.setProps({ value: 'test', showAction: true }); + expect(wrapper.vm.value).to.be.equal('test'); const eventStub = sinon.stub(wrapper.vm, '$emit'); - const input = wrapper.find('.van-search__input')[0]; - input.trigger('focus'); - - const cancelBtn = wrapper.find('.van-search__cancel')[0]; + const cancelBtn = wrapper.find('.van-search__action-text')[0]; cancelBtn.trigger('click'); wrapper.vm.$nextTick(() => { - expect(wrapper.data().value).to.be.equal(''); - expect(wrapper.data().focusStatus).to.be.false; - expect(wrapper.data().isFocus).to.be.false; - expect(eventStub.calledOnce).to.be.true; + expect(wrapper.vm.focusStatus).to.be.false; + expect(wrapper.vm.isFocus).to.be.false; + expect(eventStub.calledTwice).to.be.true; + expect(eventStub.calledWith('input')); expect(eventStub.calledWith('change')); done(); }); @@ -84,7 +95,7 @@ describe('Search', () => { const eventStub = sinon.stub(wrapper.vm, '$emit'); const input = wrapper.find('.van-search__input')[0]; - input.trigger('keyup.enter'); + input.trigger('keypress.enter'); wrapper.vm.$nextTick(() => { expect(eventStub.calledOnce).to.be.true; @@ -93,24 +104,17 @@ describe('Search', () => { }); }); - it('create a showcase type search', () => { - wrapper = mount(Search, { - propsData: { - type: 'showcase' - } - }); - - expect(wrapper.hasClass('van-search')).to.be.true; - expect(wrapper.hasClass('van-search--showcase')).to.be.true; + it('blur after click outside', () => { + wrapper = mount(Search); const input = wrapper.find('.van-search__input')[0]; input.trigger('focus'); - expect(wrapper.data().isFocus).to.be.true; + expect(wrapper.vm.isFocus).to.be.true; const body = document.body; body.click(); - expect(wrapper.data().isFocus).to.be.false; - expect(wrapper.data().focusStatus).to.be.false; + expect(wrapper.vm.isFocus).to.be.false; + expect(wrapper.vm.focusStatus).to.be.false; }); });