@schemx/vue 1.0.0-next.1 → 1.0.0-next.2

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.
package/dist/index.mjs CHANGED
@@ -1,10 +1,10 @@
1
1
  import "./style.css";
2
- import { getCurrentInstance, inject, onScopeDispose, provide, onUnmounted, shallowRef, computed, ref, onMounted, watchEffect, defineComponent, watch, createVNode, h, createTextVNode, mergeProps, reactive, openBlock, createElementBlock, normalizeClass, normalizeStyle, Fragment, renderList, unref, createBlock, createSlots, withCtx, renderSlot } from "vue";
2
+ import { getCurrentInstance, inject, shallowRef, onScopeDispose, provide, computed, onUnmounted, ref, onMounted, watchEffect, watch, readonly, defineComponent, createVNode, h, createTextVNode, mergeProps, useAttrs, openBlock, createElementBlock, unref, renderSlot, createCommentVNode, createStaticVNode, Fragment, toDisplayString, useSlots, reactive, normalizeClass, normalizeStyle, renderList, createBlock, createSlots, withCtx } from "vue";
3
3
  import { createValidationRuleRegistry, mergeSchemxConfig, createForm, createField, createWatch, isViewGroupSchema, isSchemxViewFieldSchema, isSchemxSchemas, defaultSchemxConfigKeys } from "@schemx/core";
4
4
  export * from "@schemx/core";
5
5
  import classnames from "classnames";
6
+ import { createFormExternalStore, createRendererRegistry } from "@schemx/core/adapter";
6
7
  import { pick } from "es-toolkit";
7
- import { createRendererRegistry } from "@schemx/core/adapter";
8
8
  const SCHEMX_APP_CONFIG_KEY = Symbol("schemx:app-config");
9
9
  const EMPTY_SCHEMX_CONFIG = Object.freeze({
10
10
  schemaConfig: Object.freeze({}),
@@ -15,12 +15,22 @@ function normalizeSchemxAppConfig(config) {
15
15
  const validatorAdapters = Object.freeze([...config.validatorAdapters ?? []]);
16
16
  return Object.freeze({
17
17
  schemaConfig,
18
+ rendererProps: normalizeRendererProps(config.rendererProps),
18
19
  validatorAdapters,
19
20
  defaultRendererType: config.defaultRendererType,
20
21
  rendererRegistry: config.rendererRegistry,
21
22
  validationRuleRegistry: config.validationRuleRegistry
22
23
  });
23
24
  }
25
+ function normalizeRendererProps(source) {
26
+ if (source === void 0) {
27
+ return void 0;
28
+ }
29
+ const entries = Object.entries(source).map(([type, props]) => {
30
+ return [type, props === void 0 ? void 0 : Object.freeze({ ...props })];
31
+ });
32
+ return Object.freeze(Object.fromEntries(entries));
33
+ }
24
34
  function provideSchemxAppConfig(app, config = {}) {
25
35
  const normalizedConfig = normalizeSchemxAppConfig(config);
26
36
  app.provide(SCHEMX_APP_CONFIG_KEY, normalizedConfig);
@@ -31,11 +41,258 @@ function getSchemxAppConfig() {
31
41
  }
32
42
  return inject(SCHEMX_APP_CONFIG_KEY, EMPTY_SCHEMX_CONFIG);
33
43
  }
44
+ const formBridgeCache = /* @__PURE__ */ new WeakMap();
45
+ const formFacadeCache = /* @__PURE__ */ new WeakMap();
46
+ const facadeCoreFormCache = /* @__PURE__ */ new WeakMap();
47
+ function createVueShallowRef(value) {
48
+ return shallowRef(value);
49
+ }
50
+ function getCoreForm(form) {
51
+ const coreForm = facadeCoreFormCache.get(form);
52
+ return coreForm ?? form;
53
+ }
54
+ function getVueFormBridge(form) {
55
+ const coreForm = getCoreForm(form);
56
+ const cachedBridge = formBridgeCache.get(coreForm);
57
+ if (cachedBridge) {
58
+ return cachedBridge;
59
+ }
60
+ const bridge = createVueFormBridge(coreForm);
61
+ formBridgeCache.set(coreForm, bridge);
62
+ return bridge;
63
+ }
64
+ function getVueFormFacade(form) {
65
+ const coreForm = getCoreForm(form);
66
+ const cachedFacade = formFacadeCache.get(coreForm);
67
+ if (cachedFacade) {
68
+ return cachedFacade;
69
+ }
70
+ const facade = createVueFormFacade(coreForm);
71
+ formFacadeCache.set(coreForm, facade);
72
+ facadeCoreFormCache.set(facade, coreForm);
73
+ return facade;
74
+ }
75
+ function retainVueFormBridge(bridge) {
76
+ if (bridge.destroyed) {
77
+ return () => {
78
+ };
79
+ }
80
+ bridge.refCount++;
81
+ let released = false;
82
+ return () => {
83
+ if (released || bridge.destroyed) {
84
+ return;
85
+ }
86
+ released = true;
87
+ bridge.refCount--;
88
+ if (bridge.refCount === 0) {
89
+ disposeVueFormBridge(bridge);
90
+ }
91
+ };
92
+ }
93
+ function getVueFieldBridge(bridge, name) {
94
+ const store = bridge.externalStore.field(name);
95
+ const cachedBridge = bridge.fieldBridges.get(store);
96
+ if (cachedBridge) {
97
+ return cachedBridge;
98
+ }
99
+ const snapshot = store.getSnapshot();
100
+ const value = createVueShallowRef(
101
+ snapshot.value
102
+ );
103
+ const errors = createVueShallowRef(snapshot.errors);
104
+ const touched = createVueShallowRef(snapshot.touched);
105
+ const pending = createVueShallowRef(snapshot.pending);
106
+ const updateFieldRefs = () => {
107
+ const nextSnapshot = store.getSnapshot();
108
+ if (!Object.is(value.value, nextSnapshot.value)) {
109
+ value.value = nextSnapshot.value;
110
+ }
111
+ if (!areStringListsEqual(errors.value, nextSnapshot.errors)) {
112
+ errors.value = nextSnapshot.errors;
113
+ }
114
+ if (touched.value !== nextSnapshot.touched) {
115
+ touched.value = nextSnapshot.touched;
116
+ }
117
+ if (pending.value !== nextSnapshot.pending) {
118
+ pending.value = nextSnapshot.pending;
119
+ }
120
+ };
121
+ const unsubscribe = store.subscribe(updateFieldRefs);
122
+ const dispose = () => {
123
+ unsubscribe();
124
+ };
125
+ const fieldBridge = {
126
+ store,
127
+ value,
128
+ errors,
129
+ touched,
130
+ pending,
131
+ dispose
132
+ };
133
+ bridge.fieldBridges.set(store, fieldBridge);
134
+ return fieldBridge;
135
+ }
136
+ function disposeVueFormBridge(bridge) {
137
+ if (bridge.destroyed) {
138
+ return;
139
+ }
140
+ bridge.destroyed = true;
141
+ bridge.refCount = 0;
142
+ bridge.unsubscribe();
143
+ for (const fieldBridge of bridge.fieldBridges.values()) {
144
+ fieldBridge.dispose();
145
+ }
146
+ bridge.fieldBridges.clear();
147
+ bridge.externalStore.dispose();
148
+ formBridgeCache.delete(bridge.form);
149
+ }
150
+ function createVueFormBridge(form) {
151
+ const externalStore = createFormExternalStore(form);
152
+ const values = createVueShallowRef(externalStore.values.getSnapshot());
153
+ const touchedFields = createVueShallowRef(
154
+ externalStore.touchedFields.getSnapshot()
155
+ );
156
+ const pendingFields = createVueShallowRef(
157
+ externalStore.pendingFields.getSnapshot()
158
+ );
159
+ const loading = createVueShallowRef(externalStore.loading.getSnapshot());
160
+ const fieldBridges = /* @__PURE__ */ new Map();
161
+ const syncValues = () => {
162
+ values.value = externalStore.values.getSnapshot();
163
+ };
164
+ const syncTouchedFields = () => {
165
+ touchedFields.value = externalStore.touchedFields.getSnapshot();
166
+ };
167
+ const syncPendingFields = () => {
168
+ pendingFields.value = externalStore.pendingFields.getSnapshot();
169
+ };
170
+ const syncLoading = () => {
171
+ loading.value = externalStore.loading.getSnapshot();
172
+ };
173
+ const unsubscribeValues = externalStore.values.subscribe(syncValues);
174
+ const unsubscribeTouchedFields = externalStore.touchedFields.subscribe(syncTouchedFields);
175
+ const unsubscribePendingFields = externalStore.pendingFields.subscribe(syncPendingFields);
176
+ const unsubscribeLoading = externalStore.loading.subscribe(syncLoading);
177
+ const unsubscribe = () => {
178
+ unsubscribeValues();
179
+ unsubscribeTouchedFields();
180
+ unsubscribePendingFields();
181
+ unsubscribeLoading();
182
+ };
183
+ const facade = getVueFormFacade(form);
184
+ const bridge = {
185
+ form,
186
+ externalStore,
187
+ values,
188
+ touchedFields,
189
+ pendingFields,
190
+ loading,
191
+ fieldBridges,
192
+ refCount: 0,
193
+ destroyed: false,
194
+ unsubscribe,
195
+ facade
196
+ };
197
+ return bridge;
198
+ }
199
+ function createVueFormFacade(form) {
200
+ let destroyed = false;
201
+ const getFieldValue = (name) => {
202
+ if (!destroyed) {
203
+ const bridge = getVueFormBridge(form);
204
+ void getVueFieldBridge(bridge, name).value.value;
205
+ }
206
+ return form.getFieldValue(name);
207
+ };
208
+ const getFieldErrors = (name) => {
209
+ if (!destroyed) {
210
+ const bridge = getVueFormBridge(form);
211
+ void getVueFieldBridge(bridge, name).errors.value;
212
+ }
213
+ return form.getFieldErrors(name);
214
+ };
215
+ const isFieldTouched = (name) => {
216
+ if (!destroyed) {
217
+ const bridge = getVueFormBridge(form);
218
+ void getVueFieldBridge(bridge, name).touched.value;
219
+ }
220
+ return form.isFieldTouched(name);
221
+ };
222
+ const isFieldPending = (name) => {
223
+ if (!destroyed) {
224
+ const bridge = getVueFormBridge(form);
225
+ void getVueFieldBridge(bridge, name).pending.value;
226
+ }
227
+ return form.isFieldPending(name);
228
+ };
229
+ const getFieldsValue = (names) => {
230
+ if (!destroyed) {
231
+ const bridge = getVueFormBridge(form);
232
+ if (names === void 0) {
233
+ void bridge.values.value;
234
+ } else {
235
+ for (const name of names) {
236
+ void getVueFieldBridge(bridge, name).value.value;
237
+ }
238
+ }
239
+ }
240
+ return form.getFieldsValue(names);
241
+ };
242
+ const getTouchedFields = () => {
243
+ if (!destroyed) {
244
+ const bridge = getVueFormBridge(form);
245
+ void bridge.touchedFields.value;
246
+ }
247
+ return form.getTouchedFields();
248
+ };
249
+ const getPendingFields = () => {
250
+ if (!destroyed) {
251
+ const bridge = getVueFormBridge(form);
252
+ void bridge.pendingFields.value;
253
+ }
254
+ return form.getPendingFields();
255
+ };
256
+ const isLoading = () => {
257
+ if (!destroyed) {
258
+ const bridge = getVueFormBridge(form);
259
+ void bridge.loading.value;
260
+ }
261
+ return form.isLoading();
262
+ };
263
+ const destroy = () => {
264
+ if (destroyed) {
265
+ return;
266
+ }
267
+ destroyed = true;
268
+ const bridge = formBridgeCache.get(form);
269
+ if (bridge) {
270
+ disposeVueFormBridge(bridge);
271
+ }
272
+ form.destroy();
273
+ };
274
+ return {
275
+ ...form,
276
+ getFieldValue,
277
+ getFieldErrors,
278
+ isFieldTouched,
279
+ isFieldPending,
280
+ getFieldsValue,
281
+ getTouchedFields,
282
+ getPendingFields,
283
+ isLoading,
284
+ destroy
285
+ };
286
+ }
287
+ function areStringListsEqual(previous, next) {
288
+ return previous.length === next.length && previous.every((value, index) => value === next[index]);
289
+ }
34
290
  const rendererRegistry = createRendererRegistry("input");
35
291
  const validationRuleRegistry = createValidationRuleRegistry();
36
292
  function useForm(options = {}) {
37
293
  const configuredOptions = mergeSchemxConfig(
38
294
  getUseFormSchemxConfig(options),
295
+ // App 安装配置以 Values 存储;在 useForm 边界关联到当前 TValues。
39
296
  getSchemxAppConfig(),
40
297
  {
41
298
  rendererRegistry,
@@ -47,14 +304,18 @@ function useForm(options = {}) {
47
304
  ...configuredOptions
48
305
  };
49
306
  const instance = createForm(mergedOptions);
307
+ const form = getVueFormFacade(instance);
308
+ const releaseBridge = retainVueFormBridge(getVueFormBridge(instance));
50
309
  onScopeDispose(() => {
51
- instance.destroy();
310
+ releaseBridge();
311
+ form.destroy();
52
312
  });
53
- return instance;
313
+ return form;
54
314
  }
55
315
  function getUseFormSchemxConfig(options) {
56
316
  const {
57
317
  schemaConfig = {},
318
+ rendererProps = void 0,
58
319
  validatorAdapters = [],
59
320
  defaultRendererType = void 0,
60
321
  rendererRegistry: rendererRegistry2 = void 0,
@@ -62,6 +323,7 @@ function getUseFormSchemxConfig(options) {
62
323
  } = options;
63
324
  return {
64
325
  schemaConfig,
326
+ rendererProps,
65
327
  defaultRendererType,
66
328
  rendererRegistry: rendererRegistry2,
67
329
  validationRuleRegistry: validationRuleRegistry2,
@@ -70,7 +332,11 @@ function getUseFormSchemxConfig(options) {
70
332
  }
71
333
  const SCHEMX_FORM_INSTANCE_KEY = Symbol("schemx:instance");
72
334
  function createFormContext(instance) {
73
- provide(SCHEMX_FORM_INSTANCE_KEY, instance);
335
+ const form = getVueFormFacade(instance);
336
+ const releaseBridge = retainVueFormBridge(getVueFormBridge(form));
337
+ provide(SCHEMX_FORM_INSTANCE_KEY, form);
338
+ onScopeDispose(releaseBridge);
339
+ return form;
74
340
  }
75
341
  function useFormContext() {
76
342
  const instance = inject(SCHEMX_FORM_INSTANCE_KEY, null);
@@ -81,63 +347,37 @@ function useFormContext() {
81
347
  }
82
348
  return instance;
83
349
  }
84
- const fieldHookCache = /* @__PURE__ */ new WeakMap();
85
- function createFieldHook(form, name) {
86
- const field = createField(form, name);
87
- const fieldValue = shallowRef(field.getValue());
88
- const fieldErrors = shallowRef(field.getErrors());
89
- const fieldPending = shallowRef(field.isPending());
90
- const dispose = field.effect(() => {
91
- fieldValue.value = field.getValue();
92
- fieldErrors.value = field.getErrors();
93
- fieldPending.value = field.isPending();
94
- });
95
- const errors = computed(() => fieldErrors.value);
96
- const dirty = computed(() => {
97
- void fieldValue.value;
98
- return field.isTouched();
99
- });
100
- const pending = computed(() => fieldPending.value);
101
- const result = {
350
+ function createFieldHook(form, name, fieldBridge) {
351
+ const coreForm = getCoreForm(form);
352
+ const coreField = createField(coreForm, name);
353
+ const errors = computed(() => fieldBridge.errors.value);
354
+ const dirty = computed(() => fieldBridge.touched.value);
355
+ const pending = computed(() => fieldBridge.pending.value);
356
+ const getValue = () => fieldBridge.value.value;
357
+ const getErrors = () => fieldBridge.errors.value;
358
+ const isTouched = () => fieldBridge.touched.value;
359
+ const isPending = () => fieldBridge.pending.value;
360
+ const getValues = () => form.getFieldsValue();
361
+ return {
362
+ ...coreField,
102
363
  name,
103
- value: fieldValue,
364
+ value: fieldBridge.value,
104
365
  errors,
105
366
  dirty,
106
367
  pending,
107
- ...field,
108
- getValue: () => fieldValue.value
368
+ getValue,
369
+ getErrors,
370
+ isTouched,
371
+ isPending,
372
+ getValues
109
373
  };
110
- return { result, dispose };
111
374
  }
112
375
  const useField = (name) => {
113
376
  const form = useFormContext();
114
- const key = name;
115
- let formCache = fieldHookCache.get(form);
116
- if (!formCache) {
117
- formCache = /* @__PURE__ */ new Map();
118
- fieldHookCache.set(form, formCache);
119
- }
120
- const cachedEntry = formCache.get(key);
121
- let activeEntry;
122
- if (cachedEntry) {
123
- cachedEntry.refCount++;
124
- activeEntry = cachedEntry;
125
- } else {
126
- const { result, dispose } = createFieldHook(form, name);
127
- activeEntry = {
128
- refCount: 1,
129
- result,
130
- dispose
131
- };
132
- formCache.set(key, activeEntry);
133
- }
134
- onUnmounted(() => {
135
- if (--activeEntry.refCount <= 0) {
136
- activeEntry.dispose();
137
- formCache.delete(key);
138
- }
139
- });
140
- return activeEntry.result;
377
+ const coreForm = getCoreForm(form);
378
+ const formBridge = getVueFormBridge(coreForm);
379
+ const fieldBridge = getVueFieldBridge(formBridge, name);
380
+ return createFieldHook(form, name, fieldBridge);
141
381
  };
142
382
  const SCHEMX_FORM_FIELD_KEY = Symbol(
143
383
  "schemx:field"
@@ -157,7 +397,7 @@ function useFieldContext() {
157
397
  function useWatch(nameOrNamesOrCallback, callbackOrOptions, maybeOptions) {
158
398
  const form = useFormContext();
159
399
  const dispose = createWatch(
160
- form,
400
+ getCoreForm(form),
161
401
  nameOrNamesOrCallback,
162
402
  callbackOrOptions,
163
403
  maybeOptions
@@ -301,15 +541,89 @@ function useStableRef(factory) {
301
541
  });
302
542
  return stableRef;
303
543
  }
544
+ const viewSchemaBridgeCache = /* @__PURE__ */ new WeakMap();
304
545
  function useViewSchemas(form) {
546
+ const bridge = acquireViewSchemaBridge(getCoreForm(form));
547
+ return bridge.viewSchemas;
548
+ }
549
+ function useViewSchema(form, getKey) {
550
+ const bridge = acquireViewSchemaBridge(getCoreForm(form));
551
+ return computed(() => bridge.schemasByKey.value.get(getKey()));
552
+ }
553
+ function createViewSchemaBridge(form) {
305
554
  const viewSchemas = shallowRef(
306
555
  form.getViewSchemas()
307
556
  );
308
- const unsubscribe = form.subscribeViewSchemas((nextSchemas) => {
309
- viewSchemas.value = nextSchemas;
557
+ const schemasByKey = computed(() => {
558
+ return createViewSchemaIndex(viewSchemas.value);
559
+ });
560
+ const unsubscribe = form.subscribeViewSchemas(
561
+ /**
562
+ * 接收 Core 发布的完整列表并替换共享引用,以保留更新边界。
563
+ *
564
+ * @param nextSchemas - Core 计算出的最新 ViewSchema 列表。
565
+ */
566
+ (nextSchemas) => {
567
+ viewSchemas.value = nextSchemas;
568
+ }
569
+ );
570
+ return {
571
+ refCount: 0,
572
+ schemasByKey,
573
+ unsubscribe,
574
+ viewSchemas
575
+ };
576
+ }
577
+ function acquireViewSchemaBridge(form) {
578
+ const cachedBridge = viewSchemaBridgeCache.get(form);
579
+ const bridge = cachedBridge ?? createViewSchemaBridge(form);
580
+ if (!cachedBridge) {
581
+ viewSchemaBridgeCache.set(form, bridge);
582
+ }
583
+ bridge.refCount++;
584
+ onScopeDispose(() => {
585
+ bridge.refCount--;
586
+ if (bridge.refCount > 0) {
587
+ return;
588
+ }
589
+ bridge.unsubscribe();
590
+ viewSchemaBridgeCache.delete(form);
591
+ });
592
+ return bridge;
593
+ }
594
+ function createViewSchemaIndex(viewSchemas) {
595
+ const schemasByKey = /* @__PURE__ */ new Map();
596
+ const appendSchemas = (schemas) => {
597
+ for (const schema of schemas) {
598
+ schemasByKey.set(schema.key, schema);
599
+ if (isViewGroupSchema(schema)) {
600
+ appendSchemas(schema.children);
601
+ }
602
+ }
603
+ };
604
+ appendSchemas(viewSchemas);
605
+ return schemasByKey;
606
+ }
607
+ function useFormSelector(form, selector, options = {}) {
608
+ const bridge = getVueFormBridge(getCoreForm(form));
609
+ const releaseBridge = retainVueFormBridge(bridge);
610
+ const selected = shallowRef(selector(bridge.values.value));
611
+ const equals = options.equals ?? Object.is;
612
+ const stop = watch(
613
+ bridge.values,
614
+ (values) => {
615
+ const next = selector(values);
616
+ if (!equals(selected.value, next)) {
617
+ selected.value = next;
618
+ }
619
+ },
620
+ { flush: "sync" }
621
+ );
622
+ onScopeDispose(() => {
623
+ stop();
624
+ releaseBridge();
310
625
  });
311
- onScopeDispose(unsubscribe);
312
- return viewSchemas;
626
+ return readonly(selected);
313
627
  }
314
628
  function isValidTrigger(v) {
315
629
  if (v === void 0)
@@ -582,6 +896,12 @@ const FormItem = /* @__PURE__ */ defineComponent({
582
896
  required: true
583
897
  }
584
898
  },
899
+ /**
900
+ * 根据 schema 类型分发字段组或字段项渲染。
901
+ *
902
+ * @param props - 当前组件的 schema 属性。
903
+ * @param slots - Vue setup 上下文提供的插槽集合。
904
+ */
585
905
  setup(props, {
586
906
  slots
587
907
  }) {
@@ -607,22 +927,22 @@ const FieldFormItem = /* @__PURE__ */ defineComponent({
607
927
  required: true
608
928
  }
609
929
  },
930
+ /**
931
+ * 初始化字段上下文,并组合响应式 schema、校验处理器与插槽渲染器。
932
+ *
933
+ * @param props - 当前字段项的 schema 属性。
934
+ * @param attrs - Vue setup 上下文提供的透传属性。
935
+ * @param slots - Vue setup 上下文提供的插槽集合。
936
+ */
610
937
  setup(props, {
611
938
  attrs,
612
939
  slots
613
940
  }) {
614
941
  const form = useFormContext();
615
- const inputSchema = props.schema;
616
- const schemaVersion = shallowRef(0);
617
- const disposeSchemaEffect = form.effect(() => {
618
- form.getViewSchemas();
619
- schemaVersion.value++;
620
- });
621
- onUnmounted(disposeSchemaEffect);
942
+ const inputSchema = computed(() => props.schema);
943
+ const latestSchema = useViewSchema(form, () => inputSchema.value.key);
622
944
  const schemaRef = computed(() => {
623
- void schemaVersion.value;
624
- const latestSchema = form.getViewSchemas().find((viewSchema) => viewSchema.key === inputSchema.key);
625
- return latestSchema && isSchemxViewFieldSchema(latestSchema) ? latestSchema : inputSchema;
945
+ return latestSchema.value && isSchemxViewFieldSchema(latestSchema.value) ? latestSchema.value : inputSchema.value;
626
946
  });
627
947
  const formContext = useFormConfigContext();
628
948
  const field = useField(schemaRef.value.name);
@@ -653,28 +973,10 @@ const FieldFormItem = /* @__PURE__ */ defineComponent({
653
973
  field.setValue(v);
654
974
  };
655
975
  const componentProps = useStableRef(() => {
656
- const currentSchema = schemaRef.value;
657
- const currentComponentProps = currentSchema.componentProps ?? {};
658
- const formItemProps = {
659
- name: currentSchema.name,
660
- label: currentSchema.label,
661
- componentType: currentSchema.componentType,
662
- ...currentComponentProps.formItemProps
663
- };
976
+ const currentComponentProps = schemaRef.value.componentProps ?? {};
664
977
  return {
665
978
  ...currentComponentProps,
666
979
  value: field.value.value,
667
- disabled: currentSchema.disabled,
668
- readonly: currentSchema.readonly,
669
- readonlyPlaceholder: currentSchema.readonlyPlaceholder,
670
- placeholder: currentSchema.placeholder,
671
- formItemProps: {
672
- ...formItemProps,
673
- disabled: currentSchema.disabled,
674
- readonly: currentSchema.readonly,
675
- readonlyPlaceholder: currentSchema.readonlyPlaceholder,
676
- placeholder: currentSchema.placeholder
677
- },
678
980
  onChange: handleChange,
679
981
  onBlur: handleBlur,
680
982
  "onUpdate:value": handleValueUpdate
@@ -722,6 +1024,65 @@ const FieldFormItem = /* @__PURE__ */ defineComponent({
722
1024
  }
723
1025
  });
724
1026
  const FormItem$1 = FormItem;
1027
+ const _hoisted_1$1 = ["aria-busy", "data-loading", "disabled"];
1028
+ const _hoisted_2 = {
1029
+ key: 0,
1030
+ class: "schemx-button__prefix"
1031
+ };
1032
+ const _hoisted_3 = {
1033
+ key: 1,
1034
+ class: "schemx-button__loading",
1035
+ xmlns: "http://www.w3.org/2000/svg",
1036
+ width: "1em",
1037
+ height: "1em",
1038
+ viewBox: "0 0 24 24",
1039
+ fill: "none",
1040
+ stroke: "currentColor",
1041
+ "stroke-width": "2",
1042
+ "stroke-linecap": "round",
1043
+ "stroke-linejoin": "round"
1044
+ };
1045
+ const _hoisted_4 = {
1046
+ key: 4,
1047
+ class: "schemx-button__suffix"
1048
+ };
1049
+ const _sfc_main$1 = /* @__PURE__ */ defineComponent({
1050
+ ...{ name: "SchemxButton", inheritAttrs: false },
1051
+ __name: "index",
1052
+ props: {
1053
+ loading: { type: Boolean, default: false },
1054
+ loadingText: { default: void 0 },
1055
+ disabled: { type: Boolean, default: false },
1056
+ size: { default: "medium" }
1057
+ },
1058
+ setup(__props) {
1059
+ const props = __props;
1060
+ const attrs = useAttrs();
1061
+ const isDisabled = computed(() => props.disabled || props.loading);
1062
+ const buttonClass = computed(() => ["schemx-button", `schemx-button--${props.size}`]);
1063
+ return (_ctx, _cache) => {
1064
+ return openBlock(), createElementBlock("button", mergeProps(unref(attrs), {
1065
+ class: buttonClass.value,
1066
+ "aria-busy": props.loading || void 0,
1067
+ "data-loading": props.loading || void 0,
1068
+ disabled: isDisabled.value
1069
+ }), [
1070
+ _ctx.$slots.prefix ? (openBlock(), createElementBlock("span", _hoisted_2, [
1071
+ renderSlot(_ctx.$slots, "prefix")
1072
+ ])) : createCommentVNode("", true),
1073
+ props.loading ? (openBlock(), createElementBlock("svg", _hoisted_3, [..._cache[0] || (_cache[0] = [
1074
+ createStaticVNode('<path d="M12 2v4"></path><path d="m16.2 7.8 2.9-2.9"></path><path d="M18 12h4"></path><path d="m16.2 16.2 2.9 2.9"></path><path d="M12 18v4"></path><path d="m4.9 19.1 2.9-2.9"></path><path d="M2 12h4"></path><path d="m4.9 4.9 2.9 2.9"></path>', 8)
1075
+ ])])) : createCommentVNode("", true),
1076
+ props.loading && props.loadingText ? (openBlock(), createElementBlock(Fragment, { key: 2 }, [
1077
+ createTextVNode(toDisplayString(props.loadingText), 1)
1078
+ ], 64)) : renderSlot(_ctx.$slots, "default", {}, void 0, void 0, 3),
1079
+ _ctx.$slots.suffix ? (openBlock(), createElementBlock("span", _hoisted_4, [
1080
+ renderSlot(_ctx.$slots, "suffix")
1081
+ ])) : createCommentVNode("", true)
1082
+ ], 16, _hoisted_1$1);
1083
+ };
1084
+ }
1085
+ });
725
1086
  function getSectionPosition(list, currentKey) {
726
1087
  const currentIndex = list.findIndex((item) => item.key === currentKey);
727
1088
  if (currentIndex === -1) {
@@ -763,6 +1124,10 @@ function hasPositionItemInSection(list, startIndex, step) {
763
1124
  }
764
1125
  return false;
765
1126
  }
1127
+ const _hoisted_1 = {
1128
+ key: 0,
1129
+ class: "schemx-actions"
1130
+ };
766
1131
  const _sfc_main = /* @__PURE__ */ defineComponent({
767
1132
  ...{ name: "SchemxForm" },
768
1133
  __name: "form",
@@ -771,8 +1136,12 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
771
1136
  initialValues: { default: () => ({}) },
772
1137
  modelValue: { default: () => ({}) },
773
1138
  form: { default: void 0 },
1139
+ loading: { type: Boolean, default: void 0 },
1140
+ submitter: { type: [Boolean, Object], default: true },
1141
+ resetter: { type: [Boolean, Object], default: true },
774
1142
  class: { default: "" },
775
1143
  style: { type: [Boolean, null, String, Object, Array], default: () => ({}) },
1144
+ rendererProps: { default: void 0 },
776
1145
  validatorAdapters: {},
777
1146
  defaultRendererType: {},
778
1147
  rendererRegistry: { default: void 0 },
@@ -780,6 +1149,8 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
780
1149
  onRuleError: {},
781
1150
  onFinish: { type: Function, default: void 0 },
782
1151
  onFinishFailed: { type: Function, default: void 0 },
1152
+ onReset: { type: Function, default: void 0 },
1153
+ onLoadingChange: { type: Function, default: void 0 },
783
1154
  onValuesChange: { type: Function, default: void 0 },
784
1155
  onFieldsChange: { type: Function, default: void 0 },
785
1156
  lifecycleHooks: {},
@@ -800,64 +1171,150 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
800
1171
  setup(__props, { expose: __expose, emit: __emit }) {
801
1172
  const props = __props;
802
1173
  const emit = __emit;
1174
+ const slots = useSlots();
803
1175
  const pickSchemaConfig = () => {
804
1176
  return pick(props, defaultSchemxConfigKeys);
805
1177
  };
806
1178
  const formSchemaConfig = reactive(pickSchemaConfig());
807
1179
  createFormConfigContext({ schemaConfig: formSchemaConfig });
808
- const form = props.form ? props.form : useForm({
1180
+ const providedForm = props.form ? props.form : useForm({
809
1181
  schemas: props.schemas,
810
1182
  schemaConfig: pickSchemaConfig(),
811
1183
  initialValues: Object.keys(props.modelValue).length > 0 ? props.modelValue : props.initialValues,
1184
+ rendererProps: props.rendererProps,
812
1185
  rendererRegistry: props.rendererRegistry,
813
1186
  defaultRendererType: props.defaultRendererType,
814
1187
  validationRuleRegistry: props.validationRuleRegistry,
815
1188
  validatorAdapters: props.validatorAdapters,
816
- onFinish: async (values) => {
1189
+ /**
1190
+ * 转发提交成功回调。
1191
+ *
1192
+ * @param values - 提交成功时的完整表单快照。
1193
+ */
1194
+ onFinish: (values) => {
1195
+ var _a;
1196
+ return (_a = props.onFinish) == null ? void 0 : _a.call(props, values);
1197
+ },
1198
+ /**
1199
+ * 转发提交失败回调。
1200
+ *
1201
+ * @param errors - 提交失败时的字段错误集合。
1202
+ */
1203
+ onFinishFailed: (errors) => {
817
1204
  var _a;
818
- (_a = props.onFinish) == null ? void 0 : _a.call(props, values);
1205
+ return (_a = props.onFinishFailed) == null ? void 0 : _a.call(props, errors);
819
1206
  },
820
- onFinishFailed: async (errors) => {
1207
+ /**
1208
+ * 转发完整表单重置回调。
1209
+ */
1210
+ onReset: () => {
821
1211
  var _a;
822
- (_a = props.onFinishFailed) == null ? void 0 : _a.call(props, errors);
1212
+ (_a = props.onReset) == null ? void 0 : _a.call(props);
823
1213
  },
1214
+ /**
1215
+ * 转发 Core 发出的提交 loading 状态。
1216
+ *
1217
+ * 外部传入 Form 时不会注入该回调,保持实例的创建期边界。
1218
+ *
1219
+ * @param loading - 当前是否处于提交流程中。
1220
+ */
1221
+ onLoadingChange: (loading) => {
1222
+ var _a;
1223
+ (_a = props.onLoadingChange) == null ? void 0 : _a.call(props, loading);
1224
+ },
1225
+ /**
1226
+ * 转发值变化回调。
1227
+ *
1228
+ * @param changedValues - 本次变更涉及的字段值。
1229
+ * @param latestSnapshot - 变更后的完整表单快照。
1230
+ */
824
1231
  onValuesChange: (changedValues, latestSnapshot) => {
825
1232
  var _a;
826
1233
  (_a = props.onValuesChange) == null ? void 0 : _a.call(props, changedValues, latestSnapshot);
827
1234
  },
1235
+ /**
1236
+ * 转发字段变化回调。
1237
+ *
1238
+ * @param changedPaths - 本次发生变化的字段路径。
1239
+ * @param allPaths - 当前已变更字段路径集合。
1240
+ */
828
1241
  onFieldsChange: (changedPaths, allPaths) => {
829
1242
  var _a;
830
1243
  (_a = props.onFieldsChange) == null ? void 0 : _a.call(props, changedPaths, allPaths);
831
1244
  }
832
1245
  });
833
1246
  const isExternalForm = props.form !== void 0;
834
- createFormContext(form);
1247
+ const formInstance = createFormContext(providedForm);
1248
+ const effectiveLoading = computed(() => props.loading ?? formInstance.isLoading());
1249
+ const normalizeActionConfig = (action) => action === true ? {} : action || {};
1250
+ const submitterConfig = computed(() => normalizeActionConfig(props.submitter));
1251
+ const resetterConfig = computed(() => normalizeActionConfig(props.resetter));
1252
+ const getButtonProps = (config) => {
1253
+ const buttonProps = Object.fromEntries(
1254
+ Object.entries(config.buttonProps ?? {}).filter(
1255
+ ([name]) => name !== "type" && name !== "onClick"
1256
+ )
1257
+ );
1258
+ return buttonProps;
1259
+ };
1260
+ const submitterButtonProps = computed(() => getButtonProps(submitterConfig.value));
1261
+ const resetterButtonProps = computed(() => getButtonProps(resetterConfig.value));
1262
+ const isSubmitterVisible = computed(
1263
+ () => props.submitter !== false && (props.submitter !== void 0 || slots.submitter)
1264
+ );
1265
+ const isResetterVisible = computed(
1266
+ () => props.resetter !== false && (props.resetter !== void 0 || slots.resetter)
1267
+ );
1268
+ const isActionsVisible = computed(
1269
+ () => isSubmitterVisible.value || isResetterVisible.value
1270
+ );
1271
+ const fieldSlots = computed(
1272
+ () => Object.fromEntries(
1273
+ Object.entries(slots).filter(
1274
+ ([slotName]) => slotName !== "submitter" && slotName !== "resetter"
1275
+ )
1276
+ )
1277
+ );
1278
+ const isSubmitterDisabled = computed(
1279
+ () => effectiveLoading.value || Boolean(submitterButtonProps.value.disabled)
1280
+ );
1281
+ const isResetterDisabled = computed(
1282
+ () => effectiveLoading.value || Boolean(resetterButtonProps.value.disabled)
1283
+ );
835
1284
  let syncingFromModel = false;
836
1285
  watch(
837
1286
  () => props.modelValue,
838
1287
  (values) => {
839
1288
  syncingFromModel = true;
840
- form.setFieldsValue(values);
1289
+ formInstance.setFieldsValue(values);
841
1290
  syncingFromModel = false;
842
1291
  },
843
1292
  { deep: true }
844
1293
  );
845
- const disposeWatch = createWatch(form, (latestSnapshot) => {
846
- if (syncingFromModel)
847
- return;
848
- emit("update:modelValue", latestSnapshot);
849
- });
850
- onUnmounted(disposeWatch);
1294
+ const handleSubmit = () => formInstance.submit();
1295
+ const handleReset = () => {
1296
+ formInstance.reset();
1297
+ };
1298
+ const formValues = useFormSelector(formInstance, (values) => values);
1299
+ watch(
1300
+ formValues,
1301
+ (latestSnapshot) => {
1302
+ if (syncingFromModel)
1303
+ return;
1304
+ emit("update:modelValue", latestSnapshot);
1305
+ },
1306
+ { flush: "sync" }
1307
+ );
851
1308
  watch(
852
1309
  () => props.schemas,
853
1310
  (schemas) => {
854
1311
  if (!isSchemxSchemas(schemas)) {
855
- form.setSchemas(schemas);
1312
+ formInstance.setSchemas(schemas);
856
1313
  }
857
1314
  },
858
1315
  { deep: false, immediate: !!props.form }
859
1316
  );
860
- const viewSchemas = useViewSchemas(form);
1317
+ const viewSchemas = useViewSchemas(formInstance);
861
1318
  const getFormItemClass = (schema) => {
862
1319
  const { isFirst, isLast } = getSectionPosition(
863
1320
  viewSchemas.value,
@@ -872,12 +1329,14 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
872
1329
  pickSchemaConfig,
873
1330
  (nextSchemaConfig) => {
874
1331
  Object.assign(formSchemaConfig, nextSchemaConfig);
875
- form.updateSchemaConfig(nextSchemaConfig);
1332
+ formInstance.updateSchemaConfig(nextSchemaConfig);
876
1333
  },
877
1334
  { deep: false, immediate: isExternalForm }
878
1335
  );
879
1336
  __expose({
880
- ...form
1337
+ ...formInstance,
1338
+ submit: handleSubmit,
1339
+ reset: handleReset
881
1340
  });
882
1341
  return (_ctx, _cache) => {
883
1342
  return openBlock(), createElementBlock("div", {
@@ -890,7 +1349,7 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
890
1349
  schema,
891
1350
  class: normalizeClass(getFormItemClass(schema))
892
1351
  }, createSlots({ _: 2 }, [
893
- renderList(_ctx.$slots, (_, slotName) => {
1352
+ renderList(fieldSlots.value, (_, slotName) => {
894
1353
  return {
895
1354
  name: slotName,
896
1355
  fn: withCtx((slotProps) => [
@@ -899,7 +1358,42 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
899
1358
  };
900
1359
  })
901
1360
  ]), 1032, ["schema", "class"]);
902
- }), 128))
1361
+ }), 128)),
1362
+ isActionsVisible.value ? (openBlock(), createElementBlock("div", _hoisted_1, [
1363
+ isResetterVisible.value && unref(slots).resetter ? renderSlot(_ctx.$slots, "resetter", {
1364
+ form: unref(formInstance),
1365
+ loading: effectiveLoading.value,
1366
+ disabled: isResetterDisabled.value,
1367
+ reset: handleReset
1368
+ }, void 0, void 0, 0) : isResetterVisible.value ? (openBlock(), createBlock(unref(_sfc_main$1), mergeProps({ key: 1 }, resetterButtonProps.value, {
1369
+ class: "schemx-actions-button schemx-actions-button--reset",
1370
+ type: "button",
1371
+ disabled: isResetterDisabled.value,
1372
+ onClick: handleReset
1373
+ }), {
1374
+ default: withCtx(() => [
1375
+ createTextVNode(toDisplayString(resetterConfig.value.text ?? "重置"), 1)
1376
+ ]),
1377
+ _: 1
1378
+ }, 16, ["disabled"])) : createCommentVNode("", true),
1379
+ isSubmitterVisible.value && unref(slots).submitter ? renderSlot(_ctx.$slots, "submitter", {
1380
+ form: unref(formInstance),
1381
+ loading: effectiveLoading.value,
1382
+ disabled: isSubmitterDisabled.value,
1383
+ submit: handleSubmit
1384
+ }, void 0, void 0, 2) : isSubmitterVisible.value ? (openBlock(), createBlock(unref(_sfc_main$1), mergeProps({ key: 3 }, submitterButtonProps.value, {
1385
+ class: "schemx-actions-button schemx-actions-button--submit",
1386
+ type: "button",
1387
+ loading: effectiveLoading.value,
1388
+ disabled: isSubmitterDisabled.value,
1389
+ onClick: handleSubmit
1390
+ }), {
1391
+ default: withCtx(() => [
1392
+ createTextVNode(toDisplayString(submitterConfig.value.text ?? "提交"), 1)
1393
+ ]),
1394
+ _: 1
1395
+ }, 16, ["loading", "disabled"])) : createCommentVNode("", true)
1396
+ ])) : createCommentVNode("", true)
903
1397
  ], 6);
904
1398
  };
905
1399
  }
@@ -950,6 +1444,7 @@ function WithRemoteOptions(WrappedComponent) {
950
1444
  });
951
1445
  }
952
1446
  export {
1447
+ _sfc_main$1 as Button,
953
1448
  FormGroup$1 as FormGroup,
954
1449
  FormItem$1 as FormItem,
955
1450
  WithRemoteOptions,
@@ -957,6 +1452,7 @@ export {
957
1452
  createFormConfigContext,
958
1453
  createFormContext,
959
1454
  SchemxFormExport$1 as default,
1455
+ getCoreForm,
960
1456
  rendererRegistry,
961
1457
  SchemxFormExport$1 as schemxForm,
962
1458
  useDictionary,
@@ -965,6 +1461,7 @@ export {
965
1461
  useForm,
966
1462
  useFormConfigContext,
967
1463
  useFormContext,
1464
+ useFormSelector,
968
1465
  useStableRef,
969
1466
  useViewSchemas,
970
1467
  useWatch,