cabloy 5.1.108 → 5.1.109

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 (39) hide show
  1. package/.cabloy-version +1 -1
  2. package/CHANGELOG.md +17 -0
  3. package/cabloy-docs/frontend/table-resource-crud-cookbook.md +33 -1
  4. package/package.json +1 -1
  5. package/vona/pnpm-lock.yaml +16 -16
  6. package/vona/src/suite/a-training/modules/training-student/src/config/locale/en-us.ts +2 -0
  7. package/vona/src/suite/a-training/modules/training-student/src/config/locale/zh-cn.ts +2 -0
  8. package/vona/src/suite/a-training/modules/training-student/src/dto/studentCreate.tsx +50 -1
  9. package/vona/src/suite/a-training/modules/training-student/src/dto/studentSelectResItem.tsx +21 -1
  10. package/vona/src/suite/a-training/modules/training-student/src/dto/studentUpdate.tsx +47 -1
  11. package/vona/src/suite/a-training/modules/training-student/src/dto/studentView.tsx +47 -1
  12. package/vona/src/suite/a-training/modules/training-student/test/student.test.ts +60 -2
  13. package/zova/packages-zova/zova/package.json +2 -2
  14. package/zova/pnpm-lock.yaml +27 -2
  15. package/zova/src/suite/cabloy-basic/modules/basic-form/src/.metadata/component/blockFormLayout.ts +31 -0
  16. package/zova/src/suite/cabloy-basic/modules/basic-form/src/.metadata/index.ts +13 -0
  17. package/zova/src/suite/cabloy-basic/modules/basic-form/src/component/blockFormLayout/controller.tsx +165 -0
  18. package/zova/src/suite/cabloy-basic/modules/basic-page/src/.metadata/component/blockFilterActions.ts +31 -0
  19. package/zova/src/suite/cabloy-basic/modules/basic-page/src/.metadata/index.ts +13 -0
  20. package/zova/src/suite/cabloy-basic/modules/basic-page/src/component/blockFilter/controller.tsx +61 -31
  21. package/zova/src/suite/cabloy-basic/modules/basic-page/src/component/blockFilterActions/controller.tsx +51 -0
  22. package/zova/src/suite/cabloy-basic/modules/basic-page/src/types/page.ts +10 -0
  23. package/zova/src/suite/cabloy-basic/modules/basic-pageentry/src/component/blockForm/controller.tsx +9 -2
  24. package/zova/src/suite-vendor/a-zova/modules/a-form/package.json +5 -3
  25. package/zova/src/suite-vendor/a-zova/modules/a-form/src/component/form/controller.tsx +44 -3
  26. package/zova/src/suite-vendor/a-zova/modules/a-form/src/component/form/render.tsx +22 -4
  27. package/zova/src/suite-vendor/a-zova/modules/a-form/src/component/formField/controller.tsx +3 -2
  28. package/zova/src/suite-vendor/a-zova/modules/a-form/src/lib/formLayout.ts +203 -0
  29. package/zova/src/suite-vendor/a-zova/modules/a-form/src/lib/index.ts +1 -0
  30. package/zova/src/suite-vendor/a-zova/modules/a-form/src/types/formField.ts +7 -4
  31. package/zova/src/suite-vendor/a-zova/modules/a-form/src/types/formLayout.ts +59 -0
  32. package/zova/src/suite-vendor/a-zova/modules/a-form/src/types/index.ts +1 -0
  33. package/zova/src/suite-vendor/a-zova/modules/a-openapi/package.json +1 -1
  34. package/zova/src/suite-vendor/a-zova/modules/a-openapi/src/types/action.ts +1 -1
  35. package/zova/src/suite-vendor/a-zova/modules/a-openapi/src/types/resource/formLayout.ts +53 -0
  36. package/zova/src/suite-vendor/a-zova/modules/a-openapi/src/types/resource/index.ts +1 -0
  37. package/zova/src/suite-vendor/a-zova/modules/a-ssr/package.json +1 -1
  38. package/zova/src/suite-vendor/a-zova/modules/a-ssr/src/lib/ssr.ts +1 -1
  39. package/zova/src/suite-vendor/a-zova/package.json +4 -4
@@ -0,0 +1,165 @@
1
+ import type { IComponentOptions } from 'zova';
2
+ import type {
3
+ IJsxRenderContextForm,
4
+ IResolvedFormLayout,
5
+ IResolvedFormLayoutField,
6
+ IResolvedFormLayoutGroup,
7
+ IResolvedFormLayoutNode,
8
+ IResolvedFormLayoutSection,
9
+ IResolvedFormLayoutTab,
10
+ IResolvedFormLayoutTabs,
11
+ } from 'zova-module-a-form';
12
+ import type { IFormLayout, IResourceBlockOptionsBase } from 'zova-module-a-openapi';
13
+
14
+ import { classes } from 'typestyle';
15
+ import { useId } from 'vue';
16
+ import { BeanControllerBase, Use } from 'zova';
17
+ import { Controller } from 'zova-module-a-bean';
18
+ import { resolveFormLayout } from 'zova-module-a-form';
19
+
20
+ declare module 'zova-module-a-openapi' {
21
+ export interface IResourceBlockRecord {
22
+ 'basic-form:blockFormLayout'?: ControllerBlockFormLayoutProps;
23
+ }
24
+ }
25
+
26
+ export interface ControllerBlockFormLayoutProps extends IResourceBlockOptionsBase {
27
+ formLayout: IFormLayout;
28
+ }
29
+
30
+ @Controller()
31
+ export class ControllerBlockFormLayout extends BeanControllerBase {
32
+ static $propsDefault = {};
33
+ static $componentOptions: IComponentOptions = { inheritAttrs: false, deepExtendDefault: true };
34
+
35
+ private formLayoutPlan: IResolvedFormLayout | undefined;
36
+ private formLayoutActiveTabs: Record<string, string | undefined> = {};
37
+ private formLayoutDomIdPrefix: string;
38
+
39
+ @Use({ injectionScope: 'host' })
40
+ $$renderContext: IJsxRenderContextForm;
41
+
42
+ protected async __init__() {
43
+ this.formLayoutDomIdPrefix = `basic-form-layout-${useId()}`;
44
+ this.formLayoutPlan = this.$computed(() => {
45
+ const { $$form } = this.$$renderContext;
46
+ const formLayout = this.$props.formLayout;
47
+ return resolveFormLayout(formLayout, $$form.properties);
48
+ });
49
+ }
50
+
51
+ protected render() {
52
+ const plan = this.formLayoutPlan!;
53
+ return <>{plan.children.map(node => this._renderNode(node))}</>;
54
+ }
55
+
56
+ private _renderNode(node: IResolvedFormLayoutNode) {
57
+ switch (node.type) {
58
+ case 'field':
59
+ return this._renderField(node);
60
+ case 'group':
61
+ return this._renderGroup(node);
62
+ case 'section':
63
+ return this._renderSection(node);
64
+ case 'tabs':
65
+ return this._renderTabs(node);
66
+ }
67
+ }
68
+
69
+ private _renderField(node: IResolvedFormLayoutField) {
70
+ const { $$form } = this.$$renderContext;
71
+ const span = this._gridClasses('col-span', node.span);
72
+ return <div class={span}>{$$form.renderField(node.name)}</div>;
73
+ }
74
+
75
+ private _renderGroup(node: IResolvedFormLayoutGroup) {
76
+ return (
77
+ <fieldset class="fieldset mb-6 rounded-box border border-base-300 p-4">
78
+ {!!node.title && <legend class="fieldset-legend">{node.title}</legend>}
79
+ {!!node.description && <p class="mb-4 text-sm text-base-content/70">{node.description}</p>}
80
+ {node.children.map(child => this._renderNode(child))}
81
+ </fieldset>
82
+ );
83
+ }
84
+
85
+ private _renderSection(node: IResolvedFormLayoutSection) {
86
+ const className = classes('mb-6 grid gap-4', this._gridClasses('grid-cols', node.columns));
87
+ return (
88
+ <section>
89
+ {!!node.title && <h3 class="mb-1 text-lg font-semibold">{node.title}</h3>}
90
+ {!!node.description && <p class="mb-4 text-sm text-base-content/70">{node.description}</p>}
91
+ <div class={className}>{node.children.map(child => this._renderField(child))}</div>
92
+ </section>
93
+ );
94
+ }
95
+
96
+ private getActiveTabId(node: IResolvedFormLayoutTabs) {
97
+ const activeTabId = this.formLayoutActiveTabs[node.id];
98
+ if (node.children.some(tab => tab.id === activeTabId)) return activeTabId;
99
+ const fallbackTabId = node.children[0]?.id;
100
+ this.formLayoutActiveTabs[node.id] = fallbackTabId;
101
+ return fallbackTabId;
102
+ }
103
+
104
+ private setActiveTab(tabsId: string, tabId: string) {
105
+ this.formLayoutActiveTabs[tabsId] = tabId;
106
+ }
107
+
108
+ private _renderTabs(node: IResolvedFormLayoutTabs) {
109
+ const { $$form } = this.$$renderContext;
110
+ const activeTabId = this.getActiveTabId(node);
111
+ const domIdBase = `${this.formLayoutDomIdPrefix}-${node.id}`;
112
+ return (
113
+ <div class="mb-6">
114
+ <div role="tablist" class="tabs tabs-lifted">
115
+ {node.children.map(tab => {
116
+ const active = tab.id === activeTabId;
117
+ const errorFieldCount = $$form.getErrorFieldCount(tab);
118
+ const invalid = errorFieldCount > 0;
119
+ return (
120
+ <button
121
+ id={`${domIdBase}-${tab.id}-tab`}
122
+ role="tab"
123
+ type="button"
124
+ class={classes('tab', active && 'tab-active')}
125
+ aria-selected={active}
126
+ aria-controls={`${domIdBase}-${tab.id}-panel`}
127
+ onClick={() => this.setActiveTab(node.id, tab.id)}
128
+ >
129
+ {tab.title}
130
+ {invalid && <span class="badge badge-error badge-sm ml-1">{errorFieldCount}</span>}
131
+ </button>
132
+ );
133
+ })}
134
+ </div>
135
+ {node.children.map(tab => this._renderTabPanel(domIdBase, tab, tab.id === activeTabId))}
136
+ </div>
137
+ );
138
+ }
139
+
140
+ private _renderTabPanel(domIdBase: string, tab: IResolvedFormLayoutTab, active: boolean) {
141
+ return (
142
+ <div
143
+ id={`${domIdBase}-${tab.id}-panel`}
144
+ role="tabpanel"
145
+ aria-labelledby={`${domIdBase}-${tab.id}-tab`}
146
+ hidden={!active}
147
+ class="rounded-box border border-base-300 bg-base-100 p-4"
148
+ >
149
+ {tab.children.map(child => this._renderNode(child))}
150
+ </div>
151
+ );
152
+ }
153
+
154
+ private _gridClasses(
155
+ prefix: 'grid-cols' | 'col-span',
156
+ columns?: { default?: number; md?: number; lg?: number },
157
+ ) {
158
+ const classes: string[] = [];
159
+ const valueDefault = columns?.default ?? (prefix === 'grid-cols' ? 1 : undefined);
160
+ if (valueDefault) classes.push(`${prefix}-${valueDefault}`);
161
+ if (columns?.md) classes.push(`md:${prefix}-${columns.md}`);
162
+ if (columns?.lg) classes.push(`lg:${prefix}-${columns.lg}`);
163
+ return classes.join(' ');
164
+ }
165
+ }
@@ -0,0 +1,31 @@
1
+ import type { TypeControllerInnerProps } from 'zova';
2
+
3
+ import { defineComponent } from 'vue';
4
+ import { prepareComponentOptions, useController } from 'zova';
5
+
6
+ import type { ControllerBlockFilterActionsProps } from '../../component/blockFilterActions/controller.jsx';
7
+
8
+ import { ControllerBlockFilterActions } from '../../component/blockFilterActions/controller.jsx';
9
+ export type ZBlockFilterActionsProps = {
10
+ controllerRef?: (ref: ControllerBlockFilterActions) => void;
11
+ } & ControllerBlockFilterActionsProps;
12
+
13
+ type ControllerInnerProps = TypeControllerInnerProps<
14
+ ControllerBlockFilterActionsProps,
15
+ keyof typeof ControllerBlockFilterActions.$propsDefault
16
+ >;
17
+ declare module 'zova-module-basic-page' {
18
+ export interface ControllerBlockFilterActions {
19
+ $props: ControllerInnerProps;
20
+ }
21
+ }
22
+
23
+ export const ZBlockFilterActions = defineComponent((_props: ZBlockFilterActionsProps) => {
24
+ useController(ControllerBlockFilterActions, undefined, undefined);
25
+ return () => {};
26
+ }, prepareComponentOptions(ControllerBlockFilterActions.$componentOptions));
27
+ declare module 'zova-module-a-bean' {
28
+ export interface IVonaComponentRecord {
29
+ 'basic-page:blockFilterActions': ControllerBlockFilterActionsProps;
30
+ }
31
+ }
@@ -1,6 +1,7 @@
1
1
  // eslint-disable
2
2
  /** controller: begin */
3
3
  export * from '../component/blockFilter/controller.jsx';
4
+ export * from '../component/blockFilterActions/controller.jsx';
4
5
  export * from '../component/blockPage/controller.jsx';
5
6
  export * from '../component/blockPager/controller.jsx';
6
7
  export * from '../component/blockTable/controller.jsx';
@@ -18,6 +19,11 @@ declare module 'zova-module-basic-page' {
18
19
  get scope(): ScopeModuleBasicPage;
19
20
  }
20
21
 
22
+ export interface ControllerBlockFilterActions {
23
+ /** @internal */
24
+ get scope(): ScopeModuleBasicPage;
25
+ }
26
+
21
27
  export interface ControllerBlockPage {
22
28
  /** @internal */
23
29
  get scope(): ScopeModuleBasicPage;
@@ -41,6 +47,7 @@ declare module 'zova-module-basic-page' {
41
47
  /** controller: end */
42
48
  /** controller: begin */
43
49
  import { ControllerBlockFilter } from '../component/blockFilter/controller.jsx';
50
+ import { ControllerBlockFilterActions } from '../component/blockFilterActions/controller.jsx';
44
51
  import { ControllerBlockPage } from '../component/blockPage/controller.jsx';
45
52
  import { ControllerBlockPager } from '../component/blockPager/controller.jsx';
46
53
  import { ControllerBlockTable } from '../component/blockTable/controller.jsx';
@@ -49,6 +56,7 @@ import 'zova';
49
56
  declare module 'zova' {
50
57
  export interface IBeanRecordLocal {
51
58
  'basic-page.controller.blockFilter': ControllerBlockFilter;
59
+ 'basic-page.controller.blockFilterActions': ControllerBlockFilterActions;
52
60
  'basic-page.controller.blockPage': ControllerBlockPage;
53
61
  'basic-page.controller.blockPager': ControllerBlockPager;
54
62
  'basic-page.controller.blockTable': ControllerBlockTable;
@@ -60,6 +68,8 @@ declare module 'zova' {
60
68
  /** components: begin */
61
69
  export * from './component/blockFilter.js';
62
70
  import { ZBlockFilter } from './component/blockFilter.js';
71
+ export * from './component/blockFilterActions.js';
72
+ import { ZBlockFilterActions } from './component/blockFilterActions.js';
63
73
  export * from './component/blockPage.js';
64
74
  import { ZBlockPage } from './component/blockPage.js';
65
75
  export * from './component/blockPager.js';
@@ -70,6 +80,7 @@ export * from './component/blockToolbarBulk.js';
70
80
  import { ZBlockToolbarBulk } from './component/blockToolbarBulk.js';
71
81
  export const components = {
72
82
  'blockFilter': ZBlockFilter,
83
+ 'blockFilterActions': ZBlockFilterActions,
73
84
  'blockPage': ZBlockPage,
74
85
  'blockPager': ZBlockPager,
75
86
  'blockTable': ZBlockTable,
@@ -79,6 +90,7 @@ import 'zova';
79
90
  declare module 'zova' {
80
91
  export interface IComponentRecord {
81
92
  'basic-page:blockFilter': ControllerBlockFilter;
93
+ 'basic-page:blockFilterActions': ControllerBlockFilterActions;
82
94
  'basic-page:blockPage': ControllerBlockPage;
83
95
  'basic-page:blockPager': ControllerBlockPager;
84
96
  'basic-page:blockTable': ControllerBlockTable;
@@ -86,6 +98,7 @@ export interface IComponentRecord {
86
98
  }
87
99
  export interface IZovaComponentRecord {
88
100
  'basic-page:blockFilter': typeof ZBlockFilter;
101
+ 'basic-page:blockFilterActions': typeof ZBlockFilterActions;
89
102
  'basic-page:blockPage': typeof ZBlockPage;
90
103
  'basic-page:blockPager': typeof ZBlockPager;
91
104
  'basic-page:blockTable': typeof ZBlockTable;
@@ -4,12 +4,16 @@ import type {
4
4
  IJsxRenderContextPage,
5
5
  IResourceBlockOptionsBase,
6
6
  IResourceFormFieldLayoutOptions,
7
+ IResourceRenderBlockOptionsBlock,
7
8
  } from 'zova-module-a-openapi';
8
9
 
9
10
  import { isNilOrEmptyString } from '@cabloy/utils';
10
- import { BeanControllerBase, Use } from 'zova';
11
+ import { VNode } from 'vue';
12
+ import { BeanControllerBase, objectAssignReactive, Use } from 'zova';
11
13
  import { Controller } from 'zova-module-a-bean';
12
- import { TypeFormOnSubmitData, ZForm } from 'zova-module-a-form';
14
+ import { ControllerForm, TypeFormOnSubmitData, ZForm } from 'zova-module-a-form';
15
+
16
+ import { IPageFilterScope } from '../../types/page.js';
13
17
 
14
18
  declare module 'zova-module-a-openapi' {
15
19
  export interface IResourceBlockRecord {
@@ -17,22 +21,29 @@ declare module 'zova-module-a-openapi' {
17
21
  }
18
22
  }
19
23
 
20
- export interface ControllerBlockFilterProps extends IResourceBlockOptionsBase {}
24
+ export interface ControllerBlockFilterProps extends IResourceBlockOptionsBase {
25
+ blocks?: IResourceRenderBlockOptionsBlock[];
26
+ formFieldLayout?: IResourceFormFieldLayoutOptions;
27
+ }
21
28
 
22
29
  @Controller()
23
30
  export class ControllerBlockFilter extends BeanControllerBase {
24
- static $propsDefault = {};
31
+ static $propsDefault = {
32
+ formFieldLayout: { inline: true },
33
+ };
34
+
25
35
  static $componentOptions: IComponentOptions = { inheritAttrs: false, deepExtendDefault: true };
26
36
 
27
37
  formMeta: IFormMeta;
28
- formFieldLayout: IResourceFormFieldLayoutOptions;
38
+ formRef: ControllerForm | undefined;
39
+ formScope: IPageFilterScope;
29
40
 
30
41
  @Use({ injectionScope: 'host' })
31
42
  $$renderContext: IJsxRenderContextPage;
32
43
 
33
44
  protected async __init__() {
34
45
  this.formMeta = { formMode: 'edit' };
35
- this.formFieldLayout = { inline: true };
46
+ this.formScope = this._prepareFormScope();
36
47
  }
37
48
 
38
49
  get schemaFilter() {
@@ -48,6 +59,16 @@ export class ControllerBlockFilter extends BeanControllerBase {
48
59
  this._onFilter(data);
49
60
  }
50
61
 
62
+ public submitFilter() {
63
+ return this.formRef?.submit() ?? Promise.resolve(false);
64
+ }
65
+
66
+ public resetFilter() {
67
+ if (!this.formRef) return;
68
+ const data = this.formRef.reset();
69
+ this.resetData(data);
70
+ }
71
+
51
72
  _onFilter(dataOld: any) {
52
73
  const { $$page } = this.$$renderContext;
53
74
  const dataNew = {};
@@ -62,40 +83,49 @@ export class ControllerBlockFilter extends BeanControllerBase {
62
83
 
63
84
  protected render() {
64
85
  const { $$page } = this.$$renderContext;
86
+ const blocks = this.$props.blocks;
87
+ const hasBlocks = !!blocks && blocks.length > 0;
88
+ const formFieldLayout = this.$props.formFieldLayout;
65
89
  return (
66
90
  <ZForm
67
91
  class={this.$props.class}
68
- inline={true}
92
+ controllerRef={ref => {
93
+ this.formRef = ref;
94
+ }}
69
95
  data={$$page.queryFilterData}
70
96
  schema={this.schemaFilter}
71
97
  schemaScene="filter"
72
98
  formMeta={this.formMeta}
73
- formFieldLayout={this.formFieldLayout}
99
+ formFieldLayout={formFieldLayout}
100
+ blocks={blocks}
101
+ formScope={this.formScope}
74
102
  onSubmitData={data => this.submitData(data as never)}
75
- slotFooter={$$form => {
76
- return (
77
- <>
78
- <button
79
- class="btn btn-primary"
80
- onClick={() => {
81
- $$form.submit();
82
- }}
83
- >
84
- {this.scope.locale.Search()}
85
- </button>
86
- <button
87
- class="btn btn-warning"
88
- onClick={() => {
89
- const data = $$form.reset();
90
- this.resetData(data);
91
- }}
92
- >
93
- {this.scope.locale.Reset()}
94
- </button>
95
- </>
96
- );
97
- }}
103
+ slotFooter={hasBlocks ? undefined : $$form => this._renderActions($$form)}
98
104
  ></ZForm>
99
105
  );
100
106
  }
107
+
108
+ private _prepareFormScope(): IPageFilterScope {
109
+ // eslint-disable-next-line
110
+ const self = this;
111
+ const $$filter = this.$customRef(() => {
112
+ return {
113
+ get() {
114
+ return self;
115
+ },
116
+ set(_value) {},
117
+ };
118
+ }) as any;
119
+ return objectAssignReactive({}, this.$$renderContext.$$page.jsxCelScope, { $$filter });
120
+ }
121
+
122
+ private _renderActions($$form: ControllerForm): VNode {
123
+ const jsxRenderContext = $$form.getFormJsxRenderContext(this.formScope);
124
+ return $$form.zovaJsx.render(
125
+ 'basic-page:blockFilterActions',
126
+ {},
127
+ this.formScope,
128
+ jsxRenderContext,
129
+ ) as VNode;
130
+ }
101
131
  }
@@ -0,0 +1,51 @@
1
+ import type { IComponentOptions } from 'zova';
2
+ import type { IJsxRenderContextForm } from 'zova-module-a-form';
3
+ import type { IResourceBlockOptionsBase } from 'zova-module-a-openapi';
4
+
5
+ import { BeanControllerBase, Use } from 'zova';
6
+ import { Controller } from 'zova-module-a-bean';
7
+
8
+ declare module 'zova-module-a-openapi' {
9
+ export interface IResourceBlockRecord {
10
+ 'basic-page:blockFilterActions'?: ControllerBlockFilterActionsProps;
11
+ }
12
+ }
13
+
14
+ export interface ControllerBlockFilterActionsProps extends IResourceBlockOptionsBase {}
15
+
16
+ @Controller()
17
+ export class ControllerBlockFilterActions extends BeanControllerBase {
18
+ static $propsDefault = {};
19
+ static $componentOptions: IComponentOptions = { inheritAttrs: false, deepExtendDefault: true };
20
+
21
+ @Use({ injectionScope: 'host' })
22
+ $$renderContext: IJsxRenderContextForm;
23
+
24
+ protected async __init__() {}
25
+
26
+ protected render() {
27
+ const { $$filter } = this.$$renderContext.$celScope;
28
+ return (
29
+ <div class={this.$props.class}>
30
+ <button
31
+ class="btn btn-primary"
32
+ type="button"
33
+ onClick={() => {
34
+ $$filter?.submitFilter();
35
+ }}
36
+ >
37
+ {this.scope.locale.Search()}
38
+ </button>
39
+ <button
40
+ class="btn btn-warning"
41
+ type="button"
42
+ onClick={() => {
43
+ $$filter?.resetFilter();
44
+ }}
45
+ >
46
+ {this.scope.locale.Reset()}
47
+ </button>
48
+ </div>
49
+ );
50
+ }
51
+ }
@@ -1,15 +1,25 @@
1
1
  import type { IPageScope } from 'zova-module-a-openapi';
2
2
 
3
+ import 'zova-module-a-form';
3
4
  import 'zova-module-a-table';
4
5
  import 'zova-module-a-openapi';
6
+ import { ControllerBlockFilter } from '../component/blockFilter/controller.jsx';
5
7
  import { ControllerBlockPage } from '../component/blockPage/controller.jsx';
6
8
 
9
+ export interface IPageFilterScope extends IPageScope {
10
+ $$filter?: ControllerBlockFilter;
11
+ }
12
+
7
13
  declare module 'zova-module-a-openapi' {
8
14
  export interface IJsxRenderContextPage<TData extends {} = {}> {
9
15
  $$page: ControllerBlockPage<TData>;
10
16
  }
11
17
  }
12
18
 
19
+ declare module 'zova-module-a-form' {
20
+ export interface IFormScope extends IPageScope, IPageFilterScope {}
21
+ }
22
+
13
23
  declare module 'zova-module-a-table' {
14
24
  export interface ITableScope extends IPageScope {}
15
25
  }
@@ -1,5 +1,9 @@
1
1
  import type { IComponentOptions } from 'zova';
2
- import type { IJsxRenderContextPageEntry, IResourceBlockOptionsBase } from 'zova-module-a-openapi';
2
+ import type {
3
+ IJsxRenderContextPageEntry,
4
+ IResourceBlockOptionsBase,
5
+ IResourceRenderBlockOptionsBlock,
6
+ } from 'zova-module-a-openapi';
3
7
 
4
8
  import { BeanControllerBase, Use } from 'zova';
5
9
  import { Controller } from 'zova-module-a-bean';
@@ -11,7 +15,9 @@ declare module 'zova-module-a-openapi' {
11
15
  }
12
16
  }
13
17
 
14
- export interface ControllerBlockFormProps extends IResourceBlockOptionsBase {}
18
+ export interface ControllerBlockFormProps extends IResourceBlockOptionsBase {
19
+ blocks?: IResourceRenderBlockOptionsBlock[];
20
+ }
15
21
 
16
22
  @Controller()
17
23
  export class ControllerBlockForm extends BeanControllerBase {
@@ -40,6 +46,7 @@ export class ControllerBlockForm extends BeanControllerBase {
40
46
  schemaScene={$$pageEntry.schemaScene}
41
47
  formMeta={$$pageEntry.formMeta}
42
48
  formProvider={$$pageEntry.formProvider}
49
+ blocks={this.$props.blocks}
43
50
  formScope={$$pageEntry.jsxCelScope}
44
51
  onSubmitData={data => $$pageEntry.submitData(data)}
45
52
  onShowError={async ({ error }) => {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zova-module-a-form",
3
- "version": "5.1.43",
3
+ "version": "5.1.44",
4
4
  "gitHead": "09d901d17140a80ee0764211b441cda72fd94663",
5
5
  "description": "",
6
6
  "keywords": [
@@ -34,7 +34,8 @@
34
34
  "postpack": "clean-package restore"
35
35
  },
36
36
  "dependencies": {
37
- "@tanstack/vue-form": "^1.32.0"
37
+ "@tanstack/vue-form": "^1.33.2",
38
+ "@tanstack/vue-store": "^0.11.0"
38
39
  },
39
40
  "devDependencies": {
40
41
  "clean-package": "^2.2.0",
@@ -54,7 +55,8 @@
54
55
  "title": "a-form",
55
56
  "zovaModule": {
56
57
  "globalDependencies": {
57
- "@tanstack/vue-form": true
58
+ "@tanstack/vue-form": true,
59
+ "@tanstack/vue-store": true
58
60
  },
59
61
  "bundle": {
60
62
  "vendors": [
@@ -7,10 +7,10 @@ import {
7
7
  getBy,
8
8
  isGlobalFormValidationError,
9
9
  revalidateLogic,
10
- useStore,
11
10
  ValidationCause,
12
11
  ValidationError,
13
12
  } from '@tanstack/vue-form';
13
+ import { useSelector } from '@tanstack/vue-store';
14
14
  import { SchemaObject } from 'openapi3-ts/oas31';
15
15
  import { VNode } from 'vue';
16
16
  import { z } from 'zod';
@@ -22,6 +22,7 @@ import {
22
22
  IFormMeta,
23
23
  IFormProvider,
24
24
  IResourceFormFieldLayoutOptions,
25
+ IResourceRenderBlockOptionsBlock,
25
26
  ISchemaObjectExtensionField,
26
27
  renderFormFieldTopPropsSystem,
27
28
  ScopeModuleAOpenapi,
@@ -30,6 +31,8 @@ import {
30
31
  TypeFormSchemaScene,
31
32
  } from 'zova-module-a-openapi';
32
33
 
34
+ import type { IResolvedFormLayoutNode, IResolvedFormLayoutTab } from '../../types/formLayout.js';
35
+
33
36
  import { BeanControllerFormBase } from '../../lib/beanControllerFormBase.js';
34
37
  import {
35
38
  IFormScope,
@@ -43,13 +46,13 @@ import {
43
46
  IFormFieldOptions,
44
47
  IFormFieldRenderContextPropsBucket,
45
48
  IFormFieldScope,
49
+ IJsxRenderContextForm,
46
50
  IJsxRenderContextFormField,
47
51
  } from '../../types/formField.js';
48
52
  import { ControllerFormField } from '../formField/controller.jsx';
49
53
 
50
54
  export interface ControllerFormProps<TFormData extends {} = {}, TSubmitMeta = never> {
51
55
  formTag?: string;
52
- inline?: boolean;
53
56
  data?: TFormData;
54
57
  schema?: SchemaObject;
55
58
  schemaScene?: TypeFormSchemaScene;
@@ -60,6 +63,7 @@ export interface ControllerFormProps<TFormData extends {} = {}, TSubmitMeta = ne
60
63
  formProvider?: IFormProvider;
61
64
  formScope?: IFormScope;
62
65
  formFieldLayout?: IResourceFormFieldLayoutOptions;
66
+ blocks?: IResourceRenderBlockOptionsBlock[];
63
67
  onFormSubmit?: (e: SubmitEvent, form: ControllerForm<TFormData, TSubmitMeta>) => any;
64
68
  onSubmitInvalid?: TypeFormOnSubmitInvalid<TFormData, TSubmitMeta>;
65
69
  onSubmitData?: TypeFormOnSubmit<TFormData, TSubmitMeta>;
@@ -94,7 +98,7 @@ export class ControllerForm<
94
98
  protected async __init__() {
95
99
  this.bean._setBean('$$form', this);
96
100
  this.form = this._createForm();
97
- this.formState = useStore(this.form.store, state => state) as any;
101
+ this.formState = useSelector(this.form.store, state => state) as any;
98
102
  this.formProvider = this.$computed(() => {
99
103
  const formProvider = this.$$scopeOpenapi.config.formProvider;
100
104
  return this.$props.formProvider
@@ -143,6 +147,14 @@ export class ControllerForm<
143
147
  return this.$props.formMeta;
144
148
  }
145
149
 
150
+ public hasErrors(node: IResolvedFormLayoutNode | IResolvedFormLayoutTab) {
151
+ return this.getErrorFieldCount(node) > 0;
152
+ }
153
+
154
+ public getErrorFieldCount(node: IResolvedFormLayoutNode | IResolvedFormLayoutTab) {
155
+ return this._getFormLayoutErrorFieldCount(node);
156
+ }
157
+
146
158
  public getFieldValue<K extends DeepKeys<TFormData>>(name: K) {
147
159
  return getBy(this.formState.values, name) ?? null;
148
160
  }
@@ -195,6 +207,12 @@ export class ControllerForm<
195
207
  });
196
208
  }
197
209
 
210
+ public getFormScope(scopeExtra?: {}): IFormScope {
211
+ return objectAssignReactive({}, this.$props.formScope, {
212
+ ...scopeExtra,
213
+ });
214
+ }
215
+
198
216
  public getFieldJsxRenderContext(
199
217
  $$formField: ControllerFormField<TFormData> | undefined,
200
218
  celScope: IFormFieldScope<TFormData>,
@@ -211,6 +229,20 @@ export class ControllerForm<
211
229
  };
212
230
  }
213
231
 
232
+ public getFormJsxRenderContext(
233
+ celScope: IFormScope,
234
+ ): IJsxRenderContextForm<TFormData, TSubmitMeta> {
235
+ return {
236
+ app: this.app,
237
+ ctx: this.ctx,
238
+ $scene: 'form',
239
+ $host: this,
240
+ $celScope: celScope,
241
+ $jsx: this.zovaJsx,
242
+ $$form: this,
243
+ };
244
+ }
245
+
214
246
  public getFieldComponentPropsTop<K extends DeepKeys<TFormData>>(
215
247
  name: K,
216
248
  celScope: IFormFieldScope<TFormData>,
@@ -375,6 +407,15 @@ export class ControllerForm<
375
407
  return typeof renderProvider === 'string' && renderProvider.includes(':formField');
376
408
  }
377
409
 
410
+ private _getFormLayoutErrorFieldCount(node: IResolvedFormLayoutNode | IResolvedFormLayoutTab) {
411
+ if (node.type === 'field') {
412
+ return this.formState.fieldMeta[node.name]?.errors?.length ? 1 : 0;
413
+ }
414
+ return node.children.reduce((count, child) => {
415
+ return count + this._getFormLayoutErrorFieldCount(child);
416
+ }, 0);
417
+ }
418
+
378
419
  private _handleError422(error: Error, cause: ValidationCause = 'submit') {
379
420
  const formApi = this.form;
380
421