cabloy 5.1.247 → 5.1.249
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/.vscode/settings.json +1 -2
- package/CHANGELOG.md +27 -0
- package/package.json +1 -7
- package/repo-docs/frontend/column-configuration-guide.md +3 -1
- package/repo-docs/frontend/table-guide.md +27 -2
- package/repo-e2e/specs/a-commerce.spec.ts +32 -0
- package/repo-e2e/specs/cabloy-basic.spec.ts +1236 -0
- package/vona/package.original.json +1 -7
- package/vona/pnpm-lock.yaml +139 -1011
- package/zova/package.original.json +0 -6
- package/zova/packages-cli/cli/package.json +2 -2
- package/zova/packages-cli/cli-set-front/package.json +2 -2
- package/zova/packages-utils/zova-vite/package.json +1 -1
- package/zova/packages-utils/zova-vite/src/teleports.ts +7 -0
- package/zova/packages-utils/zova-vite/test/teleports.test.ts +114 -0
- package/zova/packages-zova/zova/package.json +2 -2
- package/zova/pnpm-lock.yaml +6 -958
- package/zova/src/suite/a-home/modules/home-layoutadmin/src/component/layoutAdmin/controller.tsx +267 -0
- package/zova/src/suite/a-home/modules/home-layoutadmin/src/component/layoutAdmin/render.header.tsx +42 -10
- package/zova/src/suite/a-home/modules/home-layoutadmin/src/component/layoutAdmin/render.locale.tsx +10 -5
- package/zova/src/suite/a-home/modules/home-layoutadmin/src/component/layoutAdmin/render.tabs.tsx +97 -11
- package/zova/src/suite/a-home/modules/home-layoutadmin/src/component/layoutAdmin/render.theme.tsx +19 -10
- package/zova/src/suite/a-home/modules/home-layoutadmin/src/component/layoutAdmin/render.user.tsx +11 -0
- package/zova/src/suite/a-home/modules/home-layoutadmin/src/component/layoutAdmin/style.ts +128 -0
- package/zova/src/suite/a-home/modules/home-layoutadmin/src/config/locale/en-us.ts +6 -0
- package/zova/src/suite/a-home/modules/home-layoutadmin/src/config/locale/zh-cn.ts +6 -0
- package/zova/src/suite/a-home/modules/home-layoutweb/src/.metadata/component/layoutWeb.ts +10 -8
- package/zova/src/suite/a-home/modules/home-layoutweb/src/.metadata/index.ts +25 -0
- package/zova/src/suite/a-home/modules/home-layoutweb/src/component/layoutWeb/controller.tsx +83 -0
- package/zova/src/suite/a-home/modules/home-layoutweb/src/component/layoutWeb/render.header.tsx +37 -8
- package/zova/src/suite/a-home/modules/home-layoutweb/src/component/layoutWeb/render.locale.tsx +25 -8
- package/zova/src/suite/a-home/modules/home-layoutweb/src/component/layoutWeb/render.tabs.tsx +3 -1
- package/zova/src/suite/a-home/modules/home-layoutweb/src/component/layoutWeb/render.theme.tsx +10 -5
- package/zova/src/suite/a-home/modules/home-layoutweb/src/component/layoutWeb/render.user.tsx +64 -21
- package/zova/src/suite/a-home/modules/home-layoutweb/src/component/layoutWeb/style.ts +42 -0
- package/zova/src/suite/a-home/modules/home-layoutweb/src/config/locale/en-us.ts +4 -0
- package/zova/src/suite/a-home/modules/home-layoutweb/src/config/locale/zh-cn.ts +4 -0
- package/zova/src/suite/cabloy-basic/modules/basic-table/src/component/rowOperations/controller.tsx +2 -1
- package/zova/src/suite/cabloy-basic/modules/basic-table/src/component/table/controller.tsx +6 -10
- package/zova/src/suite/cabloy-basic/modules/basic-table/src/component/table/render.tsx +9 -6
- package/zova/src/suite-vendor/a-zova/modules/a-ssr/package.json +1 -1
- package/zova/src/suite-vendor/a-zova/modules/a-ssr/src/lib/ssr.ts +17 -1
- package/zova/src/suite-vendor/a-zova/modules/a-ssr/src/lib/ssrProfile.ts +15 -0
- package/zova/src/suite-vendor/a-zova/modules/a-ssr/src/types/ssr.ts +1 -0
- package/zova/src/suite-vendor/a-zova/modules/a-ssr/test/lib/ssrProfile.test.ts +23 -1
- package/zova/src/suite-vendor/a-zova/modules/a-ssrserver/package.json +1 -1
- package/zova/src/suite-vendor/a-zova/modules/a-ssrserver/src/service/ssrHandler.ts +11 -11
- package/zova/src/suite-vendor/a-zova/package.json +3 -3
- package/vona/.textlintrc.json +0 -19
package/zova/src/suite/a-home/modules/home-layoutadmin/src/component/layoutAdmin/controller.tsx
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { ModelTabs, ModelTabsOptions } from 'zova-module-a-routertabs';
|
|
2
2
|
|
|
3
|
+
import { nextTick, useId } from 'vue';
|
|
3
4
|
import { BeanControllerBase, Use, usePrepareArg } from 'zova';
|
|
4
5
|
import { Controller } from 'zova-module-a-bean';
|
|
5
6
|
import { $QueryEnsureLoaded } from 'zova-module-a-model';
|
|
@@ -40,14 +41,95 @@ export class ControllerLayoutAdmin extends BeanControllerBase {
|
|
|
40
41
|
private viewportWidth: number = 0;
|
|
41
42
|
private _windowResizeHandler?: () => void;
|
|
42
43
|
|
|
44
|
+
headerActionsOpen = false;
|
|
45
|
+
headerActionsId = 'home-layoutadmin-header-actions';
|
|
46
|
+
private _headerActionsElement?: HTMLDivElement;
|
|
47
|
+
private _headerOutsideHandler: (event: PointerEvent) => void;
|
|
48
|
+
private _headerClickHandler: (event: MouseEvent) => void;
|
|
49
|
+
|
|
50
|
+
tabItemsWidth: number | undefined = undefined;
|
|
51
|
+
tabItemsOverflowStart = false;
|
|
52
|
+
tabItemsOverflowEnd = false;
|
|
53
|
+
tabItemsMenuOpen = false;
|
|
54
|
+
tabItemsMenuId: string;
|
|
55
|
+
private _tabItemsElement?: HTMLDivElement;
|
|
56
|
+
private _tabItemsMenuElement?: HTMLDivElement;
|
|
57
|
+
private _tabItemsObserver?: ResizeObserver;
|
|
58
|
+
private _tabItemsMounted = false;
|
|
59
|
+
private _tabItemsMenuFrame?: number;
|
|
60
|
+
private _tabItemsOnScroll: () => void;
|
|
61
|
+
private _tabItemsOnToggle: (event: ToggleEvent) => void;
|
|
62
|
+
private _tabItemsOnViewport: (event: Event) => void;
|
|
63
|
+
private _tabItemsOnKeydown: (event: KeyboardEvent) => void;
|
|
64
|
+
|
|
65
|
+
get tabItemsCompact() {
|
|
66
|
+
return this.tabItemsWidth !== undefined && this.tabItemsWidth < 240;
|
|
67
|
+
}
|
|
68
|
+
|
|
43
69
|
protected async __init__() {
|
|
70
|
+
this.tabItemsMenuId = this.$composable(useId);
|
|
71
|
+
this._tabItemsOnScroll = () => this.measureTabItems();
|
|
72
|
+
this._tabItemsOnToggle = event => this.onTabItemsMenuToggle(event);
|
|
73
|
+
this._tabItemsOnViewport = event => {
|
|
74
|
+
if (event.target instanceof Node && this._tabItemsMenuElement?.contains(event.target)) return;
|
|
75
|
+
this.closeTabItemsMenu();
|
|
76
|
+
};
|
|
77
|
+
this._tabItemsOnKeydown = event => {
|
|
78
|
+
if (event.key !== 'Escape' || event.defaultPrevented || !this.tabItemsMenuOpen) return;
|
|
79
|
+
const active = this._tabItemsMenuElement?.ownerDocument.activeElement;
|
|
80
|
+
const trigger = this._tabItemsElement?.querySelector('[data-testid="tab-items-more"]');
|
|
81
|
+
if (active?.closest('dialog[open], [role="dialog"]')) return;
|
|
82
|
+
if (
|
|
83
|
+
active !== document.body &&
|
|
84
|
+
active !== trigger &&
|
|
85
|
+
!this._tabItemsMenuElement?.contains(active ?? null)
|
|
86
|
+
) {
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
event.preventDefault();
|
|
90
|
+
event.stopPropagation();
|
|
91
|
+
this.closeTabItemsMenu(true);
|
|
92
|
+
};
|
|
93
|
+
this.$controllerMounted(() => {
|
|
94
|
+
this.$ssr.handleDirectOrOnHydrated(() => {
|
|
95
|
+
if (this.ctx.disposed) return;
|
|
96
|
+
this._tabItemsMounted = true;
|
|
97
|
+
this.observeTabItems();
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
this.$watch(
|
|
101
|
+
() => [
|
|
102
|
+
this.$$modelTabs?.tabKeyCurrent,
|
|
103
|
+
this.$$modelTabs?.componentKeyCurrent,
|
|
104
|
+
this.$$modelTabs?.tabCurrent?.items?.map(item => [
|
|
105
|
+
item.componentKey,
|
|
106
|
+
item.pageMeta?.pageTitle,
|
|
107
|
+
item.pageMeta?.pageDirty,
|
|
108
|
+
item.pageMeta?.formMeta?.formScene,
|
|
109
|
+
]),
|
|
110
|
+
this.tabItemsCompact,
|
|
111
|
+
],
|
|
112
|
+
([tabKey], [previousTabKey]) => {
|
|
113
|
+
if (tabKey !== previousTabKey) this.closeTabItemsMenu();
|
|
114
|
+
void nextTick(() => this.observeTabItems());
|
|
115
|
+
},
|
|
116
|
+
);
|
|
117
|
+
this._headerClickHandler = event => this.onHeaderActionsClick(event);
|
|
118
|
+
this._headerOutsideHandler = event => {
|
|
119
|
+
if (event.target instanceof Node && !this._headerActionsElement?.contains(event.target)) {
|
|
120
|
+
this.closeHeaderActions();
|
|
121
|
+
}
|
|
122
|
+
};
|
|
44
123
|
// viewport
|
|
45
124
|
if (process.env.CLIENT) {
|
|
46
125
|
this._windowResizeHandler = () => {
|
|
47
126
|
this.viewportWidth = document.documentElement.clientWidth;
|
|
127
|
+
this.closeHeaderActions();
|
|
128
|
+
this.closeTabItemsMenu();
|
|
48
129
|
};
|
|
49
130
|
this._windowResizeHandler();
|
|
50
131
|
window.addEventListener('resize', this._windowResizeHandler);
|
|
132
|
+
document.addEventListener('pointerdown', this._headerOutsideHandler);
|
|
51
133
|
}
|
|
52
134
|
// belowBreakpoint
|
|
53
135
|
this.belowBreakpoint = this.$computed(() => {
|
|
@@ -82,9 +164,194 @@ export class ControllerLayoutAdmin extends BeanControllerBase {
|
|
|
82
164
|
await this._initTabs();
|
|
83
165
|
}
|
|
84
166
|
|
|
167
|
+
setTabItemsElement(element?: HTMLDivElement) {
|
|
168
|
+
if (element === this._tabItemsElement) return;
|
|
169
|
+
this._tabItemsObserver?.disconnect();
|
|
170
|
+
this._tabItemsElement?.removeEventListener('scroll', this._tabItemsOnScroll, true);
|
|
171
|
+
this._tabItemsElement = element;
|
|
172
|
+
element?.addEventListener('scroll', this._tabItemsOnScroll, true);
|
|
173
|
+
this.observeTabItems();
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
private observeTabItems() {
|
|
177
|
+
const element = this._tabItemsElement;
|
|
178
|
+
if (!this._tabItemsMounted || this.ctx.disposed || !element) return;
|
|
179
|
+
this._tabItemsObserver?.disconnect();
|
|
180
|
+
this._tabItemsObserver = new ResizeObserver(() => {
|
|
181
|
+
this.measureTabItems();
|
|
182
|
+
this.revealTabItem();
|
|
183
|
+
});
|
|
184
|
+
this._tabItemsObserver.observe(element);
|
|
185
|
+
const content = element.querySelector('.tab-items-content');
|
|
186
|
+
if (content) this._tabItemsObserver.observe(content);
|
|
187
|
+
this.measureTabItems();
|
|
188
|
+
this.revealTabItem();
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
private measureTabItems() {
|
|
192
|
+
const element = this._tabItemsElement;
|
|
193
|
+
if (this.ctx.disposed || !element) return;
|
|
194
|
+
this.tabItemsWidth = element.clientWidth;
|
|
195
|
+
const scroller = element.querySelector('.tab-items-scroll');
|
|
196
|
+
const content = element.querySelector('.tab-items-content');
|
|
197
|
+
const bounds = scroller?.getBoundingClientRect();
|
|
198
|
+
const items = content?.getBoundingClientRect();
|
|
199
|
+
this.tabItemsOverflowStart = !!bounds && !!items && items.left < bounds.left - 1;
|
|
200
|
+
this.tabItemsOverflowEnd = !!bounds && !!items && items.right > bounds.right + 1;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
private revealTabItem() {
|
|
204
|
+
const element = this._tabItemsElement;
|
|
205
|
+
const scroller = element?.querySelector('.tab-items-scroll');
|
|
206
|
+
const active = scroller?.querySelector('[aria-selected="true"]');
|
|
207
|
+
if (!scroller || !active || this.ctx.disposed) return;
|
|
208
|
+
const bounds = scroller.getBoundingClientRect();
|
|
209
|
+
const item = active.getBoundingClientRect();
|
|
210
|
+
const delta =
|
|
211
|
+
item.left < bounds.left ? item.left - bounds.left : Math.max(0, item.right - bounds.right);
|
|
212
|
+
if (delta) scroller.scrollBy({ left: delta, behavior: 'instant' });
|
|
213
|
+
this.measureTabItems();
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
setTabItemsMenuElement(element?: HTMLDivElement) {
|
|
217
|
+
if (element === this._tabItemsMenuElement) return;
|
|
218
|
+
this.closeTabItemsMenu();
|
|
219
|
+
this._tabItemsMenuElement?.removeEventListener('beforetoggle', this._tabItemsOnToggle);
|
|
220
|
+
this._tabItemsMenuElement = element;
|
|
221
|
+
element?.addEventListener('beforetoggle', this._tabItemsOnToggle);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
closeTabItemsMenu(restoreFocus = false) {
|
|
225
|
+
const element = this._tabItemsMenuElement;
|
|
226
|
+
if (this.tabItemsMenuOpen && element?.isConnected) element.hidePopover();
|
|
227
|
+
this.clearTabItemsMenu();
|
|
228
|
+
if (restoreFocus) {
|
|
229
|
+
this._tabItemsElement
|
|
230
|
+
?.querySelector<HTMLButtonElement>('[data-testid="tab-items-more"]')
|
|
231
|
+
?.focus({ preventScroll: true });
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
private clearTabItemsMenu() {
|
|
236
|
+
this.tabItemsMenuOpen = false;
|
|
237
|
+
if (this._tabItemsMenuFrame !== undefined) {
|
|
238
|
+
window.cancelAnimationFrame(this._tabItemsMenuFrame);
|
|
239
|
+
this._tabItemsMenuFrame = undefined;
|
|
240
|
+
}
|
|
241
|
+
if (process.env.CLIENT) {
|
|
242
|
+
window.removeEventListener('scroll', this._tabItemsOnViewport, true);
|
|
243
|
+
document.removeEventListener('keydown', this._tabItemsOnKeydown);
|
|
244
|
+
}
|
|
245
|
+
this._tabItemsMenuElement?.style.removeProperty('visibility');
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
private onTabItemsMenuToggle(event: ToggleEvent) {
|
|
249
|
+
if (event.target !== this._tabItemsMenuElement) return;
|
|
250
|
+
if (event.newState !== 'open') {
|
|
251
|
+
this.clearTabItemsMenu();
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
this.closeHeaderActions();
|
|
255
|
+
this.tabItemsMenuOpen = true;
|
|
256
|
+
const element = this._tabItemsMenuElement!;
|
|
257
|
+
element.style.visibility = 'hidden';
|
|
258
|
+
this._tabItemsMenuFrame = window.requestAnimationFrame(() => {
|
|
259
|
+
this._tabItemsMenuFrame = undefined;
|
|
260
|
+
if (!this.tabItemsMenuOpen) return;
|
|
261
|
+
this.positionTabItemsMenu();
|
|
262
|
+
element.style.removeProperty('visibility');
|
|
263
|
+
element.querySelector<HTMLButtonElement>('.tab-items-select')?.focus({ preventScroll: true });
|
|
264
|
+
window.addEventListener('scroll', this._tabItemsOnViewport, true);
|
|
265
|
+
});
|
|
266
|
+
document.addEventListener('keydown', this._tabItemsOnKeydown);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
private positionTabItemsMenu() {
|
|
270
|
+
const element = this._tabItemsMenuElement;
|
|
271
|
+
const trigger = this._tabItemsElement?.querySelector('[data-testid="tab-items-more"]');
|
|
272
|
+
if (!element || !trigger) return;
|
|
273
|
+
const anchor = trigger.getBoundingClientRect();
|
|
274
|
+
const popup = element.getBoundingClientRect();
|
|
275
|
+
const viewport = element.ownerDocument.documentElement;
|
|
276
|
+
element.style.left = `${Math.max(8, Math.min(anchor.right - popup.width, viewport.clientWidth - popup.width - 8))}px`;
|
|
277
|
+
element.style.top = `${Math.max(8, Math.min(anchor.bottom + 4, viewport.clientHeight - popup.height - 8))}px`;
|
|
278
|
+
}
|
|
279
|
+
|
|
85
280
|
protected __dispose__() {
|
|
281
|
+
this._tabItemsMounted = false;
|
|
282
|
+
this.setTabItemsElement();
|
|
283
|
+
this.setTabItemsMenuElement();
|
|
284
|
+
this.setHeaderActionsElement();
|
|
86
285
|
if (this._windowResizeHandler) {
|
|
87
286
|
window.removeEventListener('resize', this._windowResizeHandler);
|
|
287
|
+
document.removeEventListener('pointerdown', this._headerOutsideHandler);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
setHeaderActionsElement(element?: HTMLDivElement) {
|
|
292
|
+
if (element === this._headerActionsElement) return;
|
|
293
|
+
this._headerActionsElement?.removeEventListener('click', this._headerClickHandler, true);
|
|
294
|
+
this._headerActionsElement = element;
|
|
295
|
+
element?.addEventListener('click', this._headerClickHandler, true);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
toggleHeaderActions() {
|
|
299
|
+
if (this.headerActionsOpen) {
|
|
300
|
+
this.closeHeaderActions();
|
|
301
|
+
} else {
|
|
302
|
+
this.headerActionsOpen = true;
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
closeHeaderActions(restoreFocus = false) {
|
|
307
|
+
this.headerActionsOpen = false;
|
|
308
|
+
this._headerActionsElement
|
|
309
|
+
?.querySelectorAll<HTMLDetailsElement>('details[open]')
|
|
310
|
+
.forEach(details => {
|
|
311
|
+
details.open = false;
|
|
312
|
+
});
|
|
313
|
+
if (restoreFocus) {
|
|
314
|
+
this._headerActionsElement?.querySelector<HTMLButtonElement>('button')?.focus();
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
onHeaderActionsFocusout(event: FocusEvent) {
|
|
319
|
+
if (
|
|
320
|
+
this.headerActionsOpen &&
|
|
321
|
+
(!(event.relatedTarget instanceof Node) ||
|
|
322
|
+
!this._headerActionsElement?.contains(event.relatedTarget))
|
|
323
|
+
) {
|
|
324
|
+
this.closeHeaderActions();
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
onHeaderActionsClick(event: MouseEvent) {
|
|
329
|
+
if (!this.headerActionsOpen || !(event.target instanceof Element)) return;
|
|
330
|
+
const action = event.target.closest('a, button');
|
|
331
|
+
if (action?.closest('details') && !action.matches(':disabled, [aria-disabled="true"]')) {
|
|
332
|
+
this.closeHeaderActions(true);
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
onHeaderActionsKeydown(event: KeyboardEvent) {
|
|
337
|
+
if (
|
|
338
|
+
event.key !== 'Escape' ||
|
|
339
|
+
event.defaultPrevented ||
|
|
340
|
+
!(event.target instanceof Element) ||
|
|
341
|
+
event.target.closest('dialog[open], [role="dialog"]')
|
|
342
|
+
) {
|
|
343
|
+
return;
|
|
344
|
+
}
|
|
345
|
+
const details = event.target.closest<HTMLDetailsElement>('details[open]');
|
|
346
|
+
if (details) {
|
|
347
|
+
event.preventDefault();
|
|
348
|
+
event.stopPropagation();
|
|
349
|
+
details.open = false;
|
|
350
|
+
details.querySelector<HTMLElement>('summary')?.focus();
|
|
351
|
+
} else if (this.headerActionsOpen) {
|
|
352
|
+
event.preventDefault();
|
|
353
|
+
event.stopPropagation();
|
|
354
|
+
this.closeHeaderActions(true);
|
|
88
355
|
}
|
|
89
356
|
}
|
|
90
357
|
|
package/zova/src/suite/a-home/modules/home-layoutadmin/src/component/layoutAdmin/render.header.tsx
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
import { classes } from 'typestyle';
|
|
1
2
|
import { BeanRenderBase, Use } from 'zova';
|
|
2
3
|
import { Render } from 'zova-module-a-bean';
|
|
4
|
+
import { ZIcon } from 'zova-module-a-icon';
|
|
3
5
|
|
|
4
6
|
import type { RenderLayoutAdmin } from './render.tsx';
|
|
5
7
|
|
|
@@ -11,7 +13,7 @@ export class RenderHeader extends BeanRenderBase {
|
|
|
11
13
|
public render() {
|
|
12
14
|
return (
|
|
13
15
|
<>
|
|
14
|
-
<div class=
|
|
16
|
+
<div class={classes('navbar bg-base-300 w-full', this.cHeader)}>
|
|
15
17
|
<div class="flex-none">
|
|
16
18
|
<button
|
|
17
19
|
class="btn btn-square btn-ghost"
|
|
@@ -34,15 +36,45 @@ export class RenderHeader extends BeanRenderBase {
|
|
|
34
36
|
</svg>
|
|
35
37
|
</button>
|
|
36
38
|
</div>
|
|
37
|
-
<div class="text-xl px-4">{this.sys.env.APP_TITLE}</div>
|
|
38
|
-
<div class="mx-2 min-w-0 flex-2 px-2">
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
39
|
+
<div class="header-title text-xl px-4">{this.sys.env.APP_TITLE}</div>
|
|
40
|
+
<div class="header-navigation mx-2 min-w-0 flex-2 px-2">
|
|
41
|
+
{this.$$r.$$renderTabs.renderTabItems()}
|
|
42
|
+
</div>
|
|
43
|
+
<div
|
|
44
|
+
ref={ref => {
|
|
45
|
+
if (!this.ctx.disposed)
|
|
46
|
+
this.setHeaderActionsElement?.((ref as HTMLDivElement | null) ?? undefined);
|
|
47
|
+
}}
|
|
48
|
+
class={classes('relative flex-none', this.cHeaderActions)}
|
|
49
|
+
data-testid="header-actions"
|
|
50
|
+
onFocusout={event => this.onHeaderActionsFocusout(event)}
|
|
51
|
+
onKeydown={event => this.onHeaderActionsKeydown(event)}
|
|
52
|
+
>
|
|
53
|
+
<button
|
|
54
|
+
type="button"
|
|
55
|
+
class="btn btn-square btn-ghost lg:hidden"
|
|
56
|
+
aria-label={this.scope.locale.MoreActions()}
|
|
57
|
+
aria-expanded={this.headerActionsOpen}
|
|
58
|
+
aria-controls={this.headerActionsId}
|
|
59
|
+
onClick={() => this.toggleHeaderActions()}
|
|
60
|
+
>
|
|
61
|
+
<ZIcon name="::more-horiz" width={24} aria-hidden="true" />
|
|
62
|
+
</button>
|
|
63
|
+
<div
|
|
64
|
+
id={this.headerActionsId}
|
|
65
|
+
class={classes(
|
|
66
|
+
'header-actions-panel absolute right-0 top-full z-30 rounded-box bg-base-100 shadow-lg lg:static lg:bg-transparent lg:shadow-none',
|
|
67
|
+
this.headerActionsOpen ? 'block' : 'hidden',
|
|
68
|
+
'lg:block',
|
|
69
|
+
)}
|
|
70
|
+
>
|
|
71
|
+
<ul class="menu w-full lg:w-auto lg:menu-horizontal">
|
|
72
|
+
{this.$$r.$$renderLocale.render()}
|
|
73
|
+
{this.$$r.$$renderTheme.renderThemeDark()}
|
|
74
|
+
{this.$$r.$$renderTheme.renderThemeName()}
|
|
75
|
+
{this.$$r.$$renderUser.render()}
|
|
76
|
+
</ul>
|
|
77
|
+
</div>
|
|
46
78
|
</div>
|
|
47
79
|
</div>
|
|
48
80
|
<div class="navbar bg-base-300 w-full">{this.$$r.$$renderTabs.renderTabs()}</div>
|
package/zova/src/suite/a-home/modules/home-layoutadmin/src/component/layoutAdmin/render.locale.tsx
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { BeanRenderBase, ClientOnly } from 'zova';
|
|
2
2
|
import { Render } from 'zova-module-a-bean';
|
|
3
3
|
import { $icon } from 'zova-module-a-icon';
|
|
4
|
+
import { ZButton } from 'zova-module-basic-button';
|
|
4
5
|
import { closeNearestDetails } from 'zova-module-home-base';
|
|
5
6
|
|
|
6
7
|
@Render()
|
|
@@ -10,22 +11,26 @@ export class RenderLocale extends BeanRenderBase {
|
|
|
10
11
|
return (
|
|
11
12
|
<li>
|
|
12
13
|
<details>
|
|
13
|
-
<summary
|
|
14
|
+
<summary aria-label={this.scope.locale.Language()} title={this.scope.locale.Language()}>
|
|
15
|
+
{$icon('::language', 24)}
|
|
16
|
+
<span class="lg:hidden">{this.scope.locale.Language()}</span>
|
|
17
|
+
</summary>
|
|
14
18
|
<ClientOnly>
|
|
15
19
|
<ul class="bg-base-100 rounded-t-none p-2 w-48">
|
|
16
20
|
{Object.keys(locales).map(key => {
|
|
17
21
|
const title = this.$scopeBase.locale[locales[key]]();
|
|
18
22
|
return (
|
|
19
23
|
<li key={key} class={this.app.meta.locale.current === key ? 'disabled' : ''}>
|
|
20
|
-
<
|
|
21
|
-
|
|
24
|
+
<ZButton
|
|
25
|
+
disabled={this.app.meta.locale.current === key}
|
|
26
|
+
onPerform={event => {
|
|
22
27
|
this.app.meta.locale.current = key as any;
|
|
23
|
-
closeNearestDetails(event);
|
|
28
|
+
closeNearestDetails({ currentTarget: event.target });
|
|
24
29
|
}}
|
|
25
30
|
>
|
|
26
31
|
{$icon(this.app.meta.locale.current === key ? '::done' : '::none', 24)}
|
|
27
32
|
{title}
|
|
28
|
-
</
|
|
33
|
+
</ZButton>
|
|
29
34
|
</li>
|
|
30
35
|
);
|
|
31
36
|
})}
|
package/zova/src/suite/a-home/modules/home-layoutadmin/src/component/layoutAdmin/render.tabs.tsx
CHANGED
|
@@ -6,6 +6,7 @@ import { Render } from 'zova-module-a-bean';
|
|
|
6
6
|
import { $iconName, IIconRecord, ZIcon } from 'zova-module-a-icon';
|
|
7
7
|
import { IRouteViewRouteItem } from 'zova-module-a-router';
|
|
8
8
|
import { RouteTab, ZRouterViewTabs } from 'zova-module-a-routertabs';
|
|
9
|
+
import { ZButton } from 'zova-module-basic-button';
|
|
9
10
|
|
|
10
11
|
@Render()
|
|
11
12
|
export class RenderTabs extends BeanRenderBase {
|
|
@@ -77,22 +78,31 @@ export class RenderTabs extends BeanRenderBase {
|
|
|
77
78
|
return <ClientOnly>{domWrapper}</ClientOnly>;
|
|
78
79
|
}
|
|
79
80
|
|
|
81
|
+
const tabItems = tabCurrent.items.filter(item => item.componentKey !== tabKey);
|
|
82
|
+
if (!tabItems.length) return;
|
|
83
|
+
const visibleItems = this.tabItemsCompact
|
|
84
|
+
? tabItems.filter(item => item.componentKey === $$modelTabs.componentKeyCurrent)
|
|
85
|
+
: tabItems;
|
|
80
86
|
const domTabs: VNode[] = [];
|
|
81
|
-
for (const tabItem of
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
const
|
|
85
|
-
const
|
|
86
|
-
componentKey === $$modelTabs.componentKeyCurrent ? 'tab-active text-primary' : '';
|
|
87
|
-
const pageContent = pageMeta?.onCustomRender
|
|
88
|
-
? pageMeta.onCustomRender(tabItem)
|
|
89
|
-
: pageMeta?.pageTitle || '';
|
|
87
|
+
for (const tabItem of visibleItems) {
|
|
88
|
+
const { componentKey } = tabItem;
|
|
89
|
+
const current = componentKey === $$modelTabs.componentKeyCurrent;
|
|
90
|
+
const className = current ? 'tab-active text-primary' : '';
|
|
91
|
+
const pageContent = this.getTabItemContent(tabItem);
|
|
90
92
|
const tabItemIcon = this.getTabItemIcon(tabItem);
|
|
91
93
|
const domTab = (
|
|
92
94
|
<a
|
|
93
95
|
key={componentKey}
|
|
94
96
|
role="tab"
|
|
97
|
+
aria-selected={current}
|
|
98
|
+
tabindex={0}
|
|
95
99
|
class={`tab flex items-center ${className} ${this.cTab}`}
|
|
100
|
+
onKeydown={event => {
|
|
101
|
+
if (event.key === 'Enter' || event.key === ' ') {
|
|
102
|
+
event.preventDefault();
|
|
103
|
+
void $$modelTabs.activeTabItem(tabKey, componentKey);
|
|
104
|
+
}
|
|
105
|
+
}}
|
|
96
106
|
onClick={() => {
|
|
97
107
|
$$modelTabs.activeTabItem(tabKey, componentKey);
|
|
98
108
|
}}
|
|
@@ -118,14 +128,90 @@ export class RenderTabs extends BeanRenderBase {
|
|
|
118
128
|
domTabs.push(domTab);
|
|
119
129
|
}
|
|
120
130
|
const domWrapper = (
|
|
121
|
-
<div
|
|
122
|
-
{
|
|
131
|
+
<div
|
|
132
|
+
class={`tab-items ${this.cTabItems} ${this.tabItemsCompact ? 'is-compact' : ''}`}
|
|
133
|
+
data-testid="tab-items"
|
|
134
|
+
data-compact={this.tabItemsCompact ? 'true' : 'false'}
|
|
135
|
+
ref={ref => {
|
|
136
|
+
if (!this.ctx.disposed)
|
|
137
|
+
this.setTabItemsElement?.((ref as HTMLDivElement | null) ?? undefined);
|
|
138
|
+
}}
|
|
139
|
+
>
|
|
140
|
+
<div
|
|
141
|
+
class={`tab-items-viewport ${this.tabItemsOverflowStart ? 'has-start' : ''} ${this.tabItemsOverflowEnd ? 'has-end' : ''}`}
|
|
142
|
+
>
|
|
143
|
+
<div class="tab-items-scroll">
|
|
144
|
+
<div role="tablist" class="tab-items-content tabs tabs-border">
|
|
145
|
+
{domTabs}
|
|
146
|
+
</div>
|
|
147
|
+
</div>
|
|
148
|
+
</div>
|
|
149
|
+
<ZButton
|
|
150
|
+
class="tab-items-more btn btn-square btn-ghost"
|
|
151
|
+
data-testid="tab-items-more"
|
|
152
|
+
aria-label={this.scope.locale.AllTabs()}
|
|
153
|
+
title={this.scope.locale.AllTabs()}
|
|
154
|
+
aria-controls={this.tabItemsMenuId}
|
|
155
|
+
aria-expanded={this.tabItemsMenuOpen}
|
|
156
|
+
{...{ 'popovertarget': this.tabItemsMenuId, 'data-hydrate-ignore-popovertarget': '' }}
|
|
157
|
+
>
|
|
158
|
+
<span aria-hidden="true">▾</span>
|
|
159
|
+
</ZButton>
|
|
160
|
+
<div
|
|
161
|
+
id={this.tabItemsMenuId}
|
|
162
|
+
ref={ref => {
|
|
163
|
+
if (!this.ctx.disposed)
|
|
164
|
+
this.setTabItemsMenuElement?.((ref as HTMLDivElement | null) ?? undefined);
|
|
165
|
+
}}
|
|
166
|
+
{...{ popover: 'auto' }}
|
|
167
|
+
class={`${this.cTabItemsMenu} bg-base-100 border border-base-300 rounded-box shadow-lg p-2`}
|
|
168
|
+
data-testid="tab-items-list"
|
|
169
|
+
role="group"
|
|
170
|
+
aria-label={this.scope.locale.AllTabs()}
|
|
171
|
+
>
|
|
172
|
+
{tabItems.map(tabItem => {
|
|
173
|
+
const current = tabItem.componentKey === $$modelTabs.componentKeyCurrent;
|
|
174
|
+
const icon = this.getTabItemIcon(tabItem);
|
|
175
|
+
return (
|
|
176
|
+
<div key={tabItem.componentKey} class="tab-items-row flex items-center gap-1">
|
|
177
|
+
<ZButton
|
|
178
|
+
class={`tab-items-select btn btn-ghost ${current ? 'bg-base-200 text-primary' : ''}`}
|
|
179
|
+
aria-current={current ? 'page' : undefined}
|
|
180
|
+
onPerform={async () => {
|
|
181
|
+
this.closeTabItemsMenu();
|
|
182
|
+
await $$modelTabs.activeTabItem(tabKey, tabItem.componentKey);
|
|
183
|
+
}}
|
|
184
|
+
>
|
|
185
|
+
{!!icon && <ZIcon name={icon} width={20} aria-hidden="true" />}
|
|
186
|
+
<span class="tab-items-label">{this.getTabItemContent(tabItem)}</span>
|
|
187
|
+
{current && <ZIcon name="::done" width={16} aria-hidden="true" />}
|
|
188
|
+
</ZButton>
|
|
189
|
+
<ZButton
|
|
190
|
+
class="btn btn-square btn-ghost shrink-0"
|
|
191
|
+
aria-label={this.scope.locale.CloseTab(tabItem.pageMeta?.pageTitle || '')}
|
|
192
|
+
title={this.scope.locale.CloseTab(tabItem.pageMeta?.pageTitle || '')}
|
|
193
|
+
onPerform={async () => {
|
|
194
|
+
await $$modelTabs.deleteTabItem(tabKey, tabItem.componentKey, false);
|
|
195
|
+
if (tabItems.length === 1) this.closeTabItemsMenu();
|
|
196
|
+
}}
|
|
197
|
+
>
|
|
198
|
+
<ZIcon name="::close" width={16} aria-hidden="true" />
|
|
199
|
+
</ZButton>
|
|
200
|
+
</div>
|
|
201
|
+
);
|
|
202
|
+
})}
|
|
203
|
+
</div>
|
|
123
204
|
</div>
|
|
124
205
|
);
|
|
125
206
|
if (!this.$$modelTabs.cache) return domWrapper;
|
|
126
207
|
return <ClientOnly>{domWrapper}</ClientOnly>;
|
|
127
208
|
}
|
|
128
209
|
|
|
210
|
+
private getTabItemContent(tabItem: IRouteViewRouteItem) {
|
|
211
|
+
const pageMeta = tabItem.pageMeta;
|
|
212
|
+
return pageMeta?.onCustomRender ? pageMeta.onCustomRender(tabItem) : pageMeta?.pageTitle || '';
|
|
213
|
+
}
|
|
214
|
+
|
|
129
215
|
public getTabIcon(tab: RouteTab) {
|
|
130
216
|
const { info, items } = tab;
|
|
131
217
|
// pageDirty
|
package/zova/src/suite/a-home/modules/home-layoutadmin/src/component/layoutAdmin/render.theme.tsx
CHANGED
|
@@ -3,6 +3,7 @@ import type { IThemeRecord } from 'zova-module-a-style';
|
|
|
3
3
|
import { BeanRenderBase, ClientOnly } from 'zova';
|
|
4
4
|
import { Render } from 'zova-module-a-bean';
|
|
5
5
|
import { $icon } from 'zova-module-a-icon';
|
|
6
|
+
import { ZButton } from 'zova-module-basic-button';
|
|
6
7
|
import { closeNearestDetails } from 'zova-module-home-base';
|
|
7
8
|
|
|
8
9
|
@Render()
|
|
@@ -25,7 +26,10 @@ export class RenderTheme extends BeanRenderBase {
|
|
|
25
26
|
return (
|
|
26
27
|
<li>
|
|
27
28
|
<details>
|
|
28
|
-
<summary
|
|
29
|
+
<summary aria-label={this.scope.locale.ColorMode()} title={this.scope.locale.ColorMode()}>
|
|
30
|
+
{$icon('::dark-theme', 24)}
|
|
31
|
+
<span class="lg:hidden">{this.scope.locale.ColorMode()}</span>
|
|
32
|
+
</summary>
|
|
29
33
|
<ClientOnly>
|
|
30
34
|
<ul class="bg-base-100 rounded-t-none p-2 w-48">
|
|
31
35
|
{themes.map(item => {
|
|
@@ -34,15 +38,16 @@ export class RenderTheme extends BeanRenderBase {
|
|
|
34
38
|
key={item.mode.toString()}
|
|
35
39
|
class={this.$theme.darkMode === item.mode ? 'disabled' : ''}
|
|
36
40
|
>
|
|
37
|
-
<
|
|
38
|
-
|
|
41
|
+
<ZButton
|
|
42
|
+
disabled={this.$theme.darkMode === item.mode}
|
|
43
|
+
onPerform={event => {
|
|
39
44
|
this.$theme.darkMode = item.mode as any;
|
|
40
|
-
closeNearestDetails(event);
|
|
45
|
+
closeNearestDetails({ currentTarget: event.target });
|
|
41
46
|
}}
|
|
42
47
|
>
|
|
43
48
|
{$icon(this.$theme.darkMode === item.mode ? '::done' : '::none', 24)}
|
|
44
49
|
{item.title}
|
|
45
|
-
</
|
|
50
|
+
</ZButton>
|
|
46
51
|
</li>
|
|
47
52
|
);
|
|
48
53
|
})}
|
|
@@ -58,21 +63,25 @@ export class RenderTheme extends BeanRenderBase {
|
|
|
58
63
|
return (
|
|
59
64
|
<li>
|
|
60
65
|
<details>
|
|
61
|
-
<summary
|
|
66
|
+
<summary aria-label={this.scope.locale.Theme()} title={this.scope.locale.Theme()}>
|
|
67
|
+
{$icon(':outline:theme-outline', 24)}
|
|
68
|
+
<span class="lg:hidden">{this.scope.locale.Theme()}</span>
|
|
69
|
+
</summary>
|
|
62
70
|
<ClientOnly>
|
|
63
71
|
<ul class="bg-base-100 rounded-t-none p-2 w-48">
|
|
64
72
|
{themes.map(item => {
|
|
65
73
|
return (
|
|
66
74
|
<li key={item.name} class={this.$theme.name === item.name ? 'disabled' : ''}>
|
|
67
|
-
<
|
|
68
|
-
|
|
75
|
+
<ZButton
|
|
76
|
+
disabled={this.$theme.name === item.name}
|
|
77
|
+
onPerform={event => {
|
|
69
78
|
this.$theme.name = item.name as keyof IThemeRecord;
|
|
70
|
-
closeNearestDetails(event);
|
|
79
|
+
closeNearestDetails({ currentTarget: event.target });
|
|
71
80
|
}}
|
|
72
81
|
>
|
|
73
82
|
{$icon(this.$theme.name === item.name ? '::done' : '::none', 24)}
|
|
74
83
|
{item.title}
|
|
75
|
-
</
|
|
84
|
+
</ZButton>
|
|
76
85
|
</li>
|
|
77
86
|
);
|
|
78
87
|
})}
|
package/zova/src/suite/a-home/modules/home-layoutadmin/src/component/layoutAdmin/render.user.tsx
CHANGED
|
@@ -24,7 +24,18 @@ export class RenderUser extends BeanRenderBase {
|
|
|
24
24
|
<ul class="bg-base-100 rounded-t-none p-2 w-32">
|
|
25
25
|
<li>
|
|
26
26
|
<a
|
|
27
|
+
href={this.$router.getPagePath('/home/user/account')}
|
|
27
28
|
onClick={event => {
|
|
29
|
+
if (
|
|
30
|
+
event.button !== 0 ||
|
|
31
|
+
event.metaKey ||
|
|
32
|
+
event.ctrlKey ||
|
|
33
|
+
event.shiftKey ||
|
|
34
|
+
event.altKey
|
|
35
|
+
) {
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
event.preventDefault();
|
|
28
39
|
this.app.$gotoPage('/home/user/account');
|
|
29
40
|
closeNearestDetails(event);
|
|
30
41
|
}}
|