@veltra/desktop 1.7.11 → 1.7.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -15,7 +15,7 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
15
15
  __name: "batch-edit-form",
16
16
  setup(__props, { expose: __expose }) {
17
17
  const batchEditCtx = inject(BatchEditDIKey);
18
- const { cls, props, state, emit, handleClose, handleSave, staticFeatures, dynamicFeatures, syncing } = batchEditCtx;
18
+ const { cls, props, state, handleClose, handleSave, staticFeatures, dynamicFeatures, syncing } = batchEditCtx;
19
19
  const focused = toRef(batchEditCtx, "focused");
20
20
  const formComponentRef = shallowRef();
21
21
  __expose({
@@ -28,9 +28,6 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
28
28
  if (syncing.value) return;
29
29
  if (state.row && props.quickEdit) o(state.row.data).set(field, value);
30
30
  }
31
- function handleFormFieldChange(field, ...args) {
32
- emit("field:change", field, ...args);
33
- }
34
31
  const creatable = computed(() => {
35
32
  if (state.formActionType !== "create") return false;
36
33
  return staticFeatures.value.has("create") || dynamicFeatures.value.create?.(state.row);
@@ -89,8 +86,7 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
89
86
  model: unref(props).model,
90
87
  readonly: unref(props).readonly,
91
88
  "label-width": unref(props).labelWidth,
92
- "onField:update": handleFieldUpdate,
93
- "onField:change": handleFormFieldChange
89
+ "onField:update": handleFieldUpdate
94
90
  }, {
95
91
  default: withCtx(() => [renderSlot(_ctx.$slots, "default", normalizeProps(guardReactiveProps({
96
92
  row: unref(state).row,
@@ -1 +1 @@
1
- {"version":3,"file":"batch-edit-form.js","names":[],"sources":["../../../src/components/batch-edit/batch-edit-form.vue"],"sourcesContent":["<template>\n <!-- 表单常驻挂载(快照只在挂载时对传入的 model 拍一次),仅用 v-show 控制显隐 -->\n <aside :class=\"cls.e('form')\" v-if=\"!!props.model\" v-show=\"state.formVisible\">\n <header :class=\"cls.e('form-header')\">\n <span :class=\"[cls.e('form-icon'), bem.is(state.formActionType)]\">\n <u-icon>\n <component :is=\"headerInfo.icon\" />\n </u-icon>\n </span>\n <div :class=\"cls.e('form-title')\">\n <h3 :class=\"cls.e('form-title-text')\">{{ headerInfo.title }}</h3>\n </div>\n <u-button\n :class=\"cls.e('form-close')\"\n :icon=\"Close\"\n text\n circle\n size=\"small\"\n title=\"关闭\"\n @click=\"handleClose\"\n />\n </header>\n\n <u-scroll always :class=\"cls.e('form-body')\">\n <transition name=\"fade\" appear mode=\"out-in\">\n <u-form\n ref=\"formComponentRef\"\n :model=\"props.model\"\n :readonly=\"props.readonly\"\n :label-width=\"props.labelWidth\"\n @field:update=\"handleFieldUpdate\"\n @field:change=\"handleFormFieldChange\"\n >\n <slot\n v-bind=\"{\n row: state.row,\n parentRow: state.parentRow,\n formActionType: state.formActionType,\n depth: state.depth,\n indexes: state.indexPath,\n index: state.row?.index\n }\"\n />\n </u-form>\n </transition>\n </u-scroll>\n\n <footer :class=\"cls.e('form-footer')\">\n <span :class=\"cls.e('form-hint')\">\n <template v-if=\"props.readonly\"> 只读模式 </template>\n\n <span :class=\"{ [bem.is('concealed')]: !focused }\">\n <template v-if=\"!(state.formActionType === 'update' && props.quickEdit)\">\n <u-kbd>Ctrl + S</u-kbd> 保存 ·\n </template>\n <u-kbd>Esc</u-kbd> 关闭\n </span>\n </span>\n\n <div :class=\"cls.e('form-actions')\">\n <u-button size=\"small\" text :loading=\"state.loading\" @click=\"handleClose\"> 取消 </u-button>\n <u-button\n v-if=\"showSaveBtn\"\n type=\"primary\"\n size=\"small\"\n title=\"保存\"\n :icon=\"Save\"\n :loading=\"state.loading\"\n @click=\"handleSave\"\n />\n </div>\n </footer>\n </aside>\n</template>\n\n<script lang=\"ts\" setup>\nimport { o } from '@cat-kit/core'\nimport { Close, Save } from '@veltra/icons/normal'\nimport { bem } from '@veltra/utils'\nimport { computed, inject, shallowRef, toRef } from 'vue'\n\nimport type { _FormExposed, FormExposed } from '../../types/form'\nimport { UButton } from '../button'\nimport { UForm } from '../form'\nimport { UIcon } from '../icon'\nimport { UKbd } from '../kbd'\nimport { UScroll } from '../scroll'\nimport { BatchEditDIKey } from './di'\nimport { FORM_ACTION_HEADER_MAP } from './form-action-header'\n\ndefineOptions({ name: 'UBatchEditForm' })\n\nconst batchEditCtx = inject(BatchEditDIKey)!\n\nconst {\n cls,\n props,\n state,\n emit,\n handleClose,\n handleSave,\n staticFeatures,\n dynamicFeatures,\n syncing\n} = batchEditCtx\n\nconst focused = toRef(batchEditCtx, 'focused')\n\nconst formComponentRef = shallowRef<FormExposed>()\n\ndefineExpose<_FormExposed>({\n el: toRef(() => formComponentRef.value?.el),\n validate: () => formComponentRef.value?.validate() ?? Promise.resolve(true),\n clearValidate: () => formComponentRef.value?.clearValidate(),\n reset: () => formComponentRef.value?.reset()\n})\n\nfunction handleFieldUpdate(field: string, value: any) {\n // 编程方式重置/回显期间不写回行数据,避免默认值污染源数据\n if (syncing.value) return\n\n if (state.row && props.quickEdit) {\n o(state.row.data).set(field, value)\n }\n}\n\nfunction handleFormFieldChange(field: string, ...args: any[]) {\n emit('field:change', field, ...args)\n}\n\nconst creatable = computed(() => {\n if (state.formActionType !== 'create') return false\n return staticFeatures.value.has('create') || dynamicFeatures.value.create?.(state.row)\n})\n\nconst creatableChild = computed(() => {\n if (state.formActionType !== 'createChild') return false\n return (\n staticFeatures.value.has('createChild') || dynamicFeatures.value.createChild?.(state.parentRow)\n )\n})\n\nconst updatable = computed(() => {\n if (state.formActionType !== 'update') return false\n return staticFeatures.value.has('update') || dynamicFeatures.value.update?.(state.row)\n})\n\nconst showSaveBtn = computed(() => {\n if (props.readonly) return false\n if (props.quickEdit && state.formActionType === 'update') return false\n return creatable.value || updatable.value || creatableChild.value\n})\n\nconst headerInfo = computed(() => FORM_ACTION_HEADER_MAP[state.formActionType])\n</script>\n"],"mappings":";;;;;;;;;;;;;;;;EA4FA,MAAM,eAAe,OAAO,cAAc;EAE1C,MAAM,EACJ,KACA,OACA,OACA,MACA,aACA,YACA,gBACA,iBACA,YACE;EAEJ,MAAM,UAAU,MAAM,cAAc,SAAS;EAE7C,MAAM,mBAAmB,WAAwB;EAEjD,SAA2B;GACzB,IAAI,YAAY,iBAAiB,OAAO,EAAE;GAC1C,gBAAgB,iBAAiB,OAAO,SAAS,KAAK,QAAQ,QAAQ,IAAI;GAC1E,qBAAqB,iBAAiB,OAAO,cAAc;GAC3D,aAAa,iBAAiB,OAAO,MAAM;EAC7C,CAAC;EAED,SAAS,kBAAkB,OAAe,OAAY;GAEpD,IAAI,QAAQ,OAAO;GAEnB,IAAI,MAAM,OAAO,MAAM,WACrB,EAAE,MAAM,IAAI,IAAI,CAAC,CAAC,IAAI,OAAO,KAAK;EAEtC;EAEA,SAAS,sBAAsB,OAAe,GAAG,MAAa;GAC5D,KAAK,gBAAgB,OAAO,GAAG,IAAI;EACrC;EAEA,MAAM,YAAY,eAAe;GAC/B,IAAI,MAAM,mBAAmB,UAAU,OAAO;GAC9C,OAAO,eAAe,MAAM,IAAI,QAAQ,KAAK,gBAAgB,MAAM,SAAS,MAAM,GAAG;EACvF,CAAC;EAED,MAAM,iBAAiB,eAAe;GACpC,IAAI,MAAM,mBAAmB,eAAe,OAAO;GACnD,OACE,eAAe,MAAM,IAAI,aAAa,KAAK,gBAAgB,MAAM,cAAc,MAAM,SAAS;EAElG,CAAC;EAED,MAAM,YAAY,eAAe;GAC/B,IAAI,MAAM,mBAAmB,UAAU,OAAO;GAC9C,OAAO,eAAe,MAAM,IAAI,QAAQ,KAAK,gBAAgB,MAAM,SAAS,MAAM,GAAG;EACvF,CAAC;EAED,MAAM,cAAc,eAAe;GACjC,IAAI,MAAM,UAAU,OAAO;GAC3B,IAAI,MAAM,aAAa,MAAM,mBAAmB,UAAU,OAAO;GACjE,OAAO,UAAU,SAAS,UAAU,SAAS,eAAe;EAC9D,CAAC;EAED,MAAM,aAAa,eAAe,uBAAuB,MAAM,eAAe;;GAxJ5E,OAAA,UAAA,GAAA,mBAAA,UAAA,MAAA,CAAA,mBAAA,gDAAA,GACsC,CAAA,CAAA,MAAA,KAAA,CAAK,CAAC,QAA5C,gBAAA,UAAA,GAAA,mBAsEQ,SAAA;;IAtEA,OAAK,eAAE,MAAA,GAAA,CAAG,CAAC,EAAC,MAAA,CAAA;;IAClB,mBAkBS,UAAA,EAlBA,OAAK,eAAE,MAAA,GAAA,CAAG,CAAC,EAAC,aAAA,CAAA,EAAA,GAAA;KACnB,mBAIO,QAAA,EAJA,OAAK,eAAA,CAAG,MAAA,GAAA,CAAG,CAAC,EAAC,WAAA,GAAe,MAAA,GAAA,CAAG,CAAC,GAAG,MAAA,KAAA,CAAK,CAAC,cAAc,CAAA,CAAA,EAAA,GAC5D,CAAA,YAES,MAAA,WAAA,GAAA,MAAA;MADP,SAAA,cAAmC,EAAnC,UAAA,GAAA,YAAmC,wBAAnB,WAAA,MAAW,IAAI,CAAA,EAAA,CAAA;;;KAGnC,mBAEM,OAAA,EAFA,OAAK,eAAE,MAAA,GAAA,CAAG,CAAC,EAAC,YAAA,CAAA,EAAA,GAChB,CAAA,mBAAiE,MAAA,EAA5D,OAAK,eAAE,MAAA,GAAA,CAAG,CAAC,EAAC,iBAAA,CAAA,EAAwB,GAAA,gBAAA,WAAA,MAAW,KAAK,GAAA,CAAA,CAAA,GAAA,CAAA;KAE3D,YAQE,MAAA,WAAA,GAAA;MAPC,OAAK,eAAE,MAAA,GAAA,CAAG,CAAC,EAAC,YAAA,CAAA;MACZ,MAAM,MAAA,KAAA;MACP,MAAA;MACA,QAAA;MACA,MAAK;MACL,OAAM;MACL,SAAO,MAAA,WAAA;;;;;;;IAIZ,YAsBW,MAAA,WAAA,GAAA;KAtBD,QAAA;KAAQ,OAAK,eAAE,MAAA,GAAA,CAAG,CAAC,EAAC,WAAA,CAAA;;KAC5B,SAAA,cAoBa,CApBb,YAoBa,YAAA;MApBD,MAAK;MAAO,QAAA;MAAO,MAAK;;MAClC,SAAA,cAkBS,CAlBT,YAkBS,MAAA,YAAA,GAAA;OAjBH,SAAA;OAAJ,KAAI;OACH,OAAO,MAAA,KAAA,CAAK,CAAC;OACb,UAAU,MAAA,KAAA,CAAK,CAAC;OAChB,eAAa,MAAA,KAAA,CAAK,CAAC;OACnB,kBAAc;OACd,kBAAc;;OAEf,SAAA,cASE,CATF,WASE,KAAA,QAAA,WAAA,eAAA,mBAAA;QAR6B,KAAA,MAAA,KAAA,CAAK,CAAC;QAA8B,WAAA,MAAA,KAAA,CAAK,CAAC;QAAyC,gBAAA,MAAA,KAAA,CAAK,CAAC;QAAqC,OAAA,MAAA,KAAA,CAAK,CAAC;QAA8B,SAAA,MAAA,KAAA,CAAK,CAAC;QAAgC,OAAA,MAAA,KAAA,CAAK,CAAC,KAAK;;;;;;;;;;;;IAaxP,mBAwBS,UAAA,EAxBA,OAAK,eAAE,MAAA,GAAA,CAAG,CAAC,EAAC,aAAA,CAAA,EAAA,GACnB,CAAA,mBASO,QAAA,EATA,OAAK,eAAE,MAAA,GAAA,CAAG,CAAC,EAAC,WAAA,CAAA,EAAA,GACD,CAAA,MAAA,KAAA,CAAK,CAAC,YAAtB,UAAA,GAAA,mBAAiD,UAAA,EAAA,KAAA,EAAA,GAAA,CAAjB,gBAAA,QAAM,CAAA,GAAA,EAEtC,KAAA,mBAAA,QAAA,IAAA,GAAA,mBAKO,QAAA,EALA,OAAK,eAAA,GAAK,MAAA,GAAA,CAAG,CAAC,GAAE,WAAA,IAAA,CAAiB,QAAA,MAAO,CAAA,EAAA,GAAA;KAC3B,EAAA,MAAA,KAAA,CAAK,CAAC,mBAAc,YAAiB,MAAA,KAAA,CAAK,CAAC,cAA7D,UAAA,GAAA,mBAEW,UAAA,EAAA,KAAA,EAAA,GAAA,CADT,YAAuB,MAAA,WAAA,GAAA,MAAA;MAAhB,SAAA,cAAQ,CAAA,GAAA,OAAA,OAAA,OAAA,KAAA,CAAR,gBAAA,YAAQ,EAAA,CAAA,EAAA,CAAA;;KAAQ,CAAA,GAAA,OAAA,OAAA,OAAA,KAAA,gBAAA,UACzB,EAAA,EAAA,GAAA,EAAA,KAAA,mBAAA,QAAA,IAAA;KACA,YAAkB,MAAA,WAAA,GAAA,MAAA;MAAX,SAAA,cAAG,CAAA,GAAA,OAAA,OAAA,OAAA,KAAA,CAAH,gBAAA,OAAG,EAAA,CAAA,EAAA,CAAA;;;KAAQ,OAAA,OAAA,OAAA,KAAA,gBAAA,QACpB,EAAA;IAGF,GAAA,CAAA,CAAA,GAAA,CAAA,GAAA,mBAWM,OAAA,EAXA,OAAK,eAAE,MAAA,GAAA,CAAG,CAAC,EAAC,cAAA,CAAA,EAAA,GAChB,CAAA,YAAyF,MAAA,WAAA,GAAA;KAA/E,MAAK;KAAQ,MAAA;KAAM,SAAS,MAAA,KAAA,CAAK,CAAC;KAAU,SAAO,MAAA,WAAA;;KAAa,SAAA,cAAI,CAAA,GAAA,OAAA,OAAA,OAAA,KAAA,CAAJ,gBAAA,QAAI,EAAA,CAAA,EAAA,CAAA;;IAEtE,GAAA,GAAA,CAAA,WAAA,SAAA,CAAA,GAAA,YAAA,SADR,UAAA,GAAA,YAQE,MAAA,WAAA,GAAA;;KANA,MAAK;KACL,MAAK;KACL,OAAM;KACL,MAAM,MAAA,IAAA;KACN,SAAS,MAAA,KAAA,CAAK,CAAC;KACf,SAAO,MAAA,UAAA;;;;;;GAlE2C,GAAA,CAAA,IAAA,CAAA,CAAA,OAAA,MAAA,KAAA,CAAK,CAAC,WAAW,CAAA,CAAA,IAAA,mBAAA,QAAA,IAAA,CAAA,GAAA,IAAA"}
1
+ {"version":3,"file":"batch-edit-form.js","names":[],"sources":["../../../src/components/batch-edit/batch-edit-form.vue"],"sourcesContent":["<template>\n <!-- 表单常驻挂载(快照只在挂载时对传入的 model 拍一次),仅用 v-show 控制显隐 -->\n <aside :class=\"cls.e('form')\" v-if=\"!!props.model\" v-show=\"state.formVisible\">\n <header :class=\"cls.e('form-header')\">\n <span :class=\"[cls.e('form-icon'), bem.is(state.formActionType)]\">\n <u-icon>\n <component :is=\"headerInfo.icon\" />\n </u-icon>\n </span>\n <div :class=\"cls.e('form-title')\">\n <h3 :class=\"cls.e('form-title-text')\">{{ headerInfo.title }}</h3>\n </div>\n <u-button\n :class=\"cls.e('form-close')\"\n :icon=\"Close\"\n text\n circle\n size=\"small\"\n title=\"关闭\"\n @click=\"handleClose\"\n />\n </header>\n\n <u-scroll always :class=\"cls.e('form-body')\">\n <transition name=\"fade\" appear mode=\"out-in\">\n <u-form\n ref=\"formComponentRef\"\n :model=\"props.model\"\n :readonly=\"props.readonly\"\n :label-width=\"props.labelWidth\"\n @field:update=\"handleFieldUpdate\"\n >\n <slot\n v-bind=\"{\n row: state.row,\n parentRow: state.parentRow,\n formActionType: state.formActionType,\n depth: state.depth,\n indexes: state.indexPath,\n index: state.row?.index\n }\"\n />\n </u-form>\n </transition>\n </u-scroll>\n\n <footer :class=\"cls.e('form-footer')\">\n <span :class=\"cls.e('form-hint')\">\n <template v-if=\"props.readonly\"> 只读模式 </template>\n\n <span :class=\"{ [bem.is('concealed')]: !focused }\">\n <template v-if=\"!(state.formActionType === 'update' && props.quickEdit)\">\n <u-kbd>Ctrl + S</u-kbd> 保存 ·\n </template>\n <u-kbd>Esc</u-kbd> 关闭\n </span>\n </span>\n\n <div :class=\"cls.e('form-actions')\">\n <u-button size=\"small\" text :loading=\"state.loading\" @click=\"handleClose\"> 取消 </u-button>\n <u-button\n v-if=\"showSaveBtn\"\n type=\"primary\"\n size=\"small\"\n title=\"保存\"\n :icon=\"Save\"\n :loading=\"state.loading\"\n @click=\"handleSave\"\n />\n </div>\n </footer>\n </aside>\n</template>\n\n<script lang=\"ts\" setup>\nimport { o } from '@cat-kit/core'\nimport { Close, Save } from '@veltra/icons/normal'\nimport { bem } from '@veltra/utils'\nimport { computed, inject, shallowRef, toRef } from 'vue'\n\nimport type { _FormExposed, FormExposed } from '../../types/form'\nimport { UButton } from '../button'\nimport { UForm } from '../form'\nimport { UIcon } from '../icon'\nimport { UKbd } from '../kbd'\nimport { UScroll } from '../scroll'\nimport { BatchEditDIKey } from './di'\nimport { FORM_ACTION_HEADER_MAP } from './form-action-header'\n\ndefineOptions({ name: 'UBatchEditForm' })\n\nconst batchEditCtx = inject(BatchEditDIKey)!\n\nconst { cls, props, state, handleClose, handleSave, staticFeatures, dynamicFeatures, syncing } =\n batchEditCtx\n\nconst focused = toRef(batchEditCtx, 'focused')\n\nconst formComponentRef = shallowRef<FormExposed>()\n\ndefineExpose<_FormExposed>({\n el: toRef(() => formComponentRef.value?.el),\n validate: () => formComponentRef.value?.validate() ?? Promise.resolve(true),\n clearValidate: () => formComponentRef.value?.clearValidate(),\n reset: () => formComponentRef.value?.reset()\n})\n\nfunction handleFieldUpdate(field: string, value: any) {\n // 编程方式重置/回显期间不写回行数据,避免默认值污染源数据\n if (syncing.value) return\n\n if (state.row && props.quickEdit) {\n o(state.row.data).set(field, value)\n }\n}\n\nconst creatable = computed(() => {\n if (state.formActionType !== 'create') return false\n return staticFeatures.value.has('create') || dynamicFeatures.value.create?.(state.row)\n})\n\nconst creatableChild = computed(() => {\n if (state.formActionType !== 'createChild') return false\n return (\n staticFeatures.value.has('createChild') || dynamicFeatures.value.createChild?.(state.parentRow)\n )\n})\n\nconst updatable = computed(() => {\n if (state.formActionType !== 'update') return false\n return staticFeatures.value.has('update') || dynamicFeatures.value.update?.(state.row)\n})\n\nconst showSaveBtn = computed(() => {\n if (props.readonly) return false\n if (props.quickEdit && state.formActionType === 'update') return false\n return creatable.value || updatable.value || creatableChild.value\n})\n\nconst headerInfo = computed(() => FORM_ACTION_HEADER_MAP[state.formActionType])\n</script>\n"],"mappings":";;;;;;;;;;;;;;;;EA2FA,MAAM,eAAe,OAAO,cAAc;EAE1C,MAAM,EAAE,KAAK,OAAO,OAAO,aAAa,YAAY,gBAAgB,iBAAiB,YACnF;EAEF,MAAM,UAAU,MAAM,cAAc,SAAS;EAE7C,MAAM,mBAAmB,WAAwB;EAEjD,SAA2B;GACzB,IAAI,YAAY,iBAAiB,OAAO,EAAE;GAC1C,gBAAgB,iBAAiB,OAAO,SAAS,KAAK,QAAQ,QAAQ,IAAI;GAC1E,qBAAqB,iBAAiB,OAAO,cAAc;GAC3D,aAAa,iBAAiB,OAAO,MAAM;EAC7C,CAAC;EAED,SAAS,kBAAkB,OAAe,OAAY;GAEpD,IAAI,QAAQ,OAAO;GAEnB,IAAI,MAAM,OAAO,MAAM,WACrB,EAAE,MAAM,IAAI,IAAI,CAAC,CAAC,IAAI,OAAO,KAAK;EAEtC;EAEA,MAAM,YAAY,eAAe;GAC/B,IAAI,MAAM,mBAAmB,UAAU,OAAO;GAC9C,OAAO,eAAe,MAAM,IAAI,QAAQ,KAAK,gBAAgB,MAAM,SAAS,MAAM,GAAG;EACvF,CAAC;EAED,MAAM,iBAAiB,eAAe;GACpC,IAAI,MAAM,mBAAmB,eAAe,OAAO;GACnD,OACE,eAAe,MAAM,IAAI,aAAa,KAAK,gBAAgB,MAAM,cAAc,MAAM,SAAS;EAElG,CAAC;EAED,MAAM,YAAY,eAAe;GAC/B,IAAI,MAAM,mBAAmB,UAAU,OAAO;GAC9C,OAAO,eAAe,MAAM,IAAI,QAAQ,KAAK,gBAAgB,MAAM,SAAS,MAAM,GAAG;EACvF,CAAC;EAED,MAAM,cAAc,eAAe;GACjC,IAAI,MAAM,UAAU,OAAO;GAC3B,IAAI,MAAM,aAAa,MAAM,mBAAmB,UAAU,OAAO;GACjE,OAAO,UAAU,SAAS,UAAU,SAAS,eAAe;EAC9D,CAAC;EAED,MAAM,aAAa,eAAe,uBAAuB,MAAM,eAAe;;GA1I5E,OAAA,UAAA,GAAA,mBAAA,UAAA,MAAA,CAAA,mBAAA,gDAAA,GACsC,CAAA,CAAA,MAAA,KAAA,CAAK,CAAC,QAA5C,gBAAA,UAAA,GAAA,mBAqEQ,SAAA;;IArEA,OAAK,eAAE,MAAA,GAAA,CAAG,CAAC,EAAC,MAAA,CAAA;;IAClB,mBAkBS,UAAA,EAlBA,OAAK,eAAE,MAAA,GAAA,CAAG,CAAC,EAAC,aAAA,CAAA,EAAA,GAAA;KACnB,mBAIO,QAAA,EAJA,OAAK,eAAA,CAAG,MAAA,GAAA,CAAG,CAAC,EAAC,WAAA,GAAe,MAAA,GAAA,CAAG,CAAC,GAAG,MAAA,KAAA,CAAK,CAAC,cAAc,CAAA,CAAA,EAAA,GAC5D,CAAA,YAES,MAAA,WAAA,GAAA,MAAA;MADP,SAAA,cAAmC,EAAnC,UAAA,GAAA,YAAmC,wBAAnB,WAAA,MAAW,IAAI,CAAA,EAAA,CAAA;;;KAGnC,mBAEM,OAAA,EAFA,OAAK,eAAE,MAAA,GAAA,CAAG,CAAC,EAAC,YAAA,CAAA,EAAA,GAChB,CAAA,mBAAiE,MAAA,EAA5D,OAAK,eAAE,MAAA,GAAA,CAAG,CAAC,EAAC,iBAAA,CAAA,EAAwB,GAAA,gBAAA,WAAA,MAAW,KAAK,GAAA,CAAA,CAAA,GAAA,CAAA;KAE3D,YAQE,MAAA,WAAA,GAAA;MAPC,OAAK,eAAE,MAAA,GAAA,CAAG,CAAC,EAAC,YAAA,CAAA;MACZ,MAAM,MAAA,KAAA;MACP,MAAA;MACA,QAAA;MACA,MAAK;MACL,OAAM;MACL,SAAO,MAAA,WAAA;;;;;;;IAIZ,YAqBW,MAAA,WAAA,GAAA;KArBD,QAAA;KAAQ,OAAK,eAAE,MAAA,GAAA,CAAG,CAAC,EAAC,WAAA,CAAA;;KAC5B,SAAA,cAmBa,CAnBb,YAmBa,YAAA;MAnBD,MAAK;MAAO,QAAA;MAAO,MAAK;;MAClC,SAAA,cAiBS,CAjBT,YAiBS,MAAA,YAAA,GAAA;OAhBH,SAAA;OAAJ,KAAI;OACH,OAAO,MAAA,KAAA,CAAK,CAAC;OACb,UAAU,MAAA,KAAA,CAAK,CAAC;OAChB,eAAa,MAAA,KAAA,CAAK,CAAC;OACnB,kBAAc;;OAEf,SAAA,cASE,CATF,WASE,KAAA,QAAA,WAAA,eAAA,mBAAA;QAR6B,KAAA,MAAA,KAAA,CAAK,CAAC;QAA8B,WAAA,MAAA,KAAA,CAAK,CAAC;QAAyC,gBAAA,MAAA,KAAA,CAAK,CAAC;QAAqC,OAAA,MAAA,KAAA,CAAK,CAAC;QAA8B,SAAA,MAAA,KAAA,CAAK,CAAC;QAAgC,OAAA,MAAA,KAAA,CAAK,CAAC,KAAK;;;;;;;;;;;;IAaxP,mBAwBS,UAAA,EAxBA,OAAK,eAAE,MAAA,GAAA,CAAG,CAAC,EAAC,aAAA,CAAA,EAAA,GACnB,CAAA,mBASO,QAAA,EATA,OAAK,eAAE,MAAA,GAAA,CAAG,CAAC,EAAC,WAAA,CAAA,EAAA,GACD,CAAA,MAAA,KAAA,CAAK,CAAC,YAAtB,UAAA,GAAA,mBAAiD,UAAA,EAAA,KAAA,EAAA,GAAA,CAAjB,gBAAA,QAAM,CAAA,GAAA,EAEtC,KAAA,mBAAA,QAAA,IAAA,GAAA,mBAKO,QAAA,EALA,OAAK,eAAA,GAAK,MAAA,GAAA,CAAG,CAAC,GAAE,WAAA,IAAA,CAAiB,QAAA,MAAO,CAAA,EAAA,GAAA;KAC3B,EAAA,MAAA,KAAA,CAAK,CAAC,mBAAc,YAAiB,MAAA,KAAA,CAAK,CAAC,cAA7D,UAAA,GAAA,mBAEW,UAAA,EAAA,KAAA,EAAA,GAAA,CADT,YAAuB,MAAA,WAAA,GAAA,MAAA;MAAhB,SAAA,cAAQ,CAAA,GAAA,OAAA,OAAA,OAAA,KAAA,CAAR,gBAAA,YAAQ,EAAA,CAAA,EAAA,CAAA;;KAAQ,CAAA,GAAA,OAAA,OAAA,OAAA,KAAA,gBAAA,UACzB,EAAA,EAAA,GAAA,EAAA,KAAA,mBAAA,QAAA,IAAA;KACA,YAAkB,MAAA,WAAA,GAAA,MAAA;MAAX,SAAA,cAAG,CAAA,GAAA,OAAA,OAAA,OAAA,KAAA,CAAH,gBAAA,OAAG,EAAA,CAAA,EAAA,CAAA;;;KAAQ,OAAA,OAAA,OAAA,KAAA,gBAAA,QACpB,EAAA;IAGF,GAAA,CAAA,CAAA,GAAA,CAAA,GAAA,mBAWM,OAAA,EAXA,OAAK,eAAE,MAAA,GAAA,CAAG,CAAC,EAAC,cAAA,CAAA,EAAA,GAChB,CAAA,YAAyF,MAAA,WAAA,GAAA;KAA/E,MAAK;KAAQ,MAAA;KAAM,SAAS,MAAA,KAAA,CAAK,CAAC;KAAU,SAAO,MAAA,WAAA;;KAAa,SAAA,cAAI,CAAA,GAAA,OAAA,OAAA,OAAA,KAAA,CAAJ,gBAAA,QAAI,EAAA,CAAA,EAAA,CAAA;;IAEtE,GAAA,GAAA,CAAA,WAAA,SAAA,CAAA,GAAA,YAAA,SADR,UAAA,GAAA,YAQE,MAAA,WAAA,GAAA;;KANA,MAAK;KACL,MAAK;KACL,OAAM;KACL,MAAM,MAAA,IAAA;KACN,SAAS,MAAA,KAAA,CAAK,CAAC;KACf,SAAO,MAAA,UAAA;;;;;;GAjE2C,GAAA,CAAA,IAAA,CAAA,CAAA,OAAA,MAAA,KAAA,CAAK,CAAC,WAAW,CAAA,CAAA,IAAA,mBAAA,QAAA,IAAA,CAAA,GAAA,IAAA"}
@@ -46,7 +46,6 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
46
46
  textEllipsis: { type: Boolean }
47
47
  },
48
48
  emits: [
49
- "field:change",
50
49
  "update:data",
51
50
  "create",
52
51
  "create-prev",
@@ -1 +1 @@
1
- {"version":3,"file":"batch-edit.js","names":[],"sources":["../../../src/components/batch-edit/batch-edit.vue"],"sourcesContent":["<template>\n <u-layout\n :class=\"cls.b\"\n :cols=\"cols\"\n rows=\"minmax(0, 1fr) \"\n gap=\"8px\"\n resizable\n tabindex=\"-1\"\n @focusin=\"handleFocusIn\"\n @focusout=\"handleFocusOut\"\n @keydown.capture=\"handleKeydown\"\n >\n <!-- 编辑列表 -->\n <BatchEditList :slots=\"slots\" />\n\n <!-- 编辑表单 -->\n <BatchEditForm ref=\"form\" v-slot=\"scoped\">\n <slot name=\"form\" v-bind=\"scoped\" />\n </BatchEditForm>\n </u-layout>\n</template>\n\n<script lang=\"ts\" setup>\nimport { bem } from '@veltra/utils'\nimport { computed, provide, useTemplateRef } from 'vue'\n\nimport type { BatchEditEmits, BatchEditProps, BatchEditSlots, FormExposed } from '../../types'\nimport { ULayout } from '../layout'\nimport BatchEditForm from './batch-edit-form.vue'\nimport BatchEditList from './batch-edit-list.vue'\nimport { BatchEditDIKey } from './di'\nimport { useEditState } from './use-edit-state'\nimport { useFeatures } from './use-features'\nimport { useHandlers } from './use-handlers'\nimport { useShortcutKey } from './use-shortcut-key'\n\ndefineOptions({ name: 'UBatchEdit' })\n\nconst props = withDefaults(defineProps<BatchEditProps>(), { cols: () => ['1fr', '420px'] })\n\nconst emit = defineEmits<BatchEditEmits>()\n\nconst slots = defineSlots<BatchEditSlots>()\n\nconst cls = bem('batch-edit')\n\nconst formRef = useTemplateRef<FormExposed>('form')\n\nconst { state, resetState, syncing } = useEditState({ props, formRef })\n\nconst { staticFeatures, dynamicFeatures } = useFeatures({ props })\n\nconst handlers = useHandlers({ props, emit, state, resetState, formRef })\n\n// 快捷键处理\nconst { handleFocusIn, handleFocusOut, handleKeydown, focused } = useShortcutKey({\n props,\n onSave: handlers.handleSave,\n onClose: handlers.handleClose,\n state\n})\n\nconst cols = computed(() => {\n return state.formVisible ? props.cols : undefined\n})\n\nprovide(BatchEditDIKey, {\n cls,\n props,\n emit,\n state,\n staticFeatures,\n dynamicFeatures,\n focused,\n syncing,\n ...handlers\n})\n</script>\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAsCA,MAAM,QAAQ;EAEd,MAAM,OAAO;EAEb,MAAM,QAAQ,SAAA;EAEd,MAAM,MAAM,IAAI,YAAY;EAE5B,MAAM,UAAU,eAA4B,MAAM;EAElD,MAAM,EAAE,OAAO,YAAY,YAAY,aAAa;GAAE;GAAO;EAAQ,CAAC;EAEtE,MAAM,EAAE,gBAAgB,oBAAoB,YAAY,EAAE,MAAM,CAAC;EAEjE,MAAM,WAAW,YAAY;GAAE;GAAO;GAAM;GAAO;GAAY;EAAQ,CAAC;EAGxE,MAAM,EAAE,eAAe,gBAAgB,eAAe,YAAY,eAAe;GAC/E;GACA,QAAQ,SAAS;GACjB,SAAS,SAAS;GAClB;EACF,CAAC;EAED,MAAM,OAAO,eAAe;GAC1B,OAAO,MAAM,cAAc,MAAM,OAAO,KAAA;EAC1C,CAAC;EAED,QAAQ,gBAAgB;GACtB;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA,GAAG;EACL,CAAC;;GA3EC,OAAA,UAAA,GAAA,YAkBW,MAAA,WAAA,GAAA;IAjBR,OAAK,eAAE,MAAA,GAAA,CAAG,CAAC,CAAC;IACZ,MAAM,KAAA;IACP,MAAK;IACL,KAAI;IACJ,WAAA;IACA,UAAS;IACR,WAAS,MAAA,aAAA;IACT,YAAU,MAAA,cAAA;IACO,kBAAA,MAAA,aAAA;;IAElB,SAAA,cAAa;KAAb,mBAAA,QAAA;KACA,YAAgC,aAAA,EAAT,MAAK,CAAA;KAE5B,mBAAA,QAAA;KACA,YAEgB,aAAA,EAFD,KAAI,OAAM,GAAA;MACvB,SAAA,SADgC,WAAM,CACtC,WAAoC,KAAA,QAAA,QAAA,eAAA,mBAAV,MAAM,CAAA,CAAA,CAAA,CAAA"}
1
+ {"version":3,"file":"batch-edit.js","names":[],"sources":["../../../src/components/batch-edit/batch-edit.vue"],"sourcesContent":["<template>\n <u-layout\n :class=\"cls.b\"\n :cols=\"cols\"\n rows=\"minmax(0, 1fr) \"\n gap=\"8px\"\n resizable\n tabindex=\"-1\"\n @focusin=\"handleFocusIn\"\n @focusout=\"handleFocusOut\"\n @keydown.capture=\"handleKeydown\"\n >\n <!-- 编辑列表 -->\n <BatchEditList :slots=\"slots\" />\n\n <!-- 编辑表单 -->\n <BatchEditForm ref=\"form\" v-slot=\"scoped\">\n <slot name=\"form\" v-bind=\"scoped\" />\n </BatchEditForm>\n </u-layout>\n</template>\n\n<script lang=\"ts\" setup>\nimport { bem } from '@veltra/utils'\nimport { computed, provide, useTemplateRef } from 'vue'\n\nimport type { BatchEditEmits, BatchEditProps, BatchEditSlots, FormExposed } from '../../types'\nimport { ULayout } from '../layout'\nimport BatchEditForm from './batch-edit-form.vue'\nimport BatchEditList from './batch-edit-list.vue'\nimport { BatchEditDIKey } from './di'\nimport { useEditState } from './use-edit-state'\nimport { useFeatures } from './use-features'\nimport { useHandlers } from './use-handlers'\nimport { useShortcutKey } from './use-shortcut-key'\n\ndefineOptions({ name: 'UBatchEdit' })\n\nconst props = withDefaults(defineProps<BatchEditProps>(), { cols: () => ['1fr', '420px'] })\n\nconst emit = defineEmits<BatchEditEmits>()\n\nconst slots = defineSlots<BatchEditSlots>()\n\nconst cls = bem('batch-edit')\n\nconst formRef = useTemplateRef<FormExposed>('form')\n\nconst { state, resetState, syncing } = useEditState({ props, formRef })\n\nconst { staticFeatures, dynamicFeatures } = useFeatures({ props })\n\nconst handlers = useHandlers({ props, emit, state, resetState, formRef })\n\n// 快捷键处理\nconst { handleFocusIn, handleFocusOut, handleKeydown, focused } = useShortcutKey({\n props,\n onSave: handlers.handleSave,\n onClose: handlers.handleClose,\n state\n})\n\nconst cols = computed(() => {\n return state.formVisible ? props.cols : undefined\n})\n\nprovide(BatchEditDIKey, {\n cls,\n props,\n emit,\n state,\n staticFeatures,\n dynamicFeatures,\n focused,\n syncing,\n ...handlers\n})\n</script>\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAsCA,MAAM,QAAQ;EAEd,MAAM,OAAO;EAEb,MAAM,QAAQ,SAAA;EAEd,MAAM,MAAM,IAAI,YAAY;EAE5B,MAAM,UAAU,eAA4B,MAAM;EAElD,MAAM,EAAE,OAAO,YAAY,YAAY,aAAa;GAAE;GAAO;EAAQ,CAAC;EAEtE,MAAM,EAAE,gBAAgB,oBAAoB,YAAY,EAAE,MAAM,CAAC;EAEjE,MAAM,WAAW,YAAY;GAAE;GAAO;GAAM;GAAO;GAAY;EAAQ,CAAC;EAGxE,MAAM,EAAE,eAAe,gBAAgB,eAAe,YAAY,eAAe;GAC/E;GACA,QAAQ,SAAS;GACjB,SAAS,SAAS;GAClB;EACF,CAAC;EAED,MAAM,OAAO,eAAe;GAC1B,OAAO,MAAM,cAAc,MAAM,OAAO,KAAA;EAC1C,CAAC;EAED,QAAQ,gBAAgB;GACtB;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA,GAAG;EACL,CAAC;;GA3EC,OAAA,UAAA,GAAA,YAkBW,MAAA,WAAA,GAAA;IAjBR,OAAK,eAAE,MAAA,GAAA,CAAG,CAAC,CAAC;IACZ,MAAM,KAAA;IACP,MAAK;IACL,KAAI;IACJ,WAAA;IACA,UAAS;IACR,WAAS,MAAA,aAAA;IACT,YAAU,MAAA,cAAA;IACO,kBAAA,MAAA,aAAA;;IAElB,SAAA,cAAa;KAAb,mBAAA,QAAA;KACA,YAAgC,aAAA,EAAT,MAAK,CAAA;KAE5B,mBAAA,QAAA;KACA,YAEgB,aAAA,EAFD,KAAI,OAAM,GAAA;MACvB,SAAA,SADgC,WAAM,CACtC,WAAoC,KAAA,QAAA,QAAA,eAAA,mBAAV,MAAM,CAAA,CAAA,CAAA,CAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"form.js","names":[],"sources":["../../../src/components/form/form.vue"],"sourcesContent":["<template>\n <u-grid\n tag=\"form\"\n ref=\"grid\"\n @submit.prevent\n gap=\"0 12px\"\n :cols=\"cols || breakpointCols\"\n :class=\"[cls.b, bem.is('readonly', readonly)]\"\n >\n <template\n v-for=\"{ node, isFormItem, formItemProps, field, modelValue } of getSlotsNodes()\"\n :key=\"node.key\"\n >\n <u-form-item v-if=\"!isFormItem && field\" v-bind=\"formItemProps\">\n <component\n :is=\"node\"\n :model-value=\"modelValue ?? o(model ?? {}).get(field)\"\n @update:model-value=\"handleUpdateValue(field, $event)\"\n />\n <div v-if=\"shouldShowModified(field)\" :class=\"cls.e('data-before')\">\n <span :class=\"cls.e('changed-tag')\">{{ modifiedLabel }}</span>\n <component :is=\"node\" readonly :model-value=\"getBaselineFieldValue(field)\" />\n </div>\n </u-form-item>\n\n <component v-else :is=\"node\" />\n </template>\n </u-grid>\n</template>\n\n<script lang=\"tsx\" setup>\nimport { o } from '@cat-kit/core'\nimport { bem } from '@veltra/utils'\nimport { provideFormContext } from '@veltra/utils'\nimport { nextTick, toRef, useTemplateRef } from 'vue'\n\nimport type { BreakCols, FormProps, _FormExposed, FormEmits } from '../../types'\nimport { UFormItem } from '../form-item'\nimport { UGrid } from '../grid'\nimport { isFieldModified } from './is-field-modified'\nimport { useFormFields } from './use-form-fields'\nimport { useNodeInterceptor } from './use-node-interceptor'\n\ndefineOptions({ name: 'UForm' })\n\nconst props = withDefaults(defineProps<FormProps>(), { modifiedLabel: '变更前:' })\n\nconst emit = defineEmits<FormEmits>()\n\ndefineSlots<{ default(props?: { data: Record<string, any> | undefined }): any }>()\n\nconst cls = bem('form')\nconst formItemCls = bem('form-item')\nconst gridRef = useTemplateRef('grid')\n\nconst breakpointCols: BreakCols = { xs: 1, md: 2, lg: 3, xl: 4, default: 4 }\n\nconst {\n validate: runValidate,\n clearValidate,\n reset,\n registerField,\n unregisterField,\n shouldValidate,\n getBaselineModel\n} = useFormFields({ props })\n\nasync function validate(keys?: string[]) {\n const valid = await runValidate(keys)\n if (!valid) {\n await nextTick()\n\n gridRef.value?.el\n ?.querySelector(`.${formItemCls.e('error-text')}`)\n ?.scrollIntoView({ block: 'center' })\n }\n return valid\n}\n\nfunction handleFieldUpdate(field: string, value: any) {\n emit('field:update', field, value)\n}\n\nfunction handleFieldChange(field: string, ...args: any[]) {\n emit('field:change', field, ...args)\n}\n\nprovideFormContext({\n formProps: props,\n registerField,\n unregisterField,\n validateFields: validate,\n shouldValidate,\n handleFieldUpdate,\n handleFieldChange\n})\n\nconst { getSlotsNodes } = useNodeInterceptor()\n\nfunction handleUpdateValue(field: string, value: any) {\n if (!props.model) return\n o(props.model).set(field, value)\n}\n\nfunction getBaselineFieldValue(field: string) {\n return o(getBaselineModel() ?? {}).get(field)\n}\n\nfunction shouldShowModified(field: string) {\n if (!props.showModified) return false\n const baseline = getBaselineModel()\n if (!baseline || !props.model) return false\n const current = o(props.model).get(field)\n const initial = o(baseline).get(field)\n return isFieldModified(current, initial)\n}\n\ndefineExpose<_FormExposed>({ el: toRef(() => gridRef.value?.el), validate, clearValidate, reset })\n</script>\n"],"mappings":""}
1
+ {"version":3,"file":"form.js","names":[],"sources":["../../../src/components/form/form.vue"],"sourcesContent":["<template>\n <u-grid\n tag=\"form\"\n ref=\"grid\"\n @submit.prevent\n gap=\"0 12px\"\n :cols=\"cols || breakpointCols\"\n :class=\"[cls.b, bem.is('readonly', readonly)]\"\n >\n <template\n v-for=\"{ node, isFormItem, formItemProps, field, modelValue, renderKey } of getSlotsNodes()\"\n :key=\"renderKey\"\n >\n <u-form-item v-if=\"!isFormItem && field\" v-bind=\"formItemProps\">\n <component\n :is=\"node\"\n :model-value=\"modelValue ?? o(model ?? {}).get(field)\"\n @update:model-value=\"handleUpdateValue(field, $event)\"\n />\n <div v-if=\"shouldShowModified(field)\" :class=\"cls.e('data-before')\">\n <span :class=\"cls.e('changed-tag')\">{{ modifiedLabel }}</span>\n <component :is=\"createModifiedPreview(node, field)\" />\n </div>\n </u-form-item>\n\n <component v-else :is=\"node\" />\n </template>\n </u-grid>\n</template>\n\n<script lang=\"tsx\" setup>\nimport { o } from '@cat-kit/core'\nimport { bem } from '@veltra/utils'\nimport { provideFormContext } from '@veltra/utils'\nimport { nextTick, toRef, useTemplateRef, type VNode } from 'vue'\n\nimport type { BreakCols, FormProps, _FormExposed, FormEmits } from '../../types'\nimport { UFormItem } from '../form-item'\nimport { UGrid } from '../grid'\nimport { isFieldModified } from './is-field-modified'\nimport { useFormFields } from './use-form-fields'\nimport { forkUnmountedVNode, useNodeInterceptor } from './use-node-interceptor'\n\ndefineOptions({ name: 'UForm' })\n\nconst props = withDefaults(defineProps<FormProps>(), { modifiedLabel: '变更前:' })\n\nconst emit = defineEmits<FormEmits>()\n\ndefineSlots<{ default(props?: { data: Record<string, any> | undefined }): any }>()\n\nconst cls = bem('form')\nconst formItemCls = bem('form-item')\nconst gridRef = useTemplateRef('grid')\n\nconst breakpointCols: BreakCols = { xs: 1, md: 2, lg: 3, xl: 4, default: 4 }\n\nconst {\n validate: runValidate,\n clearValidate,\n reset,\n registerField,\n unregisterField,\n shouldValidate,\n getBaselineModel\n} = useFormFields({ props })\n\nasync function validate(keys?: string[]) {\n const valid = await runValidate(keys)\n if (!valid) {\n await nextTick()\n\n gridRef.value?.el\n ?.querySelector(`.${formItemCls.e('error-text')}`)\n ?.scrollIntoView({ block: 'center' })\n }\n return valid\n}\n\nfunction handleFieldUpdate(field: string, value: any) {\n emit('field:update', field, value)\n}\n\nprovideFormContext({\n formProps: props,\n registerField,\n unregisterField,\n validateFields: validate,\n shouldValidate,\n handleFieldUpdate\n})\n\nconst { getSlotsNodes } = useNodeInterceptor()\n\nfunction handleUpdateValue(field: string, value: any) {\n if (!props.model) return\n o(props.model).set(field, value)\n}\n\nfunction getBaselineFieldValue(field: string) {\n return o(getBaselineModel() ?? {}).get(field)\n}\n\nfunction createModifiedPreview(node: VNode, field: string) {\n return forkUnmountedVNode(node, { readonly: true, modelValue: getBaselineFieldValue(field) })\n}\n\nfunction shouldShowModified(field: string) {\n if (!props.showModified) return false\n const baseline = getBaselineModel()\n if (!baseline || !props.model) return false\n const current = o(props.model).get(field)\n const initial = o(baseline).get(field)\n return isFieldModified(current, initial)\n}\n\ndefineExpose<_FormExposed>({ el: toRef(() => gridRef.value?.el), validate, clearValidate, reset })\n</script>\n"],"mappings":""}
@@ -2,7 +2,7 @@ import _sfc_main from "../grid/grid.js";
2
2
  import form_item_default from "../form-item/form-item.js";
3
3
  import { isFieldModified } from "./is-field-modified.js";
4
4
  import { useFormFields } from "./use-form-fields.js";
5
- import { useNodeInterceptor } from "./use-node-interceptor.js";
5
+ import { forkUnmountedVNode, useNodeInterceptor } from "./use-node-interceptor.js";
6
6
  import { Fragment, createBlock, createCommentVNode, createElementBlock, createElementVNode, defineComponent, mergeProps, nextTick, normalizeClass, openBlock, renderList, resolveDynamicComponent, toDisplayString, toRef, unref, useTemplateRef, withCtx, withModifiers } from "vue";
7
7
  import { bem, provideFormContext } from "@veltra/utils";
8
8
  import { o } from "@cat-kit/core";
@@ -23,7 +23,7 @@ var form_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ defineCompo
23
23
  disabled: { type: Boolean },
24
24
  size: {}
25
25
  },
26
- emits: ["field:change", "field:update"],
26
+ emits: ["field:update"],
27
27
  setup(__props, { expose: __expose, emit: __emit }) {
28
28
  const props = __props;
29
29
  const emit = __emit;
@@ -49,17 +49,13 @@ var form_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ defineCompo
49
49
  function handleFieldUpdate(field, value) {
50
50
  emit("field:update", field, value);
51
51
  }
52
- function handleFieldChange(field, ...args) {
53
- emit("field:change", field, ...args);
54
- }
55
52
  provideFormContext({
56
53
  formProps: props,
57
54
  registerField,
58
55
  unregisterField,
59
56
  validateFields: validate,
60
57
  shouldValidate,
61
- handleFieldUpdate,
62
- handleFieldChange
58
+ handleFieldUpdate
63
59
  });
64
60
  const { getSlotsNodes } = useNodeInterceptor();
65
61
  function handleUpdateValue(field, value) {
@@ -69,6 +65,12 @@ var form_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ defineCompo
69
65
  function getBaselineFieldValue(field) {
70
66
  return o(getBaselineModel() ?? {}).get(field);
71
67
  }
68
+ function createModifiedPreview(node, field) {
69
+ return forkUnmountedVNode(node, {
70
+ readonly: true,
71
+ modelValue: getBaselineFieldValue(field)
72
+ });
73
+ }
72
74
  function shouldShowModified(field) {
73
75
  if (!props.showModified) return false;
74
76
  const baseline = getBaselineModel();
@@ -92,8 +94,8 @@ var form_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ defineCompo
92
94
  cols: __props.cols || breakpointCols,
93
95
  class: normalizeClass([unref(cls).b, unref(bem).is("readonly", __props.readonly)])
94
96
  }, {
95
- default: withCtx(() => [(openBlock(true), createElementBlock(Fragment, null, renderList(unref(getSlotsNodes)(), ({ node, isFormItem, formItemProps, field, modelValue }) => {
96
- return openBlock(), createElementBlock(Fragment, { key: node.key }, [!isFormItem && field ? (openBlock(), createBlock(unref(form_item_default), mergeProps({
97
+ default: withCtx(() => [(openBlock(true), createElementBlock(Fragment, null, renderList(unref(getSlotsNodes)(), ({ node, isFormItem, formItemProps, field, modelValue, renderKey }) => {
98
+ return openBlock(), createElementBlock(Fragment, { key: renderKey }, [!isFormItem && field ? (openBlock(), createBlock(unref(form_item_default), mergeProps({
97
99
  key: 0,
98
100
  ref_for: true
99
101
  }, formItemProps), {
@@ -103,10 +105,7 @@ var form_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ defineCompo
103
105
  }, null, 8, ["model-value", "onUpdate:modelValue"])), shouldShowModified(field) ? (openBlock(), createElementBlock("div", {
104
106
  key: 0,
105
107
  class: normalizeClass(unref(cls).e("data-before"))
106
- }, [createElementVNode("span", { class: normalizeClass(unref(cls).e("changed-tag")) }, toDisplayString(__props.modifiedLabel), 3), (openBlock(), createBlock(resolveDynamicComponent(node), {
107
- readonly: "",
108
- "model-value": getBaselineFieldValue(field)
109
- }, null, 8, ["model-value"]))], 2)) : createCommentVNode("v-if", true)]),
108
+ }, [createElementVNode("span", { class: normalizeClass(unref(cls).e("changed-tag")) }, toDisplayString(__props.modifiedLabel), 3), (openBlock(), createBlock(resolveDynamicComponent(createModifiedPreview(node, field))))], 2)) : createCommentVNode("v-if", true)]),
110
109
  _: 2
111
110
  }, 1040)) : (openBlock(), createBlock(resolveDynamicComponent(node), { key: 1 }))], 64);
112
111
  }), 128))]),
@@ -1 +1 @@
1
- {"version":3,"file":"form.vue_vue_type_script_setup_true_lang.js","names":[],"sources":["../../../src/components/form/form.vue"],"sourcesContent":["<template>\n <u-grid\n tag=\"form\"\n ref=\"grid\"\n @submit.prevent\n gap=\"0 12px\"\n :cols=\"cols || breakpointCols\"\n :class=\"[cls.b, bem.is('readonly', readonly)]\"\n >\n <template\n v-for=\"{ node, isFormItem, formItemProps, field, modelValue } of getSlotsNodes()\"\n :key=\"node.key\"\n >\n <u-form-item v-if=\"!isFormItem && field\" v-bind=\"formItemProps\">\n <component\n :is=\"node\"\n :model-value=\"modelValue ?? o(model ?? {}).get(field)\"\n @update:model-value=\"handleUpdateValue(field, $event)\"\n />\n <div v-if=\"shouldShowModified(field)\" :class=\"cls.e('data-before')\">\n <span :class=\"cls.e('changed-tag')\">{{ modifiedLabel }}</span>\n <component :is=\"node\" readonly :model-value=\"getBaselineFieldValue(field)\" />\n </div>\n </u-form-item>\n\n <component v-else :is=\"node\" />\n </template>\n </u-grid>\n</template>\n\n<script lang=\"tsx\" setup>\nimport { o } from '@cat-kit/core'\nimport { bem } from '@veltra/utils'\nimport { provideFormContext } from '@veltra/utils'\nimport { nextTick, toRef, useTemplateRef } from 'vue'\n\nimport type { BreakCols, FormProps, _FormExposed, FormEmits } from '../../types'\nimport { UFormItem } from '../form-item'\nimport { UGrid } from '../grid'\nimport { isFieldModified } from './is-field-modified'\nimport { useFormFields } from './use-form-fields'\nimport { useNodeInterceptor } from './use-node-interceptor'\n\ndefineOptions({ name: 'UForm' })\n\nconst props = withDefaults(defineProps<FormProps>(), { modifiedLabel: '变更前:' })\n\nconst emit = defineEmits<FormEmits>()\n\ndefineSlots<{ default(props?: { data: Record<string, any> | undefined }): any }>()\n\nconst cls = bem('form')\nconst formItemCls = bem('form-item')\nconst gridRef = useTemplateRef('grid')\n\nconst breakpointCols: BreakCols = { xs: 1, md: 2, lg: 3, xl: 4, default: 4 }\n\nconst {\n validate: runValidate,\n clearValidate,\n reset,\n registerField,\n unregisterField,\n shouldValidate,\n getBaselineModel\n} = useFormFields({ props })\n\nasync function validate(keys?: string[]) {\n const valid = await runValidate(keys)\n if (!valid) {\n await nextTick()\n\n gridRef.value?.el\n ?.querySelector(`.${formItemCls.e('error-text')}`)\n ?.scrollIntoView({ block: 'center' })\n }\n return valid\n}\n\nfunction handleFieldUpdate(field: string, value: any) {\n emit('field:update', field, value)\n}\n\nfunction handleFieldChange(field: string, ...args: any[]) {\n emit('field:change', field, ...args)\n}\n\nprovideFormContext({\n formProps: props,\n registerField,\n unregisterField,\n validateFields: validate,\n shouldValidate,\n handleFieldUpdate,\n handleFieldChange\n})\n\nconst { getSlotsNodes } = useNodeInterceptor()\n\nfunction handleUpdateValue(field: string, value: any) {\n if (!props.model) return\n o(props.model).set(field, value)\n}\n\nfunction getBaselineFieldValue(field: string) {\n return o(getBaselineModel() ?? {}).get(field)\n}\n\nfunction shouldShowModified(field: string) {\n if (!props.showModified) return false\n const baseline = getBaselineModel()\n if (!baseline || !props.model) return false\n const current = o(props.model).get(field)\n const initial = o(baseline).get(field)\n return isFieldModified(current, initial)\n}\n\ndefineExpose<_FormExposed>({ el: toRef(() => gridRef.value?.el), validate, clearValidate, reset })\n</script>\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;EA6CA,MAAM,QAAQ;EAEd,MAAM,OAAO;EAIb,MAAM,MAAM,IAAI,MAAM;EACtB,MAAM,cAAc,IAAI,WAAW;EACnC,MAAM,UAAU,eAAe,MAAM;EAErC,MAAM,iBAA4B;GAAE,IAAI;GAAG,IAAI;GAAG,IAAI;GAAG,IAAI;GAAG,SAAS;EAAE;EAE3E,MAAM,EACJ,UAAU,aACV,eACA,OACA,eACA,iBACA,gBACA,qBACE,cAAc,EAAE,MAAM,CAAC;EAE3B,eAAe,SAAS,MAAiB;GACvC,MAAM,QAAQ,MAAM,YAAY,IAAI;GACpC,IAAI,CAAC,OAAO;IACV,MAAM,SAAS;IAEf,QAAQ,OAAO,IACX,cAAc,IAAI,YAAY,EAAE,YAAY,GAAG,CAAA,EAC/C,eAAe,EAAE,OAAO,SAAS,CAAC;GACxC;GACA,OAAO;EACT;EAEA,SAAS,kBAAkB,OAAe,OAAY;GACpD,KAAK,gBAAgB,OAAO,KAAK;EACnC;EAEA,SAAS,kBAAkB,OAAe,GAAG,MAAa;GACxD,KAAK,gBAAgB,OAAO,GAAG,IAAI;EACrC;EAEA,mBAAmB;GACjB,WAAW;GACX;GACA;GACA,gBAAgB;GAChB;GACA;GACA;EACF,CAAC;EAED,MAAM,EAAE,kBAAkB,mBAAmB;EAE7C,SAAS,kBAAkB,OAAe,OAAY;GACpD,IAAI,CAAC,MAAM,OAAO;GAClB,EAAE,MAAM,KAAK,CAAC,CAAC,IAAI,OAAO,KAAK;EACjC;EAEA,SAAS,sBAAsB,OAAe;GAC5C,OAAO,EAAE,iBAAiB,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,KAAK;EAC9C;EAEA,SAAS,mBAAmB,OAAe;GACzC,IAAI,CAAC,MAAM,cAAc,OAAO;GAChC,MAAM,WAAW,iBAAiB;GAClC,IAAI,CAAC,YAAY,CAAC,MAAM,OAAO,OAAO;GACtC,MAAM,UAAU,EAAE,MAAM,KAAK,CAAC,CAAC,IAAI,KAAK;GACxC,MAAM,UAAU,EAAE,QAAQ,CAAC,CAAC,IAAI,KAAK;GACrC,OAAO,gBAAgB,SAAS,OAAO;EACzC;EAEA,SAA2B;GAAE,IAAI,YAAY,QAAQ,OAAO,EAAE;GAAG;GAAU;GAAe;EAAM,CAAC;;GApH/F,OAAA,UAAA,GAAA,YA0BS,MAAA,SAAA,GAAA;IAzBP,KAAI;IACJ,KAAI;IACH,UAAM,OAAA,OAAA,OAAA,KAAA,oBAAP,CAAA,GAAe,CAAA,SAAA,CAAA;IACf,KAAI;IACH,MAAM,QAAA,QAAQ;IACd,OAAK,eAAA,CAAG,MAAA,GAAA,CAAG,CAAC,GAAG,MAAA,GAAA,CAAG,CAAC,GAAE,YAAa,QAAA,QAAQ,CAAA,CAAA;;IAGzC,SAAA,cAAiF,EADnF,UAAA,IAAA,GAAA,mBAiBW,UAAA,MAAA,WAhBwD,MAAA,aAAA,CAAa,CAAA,IAAA,EAArE,MAAM,YAAY,eAAe,OAAO,iBAAU;KACrD,OAAA,UAAA,GAAA,mBAAA,UAAA,EAAA,KAAA,KAAK,IAAA,GAAA,CAES,CAAA,cAAc,SAAlC,UAAA,GAAA,YAUc,MAAA,iBAAA,GAVd,WAUc;;;KAVmC,GAAA,aAAa,GAAA;MAC5D,SAAA,cAIE,EAJF,UAAA,GAAA,YAIE,wBAHK,IAAI,GAAA;OACR,eAAa,cAAc,MAAA,CAAA,CAAC,CAAC,QAAA,SAAK,CAAA,CAAA,CAAA,CAAQ,IAAI,KAAK;OACnD,wBAAkB,WAAE,kBAAkB,OAAO,MAAM;MAE3C,GAAA,MAAA,GAAA,CAAA,eAAA,qBAAA,CAAA,IAAA,mBAAmB,KAAK,KAAnC,UAAA,GAAA,mBAGM,OAAA;;OAHiC,OAAK,eAAE,MAAA,GAAA,CAAG,CAAC,EAAC,aAAA,CAAA;MACjD,GAAA,CAAA,mBAA8D,QAAA,EAAvD,OAAK,eAAE,MAAA,GAAA,CAAG,CAAC,EAAC,aAAA,CAAA,EAAoB,GAAA,gBAAA,QAAA,aAAa,GAAA,CAAA,IACpD,UAAA,GAAA,YAA6E,wBAA7D,IAAI,GAAA;OAAE,UAAA;OAAU,eAAa,sBAAsB,KAAK;;;KAI5E,GAAA,IAAA,MAAA,UAAA,GAAA,YAA+B,wBAAR,IAAI,GAAA,EAAA,KAAA,EAAA,CAAA,EAAA,GAAA,EAAA"}
1
+ {"version":3,"file":"form.vue_vue_type_script_setup_true_lang.js","names":[],"sources":["../../../src/components/form/form.vue"],"sourcesContent":["<template>\n <u-grid\n tag=\"form\"\n ref=\"grid\"\n @submit.prevent\n gap=\"0 12px\"\n :cols=\"cols || breakpointCols\"\n :class=\"[cls.b, bem.is('readonly', readonly)]\"\n >\n <template\n v-for=\"{ node, isFormItem, formItemProps, field, modelValue, renderKey } of getSlotsNodes()\"\n :key=\"renderKey\"\n >\n <u-form-item v-if=\"!isFormItem && field\" v-bind=\"formItemProps\">\n <component\n :is=\"node\"\n :model-value=\"modelValue ?? o(model ?? {}).get(field)\"\n @update:model-value=\"handleUpdateValue(field, $event)\"\n />\n <div v-if=\"shouldShowModified(field)\" :class=\"cls.e('data-before')\">\n <span :class=\"cls.e('changed-tag')\">{{ modifiedLabel }}</span>\n <component :is=\"createModifiedPreview(node, field)\" />\n </div>\n </u-form-item>\n\n <component v-else :is=\"node\" />\n </template>\n </u-grid>\n</template>\n\n<script lang=\"tsx\" setup>\nimport { o } from '@cat-kit/core'\nimport { bem } from '@veltra/utils'\nimport { provideFormContext } from '@veltra/utils'\nimport { nextTick, toRef, useTemplateRef, type VNode } from 'vue'\n\nimport type { BreakCols, FormProps, _FormExposed, FormEmits } from '../../types'\nimport { UFormItem } from '../form-item'\nimport { UGrid } from '../grid'\nimport { isFieldModified } from './is-field-modified'\nimport { useFormFields } from './use-form-fields'\nimport { forkUnmountedVNode, useNodeInterceptor } from './use-node-interceptor'\n\ndefineOptions({ name: 'UForm' })\n\nconst props = withDefaults(defineProps<FormProps>(), { modifiedLabel: '变更前:' })\n\nconst emit = defineEmits<FormEmits>()\n\ndefineSlots<{ default(props?: { data: Record<string, any> | undefined }): any }>()\n\nconst cls = bem('form')\nconst formItemCls = bem('form-item')\nconst gridRef = useTemplateRef('grid')\n\nconst breakpointCols: BreakCols = { xs: 1, md: 2, lg: 3, xl: 4, default: 4 }\n\nconst {\n validate: runValidate,\n clearValidate,\n reset,\n registerField,\n unregisterField,\n shouldValidate,\n getBaselineModel\n} = useFormFields({ props })\n\nasync function validate(keys?: string[]) {\n const valid = await runValidate(keys)\n if (!valid) {\n await nextTick()\n\n gridRef.value?.el\n ?.querySelector(`.${formItemCls.e('error-text')}`)\n ?.scrollIntoView({ block: 'center' })\n }\n return valid\n}\n\nfunction handleFieldUpdate(field: string, value: any) {\n emit('field:update', field, value)\n}\n\nprovideFormContext({\n formProps: props,\n registerField,\n unregisterField,\n validateFields: validate,\n shouldValidate,\n handleFieldUpdate\n})\n\nconst { getSlotsNodes } = useNodeInterceptor()\n\nfunction handleUpdateValue(field: string, value: any) {\n if (!props.model) return\n o(props.model).set(field, value)\n}\n\nfunction getBaselineFieldValue(field: string) {\n return o(getBaselineModel() ?? {}).get(field)\n}\n\nfunction createModifiedPreview(node: VNode, field: string) {\n return forkUnmountedVNode(node, { readonly: true, modelValue: getBaselineFieldValue(field) })\n}\n\nfunction shouldShowModified(field: string) {\n if (!props.showModified) return false\n const baseline = getBaselineModel()\n if (!baseline || !props.model) return false\n const current = o(props.model).get(field)\n const initial = o(baseline).get(field)\n return isFieldModified(current, initial)\n}\n\ndefineExpose<_FormExposed>({ el: toRef(() => gridRef.value?.el), validate, clearValidate, reset })\n</script>\n"],"mappings":"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"}
@@ -1,7 +1,25 @@
1
- import { useSlots } from "vue";
1
+ import { cloneVNode, useSlots } from "vue";
2
2
  import { extractNormalVNodes } from "@veltra/utils";
3
3
  import { o } from "@cat-kit/core";
4
4
  //#region src/components/form/use-node-interceptor.ts
5
+ function getVNodeTypeName(node) {
6
+ const type = node.type;
7
+ if (typeof type === "string") return type;
8
+ if (typeof type === "object" && type) return type.name || type.__name || "";
9
+ return "";
10
+ }
11
+ /**
12
+ * 从源 vnode 派生一份未挂载副本。
13
+ * Vue 的 cloneVNode 会拷贝 el/component,同一源 vnode 渲两次会把实例从编辑控件抢走。
14
+ */
15
+ function forkUnmountedVNode(node, extra) {
16
+ const forked = extra ? cloneVNode(node, extra) : cloneVNode(node);
17
+ forked.el = null;
18
+ forked.component = null;
19
+ forked.anchor = null;
20
+ forked.suspense = null;
21
+ return forked;
22
+ }
5
23
  /**
6
24
  * 虚拟node拦截
7
25
  * @returns
@@ -13,6 +31,7 @@ function useNodeInterceptor() {
13
31
  if (!nodes?.length) return null;
14
32
  const flattedNodes = extractNormalVNodes(nodes);
15
33
  const results = [];
34
+ const seen = /* @__PURE__ */ new Map();
16
35
  let i = 0;
17
36
  while (i < flattedNodes.length) {
18
37
  const node = flattedNodes[i];
@@ -28,12 +47,21 @@ function useNodeInterceptor() {
28
47
  "readonly",
29
48
  "field"
30
49
  ]);
50
+ let renderKey;
51
+ if (node.key != null) renderKey = node.key;
52
+ else {
53
+ const base = `${getVNodeTypeName(node)}:${field ?? ""}`;
54
+ const n = seen.get(base) ?? 0;
55
+ seen.set(base, n + 1);
56
+ renderKey = n === 0 ? `uform:${base}` : `uform:${base}#${n}`;
57
+ }
31
58
  results.push({
32
59
  isFormItem,
33
60
  formItemProps,
34
61
  node,
35
62
  field,
36
- modelValue: props?.["model-value"] ?? props?.modelValue
63
+ modelValue: props?.["model-value"] ?? props?.modelValue,
64
+ renderKey
37
65
  });
38
66
  }
39
67
  return results;
@@ -41,6 +69,6 @@ function useNodeInterceptor() {
41
69
  return { getSlotsNodes };
42
70
  }
43
71
  //#endregion
44
- export { useNodeInterceptor };
72
+ export { forkUnmountedVNode, useNodeInterceptor };
45
73
 
46
74
  //# sourceMappingURL=use-node-interceptor.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"use-node-interceptor.js","names":[],"sources":["../../../src/components/form/use-node-interceptor.ts"],"sourcesContent":["import { o } from '@cat-kit/core'\nimport { extractNormalVNodes } from '@veltra/utils'\nimport { useSlots, type VNode } from 'vue'\n\nexport type SlotRenderItem = {\n isFormItem: boolean\n formItemProps?: Record<string, any>\n node: VNode\n field?: string\n modelValue?: any\n}\n\n/**\n * 虚拟node拦截\n * @returns\n */\nexport function useNodeInterceptor(): { getSlotsNodes: () => SlotRenderItem[] | null } {\n const slots = useSlots()\n\n function getSlotsNodes() {\n const nodes = slots.default?.()\n if (!nodes?.length) return null\n\n const flattedNodes = extractNormalVNodes(nodes)\n\n const results: SlotRenderItem[] = []\n\n let i = 0\n while (i < flattedNodes.length) {\n const node = flattedNodes[i]!\n i++\n\n const { props, type } = node\n const field = props?.field as string | undefined\n const isFormItem = (type as any)?.name === 'UFormItem'\n\n const formItemProps = o((props ?? {}) as Record<string, any>).pick([\n 'label',\n 'rules',\n 'span',\n 'tips',\n 'readonly',\n 'field'\n ]) as Record<string, any>\n\n results.push({\n isFormItem,\n formItemProps,\n node,\n field,\n modelValue: props?.['model-value'] ?? props?.modelValue\n })\n }\n\n return results\n }\n\n return { getSlotsNodes }\n}\n"],"mappings":";;;;;;;;AAgBA,SAAgB,qBAAuE;CACrF,MAAM,QAAQ,SAAS;CAEvB,SAAS,gBAAgB;EACvB,MAAM,QAAQ,MAAM,UAAU;EAC9B,IAAI,CAAC,OAAO,QAAQ,OAAO;EAE3B,MAAM,eAAe,oBAAoB,KAAK;EAE9C,MAAM,UAA4B,CAAC;EAEnC,IAAI,IAAI;EACR,OAAO,IAAI,aAAa,QAAQ;GAC9B,MAAM,OAAO,aAAa;GAC1B;GAEA,MAAM,EAAE,OAAO,SAAS;GACxB,MAAM,QAAQ,OAAO;GACrB,MAAM,aAAc,MAAc,SAAS;GAE3C,MAAM,gBAAgB,EAAG,SAAS,CAAC,CAAyB,CAAC,CAAC,KAAK;IACjE;IACA;IACA;IACA;IACA;IACA;GACF,CAAC;GAED,QAAQ,KAAK;IACX;IACA;IACA;IACA;IACA,YAAY,QAAQ,kBAAkB,OAAO;GAC/C,CAAC;EACH;EAEA,OAAO;CACT;CAEA,OAAO,EAAE,cAAc;AACzB"}
1
+ {"version":3,"file":"use-node-interceptor.js","names":[],"sources":["../../../src/components/form/use-node-interceptor.ts"],"sourcesContent":["import { o } from '@cat-kit/core'\nimport { extractNormalVNodes } from '@veltra/utils'\nimport { cloneVNode, useSlots, type VNode } from 'vue'\n\nexport type SlotRenderItem = {\n isFormItem: boolean\n formItemProps?: Record<string, any>\n node: VNode\n field?: string\n modelValue?: any\n /** v-for 身份:调用方 :key 优先,否则 type+field+出现次序 */\n renderKey: string | number | symbol\n}\n\nfunction getVNodeTypeName(node: VNode): string {\n const type = node.type as { name?: string; __name?: string } | string\n if (typeof type === 'string') return type\n if (typeof type === 'object' && type) return type.name || type.__name || ''\n return ''\n}\n\n/**\n * 从源 vnode 派生一份未挂载副本。\n * Vue 的 cloneVNode 会拷贝 el/component,同一源 vnode 渲两次会把实例从编辑控件抢走。\n */\nexport function forkUnmountedVNode(node: VNode, extra?: Record<string, unknown>): VNode {\n const forked = extra ? cloneVNode(node, extra) : cloneVNode(node)\n forked.el = null\n forked.component = null\n forked.anchor = null\n forked.suspense = null\n return forked\n}\n\n/**\n * 虚拟node拦截\n * @returns\n */\nexport function useNodeInterceptor(): { getSlotsNodes: () => SlotRenderItem[] | null } {\n const slots = useSlots()\n\n function getSlotsNodes() {\n const nodes = slots.default?.()\n if (!nodes?.length) return null\n\n const flattedNodes = extractNormalVNodes(nodes)\n\n const results: SlotRenderItem[] = []\n const seen = new Map<string, number>()\n\n let i = 0\n while (i < flattedNodes.length) {\n const node = flattedNodes[i]!\n i++\n\n const { props, type } = node\n const field = props?.field as string | undefined\n const isFormItem = (type as any)?.name === 'UFormItem'\n\n const formItemProps = o((props ?? {}) as Record<string, any>).pick([\n 'label',\n 'rules',\n 'span',\n 'tips',\n 'readonly',\n 'field'\n ]) as Record<string, any>\n\n let renderKey: string | number | symbol\n if (node.key != null) {\n renderKey = node.key\n } else {\n const base = `${getVNodeTypeName(node)}:${field ?? ''}`\n const n = seen.get(base) ?? 0\n seen.set(base, n + 1)\n renderKey = n === 0 ? `uform:${base}` : `uform:${base}#${n}`\n }\n\n results.push({\n isFormItem,\n formItemProps,\n node,\n field,\n modelValue: props?.['model-value'] ?? props?.modelValue,\n renderKey\n })\n }\n\n return results\n }\n\n return { getSlotsNodes }\n}\n"],"mappings":";;;;AAcA,SAAS,iBAAiB,MAAqB;CAC7C,MAAM,OAAO,KAAK;CAClB,IAAI,OAAO,SAAS,UAAU,OAAO;CACrC,IAAI,OAAO,SAAS,YAAY,MAAM,OAAO,KAAK,QAAQ,KAAK,UAAU;CACzE,OAAO;AACT;;;;;AAMA,SAAgB,mBAAmB,MAAa,OAAwC;CACtF,MAAM,SAAS,QAAQ,WAAW,MAAM,KAAK,IAAI,WAAW,IAAI;CAChE,OAAO,KAAK;CACZ,OAAO,YAAY;CACnB,OAAO,SAAS;CAChB,OAAO,WAAW;CAClB,OAAO;AACT;;;;;AAMA,SAAgB,qBAAuE;CACrF,MAAM,QAAQ,SAAS;CAEvB,SAAS,gBAAgB;EACvB,MAAM,QAAQ,MAAM,UAAU;EAC9B,IAAI,CAAC,OAAO,QAAQ,OAAO;EAE3B,MAAM,eAAe,oBAAoB,KAAK;EAE9C,MAAM,UAA4B,CAAC;EACnC,MAAM,uBAAO,IAAI,IAAoB;EAErC,IAAI,IAAI;EACR,OAAO,IAAI,aAAa,QAAQ;GAC9B,MAAM,OAAO,aAAa;GAC1B;GAEA,MAAM,EAAE,OAAO,SAAS;GACxB,MAAM,QAAQ,OAAO;GACrB,MAAM,aAAc,MAAc,SAAS;GAE3C,MAAM,gBAAgB,EAAG,SAAS,CAAC,CAAyB,CAAC,CAAC,KAAK;IACjE;IACA;IACA;IACA;IACA;IACA;GACF,CAAC;GAED,IAAI;GACJ,IAAI,KAAK,OAAO,MACd,YAAY,KAAK;QACZ;IACL,MAAM,OAAO,GAAG,iBAAiB,IAAI,EAAE,GAAG,SAAS;IACnD,MAAM,IAAI,KAAK,IAAI,IAAI,KAAK;IAC5B,KAAK,IAAI,MAAM,IAAI,CAAC;IACpB,YAAY,MAAM,IAAI,SAAS,SAAS,SAAS,KAAK,GAAG;GAC3D;GAEA,QAAQ,KAAK;IACX;IACA;IACA;IACA;IACA,YAAY,QAAQ,kBAAkB,OAAO;IAC7C;GACF,CAAC;EACH;EAEA,OAAO;CACT;CAEA,OAAO,EAAE,cAAc;AACzB"}
@@ -1 +1 @@
1
- {"version":3,"file":"form-item.js","names":[],"sources":["../../../src/components/form-item/form-item.vue"],"sourcesContent":["<template>\n <u-grid-item :span=\"span\" :class=\"className\">\n <label\n v-if=\"props.label || $slots.label\"\n :class=\"[cls.e('label'), bem.is('required', !!rules?.required)]\"\n :style=\"labelStyles\"\n >\n <u-tip v-if=\"tips\" :content=\"tips\" :class=\"cls.e('tips')\" :show-delay=\"500\">\n <span :class=\"cls.e('tip-label')\">\n <slot name=\"label\">{{ label }}{{ labelPosition === 'top' ? '' : ':' }}</slot>\n </span>\n </u-tip>\n <template v-else>\n <slot name=\"label\">{{ label }}{{ labelPosition === 'top' ? '' : ':' }}</slot>\n </template>\n </label>\n\n <section :class=\"cls.e('wrapper')\">\n <div :class=\"cls.e('content')\">\n <ContentSlot>\n <slot></slot>\n </ContentSlot>\n </div>\n\n <!-- 只有表单控件处于非只读状态时,才显示错误提示 -->\n <section :class=\"cls.e('error')\" v-if=\"!readonly && !formProps?.noTips\">\n <transition name=\"form-item-tips\" mode=\"out-in\">\n <span :class=\"cls.e('error-text')\" v-if=\"!!errorTip\">\n {{ errorTip }}\n </span>\n </transition>\n </section>\n </section>\n </u-grid-item>\n</template>\n\n<script lang=\"tsx\" setup>\nimport { o } from '@cat-kit/core'\nimport { useConfig, useFallbackProps } from '@veltra/compositions'\nimport { bem, extractNormalVNodes, withUnit } from '@veltra/utils'\nimport { injectFormContext } from '@veltra/utils'\nimport {\n type CSSProperties,\n type SetupContext,\n type VNode,\n cloneVNode,\n computed,\n onBeforeUnmount,\n shallowRef,\n watch\n} from 'vue'\n\nimport type { FormItemEmits, FormItemProps, ComponentSize } from '../../types'\nimport { UGridItem } from '../grid'\nimport { UTip } from '../tip'\nimport { formItemCls as cls, defineField } from './helper'\nimport { validateField } from './validate'\n\ndefineOptions({ name: 'UFormItem' })\n\nconst props = withDefaults(defineProps<FormItemProps>(), { readonly: undefined })\n\nconst emit = defineEmits<FormItemEmits>()\n\ndefineSlots<{\n /** 标签插槽 */\n label?: () => any\n default?: () => any\n}>()\n\n/** 表单组件上下文 */\nconst {\n formProps,\n registerField,\n unregisterField,\n shouldValidate,\n handleFieldUpdate,\n handleFieldChange\n} = injectFormContext()\n\nfunction wrapControlChange(node: VNode) {\n if (!node || typeof node.type === 'symbol') return node\n\n return cloneVNode(node, {\n onChange: (...args: any[]) => {\n emit('change', ...args)\n if (props.field) handleFieldChange?.(props.field, ...args)\n }\n })\n}\n\n/**\n * 承载默认插槽并拦截控件 change。\n * 必须经填充插槽接收内容(而非直接读 UFormItem 的 $slots):\n * 读父级 $slots 时组件自身没有更新路径,父级重渲染后动态 props(disabled、data 等)会冻结在首帧。\n */\nfunction ContentSlot(_: unknown, { slots }: SetupContext) {\n return extractNormalVNodes(slots.default?.() ?? []).map(wrapControlChange)\n}\n\nconst { config } = useConfig()\n\nconst { size, readonly } = useFallbackProps([formProps ?? {}, props], {\n size: 'default' as ComponentSize,\n readonly: false\n})\n\nconst errorTip = shallowRef<string>()\n/** 递增序号,丢弃过期的异步校验结果 */\nlet validateSeq = 0\n/** 最近一次 validate 的 Promise,供过期调用等待最新结果 */\nlet latestValidatePromise: Promise<boolean> = Promise.resolve(true)\n\nconst labelPosition = computed(() => props.labelPosition ?? formProps?.labelPosition ?? 'left')\n\nconst className = computed(() => {\n return [\n cls.b,\n cls.m(size.value),\n bem.is('error', !!errorTip.value),\n bem.is('label-top', labelPosition.value === 'top')\n ].join(' ')\n})\n\n/** label样式 */\nconst labelStyles = computed<CSSProperties>(() => {\n if (labelPosition.value === 'top') return {}\n return {\n width: withUnit(props.labelWidth ?? formProps?.labelWidth ?? config.form.labelWidth, 'px')\n }\n})\n\nconst fieldItem = defineField({\n clearValidate() {\n errorTip.value = ''\n },\n async validate() {\n if (!props.field || !formProps?.model || !props.rules || !shouldValidate?.()) return true\n\n const seq = ++validateSeq\n\n latestValidatePromise = validateField(formProps.model!, props.field!, props.rules!).then(\n (tip) => {\n if (seq !== validateSeq) return latestValidatePromise\n\n errorTip.value = tip\n return !errorTip.value\n }\n )\n return latestValidatePromise\n }\n})\n\nwatch(\n () => props.field,\n (field, oldField) => {\n if (oldField) {\n unregisterField?.(oldField)\n }\n if (field) {\n registerField?.(field, fieldItem)\n }\n },\n { immediate: true }\n)\n\nlet stopWatchFieldValue: (() => void) | undefined\n\nwatch(\n [() => formProps?.model, () => props.field],\n ([model, field]) => {\n stopWatchFieldValue?.()\n\n if (!field || !model) return\n\n stopWatchFieldValue = watch(\n () => o(model).get(field),\n (value) => {\n handleFieldUpdate?.(field, value)\n fieldItem.validate()\n }\n )\n },\n { immediate: true }\n)\n\nonBeforeUnmount(() => {\n props.field && unregisterField?.(props.field)\n})\n</script>\n"],"mappings":""}
1
+ {"version":3,"file":"form-item.js","names":[],"sources":["../../../src/components/form-item/form-item.vue"],"sourcesContent":["<template>\n <u-grid-item :span=\"span\" :class=\"className\">\n <label\n v-if=\"props.label || $slots.label\"\n :class=\"[cls.e('label'), bem.is('required', !!rules?.required)]\"\n :style=\"labelStyles\"\n >\n <u-tip v-if=\"tips\" :content=\"tips\" :class=\"cls.e('tips')\" :show-delay=\"500\">\n <span :class=\"cls.e('tip-label')\">\n <slot name=\"label\">{{ label }}{{ labelPosition === 'top' ? '' : ':' }}</slot>\n </span>\n </u-tip>\n <template v-else>\n <slot name=\"label\">{{ label }}{{ labelPosition === 'top' ? '' : ':' }}</slot>\n </template>\n </label>\n\n <section :class=\"cls.e('wrapper')\">\n <div :class=\"cls.e('content')\">\n <ContentSlot>\n <slot></slot>\n </ContentSlot>\n </div>\n\n <!-- 只有表单控件处于非只读状态时,才显示错误提示 -->\n <section :class=\"cls.e('error')\" v-if=\"!readonly && !formProps?.noTips\">\n <transition name=\"form-item-tips\" mode=\"out-in\">\n <span :class=\"cls.e('error-text')\" v-if=\"!!errorTip\">\n {{ errorTip }}\n </span>\n </transition>\n </section>\n </section>\n </u-grid-item>\n</template>\n\n<script lang=\"tsx\" setup>\nimport { o } from '@cat-kit/core'\nimport { useConfig, useFallbackProps } from '@veltra/compositions'\nimport { bem, extractNormalVNodes, withUnit } from '@veltra/utils'\nimport { injectFormContext } from '@veltra/utils'\nimport {\n type CSSProperties,\n type SetupContext,\n type VNode,\n cloneVNode,\n computed,\n onBeforeUnmount,\n shallowRef,\n watch\n} from 'vue'\n\nimport type { FormItemEmits, FormItemProps, ComponentSize } from '../../types'\nimport { UGridItem } from '../grid'\nimport { UTip } from '../tip'\nimport { formItemCls as cls, defineField } from './helper'\nimport { validateField } from './validate'\n\ndefineOptions({ name: 'UFormItem' })\n\nconst props = withDefaults(defineProps<FormItemProps>(), { readonly: undefined })\n\nconst emit = defineEmits<FormItemEmits>()\n\ndefineSlots<{\n /** 标签插槽 */\n label?: () => any\n default?: () => any\n}>()\n\n/** 表单组件上下文 */\nconst { formProps, registerField, unregisterField, shouldValidate, handleFieldUpdate } =\n injectFormContext()\n\nfunction wrapControlChange(node: VNode) {\n if (!node || typeof node.type === 'symbol') return node\n\n return cloneVNode(node, {\n onChange: (...args: any[]) => {\n emit('change', ...args)\n }\n })\n}\n\n/**\n * 承载默认插槽并拦截控件 change。\n * 必须经填充插槽接收内容(而非直接读 UFormItem 的 $slots):\n * 读父级 $slots 时组件自身没有更新路径,父级重渲染后动态 props(disabled、data 等)会冻结在首帧。\n */\nfunction ContentSlot(_: unknown, { slots }: SetupContext) {\n return extractNormalVNodes(slots.default?.() ?? []).map(wrapControlChange)\n}\n\nconst { config } = useConfig()\n\nconst { size, readonly } = useFallbackProps([formProps ?? {}, props], {\n size: 'default' as ComponentSize,\n readonly: false\n})\n\nconst errorTip = shallowRef<string>()\n/** 递增序号,丢弃过期的异步校验结果 */\nlet validateSeq = 0\n/** 最近一次 validate 的 Promise,供过期调用等待最新结果 */\nlet latestValidatePromise: Promise<boolean> = Promise.resolve(true)\n\nconst labelPosition = computed(() => props.labelPosition ?? formProps?.labelPosition ?? 'left')\n\nconst className = computed(() => {\n return [\n cls.b,\n cls.m(size.value),\n bem.is('error', !!errorTip.value),\n bem.is('label-top', labelPosition.value === 'top')\n ].join(' ')\n})\n\n/** label样式 */\nconst labelStyles = computed<CSSProperties>(() => {\n if (labelPosition.value === 'top') return {}\n return {\n width: withUnit(props.labelWidth ?? formProps?.labelWidth ?? config.form.labelWidth, 'px')\n }\n})\n\nconst fieldItem = defineField({\n clearValidate() {\n errorTip.value = ''\n },\n async validate() {\n if (!props.field || !formProps?.model || !props.rules || !shouldValidate?.()) return true\n\n const seq = ++validateSeq\n\n latestValidatePromise = validateField(formProps.model!, props.field!, props.rules!).then(\n (tip) => {\n if (seq !== validateSeq) return latestValidatePromise\n\n errorTip.value = tip\n return !errorTip.value\n }\n )\n return latestValidatePromise\n }\n})\n\nwatch(\n () => props.field,\n (field, oldField) => {\n if (oldField) {\n unregisterField?.(oldField)\n }\n if (field) {\n registerField?.(field, fieldItem)\n }\n },\n { immediate: true }\n)\n\nlet stopWatchFieldValue: (() => void) | undefined\n\nwatch(\n [() => formProps?.model, () => props.field],\n ([model, field]) => {\n stopWatchFieldValue?.()\n\n if (!field || !model) return\n\n stopWatchFieldValue = watch(\n () => o(model).get(field),\n (value) => {\n handleFieldUpdate?.(field, value)\n fieldItem.validate()\n }\n )\n },\n { immediate: true }\n)\n\nonBeforeUnmount(() => {\n props.field && unregisterField?.(props.field)\n})\n</script>\n"],"mappings":""}
@@ -30,12 +30,11 @@ var form_item_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ define
30
30
  const props = __props;
31
31
  const emit = __emit;
32
32
  /** 表单组件上下文 */
33
- const { formProps, registerField, unregisterField, shouldValidate, handleFieldUpdate, handleFieldChange } = injectFormContext();
33
+ const { formProps, registerField, unregisterField, shouldValidate, handleFieldUpdate } = injectFormContext();
34
34
  function wrapControlChange(node) {
35
35
  if (!node || typeof node.type === "symbol") return node;
36
36
  return cloneVNode(node, { onChange: (...args) => {
37
37
  emit("change", ...args);
38
- if (props.field) handleFieldChange?.(props.field, ...args);
39
38
  } });
40
39
  }
41
40
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"form-item.vue_vue_type_script_setup_true_lang.js","names":["$slots"],"sources":["../../../src/components/form-item/form-item.vue"],"sourcesContent":["<template>\n <u-grid-item :span=\"span\" :class=\"className\">\n <label\n v-if=\"props.label || $slots.label\"\n :class=\"[cls.e('label'), bem.is('required', !!rules?.required)]\"\n :style=\"labelStyles\"\n >\n <u-tip v-if=\"tips\" :content=\"tips\" :class=\"cls.e('tips')\" :show-delay=\"500\">\n <span :class=\"cls.e('tip-label')\">\n <slot name=\"label\">{{ label }}{{ labelPosition === 'top' ? '' : ':' }}</slot>\n </span>\n </u-tip>\n <template v-else>\n <slot name=\"label\">{{ label }}{{ labelPosition === 'top' ? '' : ':' }}</slot>\n </template>\n </label>\n\n <section :class=\"cls.e('wrapper')\">\n <div :class=\"cls.e('content')\">\n <ContentSlot>\n <slot></slot>\n </ContentSlot>\n </div>\n\n <!-- 只有表单控件处于非只读状态时,才显示错误提示 -->\n <section :class=\"cls.e('error')\" v-if=\"!readonly && !formProps?.noTips\">\n <transition name=\"form-item-tips\" mode=\"out-in\">\n <span :class=\"cls.e('error-text')\" v-if=\"!!errorTip\">\n {{ errorTip }}\n </span>\n </transition>\n </section>\n </section>\n </u-grid-item>\n</template>\n\n<script lang=\"tsx\" setup>\nimport { o } from '@cat-kit/core'\nimport { useConfig, useFallbackProps } from '@veltra/compositions'\nimport { bem, extractNormalVNodes, withUnit } from '@veltra/utils'\nimport { injectFormContext } from '@veltra/utils'\nimport {\n type CSSProperties,\n type SetupContext,\n type VNode,\n cloneVNode,\n computed,\n onBeforeUnmount,\n shallowRef,\n watch\n} from 'vue'\n\nimport type { FormItemEmits, FormItemProps, ComponentSize } from '../../types'\nimport { UGridItem } from '../grid'\nimport { UTip } from '../tip'\nimport { formItemCls as cls, defineField } from './helper'\nimport { validateField } from './validate'\n\ndefineOptions({ name: 'UFormItem' })\n\nconst props = withDefaults(defineProps<FormItemProps>(), { readonly: undefined })\n\nconst emit = defineEmits<FormItemEmits>()\n\ndefineSlots<{\n /** 标签插槽 */\n label?: () => any\n default?: () => any\n}>()\n\n/** 表单组件上下文 */\nconst {\n formProps,\n registerField,\n unregisterField,\n shouldValidate,\n handleFieldUpdate,\n handleFieldChange\n} = injectFormContext()\n\nfunction wrapControlChange(node: VNode) {\n if (!node || typeof node.type === 'symbol') return node\n\n return cloneVNode(node, {\n onChange: (...args: any[]) => {\n emit('change', ...args)\n if (props.field) handleFieldChange?.(props.field, ...args)\n }\n })\n}\n\n/**\n * 承载默认插槽并拦截控件 change。\n * 必须经填充插槽接收内容(而非直接读 UFormItem 的 $slots):\n * 读父级 $slots 时组件自身没有更新路径,父级重渲染后动态 props(disabled、data 等)会冻结在首帧。\n */\nfunction ContentSlot(_: unknown, { slots }: SetupContext) {\n return extractNormalVNodes(slots.default?.() ?? []).map(wrapControlChange)\n}\n\nconst { config } = useConfig()\n\nconst { size, readonly } = useFallbackProps([formProps ?? {}, props], {\n size: 'default' as ComponentSize,\n readonly: false\n})\n\nconst errorTip = shallowRef<string>()\n/** 递增序号,丢弃过期的异步校验结果 */\nlet validateSeq = 0\n/** 最近一次 validate 的 Promise,供过期调用等待最新结果 */\nlet latestValidatePromise: Promise<boolean> = Promise.resolve(true)\n\nconst labelPosition = computed(() => props.labelPosition ?? formProps?.labelPosition ?? 'left')\n\nconst className = computed(() => {\n return [\n cls.b,\n cls.m(size.value),\n bem.is('error', !!errorTip.value),\n bem.is('label-top', labelPosition.value === 'top')\n ].join(' ')\n})\n\n/** label样式 */\nconst labelStyles = computed<CSSProperties>(() => {\n if (labelPosition.value === 'top') return {}\n return {\n width: withUnit(props.labelWidth ?? formProps?.labelWidth ?? config.form.labelWidth, 'px')\n }\n})\n\nconst fieldItem = defineField({\n clearValidate() {\n errorTip.value = ''\n },\n async validate() {\n if (!props.field || !formProps?.model || !props.rules || !shouldValidate?.()) return true\n\n const seq = ++validateSeq\n\n latestValidatePromise = validateField(formProps.model!, props.field!, props.rules!).then(\n (tip) => {\n if (seq !== validateSeq) return latestValidatePromise\n\n errorTip.value = tip\n return !errorTip.value\n }\n )\n return latestValidatePromise\n }\n})\n\nwatch(\n () => props.field,\n (field, oldField) => {\n if (oldField) {\n unregisterField?.(oldField)\n }\n if (field) {\n registerField?.(field, fieldItem)\n }\n },\n { immediate: true }\n)\n\nlet stopWatchFieldValue: (() => void) | undefined\n\nwatch(\n [() => formProps?.model, () => props.field],\n ([model, field]) => {\n stopWatchFieldValue?.()\n\n if (!field || !model) return\n\n stopWatchFieldValue = watch(\n () => o(model).get(field),\n (value) => {\n handleFieldUpdate?.(field, value)\n fieldItem.validate()\n }\n )\n },\n { immediate: true }\n)\n\nonBeforeUnmount(() => {\n props.field && unregisterField?.(props.field)\n})\n</script>\n"],"mappings":"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"}
1
+ {"version":3,"file":"form-item.vue_vue_type_script_setup_true_lang.js","names":["$slots"],"sources":["../../../src/components/form-item/form-item.vue"],"sourcesContent":["<template>\n <u-grid-item :span=\"span\" :class=\"className\">\n <label\n v-if=\"props.label || $slots.label\"\n :class=\"[cls.e('label'), bem.is('required', !!rules?.required)]\"\n :style=\"labelStyles\"\n >\n <u-tip v-if=\"tips\" :content=\"tips\" :class=\"cls.e('tips')\" :show-delay=\"500\">\n <span :class=\"cls.e('tip-label')\">\n <slot name=\"label\">{{ label }}{{ labelPosition === 'top' ? '' : ':' }}</slot>\n </span>\n </u-tip>\n <template v-else>\n <slot name=\"label\">{{ label }}{{ labelPosition === 'top' ? '' : ':' }}</slot>\n </template>\n </label>\n\n <section :class=\"cls.e('wrapper')\">\n <div :class=\"cls.e('content')\">\n <ContentSlot>\n <slot></slot>\n </ContentSlot>\n </div>\n\n <!-- 只有表单控件处于非只读状态时,才显示错误提示 -->\n <section :class=\"cls.e('error')\" v-if=\"!readonly && !formProps?.noTips\">\n <transition name=\"form-item-tips\" mode=\"out-in\">\n <span :class=\"cls.e('error-text')\" v-if=\"!!errorTip\">\n {{ errorTip }}\n </span>\n </transition>\n </section>\n </section>\n </u-grid-item>\n</template>\n\n<script lang=\"tsx\" setup>\nimport { o } from '@cat-kit/core'\nimport { useConfig, useFallbackProps } from '@veltra/compositions'\nimport { bem, extractNormalVNodes, withUnit } from '@veltra/utils'\nimport { injectFormContext } from '@veltra/utils'\nimport {\n type CSSProperties,\n type SetupContext,\n type VNode,\n cloneVNode,\n computed,\n onBeforeUnmount,\n shallowRef,\n watch\n} from 'vue'\n\nimport type { FormItemEmits, FormItemProps, ComponentSize } from '../../types'\nimport { UGridItem } from '../grid'\nimport { UTip } from '../tip'\nimport { formItemCls as cls, defineField } from './helper'\nimport { validateField } from './validate'\n\ndefineOptions({ name: 'UFormItem' })\n\nconst props = withDefaults(defineProps<FormItemProps>(), { readonly: undefined })\n\nconst emit = defineEmits<FormItemEmits>()\n\ndefineSlots<{\n /** 标签插槽 */\n label?: () => any\n default?: () => any\n}>()\n\n/** 表单组件上下文 */\nconst { formProps, registerField, unregisterField, shouldValidate, handleFieldUpdate } =\n injectFormContext()\n\nfunction wrapControlChange(node: VNode) {\n if (!node || typeof node.type === 'symbol') return node\n\n return cloneVNode(node, {\n onChange: (...args: any[]) => {\n emit('change', ...args)\n }\n })\n}\n\n/**\n * 承载默认插槽并拦截控件 change。\n * 必须经填充插槽接收内容(而非直接读 UFormItem 的 $slots):\n * 读父级 $slots 时组件自身没有更新路径,父级重渲染后动态 props(disabled、data 等)会冻结在首帧。\n */\nfunction ContentSlot(_: unknown, { slots }: SetupContext) {\n return extractNormalVNodes(slots.default?.() ?? []).map(wrapControlChange)\n}\n\nconst { config } = useConfig()\n\nconst { size, readonly } = useFallbackProps([formProps ?? {}, props], {\n size: 'default' as ComponentSize,\n readonly: false\n})\n\nconst errorTip = shallowRef<string>()\n/** 递增序号,丢弃过期的异步校验结果 */\nlet validateSeq = 0\n/** 最近一次 validate 的 Promise,供过期调用等待最新结果 */\nlet latestValidatePromise: Promise<boolean> = Promise.resolve(true)\n\nconst labelPosition = computed(() => props.labelPosition ?? formProps?.labelPosition ?? 'left')\n\nconst className = computed(() => {\n return [\n cls.b,\n cls.m(size.value),\n bem.is('error', !!errorTip.value),\n bem.is('label-top', labelPosition.value === 'top')\n ].join(' ')\n})\n\n/** label样式 */\nconst labelStyles = computed<CSSProperties>(() => {\n if (labelPosition.value === 'top') return {}\n return {\n width: withUnit(props.labelWidth ?? formProps?.labelWidth ?? config.form.labelWidth, 'px')\n }\n})\n\nconst fieldItem = defineField({\n clearValidate() {\n errorTip.value = ''\n },\n async validate() {\n if (!props.field || !formProps?.model || !props.rules || !shouldValidate?.()) return true\n\n const seq = ++validateSeq\n\n latestValidatePromise = validateField(formProps.model!, props.field!, props.rules!).then(\n (tip) => {\n if (seq !== validateSeq) return latestValidatePromise\n\n errorTip.value = tip\n return !errorTip.value\n }\n )\n return latestValidatePromise\n }\n})\n\nwatch(\n () => props.field,\n (field, oldField) => {\n if (oldField) {\n unregisterField?.(oldField)\n }\n if (field) {\n registerField?.(field, fieldItem)\n }\n },\n { immediate: true }\n)\n\nlet stopWatchFieldValue: (() => void) | undefined\n\nwatch(\n [() => formProps?.model, () => props.field],\n ([model, field]) => {\n stopWatchFieldValue?.()\n\n if (!field || !model) return\n\n stopWatchFieldValue = watch(\n () => o(model).get(field),\n (value) => {\n handleFieldUpdate?.(field, value)\n fieldItem.validate()\n }\n )\n },\n { immediate: true }\n)\n\nonBeforeUnmount(() => {\n props.field && unregisterField?.(props.field)\n})\n</script>\n"],"mappings":"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"}
@@ -99,8 +99,6 @@ interface BatchEditProps extends TableProps {
99
99
  }
100
100
  /** 批量编辑组件定义的事件 */
101
101
  interface BatchEditEmits extends TableEmits {
102
- /** 表单控件 change,透传自 UForm */
103
- (e: 'field:change', field: string, ...args: any[]): void;
104
102
  /** 更新数据 */
105
103
  (e: 'update:data', value: Record<string, any>[]): void;
106
104
  /** 点击底部「新增一行」 */
@@ -28,8 +28,6 @@ interface FormProps extends ComponentProps {
28
28
  disabled?: boolean;
29
29
  }
30
30
  interface FormEmits {
31
- /** 控件 change 事件,仅用户操作触发 */
32
- (e: 'field:change', field: string, ...args: any[]): void;
33
31
  /** model 字段值更新,含编程写入 */
34
32
  (e: 'field:update', field: string, value: any): void;
35
33
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@veltra/desktop",
3
- "version": "1.7.11",
3
+ "version": "1.7.12",
4
4
  "homepage": "https://github.com/cabinet-fe/ultra-ui#readme",
5
5
  "bugs": {
6
6
  "url": "https://github.com/cabinet-fe/ultra-ui/issues"
@@ -80,12 +80,12 @@
80
80
  "peerDependencies": {
81
81
  "@cat-kit/core": ">=1.2.1",
82
82
  "@cat-kit/fe": ">=1.2.1",
83
- "@veltra/compositions": "^1.7.11",
84
- "@veltra/directives": "^1.7.11",
83
+ "@veltra/compositions": "^1.7.12",
84
+ "@veltra/directives": "^1.7.12",
85
85
  "@veltra/icons": "^1.5.0",
86
86
  "@veltra/sheet-core": "^2.5.6",
87
- "@veltra/styles": "^1.7.11",
88
- "@veltra/utils": "^1.7.11",
87
+ "@veltra/styles": "^1.7.12",
88
+ "@veltra/utils": "^1.7.12",
89
89
  "vue": ">=3.5.42"
90
90
  },
91
91
  "peerDependenciesMeta": {
@@ -30,8 +30,6 @@ function mountBatchEdit() {
30
30
  apiMethod: undefined as string | undefined
31
31
  })
32
32
 
33
- const fieldChanges: { field: string; args: unknown[] }[] = []
34
-
35
33
  const app = createApp({
36
34
  render() {
37
35
  return h(
@@ -41,9 +39,6 @@ function mountBatchEdit() {
41
39
  'onUpdate:data': (value: any[]) => {
42
40
  data.value = value
43
41
  },
44
- 'onField:change': (field: string, ...args: unknown[]) => {
45
- fieldChanges.push({ field, args })
46
- },
47
42
  columns,
48
43
  model,
49
44
  rowKey: 'id',
@@ -94,7 +89,6 @@ function mountBatchEdit() {
94
89
  host,
95
90
  data,
96
91
  model,
97
- fieldChanges,
98
92
  unmount() {
99
93
  app.unmount()
100
94
  host.remove()
@@ -202,136 +196,4 @@ describe('UBatchEdit quick-edit 切换编辑行', () => {
202
196
 
203
197
  unmount()
204
198
  })
205
-
206
- it('切换行回显不触发 field:change', async () => {
207
- const { host, fieldChanges, unmount } = mountBatchEdit()
208
-
209
- fieldChanges.length = 0
210
- await clickRow(host, 0)
211
- await clickRow(host, 1)
212
-
213
- expect(fieldChanges).toHaveLength(0)
214
-
215
- unmount()
216
- })
217
-
218
- it('用户变更 Select 触发 field:change', async () => {
219
- const { host, fieldChanges, unmount } = mountBatchEdit()
220
-
221
- await clickRow(host, 0)
222
- fieldChanges.length = 0
223
-
224
- const input = host.querySelector<HTMLInputElement>('.u-select input')
225
- expect(input).toBeTruthy()
226
- input!.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true }))
227
- await nextTick()
228
- await nextTick()
229
-
230
- const option = [...document.body.querySelectorAll<HTMLElement>('.u-select__option')].find(
231
- (el) => el.textContent === '接口'
232
- )
233
- expect(option).toBeTruthy()
234
- option!.dispatchEvent(new MouseEvent('click', { bubbles: true }))
235
- await nextTick()
236
- await nextTick()
237
-
238
- expect(fieldChanges).toHaveLength(1)
239
- expect(fieldChanges[0]).toEqual({ field: 'behavior', args: [{ label: '接口', value: 'api' }] })
240
-
241
- unmount()
242
- })
243
-
244
- it('field:change 联动改其他字段不循环触发', async () => {
245
- const host = document.createElement('div')
246
- document.body.appendChild(host)
247
-
248
- const columns = defineTableColumns([
249
- { key: 'label', name: '名称' },
250
- { key: 'behavior', name: '行为' }
251
- ])
252
-
253
- const data = ref([
254
- { id: '1', label: '存草稿', behavior: 'common-resource', submitType: 'DRAFT' },
255
- { id: '2', label: '调接口', behavior: 'api', submitType: 'SUBMIT', apiMethod: 'POST' }
256
- ])
257
-
258
- const model = reactive({
259
- id: '',
260
- label: '',
261
- behavior: 'event',
262
- submitType: undefined as string | undefined,
263
- apiMethod: undefined as string | undefined
264
- })
265
-
266
- const fieldChanges: string[] = []
267
-
268
- const app = createApp({
269
- render() {
270
- return h(
271
- UBatchEdit,
272
- {
273
- data: data.value,
274
- 'onUpdate:data': (value: any[]) => {
275
- data.value = value
276
- },
277
- 'onField:change': (field: string) => {
278
- fieldChanges.push(field)
279
- if (field === 'behavior') {
280
- model.submitType = undefined
281
- }
282
- },
283
- columns,
284
- model,
285
- rowKey: 'id',
286
- quickEdit: true
287
- },
288
- {
289
- form: () => [
290
- h(UInput, { field: 'label', label: '名称' }),
291
- h(USelect, {
292
- field: 'behavior',
293
- label: '行为',
294
- options: [
295
- { label: '事件', value: 'event' },
296
- { label: '通用资源', value: 'common-resource' },
297
- { label: '接口', value: 'api' }
298
- ]
299
- }),
300
- h(USelect, {
301
- field: 'submitType',
302
- label: '提交类型',
303
- options: [
304
- { label: 'DRAFT', value: 'DRAFT' },
305
- { label: 'SUBMIT', value: 'SUBMIT' }
306
- ]
307
- })
308
- ]
309
- }
310
- )
311
- }
312
- })
313
-
314
- app.mount(host)
315
-
316
- await clickRow(host, 0)
317
- fieldChanges.length = 0
318
-
319
- const input = host.querySelector<HTMLInputElement>('.u-select input')
320
- input!.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true }))
321
- await nextTick()
322
- await nextTick()
323
-
324
- const option = [...document.body.querySelectorAll<HTMLElement>('.u-select__option')].find(
325
- (el) => el.textContent === '接口'
326
- )
327
- option!.dispatchEvent(new MouseEvent('click', { bubbles: true }))
328
- await nextTick()
329
- await nextTick()
330
-
331
- expect(fieldChanges).toEqual(['behavior'])
332
- expect(model.submitType).toBeUndefined()
333
-
334
- app.unmount()
335
- host.remove()
336
- })
337
199
  })
@@ -29,7 +29,6 @@
29
29
  :readonly="props.readonly"
30
30
  :label-width="props.labelWidth"
31
31
  @field:update="handleFieldUpdate"
32
- @field:change="handleFormFieldChange"
33
32
  >
34
33
  <slot
35
34
  v-bind="{
@@ -92,17 +91,8 @@ defineOptions({ name: 'UBatchEditForm' })
92
91
 
93
92
  const batchEditCtx = inject(BatchEditDIKey)!
94
93
 
95
- const {
96
- cls,
97
- props,
98
- state,
99
- emit,
100
- handleClose,
101
- handleSave,
102
- staticFeatures,
103
- dynamicFeatures,
104
- syncing
105
- } = batchEditCtx
94
+ const { cls, props, state, handleClose, handleSave, staticFeatures, dynamicFeatures, syncing } =
95
+ batchEditCtx
106
96
 
107
97
  const focused = toRef(batchEditCtx, 'focused')
108
98
 
@@ -124,10 +114,6 @@ function handleFieldUpdate(field: string, value: any) {
124
114
  }
125
115
  }
126
116
 
127
- function handleFormFieldChange(field: string, ...args: any[]) {
128
- emit('field:change', field, ...args)
129
- }
130
-
131
117
  const creatable = computed(() => {
132
118
  if (state.formActionType !== 'create') return false
133
119
  return staticFeatures.value.has('create') || dynamicFeatures.value.create?.(state.row)
@@ -1,42 +1,22 @@
1
1
  import { describe, expect, it } from 'vitest'
2
- import { createApp, h, nextTick, reactive, ref } from 'vue'
2
+ import { createApp, defineComponent, h, nextTick, reactive, ref } from 'vue'
3
3
 
4
4
  import { UInput } from '../../input'
5
5
  import { USelect } from '../../select'
6
6
  import { UForm } from '../index'
7
7
 
8
- const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms))
9
-
10
8
  const behaviorOptions = [
11
9
  { label: '事件', value: 'event' },
12
10
  { label: '接口', value: 'api' }
13
11
  ]
14
12
 
15
- async function openSelect(host: HTMLElement) {
16
- const input = host.querySelector('input')!
17
- input.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true }))
18
- await nextTick()
19
- await nextTick()
20
- }
21
-
22
- async function clickSelectOption(label: string) {
23
- const option = [...document.body.querySelectorAll<HTMLElement>('.u-select__option')].find(
24
- (el) => el.textContent === label
25
- )
26
- expect(option, `选项「${label}」应存在`).toBeTruthy()
27
- option!.dispatchEvent(new MouseEvent('click', { bubbles: true }))
28
- await nextTick()
29
- await sleep(50)
30
- }
31
-
32
13
  describe('UForm field events', () => {
33
- it('field:update and field:change are separate', async () => {
14
+ it('编程写入触发 field:update,不依赖用户操作', async () => {
34
15
  const host = document.createElement('div')
35
16
  document.body.appendChild(host)
36
17
 
37
18
  const model = reactive({ behavior: 'event' })
38
19
  const updates: { field: string; value: unknown }[] = []
39
- const changes: { field: string; args: unknown[] }[] = []
40
20
 
41
21
  const app = createApp({
42
22
  render() {
@@ -46,9 +26,6 @@ describe('UForm field events', () => {
46
26
  model,
47
27
  'onField:update': (field: string, value: unknown) => {
48
28
  updates.push({ field, value })
49
- },
50
- 'onField:change': (field: string, ...args: unknown[]) => {
51
- changes.push({ field, args })
52
29
  }
53
30
  },
54
31
  {
@@ -61,19 +38,11 @@ describe('UForm field events', () => {
61
38
 
62
39
  app.mount(host)
63
40
  updates.length = 0
64
- changes.length = 0
65
41
 
66
42
  model.behavior = 'api'
67
43
  await nextTick()
68
44
 
69
45
  expect(updates.some((item) => item.field === 'behavior' && item.value === 'api')).toBe(true)
70
- expect(changes).toHaveLength(0)
71
-
72
- await openSelect(host)
73
- await clickSelectOption('事件')
74
-
75
- expect(changes).toHaveLength(1)
76
- expect(changes[0]).toEqual({ field: 'behavior', args: [{ label: '事件', value: 'event' }] })
77
46
 
78
47
  app.unmount()
79
48
  host.remove()
@@ -122,3 +91,126 @@ describe('UForm field events', () => {
122
91
  host.remove()
123
92
  })
124
93
  })
94
+
95
+ /** 用 setup 期间生成的 id 断言实例身份 */
96
+ let probeSeq = 0
97
+ const Probe = defineComponent({
98
+ name: 'Probe',
99
+ props: { field: String, label: String, modelValue: String },
100
+ setup(props) {
101
+ const id = `p${++probeSeq}`
102
+ return () =>
103
+ h('div', { class: 'probe', 'data-field': props.field, 'data-id': id }, [
104
+ h('span', { class: 'probe-value' }, props.modelValue ?? '')
105
+ ])
106
+ }
107
+ })
108
+
109
+ describe('UForm slot identity', () => {
110
+ it('中间字段卸载后不复用相邻控件实例', async () => {
111
+ probeSeq = 0
112
+ const host = document.createElement('div')
113
+ document.body.appendChild(host)
114
+
115
+ const model = reactive({ a: 'A', b: 'B', c: 'C' })
116
+ const showB = ref(true)
117
+
118
+ const app = createApp({
119
+ render() {
120
+ return h(
121
+ UForm,
122
+ { model },
123
+ {
124
+ default: () => [
125
+ h(Probe, { field: 'a', label: 'A' }),
126
+ showB.value ? h(Probe, { field: 'b', label: 'B' }) : null,
127
+ h(Probe, { field: 'c', label: 'C' })
128
+ ]
129
+ }
130
+ )
131
+ }
132
+ })
133
+
134
+ app.mount(host)
135
+
136
+ const idOf = (field: string) =>
137
+ host.querySelector(`[data-field="${field}"]`)?.getAttribute('data-id')
138
+
139
+ const cId = idOf('c')
140
+ expect(cId).toBe('p3')
141
+
142
+ showB.value = false
143
+ await nextTick()
144
+
145
+ expect(host.querySelector('[data-field="b"]')).toBeNull()
146
+ expect(idOf('c')).toBe(cId)
147
+ expect(host.querySelector('[data-field="c"] .probe-value')!.textContent).toBe('C')
148
+
149
+ app.unmount()
150
+ host.remove()
151
+ })
152
+
153
+ it('重复 field 各自保有独立实例', async () => {
154
+ probeSeq = 0
155
+ const host = document.createElement('div')
156
+ document.body.appendChild(host)
157
+
158
+ const model = reactive({ name: '同名' })
159
+
160
+ const app = createApp({
161
+ render() {
162
+ return h(
163
+ UForm,
164
+ { model },
165
+ {
166
+ default: () => [
167
+ h(Probe, { field: 'name', label: '左' }),
168
+ h(Probe, { field: 'name', label: '右' })
169
+ ]
170
+ }
171
+ )
172
+ }
173
+ })
174
+
175
+ app.mount(host)
176
+
177
+ const probes = [...host.querySelectorAll('.probe')]
178
+ expect(probes).toHaveLength(2)
179
+ expect(probes[0]!.getAttribute('data-id')).toBe('p1')
180
+ expect(probes[1]!.getAttribute('data-id')).toBe('p2')
181
+ expect(probes[0]!.querySelector('.probe-value')!.textContent).toBe('同名')
182
+ expect(probes[1]!.querySelector('.probe-value')!.textContent).toBe('同名')
183
+
184
+ app.unmount()
185
+ host.remove()
186
+ })
187
+
188
+ it('showModified 变更前副本不抢走编辑控件', async () => {
189
+ const host = document.createElement('div')
190
+ document.body.appendChild(host)
191
+
192
+ const model = reactive({ name: '新值' })
193
+ const initialModel = { name: '旧值' }
194
+
195
+ const app = createApp({
196
+ render() {
197
+ return h(
198
+ UForm,
199
+ { model, initialModel, showModified: true },
200
+ { default: () => h(UInput, { field: 'name', label: '名称' }) }
201
+ )
202
+ }
203
+ })
204
+
205
+ app.mount(host)
206
+ await nextTick()
207
+
208
+ const live = host.querySelector('input') as HTMLInputElement | null
209
+ expect(live).toBeTruthy()
210
+ expect(live!.value).toBe('新值')
211
+ expect(host.querySelector('.u-form__data-before')?.textContent).toContain('旧值')
212
+
213
+ app.unmount()
214
+ host.remove()
215
+ })
216
+ })
@@ -8,8 +8,8 @@
8
8
  :class="[cls.b, bem.is('readonly', readonly)]"
9
9
  >
10
10
  <template
11
- v-for="{ node, isFormItem, formItemProps, field, modelValue } of getSlotsNodes()"
12
- :key="node.key"
11
+ v-for="{ node, isFormItem, formItemProps, field, modelValue, renderKey } of getSlotsNodes()"
12
+ :key="renderKey"
13
13
  >
14
14
  <u-form-item v-if="!isFormItem && field" v-bind="formItemProps">
15
15
  <component
@@ -19,7 +19,7 @@
19
19
  />
20
20
  <div v-if="shouldShowModified(field)" :class="cls.e('data-before')">
21
21
  <span :class="cls.e('changed-tag')">{{ modifiedLabel }}</span>
22
- <component :is="node" readonly :model-value="getBaselineFieldValue(field)" />
22
+ <component :is="createModifiedPreview(node, field)" />
23
23
  </div>
24
24
  </u-form-item>
25
25
 
@@ -32,14 +32,14 @@
32
32
  import { o } from '@cat-kit/core'
33
33
  import { bem } from '@veltra/utils'
34
34
  import { provideFormContext } from '@veltra/utils'
35
- import { nextTick, toRef, useTemplateRef } from 'vue'
35
+ import { nextTick, toRef, useTemplateRef, type VNode } from 'vue'
36
36
 
37
37
  import type { BreakCols, FormProps, _FormExposed, FormEmits } from '../../types'
38
38
  import { UFormItem } from '../form-item'
39
39
  import { UGrid } from '../grid'
40
40
  import { isFieldModified } from './is-field-modified'
41
41
  import { useFormFields } from './use-form-fields'
42
- import { useNodeInterceptor } from './use-node-interceptor'
42
+ import { forkUnmountedVNode, useNodeInterceptor } from './use-node-interceptor'
43
43
 
44
44
  defineOptions({ name: 'UForm' })
45
45
 
@@ -81,18 +81,13 @@ function handleFieldUpdate(field: string, value: any) {
81
81
  emit('field:update', field, value)
82
82
  }
83
83
 
84
- function handleFieldChange(field: string, ...args: any[]) {
85
- emit('field:change', field, ...args)
86
- }
87
-
88
84
  provideFormContext({
89
85
  formProps: props,
90
86
  registerField,
91
87
  unregisterField,
92
88
  validateFields: validate,
93
89
  shouldValidate,
94
- handleFieldUpdate,
95
- handleFieldChange
90
+ handleFieldUpdate
96
91
  })
97
92
 
98
93
  const { getSlotsNodes } = useNodeInterceptor()
@@ -106,6 +101,10 @@ function getBaselineFieldValue(field: string) {
106
101
  return o(getBaselineModel() ?? {}).get(field)
107
102
  }
108
103
 
104
+ function createModifiedPreview(node: VNode, field: string) {
105
+ return forkUnmountedVNode(node, { readonly: true, modelValue: getBaselineFieldValue(field) })
106
+ }
107
+
109
108
  function shouldShowModified(field: string) {
110
109
  if (!props.showModified) return false
111
110
  const baseline = getBaselineModel()
@@ -1,6 +1,6 @@
1
1
  import { o } from '@cat-kit/core'
2
2
  import { extractNormalVNodes } from '@veltra/utils'
3
- import { useSlots, type VNode } from 'vue'
3
+ import { cloneVNode, useSlots, type VNode } from 'vue'
4
4
 
5
5
  export type SlotRenderItem = {
6
6
  isFormItem: boolean
@@ -8,6 +8,28 @@ export type SlotRenderItem = {
8
8
  node: VNode
9
9
  field?: string
10
10
  modelValue?: any
11
+ /** v-for 身份:调用方 :key 优先,否则 type+field+出现次序 */
12
+ renderKey: string | number | symbol
13
+ }
14
+
15
+ function getVNodeTypeName(node: VNode): string {
16
+ const type = node.type as { name?: string; __name?: string } | string
17
+ if (typeof type === 'string') return type
18
+ if (typeof type === 'object' && type) return type.name || type.__name || ''
19
+ return ''
20
+ }
21
+
22
+ /**
23
+ * 从源 vnode 派生一份未挂载副本。
24
+ * Vue 的 cloneVNode 会拷贝 el/component,同一源 vnode 渲两次会把实例从编辑控件抢走。
25
+ */
26
+ export function forkUnmountedVNode(node: VNode, extra?: Record<string, unknown>): VNode {
27
+ const forked = extra ? cloneVNode(node, extra) : cloneVNode(node)
28
+ forked.el = null
29
+ forked.component = null
30
+ forked.anchor = null
31
+ forked.suspense = null
32
+ return forked
11
33
  }
12
34
 
13
35
  /**
@@ -24,6 +46,7 @@ export function useNodeInterceptor(): { getSlotsNodes: () => SlotRenderItem[] |
24
46
  const flattedNodes = extractNormalVNodes(nodes)
25
47
 
26
48
  const results: SlotRenderItem[] = []
49
+ const seen = new Map<string, number>()
27
50
 
28
51
  let i = 0
29
52
  while (i < flattedNodes.length) {
@@ -43,12 +66,23 @@ export function useNodeInterceptor(): { getSlotsNodes: () => SlotRenderItem[] |
43
66
  'field'
44
67
  ]) as Record<string, any>
45
68
 
69
+ let renderKey: string | number | symbol
70
+ if (node.key != null) {
71
+ renderKey = node.key
72
+ } else {
73
+ const base = `${getVNodeTypeName(node)}:${field ?? ''}`
74
+ const n = seen.get(base) ?? 0
75
+ seen.set(base, n + 1)
76
+ renderKey = n === 0 ? `uform:${base}` : `uform:${base}#${n}`
77
+ }
78
+
46
79
  results.push({
47
80
  isFormItem,
48
81
  formItemProps,
49
82
  node,
50
83
  field,
51
- modelValue: props?.['model-value'] ?? props?.modelValue
84
+ modelValue: props?.['model-value'] ?? props?.modelValue,
85
+ renderKey
52
86
  })
53
87
  }
54
88
 
@@ -37,18 +37,12 @@ describe('UFormItem change', () => {
37
37
 
38
38
  const model = reactive({ behavior: 'event' })
39
39
  const itemChanges: unknown[][] = []
40
- const formChanges: { field: string; args: unknown[] }[] = []
41
40
 
42
41
  const app = createApp({
43
42
  render() {
44
43
  return h(
45
44
  UForm,
46
- {
47
- model,
48
- 'onField:change': (field: string, ...args: unknown[]) => {
49
- formChanges.push({ field, args })
50
- }
51
- },
45
+ { model },
52
46
  {
53
47
  default: () =>
54
48
  h(
@@ -82,9 +76,6 @@ describe('UFormItem change', () => {
82
76
  expect(itemChanges).toHaveLength(1)
83
77
  expect(itemChanges[0]![0]).toEqual({ label: '接口', value: 'api' })
84
78
 
85
- expect(formChanges).toHaveLength(1)
86
- expect(formChanges[0]).toEqual({ field: 'behavior', args: [{ label: '接口', value: 'api' }] })
87
-
88
79
  app.unmount()
89
80
  host.remove()
90
81
  })
@@ -69,14 +69,8 @@ defineSlots<{
69
69
  }>()
70
70
 
71
71
  /** 表单组件上下文 */
72
- const {
73
- formProps,
74
- registerField,
75
- unregisterField,
76
- shouldValidate,
77
- handleFieldUpdate,
78
- handleFieldChange
79
- } = injectFormContext()
72
+ const { formProps, registerField, unregisterField, shouldValidate, handleFieldUpdate } =
73
+ injectFormContext()
80
74
 
81
75
  function wrapControlChange(node: VNode) {
82
76
  if (!node || typeof node.type === 'symbol') return node
@@ -84,7 +78,6 @@ function wrapControlChange(node: VNode) {
84
78
  return cloneVNode(node, {
85
79
  onChange: (...args: any[]) => {
86
80
  emit('change', ...args)
87
- if (props.field) handleFieldChange?.(props.field, ...args)
88
81
  }
89
82
  })
90
83
  }
@@ -115,8 +115,6 @@ export interface BatchEditProps extends TableProps {
115
115
 
116
116
  /** 批量编辑组件定义的事件 */
117
117
  export interface BatchEditEmits extends TableEmits {
118
- /** 表单控件 change,透传自 UForm */
119
- (e: 'field:change', field: string, ...args: any[]): void
120
118
  /** 更新数据 */
121
119
  (e: 'update:data', value: Record<string, any>[]): void
122
120
  /** 点击底部「新增一行」 */
package/src/types/form.ts CHANGED
@@ -29,8 +29,6 @@ export interface FormProps extends ComponentProps {
29
29
  }
30
30
 
31
31
  export interface FormEmits {
32
- /** 控件 change 事件,仅用户操作触发 */
33
- (e: 'field:change', field: string, ...args: any[]): void
34
32
  /** model 字段值更新,含编程写入 */
35
33
  (e: 'field:update', field: string, value: any): void
36
34
  }