Repository: vuejs/vue
Pull request: Adds handler for kebab-case event for v-bind:sync; fix #6428

<!--
Please make sure to read the Pull Request Guidelines:
https://github.com/vuejs/vue/blob/dev/.github/CONTRIBUTING.md#pull-request-guidelines
-->

<!-- PULL REQUEST TEMPLATE -->
<!-- (Update "[ ]" to "[x]" to check a box) -->

**What kind of change does this PR introduce?** (check at least one)

- [x] Bugfix
- [x] Feature

**Does this PR introduce a breaking change?** (check one)

- [x] No

If yes, please describe the impact and migration path for existing applications:

**The PR fulfills these requirements:**

- [x] It's submitted to the `dev` branch for v2.x (or to a previous version branch), _not_ the `master` branch
- [x] When resolving a specific issue, it's referenced in the PR's title (e.g. `fix #xxx[,#xxx]`, where "xxx" is the issue number)
- [x] All tests are passing: https://github.com/vuejs/vue/blob/dev/.github/CONTRIBUTING.md#development-setup
- [x] New/updated tests are included

**Other information:**

Addresses #6428 and https://github.com/vuejs/vuejs.org/issues/1648

Changed files:
filename: src/compiler/parser/index.js
patch: @@ -5,7 +5,7 @@ import { parseHTML } from './html-parser'
 import { parseText } from './text-parser'
 import { parseFilters } from './filter-parser'
 import { genAssignmentCode } from '../directives/model'
-import { extend, cached, no, camelize } from 'shared/util'
+import { extend, cached, no, camelize, hyphenate } from 'shared/util'
 import { isIE, isEdge, isServerRendering } from 'core/util/env'
 
 import {
@@ -524,7 +524,7 @@ function processComponent (el) {
 
 function processAttrs (el) {
   const list = el.attrsList
-  let i, l, name, rawName, value, modifiers, isProp
+  let i, l, name, rawName, value, modifiers, isProp, syncGen
   for (i = 0, l = list.length; i < l; i++) {
     name = rawName = list[i].name
     value = list[i].value
@@ -558,11 +558,19 @@ function processAttrs (el) {
             name = camelize(name)
           }
           if (modifiers.sync) {
+            syncGen = genAssignmentCode(value, `$event`)
             addHandler(
               el,
               `update:${camelize(name)}`,
-              genAssignmentCode(value, `$event`)
+              syncGen
             )
+            if (hyphenate(name) !== camelize(name)) {
+              addHandler(
+                el,
+                `update:${hyphenate(name)}`,
+                syncGen
+              )
+            }
           }
         }
         if (isProp || (

filename: test/unit/features/directives/bind.spec.js
patch: @@ -166,6 +166,27 @@ describe('Directive v-bind', () => {
     }).then(done)
   })
 
+  it('.sync modifier with kebab case event', done => {
+    const vm = new Vue({
+      template: `<test :foo-bar.sync="bar"/>`,
+      data: {
+        bar: 1
+      },
+      components: {
+        test: {
+          props: ['fooBar'],
+          template: `<div @click="$emit('update:foo-bar', 2)">{{ fooBar }}</div>`
+        }
+      }
+    }).$mount()
+
+    expect(vm.$el.textContent).toBe('1')
+    triggerEvent(vm.$el, 'click')
+    waitForUpdate(() => {
+      expect(vm.$el.textContent).toBe('2')
+    }).then(done)
+  })
+
   it('bind object', done => {
     const vm = new Vue({
       template: '<input v-bind="test">',