fix: jsx slot modelvalue can't update value (#734)

* fix: jsx slot modelvalue can't update value

* fix: add unit test for updateModel event
This commit is contained in:
chilingling 2024-09-04 20:39:49 -07:00 committed by GitHub
parent c35c34e036
commit 9ccfc9fa8a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 313 additions and 2 deletions

View File

@ -1,3 +1,5 @@
const { rules } = require('../../.eslintrc')
/* eslint-env node */
require('@rushstack/eslint-patch/modern-module-resolution')
@ -17,5 +19,6 @@ module.exports = {
}
},
// 忽略 expected 中的内容
ignorePatterns: ['**/**/expected/*', '**/**.ts']
ignorePatterns: ['**/**/expected/*', '**/**.ts'],
rules
}

View File

@ -19,7 +19,8 @@ import {
handleI18nAttrHook,
handleObjBindAttrHook,
handleEventAttrHook,
handleTinyIconPropsHook
handleTinyIconPropsHook,
handleJsxModelValueUpdate
} from './generateAttribute'
import {
GEN_SCRIPT_HOOKS,
@ -213,6 +214,7 @@ export const genSFCWithDefaultPlugin = (schema, componentsMap, config = {}) => {
const defaultAttributeHook = [
handleTinyGrid,
handleJsxModelValueUpdate,
handleConditionAttrHook,
handleLoopAttrHook,
handleSlotBindAttrHook,

View File

@ -515,3 +515,28 @@ export const handleBindUtilsHooks = (schemaData, globalHooks, config) => {
}
})
}
/**
* modelvalue 绑定自动生成 onUpdate:modelValue 事件绑定
* @param {*} schemaData
* @param {*} globalHooks
* @param {*} config
*/
export const handleJsxModelValueUpdate = (schemaData, globalHooks, config) => {
const { schema: { props = {} } = {} } = schemaData || {}
const isJSX = config.isJSX
if (!isJSX) {
return
}
const propsEntries = Object.entries(props)
const modelValue = propsEntries.find(([_key, value]) => value?.type === JS_EXPRESSION && value?.model === true)
const hasUpdateModelValue = propsEntries.find(([key]) => isOn(key) && key.startsWith(`onUpdate:${modelValue?.[0]}`))
// jsx 形式的 modelvalue, 如果 schema 没有声明,出码需要同时声明 onUpdate:modelValue否则更新失效
if (modelValue && !hasUpdateModelValue) {
// 添加 onUpdate:modelKey 事件,让后续钩子生成 对应的事件声明
props[`onUpdate:${modelValue?.[0]}`] = { type: JS_EXPRESSION, value: `(value) => ${modelValue[1].value}=value` }
}
}

View File

@ -0,0 +1,23 @@
[
{
"componentName": "TinyGrid",
"exportName": "Grid",
"package": "@opentiny/vue",
"version": "^3.10.0",
"destructuring": true
},
{
"componentName": "TinyNumeric",
"exportName": "Numeric",
"package": "@opentiny/vue",
"version": "^3.10.0",
"destructuring": true
},
{
"componentName": "TinyInput",
"exportName": "Input",
"package": "@opentiny/vue",
"version": "^3.10.0",
"destructuring": true
}
]

View File

@ -0,0 +1,96 @@
<template>
<div>
<div>
<tiny-grid
:editConfig="{ trigger: 'click', mode: 'cell', showStatus: true }"
:columns="state.columns"
:data="[
{
id: '1',
name: 'GFD科技有限公司',
city: '福州',
employees: 800,
created_date: '2014-04-30 00:56:00',
boole: false
},
{
id: '2',
name: 'WWW科技有限公司',
city: '深圳',
employees: 300,
created_date: '2016-07-08 12:36:22',
boole: true
}
]"
></tiny-grid>
</div>
</div>
</template>
<script setup lang="jsx">
import { Grid as TinyGrid, Numeric as TinyNumeric, Input as TinyInput } from '@opentiny/vue'
import * as vue from 'vue'
import { defineProps, defineEmits } from 'vue'
import { I18nInjectionKey } from 'vue-i18n'
const props = defineProps({})
const emit = defineEmits([])
const { t, lowcodeWrap, stores } = vue.inject(I18nInjectionKey).lowcode()
const wrap = lowcodeWrap(props, { emit })
wrap({ stores })
const state = vue.reactive({
columns: [
{ type: 'index', width: 60 },
{ type: 'selection', width: 60 },
{
field: 'employees',
title: '员工数',
slots: {
default: ({ row, column, rowIndex }, h) => (
<div>
<TinyNumeric
allow-empty={true}
placeholder="请输入"
controlsPosition="right"
step={1}
modelValue={row.employees}
onChange={(...eventArgs) => onChangeNumber(eventArgs, row, column, rowIndex)}
onUpdate:modelValue={(value) => (row.employees = value)}
></TinyNumeric>
</div>
)
}
},
{ field: 'created_date', title: '创建日期' },
{
field: 'city',
title: '城市',
slots: {
default: ({ row, column, rowIndex }, h) => (
<div>
<TinyInput
placeholder="请输入"
modelValue={row.city}
onChange={(...eventArgs) => onChangeInput(eventArgs, row, column, rowIndex)}
onUpdate:modelValue={(value) => (row.city = value)}
></TinyInput>
</div>
)
}
}
]
})
wrap({ state })
const onChangeInput = wrap(function onChangeInput(eventArgs, args0, args1, args2) {
console.log('onChangeInput', eventArgs)
})
const onChangeNumber = wrap(function onChangeNumber(eventArgs, args0, args1, args2) {
console.log('onChangeNumber', eventArgs)
})
wrap({ onChangeInput, onChangeNumber })
</script>
<style scoped></style>

View File

@ -0,0 +1,150 @@
{
"state": {},
"methods": {
"onChangeInput": {
"type": "JSFunction",
"value": "function onChangeInput(eventArgs,args0,args1,args2) {\n console.log('onChangeInput', eventArgs);\n}"
},
"onChangeNumber": {
"type": "JSFunction",
"value": "function onChangeNumber(eventArgs, args0, args1, args2) {\n console.log('onChangeNumber', eventArgs)\n}"
}
},
"componentName": "Page",
"css": "",
"props": {},
"lifeCycles": {},
"children": [
{
"componentName": "div",
"props": {},
"id": "85375559",
"children": [
{
"componentName": "TinyGrid",
"props": {
"editConfig": {
"trigger": "click",
"mode": "cell",
"showStatus": true
},
"columns": [
{
"type": "index",
"width": 60
},
{
"type": "selection",
"width": 60
},
{
"field": "employees",
"title": "员工数",
"slots": {
"default": {
"type": "JSSlot",
"value": [
{
"componentName": "div",
"id": "44523622",
"children": [
{
"componentName": "TinyNumeric",
"props": {
"allow-empty": true,
"placeholder": "请输入",
"controlsPosition": "right",
"step": 1,
"modelValue": {
"type": "JSExpression",
"value": "row.employees",
"model": true
},
"onChange": {
"type": "JSExpression",
"value": "this.onChangeNumber",
"params": ["row", "column", "rowIndex"]
}
},
"id": "62166343"
}
]
}
],
"params": ["row", "column", "rowIndex"]
}
}
},
{
"field": "created_date",
"title": "创建日期"
},
{
"field": "city",
"title": "城市",
"slots": {
"default": {
"type": "JSSlot",
"value": [
{
"componentName": "div",
"id": "66326314",
"children": [
{
"componentName": "TinyInput",
"props": {
"placeholder": "请输入",
"modelValue": {
"type": "JSExpression",
"value": "row.city",
"model": true
},
"onChange": {
"type": "JSExpression",
"value": "this.onChangeInput",
"params": ["row", "column", "rowIndex"]
}
},
"id": "22396a2a"
}
]
}
],
"params": ["row", "column", "rowIndex"]
}
}
}
],
"data": [
{
"id": "1",
"name": "GFD科技有限公司",
"city": "福州",
"employees": 800,
"created_date": "2014-04-30 00:56:00",
"boole": false
},
{
"id": "2",
"name": "WWW科技有限公司",
"city": "深圳",
"employees": 300,
"created_date": "2016-07-08 12:36:22",
"boole": true
}
]
},
"id": "63623253"
}
]
}
],
"dataSource": {
"list": []
},
"utils": [],
"bridge": [],
"inputs": [],
"outputs": [],
"fileName": "slotModelValueTest"
}

View File

@ -0,0 +1,12 @@
import { expect, test } from 'vitest'
import { genSFCWithDefaultPlugin } from '@/generator/vue/sfc'
import schema from './page.schema.json'
import componentsMap from './components-map.json'
import { formatCode } from '@/utils/formatCode'
test('should generate onUpdate:modelValue event', async () => {
const res = genSFCWithDefaultPlugin(schema, componentsMap)
const formattedCode = formatCode(res, 'vue')
await expect(formattedCode).toMatchFileSnapshot('./expected/slotModelValueTest.vue')
})