Skip to content

Commit f3ce9ee

Browse files
authored
Merge pull request #4 from VladimirIvanin/fix-simple-checkbox
фикс чекбокса
2 parents be9e1d4 + bf1d314 commit f3ce9ee

File tree

8 files changed

+145
-67
lines changed

8 files changed

+145
-67
lines changed

packages/vuetify/src/components/VCheckbox/VSimpleCheckbox.ts

Lines changed: 84 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,7 @@ import './VSimpleCheckbox.sass'
22

33
import Ripple from '../../directives/ripple'
44

5-
import { VNode, VNodeDirective, h } from 'vue'
6-
import {defineComponent} from 'vue'
5+
import { VNode, h, defineComponent, withDirectives } from 'vue'
76

87
import { VIcon } from '../VIcon'
98

@@ -18,8 +17,6 @@ import { wrapInArray } from '../../util/helpers'
1817
export default defineComponent({
1918
name: 'v-simple-checkbox',
2019

21-
functional: true,
22-
2320
directives: {
2421
Ripple,
2522
},
@@ -32,7 +29,7 @@ export default defineComponent({
3229
type: Boolean,
3330
default: true,
3431
},
35-
value: Boolean,
32+
modelValue: Boolean,
3633
indeterminate: Boolean,
3734
indeterminateIcon: {
3835
type: String,
@@ -48,49 +45,94 @@ export default defineComponent({
4845
},
4946
},
5047

48+
emits: ['input', 'update:modelValue'],
49+
50+
methods: {
51+
getIcon (): string {
52+
const { indeterminate, modelValue, indeterminateIcon, onIcon, offIcon } = this.$props
53+
54+
if (indeterminate) return indeterminateIcon
55+
if (modelValue) return onIcon
56+
return offIcon
57+
},
58+
59+
createIcon (): VNode {
60+
const { modelValue, disabled, dark, light, color } = this.$props
61+
62+
return h(
63+
VIcon,
64+
Colorable.methods.setTextColor(modelValue && color, {
65+
disabled,
66+
dark,
67+
light,
68+
}),
69+
() => this.getIcon()
70+
)
71+
},
72+
73+
createRipple (): VNode | null {
74+
const { ripple, disabled, color } = this.$props
75+
76+
if (!ripple || disabled) return null
77+
78+
return withDirectives(
79+
h(
80+
'div',
81+
Colorable.methods.setTextColor(color, {
82+
class: 'v-input--selection-controls__ripple',
83+
})
84+
),
85+
[
86+
[Ripple, { center: true }],
87+
]
88+
)
89+
},
90+
91+
handleClick (e: MouseEvent): void {
92+
e.stopPropagation()
93+
94+
if (this.$props.disabled) return
95+
96+
const newValue = !this.modelValue
97+
const attrs = this.$attrs
98+
99+
100+
this.$emit("input", newValue);
101+
this.$emit('update:modelValue', newValue)
102+
},
103+
104+
createChildren (): VNode[] {
105+
const children = [this.createIcon()]
106+
107+
const ripple = this.createRipple()
108+
if (ripple) {
109+
children.push(ripple)
110+
}
111+
112+
return children
113+
},
114+
},
115+
51116
render (): VNode {
52-
const props = this.$props
117+
const { disabled } = this.$props
53118
const data = this.$attrs
54119

55-
const children = []
56-
let icon = props.offIcon
57-
if (props.indeterminate) icon = props.indeterminateIcon
58-
else if (props.value) icon = props.onIcon
59-
60-
children.push(h(VIcon, Colorable.methods.setTextColor(props.value && props.color, {
61-
disabled: props.disabled,
62-
dark: props.dark,
63-
light: props.light
64-
}), icon))
65-
66-
if (props.ripple && !props.disabled) {
67-
const ripple = h('div', Colorable.methods.setTextColor(props.color, {
68-
class: 'v-input--selection-controls__ripple',
69-
directives: [{
70-
def: Ripple,
71-
name: 'ripple',
72-
value: { center: true },
73-
}] as VNodeDirective[],
74-
}))
75-
76-
children.push(ripple)
77-
}
78-
79-
return h('div',
120+
return h(
121+
'div',
80122
mergeData(data, {
81123
class: {
82124
'v-simple-checkbox': true,
83-
'v-simple-checkbox--disabled': props.disabled,
84-
},
85-
onClick: (e: MouseEvent) => {
86-
e.stopPropagation()
87-
88-
if (data.on && data.on.input && !props.disabled) {
89-
data.onInput && data.onInput(!props.value)
90-
}
125+
'v-simple-checkbox--disabled': disabled,
91126
},
92-
}), [
93-
h('div', { class: 'v-input--selection-controls__input' }, children),
94-
])
127+
onClick: this.handleClick,
128+
}),
129+
[
130+
h(
131+
'div',
132+
{ class: 'v-input--selection-controls__input' },
133+
this.createChildren()
134+
)
135+
]
136+
)
95137
},
96138
})
Lines changed: 46 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,65 @@
11
import {
22
mount,
3-
Wrapper,
4-
MountOptions,
3+
VueWrapper,
54
} from '@vue/test-utils'
65
import VSimpleCheckbox from '../VSimpleCheckbox'
76

87
describe('VSimpleCheckbox.ts', () => {
98
type Instance = InstanceType<typeof VSimpleCheckbox>
10-
let mountFunction: (options?: MountOptions<Instance>) => Wrapper<Instance>
9+
let mountFunction: (options?: any) => VueWrapper<Instance>
10+
1111
beforeEach(() => {
12-
mountFunction = (options?: MountOptions<Instance>) => {
13-
return mount(VSimpleCheckbox, options)
12+
mountFunction = (options = {}) => {
13+
return mount(VSimpleCheckbox, {
14+
global: {
15+
stubs: {
16+
VIcon: {
17+
template: '<span class="v-icon"></span>',
18+
},
19+
},
20+
},
21+
...options,
22+
})
1423
}
1524
})
1625

17-
it('should pass down listeners', () => {
18-
const mouseleave = jest.fn()
26+
it('should render simple checkbox', () => {
1927
const wrapper = mountFunction({
20-
propsData: { disabled: true },
21-
listeners: {
22-
mouseleave,
23-
},
28+
props: { modelValue: false },
29+
})
30+
31+
expect(wrapper.find('.v-simple-checkbox').exists()).toBe(true)
32+
expect(wrapper.find('.v-input--selection-controls__input').exists()).toBe(true)
33+
})
34+
35+
it('should emit update:modelValue event on click', async () => {
36+
const wrapper = mountFunction({
37+
props: { modelValue: false },
2438
})
2539

2640
const element = wrapper.find('.v-simple-checkbox')
41+
await element.trigger('click')
42+
43+
expect(wrapper.emitted()['update:modelValue']).toBeTruthy()
44+
expect(wrapper.emitted()['update:modelValue'][0]).toEqual([true])
45+
})
2746

28-
element.trigger('mouseleave')
47+
it('should not emit update:modelValue when disabled', async () => {
48+
const wrapper = mountFunction({
49+
props: { modelValue: false, disabled: true },
50+
})
51+
52+
const element = wrapper.find('.v-simple-checkbox')
53+
await element.trigger('click')
54+
55+
expect(wrapper.emitted()['update:modelValue']).toBeFalsy()
56+
})
57+
58+
it('should apply disabled class when disabled', () => {
59+
const wrapper = mountFunction({
60+
props: { disabled: true },
61+
})
2962

30-
expect(mouseleave).toHaveBeenCalledTimes(1)
63+
expect(wrapper.find('.v-simple-checkbox--disabled').exists()).toBe(true)
3164
})
3265
})

packages/vuetify/src/components/VDataTable/VDataTable.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -511,7 +511,7 @@ export default mixins(
511511
isMobile: this.isMobile,
512512
}) : () => h(VSimpleCheckbox, {
513513
class: 'v-data-table__checkbox',
514-
value: data.isSelected,
514+
modelValue: data.isSelected,
515515
disabled: !this.isSelectable(item),
516516
color: this.checkboxColor ?? '',
517517
onInput: (val: boolean) => data.select(val),

packages/vuetify/src/components/VDataTable/VDataTableHeader.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,21 +20,22 @@ import {defineComponent, h} from 'vue'
2020
export default defineComponent({
2121
name: 'v-data-table-header',
2222

23-
functional: true,
24-
2523
props: {
2624
...header.props,
2725
mobile: Boolean,
2826
},
2927

3028
render () {
3129
const props = this.$props
32-
let data = this.$attrs
30+
let data = {
31+
...this.$attrs,
32+
...props,
33+
}
3334

3435
// dedupeModelListeners(data)
3536
const children = rebuildSlots(this.$slots, h)
3637

37-
if (this.mobile) {
38+
if (props.mobile) {
3839
return h(VDataTableHeaderMobile, data, children)
3940
} else {
4041
return h(VDataTableHeaderDesktop, data, children)

packages/vuetify/src/components/VDataTable/VDataTableHeaderDesktop.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ export default mixins(header).extend({
110110
return h('thead', {
111111
class: 'v-data-table-header',
112112
}, [
113-
h('tr', this.headers.map(header => this.genHeader(header))),
113+
h('tr', this.headers?.map(header => this.genHeader(header)) || []),
114114
])
115115
},
116116
})

packages/vuetify/src/components/VDataTable/mixins/header.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,18 @@ import VIcon from '../../VIcon'
33
import VSimpleCheckbox from '../../VCheckbox/VSimpleCheckbox'
44
import ripple from '../../../directives/ripple'
55

6-
import {defineComponent, h} from 'vue'
6+
import { defineComponent, h } from 'vue'
77
import { PropValidator } from 'vue/types/options'
88
import mixins from '../../../util/mixins'
99
import { DataOptions, DataTableHeader } from 'vuetify/types'
1010

1111
type VDataTableInstance = InstanceType<typeof VDataTable>
1212

13-
interface options extends Vue {
13+
interface HeaderOptions {
1414
dataTable: VDataTableInstance
1515
}
1616

17-
export default mixins<options>().extend({
17+
export default mixins<HeaderOptions>().extend({
1818
// https://github.com/vuejs/vue/issues/6872
1919
directives: {
2020
ripple,
@@ -53,7 +53,7 @@ export default mixins<options>().extend({
5353
methods: {
5454
genSelectAll () {
5555
const data = {
56-
value: this.everyItem,
56+
modelValue: this.everyItem,
5757
indeterminate: !this.everyItem && this.someItems,
5858
color: this.checkboxColor ?? '',
5959
onInput: (v: boolean) => this.$emit('toggle-select-all', v)

packages/vuetify/src/components/VSelect/VSelectList.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ export default mixins(Colorable, Themeable).extend({
9090
return h(VListItemAction, [
9191
h(VSimpleCheckbox, {
9292
color: this.color,
93-
value: inputValue,
93+
modelValue: inputValue,
9494
ripple: false,
9595
onInput: () => this.$emit('select', item)
9696
}),

packages/vuetify/src/mixins/selectable/index.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,12 @@ import Comparable from '../comparable'
77

88
// Utilities
99
import mixins from '../../util/mixins'
10-
import {h} from 'vue'
10+
import { h } from 'vue'
1111

1212
export function prevent (e: Event) {
1313
e.preventDefault()
14+
// всплытие провоцирует двойной onChange
15+
e.stopPropagation()
1416
}
1517

1618
/* @vue/component */

0 commit comments

Comments
 (0)