cabloy 5.1.246 → 5.1.247
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.cabloy-version +1 -1
- package/CHANGELOG.md +6 -0
- package/package.json +1 -1
- package/repo-docs/frontend/column-configuration-guide.md +2 -0
- package/repo-docs/frontend/table-guide.md +24 -0
- package/repo-e2e/specs/cabloy-basic.spec.ts +350 -28
- package/zova/packages-zova/zova/package.json +2 -2
- package/zova/src/suite/cabloy-basic/modules/basic-date/src/component/dateRange/controller.tsx +4 -3
- package/zova/src/suite/cabloy-basic/modules/basic-date/src/component/formFieldDateRange/controller.tsx +13 -1
- package/zova/src/suite/cabloy-basic/modules/basic-details/src/component/blockTable/controller.tsx +1 -1
- package/zova/src/suite/cabloy-basic/modules/basic-page/src/component/blockTable/controller.tsx +1 -1
- package/zova/src/suite/cabloy-basic/modules/basic-table/src/.metadata/component/rowOperations.ts +31 -0
- package/zova/src/suite/cabloy-basic/modules/basic-table/src/.metadata/index.ts +13 -0
- package/zova/src/suite/cabloy-basic/modules/basic-table/src/bean/tableCell.actionDelete.tsx +6 -2
- package/zova/src/suite/cabloy-basic/modules/basic-table/src/bean/tableCell.actionOperationsRow.tsx +32 -4
- package/zova/src/suite/cabloy-basic/modules/basic-table/src/bean/tableCell.actionUpdate.tsx +6 -2
- package/zova/src/suite/cabloy-basic/modules/basic-table/src/component/rowOperations/controller.tsx +233 -0
- package/zova/src/suite/cabloy-basic/modules/basic-table/src/component/table/controller.tsx +75 -2
- package/zova/src/suite/cabloy-basic/modules/basic-table/src/component/table/render.tsx +40 -22
- package/zova/src/suite/cabloy-basic/modules/basic-table/src/config/locale/en-us.ts +4 -0
- package/zova/src/suite/cabloy-basic/modules/basic-table/src/config/locale/zh-cn.ts +4 -0
- package/zova/src/suite-vendor/a-zova/modules/a-table/package.json +1 -1
- package/zova/src/suite-vendor/a-zova/modules/a-table/src/component/table/controller.tsx +19 -11
- package/zova/src/suite-vendor/a-zova/package.json +2 -2
|
@@ -4,6 +4,7 @@ export * from '../component/actionColumnConfig/controller.tsx';
|
|
|
4
4
|
export * from '../component/actionCreate/controller.tsx';
|
|
5
5
|
export * from '../component/actionDeleteBulk/controller.tsx';
|
|
6
6
|
export * from '../component/actionRefresh/controller.tsx';
|
|
7
|
+
export * from '../component/rowOperations/controller.tsx';
|
|
7
8
|
export * from '../component/table/controller.tsx';
|
|
8
9
|
|
|
9
10
|
import 'zova';
|
|
@@ -33,6 +34,11 @@ declare module 'zova-module-basic-table' {
|
|
|
33
34
|
get scope(): ScopeModuleBasicTable;
|
|
34
35
|
}
|
|
35
36
|
|
|
37
|
+
export interface ControllerRowOperations {
|
|
38
|
+
/** @internal */
|
|
39
|
+
get scope(): ScopeModuleBasicTable;
|
|
40
|
+
}
|
|
41
|
+
|
|
36
42
|
export interface ControllerTable {
|
|
37
43
|
/** @internal */
|
|
38
44
|
get scope(): ScopeModuleBasicTable;
|
|
@@ -44,6 +50,7 @@ import type { ControllerActionColumnConfig } from '../component/actionColumnConf
|
|
|
44
50
|
import type { ControllerActionCreate } from '../component/actionCreate/controller.tsx';
|
|
45
51
|
import type { ControllerActionDeleteBulk } from '../component/actionDeleteBulk/controller.tsx';
|
|
46
52
|
import type { ControllerActionRefresh } from '../component/actionRefresh/controller.tsx';
|
|
53
|
+
import type { ControllerRowOperations } from '../component/rowOperations/controller.tsx';
|
|
47
54
|
import type { ControllerTable } from '../component/table/controller.tsx';
|
|
48
55
|
import 'zova';
|
|
49
56
|
declare module 'zova' {
|
|
@@ -52,6 +59,7 @@ declare module 'zova' {
|
|
|
52
59
|
'basic-table.controller.actionCreate': ControllerActionCreate;
|
|
53
60
|
'basic-table.controller.actionDeleteBulk': ControllerActionDeleteBulk;
|
|
54
61
|
'basic-table.controller.actionRefresh': ControllerActionRefresh;
|
|
62
|
+
'basic-table.controller.rowOperations': ControllerRowOperations;
|
|
55
63
|
'basic-table.controller.table': ControllerTable;
|
|
56
64
|
}
|
|
57
65
|
}
|
|
@@ -66,6 +74,8 @@ export * from './component/actionDeleteBulk.ts';
|
|
|
66
74
|
import { ZActionDeleteBulk } from './component/actionDeleteBulk.ts';
|
|
67
75
|
export * from './component/actionRefresh.ts';
|
|
68
76
|
import { ZActionRefresh } from './component/actionRefresh.ts';
|
|
77
|
+
export * from './component/rowOperations.ts';
|
|
78
|
+
import { ZRowOperations } from './component/rowOperations.ts';
|
|
69
79
|
export * from './component/table.ts';
|
|
70
80
|
import { ZTable } from './component/table.ts';
|
|
71
81
|
export const components = {
|
|
@@ -73,6 +83,7 @@ export const components = {
|
|
|
73
83
|
'actionCreate': ZActionCreate,
|
|
74
84
|
'actionDeleteBulk': ZActionDeleteBulk,
|
|
75
85
|
'actionRefresh': ZActionRefresh,
|
|
86
|
+
'rowOperations': ZRowOperations,
|
|
76
87
|
'table': ZTable,
|
|
77
88
|
};
|
|
78
89
|
import 'zova';
|
|
@@ -82,6 +93,7 @@ export interface IComponentRecord {
|
|
|
82
93
|
'basic-table:actionCreate': ControllerActionCreate;
|
|
83
94
|
'basic-table:actionDeleteBulk': ControllerActionDeleteBulk;
|
|
84
95
|
'basic-table:actionRefresh': ControllerActionRefresh;
|
|
96
|
+
'basic-table:rowOperations': ControllerRowOperations;
|
|
85
97
|
'basic-table:table': ControllerTable;
|
|
86
98
|
}
|
|
87
99
|
export interface IZovaComponentRecord {
|
|
@@ -89,6 +101,7 @@ export interface IZovaComponentRecord {
|
|
|
89
101
|
'basic-table:actionCreate': typeof ZActionCreate;
|
|
90
102
|
'basic-table:actionDeleteBulk': typeof ZActionDeleteBulk;
|
|
91
103
|
'basic-table:actionRefresh': typeof ZActionRefresh;
|
|
104
|
+
'basic-table:rowOperations': typeof ZRowOperations;
|
|
92
105
|
'basic-table:table': typeof ZTable;
|
|
93
106
|
}
|
|
94
107
|
}
|
|
@@ -16,7 +16,9 @@ declare module 'zova-module-a-openapi' {
|
|
|
16
16
|
}
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
-
export interface ITableCellOptionsActionDelete extends IResourceTableActionRowOptionsBase {
|
|
19
|
+
export interface ITableCellOptionsActionDelete extends IResourceTableActionRowOptionsBase {
|
|
20
|
+
showLabel?: boolean;
|
|
21
|
+
}
|
|
20
22
|
|
|
21
23
|
@TableCell<ITableCellOptionsActionDelete>({
|
|
22
24
|
class: 'btn btn-outline btn-error join-item',
|
|
@@ -31,6 +33,7 @@ export class TableCellActionDelete extends BeanBase implements ITableCellRender
|
|
|
31
33
|
return (
|
|
32
34
|
<ZButton
|
|
33
35
|
class={options.class}
|
|
36
|
+
aria-label={this.scope.locale.Delete()}
|
|
34
37
|
onPerform={async () => {
|
|
35
38
|
const confirmed = await $host.$performCommand('basic-commands:confirm', {
|
|
36
39
|
text: this.scope.locale.DeleteConfirm(),
|
|
@@ -39,7 +42,8 @@ export class TableCellActionDelete extends BeanBase implements ITableCellRender
|
|
|
39
42
|
await $host.$performCommand('basic-commands:delete', options, renderContext);
|
|
40
43
|
}}
|
|
41
44
|
>
|
|
42
|
-
<ZIcon name="::delete" width={24}></ZIcon>
|
|
45
|
+
<ZIcon name="::delete" width={24} aria-hidden="true"></ZIcon>
|
|
46
|
+
{options.showLabel && this.scope.locale.Delete()}
|
|
43
47
|
</ZButton>
|
|
44
48
|
);
|
|
45
49
|
}
|
package/zova/src/suite/cabloy-basic/modules/basic-table/src/bean/tableCell.actionOperationsRow.tsx
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { VNode } from 'vue';
|
|
1
2
|
import type {
|
|
2
3
|
IResourceRenderTableActionRowOptionsAction,
|
|
3
4
|
IResourceTableActionRowOptionsBase,
|
|
@@ -9,10 +10,12 @@ import type {
|
|
|
9
10
|
NextTableCellRender,
|
|
10
11
|
} from 'zova-module-a-table';
|
|
11
12
|
|
|
12
|
-
import {
|
|
13
|
+
import { Comment, Fragment, isVNode, Text } from 'vue';
|
|
13
14
|
import { BeanBase } from 'zova';
|
|
14
15
|
import { IJsxRenderContextTableColumn, TableCell } from 'zova-module-a-table';
|
|
15
16
|
|
|
17
|
+
import { ZRowOperations } from '../.metadata/component/rowOperations.ts';
|
|
18
|
+
|
|
16
19
|
declare module 'zova-module-a-openapi' {
|
|
17
20
|
export interface IResourceTableActionRowRecord {
|
|
18
21
|
'basic-table:actionOperationsRow'?: ITableCellOptionsActionOperationsRow;
|
|
@@ -65,10 +68,35 @@ export class TableCellActionOperationsRow extends BeanBase implements ITableCell
|
|
|
65
68
|
const actionName = action.name;
|
|
66
69
|
const permissionHint = action.options?.permission;
|
|
67
70
|
if (!$host.$passport.checkPermission(permissions, actionName, permissionHint, data)) return;
|
|
68
|
-
const
|
|
69
|
-
|
|
71
|
+
const builtInLabel =
|
|
72
|
+
action.render === 'basic-table:actionUpdate' ||
|
|
73
|
+
action.render === 'basic-table:actionDelete';
|
|
74
|
+
const options2 = Object.assign(
|
|
75
|
+
{ key: index },
|
|
76
|
+
action.options,
|
|
77
|
+
builtInLabel ? { showLabel: $$table.compact } : undefined,
|
|
78
|
+
);
|
|
79
|
+
const domAction = $$table.cellRender(action.render!, options2, renderContext);
|
|
80
|
+
if (this._hasContent(domAction)) domActions.push(domAction);
|
|
70
81
|
});
|
|
71
82
|
if (domActions.length === 0) return;
|
|
72
|
-
return
|
|
83
|
+
return (
|
|
84
|
+
<ZRowOperations
|
|
85
|
+
compact={$$table.compact}
|
|
86
|
+
class={options.class}
|
|
87
|
+
slotDefault={() => <>{domActions}</>}
|
|
88
|
+
/>
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
private _hasContent(value: unknown): boolean {
|
|
93
|
+
if (value === undefined || value === null || typeof value === 'boolean') return false;
|
|
94
|
+
if (typeof value === 'string') return value.trim().length > 0;
|
|
95
|
+
if (Array.isArray(value)) return value.some(item => this._hasContent(item));
|
|
96
|
+
if (!isVNode(value)) return true;
|
|
97
|
+
if (value.type === Comment) return false;
|
|
98
|
+
if (value.type === Text || value.type === Fragment) return this._hasContent(value.children);
|
|
99
|
+
// Component renderers stay opaque: preserve their VNode and runtime ownership.
|
|
100
|
+
return true;
|
|
73
101
|
}
|
|
74
102
|
}
|
|
@@ -16,7 +16,9 @@ declare module 'zova-module-a-openapi' {
|
|
|
16
16
|
}
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
-
export interface ITableCellOptionsActionUpdate extends IResourceTableActionRowOptionsBase {
|
|
19
|
+
export interface ITableCellOptionsActionUpdate extends IResourceTableActionRowOptionsBase {
|
|
20
|
+
showLabel?: boolean;
|
|
21
|
+
}
|
|
20
22
|
|
|
21
23
|
@TableCell<ITableCellOptionsActionUpdate>({
|
|
22
24
|
class: 'btn btn-outline btn-primary join-item',
|
|
@@ -31,11 +33,13 @@ export class TableCellActionUpdate extends BeanBase implements ITableCellRender
|
|
|
31
33
|
return (
|
|
32
34
|
<ZButton
|
|
33
35
|
class={options.class}
|
|
36
|
+
aria-label={this.scope.locale.Edit()}
|
|
34
37
|
onPerform={async () => {
|
|
35
38
|
await $host.$performCommand('basic-commands:edit', options, renderContext);
|
|
36
39
|
}}
|
|
37
40
|
>
|
|
38
|
-
<ZIcon name="::draft" width={24}></ZIcon>
|
|
41
|
+
<ZIcon name="::draft" width={24} aria-hidden="true"></ZIcon>
|
|
42
|
+
{options.showLabel && this.scope.locale.Edit()}
|
|
39
43
|
</ZButton>
|
|
40
44
|
);
|
|
41
45
|
}
|
package/zova/src/suite/cabloy-basic/modules/basic-table/src/component/rowOperations/controller.tsx
ADDED
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
import type { IComponentOptions, ISlot } from 'zova';
|
|
2
|
+
|
|
3
|
+
import { useId } from 'vue';
|
|
4
|
+
import { BeanControllerBase } from 'zova';
|
|
5
|
+
import { Controller } from 'zova-module-a-bean';
|
|
6
|
+
import { ZIcon } from 'zova-module-a-icon';
|
|
7
|
+
import { ZButton } from 'zova-module-basic-button';
|
|
8
|
+
|
|
9
|
+
export interface ControllerRowOperationsProps {
|
|
10
|
+
compact?: boolean;
|
|
11
|
+
class?: unknown;
|
|
12
|
+
slotDefault?: ISlot;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
@Controller()
|
|
16
|
+
export class ControllerRowOperations extends BeanControllerBase {
|
|
17
|
+
static $propsDefault = { compact: false };
|
|
18
|
+
static $componentOptions: IComponentOptions = { inheritAttrs: false };
|
|
19
|
+
|
|
20
|
+
private _id: string;
|
|
21
|
+
private _actionsRef?: HTMLDivElement;
|
|
22
|
+
private _open = false;
|
|
23
|
+
private _positionFrame?: number;
|
|
24
|
+
private _cPopover: string;
|
|
25
|
+
private _onBeforeToggle: (event: ToggleEvent) => void;
|
|
26
|
+
private _onViewportChange: (event: Event) => void;
|
|
27
|
+
private _onKeydown: (event: KeyboardEvent) => void;
|
|
28
|
+
private _onActionClick: (event: MouseEvent) => void;
|
|
29
|
+
|
|
30
|
+
protected async __init__() {
|
|
31
|
+
this._onActionClick = event => this._handleActionClick(event);
|
|
32
|
+
this._onBeforeToggle = event => this._handleBeforeToggle(event);
|
|
33
|
+
this._onKeydown = event => this._handleKeydown(event);
|
|
34
|
+
this._onViewportChange = event => {
|
|
35
|
+
if (event.target instanceof Node && this._actionsRef?.contains(event.target)) return;
|
|
36
|
+
this._close();
|
|
37
|
+
};
|
|
38
|
+
this._id = this.$composable(useId);
|
|
39
|
+
this._cPopover = this.$style({
|
|
40
|
+
position: 'fixed',
|
|
41
|
+
inset: 'auto',
|
|
42
|
+
margin: 0,
|
|
43
|
+
minWidth: '10rem',
|
|
44
|
+
maxWidth: 'calc(100vw - 16px)',
|
|
45
|
+
maxHeight: 'calc(100vh - 16px)',
|
|
46
|
+
overflowY: 'auto',
|
|
47
|
+
$nest: {
|
|
48
|
+
'&:popover-open': {
|
|
49
|
+
display: 'flex',
|
|
50
|
+
flexDirection: 'column',
|
|
51
|
+
alignItems: 'stretch',
|
|
52
|
+
gap: '0.25rem',
|
|
53
|
+
},
|
|
54
|
+
'& > .btn': {
|
|
55
|
+
justifyContent: 'flex-start',
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
});
|
|
59
|
+
this.$watch(
|
|
60
|
+
() => this.$props.compact,
|
|
61
|
+
() => this._close(),
|
|
62
|
+
{ flush: 'sync' },
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
protected __dispose__() {
|
|
67
|
+
this._close();
|
|
68
|
+
this._setActionsRef(undefined);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
private _setActionsRef(element?: HTMLDivElement) {
|
|
72
|
+
if (element === this._actionsRef) return;
|
|
73
|
+
this._actionsRef?.removeEventListener('beforetoggle', this._onBeforeToggle);
|
|
74
|
+
this._actionsRef?.removeEventListener('click', this._onActionClick, true);
|
|
75
|
+
this._actionsRef = element;
|
|
76
|
+
element?.addEventListener('beforetoggle', this._onBeforeToggle);
|
|
77
|
+
element?.addEventListener('click', this._onActionClick, true);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
private _handleBeforeToggle(event: ToggleEvent) {
|
|
81
|
+
if (event.target !== this._actionsRef) return;
|
|
82
|
+
this._open = event.newState === 'open';
|
|
83
|
+
if (this._open) {
|
|
84
|
+
// beforetoggle precedes display; measure in the first frame after native opening.
|
|
85
|
+
this._actionsRef!.style.visibility = 'hidden';
|
|
86
|
+
this._positionFrame = window.requestAnimationFrame(() => {
|
|
87
|
+
this._positionFrame = undefined;
|
|
88
|
+
if (!this._open) return;
|
|
89
|
+
this._position();
|
|
90
|
+
const element = this._actionsRef;
|
|
91
|
+
element?.style.removeProperty('visibility');
|
|
92
|
+
const document = element?.ownerDocument;
|
|
93
|
+
if (
|
|
94
|
+
document &&
|
|
95
|
+
(document.activeElement?.id === `${this._id}-trigger` ||
|
|
96
|
+
document.activeElement === document.body)
|
|
97
|
+
) {
|
|
98
|
+
element
|
|
99
|
+
?.querySelector<HTMLElement>('button:not(:disabled), a[href], [tabindex="0"]')
|
|
100
|
+
?.focus({ preventScroll: true });
|
|
101
|
+
}
|
|
102
|
+
// Opening can follow scroll-into-view with its scroll event still queued.
|
|
103
|
+
// Arm dismissal only after measuring the settled anchor and revealing the popup.
|
|
104
|
+
window.addEventListener('scroll', this._onViewportChange, true);
|
|
105
|
+
});
|
|
106
|
+
window.addEventListener('resize', this._onViewportChange);
|
|
107
|
+
this._actionsRef!.ownerDocument.addEventListener('keydown', this._onKeydown);
|
|
108
|
+
} else {
|
|
109
|
+
this._clearOpenState();
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
private _clearOpenState() {
|
|
114
|
+
this._open = false;
|
|
115
|
+
if (this._positionFrame !== undefined) {
|
|
116
|
+
window.cancelAnimationFrame(this._positionFrame);
|
|
117
|
+
this._positionFrame = undefined;
|
|
118
|
+
}
|
|
119
|
+
window.removeEventListener('scroll', this._onViewportChange, true);
|
|
120
|
+
window.removeEventListener('resize', this._onViewportChange);
|
|
121
|
+
this._actionsRef?.ownerDocument.removeEventListener('keydown', this._onKeydown);
|
|
122
|
+
this._actionsRef?.style.removeProperty('visibility');
|
|
123
|
+
this._actionsRef?.style.removeProperty('left');
|
|
124
|
+
this._actionsRef?.style.removeProperty('top');
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
private _close() {
|
|
128
|
+
if (!this._open) return;
|
|
129
|
+
const element = this._actionsRef;
|
|
130
|
+
if (element?.isConnected && element.popover === 'auto') element.hidePopover();
|
|
131
|
+
this._clearOpenState();
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
private _handleActionClick(event: MouseEvent) {
|
|
135
|
+
if (!this.$props.compact || !this._open || !(event.target instanceof Element)) return;
|
|
136
|
+
const action = event.target.closest('button, a[href], [role="button"]');
|
|
137
|
+
if (
|
|
138
|
+
!action ||
|
|
139
|
+
!this._actionsRef?.contains(action) ||
|
|
140
|
+
action.matches(':disabled, [aria-disabled="true"]')
|
|
141
|
+
) {
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
this._close();
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
private _handleKeydown(event: KeyboardEvent) {
|
|
148
|
+
if (event.key !== 'Escape' || event.defaultPrevented || !this._open) return;
|
|
149
|
+
if (event.target instanceof Element && event.target.closest('dialog[open], [role="dialog"]'))
|
|
150
|
+
return;
|
|
151
|
+
const element = this._actionsRef;
|
|
152
|
+
const document = element?.ownerDocument;
|
|
153
|
+
const activeElement = document?.activeElement;
|
|
154
|
+
const trigger = document?.getElementById(`${this._id}-trigger`);
|
|
155
|
+
if (!document || !activeElement) return;
|
|
156
|
+
if (activeElement.closest('dialog[open], [role="dialog"]')) return;
|
|
157
|
+
if (
|
|
158
|
+
activeElement !== document.body &&
|
|
159
|
+
activeElement !== trigger &&
|
|
160
|
+
!element?.contains(activeElement)
|
|
161
|
+
) {
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
if (activeElement === document.body && document.querySelector('dialog:modal')) return;
|
|
165
|
+
// A disabled action can leave focus on body; never claim unrelated focused controls.
|
|
166
|
+
event.preventDefault();
|
|
167
|
+
event.stopPropagation();
|
|
168
|
+
this._close();
|
|
169
|
+
if (trigger?.isConnected) trigger.focus({ preventScroll: true });
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
private _position() {
|
|
173
|
+
const element = this._actionsRef;
|
|
174
|
+
const trigger = element?.ownerDocument.getElementById(`${this._id}-trigger`);
|
|
175
|
+
if (!element || !trigger) return;
|
|
176
|
+
const anchor = trigger.getBoundingClientRect();
|
|
177
|
+
const popup = element.getBoundingClientRect();
|
|
178
|
+
const viewport = element.ownerDocument.documentElement;
|
|
179
|
+
const margin = 8;
|
|
180
|
+
const gap = 4;
|
|
181
|
+
const left = Math.max(
|
|
182
|
+
margin,
|
|
183
|
+
Math.min(anchor.right - popup.width, viewport.clientWidth - popup.width - margin),
|
|
184
|
+
);
|
|
185
|
+
const below = anchor.bottom + gap;
|
|
186
|
+
const above = anchor.top - gap - popup.height;
|
|
187
|
+
const preferredTop =
|
|
188
|
+
below + popup.height > viewport.clientHeight - margin && above >= margin ? above : below;
|
|
189
|
+
const top = Math.max(
|
|
190
|
+
margin,
|
|
191
|
+
Math.min(preferredTop, viewport.clientHeight - popup.height - margin),
|
|
192
|
+
);
|
|
193
|
+
element.style.left = `${left}px`;
|
|
194
|
+
element.style.top = `${top}px`;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
protected render() {
|
|
198
|
+
const { compact } = this.$props;
|
|
199
|
+
const label = this.scope.locale.MoreActions();
|
|
200
|
+
return (
|
|
201
|
+
<div class="inline-flex items-center">
|
|
202
|
+
{compact && (
|
|
203
|
+
<ZButton
|
|
204
|
+
key="trigger"
|
|
205
|
+
id={`${this._id}-trigger`}
|
|
206
|
+
class="btn btn-ghost btn-square"
|
|
207
|
+
aria-label={label}
|
|
208
|
+
aria-controls={this._id}
|
|
209
|
+
aria-expanded={this._open}
|
|
210
|
+
{...{ popovertarget: this._id }}
|
|
211
|
+
>
|
|
212
|
+
<ZIcon name="::more-horiz" width={24} aria-hidden="true" />
|
|
213
|
+
</ZButton>
|
|
214
|
+
)}
|
|
215
|
+
<div
|
|
216
|
+
key="actions"
|
|
217
|
+
ref={ref => this._setActionsRef((ref as HTMLDivElement | null) ?? undefined)}
|
|
218
|
+
id={this._id}
|
|
219
|
+
class={
|
|
220
|
+
compact
|
|
221
|
+
? [this._cPopover, 'bg-base-100 border border-base-300 rounded-box shadow-lg p-2']
|
|
222
|
+
: this.$props.class
|
|
223
|
+
}
|
|
224
|
+
{...{ popover: compact ? 'auto' : undefined }}
|
|
225
|
+
role="group"
|
|
226
|
+
aria-label={this.scope.locale.RowActions()}
|
|
227
|
+
>
|
|
228
|
+
{this.$slotDefault?.()}
|
|
229
|
+
</div>
|
|
230
|
+
</div>
|
|
231
|
+
);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type {
|
|
2
2
|
BeanControllerTableBase,
|
|
3
|
+
ControllerTable as ControllerTableInner,
|
|
3
4
|
ControllerTableProps as ControllerTablePropsSuper,
|
|
4
5
|
} from 'zova-module-a-table';
|
|
5
6
|
|
|
@@ -9,12 +10,84 @@ import { Controller } from 'zova-module-a-bean';
|
|
|
9
10
|
export interface ControllerTableProps<
|
|
10
11
|
TData extends {} = {},
|
|
11
12
|
> extends ControllerTablePropsSuper<TData> {
|
|
13
|
+
compactBreakpoint?: number | false;
|
|
12
14
|
tableRef?: (ref: BeanControllerTableBase<TData>) => void;
|
|
13
15
|
}
|
|
14
16
|
|
|
15
17
|
@Controller()
|
|
16
18
|
export class ControllerTable extends BeanControllerBase {
|
|
17
|
-
static $propsDefault = {};
|
|
19
|
+
static $propsDefault = { compactBreakpoint: 768 };
|
|
18
20
|
|
|
19
|
-
|
|
21
|
+
innerTable?: ControllerTableInner<any>;
|
|
22
|
+
containerWidth: number = 0;
|
|
23
|
+
popoverSupported: boolean = false;
|
|
24
|
+
private _container?: HTMLElement;
|
|
25
|
+
private _observer?: ResizeObserver;
|
|
26
|
+
private _mounted: boolean = false;
|
|
27
|
+
private _disposed: boolean = false;
|
|
28
|
+
|
|
29
|
+
protected async __init__() {
|
|
30
|
+
this.$controllerMounted(() => {
|
|
31
|
+
this.$ssr.handleDirectOrOnHydrated(() => {
|
|
32
|
+
if (this._disposed) return;
|
|
33
|
+
this._mounted = true;
|
|
34
|
+
this.popoverSupported = typeof HTMLElement.prototype.showPopover === 'function';
|
|
35
|
+
this._observeContainer();
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
public get compact() {
|
|
41
|
+
const breakpoint = this.$props.compactBreakpoint;
|
|
42
|
+
return (
|
|
43
|
+
this.popoverSupported &&
|
|
44
|
+
breakpoint !== false &&
|
|
45
|
+
this.containerWidth > 0 &&
|
|
46
|
+
this.containerWidth < (breakpoint ?? 768)
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
public get columnSizing() {
|
|
51
|
+
const sizing = { ...this.$props.columnSizing };
|
|
52
|
+
if (this.compact) {
|
|
53
|
+
for (const property of this.innerTable?.tableMeta?.properties ?? []) {
|
|
54
|
+
if (this.isOperationsColumn(property.key!)) sizing[property.key!] = 80;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return sizing;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
public isOperationsColumn(key: string) {
|
|
61
|
+
const table = this.innerTable;
|
|
62
|
+
if (!table) return false;
|
|
63
|
+
const scope = table.getColumnScope(key);
|
|
64
|
+
const { render } = table.getColumnComponentPropsTop(key, scope, {});
|
|
65
|
+
return table.getRenderProvider(render) === 'basic-table.tableCell.actionOperationsRow';
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
public setContainer(element: HTMLElement | undefined) {
|
|
69
|
+
if (element === this._container) return;
|
|
70
|
+
this._observer?.disconnect();
|
|
71
|
+
this._container = element;
|
|
72
|
+
this._observeContainer();
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
private _observeContainer() {
|
|
76
|
+
const container = this._container;
|
|
77
|
+
if (!this._mounted || this._disposed || !container) return;
|
|
78
|
+
this._observer?.disconnect();
|
|
79
|
+
const measure = () => {
|
|
80
|
+
if (this._disposed || container !== this._container) return;
|
|
81
|
+
this.containerWidth = container.clientWidth;
|
|
82
|
+
};
|
|
83
|
+
measure();
|
|
84
|
+
this._observer = new ResizeObserver(measure);
|
|
85
|
+
this._observer.observe(container);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
protected __dispose__() {
|
|
89
|
+
this._disposed = true;
|
|
90
|
+
this._observer?.disconnect();
|
|
91
|
+
this._container = undefined;
|
|
92
|
+
}
|
|
20
93
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AriaAttributes, CSSProperties } from 'vue';
|
|
1
|
+
import type { AriaAttributes, CSSProperties, ComponentPublicInstance } from 'vue';
|
|
2
2
|
|
|
3
3
|
import { FlexRender } from '@tanstack/vue-table';
|
|
4
4
|
import { BeanRenderBase } from 'zova';
|
|
@@ -9,27 +9,39 @@ import { ControllerTable, TableColumnIdSelection, ZTable } from 'zova-module-a-t
|
|
|
9
9
|
export class RenderTable<TData extends {} = {}> extends BeanRenderBase {
|
|
10
10
|
public render() {
|
|
11
11
|
return (
|
|
12
|
-
<
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
enableRowSelection={this.$props.enableRowSelection}
|
|
19
|
-
selectionMode={this.$props.selectionMode}
|
|
20
|
-
rowSelection={this.$props.rowSelection}
|
|
21
|
-
onRowSelectionChange={this.$props.onRowSelectionChange}
|
|
22
|
-
tableScope={this.$props.tableScope}
|
|
23
|
-
getColumns={this.$props.getColumns}
|
|
24
|
-
getRowId={this.$props.getRowId}
|
|
25
|
-
layout={this.$props.layout}
|
|
26
|
-
controllerRef={ref => {
|
|
27
|
-
this.$props?.tableRef?.(ref);
|
|
12
|
+
<div
|
|
13
|
+
class="min-w-0 max-w-full overflow-x-auto"
|
|
14
|
+
data-table-compact={this.compact ? 'true' : 'false'}
|
|
15
|
+
ref={(element: Element | ComponentPublicInstance | null) => {
|
|
16
|
+
if (this.ctx.disposed) return;
|
|
17
|
+
this.setContainer?.((element as HTMLElement | null) ?? undefined);
|
|
28
18
|
}}
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
19
|
+
>
|
|
20
|
+
<ZTable
|
|
21
|
+
data={this.$props.data}
|
|
22
|
+
schema={this.$props.schema}
|
|
23
|
+
schemaOrder={this.$props.schemaOrder}
|
|
24
|
+
sorting={this.$props.sorting}
|
|
25
|
+
onSortingChange={this.$props.onSortingChange}
|
|
26
|
+
enableRowSelection={this.$props.enableRowSelection}
|
|
27
|
+
selectionMode={this.$props.selectionMode}
|
|
28
|
+
rowSelection={this.$props.rowSelection}
|
|
29
|
+
onRowSelectionChange={this.$props.onRowSelectionChange}
|
|
30
|
+
tableScope={this.$props.tableScope}
|
|
31
|
+
getColumns={this.$props.getColumns}
|
|
32
|
+
getRowId={this.$props.getRowId}
|
|
33
|
+
layout={this.$props.layout}
|
|
34
|
+
compact={this.compact}
|
|
35
|
+
columnSizing={this.columnSizing}
|
|
36
|
+
controllerRef={ref => {
|
|
37
|
+
this.innerTable = ref;
|
|
38
|
+
this.$props?.tableRef?.(ref);
|
|
39
|
+
}}
|
|
40
|
+
slotDefault={$$table => {
|
|
41
|
+
return this._renderTable($$table);
|
|
42
|
+
}}
|
|
43
|
+
></ZTable>
|
|
44
|
+
</div>
|
|
33
45
|
);
|
|
34
46
|
}
|
|
35
47
|
|
|
@@ -38,7 +50,9 @@ export class RenderTable<TData extends {} = {}> extends BeanRenderBase {
|
|
|
38
50
|
const fixed = column.getIsPinned();
|
|
39
51
|
const width = column.getSize();
|
|
40
52
|
return {
|
|
41
|
-
...(typeof rest?.width === 'number'
|
|
53
|
+
...(typeof rest?.width === 'number' || typeof this.columnSizing[column.id] === 'number'
|
|
54
|
+
? { width: `${width}px`, minWidth: `${width}px` }
|
|
55
|
+
: {}),
|
|
42
56
|
textAlign: rest?.align,
|
|
43
57
|
position: fixed ? ('sticky' as const) : undefined,
|
|
44
58
|
left: fixed === 'left' ? `${column.getStart('left')}px` : undefined,
|
|
@@ -75,6 +89,10 @@ export class RenderTable<TData extends {} = {}> extends BeanRenderBase {
|
|
|
75
89
|
onChange={table.getToggleAllPageRowsSelectedHandler()}
|
|
76
90
|
/>
|
|
77
91
|
)
|
|
92
|
+
) : this.compact && this.isOperationsColumn(column.id) ? (
|
|
93
|
+
<span class="sr-only">
|
|
94
|
+
<FlexRender render={headerDefinition} props={header.getContext()}></FlexRender>
|
|
95
|
+
</span>
|
|
78
96
|
) : (
|
|
79
97
|
<FlexRender render={headerDefinition} props={header.getContext()}></FlexRender>
|
|
80
98
|
);
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
export default {
|
|
2
2
|
Create: 'Create',
|
|
3
|
+
Edit: 'Edit',
|
|
4
|
+
Delete: 'Delete',
|
|
5
|
+
MoreActions: 'More actions',
|
|
6
|
+
RowActions: 'Row actions',
|
|
3
7
|
DeleteConfirm: 'Are you sure you want to delete this item?',
|
|
4
8
|
SelectAllPageRows: 'Select all rows on this page',
|
|
5
9
|
SelectRow: 'Select row %s',
|