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

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