@acorex/platform 21.0.0-next.85 → 21.0.0-next.88

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 (74) hide show
  1. package/fesm2022/acorex-platform-auth.mjs +1 -1
  2. package/fesm2022/acorex-platform-auth.mjs.map +1 -1
  3. package/fesm2022/{acorex-platform-common-common-settings.provider-Ytey9uhY.mjs → acorex-platform-common-common-settings.provider-VfmZct9H.mjs} +43 -3
  4. package/fesm2022/acorex-platform-common-common-settings.provider-VfmZct9H.mjs.map +1 -0
  5. package/fesm2022/acorex-platform-common.mjs +1709 -870
  6. package/fesm2022/acorex-platform-common.mjs.map +1 -1
  7. package/fesm2022/acorex-platform-contracts.mjs +1160 -9
  8. package/fesm2022/acorex-platform-contracts.mjs.map +1 -1
  9. package/fesm2022/acorex-platform-core.mjs +210 -131
  10. package/fesm2022/acorex-platform-core.mjs.map +1 -1
  11. package/fesm2022/acorex-platform-domain-contracts.mjs +58 -0
  12. package/fesm2022/acorex-platform-domain-contracts.mjs.map +1 -0
  13. package/fesm2022/acorex-platform-domain.mjs +2 -50
  14. package/fesm2022/acorex-platform-domain.mjs.map +1 -1
  15. package/fesm2022/acorex-platform-layout-builder.mjs.map +1 -1
  16. package/fesm2022/acorex-platform-layout-components.mjs +59 -23
  17. package/fesm2022/acorex-platform-layout-components.mjs.map +1 -1
  18. package/fesm2022/acorex-platform-layout-designer.mjs +3 -2
  19. package/fesm2022/acorex-platform-layout-designer.mjs.map +1 -1
  20. package/fesm2022/acorex-platform-layout-entity-attachments-page.component-CwG6n_Yb.mjs.map +1 -1
  21. package/fesm2022/acorex-platform-layout-entity-contracts.mjs +1114 -0
  22. package/fesm2022/acorex-platform-layout-entity-contracts.mjs.map +1 -0
  23. package/fesm2022/acorex-platform-layout-entity-file-list-popup.component--IeMZLIr.mjs.map +1 -1
  24. package/fesm2022/acorex-platform-layout-entity.mjs +661 -742
  25. package/fesm2022/acorex-platform-layout-entity.mjs.map +1 -1
  26. package/fesm2022/acorex-platform-layout-views.mjs +333 -119
  27. package/fesm2022/acorex-platform-layout-views.mjs.map +1 -1
  28. package/fesm2022/acorex-platform-layout-widget-core.mjs +11 -105
  29. package/fesm2022/acorex-platform-layout-widget-core.mjs.map +1 -1
  30. package/fesm2022/acorex-platform-layout-widgets-repeater-widget-column.component-CFyfix31.mjs +513 -0
  31. package/fesm2022/acorex-platform-layout-widgets-repeater-widget-column.component-CFyfix31.mjs.map +1 -0
  32. package/fesm2022/{acorex-platform-layout-widgets-tabular-data-edit-popup.component-DjpZU6gz.mjs → acorex-platform-layout-widgets-tabular-data-edit-popup.component-D4MOHEpI.mjs} +4 -4
  33. package/fesm2022/acorex-platform-layout-widgets-tabular-data-edit-popup.component-D4MOHEpI.mjs.map +1 -0
  34. package/fesm2022/acorex-platform-layout-widgets.mjs +868 -57
  35. package/fesm2022/acorex-platform-layout-widgets.mjs.map +1 -1
  36. package/fesm2022/acorex-platform-runtime.mjs.map +1 -1
  37. package/fesm2022/{acorex-platform-themes-default-entity-master-single-view.component-Br9p5aXT.mjs → acorex-platform-themes-default-entity-master-single-view.component-DrUwqKKA.mjs} +2 -3
  38. package/fesm2022/acorex-platform-themes-default-entity-master-single-view.component-DrUwqKKA.mjs.map +1 -0
  39. package/fesm2022/acorex-platform-themes-default.mjs +740 -96
  40. package/fesm2022/acorex-platform-themes-default.mjs.map +1 -1
  41. package/fesm2022/{acorex-platform-themes-shared-settings.provider-BjuzSe0T.mjs → acorex-platform-themes-shared-settings.provider-BUwPqwUS.mjs} +3 -3
  42. package/fesm2022/acorex-platform-themes-shared-settings.provider-BUwPqwUS.mjs.map +1 -0
  43. package/fesm2022/acorex-platform-themes-shared.mjs +4 -3
  44. package/fesm2022/acorex-platform-themes-shared.mjs.map +1 -1
  45. package/fesm2022/acorex-platform-workflow.mjs +6 -8
  46. package/fesm2022/acorex-platform-workflow.mjs.map +1 -1
  47. package/package.json +9 -5
  48. package/types/acorex-platform-auth.d.ts +2 -1
  49. package/types/acorex-platform-common.d.ts +419 -1083
  50. package/types/acorex-platform-contracts.d.ts +1791 -260
  51. package/types/acorex-platform-core.d.ts +2153 -2806
  52. package/types/acorex-platform-domain-contracts.d.ts +416 -0
  53. package/types/acorex-platform-domain.d.ts +4 -415
  54. package/types/acorex-platform-layout-builder.d.ts +2 -1
  55. package/types/acorex-platform-layout-components.d.ts +15 -4
  56. package/types/acorex-platform-layout-designer.d.ts +2 -1
  57. package/types/acorex-platform-layout-entity-contracts.d.ts +864 -0
  58. package/types/acorex-platform-layout-entity.d.ts +88 -269
  59. package/types/acorex-platform-layout-views.d.ts +48 -14
  60. package/types/acorex-platform-layout-widget-core.d.ts +9 -5
  61. package/types/acorex-platform-layout-widgets.d.ts +89 -13
  62. package/types/acorex-platform-runtime.d.ts +1 -1
  63. package/types/acorex-platform-themes-default.d.ts +173 -70
  64. package/types/acorex-platform-workflow.d.ts +5 -18
  65. package/fesm2022/acorex-platform-common-common-settings.provider-Ytey9uhY.mjs.map +0 -1
  66. package/fesm2022/acorex-platform-layout-widgets-repeater-widget-column.component-J0zcGKBX.mjs +0 -116
  67. package/fesm2022/acorex-platform-layout-widgets-repeater-widget-column.component-J0zcGKBX.mjs.map +0 -1
  68. package/fesm2022/acorex-platform-layout-widgets-tabular-data-edit-popup.component-DjpZU6gz.mjs.map +0 -1
  69. package/fesm2022/acorex-platform-native.mjs +0 -155
  70. package/fesm2022/acorex-platform-native.mjs.map +0 -1
  71. package/fesm2022/acorex-platform-themes-default-entity-master-single-view.component-Br9p5aXT.mjs.map +0 -1
  72. package/fesm2022/acorex-platform-themes-shared-settings.provider-BjuzSe0T.mjs.map +0 -1
  73. package/native/README.md +0 -4
  74. package/types/acorex-platform-native.d.ts +0 -28
@@ -1,7 +1,7 @@
1
1
  import * as i2 from '@angular/common';
2
2
  import { CommonModule } from '@angular/common';
3
3
  import * as i0 from '@angular/core';
4
- import { inject, viewChild, afterNextRender, effect, ChangeDetectionStrategy, ViewEncapsulation, Component, signal, computed, Injectable, input, output, ViewContainerRef, Directive, DestroyRef, ElementRef, untracked } from '@angular/core';
4
+ import { inject, viewChild, afterNextRender, effect, ChangeDetectionStrategy, ViewEncapsulation, Component, signal, untracked, computed, Injectable, input, output, ViewContainerRef, Directive, DestroyRef, ElementRef } from '@angular/core';
5
5
  import { AXCommonModule } from '@acorex/cdk/common';
6
6
  import * as i4 from '@acorex/components/breadcrumbs';
7
7
  import { AXBreadcrumbsModule } from '@acorex/components/breadcrumbs';
@@ -12,9 +12,9 @@ import { AXDecoratorModule } from '@acorex/components/decorators';
12
12
  import * as i5 from '@acorex/components/dropdown';
13
13
  import { AXDropdownModule } from '@acorex/components/dropdown';
14
14
  import { AXDropdownButtonModule } from '@acorex/components/dropdown-button';
15
- import { AXPStickyDirective, AXPCommonSettings, AXPHomePageService, AXPSettingsService, axpIsEntityDetailsViewRoute, axpNavigateAppPath, AXPEntityCommandScope, AXPUnsavedChangesConfirmService, AXPUnsavedChangesPopstateService, createUnsavedChangesCanDeactivateGuard } from '@acorex/platform/common';
15
+ import { AXPTabNavigationService, AXPStickyDirective, AXPCommonSettings, AXPHomePageService, AXPSettingsService, AXPTabPreviewService, AXPUnsavedChangesConfirmService, normalizeRouterUrl, AXP_HOME_TAB_KEY, axpResolveTabKey, axpIsEntityDetailsViewRoute, axpNavigateAppPath, AXPEntityCommandScope, AXPUnsavedChangesPopstateService, createUnsavedChangesCanDeactivateGuard } from '@acorex/platform/common';
16
16
  import * as i8 from '@acorex/platform/core';
17
- import { AXPDeviceService, AXPComponentSlotModule, getSmart, AXPExpressionEvaluatorService, AXPContextStore, normalizeKeyboardShortcuts, AXPBroadcastEventService, AXPKeyboardShortcutRegistry, AXPKeyboardShortcutPriority } from '@acorex/platform/core';
17
+ import { AXPDeviceService, AXPComponentSlotModule, getSmart, AXPExpressionEvaluatorService, AXPContextEvalFactory, normalizeKeyboardShortcuts, AXPContextStore, AXPBroadcastEventService, AXPKeyboardShortcutRegistry, AXPKeyboardShortcutPriority } from '@acorex/platform/core';
18
18
  import * as i1 from '@acorex/cdk/drawer';
19
19
  import { AXDrawerDirectiveModule, AXDrawerContainerDirective } from '@acorex/cdk/drawer';
20
20
  import { AXDrawerModule } from '@acorex/components/drawer';
@@ -28,11 +28,11 @@ import { AXPWorkflowService } from '@acorex/platform/workflow';
28
28
  import { AXToastService } from '@acorex/components/toast';
29
29
  import { AXFormatService, AXFormatModule } from '@acorex/core/format';
30
30
  import { AXPSessionService } from '@acorex/platform/auth';
31
+ import { Router, ActivatedRoute } from '@angular/router';
31
32
  import { isEmpty, cloneDeep, get } from 'lodash-es';
32
33
  import { AXBasePageComponent } from '@acorex/components/page';
33
34
  import * as i7 from '@acorex/platform/layout/widget-core';
34
35
  import { AXPPageStatus, AXPWidgetContainerComponent, AXPWidgetCoreModule } from '@acorex/platform/layout/widget-core';
35
- import { Router, ActivatedRoute } from '@angular/router';
36
36
  import { signalStore, withState, withComputed, withMethods, patchState } from '@ngrx/signals';
37
37
  import { AXBadgeModule } from '@acorex/components/badge';
38
38
  import { AXButtonGroupModule } from '@acorex/components/button-group';
@@ -56,6 +56,7 @@ class AXPPageLayoutComponent {
56
56
  this.page = inject(AXPPageLayoutBase, { optional: true });
57
57
  this.layoutService = inject(AXPLayoutThemeService);
58
58
  this.deviceService = inject(AXPDeviceService);
59
+ this.tabNavigation = inject(AXPTabNavigationService);
59
60
  this.startSideDrawer = viewChild('startSideDrawer', ...(ngDevMode ? [{ debugName: "startSideDrawer" }] : /* istanbul ignore next */ []));
60
61
  this.workflow = inject(AXPWorkflowService);
61
62
  this.commandService = inject(AXPCommandService);
@@ -219,11 +220,55 @@ class AXPPageLayoutBaseComponent {
219
220
  this.homePageService = inject(AXPHomePageService);
220
221
  this.policyService = inject(AXPPolicyEngineService);
221
222
  this.settingsService = inject(AXPSettingsService);
223
+ this.tabNavigation = inject(AXPTabNavigationService);
224
+ this.tabPreviewService = inject(AXPTabPreviewService);
225
+ this.unsavedChangesConfirm = inject(AXPUnsavedChangesConfirmService);
226
+ this.router = inject(Router);
222
227
  //#endregion
223
228
  //#region ---------------- Signal to force recomputation ----------------
224
229
  this._updateTrigger = signal(0, ...(ngDevMode ? [{ debugName: "_updateTrigger" }] : /* istanbul ignore next */ []));
230
+ /** Tab key captured at page init — stable while the route instance is cached. */
231
+ this.pageTabKey = signal(null, ...(ngDevMode ? [{ debugName: "pageTabKey" }] : /* istanbul ignore next */ []));
225
232
  this.updateTrigger = this._updateTrigger.asReadonly();
226
233
  //#endregion
234
+ //#region ---------------- Tab Title Sync ----------------
235
+ /** Refreshes tab title when this cached page becomes the active tab again. */
236
+ this.#syncTabTitleOnActivate = effect(() => {
237
+ const activeKey = this.tabNavigation.activeKey();
238
+ const pageKey = this.pageTabKey();
239
+ if (!activeKey || !pageKey || activeKey !== pageKey) {
240
+ return;
241
+ }
242
+ if (this.tabNavigation.effectiveMode() !== 'tabular') {
243
+ return;
244
+ }
245
+ untracked(() => {
246
+ this.recompute();
247
+ });
248
+ }, ...(ngDevMode ? [{ debugName: "#syncTabTitleOnActivate" }] : /* istanbul ignore next */ []));
249
+ /** Publishes structured tab preview (hover card + dirty badge) for this cached page. */
250
+ this.#syncTabStructuredPreview = effect(() => {
251
+ this.updateTrigger();
252
+ const tabKey = this.pageTabKey();
253
+ const titleValue = this.title();
254
+ const descriptionValue = this.description();
255
+ const icon = this.titleIcon();
256
+ const breadcrumbItems = this.breadcrumbs();
257
+ if (!tabKey || this.tabNavigation.effectiveMode() !== 'tabular') {
258
+ return;
259
+ }
260
+ const isDirty = this.hasUnsavedChanges();
261
+ untracked(() => {
262
+ void this.publishTabStructuredPreview(tabKey, {
263
+ titleValue,
264
+ descriptionValue,
265
+ icon,
266
+ breadcrumbItems,
267
+ isDirty,
268
+ });
269
+ });
270
+ }, ...(ngDevMode ? [{ debugName: "#syncTabStructuredPreview" }] : /* istanbul ignore next */ []));
271
+ //#endregion
227
272
  //#region ---------------- Title ----------------
228
273
  this.title = signal('', ...(ngDevMode ? [{ debugName: "title" }] : /* istanbul ignore next */ []));
229
274
  this.titleIcon = signal(null, ...(ngDevMode ? [{ debugName: "titleIcon" }] : /* istanbul ignore next */ []));
@@ -232,6 +277,12 @@ class AXPPageLayoutBaseComponent {
232
277
  this.title.set(await this.getPageTitle());
233
278
  const icon = await this.getPageIcon();
234
279
  this.titleIcon.set(icon);
280
+ if (this.tabNavigation.effectiveMode() === 'tabular') {
281
+ const tabKey = this.pageTabKey();
282
+ if (tabKey && icon) {
283
+ this.tabNavigation.syncTabIcon(tabKey, icon);
284
+ }
285
+ }
235
286
  }, ...(ngDevMode ? [{ debugName: "#titleEffect" }] : /* istanbul ignore next */ []));
236
287
  this.hasTitle = computed(() => {
237
288
  const value = this.title();
@@ -285,9 +336,20 @@ class AXPPageLayoutBaseComponent {
285
336
  this.#breadcrumbsEffect = effect(async () => {
286
337
  this.updateTrigger();
287
338
  const breadCrumbs = await this.getPageBreadcrumbs();
288
- this.breadcrumbs.set([...this.defaultBreadCrumbs, ...breadCrumbs]);
339
+ const trail = [...this.defaultBreadCrumbs, ...breadCrumbs];
340
+ this.breadcrumbs.set(trail);
341
+ if (this.tabNavigation.effectiveMode() === 'tabular') {
342
+ const tabKey = this.pageTabKey();
343
+ let tabTitle = await this.resolveBreadcrumbTabTitle(breadCrumbs);
344
+ if (!tabTitle) {
345
+ tabTitle = await this.resolvePageTitleForTab();
346
+ }
347
+ if (tabKey && tabTitle) {
348
+ this.tabNavigation.syncTabTitle(tabKey, tabTitle);
349
+ }
350
+ }
289
351
  }, ...(ngDevMode ? [{ debugName: "#breadcrumbsEffect" }] : /* istanbul ignore next */ []));
290
- this.hasBreadcrumbs = computed(() => this.breadcrumbs().length > 1, ...(ngDevMode ? [{ debugName: "hasBreadcrumbs" }] : /* istanbul ignore next */ []));
352
+ this.hasBreadcrumbs = computed(() => this.breadcrumbs().length > 1 && this.tabNavigation.effectiveMode() !== 'tabular', ...(ngDevMode ? [{ debugName: "hasBreadcrumbs" }] : /* istanbul ignore next */ []));
291
353
  //#endregion
292
354
  //#region ---------------- Badge ----------------
293
355
  this.badge = signal(null, ...(ngDevMode ? [{ debugName: "badge" }] : /* istanbul ignore next */ []));
@@ -343,10 +405,75 @@ class AXPPageLayoutBaseComponent {
343
405
  //#endregion
344
406
  //#region ---------------- Lifecycle hooks ----------------
345
407
  async ngOnInit() {
408
+ this.pageTabKey.set(this.resolvePageTabKey());
346
409
  this.translateService.langChanges$.subscribe(() => {
347
410
  this.recompute();
348
411
  });
349
412
  }
413
+ /** Maps the home-page route to the pinned home tab key instead of a path-based key. */
414
+ resolvePageTabKey() {
415
+ const homePath = this.homePageService.getCurrent().path;
416
+ if (homePath && normalizeRouterUrl(homePath) === normalizeRouterUrl(this.router.url)) {
417
+ return AXP_HOME_TAB_KEY;
418
+ }
419
+ return axpResolveTabKey(this.router);
420
+ }
421
+ //#endregion
422
+ //#region ---------------- Tab Title Sync ----------------
423
+ /** Refreshes tab title when this cached page becomes the active tab again. */
424
+ #syncTabTitleOnActivate;
425
+ /** Publishes structured tab preview (hover card + dirty badge) for this cached page. */
426
+ #syncTabStructuredPreview;
427
+ async publishTabStructuredPreview(tabKey, input) {
428
+ const tab = this.tabNavigation.tabList().find((item) => item.key === tabKey);
429
+ const breadcrumbTrail = [];
430
+ for (const item of input.breadcrumbItems.slice(1)) {
431
+ const segment = await this.resolveBreadcrumbItemTitle(item);
432
+ if (segment && segment !== breadcrumbTrail[breadcrumbTrail.length - 1]) {
433
+ breadcrumbTrail.push(segment);
434
+ }
435
+ }
436
+ const fallbackTitle = await this.translateTextIfKey(breadcrumbTrail.join(' - ') || tab?.title || '');
437
+ const title = await this.resolveMultiLanguageText(input.titleValue, fallbackTitle);
438
+ const subtitle = await this.resolveMultiLanguageText(input.descriptionValue, '');
439
+ this.tabPreviewService.setPreview(tabKey, {
440
+ title,
441
+ subtitle: subtitle || undefined,
442
+ url: tab?.url ?? '',
443
+ icon: input.icon ?? tab?.icon,
444
+ isDirty: input.isDirty,
445
+ breadcrumbTrail,
446
+ });
447
+ }
448
+ async resolveMultiLanguageText(value, fallback) {
449
+ const resolvedFallback = await this.translateTextIfKey(fallback);
450
+ if (value == null || value === '') {
451
+ return resolvedFallback;
452
+ }
453
+ if (typeof value === 'string') {
454
+ const trimmed = value.trim();
455
+ if (!trimmed) {
456
+ return resolvedFallback;
457
+ }
458
+ if (trimmed.startsWith('@')) {
459
+ return (await this.translateService.translateAsync(trimmed)) || resolvedFallback;
460
+ }
461
+ return trimmed;
462
+ }
463
+ const resolved = this.translateService.resolve(value);
464
+ return resolved || resolvedFallback;
465
+ }
466
+ /** Resolves i18n keys (`@scope:key`); returns plain text unchanged. */
467
+ async translateTextIfKey(text) {
468
+ const trimmed = text.trim();
469
+ if (!trimmed) {
470
+ return '';
471
+ }
472
+ if (trimmed.startsWith('@')) {
473
+ return (await this.translateService.translateAsync(trimmed)) || trimmed;
474
+ }
475
+ return trimmed;
476
+ }
350
477
  #titleEffect;
351
478
  getPageTitle() {
352
479
  return '';
@@ -362,10 +489,78 @@ class AXPPageLayoutBaseComponent {
362
489
  getPageBreadcrumbs() {
363
490
  return [];
364
491
  }
492
+ /** Joins all page breadcrumbs (Home is excluded upstream) as the tab label. */
493
+ async resolveBreadcrumbTabTitle(pageBreadcrumbs) {
494
+ const segments = [];
495
+ for (const item of pageBreadcrumbs) {
496
+ const title = await this.resolveBreadcrumbItemTitle(item);
497
+ if (title && title !== segments[segments.length - 1]) {
498
+ segments.push(title);
499
+ }
500
+ }
501
+ if (!segments.length) {
502
+ return null;
503
+ }
504
+ return segments.join(' - ');
505
+ }
506
+ /** Resolves a breadcrumb label the same way as page chrome (`t(item.title)`). */
507
+ async resolveBreadcrumbItemTitle(item) {
508
+ const raw = item?.title;
509
+ if (raw == null || raw === '') {
510
+ return null;
511
+ }
512
+ if (typeof raw === 'string') {
513
+ const trimmed = raw.trim();
514
+ if (!trimmed) {
515
+ return null;
516
+ }
517
+ if (trimmed.startsWith('@')) {
518
+ return (await this.translateService.translateAsync(trimmed)) || trimmed;
519
+ }
520
+ return trimmed;
521
+ }
522
+ if (typeof raw === 'object' && !Array.isArray(raw)) {
523
+ if (this.translateService.isValidMultiLanguageObject(raw)) {
524
+ const resolved = this.translateService.resolve(raw);
525
+ return resolved || null;
526
+ }
527
+ const resolved = this.translateService.resolve(raw);
528
+ return resolved || null;
529
+ }
530
+ return String(raw);
531
+ }
532
+ /** Fallback tab label from the page title when breadcrumbs are not ready yet. */
533
+ async resolvePageTitleForTab() {
534
+ const raw = await Promise.resolve(this.getPageTitle());
535
+ if (raw == null || raw === '') {
536
+ return null;
537
+ }
538
+ if (typeof raw === 'string') {
539
+ const trimmed = raw.trim();
540
+ if (trimmed.startsWith('@')) {
541
+ return (await this.translateService.translateAsync(trimmed)) || trimmed;
542
+ }
543
+ return trimmed;
544
+ }
545
+ if (typeof raw === 'object' && !Array.isArray(raw)) {
546
+ const resolved = this.translateService.resolve(raw);
547
+ return resolved || null;
548
+ }
549
+ return null;
550
+ }
365
551
  #badgeEffect;
366
552
  hasUnsavedChanges() {
367
553
  return false;
368
554
  }
555
+ /**
556
+ * Confirms closing this page as a background tab (no router navigation).
557
+ * Override when dirty detection differs from {@link hasUnsavedChanges}.
558
+ */
559
+ onBeforeTabClose() {
560
+ return this.unsavedChangesConfirm.confirmIfDirty(this.hasUnsavedChanges(), {
561
+ approveLeaveOnConfirm: false,
562
+ });
563
+ }
369
564
  async getPageBadge() {
370
565
  if (!this.hasUnsavedChanges()) {
371
566
  return null;
@@ -676,45 +871,40 @@ const AXPLayoutDetailsViewViewModel = signalStore(withState(() => {
676
871
  const evaluatedByTab = store.evaluatedTabContentsById();
677
872
  return evaluatedByTab[tab.id] ?? [];
678
873
  }),
679
- })), withMethods((store, evaluatorService = inject(AXPExpressionEvaluatorService), router = inject(Router), route = inject(ActivatedRoute), deviceService = inject(AXPDeviceService), formatService = inject(AXFormatService), toastService = inject(AXToastService), translateService = inject(AXTranslationService), settingsService = inject(AXPSettingsService)) => {
680
- /** Monotonic counter so stale async page loads cannot overwrite newer results. */
681
- let pageLoadGeneration = 0;
682
- const resolveTargetPage = (adapter, pageId) => {
683
- const queryParams = router.routerState.snapshot.root.queryParams;
684
- const targetPageId = pageId || queryParams['page'];
685
- if (targetPageId) {
686
- return adapter.pages.find((p) => p.id === targetPageId) ?? null;
874
+ })), withMethods((store, evaluatorService = inject(AXPExpressionEvaluatorService), contextEvalFactory = inject(AXPContextEvalFactory), router = inject(Router), route = inject(ActivatedRoute), deviceService = inject(AXPDeviceService), formatService = inject(AXFormatService), toastService = inject(AXToastService), translateService = inject(AXTranslationService), settingsService = inject(AXPSettingsService)) => {
875
+ const resolvePageFromId = (adapter, pageId) => {
876
+ if (pageId) {
877
+ return adapter.pages.find((p) => p.id === pageId) ?? null;
687
878
  }
688
879
  if (deviceService.isLarge() || adapter.pages.length === 1) {
689
- return adapter.pages.find((p) => p.isPrimary) ?? adapter.pages[0];
880
+ return adapter.pages.find((p) => p.isPrimary) ?? adapter.pages[0] ?? null;
690
881
  }
691
882
  return null;
692
883
  };
693
- const executeLoadPage = async (pageId, forceRefresh) => {
884
+ /** Loads page data, context, and evaluated widgets for the target sidebar page. */
885
+ const activatePageById = async (pageId, forceRefresh) => {
694
886
  const adapter = store.adapter();
695
887
  if (!adapter) {
696
- throw new Error('Adapter must be loaded before loading page');
888
+ return;
889
+ }
890
+ const queryParams = router.routerState.snapshot.root.queryParams;
891
+ const targetPageId = pageId ?? queryParams['page'];
892
+ const currentPage = resolvePageFromId(adapter, targetPageId);
893
+ if (!forceRefresh && currentPage?.id === store.currentPage()?.id && store.status() === AXPPageStatus.Rendered) {
894
+ return;
697
895
  }
698
- const generation = ++pageLoadGeneration;
699
896
  patchState(store, {
700
897
  status: AXPPageStatus.Processing,
701
898
  });
702
- const currentPage = resolveTargetPage(adapter, pageId);
703
899
  let context = {};
704
900
  if (currentPage) {
705
901
  const currentPageIndex = adapter.pages.findIndex((p) => p.id === currentPage.id);
706
902
  const loadResult = await adapter.pages[currentPageIndex]?.load?.({ forceRefresh });
707
- if (generation !== pageLoadGeneration) {
708
- return;
709
- }
710
903
  context = loadResult?.data ?? {};
711
904
  }
712
905
  const currentTab = currentPage?.tabs?.[0] ?? null;
713
906
  const rootCtx = store.rootContext();
714
907
  const { evaluatedPageContent, evaluatedTabContentsById } = await buildEvaluatedPageContentsForRootContext(currentPage, rootCtx, evaluatorService);
715
- if (generation !== pageLoadGeneration) {
716
- return;
717
- }
718
908
  patchState(store, {
719
909
  previousContext: cloneDeep(context),
720
910
  context: cloneDeep(context),
@@ -747,16 +937,16 @@ const AXPLayoutDetailsViewViewModel = signalStore(withState(() => {
747
937
  if (!adapter) {
748
938
  return;
749
939
  }
750
- const targetPage = resolveTargetPage(adapter, params['page']);
751
- const loadedPageId = store.currentPage()?.id;
752
- const isRendered = store.status() === AXPPageStatus.Rendered;
753
- if (targetPage?.id === loadedPageId && isRendered) {
940
+ const pageId = params['page'];
941
+ const targetPage = resolvePageFromId(adapter, pageId);
942
+ if (store.currentPage()?.id === targetPage?.id) {
754
943
  return;
755
944
  }
756
- if (loadedPageId == null && targetPage == null) {
945
+ // `load()` `activatePageById` owns initial hydration when both sides are still null.
946
+ if (store.currentPage() == null && targetPage == null) {
757
947
  return;
758
948
  }
759
- void executeLoadPage(targetPage?.id);
949
+ void activatePageById(targetPage?.id);
760
950
  });
761
951
  onCleanup(() => subscription.unsubscribe());
762
952
  });
@@ -817,7 +1007,12 @@ const AXPLayoutDetailsViewViewModel = signalStore(withState(() => {
817
1007
  evaluated = await evaluatorService.evaluate(raw, evalScope);
818
1008
  }
819
1009
  else if (typeof sourceAfterPreLocale === 'string') {
820
- evaluated = await formatService.format(sourceAfterPreLocale, 'string', formatContext);
1010
+ if (sourceAfterPreLocale.trim().startsWith('@')) {
1011
+ evaluated = await translateService.translateAsync(sourceAfterPreLocale);
1012
+ }
1013
+ else {
1014
+ evaluated = await formatService.format(sourceAfterPreLocale, 'string', formatContext);
1015
+ }
821
1016
  }
822
1017
  else {
823
1018
  evaluated = await formatService.format(raw, 'string', formatContext);
@@ -851,15 +1046,12 @@ const AXPLayoutDetailsViewViewModel = signalStore(withState(() => {
851
1046
  });
852
1047
  },
853
1048
  async loadPage(pageId, forceRefresh) {
854
- await executeLoadPage(pageId, forceRefresh);
1049
+ await activatePageById(pageId, forceRefresh);
855
1050
  },
856
1051
  async load(adapter) {
857
- // Setup expression evaluator scope
858
1052
  const scope = {
859
1053
  context: {
860
- eval: (path) => {
861
- return getSmart(store.context(), path);
862
- },
1054
+ ...contextEvalFactory.createLiveScope(() => store.context()),
863
1055
  isDirty: () => store.isDirty(),
864
1056
  },
865
1057
  };
@@ -940,10 +1132,7 @@ const AXPLayoutDetailsViewViewModel = signalStore(withState(() => {
940
1132
  if (page) {
941
1133
  const pageParam = route.snapshot.queryParamMap.get('page');
942
1134
  const isSamePage = previousPage?.id === page.id && pageParam === page.id;
943
- if (isSamePage) {
944
- return;
945
- }
946
- if (axpIsEntityDetailsViewRoute(router)) {
1135
+ if (!isSamePage && axpIsEntityDetailsViewRoute(router)) {
947
1136
  const queryParams = { page: page.id };
948
1137
  if (previousPage?.id && previousPage.id !== page.id) {
949
1138
  queryParams['filters'] = null;
@@ -956,16 +1145,14 @@ const AXPLayoutDetailsViewViewModel = signalStore(withState(() => {
956
1145
  replaceUrl: false,
957
1146
  });
958
1147
  }
959
- void executeLoadPage(page.id);
1148
+ void activatePageById(page.id);
960
1149
  }
961
1150
  else {
962
- pageLoadGeneration += 1;
963
1151
  patchState(store, {
964
1152
  currentPage: null,
965
1153
  currentTab: null,
966
1154
  evaluatedPageContent: [],
967
1155
  evaluatedTabContentsById: {},
968
- status: AXPPageStatus.Rendered,
969
1156
  });
970
1157
  if (axpIsEntityDetailsViewRoute(router)) {
971
1158
  const currentParams = { ...route.snapshot.queryParams };
@@ -1093,6 +1280,7 @@ const AXPLayoutDetailsViewViewModel = signalStore(withState(() => {
1093
1280
  },
1094
1281
  async discard() {
1095
1282
  patchState(store, {
1283
+ context: cloneDeep(store.previousContext()),
1096
1284
  formDirty: false,
1097
1285
  });
1098
1286
  },
@@ -1193,6 +1381,66 @@ const AXPLayoutDetailsViewViewModel = signalStore(withState(() => {
1193
1381
  };
1194
1382
  }));
1195
1383
 
1384
+ //#region ---- Details View Action Shortcut Utilities ----
1385
+ /** Platform defaults for known entity detail commands when `shortcuts` is omitted on the action. */
1386
+ const DEFAULT_ENTITY_DETAIL_ACTION_SHORTCUTS = {
1387
+ 'delete-entity': ['ctrl+shift+delete'],
1388
+ };
1389
+ const DEFAULT_FOOTER_COMMAND_SHORTCUTS = {
1390
+ 'update-entity': ['ctrl+s'],
1391
+ discard: ['escape'],
1392
+ };
1393
+ /**
1394
+ * Returns the base workflow/command name from a composite entity action command id (`cmd&actionName`).
1395
+ */
1396
+ function getDetailsViewActionCommandBaseName(commandName) {
1397
+ if (!commandName) {
1398
+ return '';
1399
+ }
1400
+ return commandName.split('&')[0];
1401
+ }
1402
+ /**
1403
+ * Resolves keyboard shortcuts for a header entity action.
1404
+ * Explicit `shortcuts` wins; pass `[]` to disable. Otherwise known commands use platform defaults.
1405
+ */
1406
+ function resolveDetailsViewActionShortcuts(commandBaseName, explicit) {
1407
+ if (explicit !== undefined) {
1408
+ const chords = flattenDetailsViewActionShortcuts(explicit);
1409
+ return chords.length ? chords : undefined;
1410
+ }
1411
+ const defaults = DEFAULT_ENTITY_DETAIL_ACTION_SHORTCUTS[commandBaseName];
1412
+ return defaults?.length ? [...defaults] : undefined;
1413
+ }
1414
+ /**
1415
+ * Resolves save/discard footer command shortcuts from page settings.
1416
+ */
1417
+ function resolveDetailsViewFooterCommandShortcuts(commandName, explicit) {
1418
+ if (explicit !== undefined) {
1419
+ const chords = flattenDetailsViewActionShortcuts(explicit);
1420
+ return chords.length ? chords : undefined;
1421
+ }
1422
+ const defaults = DEFAULT_FOOTER_COMMAND_SHORTCUTS[commandName];
1423
+ return defaults?.length ? [...defaults] : undefined;
1424
+ }
1425
+ function flattenDetailsViewActionShortcuts(shortcuts) {
1426
+ const chords = normalizeKeyboardShortcuts(shortcuts).flatMap((shortcut) => shortcut.keys);
1427
+ return dedupeDetailsViewActionShortcuts(chords);
1428
+ }
1429
+ function dedupeDetailsViewActionShortcuts(shortcuts) {
1430
+ const seen = new Set();
1431
+ const result = [];
1432
+ for (const shortcut of shortcuts) {
1433
+ const token = shortcut.trim().toLowerCase();
1434
+ if (!token || seen.has(token)) {
1435
+ continue;
1436
+ }
1437
+ seen.add(token);
1438
+ result.push(shortcut.trim());
1439
+ }
1440
+ return result;
1441
+ }
1442
+ //#endregion
1443
+
1196
1444
  /**
1197
1445
  * Registry of rendered page component instances by component key.
1198
1446
  * Used so page execute can delegate to the actual component instance in the view.
@@ -1331,77 +1579,15 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
1331
1579
  }]
1332
1580
  }], propDecorators: { componentKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "componentKey", required: true }] }], rootContext: [{ type: i0.Input, args: [{ isSignal: true, alias: "rootContext", required: false }] }], pageConfig: [{ type: i0.Input, args: [{ isSignal: true, alias: "pageConfig", required: false }] }], options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: false }] }], componentReady: [{ type: i0.Output, args: ["componentReady"] }] } });
1333
1581
 
1334
- //#region ---- Details View Action Shortcut Utilities ----
1335
- /** Platform defaults for known entity detail commands when `shortcuts` is omitted on the action. */
1336
- const DEFAULT_ENTITY_DETAIL_ACTION_SHORTCUTS = {
1337
- 'delete-entity': ['ctrl+shift+delete'],
1338
- };
1339
- const DEFAULT_FOOTER_COMMAND_SHORTCUTS = {
1340
- 'update-entity': ['ctrl+s'],
1341
- discard: ['escape'],
1342
- };
1343
- /**
1344
- * Returns the base workflow/command name from a composite entity action command id (`cmd&actionName`).
1345
- */
1346
- function getDetailsViewActionCommandBaseName(commandName) {
1347
- if (!commandName) {
1348
- return '';
1349
- }
1350
- return commandName.split('&')[0];
1351
- }
1352
- /**
1353
- * Resolves keyboard shortcuts for a header entity action.
1354
- * Explicit `shortcuts` wins; pass `[]` to disable. Otherwise known commands use platform defaults.
1355
- */
1356
- function resolveDetailsViewActionShortcuts(commandBaseName, explicit) {
1357
- if (explicit !== undefined) {
1358
- const chords = flattenDetailsViewActionShortcuts(explicit);
1359
- return chords.length ? chords : undefined;
1360
- }
1361
- const defaults = DEFAULT_ENTITY_DETAIL_ACTION_SHORTCUTS[commandBaseName];
1362
- return defaults?.length ? [...defaults] : undefined;
1363
- }
1364
- /**
1365
- * Resolves save/discard footer command shortcuts from page settings.
1366
- */
1367
- function resolveDetailsViewFooterCommandShortcuts(commandName, explicit) {
1368
- if (explicit !== undefined) {
1369
- const chords = flattenDetailsViewActionShortcuts(explicit);
1370
- return chords.length ? chords : undefined;
1371
- }
1372
- const defaults = DEFAULT_FOOTER_COMMAND_SHORTCUTS[commandName];
1373
- return defaults?.length ? [...defaults] : undefined;
1374
- }
1375
- function flattenDetailsViewActionShortcuts(shortcuts) {
1376
- const chords = normalizeKeyboardShortcuts(shortcuts).flatMap((shortcut) => shortcut.keys);
1377
- return dedupeDetailsViewActionShortcuts(chords);
1378
- }
1379
- function dedupeDetailsViewActionShortcuts(shortcuts) {
1380
- const seen = new Set();
1381
- const result = [];
1382
- for (const shortcut of shortcuts) {
1383
- const token = shortcut.trim().toLowerCase();
1384
- if (!token || seen.has(token)) {
1385
- continue;
1386
- }
1387
- seen.add(token);
1388
- result.push(shortcut.trim());
1389
- }
1390
- return result;
1391
- }
1392
- //#endregion
1393
-
1394
1582
  class AXPLayoutDetailsViewComponent extends AXPPageLayoutBaseComponent {
1395
1583
  constructor() {
1396
1584
  super(...arguments);
1397
1585
  this.adapter = input.required(...(ngDevMode ? [{ debugName: "adapter" }] : /* istanbul ignore next */ []));
1398
1586
  //
1399
1587
  this.vm = inject(AXPLayoutDetailsViewViewModel);
1400
- this.router = inject(Router);
1401
1588
  this.route = inject(ActivatedRoute);
1402
1589
  this.eventService = inject(AXPBroadcastEventService);
1403
1590
  this.deviceService = inject(AXPDeviceService);
1404
- this.unsavedChangesConfirm = inject(AXPUnsavedChangesConfirmService);
1405
1591
  this.unsavedChangesPopstate = inject(AXPUnsavedChangesPopstateService);
1406
1592
  this.pageComponentInstanceRegistry = inject(AXPPageComponentInstanceRegistryService);
1407
1593
  this.shortcutRegistry = inject(AXPKeyboardShortcutRegistry);
@@ -1650,11 +1836,17 @@ class AXPLayoutDetailsViewComponent extends AXPPageLayoutBaseComponent {
1650
1836
  }
1651
1837
  finalizeDiscard() {
1652
1838
  const container = this.widgetContainer();
1653
- if (!container) {
1839
+ if (container) {
1840
+ container.revertAndSettle();
1841
+ this.vm.applySavedState(container.contextService.data());
1654
1842
  return;
1655
1843
  }
1656
- container.revertAndSettle();
1657
- this.vm.applySavedState(container.contextService.data());
1844
+ void this.vm.discard();
1845
+ }
1846
+ /** Reverts widget form state and view-model context to the last saved baseline. */
1847
+ async discardUnsavedChanges() {
1848
+ this.finalizeDiscard();
1849
+ this.recompute();
1658
1850
  }
1659
1851
  /**
1660
1852
  * `ax-tabs` selects on click before we can confirm. Revert synchronously, then confirm + switch.
@@ -1750,6 +1942,15 @@ class AXPLayoutDetailsViewComponent extends AXPPageLayoutBaseComponent {
1750
1942
  async getBackButton() {
1751
1943
  return this.vm.getBackButton();
1752
1944
  }
1945
+ hasUnsavedChanges() {
1946
+ if (this.vm.currentPage()?.isReadonly) {
1947
+ return false;
1948
+ }
1949
+ if (this.isActivePageComponentDirty()) {
1950
+ return true;
1951
+ }
1952
+ return this.liveFormIsDirty();
1953
+ }
1753
1954
  async getPageBadge() {
1754
1955
  if (this.vm.currentPage()?.isReadonly) {
1755
1956
  return null;
@@ -1794,6 +1995,12 @@ class AXPLayoutDetailsViewComponent extends AXPPageLayoutBaseComponent {
1794
1995
  }
1795
1996
  return this.confirmLeaveIfDirty();
1796
1997
  }
1998
+ /** Background tab close — confirm without discarding (the cached instance is destroyed). */
1999
+ onBeforeTabClose() {
2000
+ return this.unsavedChangesConfirm.confirmIfDirty(this.shouldPromptLeave(), {
2001
+ approveLeaveOnConfirm: false,
2002
+ });
2003
+ }
1797
2004
  /** Active entity component page instance, when the current details sub-page is component-based. */
1798
2005
  getActivePageComponentInstance() {
1799
2006
  const componentKey = this.getActivePageComponentKey();
@@ -1818,6 +2025,12 @@ class AXPLayoutDetailsViewComponent extends AXPPageLayoutBaseComponent {
1818
2025
  static isComponentPageContent(item) {
1819
2026
  return 'type' in item && item.type === 'component';
1820
2027
  }
2028
+ pageContentTrackKey(item, index) {
2029
+ if (AXPLayoutDetailsViewComponent.isComponentPageContent(item)) {
2030
+ return `component:${item.componentKey}`;
2031
+ }
2032
+ return item.name ?? `widget:${index}`;
2033
+ }
1821
2034
  /** Whether the active component-based entity page has unsaved edits. */
1822
2035
  isActivePageComponentDirty() {
1823
2036
  return this.getActivePageComponentInstance()?.hasUnsavedChanges() ?? false;
@@ -1833,8 +2046,13 @@ class AXPLayoutDetailsViewComponent extends AXPPageLayoutBaseComponent {
1833
2046
  return this.liveFormIsDirty();
1834
2047
  }
1835
2048
  /** Single gate for leaving the page or switching details sub-pages when dirty. */
1836
- confirmLeaveIfDirty() {
1837
- return this.unsavedChangesConfirm.confirmIfDirty(this.shouldPromptLeave());
2049
+ async confirmLeaveIfDirty() {
2050
+ const shouldPrompt = this.shouldPromptLeave();
2051
+ const confirmed = await this.unsavedChangesConfirm.confirmIfDirty(shouldPrompt);
2052
+ if (confirmed && shouldPrompt) {
2053
+ await this.discardUnsavedChanges();
2054
+ }
2055
+ return confirmed;
1838
2056
  }
1839
2057
  //#endregion
1840
2058
  //#region ---- Command Execution ----
@@ -1853,9 +2071,7 @@ class AXPLayoutDetailsViewComponent extends AXPPageLayoutBaseComponent {
1853
2071
  return;
1854
2072
  }
1855
2073
  if (command.name == rejectCommand) {
1856
- await this.vm.discard();
1857
- this.finalizeDiscard();
1858
- this.recompute();
2074
+ await this.discardUnsavedChanges();
1859
2075
  return;
1860
2076
  }
1861
2077
  // Handle status update command
@@ -1918,9 +2134,7 @@ class AXPLayoutDetailsViewComponent extends AXPPageLayoutBaseComponent {
1918
2134
  return;
1919
2135
  }
1920
2136
  if (isDiscardCommand(command.name, rejectCommand)) {
1921
- await this.vm.discard();
1922
- this.finalizeDiscard();
1923
- this.recompute();
2137
+ await this.discardUnsavedChanges();
1924
2138
  return;
1925
2139
  }
1926
2140
  if (!this.isActivePageComponentDirty()) {
@@ -2129,7 +2343,7 @@ class AXPLayoutDetailsViewComponent extends AXPPageLayoutBaseComponent {
2129
2343
  useExisting: AXPLayoutDetailsViewComponent,
2130
2344
  },
2131
2345
  AXPLayoutDetailsViewViewModel,
2132
- ], viewQueries: [{ propertyName: "form", first: true, predicate: ["form"], descendants: true, isSignal: true }, { propertyName: "widgetContainer", first: true, predicate: AXPWidgetContainerComponent, descendants: true, isSignal: true }, { propertyName: "sidebarTabs", first: true, predicate: ["sidebarTabs"], descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: "<axp-page-layout *translate=\"let t\">\n @if (vm.adapter()?.pages?.length! > 1 && vm.isLarge()) {\n <axp-layout-start-side>\n <axp-layout-header>\n <axp-layout-title>{{\n (formatService.format(vm.showPages() ? (vm.adapter()?.title ?? '') : (vm.adapter()?.label ?? ''), 'string', vm.rootContext())\n | translate\n | async)\n }}</axp-layout-title>\n <axp-layout-description>{{\n (formatService.format(vm.adapter()?.description ?? '', 'string', vm.rootContext()) | translate | async)\n }}</axp-layout-description>\n </axp-layout-header>\n <axp-layout-content>\n <ax-tabs\n #sidebarTabs\n class=\"axp-vertical-tabs\"\n [look]=\"'with-line-color'\"\n [location]=\"'end'\"\n [fitParent]=\"true\"\n (onActiveTabChanged)=\"handleSidebarTabChanged($event)\"\n >\n @for (item of vm.adapter()?.pages; track item.id) {\n <ax-tab-item\n [key]=\"item.id\"\n [text]=\"(formatService.format(item.label, 'string', vm.rootContext()) | translate | async)!\"\n [active]=\"vm.currentPage()?.id === item.id\"\n >\n <ax-prefix>\n <ax-icon icon=\"far ${{ item.icon }}\"></ax-icon>\n </ax-prefix>\n </ax-tab-item>\n }\n </ax-tabs>\n </axp-layout-content>\n </axp-layout-start-side>\n }\n <!-- Content Section -->\n <axp-page-content class=\"ax-flex ax-flex-row ax-gap-4\">\n @if (vm.showPages()) {\n <axp-layout-list class=\"ax-w-full\">\n @for (item of vm.adapter()?.pages; track $index) {\n <axp-layout-list-item (click)=\"handleSelectPage(item)\">\n <axp-layout-prefix>\n <ax-icon icon=\"far ${{ item.icon }}\" class=\"ax-text-gray-500\"></ax-icon>\n </axp-layout-prefix>\n <axp-layout-content>\n <a class=\"ax-font-semibold\">{{\n formatService.format(item.label, 'string', vm.rootContext()) | translate | async\n }}</a>\n </axp-layout-content>\n <axp-layout-suffix>\n <ax-icon icon=\" far fa-chevron-right\" class=\"ax-text-gray-400\"></ax-icon>\n </axp-layout-suffix>\n </axp-layout-list-item>\n }\n </axp-layout-list>\n } @else {\n @if (vm.isBusy()) {\n <div class=\"ax-flex ax-h-full ax-w-full ax-items-center ax-justify-center\">\n <ax-loading></ax-loading>\n </div>\n } @else {\n <ax-form class=\"ax-h-full ax-w-full\" #form>\n <axp-widgets-container\n #widgetsContainerRef\n [context]=\"vm.context()\"\n (onContextChanged)=\"handleOnContextChanged($event)\"\n >\n <div class=\"ax-flex ax-flex-col ax-w-full ax-gap-4 ax-h-full\">\n @for (item of vm.currentPageContent(); track $index) {\n @if ('type' in item && item.type === 'component') {\n <!-- Component-based content -->\n <div\n axp-page-component-renderer\n [componentKey]=\"$any(item).componentKey\"\n [rootContext]=\"vm.rootContext()\"\n [pageConfig]=\"$any(item).pageConfig\"\n [options]=\"$any(item).options\"\n (componentReady)=\"recompute()\"\n ></div>\n } @else {\n <!-- Widget-based content -->\n <ng-container axp-widget-renderer [node]=\"$any(item)\" [mode]=\"$any(item).mode ?? 'view'\"></ng-container>\n }\n }\n @if (vm.currentPage()?.tabs?.length) {\n <div class=\"axp-horizontal-tabs\">\n <ax-tabs [location]=\"'bottom'\" [fitParent]=\"false\" look=\"classic\">\n @for (tab of vm.currentPageTabs(); track $index) {\n <ax-tab-item\n (onClick)=\"vm.setCurrentTab(tab)\"\n [text]=\"(tab.title | translate | async)!\"\n [active]=\"vm.currentTab()?.id === tab.id\"\n >\n <ax-prefix>\n <ax-icon icon=\"far ${{ tab.icon }}\"></ax-icon>\n </ax-prefix>\n </ax-tab-item>\n }\n </ax-tabs>\n <div class=\"content\">\n @if (vm.currentTab()) {\n @for (content of vm.activeTabContent(); track content.name) {\n <ng-container axp-widget-renderer [node]=\"content\" [mode]=\"content.mode ?? 'view'\">\n </ng-container>\n }\n }\n </div>\n </div>\n }\n </div>\n </axp-widgets-container>\n </ax-form>\n }\n }\n </axp-page-content>\n\n <!-- Footer Section -->\n @if (hasFooter()) {\n <axp-page-footer class=\"--animated\">\n <axp-layout-suffix>\n <!-- secondary footer actions -->\n @if (hasVisibleFooterSecondaryActions()) {\n <ax-button\n [class.ax-sm]=\"deviceService.isSmall()\"\n [iconOnly]=\"deviceService.isSmall()\"\n [text]=\"'@general:terms.interface.actions' | translate | async\"\n [look]=\"deviceService.isSmall() ? 'blank' : 'solid'\"\n [color]=\"'default'\"\n >\n <ax-prefix>\n <i class=\"fa-solid fa-ellipsis-vertical\"></i>\n </ax-prefix>\n <ax-dropdown-panel #panel>\n <ax-button-item-list>\n @for (action of footerSecondaryActions(); track $index) {\n @if (isFooterActionVisible(action)) {\n <ax-button-item\n [text]=\"(t(action.title) | async)!\"\n [color]=\"action.color\"\n [disabled]=\"isFooterActionDisabled(action)\"\n (onClick)=\"execute(action.command!)\"\n >\n <ax-prefix>\n <ax-icon icon=\"fa-light {{ action.icon }}\"></ax-icon>\n </ax-prefix>\n </ax-button-item>\n @if (action.break) {\n <ax-divider></ax-divider>\n }\n }\n }\n </ax-button-item-list>\n </ax-dropdown-panel>\n </ax-button>\n }\n\n @if (hasVisibleFooterPrimaryActions()) {\n @if (showSaveDiscardFooter()) {\n @if (rejectFooterCommand(); as reject) {\n <ax-button\n [class.ax-sm]=\"deviceService.isSmall()\"\n [disabled]=\"vm.isSaving()\"\n [text]=\"(t(reject.title) | async)!\"\n [look]=\"'solid'\"\n [color]=\"reject.color\"\n (onClick)=\"execute(reject.command)\"\n >\n <ax-prefix>\n <i class=\"{{ reject.icon }}\"></i>\n </ax-prefix>\n </ax-button>\n }\n @if (acceptFooterCommand(); as accept) {\n <ax-button\n [class.ax-sm]=\"deviceService.isSmall()\"\n [disabled]=\"vm.isSaving()\"\n [text]=\"(t(accept.title) | async)!\"\n [look]=\"'solid'\"\n [color]=\"accept.color\"\n (onClick)=\"execute(accept.command)\"\n >\n <ax-prefix>\n <i class=\"{{ accept.icon }}\"></i>\n </ax-prefix>\n </ax-button>\n }\n }\n @for (action of footerPrimaryActions(); track $index) {\n @if (isFooterActionVisible(action)) {\n <ax-button\n [class.ax-sm]=\"deviceService.isSmall()\"\n [disabled]=\"isFooterActionDisabled(action)\"\n [text]=\"(t(action.title) | async)!\"\n [look]=\"'solid'\"\n [color]=\"action.color\"\n (onClick)=\"execute(action.command!)\"\n >\n <ax-prefix>\n <i class=\"{{ action.icon }}\"></i>\n </ax-prefix>\n @if (action?.items) {\n <ax-dropdown-panel #panel>\n <ax-button-item-list>\n @for (sub of action?.items; track $index) {\n @if (isFooterActionVisible(sub)) {\n <ax-button-item\n [text]=\"(t(sub.title) | async)!\"\n [color]=\"sub.color\"\n [disabled]=\"isFooterActionDisabled(sub)\"\n (onClick)=\"execute(sub.command!)\"\n >\n <ax-prefix>\n <ax-icon icon=\"fa-light {{ sub.icon }}\"></ax-icon>\n </ax-prefix>\n </ax-button-item>\n @if (sub.break) {\n <ax-divider></ax-divider>\n }\n }\n }\n </ax-button-item-list>\n </ax-dropdown-panel>\n }\n </ax-button>\n }\n }\n }\n </axp-layout-suffix>\n </axp-page-footer>\n }\n</axp-page-layout>\n", styles: ["axp-layout-details-view .axp-vertical-tabs{--ax-comp-tabs-default-border-radius: 0}axp-layout-details-view .axp-vertical-tabs ax-tab-item{margin-top:0!important;margin-bottom:0!important;padding-top:.75rem!important;padding-bottom:.75rem!important;font-weight:600!important}axp-layout-details-view .axp-vertical-tabs ax-tab-item>div{display:flex!important;align-items:center!important}axp-layout-details-view .axp-vertical-tabs ax-tab-item>div .ax-tab-item-text{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}axp-layout-details-view .axp-horizontal-tabs{display:flex;width:100%;flex-direction:column}axp-layout-details-view .axp-horizontal-tabs .content{margin-top:1rem;margin-bottom:1rem;display:block;width:100%}axp-layout-details-view axp-layout-section axp-layout-content{display:flex;width:100%;flex-direction:column}axp-layout-details-view axp-layout-section axp-layout-content>:not([hidden])~:not([hidden]){--tw-divide-y-reverse: 0;border-top-width:calc(1px * calc(1 - var(--tw-divide-y-reverse)));border-bottom-width:calc(1px * var(--tw-divide-y-reverse));border-style:dashed;--tw-divide-opacity: 1;border-color:rgba(var(--ax-sys-color-border-lightest-surface),var(--tw-divide-opacity, 1))}axp-layout-details-view axp-layout-section axp-layout-content .--item-container{align-items:center;gap:1rem;padding:1rem}@media(min-width:1024px){axp-layout-details-view axp-layout-section axp-layout-content .--item-container{padding-left:1.5rem;padding-right:1.5rem}}@media(min-width:1280px){axp-layout-details-view axp-layout-section axp-layout-content .--item-container{padding-left:2rem;padding-right:2rem}}@media(min-width:1536px){axp-layout-details-view axp-layout-section axp-layout-content .--item-container{padding-left:2.5rem;padding-right:2.5rem}}axp-layout-details-view axp-layout-section axp-layout-content .--item-container{display:grid;grid-template-columns:repeat(12,minmax(0,1fr))}axp-layout-details-view axp-layout-section axp-layout-content .--item-container axp-layout-description{margin-top:.5rem!important;font-size:.75rem!important;line-height:1rem!important}axp-layout-details-view axp-page-content ax-form form{height:100%}axp-layout-details-view axp-layout-start-side{border-inline-end-width:1px}\n"], dependencies: [{ kind: "ngmodule", type:
2346
+ ], viewQueries: [{ propertyName: "form", first: true, predicate: ["form"], descendants: true, isSignal: true }, { propertyName: "widgetContainer", first: true, predicate: AXPWidgetContainerComponent, descendants: true, isSignal: true }, { propertyName: "sidebarTabs", first: true, predicate: ["sidebarTabs"], descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: "<axp-page-layout *translate=\"let t\">\n @if (vm.adapter()?.pages?.length! > 1 && vm.isLarge()) {\n <axp-layout-start-side>\n <axp-layout-header>\n <axp-layout-title>{{\n formatService.format(\n vm.showPages() ? (vm.adapter()?.title ?? '') : (vm.adapter()?.label ?? ''),\n 'string',\n vm.rootContext()\n )\n | translate\n | async\n }}</axp-layout-title>\n <axp-layout-description>{{\n formatService.format(vm.adapter()?.description ?? '', 'string', vm.rootContext()) | translate | async\n }}</axp-layout-description>\n </axp-layout-header>\n <axp-layout-content>\n <ax-tabs\n #sidebarTabs\n class=\"axp-vertical-tabs\"\n [look]=\"'with-line-color'\"\n [location]=\"'end'\"\n [fitParent]=\"true\"\n (onActiveTabChanged)=\"handleSidebarTabChanged($event)\"\n >\n @for (item of vm.adapter()?.pages; track item.id) {\n <ax-tab-item\n [key]=\"item.id\"\n [text]=\"(formatService.format(item.label, 'string', vm.rootContext()) | translate | async)!\"\n [active]=\"vm.currentPage()?.id === item.id\"\n >\n <ax-prefix>\n <ax-icon icon=\"far ${{ item.icon }}\"></ax-icon>\n </ax-prefix>\n </ax-tab-item>\n }\n </ax-tabs>\n </axp-layout-content>\n </axp-layout-start-side>\n }\n <!-- Content Section -->\n <axp-page-content class=\"ax-flex ax-flex-row ax-gap-4\">\n @if (vm.showPages()) {\n <axp-layout-list class=\"ax-w-full\">\n @for (item of vm.adapter()?.pages; track $index) {\n <axp-layout-list-item (click)=\"handleSelectPage(item)\">\n <axp-layout-prefix>\n <ax-icon icon=\"far ${{ item.icon }}\" class=\"ax-text-gray-500\"></ax-icon>\n </axp-layout-prefix>\n <axp-layout-content>\n <a class=\"ax-font-semibold\">{{\n formatService.format(item.label, 'string', vm.rootContext()) | translate | async\n }}</a>\n </axp-layout-content>\n <axp-layout-suffix>\n <ax-icon icon=\" far fa-chevron-right\" class=\"ax-text-gray-400\"></ax-icon>\n </axp-layout-suffix>\n </axp-layout-list-item>\n }\n </axp-layout-list>\n } @else {\n @if (vm.isBusy()) {\n <div class=\"ax-flex ax-h-full ax-w-full ax-items-center ax-justify-center\">\n <ax-loading></ax-loading>\n </div>\n } @else {\n <ax-form class=\"ax-h-full ax-w-full\" #form>\n <axp-widgets-container\n #widgetsContainerRef\n [context]=\"vm.context()\"\n (onContextChanged)=\"handleOnContextChanged($event)\"\n >\n <div class=\"ax-flex ax-flex-col ax-w-full ax-gap-4 ax-h-full\">\n @for (item of vm.currentPageContent(); track pageContentTrackKey($any(item), $index)) {\n @if ('type' in item && item.type === 'component') {\n <!-- Component-based content -->\n <div\n axp-page-component-renderer\n [componentKey]=\"$any(item).componentKey\"\n [rootContext]=\"vm.rootContext()\"\n [pageConfig]=\"$any(item).pageConfig\"\n [options]=\"$any(item).options\"\n (componentReady)=\"recompute()\"\n ></div>\n } @else {\n <!-- Widget-based content -->\n <ng-container axp-widget-renderer [node]=\"$any(item)\" [mode]=\"$any(item).mode ?? 'view'\"></ng-container>\n }\n }\n @if (vm.currentPage()?.tabs?.length) {\n <div class=\"axp-horizontal-tabs\">\n <ax-tabs [location]=\"'bottom'\" [fitParent]=\"false\" look=\"classic\">\n @for (tab of vm.currentPageTabs(); track $index) {\n <ax-tab-item\n (onClick)=\"vm.setCurrentTab(tab)\"\n [text]=\"(tab.title | translate | async)!\"\n [active]=\"vm.currentTab()?.id === tab.id\"\n >\n <ax-prefix>\n <ax-icon icon=\"far ${{ tab.icon }}\"></ax-icon>\n </ax-prefix>\n </ax-tab-item>\n }\n </ax-tabs>\n <div class=\"content\">\n @if (vm.currentTab()) {\n @for (content of vm.activeTabContent(); track content.name) {\n <ng-container axp-widget-renderer [node]=\"content\" [mode]=\"content.mode ?? 'view'\">\n </ng-container>\n }\n }\n </div>\n </div>\n }\n </div>\n </axp-widgets-container>\n </ax-form>\n }\n }\n </axp-page-content>\n\n <!-- Footer Section -->\n @if (hasFooter()) {\n <axp-page-footer class=\"--animated\">\n <axp-layout-suffix>\n <!-- secondary footer actions -->\n @if (hasVisibleFooterSecondaryActions()) {\n <ax-button\n [class.ax-sm]=\"deviceService.isSmall()\"\n [iconOnly]=\"deviceService.isSmall()\"\n [text]=\"'@general:terms.interface.actions' | translate | async\"\n [look]=\"deviceService.isSmall() ? 'blank' : 'solid'\"\n [color]=\"'default'\"\n >\n <ax-prefix>\n <i class=\"fa-solid fa-ellipsis-vertical\"></i>\n </ax-prefix>\n <ax-dropdown-panel #panel>\n <ax-button-item-list>\n @for (action of footerSecondaryActions(); track $index) {\n @if (isFooterActionVisible(action)) {\n <ax-button-item\n [text]=\"(t(action.title) | async)!\"\n [color]=\"action.color\"\n [disabled]=\"isFooterActionDisabled(action)\"\n (onClick)=\"execute(action.command!)\"\n >\n <ax-prefix>\n <ax-icon icon=\"fa-light {{ action.icon }}\"></ax-icon>\n </ax-prefix>\n </ax-button-item>\n @if (action.break) {\n <ax-divider></ax-divider>\n }\n }\n }\n </ax-button-item-list>\n </ax-dropdown-panel>\n </ax-button>\n }\n\n @if (hasVisibleFooterPrimaryActions()) {\n @if (showSaveDiscardFooter()) {\n @if (rejectFooterCommand(); as reject) {\n <ax-button\n [class.ax-sm]=\"deviceService.isSmall()\"\n [disabled]=\"vm.isSaving()\"\n [text]=\"(t(reject.title) | async)!\"\n [look]=\"'solid'\"\n [color]=\"reject.color\"\n (onClick)=\"execute(reject.command)\"\n >\n <ax-prefix>\n <i class=\"{{ reject.icon }}\"></i>\n </ax-prefix>\n </ax-button>\n }\n @if (acceptFooterCommand(); as accept) {\n <ax-button\n [class.ax-sm]=\"deviceService.isSmall()\"\n [disabled]=\"vm.isSaving()\"\n [text]=\"(t(accept.title) | async)!\"\n [look]=\"'solid'\"\n [color]=\"accept.color\"\n (onClick)=\"execute(accept.command)\"\n >\n <ax-prefix>\n <i class=\"{{ accept.icon }}\"></i>\n </ax-prefix>\n </ax-button>\n }\n }\n @for (action of footerPrimaryActions(); track $index) {\n @if (isFooterActionVisible(action)) {\n <ax-button\n [class.ax-sm]=\"deviceService.isSmall()\"\n [disabled]=\"isFooterActionDisabled(action)\"\n [text]=\"(t(action.title) | async)!\"\n [look]=\"'solid'\"\n [color]=\"action.color\"\n (onClick)=\"execute(action.command!)\"\n >\n <ax-prefix>\n <i class=\"{{ action.icon }}\"></i>\n </ax-prefix>\n @if (action?.items) {\n <ax-dropdown-panel #panel>\n <ax-button-item-list>\n @for (sub of action?.items; track $index) {\n @if (isFooterActionVisible(sub)) {\n <ax-button-item\n [text]=\"(t(sub.title) | async)!\"\n [color]=\"sub.color\"\n [disabled]=\"isFooterActionDisabled(sub)\"\n (onClick)=\"execute(sub.command!)\"\n >\n <ax-prefix>\n <ax-icon icon=\"fa-light {{ sub.icon }}\"></ax-icon>\n </ax-prefix>\n </ax-button-item>\n @if (sub.break) {\n <ax-divider></ax-divider>\n }\n }\n }\n </ax-button-item-list>\n </ax-dropdown-panel>\n }\n </ax-button>\n }\n }\n }\n </axp-layout-suffix>\n </axp-page-footer>\n }\n</axp-page-layout>\n", styles: ["axp-layout-details-view .axp-vertical-tabs{--ax-comp-tabs-default-border-radius: 0}axp-layout-details-view .axp-vertical-tabs ax-tab-item{margin-top:0!important;margin-bottom:0!important;padding-top:.75rem!important;padding-bottom:.75rem!important;font-weight:600!important}axp-layout-details-view .axp-vertical-tabs ax-tab-item>div{display:flex!important;align-items:center!important}axp-layout-details-view .axp-vertical-tabs ax-tab-item>div .ax-tab-item-text{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}axp-layout-details-view .axp-horizontal-tabs{display:flex;width:100%;flex-direction:column}axp-layout-details-view .axp-horizontal-tabs .content{margin-top:1rem;margin-bottom:1rem;display:block;width:100%}axp-layout-details-view axp-layout-section axp-layout-content{display:flex;width:100%;flex-direction:column}axp-layout-details-view axp-layout-section axp-layout-content>:not([hidden])~:not([hidden]){--tw-divide-y-reverse: 0;border-top-width:calc(1px * calc(1 - var(--tw-divide-y-reverse)));border-bottom-width:calc(1px * var(--tw-divide-y-reverse));border-style:dashed;--tw-divide-opacity: 1;border-color:rgba(var(--ax-sys-color-border-lightest-surface),var(--tw-divide-opacity, 1))}axp-layout-details-view axp-layout-section axp-layout-content .--item-container{align-items:center;gap:1rem;padding:1rem}@media(min-width:1024px){axp-layout-details-view axp-layout-section axp-layout-content .--item-container{padding-left:1.5rem;padding-right:1.5rem}}@media(min-width:1280px){axp-layout-details-view axp-layout-section axp-layout-content .--item-container{padding-left:2rem;padding-right:2rem}}@media(min-width:1536px){axp-layout-details-view axp-layout-section axp-layout-content .--item-container{padding-left:2.5rem;padding-right:2.5rem}}axp-layout-details-view axp-layout-section axp-layout-content .--item-container{display:grid;grid-template-columns:repeat(12,minmax(0,1fr))}axp-layout-details-view axp-layout-section axp-layout-content .--item-container axp-layout-description{margin-top:.5rem!important;font-size:.75rem!important;line-height:1rem!important}axp-layout-details-view axp-page-content ax-form form{height:100%}axp-layout-details-view axp-layout-start-side{border-inline-end-width:1px}\n"], dependencies: [{ kind: "ngmodule", type:
2133
2347
  // Angular
2134
2348
  CommonModule }, { kind: "ngmodule", type:
2135
2349
  // ACoreX
@@ -2175,7 +2389,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
2175
2389
  useExisting: AXPLayoutDetailsViewComponent,
2176
2390
  },
2177
2391
  AXPLayoutDetailsViewViewModel,
2178
- ], template: "<axp-page-layout *translate=\"let t\">\n @if (vm.adapter()?.pages?.length! > 1 && vm.isLarge()) {\n <axp-layout-start-side>\n <axp-layout-header>\n <axp-layout-title>{{\n (formatService.format(vm.showPages() ? (vm.adapter()?.title ?? '') : (vm.adapter()?.label ?? ''), 'string', vm.rootContext())\n | translate\n | async)\n }}</axp-layout-title>\n <axp-layout-description>{{\n (formatService.format(vm.adapter()?.description ?? '', 'string', vm.rootContext()) | translate | async)\n }}</axp-layout-description>\n </axp-layout-header>\n <axp-layout-content>\n <ax-tabs\n #sidebarTabs\n class=\"axp-vertical-tabs\"\n [look]=\"'with-line-color'\"\n [location]=\"'end'\"\n [fitParent]=\"true\"\n (onActiveTabChanged)=\"handleSidebarTabChanged($event)\"\n >\n @for (item of vm.adapter()?.pages; track item.id) {\n <ax-tab-item\n [key]=\"item.id\"\n [text]=\"(formatService.format(item.label, 'string', vm.rootContext()) | translate | async)!\"\n [active]=\"vm.currentPage()?.id === item.id\"\n >\n <ax-prefix>\n <ax-icon icon=\"far ${{ item.icon }}\"></ax-icon>\n </ax-prefix>\n </ax-tab-item>\n }\n </ax-tabs>\n </axp-layout-content>\n </axp-layout-start-side>\n }\n <!-- Content Section -->\n <axp-page-content class=\"ax-flex ax-flex-row ax-gap-4\">\n @if (vm.showPages()) {\n <axp-layout-list class=\"ax-w-full\">\n @for (item of vm.adapter()?.pages; track $index) {\n <axp-layout-list-item (click)=\"handleSelectPage(item)\">\n <axp-layout-prefix>\n <ax-icon icon=\"far ${{ item.icon }}\" class=\"ax-text-gray-500\"></ax-icon>\n </axp-layout-prefix>\n <axp-layout-content>\n <a class=\"ax-font-semibold\">{{\n formatService.format(item.label, 'string', vm.rootContext()) | translate | async\n }}</a>\n </axp-layout-content>\n <axp-layout-suffix>\n <ax-icon icon=\" far fa-chevron-right\" class=\"ax-text-gray-400\"></ax-icon>\n </axp-layout-suffix>\n </axp-layout-list-item>\n }\n </axp-layout-list>\n } @else {\n @if (vm.isBusy()) {\n <div class=\"ax-flex ax-h-full ax-w-full ax-items-center ax-justify-center\">\n <ax-loading></ax-loading>\n </div>\n } @else {\n <ax-form class=\"ax-h-full ax-w-full\" #form>\n <axp-widgets-container\n #widgetsContainerRef\n [context]=\"vm.context()\"\n (onContextChanged)=\"handleOnContextChanged($event)\"\n >\n <div class=\"ax-flex ax-flex-col ax-w-full ax-gap-4 ax-h-full\">\n @for (item of vm.currentPageContent(); track $index) {\n @if ('type' in item && item.type === 'component') {\n <!-- Component-based content -->\n <div\n axp-page-component-renderer\n [componentKey]=\"$any(item).componentKey\"\n [rootContext]=\"vm.rootContext()\"\n [pageConfig]=\"$any(item).pageConfig\"\n [options]=\"$any(item).options\"\n (componentReady)=\"recompute()\"\n ></div>\n } @else {\n <!-- Widget-based content -->\n <ng-container axp-widget-renderer [node]=\"$any(item)\" [mode]=\"$any(item).mode ?? 'view'\"></ng-container>\n }\n }\n @if (vm.currentPage()?.tabs?.length) {\n <div class=\"axp-horizontal-tabs\">\n <ax-tabs [location]=\"'bottom'\" [fitParent]=\"false\" look=\"classic\">\n @for (tab of vm.currentPageTabs(); track $index) {\n <ax-tab-item\n (onClick)=\"vm.setCurrentTab(tab)\"\n [text]=\"(tab.title | translate | async)!\"\n [active]=\"vm.currentTab()?.id === tab.id\"\n >\n <ax-prefix>\n <ax-icon icon=\"far ${{ tab.icon }}\"></ax-icon>\n </ax-prefix>\n </ax-tab-item>\n }\n </ax-tabs>\n <div class=\"content\">\n @if (vm.currentTab()) {\n @for (content of vm.activeTabContent(); track content.name) {\n <ng-container axp-widget-renderer [node]=\"content\" [mode]=\"content.mode ?? 'view'\">\n </ng-container>\n }\n }\n </div>\n </div>\n }\n </div>\n </axp-widgets-container>\n </ax-form>\n }\n }\n </axp-page-content>\n\n <!-- Footer Section -->\n @if (hasFooter()) {\n <axp-page-footer class=\"--animated\">\n <axp-layout-suffix>\n <!-- secondary footer actions -->\n @if (hasVisibleFooterSecondaryActions()) {\n <ax-button\n [class.ax-sm]=\"deviceService.isSmall()\"\n [iconOnly]=\"deviceService.isSmall()\"\n [text]=\"'@general:terms.interface.actions' | translate | async\"\n [look]=\"deviceService.isSmall() ? 'blank' : 'solid'\"\n [color]=\"'default'\"\n >\n <ax-prefix>\n <i class=\"fa-solid fa-ellipsis-vertical\"></i>\n </ax-prefix>\n <ax-dropdown-panel #panel>\n <ax-button-item-list>\n @for (action of footerSecondaryActions(); track $index) {\n @if (isFooterActionVisible(action)) {\n <ax-button-item\n [text]=\"(t(action.title) | async)!\"\n [color]=\"action.color\"\n [disabled]=\"isFooterActionDisabled(action)\"\n (onClick)=\"execute(action.command!)\"\n >\n <ax-prefix>\n <ax-icon icon=\"fa-light {{ action.icon }}\"></ax-icon>\n </ax-prefix>\n </ax-button-item>\n @if (action.break) {\n <ax-divider></ax-divider>\n }\n }\n }\n </ax-button-item-list>\n </ax-dropdown-panel>\n </ax-button>\n }\n\n @if (hasVisibleFooterPrimaryActions()) {\n @if (showSaveDiscardFooter()) {\n @if (rejectFooterCommand(); as reject) {\n <ax-button\n [class.ax-sm]=\"deviceService.isSmall()\"\n [disabled]=\"vm.isSaving()\"\n [text]=\"(t(reject.title) | async)!\"\n [look]=\"'solid'\"\n [color]=\"reject.color\"\n (onClick)=\"execute(reject.command)\"\n >\n <ax-prefix>\n <i class=\"{{ reject.icon }}\"></i>\n </ax-prefix>\n </ax-button>\n }\n @if (acceptFooterCommand(); as accept) {\n <ax-button\n [class.ax-sm]=\"deviceService.isSmall()\"\n [disabled]=\"vm.isSaving()\"\n [text]=\"(t(accept.title) | async)!\"\n [look]=\"'solid'\"\n [color]=\"accept.color\"\n (onClick)=\"execute(accept.command)\"\n >\n <ax-prefix>\n <i class=\"{{ accept.icon }}\"></i>\n </ax-prefix>\n </ax-button>\n }\n }\n @for (action of footerPrimaryActions(); track $index) {\n @if (isFooterActionVisible(action)) {\n <ax-button\n [class.ax-sm]=\"deviceService.isSmall()\"\n [disabled]=\"isFooterActionDisabled(action)\"\n [text]=\"(t(action.title) | async)!\"\n [look]=\"'solid'\"\n [color]=\"action.color\"\n (onClick)=\"execute(action.command!)\"\n >\n <ax-prefix>\n <i class=\"{{ action.icon }}\"></i>\n </ax-prefix>\n @if (action?.items) {\n <ax-dropdown-panel #panel>\n <ax-button-item-list>\n @for (sub of action?.items; track $index) {\n @if (isFooterActionVisible(sub)) {\n <ax-button-item\n [text]=\"(t(sub.title) | async)!\"\n [color]=\"sub.color\"\n [disabled]=\"isFooterActionDisabled(sub)\"\n (onClick)=\"execute(sub.command!)\"\n >\n <ax-prefix>\n <ax-icon icon=\"fa-light {{ sub.icon }}\"></ax-icon>\n </ax-prefix>\n </ax-button-item>\n @if (sub.break) {\n <ax-divider></ax-divider>\n }\n }\n }\n </ax-button-item-list>\n </ax-dropdown-panel>\n }\n </ax-button>\n }\n }\n }\n </axp-layout-suffix>\n </axp-page-footer>\n }\n</axp-page-layout>\n", styles: ["axp-layout-details-view .axp-vertical-tabs{--ax-comp-tabs-default-border-radius: 0}axp-layout-details-view .axp-vertical-tabs ax-tab-item{margin-top:0!important;margin-bottom:0!important;padding-top:.75rem!important;padding-bottom:.75rem!important;font-weight:600!important}axp-layout-details-view .axp-vertical-tabs ax-tab-item>div{display:flex!important;align-items:center!important}axp-layout-details-view .axp-vertical-tabs ax-tab-item>div .ax-tab-item-text{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}axp-layout-details-view .axp-horizontal-tabs{display:flex;width:100%;flex-direction:column}axp-layout-details-view .axp-horizontal-tabs .content{margin-top:1rem;margin-bottom:1rem;display:block;width:100%}axp-layout-details-view axp-layout-section axp-layout-content{display:flex;width:100%;flex-direction:column}axp-layout-details-view axp-layout-section axp-layout-content>:not([hidden])~:not([hidden]){--tw-divide-y-reverse: 0;border-top-width:calc(1px * calc(1 - var(--tw-divide-y-reverse)));border-bottom-width:calc(1px * var(--tw-divide-y-reverse));border-style:dashed;--tw-divide-opacity: 1;border-color:rgba(var(--ax-sys-color-border-lightest-surface),var(--tw-divide-opacity, 1))}axp-layout-details-view axp-layout-section axp-layout-content .--item-container{align-items:center;gap:1rem;padding:1rem}@media(min-width:1024px){axp-layout-details-view axp-layout-section axp-layout-content .--item-container{padding-left:1.5rem;padding-right:1.5rem}}@media(min-width:1280px){axp-layout-details-view axp-layout-section axp-layout-content .--item-container{padding-left:2rem;padding-right:2rem}}@media(min-width:1536px){axp-layout-details-view axp-layout-section axp-layout-content .--item-container{padding-left:2.5rem;padding-right:2.5rem}}axp-layout-details-view axp-layout-section axp-layout-content .--item-container{display:grid;grid-template-columns:repeat(12,minmax(0,1fr))}axp-layout-details-view axp-layout-section axp-layout-content .--item-container axp-layout-description{margin-top:.5rem!important;font-size:.75rem!important;line-height:1rem!important}axp-layout-details-view axp-page-content ax-form form{height:100%}axp-layout-details-view axp-layout-start-side{border-inline-end-width:1px}\n"] }]
2392
+ ], template: "<axp-page-layout *translate=\"let t\">\n @if (vm.adapter()?.pages?.length! > 1 && vm.isLarge()) {\n <axp-layout-start-side>\n <axp-layout-header>\n <axp-layout-title>{{\n formatService.format(\n vm.showPages() ? (vm.adapter()?.title ?? '') : (vm.adapter()?.label ?? ''),\n 'string',\n vm.rootContext()\n )\n | translate\n | async\n }}</axp-layout-title>\n <axp-layout-description>{{\n formatService.format(vm.adapter()?.description ?? '', 'string', vm.rootContext()) | translate | async\n }}</axp-layout-description>\n </axp-layout-header>\n <axp-layout-content>\n <ax-tabs\n #sidebarTabs\n class=\"axp-vertical-tabs\"\n [look]=\"'with-line-color'\"\n [location]=\"'end'\"\n [fitParent]=\"true\"\n (onActiveTabChanged)=\"handleSidebarTabChanged($event)\"\n >\n @for (item of vm.adapter()?.pages; track item.id) {\n <ax-tab-item\n [key]=\"item.id\"\n [text]=\"(formatService.format(item.label, 'string', vm.rootContext()) | translate | async)!\"\n [active]=\"vm.currentPage()?.id === item.id\"\n >\n <ax-prefix>\n <ax-icon icon=\"far ${{ item.icon }}\"></ax-icon>\n </ax-prefix>\n </ax-tab-item>\n }\n </ax-tabs>\n </axp-layout-content>\n </axp-layout-start-side>\n }\n <!-- Content Section -->\n <axp-page-content class=\"ax-flex ax-flex-row ax-gap-4\">\n @if (vm.showPages()) {\n <axp-layout-list class=\"ax-w-full\">\n @for (item of vm.adapter()?.pages; track $index) {\n <axp-layout-list-item (click)=\"handleSelectPage(item)\">\n <axp-layout-prefix>\n <ax-icon icon=\"far ${{ item.icon }}\" class=\"ax-text-gray-500\"></ax-icon>\n </axp-layout-prefix>\n <axp-layout-content>\n <a class=\"ax-font-semibold\">{{\n formatService.format(item.label, 'string', vm.rootContext()) | translate | async\n }}</a>\n </axp-layout-content>\n <axp-layout-suffix>\n <ax-icon icon=\" far fa-chevron-right\" class=\"ax-text-gray-400\"></ax-icon>\n </axp-layout-suffix>\n </axp-layout-list-item>\n }\n </axp-layout-list>\n } @else {\n @if (vm.isBusy()) {\n <div class=\"ax-flex ax-h-full ax-w-full ax-items-center ax-justify-center\">\n <ax-loading></ax-loading>\n </div>\n } @else {\n <ax-form class=\"ax-h-full ax-w-full\" #form>\n <axp-widgets-container\n #widgetsContainerRef\n [context]=\"vm.context()\"\n (onContextChanged)=\"handleOnContextChanged($event)\"\n >\n <div class=\"ax-flex ax-flex-col ax-w-full ax-gap-4 ax-h-full\">\n @for (item of vm.currentPageContent(); track pageContentTrackKey($any(item), $index)) {\n @if ('type' in item && item.type === 'component') {\n <!-- Component-based content -->\n <div\n axp-page-component-renderer\n [componentKey]=\"$any(item).componentKey\"\n [rootContext]=\"vm.rootContext()\"\n [pageConfig]=\"$any(item).pageConfig\"\n [options]=\"$any(item).options\"\n (componentReady)=\"recompute()\"\n ></div>\n } @else {\n <!-- Widget-based content -->\n <ng-container axp-widget-renderer [node]=\"$any(item)\" [mode]=\"$any(item).mode ?? 'view'\"></ng-container>\n }\n }\n @if (vm.currentPage()?.tabs?.length) {\n <div class=\"axp-horizontal-tabs\">\n <ax-tabs [location]=\"'bottom'\" [fitParent]=\"false\" look=\"classic\">\n @for (tab of vm.currentPageTabs(); track $index) {\n <ax-tab-item\n (onClick)=\"vm.setCurrentTab(tab)\"\n [text]=\"(tab.title | translate | async)!\"\n [active]=\"vm.currentTab()?.id === tab.id\"\n >\n <ax-prefix>\n <ax-icon icon=\"far ${{ tab.icon }}\"></ax-icon>\n </ax-prefix>\n </ax-tab-item>\n }\n </ax-tabs>\n <div class=\"content\">\n @if (vm.currentTab()) {\n @for (content of vm.activeTabContent(); track content.name) {\n <ng-container axp-widget-renderer [node]=\"content\" [mode]=\"content.mode ?? 'view'\">\n </ng-container>\n }\n }\n </div>\n </div>\n }\n </div>\n </axp-widgets-container>\n </ax-form>\n }\n }\n </axp-page-content>\n\n <!-- Footer Section -->\n @if (hasFooter()) {\n <axp-page-footer class=\"--animated\">\n <axp-layout-suffix>\n <!-- secondary footer actions -->\n @if (hasVisibleFooterSecondaryActions()) {\n <ax-button\n [class.ax-sm]=\"deviceService.isSmall()\"\n [iconOnly]=\"deviceService.isSmall()\"\n [text]=\"'@general:terms.interface.actions' | translate | async\"\n [look]=\"deviceService.isSmall() ? 'blank' : 'solid'\"\n [color]=\"'default'\"\n >\n <ax-prefix>\n <i class=\"fa-solid fa-ellipsis-vertical\"></i>\n </ax-prefix>\n <ax-dropdown-panel #panel>\n <ax-button-item-list>\n @for (action of footerSecondaryActions(); track $index) {\n @if (isFooterActionVisible(action)) {\n <ax-button-item\n [text]=\"(t(action.title) | async)!\"\n [color]=\"action.color\"\n [disabled]=\"isFooterActionDisabled(action)\"\n (onClick)=\"execute(action.command!)\"\n >\n <ax-prefix>\n <ax-icon icon=\"fa-light {{ action.icon }}\"></ax-icon>\n </ax-prefix>\n </ax-button-item>\n @if (action.break) {\n <ax-divider></ax-divider>\n }\n }\n }\n </ax-button-item-list>\n </ax-dropdown-panel>\n </ax-button>\n }\n\n @if (hasVisibleFooterPrimaryActions()) {\n @if (showSaveDiscardFooter()) {\n @if (rejectFooterCommand(); as reject) {\n <ax-button\n [class.ax-sm]=\"deviceService.isSmall()\"\n [disabled]=\"vm.isSaving()\"\n [text]=\"(t(reject.title) | async)!\"\n [look]=\"'solid'\"\n [color]=\"reject.color\"\n (onClick)=\"execute(reject.command)\"\n >\n <ax-prefix>\n <i class=\"{{ reject.icon }}\"></i>\n </ax-prefix>\n </ax-button>\n }\n @if (acceptFooterCommand(); as accept) {\n <ax-button\n [class.ax-sm]=\"deviceService.isSmall()\"\n [disabled]=\"vm.isSaving()\"\n [text]=\"(t(accept.title) | async)!\"\n [look]=\"'solid'\"\n [color]=\"accept.color\"\n (onClick)=\"execute(accept.command)\"\n >\n <ax-prefix>\n <i class=\"{{ accept.icon }}\"></i>\n </ax-prefix>\n </ax-button>\n }\n }\n @for (action of footerPrimaryActions(); track $index) {\n @if (isFooterActionVisible(action)) {\n <ax-button\n [class.ax-sm]=\"deviceService.isSmall()\"\n [disabled]=\"isFooterActionDisabled(action)\"\n [text]=\"(t(action.title) | async)!\"\n [look]=\"'solid'\"\n [color]=\"action.color\"\n (onClick)=\"execute(action.command!)\"\n >\n <ax-prefix>\n <i class=\"{{ action.icon }}\"></i>\n </ax-prefix>\n @if (action?.items) {\n <ax-dropdown-panel #panel>\n <ax-button-item-list>\n @for (sub of action?.items; track $index) {\n @if (isFooterActionVisible(sub)) {\n <ax-button-item\n [text]=\"(t(sub.title) | async)!\"\n [color]=\"sub.color\"\n [disabled]=\"isFooterActionDisabled(sub)\"\n (onClick)=\"execute(sub.command!)\"\n >\n <ax-prefix>\n <ax-icon icon=\"fa-light {{ sub.icon }}\"></ax-icon>\n </ax-prefix>\n </ax-button-item>\n @if (sub.break) {\n <ax-divider></ax-divider>\n }\n }\n }\n </ax-button-item-list>\n </ax-dropdown-panel>\n }\n </ax-button>\n }\n }\n }\n </axp-layout-suffix>\n </axp-page-footer>\n }\n</axp-page-layout>\n", styles: ["axp-layout-details-view .axp-vertical-tabs{--ax-comp-tabs-default-border-radius: 0}axp-layout-details-view .axp-vertical-tabs ax-tab-item{margin-top:0!important;margin-bottom:0!important;padding-top:.75rem!important;padding-bottom:.75rem!important;font-weight:600!important}axp-layout-details-view .axp-vertical-tabs ax-tab-item>div{display:flex!important;align-items:center!important}axp-layout-details-view .axp-vertical-tabs ax-tab-item>div .ax-tab-item-text{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}axp-layout-details-view .axp-horizontal-tabs{display:flex;width:100%;flex-direction:column}axp-layout-details-view .axp-horizontal-tabs .content{margin-top:1rem;margin-bottom:1rem;display:block;width:100%}axp-layout-details-view axp-layout-section axp-layout-content{display:flex;width:100%;flex-direction:column}axp-layout-details-view axp-layout-section axp-layout-content>:not([hidden])~:not([hidden]){--tw-divide-y-reverse: 0;border-top-width:calc(1px * calc(1 - var(--tw-divide-y-reverse)));border-bottom-width:calc(1px * var(--tw-divide-y-reverse));border-style:dashed;--tw-divide-opacity: 1;border-color:rgba(var(--ax-sys-color-border-lightest-surface),var(--tw-divide-opacity, 1))}axp-layout-details-view axp-layout-section axp-layout-content .--item-container{align-items:center;gap:1rem;padding:1rem}@media(min-width:1024px){axp-layout-details-view axp-layout-section axp-layout-content .--item-container{padding-left:1.5rem;padding-right:1.5rem}}@media(min-width:1280px){axp-layout-details-view axp-layout-section axp-layout-content .--item-container{padding-left:2rem;padding-right:2rem}}@media(min-width:1536px){axp-layout-details-view axp-layout-section axp-layout-content .--item-container{padding-left:2.5rem;padding-right:2.5rem}}axp-layout-details-view axp-layout-section axp-layout-content .--item-container{display:grid;grid-template-columns:repeat(12,minmax(0,1fr))}axp-layout-details-view axp-layout-section axp-layout-content .--item-container axp-layout-description{margin-top:.5rem!important;font-size:.75rem!important;line-height:1rem!important}axp-layout-details-view axp-page-content ax-form form{height:100%}axp-layout-details-view axp-layout-start-side{border-inline-end-width:1px}\n"] }]
2179
2393
  }], propDecorators: { adapter: [{ type: i0.Input, args: [{ isSignal: true, alias: "adapter", required: true }] }], form: [{ type: i0.ViewChild, args: ['form', { isSignal: true }] }], widgetContainer: [{ type: i0.ViewChild, args: [i0.forwardRef(() => AXPWidgetContainerComponent), { isSignal: true }] }], sidebarTabs: [{ type: i0.ViewChild, args: ['sidebarTabs', { isSignal: true }] }] } });
2180
2394
 
2181
2395
  //#region ---- Details View Can Deactivate Guard ----