@schemx/vue 1.0.0-next.0 → 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.
Files changed (48) hide show
  1. package/README.md +172 -129
  2. package/dist/analyze.html +1 -1
  3. package/dist/components/Button/index.d.ts +3 -0
  4. package/dist/components/Button/index.d.ts.map +1 -0
  5. package/dist/components/Button/types.d.ts +21 -0
  6. package/dist/components/Button/types.d.ts.map +1 -0
  7. package/dist/components/FormItem/index.d.ts +5 -6
  8. package/dist/components/FormItem/index.d.ts.map +1 -1
  9. package/dist/components/FormItem/slot.d.ts +48 -0
  10. package/dist/components/FormItem/slot.d.ts.map +1 -0
  11. package/dist/config/appConfig.d.ts.map +1 -1
  12. package/dist/form.d.ts.map +1 -1
  13. package/dist/formBridge.d.ts +188 -0
  14. package/dist/formBridge.d.ts.map +1 -0
  15. package/dist/hocs/withRemoteOptions.d.ts.map +1 -1
  16. package/dist/hooks/index.d.ts +4 -2
  17. package/dist/hooks/index.d.ts.map +1 -1
  18. package/dist/hooks/provideFormConfigContext.d.ts +3 -4
  19. package/dist/hooks/provideFormConfigContext.d.ts.map +1 -1
  20. package/dist/hooks/provideFormContext.d.ts +7 -4
  21. package/dist/hooks/provideFormContext.d.ts.map +1 -1
  22. package/dist/hooks/useField.d.ts +4 -30
  23. package/dist/hooks/useField.d.ts.map +1 -1
  24. package/dist/hooks/useForm.d.ts +3 -2
  25. package/dist/hooks/useForm.d.ts.map +1 -1
  26. package/dist/hooks/useFormSelector.d.ts +38 -0
  27. package/dist/hooks/useFormSelector.d.ts.map +1 -0
  28. package/dist/hooks/useViewSchemas.d.ts +23 -3
  29. package/dist/hooks/useViewSchemas.d.ts.map +1 -1
  30. package/dist/hooks/useWatch.d.ts.map +1 -1
  31. package/dist/index.cjs +1 -1
  32. package/dist/index.cjs.map +1 -1
  33. package/dist/index.d.ts +2 -0
  34. package/dist/index.d.ts.map +1 -1
  35. package/dist/index.mjs +807 -224
  36. package/dist/index.mjs.map +1 -1
  37. package/dist/style.css +1 -1
  38. package/dist/types/dictionary.d.ts +6 -3
  39. package/dist/types/dictionary.d.ts.map +1 -1
  40. package/dist/types/field.d.ts +1 -1
  41. package/dist/types/form.d.ts +34 -2
  42. package/dist/types/form.d.ts.map +1 -1
  43. package/dist/utils/helpers.d.ts.map +1 -1
  44. package/dist/utils/rendererProvider.d.ts +2 -1
  45. package/dist/utils/rendererProvider.d.ts.map +1 -1
  46. package/package.json +8 -6
  47. package/dist/hooks/useEffect.d.ts +0 -33
  48. package/dist/hooks/useEffect.d.ts.map +0 -1
package/dist/index.mjs CHANGED
@@ -1,8 +1,9 @@
1
1
  import "./style.css";
2
- import { getCurrentInstance, inject, onScopeDispose, provide, onUnmounted, shallowRef, computed, ref, onMounted, watchEffect, defineComponent, watch, createVNode, h, toRef, createTextVNode, reactive, openBlock, createElementBlock, normalizeStyle, normalizeClass, Fragment, renderList, unref, createBlock, createSlots, withCtx, renderSlot, mergeProps } from "vue";
3
- import classnames from "classnames";
4
- import { createRendererRegistry, createValidationRuleRegistry, mergeSchemxConfig, createForm, createField, createWatch, createEffect, isSchemxSchemas, defaultSchemxConfigKeys } from "@schemx/core";
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
+ import { createValidationRuleRegistry, mergeSchemxConfig, createForm, createField, createWatch, isViewGroupSchema, isSchemxViewFieldSchema, isSchemxSchemas, defaultSchemxConfigKeys } from "@schemx/core";
5
4
  export * from "@schemx/core";
5
+ import classnames from "classnames";
6
+ import { createFormExternalStore, createRendererRegistry } from "@schemx/core/adapter";
6
7
  import { pick } from "es-toolkit";
7
8
  const SCHEMX_APP_CONFIG_KEY = Symbol("schemx:app-config");
8
9
  const EMPTY_SCHEMX_CONFIG = Object.freeze({
@@ -14,12 +15,22 @@ function normalizeSchemxAppConfig(config) {
14
15
  const validatorAdapters = Object.freeze([...config.validatorAdapters ?? []]);
15
16
  return Object.freeze({
16
17
  schemaConfig,
18
+ rendererProps: normalizeRendererProps(config.rendererProps),
17
19
  validatorAdapters,
18
20
  defaultRendererType: config.defaultRendererType,
19
21
  rendererRegistry: config.rendererRegistry,
20
22
  validationRuleRegistry: config.validationRuleRegistry
21
23
  });
22
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
+ }
23
34
  function provideSchemxAppConfig(app, config = {}) {
24
35
  const normalizedConfig = normalizeSchemxAppConfig(config);
25
36
  app.provide(SCHEMX_APP_CONFIG_KEY, normalizedConfig);
@@ -30,11 +41,258 @@ function getSchemxAppConfig() {
30
41
  }
31
42
  return inject(SCHEMX_APP_CONFIG_KEY, EMPTY_SCHEMX_CONFIG);
32
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
+ }
33
290
  const rendererRegistry = createRendererRegistry("input");
34
291
  const validationRuleRegistry = createValidationRuleRegistry();
35
292
  function useForm(options = {}) {
36
293
  const configuredOptions = mergeSchemxConfig(
37
294
  getUseFormSchemxConfig(options),
295
+ // App 安装配置以 Values 存储;在 useForm 边界关联到当前 TValues。
38
296
  getSchemxAppConfig(),
39
297
  {
40
298
  rendererRegistry,
@@ -46,22 +304,26 @@ function useForm(options = {}) {
46
304
  ...configuredOptions
47
305
  };
48
306
  const instance = createForm(mergedOptions);
307
+ const form = getVueFormFacade(instance);
308
+ const releaseBridge = retainVueFormBridge(getVueFormBridge(instance));
49
309
  onScopeDispose(() => {
50
- instance.destroy();
310
+ releaseBridge();
311
+ form.destroy();
51
312
  });
52
- return instance;
313
+ return form;
53
314
  }
54
315
  function getUseFormSchemxConfig(options) {
55
316
  const {
56
317
  schemaConfig = {},
318
+ rendererProps = void 0,
57
319
  validatorAdapters = [],
58
320
  defaultRendererType = void 0,
59
321
  rendererRegistry: rendererRegistry2 = void 0,
60
- validationRuleRegistry: validationRuleRegistry2 = void 0,
61
- ...rest
322
+ validationRuleRegistry: validationRuleRegistry2 = void 0
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
@@ -174,13 +414,9 @@ function useWatchFields(names, callback, options) {
174
414
  function useWatchAll(callback, options) {
175
415
  return useWatch(callback, options);
176
416
  }
177
- function useEffect(callback) {
178
- const dispose = createEffect(callback);
179
- onUnmounted(dispose);
180
- return dispose;
181
- }
182
417
  function normalizeError(err) {
183
- if (err instanceof Error) return err;
418
+ if (err instanceof Error)
419
+ return err;
184
420
  return new Error(String(err));
185
421
  }
186
422
  const useDictionary = (options, fieldName) => {
@@ -229,9 +465,11 @@ const useDictionary = (options, fieldName) => {
229
465
  error.value = void 0;
230
466
  const currentCount = ++requestCount;
231
467
  const res = await executeWithRetry(formValues);
232
- if (currentCount !== requestCount) return;
468
+ if (currentCount !== requestCount)
469
+ return;
233
470
  const formatted = await format(res);
234
- if (currentCount !== requestCount) return;
471
+ if (currentCount !== requestCount)
472
+ return;
235
473
  list.value = formatted;
236
474
  error.value = void 0;
237
475
  if (typeof options.onSuccess === "function") {
@@ -289,7 +527,8 @@ function useFormConfigContext() {
289
527
  const isShallowEqual = (a, b) => {
290
528
  const keysA = Object.keys(a);
291
529
  const keysB = Object.keys(b);
292
- if (keysA.length !== keysB.length) return false;
530
+ if (keysA.length !== keysB.length)
531
+ return false;
293
532
  return keysA.every((key) => a[key] === b[key]);
294
533
  };
295
534
  function useStableRef(factory) {
@@ -302,24 +541,102 @@ function useStableRef(factory) {
302
541
  });
303
542
  return stableRef;
304
543
  }
544
+ const viewSchemaBridgeCache = /* @__PURE__ */ new WeakMap();
305
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) {
306
554
  const viewSchemas = shallowRef(
307
555
  form.getViewSchemas()
308
556
  );
309
- const unsubscribe = form.subscribeViewSchemas((nextSchemas) => {
310
- 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();
311
625
  });
312
- onScopeDispose(unsubscribe);
313
- return viewSchemas;
626
+ return readonly(selected);
314
627
  }
315
628
  function isValidTrigger(v) {
316
- if (v === void 0) return false;
317
- if (Array.isArray(v) && v.length === 0) return false;
629
+ if (v === void 0)
630
+ return false;
631
+ if (Array.isArray(v) && v.length === 0)
632
+ return false;
318
633
  return true;
319
634
  }
320
635
  function mergeTrigger(columnTrigger, contextTrigger, defaultTrigger) {
321
- if (isValidTrigger(columnTrigger)) return columnTrigger;
322
- if (isValidTrigger(contextTrigger)) return contextTrigger;
636
+ if (isValidTrigger(columnTrigger))
637
+ return columnTrigger;
638
+ if (isValidTrigger(contextTrigger))
639
+ return contextTrigger;
323
640
  return defaultTrigger;
324
641
  }
325
642
  function normalizeTrigger(t) {
@@ -334,7 +651,8 @@ function normalizeTrigger(t) {
334
651
  return map[t] ?? "submit";
335
652
  }
336
653
  function shouldValidateOn(event, trigger) {
337
- if (!trigger) return false;
654
+ if (!trigger)
655
+ return false;
338
656
  const triggers = Array.isArray(trigger) ? trigger : [trigger];
339
657
  return triggers.some((t) => normalizeTrigger(t) === event);
340
658
  }
@@ -347,17 +665,21 @@ const kebabToCamel = (str) => {
347
665
  return str.replace(/-([a-z])/g, (_match, letter) => letter.toUpperCase());
348
666
  };
349
667
  const normalizeToKebab = (str) => {
350
- if (isCamelCase(str)) return camelToKebab(str);
668
+ if (isCamelCase(str))
669
+ return camelToKebab(str);
351
670
  return str;
352
671
  };
353
672
  const normalizeToCamel = (str) => {
354
- if (isKebabCase(str)) return kebabToCamel(str);
673
+ if (isKebabCase(str))
674
+ return kebabToCamel(str);
355
675
  return str;
356
676
  };
357
677
  const resolveSlot = (slots, name) => {
358
- if (slots[name]) return slots[name];
678
+ if (slots[name])
679
+ return slots[name];
359
680
  const alt = isCamelCase(name) ? camelToKebab(name) : isKebabCase(name) ? kebabToCamel(name) : void 0;
360
- if (alt && slots[alt]) return slots[alt];
681
+ if (alt && slots[alt])
682
+ return slots[alt];
361
683
  return void 0;
362
684
  };
363
685
  const extractChildSlots = (fieldName, allSlots) => {
@@ -420,7 +742,7 @@ const FormGroup = /* @__PURE__ */ defineComponent((props, {
420
742
  "style": !destroyOnCollapse && isCollapsed ? {
421
743
  display: "none"
422
744
  } : void 0
423
- }, [schema.children.map((child) => createVNode(FormItem, {
745
+ }, [schema.children.map((child) => createVNode(FormItem$1, {
424
746
  "key": child.key,
425
747
  "schema": child
426
748
  }, slots))]);
@@ -467,9 +789,105 @@ const FormGroup = /* @__PURE__ */ defineComponent((props, {
467
789
  }
468
790
  }
469
791
  });
792
+ const FormGroup$1 = FormGroup;
470
793
  const normalizeId = (key) => {
471
794
  return String(key).replace(/[^a-zA-Z0-9_-]/g, "-");
472
795
  };
796
+ function createFormItemSlotRenderers(options) {
797
+ const {
798
+ schemaRef,
799
+ field,
800
+ form,
801
+ formContext,
802
+ componentProps,
803
+ slots
804
+ } = options;
805
+ const renderRequired = () => {
806
+ const showRequiredMark = schemaRef.value.showRequiredMark ?? Boolean(schemaRef.value.required);
807
+ if (!showRequiredMark || schemaRef.value.disabled || schemaRef.value.readonly) {
808
+ return null;
809
+ }
810
+ return createVNode("span", {
811
+ "class": "schemx-item__required"
812
+ }, [createTextVNode("*")]);
813
+ };
814
+ const createSlotProps = (additionalProps = {}) => {
815
+ return {
816
+ ...componentProps.value,
817
+ value: field.value.value,
818
+ ...additionalProps
819
+ };
820
+ };
821
+ const renderFieldSlot = (suffix) => {
822
+ const slot = resolveSlot(slots, `${schemaRef.value.name}${suffix}`);
823
+ return (slot == null ? void 0 : slot(createSlotProps())) ?? null;
824
+ };
825
+ const renderLabel = () => {
826
+ const labelSlot = resolveSlot(slots, `${schemaRef.value.name}Label`);
827
+ if (labelSlot) {
828
+ return labelSlot(schemaRef.value);
829
+ }
830
+ const labelAlign = schemaRef.value.labelAlign || formContext.schemaConfig.labelAlign;
831
+ const labelWidth = schemaRef.value.labelWidth || formContext.schemaConfig.labelWidth;
832
+ const colon = schemaRef.value.colon ?? formContext.schemaConfig.colon;
833
+ return createVNode("label", {
834
+ "class": "schemx-item__label",
835
+ "style": {
836
+ width: labelWidth,
837
+ textAlign: labelAlign
838
+ }
839
+ }, [renderRequired(), createVNode("span", {
840
+ "class": "schemx-item__label-text"
841
+ }, [schemaRef.value.label, colon ? ":" : ""])]);
842
+ };
843
+ const renderBefore = () => renderFieldSlot("Before");
844
+ const renderContent = () => {
845
+ const component = form.getRenderer(schemaRef.value.componentType);
846
+ if (!component) {
847
+ throw new Error(`[schemx] Can not find component renderer of "${schemaRef.value.componentType}".`);
848
+ }
849
+ const childSlots = extractChildSlots(normalizeNameKey(schemaRef.value.name), slots);
850
+ const columnElement = h(component, componentProps.value, childSlots);
851
+ const contentSlot = resolveSlot(slots, `${schemaRef.value.name}Content`);
852
+ if (contentSlot) {
853
+ return contentSlot(createSlotProps({
854
+ columnElement
855
+ }));
856
+ }
857
+ return createVNode("div", {
858
+ "class": "schemx-item__control"
859
+ }, [columnElement]);
860
+ };
861
+ const renderAfter = () => renderFieldSlot("After");
862
+ const renderError = () => {
863
+ const errorSlot = resolveSlot(slots, `${schemaRef.value.name}Error`);
864
+ if (errorSlot) {
865
+ return errorSlot(createSlotProps({
866
+ errors: field.errors.value
867
+ }));
868
+ }
869
+ if (field.errors.value.length === 0) {
870
+ return null;
871
+ }
872
+ return createVNode("div", {
873
+ "class": "schemx-item__error"
874
+ }, [field.errors.value[0]]);
875
+ };
876
+ return {
877
+ createSlotProps,
878
+ renderAfter,
879
+ renderBefore,
880
+ renderContent,
881
+ renderError,
882
+ renderLabel
883
+ };
884
+ }
885
+ function normalizeNameKey(name) {
886
+ if (Array.isArray(name)) {
887
+ return name.map((part) => String(part)).join(".");
888
+ }
889
+ return String(name);
890
+ }
473
891
  const FormItem = /* @__PURE__ */ defineComponent({
474
892
  name: "SchemxItem",
475
893
  props: {
@@ -478,13 +896,19 @@ const FormItem = /* @__PURE__ */ defineComponent({
478
896
  required: true
479
897
  }
480
898
  },
899
+ /**
900
+ * 根据 schema 类型分发字段组或字段项渲染。
901
+ *
902
+ * @param props - 当前组件的 schema 属性。
903
+ * @param slots - Vue setup 上下文提供的插槽集合。
904
+ */
481
905
  setup(props, {
482
906
  slots
483
907
  }) {
484
908
  return () => {
485
909
  const schema = props.schema;
486
- if (isViewGroupSchema$1(schema)) {
487
- return h(FormGroup, {
910
+ if (isViewGroupSchema(schema)) {
911
+ return h(FormGroup$1, {
488
912
  schema
489
913
  }, slots);
490
914
  }
@@ -496,152 +920,169 @@ const FormItem = /* @__PURE__ */ defineComponent({
496
920
  });
497
921
  const FieldFormItem = /* @__PURE__ */ defineComponent({
498
922
  name: "SchemxFieldItem",
923
+ inheritAttrs: false,
499
924
  props: {
500
925
  schema: {
501
926
  type: Object,
502
927
  required: true
503
928
  }
504
929
  },
930
+ /**
931
+ * 初始化字段上下文,并组合响应式 schema、校验处理器与插槽渲染器。
932
+ *
933
+ * @param props - 当前字段项的 schema 属性。
934
+ * @param attrs - Vue setup 上下文提供的透传属性。
935
+ * @param slots - Vue setup 上下文提供的插槽集合。
936
+ */
505
937
  setup(props, {
938
+ attrs,
506
939
  slots
507
940
  }) {
508
- const schemaRef = toRef(props, "schema");
509
941
  const form = useFormContext();
942
+ const inputSchema = computed(() => props.schema);
943
+ const latestSchema = useViewSchema(form, () => inputSchema.value.key);
944
+ const schemaRef = computed(() => {
945
+ return latestSchema.value && isSchemxViewFieldSchema(latestSchema.value) ? latestSchema.value : inputSchema.value;
946
+ });
510
947
  const formContext = useFormConfigContext();
511
- const schema = () => schemaRef.value;
512
- const field = useField(schema().name);
948
+ const field = useField(schemaRef.value.name);
513
949
  createFieldContext(field);
514
- const trigger = computed(() => mergeTrigger(schema().validationTrigger, formContext.schemaConfig.validationTrigger, "onChange"));
950
+ const trigger = computed(() => mergeTrigger(schemaRef.value.validationTrigger, formContext.schemaConfig.validationTrigger, "onChange"));
515
951
  const canVerified = computed(() => {
516
- const isOperate = schema().visible && !schema().readonly && !schema().disabled;
517
- const rules = schema().rules;
518
- const hasRules = Array.isArray(rules) ? (rules == null ? void 0 : rules.length) > 0 : !!schema().rules;
519
- return isOperate && (Boolean(schema().required) || hasRules);
952
+ const isOperate = schemaRef.value.visible && !schemaRef.value.readonly && !schemaRef.value.disabled;
953
+ const rules = schemaRef.value.rules;
954
+ const hasRules = Array.isArray(rules) ? (rules == null ? void 0 : rules.length) > 0 : !!schemaRef.value.rules;
955
+ return isOperate && (Boolean(schemaRef.value.required) || hasRules);
520
956
  });
521
957
  const handleChange = (v) => {
522
958
  var _a, _b;
523
959
  field.setValue(v);
524
- (_b = (_a = schema().componentProps) == null ? void 0 : _a.onChange) == null ? void 0 : _b.call(_a, v);
960
+ (_b = (_a = schemaRef.value.componentProps) == null ? void 0 : _a.onChange) == null ? void 0 : _b.call(_a, v);
525
961
  if (canVerified.value && shouldValidateOn("change", trigger.value)) {
526
962
  field.validate();
527
963
  }
528
964
  };
529
965
  const handleBlur = (v) => {
530
966
  var _a, _b;
531
- (_b = (_a = schema().componentProps) == null ? void 0 : _a.onBlur) == null ? void 0 : _b.call(_a, v);
967
+ (_b = (_a = schemaRef.value.componentProps) == null ? void 0 : _a.onBlur) == null ? void 0 : _b.call(_a, v);
532
968
  if (canVerified.value && shouldValidateOn("blur", trigger.value)) {
533
969
  field.validate();
534
970
  }
535
971
  };
972
+ const handleValueUpdate = (v) => {
973
+ field.setValue(v);
974
+ };
536
975
  const componentProps = useStableRef(() => {
537
- const currentSchema = schema();
976
+ const currentComponentProps = schemaRef.value.componentProps ?? {};
538
977
  return {
539
- ...currentSchema.componentProps,
540
- readonly: currentSchema.readonly,
541
- disabled: currentSchema.disabled,
542
- placeholder: currentSchema.placeholder,
543
- formItemProps: currentSchema,
544
- value: field.getValue(),
978
+ ...currentComponentProps,
979
+ value: field.value.value,
545
980
  onChange: handleChange,
546
981
  onBlur: handleBlur,
547
- "onUpdate:value": (v) => field.setValue(v)
982
+ "onUpdate:value": handleValueUpdate
548
983
  };
549
984
  });
550
- const renderRequired = () => {
551
- const showRequiredMark = schema().showRequiredMark ?? Boolean(schema().required);
552
- if (!showRequiredMark || schema().disabled || schema().readonly) {
553
- return null;
554
- }
555
- return createVNode("span", {
556
- "class": "schemx-item__required"
557
- }, [createTextVNode("*")]);
558
- };
559
- const renderLabel = () => {
560
- const labelSlot = resolveSlot(slots, `${schema().name}Label`);
561
- if (labelSlot) {
562
- return labelSlot(schema());
563
- }
564
- const labelAlign = schema().labelAlign || formContext.schemaConfig.labelAlign;
565
- const labelWidth = schema().labelWidth || formContext.schemaConfig.labelWidth;
566
- const colon = schema().colon ?? formContext.schemaConfig.colon;
567
- return createVNode("label", {
568
- "class": "schemx-item__label",
569
- "style": {
570
- width: labelWidth,
571
- textAlign: labelAlign
572
- }
573
- }, [renderRequired(), createVNode("span", {
574
- "class": "schemx-item__label-text"
575
- }, [schema().label, colon ? ":" : ""])]);
576
- };
577
- const renderContent = () => {
578
- const component = form.getRenderer(schema().componentType);
579
- if (!component) {
580
- throw new Error(`[schemx] Can not find component renderer of "${schema().componentType}".`);
581
- }
582
- const childSlots = extractChildSlots(normalizeNameKey(schema().name), slots);
583
- const columnElement = h(component, componentProps.value, childSlots);
584
- const contentSlot = resolveSlot(slots, `${schema().name}Content`);
585
- if (contentSlot) {
586
- return contentSlot({
587
- ...schema(),
588
- columnElement
589
- });
590
- }
591
- return createVNode("div", {
592
- "class": "schemx-item__control"
593
- }, [columnElement]);
594
- };
595
- const renderError = () => {
596
- const errorSlot = resolveSlot(slots, `${schema().name}Error`);
597
- if (errorSlot) {
598
- return errorSlot({
599
- ...schema(),
600
- errors: field.errors.value
601
- });
602
- }
603
- if (field.errors.value.length === 0) {
604
- return null;
605
- }
606
- return createVNode("div", {
607
- "class": "schemx-item__error"
608
- }, [field.errors.value[0]]);
609
- };
985
+ const {
986
+ createSlotProps,
987
+ renderAfter,
988
+ renderBefore,
989
+ renderContent,
990
+ renderError,
991
+ renderLabel
992
+ } = createFormItemSlotRenderers({
993
+ schemaRef,
994
+ field,
995
+ form,
996
+ formContext,
997
+ componentProps,
998
+ slots
999
+ });
610
1000
  return () => {
611
- if (!schema().visible) {
1001
+ if (!schemaRef.value.visible) {
612
1002
  return null;
613
1003
  }
614
- const itemSlot = resolveSlot(slots, normalizeNameKey(schema().name));
1004
+ const itemSlot = resolveSlot(slots, normalizeNameKey(schemaRef.value.name));
615
1005
  if (itemSlot) {
616
- return itemSlot(schema());
1006
+ return itemSlot(createSlotProps());
617
1007
  }
618
- const labelPosition = schema().labelPosition || formContext.schemaConfig.labelPosition;
619
- return createVNode("div", {
620
- "class": classnames("schemx-item-wrapper"),
621
- "style": schema().style
622
- }, [createVNode("div", {
623
- "class": classnames("schemx-item", `schemx-item--label-${labelPosition}`, schema().class, {
624
- "is-readonly": schema().readonly,
625
- "is-disabled": schema().disabled
1008
+ const labelPosition = schemaRef.value.labelPosition || formContext.schemaConfig.labelPosition;
1009
+ return createVNode("div", mergeProps(attrs, {
1010
+ "class": classnames("schemx-item-wrapper", attrs.class),
1011
+ "style": [attrs.style, schemaRef.value.style]
1012
+ }), [createVNode("div", {
1013
+ "class": classnames("schemx-item", `schemx-item--label-${labelPosition}`, schemaRef.value.class, {
1014
+ "is-readonly": schemaRef.value.readonly,
1015
+ "is-disabled": schemaRef.value.disabled
626
1016
  }),
627
1017
  "style": {
628
- ...schema().style ?? {}
1018
+ ...schemaRef.value.style ?? {}
629
1019
  }
630
1020
  }, [renderLabel(), createVNode("div", {
631
1021
  "class": "schemx-item__content"
632
- }, [renderContent(), renderError()])])]);
1022
+ }, [renderBefore(), renderContent(), renderAfter(), renderError()])])]);
633
1023
  };
634
1024
  }
635
1025
  });
636
- const isViewGroupSchema$1 = (schema) => {
637
- return "children" in schema;
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"
638
1031
  };
639
- const normalizeNameKey = (name) => {
640
- if (Array.isArray(name)) {
641
- return name.map((part) => String(part)).join(".");
642
- }
643
- return String(name);
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"
644
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
+ });
645
1086
  function getSectionPosition(list, currentKey) {
646
1087
  const currentIndex = list.findIndex((item) => item.key === currentKey);
647
1088
  if (currentIndex === -1) {
@@ -665,9 +1106,6 @@ function getSectionPosition(list, currentKey) {
665
1106
  isLast: !hasPositionItemInSection(list, currentIndex, 1)
666
1107
  };
667
1108
  }
668
- function isViewGroupSchema(item) {
669
- return !!item && "children" in item;
670
- }
671
1109
  function isPositionItem(item) {
672
1110
  return !!item && !isViewGroupSchema(item) && item.visible !== false;
673
1111
  }
@@ -686,6 +1124,10 @@ function hasPositionItemInSection(list, startIndex, step) {
686
1124
  }
687
1125
  return false;
688
1126
  }
1127
+ const _hoisted_1 = {
1128
+ key: 0,
1129
+ class: "schemx-actions"
1130
+ };
689
1131
  const _sfc_main = /* @__PURE__ */ defineComponent({
690
1132
  ...{ name: "SchemxForm" },
691
1133
  __name: "form",
@@ -694,8 +1136,12 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
694
1136
  initialValues: { default: () => ({}) },
695
1137
  modelValue: { default: () => ({}) },
696
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 },
697
1142
  class: { default: "" },
698
1143
  style: { type: [Boolean, null, String, Object, Array], default: () => ({}) },
1144
+ rendererProps: { default: void 0 },
699
1145
  validatorAdapters: {},
700
1146
  defaultRendererType: {},
701
1147
  rendererRegistry: { default: void 0 },
@@ -703,6 +1149,8 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
703
1149
  onRuleError: {},
704
1150
  onFinish: { type: Function, default: void 0 },
705
1151
  onFinishFailed: { type: Function, default: void 0 },
1152
+ onReset: { type: Function, default: void 0 },
1153
+ onLoadingChange: { type: Function, default: void 0 },
706
1154
  onValuesChange: { type: Function, default: void 0 },
707
1155
  onFieldsChange: { type: Function, default: void 0 },
708
1156
  lifecycleHooks: {},
@@ -723,61 +1171,150 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
723
1171
  setup(__props, { expose: __expose, emit: __emit }) {
724
1172
  const props = __props;
725
1173
  const emit = __emit;
1174
+ const slots = useSlots();
726
1175
  const pickSchemaConfig = () => {
727
1176
  return pick(props, defaultSchemxConfigKeys);
728
1177
  };
729
1178
  const formSchemaConfig = reactive(pickSchemaConfig());
730
1179
  createFormConfigContext({ schemaConfig: formSchemaConfig });
731
- const form = props.form ? props.form : useForm({
1180
+ const providedForm = props.form ? props.form : useForm({
732
1181
  schemas: props.schemas,
733
1182
  schemaConfig: pickSchemaConfig(),
734
1183
  initialValues: Object.keys(props.modelValue).length > 0 ? props.modelValue : props.initialValues,
1184
+ rendererProps: props.rendererProps,
735
1185
  rendererRegistry: props.rendererRegistry,
736
1186
  defaultRendererType: props.defaultRendererType,
737
1187
  validationRuleRegistry: props.validationRuleRegistry,
738
1188
  validatorAdapters: props.validatorAdapters,
739
- onFinish: async (values) => {
1189
+ /**
1190
+ * 转发提交成功回调。
1191
+ *
1192
+ * @param values - 提交成功时的完整表单快照。
1193
+ */
1194
+ onFinish: (values) => {
740
1195
  var _a;
741
- (_a = props.onFinish) == null ? void 0 : _a.call(props, values);
1196
+ return (_a = props.onFinish) == null ? void 0 : _a.call(props, values);
742
1197
  },
743
- onFinishFailed: async (errors) => {
1198
+ /**
1199
+ * 转发提交失败回调。
1200
+ *
1201
+ * @param errors - 提交失败时的字段错误集合。
1202
+ */
1203
+ onFinishFailed: (errors) => {
744
1204
  var _a;
745
- (_a = props.onFinishFailed) == null ? void 0 : _a.call(props, errors);
1205
+ return (_a = props.onFinishFailed) == null ? void 0 : _a.call(props, errors);
746
1206
  },
1207
+ /**
1208
+ * 转发完整表单重置回调。
1209
+ */
1210
+ onReset: () => {
1211
+ var _a;
1212
+ (_a = props.onReset) == null ? void 0 : _a.call(props);
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
+ */
747
1231
  onValuesChange: (changedValues, latestSnapshot) => {
748
1232
  var _a;
749
1233
  (_a = props.onValuesChange) == null ? void 0 : _a.call(props, changedValues, latestSnapshot);
750
1234
  },
1235
+ /**
1236
+ * 转发字段变化回调。
1237
+ *
1238
+ * @param changedPaths - 本次发生变化的字段路径。
1239
+ * @param allPaths - 当前已变更字段路径集合。
1240
+ */
751
1241
  onFieldsChange: (changedPaths, allPaths) => {
752
1242
  var _a;
753
1243
  (_a = props.onFieldsChange) == null ? void 0 : _a.call(props, changedPaths, allPaths);
754
1244
  }
755
1245
  });
756
- createFormContext(form);
1246
+ const isExternalForm = props.form !== void 0;
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
+ );
757
1284
  let syncingFromModel = false;
758
1285
  watch(
759
1286
  () => props.modelValue,
760
1287
  (values) => {
761
1288
  syncingFromModel = true;
762
- form.setFieldsValue(values);
1289
+ formInstance.setFieldsValue(values);
763
1290
  syncingFromModel = false;
764
- }
1291
+ },
1292
+ { deep: true }
1293
+ );
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" }
765
1307
  );
766
- const disposeWatch = createWatch(form, (latestSnapshot) => {
767
- if (syncingFromModel) return;
768
- emit("update:modelValue", latestSnapshot);
769
- });
770
- onUnmounted(disposeWatch);
771
1308
  watch(
772
1309
  () => props.schemas,
773
1310
  (schemas) => {
774
1311
  if (!isSchemxSchemas(schemas)) {
775
- form.setSchemas(schemas);
1312
+ formInstance.setSchemas(schemas);
776
1313
  }
777
1314
  },
778
1315
  { deep: false, immediate: !!props.form }
779
1316
  );
780
- const viewSchemas = useViewSchemas(form);
1317
+ const viewSchemas = useViewSchemas(formInstance);
781
1318
  const getFormItemClass = (schema) => {
782
1319
  const { isFirst, isLast } = getSectionPosition(
783
1320
  viewSchemas.value,
@@ -788,13 +1325,18 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
788
1325
  "schemx-item-wrapper--last": isLast
789
1326
  };
790
1327
  };
791
- watchEffect(() => {
792
- const nextSchemaConfig = pickSchemaConfig();
793
- Object.assign(formSchemaConfig, nextSchemaConfig);
794
- form.updateSchemaConfig(nextSchemaConfig);
795
- });
1328
+ watch(
1329
+ pickSchemaConfig,
1330
+ (nextSchemaConfig) => {
1331
+ Object.assign(formSchemaConfig, nextSchemaConfig);
1332
+ formInstance.updateSchemaConfig(nextSchemaConfig);
1333
+ },
1334
+ { deep: false, immediate: isExternalForm }
1335
+ );
796
1336
  __expose({
797
- ...form
1337
+ ...formInstance,
1338
+ submit: handleSubmit,
1339
+ reset: handleReset
798
1340
  });
799
1341
  return (_ctx, _cache) => {
800
1342
  return openBlock(), createElementBlock("div", {
@@ -802,12 +1344,12 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
802
1344
  style: normalizeStyle(props.style)
803
1345
  }, [
804
1346
  (openBlock(true), createElementBlock(Fragment, null, renderList(unref(viewSchemas), (schema) => {
805
- return openBlock(), createBlock(unref(FormItem), {
1347
+ return openBlock(), createBlock(unref(FormItem$1), {
806
1348
  key: schema.key,
807
1349
  schema,
808
1350
  class: normalizeClass(getFormItemClass(schema))
809
1351
  }, createSlots({ _: 2 }, [
810
- renderList(_ctx.$slots, (_, slotName) => {
1352
+ renderList(fieldSlots.value, (_, slotName) => {
811
1353
  return {
812
1354
  name: slotName,
813
1355
  fn: withCtx((slotProps) => [
@@ -816,7 +1358,42 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
816
1358
  };
817
1359
  })
818
1360
  ]), 1032, ["schema", "class"]);
819
- }), 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)
820
1397
  ], 6);
821
1398
  };
822
1399
  }
@@ -831,31 +1408,35 @@ const SchemxFormExport = withInstall(_sfc_main, {
831
1408
  app.component("SchemxForm", _sfc_main);
832
1409
  },
833
1410
  /** FormItem 子组件引用 */
834
- FormItem
1411
+ FormItem: FormItem$1
835
1412
  });
1413
+ const SchemxFormExport$1 = SchemxFormExport;
1414
+ function normalizeDictionary(dictionary) {
1415
+ if (typeof dictionary === "function") {
1416
+ return { api: dictionary };
1417
+ }
1418
+ return dictionary;
1419
+ }
836
1420
  function WithRemoteOptions(WrappedComponent) {
837
1421
  return defineComponent({
838
1422
  name: `WithRemoteOptions(${WrappedComponent.name || "Anonymous"})`,
839
1423
  inheritAttrs: false,
840
1424
  props: {
841
1425
  dict: {
842
- type: Object,
843
- default: void 0
844
- },
845
- fieldName: {
846
- type: [String, Array],
1426
+ type: [Object, Function],
847
1427
  default: void 0
848
1428
  }
849
1429
  },
850
1430
  setup(props, { attrs, slots }) {
851
- const fieldName = props.fieldName ?? (props.dict ? useFieldContext().name : void 0);
852
- const dictResult = props.dict ? useDictionary(props.dict, fieldName) : null;
1431
+ const dictionary = normalizeDictionary(props.dict);
1432
+ const fieldName = dictionary ? attrs.fieldName ?? useFieldContext().name : void 0;
1433
+ const dictResult = dictionary ? useDictionary(dictionary, fieldName) : null;
853
1434
  const childrenProps = computed(() => {
854
1435
  return {
855
1436
  ...attrs,
856
- dict: props.dict,
857
- options: props.dict ? dictResult == null ? void 0 : dictResult.list.value : attrs.options,
858
- loading: props.dict ? dictResult == null ? void 0 : dictResult.loading.value : attrs.loading
1437
+ dict: dictionary,
1438
+ options: dictionary ? dictResult == null ? void 0 : dictResult.list.value : attrs.options,
1439
+ loading: dictionary ? dictResult == null ? void 0 : dictResult.loading.value : attrs.loading
859
1440
  };
860
1441
  });
861
1442
  return () => h(WrappedComponent, childrenProps.value, slots);
@@ -863,22 +1444,24 @@ function WithRemoteOptions(WrappedComponent) {
863
1444
  });
864
1445
  }
865
1446
  export {
866
- FormGroup,
867
- FormItem,
1447
+ _sfc_main$1 as Button,
1448
+ FormGroup$1 as FormGroup,
1449
+ FormItem$1 as FormItem,
868
1450
  WithRemoteOptions,
869
1451
  createFieldContext,
870
1452
  createFormConfigContext,
871
1453
  createFormContext,
872
- SchemxFormExport as default,
1454
+ SchemxFormExport$1 as default,
1455
+ getCoreForm,
873
1456
  rendererRegistry,
874
- SchemxFormExport as schemxForm,
1457
+ SchemxFormExport$1 as schemxForm,
875
1458
  useDictionary,
876
- useEffect,
877
1459
  useField,
878
1460
  useFieldContext,
879
1461
  useForm,
880
1462
  useFormConfigContext,
881
1463
  useFormContext,
1464
+ useFormSelector,
882
1465
  useStableRef,
883
1466
  useViewSchemas,
884
1467
  useWatch,