cabloy 5.1.157 → 5.1.159

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 (90) hide show
  1. package/.cabloy-version +1 -1
  2. package/.claude/skills/cabloy-contract-loop/SKILL.md +24 -7
  3. package/.claude/skills/cabloy-contract-loop/evals/evals.json +12 -0
  4. package/.claude/skills/cabloy-contract-loop/references/contract-loop-map.md +28 -0
  5. package/.claude/skills/cabloy-contract-loop/references/verification-checklist.md +19 -2
  6. package/.claude/skills/cabloy-resource-field-update/SKILL.md +18 -0
  7. package/.claude/skills/cabloy-zova-source-reading/SKILL.md +14 -0
  8. package/.claude/skills/cabloy-zova-source-reading/references/core-reading-paths.md +10 -0
  9. package/.github/workflows/docs-pages.yml +18 -2
  10. package/CHANGELOG.md +26 -0
  11. package/CONTRIBUTING.md +84 -0
  12. package/LICENSES.md +5 -0
  13. package/package.json +2 -1
  14. package/repo-docs/.vitepress/config.mjs +34 -4
  15. package/repo-docs/.vitepress/theme/components/GitHubRepositoriesNav.vue +255 -0
  16. package/repo-docs/.vitepress/theme/custom.css +21 -0
  17. package/repo-docs/.vitepress/theme/index.js +7 -1
  18. package/repo-docs/backend/resource-field-update.md +48 -1
  19. package/repo-docs/blogs/index.md +2 -0
  20. package/repo-docs/frontend/form-layout-guide.md +6 -6
  21. package/repo-docs/frontend/page-guide.md +12 -0
  22. package/repo-docs/frontend/page-meta-guide.md +27 -0
  23. package/repo-docs/frontend/ssr-architecture-overview.md +4 -0
  24. package/repo-docs/frontend/ssr-build-deploy-guide.md +1 -1
  25. package/repo-docs/frontend/zova-form-source-reading-map.md +2 -2
  26. package/repo-docs/frontend/zova-form-under-the-hood.md +1 -1
  27. package/repo-docs/frontend/zova-reactivity-under-the-hood.md +65 -9
  28. package/repo-docs/fullstack/contract-loop-playbook.md +2 -0
  29. package/repo-docs/fullstack/ssr-site-and-flavor-setup.md +198 -0
  30. package/repo-docs/fullstack/vona-zova-integration.md +2 -0
  31. package/repo-e2e/docs/playwright.config.ts +37 -0
  32. package/repo-e2e/docs/specs/blogs-index.spec.ts +180 -0
  33. package/repo-e2e/docs/specs/github-repositories-nav.spec.ts +69 -0
  34. package/repo-e2e/docs/test-results/.last-run.json +4 -0
  35. package/repo-e2e/specs/cabloy-basic.spec.ts +136 -2
  36. package/vona/packages-cli/cli/package.json +1 -1
  37. package/vona/packages-cli/cli-set-api/cli/templates/tools/crudBasic/boilerplate/src/entity/{{resourceName}}.tsx_ +1 -1
  38. package/vona/packages-cli/cli-set-api/cli/templates/tools/crudStart/boilerplate/src/entity/{{resourceName}}.tsx_ +1 -1
  39. package/vona/packages-cli/cli-set-api/package.json +1 -1
  40. package/vona/packages-vona/vona/package.json +1 -1
  41. package/vona/pnpm-lock.yaml +21 -80
  42. package/vona/src/suite/a-commerce/modules/commerce-catalog/src/config/locale/en-us.ts +3 -0
  43. package/vona/src/suite/a-commerce/modules/commerce-catalog/src/config/locale/zh-cn.ts +3 -0
  44. package/vona/src/suite/a-commerce/modules/commerce-catalog/src/dto/productCreate.tsx +2 -0
  45. package/vona/src/suite/a-commerce/modules/commerce-catalog/src/dto/productUpdate.tsx +2 -0
  46. package/vona/src/suite/a-commerce/modules/commerce-catalog/src/dto/productView.tsx +2 -0
  47. package/vona/src/suite/a-training/modules/training-record/src/dto/recordCreate.tsx +1 -0
  48. package/vona/src/suite/a-training/modules/training-record/src/dto/recordSelectReq.tsx +3 -7
  49. package/vona/src/suite/a-training/modules/training-record/src/dto/recordUpdate.tsx +1 -0
  50. package/vona/src/suite/a-training/modules/training-record/src/dto/recordView.tsx +1 -0
  51. package/vona/src/suite/a-training/modules/training-record/src/entity/record.tsx +8 -0
  52. package/vona/src/suite/a-training/modules/training-record/test/record.test.ts +51 -0
  53. package/vona/src/suite/a-training/modules/training-student/src/config/locale/en-us.ts +3 -0
  54. package/vona/src/suite/a-training/modules/training-student/src/config/locale/zh-cn.ts +3 -0
  55. package/vona/src/suite/a-training/modules/training-student/src/dto/studentCreate.tsx +1 -0
  56. package/vona/src/suite/a-training/modules/training-student/src/dto/studentSelectResItem.tsx +1 -0
  57. package/vona/src/suite/a-training/modules/training-student/src/dto/studentUpdate.tsx +1 -0
  58. package/vona/src/suite/a-training/modules/training-student/src/dto/studentView.tsx +1 -0
  59. package/vona/src/suite/a-training/modules/training-student/src/entity/student.tsx +10 -1
  60. package/vona/src/suite/a-training/modules/training-student/test/student.test.ts +99 -1
  61. package/vona/src/suite-vendor/a-cabloy/modules/a-rbac/package.json +1 -1
  62. package/vona/src/suite-vendor/a-cabloy/package.json +2 -2
  63. package/vona/src/suite-vendor/a-vona/modules/a-permission/package.json +1 -1
  64. package/vona/src/suite-vendor/a-vona/modules/a-web/package.json +1 -1
  65. package/vona/src/suite-vendor/a-vona/modules/a-web/src/bean/pipe.filter.ts +85 -57
  66. package/vona/src/suite-vendor/a-vona/package.json +1 -1
  67. package/zova/packages-cli/cli/package.json +2 -2
  68. package/zova/packages-cli/cli-set-front/cli/templates/rest/render.ts +2 -0
  69. package/zova/packages-cli/cli-set-front/cli/templates/rest/rest.ts +13 -3
  70. package/zova/packages-cli/cli-set-front/package.json +1 -1
  71. package/zova/packages-zova/zova/package.json +2 -2
  72. package/zova/src/suite/cabloy-basic/modules/basic-page/src/component/blockPage/controller.tsx +31 -11
  73. package/zova/src/suite/cabloy-basic/modules/basic-page/src/component/blockTable/controller.tsx +3 -0
  74. package/zova/src/suite/cabloy-basic/modules/basic-pageentry/src/component/blockPageEntry/controller.tsx +1 -1
  75. package/zova/src/suite/cabloy-basic/modules/basic-table/src/component/table/render.tsx +68 -8
  76. package/zova/src/suite-vendor/a-cabloy/modules/rest-resource/package.json +1 -1
  77. package/zova/src/suite-vendor/a-cabloy/modules/rest-resource/src/model/resource.ts +4 -0
  78. package/zova/src/suite-vendor/a-cabloy/package.json +2 -2
  79. package/zova/src/suite-vendor/a-zova/modules/a-form/package.json +1 -1
  80. package/zova/src/suite-vendor/a-zova/modules/a-form/src/lib/formLayout.ts +0 -6
  81. package/zova/src/suite-vendor/a-zova/modules/a-form/test/lib/formLayout.test.ts +16 -9
  82. package/zova/src/suite-vendor/a-zova/modules/a-openapi/package.json +1 -1
  83. package/zova/src/suite-vendor/a-zova/modules/a-openapi/src/model/sdk.ts +4 -0
  84. package/zova/src/suite-vendor/a-zova/modules/a-openapi/src/types/rest.ts +13 -2
  85. package/zova/src/suite-vendor/a-zova/modules/a-openapi/src/types/schema.ts +1 -0
  86. package/zova/src/suite-vendor/a-zova/modules/a-table/package.json +1 -1
  87. package/zova/src/suite-vendor/a-zova/modules/a-table/src/component/table/controller.tsx +50 -0
  88. package/zova/src/suite-vendor/a-zova/modules/a-table/src/component/table/render.tsx +60 -7
  89. package/zova/src/suite-vendor/a-zova/modules/a-table/src/types/table.ts +6 -4
  90. package/zova/src/suite-vendor/a-zova/package.json +4 -4
@@ -1,3 +1,5 @@
1
+ import type { AriaAttributes, CSSProperties } from 'vue';
2
+
1
3
  import { FlexRender } from '@tanstack/vue-table';
2
4
  import { BeanRenderBase } from 'zova';
3
5
  import { Render } from 'zova-module-a-bean';
@@ -8,7 +10,14 @@ export class RenderTable<TData extends {} = {}> extends BeanRenderBase {
8
10
  public render() {
9
11
  return (
10
12
  <ZTable
11
- {...this.$props}
13
+ data={this.$props.data}
14
+ schema={this.$props.schema}
15
+ schemaOrder={this.$props.schemaOrder}
16
+ sorting={this.$props.sorting}
17
+ onSortingChange={this.$props.onSortingChange}
18
+ tableScope={this.$props.tableScope}
19
+ getColumns={this.$props.getColumns}
20
+ getRowId={this.$props.getRowId}
12
21
  controllerRef={ref => {
13
22
  this.$props?.tableRef?.(ref);
14
23
  }}
@@ -19,6 +28,21 @@ export class RenderTable<TData extends {} = {}> extends BeanRenderBase {
19
28
  );
20
29
  }
21
30
 
31
+ private _getColumnStyle(column: any): CSSProperties {
32
+ const rest = (column.columnDef.meta as any)?.rest;
33
+ const fixed = column.getIsPinned();
34
+ const width = rest?.width ? `${rest.width}px` : undefined;
35
+ return {
36
+ width,
37
+ minWidth: width,
38
+ textAlign: rest?.align,
39
+ position: fixed ? ('sticky' as const) : undefined,
40
+ left: fixed === 'left' ? `${column.getStart('left')}px` : undefined,
41
+ right: fixed === 'right' ? `${column.getAfter('right')}px` : undefined,
42
+ zIndex: fixed ? 1 : undefined,
43
+ };
44
+ }
45
+
22
46
  public _renderTable($$table: ControllerTable<TData>) {
23
47
  const table = $$table.table;
24
48
  return (
@@ -26,12 +50,43 @@ export class RenderTable<TData extends {} = {}> extends BeanRenderBase {
26
50
  <thead>
27
51
  <tr>
28
52
  {table.getFlatHeaders().map(header => {
53
+ const column = header.column;
54
+ const canSort = column.getCanSort();
55
+ const sorted = column.getIsSorted();
56
+ const fixed = column.getIsPinned();
57
+ const headerDefinition = column.columnDef.header;
58
+ const headerContent = (
59
+ <FlexRender render={headerDefinition} props={header.getContext()}></FlexRender>
60
+ );
61
+ let sortAria: AriaAttributes['aria-sort'] = 'none';
62
+ let sortIcon: '▲' | '▼' | '↕' = '↕';
63
+ if (sorted === 'asc') {
64
+ sortAria = 'ascending';
65
+ sortIcon = '▲';
66
+ } else if (sorted === 'desc') {
67
+ sortAria = 'descending';
68
+ sortIcon = '▼';
69
+ }
29
70
  return (
30
- <th key={header.id}>
31
- <FlexRender
32
- render={header.column.columnDef.header}
33
- props={header.getContext()}
34
- ></FlexRender>
71
+ <th
72
+ key={header.id}
73
+ class={fixed ? 'bg-base-100' : undefined}
74
+ style={this._getColumnStyle(column)}
75
+ aria-sort={sortAria}
76
+ >
77
+ {canSort ? (
78
+ <button
79
+ type="button"
80
+ class="inline-flex w-full items-center gap-1"
81
+ onClick={column.getToggleSortingHandler()}
82
+ aria-label={`Sort by ${column.id}`}
83
+ >
84
+ {headerContent}
85
+ <span aria-hidden="true">{sortIcon}</span>
86
+ </button>
87
+ ) : (
88
+ headerContent
89
+ )}
35
90
  </th>
36
91
  );
37
92
  })}
@@ -42,10 +97,15 @@ export class RenderTable<TData extends {} = {}> extends BeanRenderBase {
42
97
  return (
43
98
  <tr key={row.id}>
44
99
  {row.getVisibleCells().map(cell => {
100
+ const column = cell.column;
45
101
  return (
46
- <td key={cell.id}>
102
+ <td
103
+ key={cell.id}
104
+ class={column.getIsPinned() ? 'bg-base-100' : undefined}
105
+ style={this._getColumnStyle(column)}
106
+ >
47
107
  <FlexRender
48
- render={cell.column.columnDef.cell}
108
+ render={column.columnDef.cell}
49
109
  props={cell.getContext()}
50
110
  ></FlexRender>
51
111
  </td>
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zova-module-rest-resource",
3
- "version": "5.1.49",
3
+ "version": "5.1.50",
4
4
  "gitHead": "4dece229b6d3f4735930e833afcb4b95ec51bf86",
5
5
  "description": "",
6
6
  "keywords": [
@@ -57,6 +57,7 @@ export class ModelResource<
57
57
  public schemaCreate?: ISchemaObjectExtensionField;
58
58
  public schemaUpdate?: ISchemaObjectExtensionField;
59
59
  public schemaFilter?: ISchemaObjectExtensionField;
60
+ public schemaOrder?: ISchemaObjectExtensionField;
60
61
  public schemaRow?: ISchemaObjectExtensionField;
61
62
  public schemaPages?: ISchemaObjectExtensionField;
62
63
 
@@ -87,6 +88,9 @@ export class ModelResource<
87
88
  this.schemaFilter = this.$computed(() => {
88
89
  return this.apiSchemasSelect.filter;
89
90
  });
91
+ this.schemaOrder = this.$computed(() => {
92
+ return this.apiSchemasSelect.order;
93
+ });
90
94
  this.schemaRow = this.$computed(() => {
91
95
  return this.apiSchemasSelect.row;
92
96
  });
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zova-suite-a-cabloy",
3
- "version": "5.1.50",
3
+ "version": "5.1.51",
4
4
  "gitHead": "4dece229b6d3f4735930e833afcb4b95ec51bf86",
5
5
  "description": "",
6
6
  "author": "",
@@ -8,7 +8,7 @@
8
8
  "type": "module",
9
9
  "dependencies": {
10
10
  "zova-module-a-rbac": "^5.0.1",
11
- "zova-module-rest-resource": "^5.1.49"
11
+ "zova-module-rest-resource": "^5.1.50"
12
12
  },
13
13
  "title": "a-cabloy"
14
14
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zova-module-a-form",
3
- "version": "5.1.50",
3
+ "version": "5.1.51",
4
4
  "gitHead": "09d901d17140a80ee0764211b441cda72fd94663",
5
5
  "description": "",
6
6
  "keywords": [
@@ -58,12 +58,6 @@ export function resolveFormLayout(
58
58
  ),
59
59
  )
60
60
  .filter(Boolean) as IResolvedFormLayoutNode[];
61
- for (const property of properties ?? []) {
62
- const name = property.key;
63
- if (!name || !propertyNames.has(name) || fieldNames.has(name)) continue;
64
- fieldNames.add(name);
65
- children.push({ type: 'field', name });
66
- }
67
61
  return { children, fieldTabPaths, diagnostics };
68
62
  }
69
63
 
@@ -76,6 +76,19 @@ test('Form Layout accepts every preserved alias for the same canonical source',
76
76
  assert.deepEqual(resolved.diagnostics, []);
77
77
  });
78
78
 
79
+ test('Form Layout renders only explicitly declared visible fields', () => {
80
+ const resolved = resolveFormLayout(
81
+ createTabsLayout('name'),
82
+ createProperties({ key: 'name', type: 'string' }, { key: 'level', type: 'string' }),
83
+ );
84
+
85
+ assert.deepEqual(getTabFieldNames(resolved), ['name']);
86
+ assert.deepEqual(resolved.fieldTabPaths, {
87
+ name: [{ tabsId: 'tabs-0', tabId: 'tab-0-0' }],
88
+ });
89
+ assert.deepEqual(resolved.diagnostics, []);
90
+ });
91
+
79
92
  test('Form Layout prefers an exact canonical field over a colliding alias', () => {
80
93
  const resolved = resolveFormLayout(
81
94
  { children: [{ type: 'field', name: 'content' }] },
@@ -89,10 +102,7 @@ test('Form Layout prefers an exact canonical field over a colliding alias', () =
89
102
  ),
90
103
  );
91
104
 
92
- assert.deepEqual(resolved.children, [
93
- { type: 'field', name: 'content' },
94
- { type: 'field', name: 'content.descriptionMarkdown' },
95
- ]);
105
+ assert.deepEqual(resolved.children, [{ type: 'field', name: 'content' }]);
96
106
  assert.deepEqual(resolved.diagnostics, []);
97
107
  });
98
108
 
@@ -131,17 +141,14 @@ test('Form Layout keeps ambiguous aliases and relation prefixes unknown', () =>
131
141
  ),
132
142
  );
133
143
 
134
- assert.deepEqual(resolved.children, [
135
- { type: 'field', name: 'content.descriptionMarkdown' },
136
- { type: 'field', name: 'content.descriptionHtml' },
137
- ]);
144
+ assert.deepEqual(resolved.children, []);
138
145
  assert.deepEqual(resolved.diagnostics, [
139
146
  { type: 'unknownField', value: '_content' },
140
147
  { type: 'unknownField', value: 'content' },
141
148
  ]);
142
149
  });
143
150
 
144
- test('Form Layout does not resolve or append invisible aliases', () => {
151
+ test('Form Layout does not resolve invisible aliases', () => {
145
152
  const resolved = resolveFormLayout(
146
153
  { children: [{ type: 'field', name: '_descriptionMarkdown' }] },
147
154
  createProperties({
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zova-module-a-openapi",
3
- "version": "5.1.51",
3
+ "version": "5.1.52",
4
4
  "gitHead": "09d901d17140a80ee0764211b441cda72fd94663",
5
5
  "description": "",
6
6
  "keywords": [
@@ -183,6 +183,10 @@ export class ModelSdk extends BeanModelBase {
183
183
  const operationObject = sdk.data?.operationObject;
184
184
  return getSchemaOfRequestQueryFilter(operationObject, { where: true });
185
185
  },
186
+ get order() {
187
+ const operationObject = sdk.data?.operationObject;
188
+ return getSchemaOfRequestQueryFilter(operationObject, { order: true });
189
+ },
186
190
  get requestBody() {
187
191
  const operationObject = sdk.data?.operationObject;
188
192
  const schemaBody = getSchemaOfRequestBody(operationObject);
@@ -15,13 +15,24 @@ export type TypeFormFieldOnEffect = (value: any) => void;
15
15
 
16
16
  export interface ISchemaRenderComponentPresetRecord extends IResourceFormFieldRecord {}
17
17
 
18
- export interface ISchemaObjectExtensionFieldRest {
18
+ export type TypeTableColumnAlign = 'left' | 'center' | 'right';
19
+ export type TypeTableColumnFixed = 'left' | 'right';
20
+
21
+ export interface ITableColumnOptions {
22
+ order?: number;
23
+ align?: TypeTableColumnAlign;
24
+ width?: number;
25
+ fixed?: TypeTableColumnFixed;
26
+ enableSorting?: boolean;
27
+ sortDescFirst?: boolean;
28
+ }
29
+
30
+ export interface ISchemaObjectExtensionFieldRest extends ITableColumnOptions {
19
31
  'blocks'?: IResourceRenderBlockOptionsBlock[];
20
32
  'schemaScene'?: TypeSchemaScene;
21
33
  //
22
34
  'fieldSource'?: string;
23
35
  'visible'?: boolean;
24
- 'order'?: number;
25
36
  'disableNotifyChanged'?: boolean;
26
37
  'readonly'?: boolean;
27
38
  'onEffect'?: TypeFormFieldOnEffect;
@@ -16,6 +16,7 @@ export interface IOpenapiSchemas {
16
16
  sdk: TypeOpenapiSchemasSdk;
17
17
  query?: SchemaObject;
18
18
  filter?: SchemaObject;
19
+ order?: SchemaObject;
19
20
  requestBody?: SchemaObject;
20
21
  responseBody?: SchemaObject;
21
22
  paged?: SchemaObject;
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zova-module-a-table",
3
- "version": "5.1.38",
3
+ "version": "5.1.39",
4
4
  "gitHead": "09d901d17140a80ee0764211b441cda72fd94663",
5
5
  "description": "",
6
6
  "keywords": [
@@ -10,8 +10,11 @@ import { celEnvBase, isNilOrEmptyString } from '@cabloy/utils';
10
10
  import {
11
11
  CellContext,
12
12
  createColumnHelper,
13
+ functionalUpdate,
13
14
  getCoreRowModel,
15
+ OnChangeFn,
14
16
  Row,
17
+ SortingState,
15
18
  TableOptionsWithReactiveData,
16
19
  } from '@tanstack/vue-table';
17
20
  import { SchemaObject } from 'openapi3-ts/oas31';
@@ -48,6 +51,9 @@ import { ITableCellRenderColumnOptions } from '../../types/tableColumn.js';
48
51
  export interface ControllerTableProps<TData extends {} = {}> {
49
52
  data?: TData[];
50
53
  schema?: SchemaObject;
54
+ schemaOrder?: SchemaObject;
55
+ sorting?: SortingState;
56
+ onSortingChange?: OnChangeFn<SortingState>;
51
57
  tableScope?: ITableScope;
52
58
  getColumns?: TypeTableGetColumns<TData>;
53
59
  slotDefault?: (table: ControllerTable<TData>) => VNode;
@@ -95,9 +101,14 @@ export class ControllerTable<TData extends {} = {}> extends BeanControllerTableB
95
101
  return this.$props.data;
96
102
  }
97
103
 
104
+ public get sorting() {
105
+ return this.$props.sorting ?? [];
106
+ }
107
+
98
108
  public async refreshMeta() {
99
109
  this.tableMeta = await this._createTableMeta();
100
110
  this.columns = await this._createColumns();
111
+ this.table?.setColumnPinning(this._getColumnPinning());
101
112
  }
102
113
 
103
114
  private _createTable() {
@@ -108,6 +119,22 @@ export class ControllerTable<TData extends {} = {}> extends BeanControllerTableB
108
119
  getCoreRowModel: getCoreRowModel(),
109
120
  renderFallbackValue: this.scope.config.renderFallbackValue,
110
121
  manualPagination: true,
122
+ manualSorting: true,
123
+ enableMultiSort: false,
124
+ enableSortingRemoval: false,
125
+ state: {
126
+ get sorting() {
127
+ return self.sorting;
128
+ },
129
+ },
130
+ onSortingChange: updater => {
131
+ const sorting = functionalUpdate(updater, self.sorting).slice(0, 1);
132
+ self.$props.onSortingChange?.(sorting);
133
+ },
134
+ get initialState() {
135
+ const columnPinning = self._getColumnPinning();
136
+ return { columnPinning };
137
+ },
111
138
  get data() {
112
139
  return self.data || [];
113
140
  },
@@ -116,6 +143,7 @@ export class ControllerTable<TData extends {} = {}> extends BeanControllerTableB
116
143
  },
117
144
  };
118
145
  this.table = this.$useTable(tableOptions);
146
+ this.table.setColumnPinning(this._getColumnPinning());
119
147
  }
120
148
 
121
149
  private async _createColumns() {
@@ -162,12 +190,34 @@ export class ControllerTable<TData extends {} = {}> extends BeanControllerTableB
162
190
  return property?.title || key;
163
191
  },
164
192
  cell: props => tableMeta.renders[key](props),
193
+ size: property.rest?.width,
194
+ enableSorting: property.rest?.enableSorting === true && this._isOrderable(property),
195
+ sortDescFirst: property.rest?.sortDescFirst === true,
196
+ meta: { rest: property.rest },
165
197
  }),
166
198
  );
167
199
  }
168
200
  return columns;
169
201
  }
170
202
 
203
+ private _getColumnPinning() {
204
+ const left: string[] = [];
205
+ const right: string[] = [];
206
+ for (const property of this.tableMeta?.properties ?? []) {
207
+ const fixed = property.rest?.fixed;
208
+ if (fixed === 'left') left.push(property.key!);
209
+ if (fixed === 'right') right.push(property.key!);
210
+ }
211
+ return { left, right };
212
+ }
213
+
214
+ private _isOrderable(property: ISchemaObjectExtensionField) {
215
+ const properties = this.$props.schemaOrder?.properties;
216
+ if (!properties) return false;
217
+ const keys = [property.key, property.schemaKey, ...(property.schemaKeys ?? [])];
218
+ return keys.some(key => key && Object.prototype.hasOwnProperty.call(properties, key));
219
+ }
220
+
171
221
  private _createProperties() {
172
222
  this.properties = this.$computed(() => {
173
223
  return this.$sdk.loadSchemaProperties(this.schema, 'table');
@@ -1,9 +1,26 @@
1
+ import type { AriaAttributes, CSSProperties } from 'vue';
2
+
1
3
  import { FlexRender } from '@tanstack/vue-table';
2
4
  import { BeanRenderBase } from 'zova';
3
5
  import { Render } from 'zova-module-a-bean';
4
6
 
5
7
  @Render()
6
8
  export class RenderTable extends BeanRenderBase {
9
+ private _getColumnStyle(column: any): CSSProperties {
10
+ const rest = (column.columnDef.meta as any)?.rest;
11
+ const fixed = column.getIsPinned();
12
+ const width = rest?.width ? `${rest.width}px` : undefined;
13
+ return {
14
+ width,
15
+ minWidth: width,
16
+ textAlign: rest?.align,
17
+ position: fixed ? ('sticky' as const) : undefined,
18
+ left: fixed === 'left' ? `${column.getStart('left')}px` : undefined,
19
+ right: fixed === 'right' ? `${column.getAfter('right')}px` : undefined,
20
+ zIndex: fixed ? 1 : undefined,
21
+ };
22
+ }
23
+
7
24
  private _renderTableDefault() {
8
25
  const table = this.table;
9
26
  return (
@@ -11,12 +28,43 @@ export class RenderTable extends BeanRenderBase {
11
28
  <thead>
12
29
  <tr>
13
30
  {table.getFlatHeaders().map(header => {
31
+ const column = header.column;
32
+ const canSort = column.getCanSort();
33
+ const sorted = column.getIsSorted();
34
+ const fixed = column.getIsPinned();
35
+ const headerDefinition = column.columnDef.header;
36
+ const headerContent = (
37
+ <FlexRender render={headerDefinition} props={header.getContext()}></FlexRender>
38
+ );
39
+ let sortAria: AriaAttributes['aria-sort'] = 'none';
40
+ let sortIcon: '▲' | '▼' | '↕' = '↕';
41
+ if (sorted === 'asc') {
42
+ sortAria = 'ascending';
43
+ sortIcon = '▲';
44
+ } else if (sorted === 'desc') {
45
+ sortAria = 'descending';
46
+ sortIcon = '▼';
47
+ }
14
48
  return (
15
- <th key={header.id}>
16
- <FlexRender
17
- render={header.column.columnDef.header}
18
- props={header.getContext()}
19
- ></FlexRender>
49
+ <th
50
+ key={header.id}
51
+ class={fixed ? 'bg-base-100' : undefined}
52
+ style={this._getColumnStyle(column)}
53
+ aria-sort={sortAria}
54
+ >
55
+ {canSort ? (
56
+ <button
57
+ type="button"
58
+ class="inline-flex w-full items-center gap-1"
59
+ onClick={column.getToggleSortingHandler()}
60
+ aria-label={`Sort by ${column.id}`}
61
+ >
62
+ {headerContent}
63
+ <span aria-hidden="true">{sortIcon}</span>
64
+ </button>
65
+ ) : (
66
+ headerContent
67
+ )}
20
68
  </th>
21
69
  );
22
70
  })}
@@ -27,10 +75,15 @@ export class RenderTable extends BeanRenderBase {
27
75
  return (
28
76
  <tr key={row.id}>
29
77
  {row.getVisibleCells().map(cell => {
78
+ const column = cell.column;
30
79
  return (
31
- <td key={cell.id}>
80
+ <td
81
+ key={cell.id}
82
+ class={column.getIsPinned() ? 'bg-base-100' : undefined}
83
+ style={this._getColumnStyle(column)}
84
+ >
32
85
  <FlexRender
33
- render={cell.column.columnDef.cell}
86
+ render={column.columnDef.cell}
34
87
  props={cell.getContext()}
35
88
  ></FlexRender>
36
89
  </td>
@@ -1,6 +1,8 @@
1
1
  import type { ColumnDef, useVueTable } from '@tanstack/vue-table';
2
- import type { SchemaObject } from 'openapi3-ts/oas31';
3
- import type { TypeTableCellRenderComponent } from 'zova-module-a-openapi';
2
+ import type {
3
+ ISchemaObjectExtensionField,
4
+ TypeTableCellRenderComponent,
5
+ } from 'zova-module-a-openapi';
4
6
 
5
7
  import type { ControllerTable } from '../component/table/controller.jsx';
6
8
  import type { TypeTableCellRender } from './tableColumn.js';
@@ -10,12 +12,12 @@ export type TypeTable<TData extends {} = {}> = ReturnType<typeof useVueTable<TDa
10
12
  export type TypeColumn<TData extends {} = {}> = ColumnDef<TData, any>;
11
13
 
12
14
  export interface ITableMeta<TData extends {} = {}> {
13
- properties: SchemaObject[];
15
+ properties: ISchemaObjectExtensionField[];
14
16
  renders: Record<string, TypeTableCellRender<TData>>;
15
17
  }
16
18
 
17
19
  export type TypeTableGetColumnsNext<TData extends {} = {}> = (
18
- properties?: SchemaObject[],
20
+ properties?: ISchemaObjectExtensionField[],
19
21
  ) => Promise<TypeColumn<TData>[]>;
20
22
 
21
23
  export type TypeTableCreateColumnRender<TData extends {} = {}> = (
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zova-suite-a-zova",
3
- "version": "5.1.154",
3
+ "version": "5.1.155",
4
4
  "gitHead": "2c5c19284bab738e492856189acb6fad74b8a7b7",
5
5
  "description": "zova",
6
6
  "license": "MIT",
@@ -16,13 +16,13 @@
16
16
  "zova-module-a-boundary": "^5.1.22",
17
17
  "zova-module-a-command": "^5.1.34",
18
18
  "zova-module-a-fetch": "^5.1.25",
19
- "zova-module-a-form": "^5.1.50",
19
+ "zova-module-a-form": "^5.1.51",
20
20
  "zova-module-a-icon": "^5.1.27",
21
21
  "zova-module-a-interceptor": "^5.1.31",
22
22
  "zova-module-a-logger": "^5.1.28",
23
23
  "zova-module-a-meta": "^5.1.22",
24
24
  "zova-module-a-model": "^5.1.36",
25
- "zova-module-a-openapi": "^5.1.51",
25
+ "zova-module-a-openapi": "^5.1.52",
26
26
  "zova-module-a-router": "^5.1.34",
27
27
  "zova-module-a-routerstack": "^5.1.27",
28
28
  "zova-module-a-routertabs": "^5.1.34",
@@ -30,7 +30,7 @@
30
30
  "zova-module-a-ssrhmr": "^5.1.23",
31
31
  "zova-module-a-ssrserver": "^5.1.26",
32
32
  "zova-module-a-style": "^5.1.35",
33
- "zova-module-a-table": "^5.1.38",
33
+ "zova-module-a-table": "^5.1.39",
34
34
  "zova-module-a-zod": "^5.1.37",
35
35
  "zova-module-a-zova": "^5.1.103"
36
36
  },