@zambon-dev/ui 0.0.0-stage → 0.0.1-bootstrap.0

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 (43) hide show
  1. package/CHANGELOG.md +2325 -0
  2. package/README.md +81 -3
  3. package/fesm2022/zambon-dev-ui-framework.mjs +3997 -0
  4. package/fesm2022/zambon-dev-ui-framework.mjs.map +1 -0
  5. package/fesm2022/zambon-dev-ui-library.mjs +4280 -0
  6. package/fesm2022/zambon-dev-ui-library.mjs.map +1 -0
  7. package/fesm2022/zambon-dev-ui-shared.mjs +1802 -0
  8. package/fesm2022/zambon-dev-ui-shared.mjs.map +1 -0
  9. package/fesm2022/zambon-dev-ui-testing.mjs +80 -0
  10. package/fesm2022/zambon-dev-ui-testing.mjs.map +1 -0
  11. package/fesm2022/zambon-dev-ui.mjs +8 -0
  12. package/fesm2022/zambon-dev-ui.mjs.map +1 -0
  13. package/i18n/framework/en.json +63 -0
  14. package/i18n/framework/pt.json +63 -0
  15. package/i18n/shared/external-content/en.json +12 -0
  16. package/i18n/shared/external-content/pt.json +12 -0
  17. package/i18n/shared/language-selector/en.json +5 -0
  18. package/i18n/shared/language-selector/pt.json +5 -0
  19. package/i18n/shared/login/en.json +14 -0
  20. package/i18n/shared/login/pt.json +14 -0
  21. package/i18n/shared/operations-history/en.json +5 -0
  22. package/i18n/shared/operations-history/pt.json +5 -0
  23. package/i18n/shared/services-history/en.json +14 -0
  24. package/i18n/shared/services-history/pt.json +14 -0
  25. package/i18n/shared/top-bar/en.json +7 -0
  26. package/i18n/shared/top-bar/pt.json +7 -0
  27. package/package.json +106 -4
  28. package/styles/common.scss +10 -0
  29. package/styles/components/button.scss +101 -0
  30. package/styles/components/card.scss +13 -0
  31. package/styles/components/dropdown-static.scss +35 -0
  32. package/styles/components/input-group.scss +44 -0
  33. package/styles/components/input.scss +99 -0
  34. package/styles/components/label.scss +3 -0
  35. package/styles/components/row.scss +48 -0
  36. package/styles/mixins/background.scss +31 -0
  37. package/styles/theme.css +75 -0
  38. package/styles/variables.scss +60 -0
  39. package/types/zambon-dev-ui-framework.d.ts +1066 -0
  40. package/types/zambon-dev-ui-library.d.ts +1233 -0
  41. package/types/zambon-dev-ui-shared.d.ts +655 -0
  42. package/types/zambon-dev-ui-testing.d.ts +56 -0
  43. package/types/zambon-dev-ui.d.ts +3 -0
@@ -0,0 +1,3997 @@
1
+ import * as i0 from '@angular/core';
2
+ import { inject, ApplicationRef, Inject, signal, Injectable, viewChild, input, model, computed, effect, Component, output, NgModule, forwardRef, afterEveryRender, ViewChild, InjectionToken, Input, EventEmitter, Output } from '@angular/core';
3
+ import { toSignal, toObservable } from '@angular/core/rxjs-interop';
4
+ import * as i1$3 from '@zambon-dev/ui/library';
5
+ import { RibbonGroupChild, RibbonButtonComponent, DataProviderService, FormService, ModalComponent, DataGridDataset, DisplayControls, RibbonComponent, GroupContainerComponent, RibbonGroupComponent, DataGridConfigsProvider, MultiSelectResultDataset, RouterFormatter, MultiEditorComponent, MultiEditorDataset, MultiSelectComponent } from '@zambon-dev/ui/library';
6
+ export { DateValidators } from '@zambon-dev/ui/library';
7
+ import { ReplaySubject, take, map, switchMap, of, Subject, startWith, takeUntil, filter, skip, tap, throwError } from 'rxjs';
8
+ import { __decorate } from 'tslib';
9
+ import * as i1 from '@angular/common';
10
+ import { NgClass, NgTemplateOutlet } from '@angular/common';
11
+ import * as i1$1 from '@angular/router';
12
+ import { Router, NavigationEnd, RouterModule, ActivatedRoute } from '@angular/router';
13
+ import { TranslatePipe, TranslateService, TranslateLoader, provideTranslateService } from '@ngx-translate/core';
14
+ import * as i1$2 from '@angular/forms';
15
+ import { ControlContainer, FormGroupDirective, FormBuilder } from '@angular/forms';
16
+ import { HttpBackend, HttpErrorResponse } from '@angular/common/http';
17
+ import { MultiTranslateHttpLoader } from 'ngx-translate-multi-http-loader';
18
+
19
+ const ADMINISTRATIVE_MASTER_ACTION = 'AdministrativeMaster';
20
+ class AuthService {
21
+ jwtHelper;
22
+ tabService;
23
+ //#region ViewChilds, Inputs, Outputs
24
+ //#endregion
25
+ //#region Variables
26
+ actionsCache$ = new ReplaySubject(1);
27
+ isActionsCacheInitialized = false;
28
+ get isAuthenticated() {
29
+ return this.username !== null;
30
+ }
31
+ get isTokenExpired() {
32
+ return this.token !== null && this.jwtHelper.isTokenExpired(this.token);
33
+ }
34
+ get token() {
35
+ return localStorage.getItem('token') ?? sessionStorage.getItem('token');
36
+ }
37
+ get userID() {
38
+ return 0;
39
+ }
40
+ get refreshToken() {
41
+ return localStorage.getItem('refreshToken') ?? sessionStorage.getItem('refreshToken');
42
+ }
43
+ get userInfo() {
44
+ return localStorage.getItem('userInfo') ?? sessionStorage.getItem('userInfo');
45
+ }
46
+ get username() {
47
+ return localStorage.getItem('username') ?? sessionStorage.getItem('username');
48
+ }
49
+ //#endregion
50
+ //#region Constructor and Angular life cycle methods
51
+ constructor(jwtHelper, tabService) {
52
+ this.jwtHelper = jwtHelper;
53
+ this.tabService = tabService;
54
+ }
55
+ //#endregion
56
+ //#region Event handlers
57
+ //#endregion
58
+ //#region Public methods
59
+ checkActionIsAllowed(actionToCheck) {
60
+ if (!this.isActionsCacheInitialized) {
61
+ this.isActionsCacheInitialized = true;
62
+ this.getActions()
63
+ .pipe(take(1))
64
+ .subscribe((actions) => {
65
+ this.actionsCache$.next(actions);
66
+ });
67
+ }
68
+ return this.actionsCache$.pipe(map((actions) => actions.includes(this.adminAction) || actions.includes(actionToCheck)));
69
+ }
70
+ checkActionsAreAllowed(actionsToCheck, checkForAdministrativeMaster = true) {
71
+ if (!this.isActionsCacheInitialized) {
72
+ this.isActionsCacheInitialized = true;
73
+ this.getActions()
74
+ .pipe(take(1))
75
+ .subscribe((actions) => {
76
+ this.actionsCache$.next(actions);
77
+ });
78
+ }
79
+ return this.actionsCache$.pipe(switchMap((actions) => of(actionsToCheck.map((actionsToCheck) => (checkForAdministrativeMaster && actions.indexOf(ADMINISTRATIVE_MASTER_ACTION) >= 0) ||
80
+ actions.indexOf(actionsToCheck) >= 0))));
81
+ }
82
+ signOut() {
83
+ localStorage.clear();
84
+ sessionStorage.clear();
85
+ this.tabService.closeAllTabs();
86
+ this.resetActionsCache();
87
+ }
88
+ setStorage(key, value, useLocalStorage = null) {
89
+ if (useLocalStorage === null) {
90
+ useLocalStorage = sessionStorage.getItem(key) === null;
91
+ }
92
+ if (value) {
93
+ if (useLocalStorage) {
94
+ localStorage.setItem(key, value);
95
+ }
96
+ else {
97
+ sessionStorage.setItem(key, value);
98
+ }
99
+ }
100
+ else {
101
+ localStorage.removeItem(key);
102
+ sessionStorage.removeItem(key);
103
+ }
104
+ }
105
+ //#endregion
106
+ //#region Private methods
107
+ resetActionsCache() {
108
+ this.isActionsCacheInitialized = false;
109
+ this.actionsCache$ = new ReplaySubject(1);
110
+ }
111
+ }
112
+
113
+ let CustomReuseStrategy = class CustomReuseStrategy {
114
+ //#region ViewChilds, Inputs, Outputs
115
+ //#endregion
116
+ //#region Variables
117
+ clones = {};
118
+ redirects = {};
119
+ tabService;
120
+ applicationRef = inject(ApplicationRef);
121
+ cachedHandles = {};
122
+ componentIDs = new WeakMap();
123
+ nextComponentID = 1;
124
+ //#endregion
125
+ //#region Properties
126
+ //#endregion
127
+ //#region Constructor and Angular life cycle methods
128
+ //#endregion
129
+ //#region Event handlers
130
+ //#endregion
131
+ //#region Public methods
132
+ clearAllHandles() {
133
+ this.cachedHandles = {};
134
+ }
135
+ clearHandle(url) {
136
+ const keysToRemove = Object.keys(this.cachedHandles).filter((key) => key.startsWith(`${url}-`));
137
+ keysToRemove.forEach((key) => {
138
+ const handle = this.cachedHandles[key];
139
+ if (handle) {
140
+ if (handle?.componentRef?.instance?.ngOnDestroy) {
141
+ handle.componentRef.instance.ngOnDestroy();
142
+ }
143
+ }
144
+ delete this.cachedHandles[key];
145
+ });
146
+ }
147
+ retrieve(route) {
148
+ if (!route.component) {
149
+ return null;
150
+ }
151
+ const cacheKey = this.getCacheKey(route);
152
+ const handle = this.cachedHandles[cacheKey];
153
+ if (handle) {
154
+ // Schedule global change detection after Angular finishes reattaching the component.
155
+ // Without this, reattached components may render stale views (e.g., empty ribbon)
156
+ // because NgTemplateOutlet does not re-create its embedded view automatically.
157
+ // ApplicationRef.tick() ensures the entire app tree is checked, including embedded views.
158
+ setTimeout(() => {
159
+ this.applicationRef.tick();
160
+ });
161
+ }
162
+ return handle;
163
+ }
164
+ shouldAttach(route) {
165
+ const url = this.getUrlFromRoute(route);
166
+ const hasComponent = !!route.component;
167
+ if ((this.tabService?.isUrlOpen(url) ?? false) && hasComponent) {
168
+ const cacheKey = this.getCacheKey(route);
169
+ const isCached = !!this.cachedHandles[cacheKey];
170
+ return isCached;
171
+ }
172
+ return false;
173
+ }
174
+ shouldDetach(route) {
175
+ const url = this.getUrlFromRoute(route);
176
+ const hasComponent = !!route.component;
177
+ return (this.tabService?.isUrlOpen(url) ?? false) && hasComponent;
178
+ }
179
+ shouldReuseRoute(future, current) {
180
+ const futureUrl = this.getUrlFromRoute(future);
181
+ const currentUrl = this.getUrlFromRoute(current);
182
+ return ((futureUrl === currentUrl && future.routeConfig === current.routeConfig) ||
183
+ this.redirects[currentUrl] === futureUrl ||
184
+ this.clones[futureUrl] === currentUrl);
185
+ }
186
+ store(route, handle) {
187
+ const cacheKey = this.getCacheKey(route);
188
+ this.cachedHandles[cacheKey] = handle;
189
+ }
190
+ //#endregion
191
+ //#region Private methods
192
+ getCacheKey(route) {
193
+ let url = this.getUrlFromRoute(route);
194
+ if (this.clones[url]) {
195
+ url = this.clones[url];
196
+ }
197
+ // The component is identified by identity, never by name. A production build renames every
198
+ // class: esbuild wraps each component as `X = (() => { class i { } return i; })()`, so
199
+ // `component.name` is a single mangled letter that the whole chunk shares. Nested empty-path
200
+ // routes already resolve to the same URL, because an empty path contributes no segment, so a
201
+ // key built from the name collapsed a screen's tab view and its list into one entry: storing
202
+ // one overwrote the other, both levels were then handed the same detached view, and Angular
203
+ // blew the stack building a router state whose node was its own descendant. That reproduced
204
+ // only in a minified build, and only on the screens whose chunk happened to mangle to the
205
+ // same letter as the framework's.
206
+ //
207
+ // The depth is part of the key too, so two sibling routes sharing a component type under one
208
+ // URL cannot collide either.
209
+ return `${url}-${this.getRouteDepth(route)}-${this.getComponentID(route.component)}`;
210
+ }
211
+ getComponentID(component) {
212
+ if (typeof component !== 'function') {
213
+ return 0;
214
+ }
215
+ let componentID = this.componentIDs.get(component);
216
+ if (componentID === undefined) {
217
+ componentID = this.nextComponentID++;
218
+ this.componentIDs.set(component, componentID);
219
+ }
220
+ return componentID;
221
+ }
222
+ getRouteDepth(route) {
223
+ let depth = 0;
224
+ let current = route.parent;
225
+ while (current) {
226
+ depth++;
227
+ current = current.parent;
228
+ }
229
+ return depth;
230
+ }
231
+ getUrlFromRoute(route) {
232
+ const segments = [];
233
+ const buildRoute = (route) => {
234
+ if (route) {
235
+ if (route.url.length) {
236
+ segments.push(...route.url.map((segment) => segment.path));
237
+ }
238
+ buildRoute(route.parent);
239
+ }
240
+ };
241
+ buildRoute(route);
242
+ return `/${segments.reverse().join('/')}`;
243
+ }
244
+ };
245
+ CustomReuseStrategy = __decorate([
246
+ Inject({})
247
+ ], CustomReuseStrategy);
248
+
249
+ class TabViewService {
250
+ //#region ViewChilds, Inputs, Outputs
251
+ //#endregion
252
+ //#region Variables
253
+ onUpdateRibbonTemplate = new ReplaySubject(1);
254
+ onViewChanged = new Subject();
255
+ _activeView = signal(undefined, /* @ts-ignore */
256
+ ...(ngDevMode ? [{ debugName: "_activeView" }] : /* istanbul ignore next */ []));
257
+ //#endregion
258
+ //#region Properties
259
+ get activeView() {
260
+ return this._activeView() ?? '';
261
+ }
262
+ //#endregion
263
+ //#region Constructor and Angular life cycle methods
264
+ //#endregion
265
+ //#region Event handlers
266
+ //#endregion
267
+ //#region Public methods
268
+ setActiveView(viewId) {
269
+ if (viewId === this._activeView()) {
270
+ return;
271
+ }
272
+ this._activeView.set(viewId);
273
+ this.onViewChanged.next(viewId);
274
+ }
275
+ updateRibbonTemplate(template) {
276
+ this.onUpdateRibbonTemplate.next(template);
277
+ }
278
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: TabViewService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
279
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: TabViewService });
280
+ }
281
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: TabViewService, decorators: [{
282
+ type: Injectable
283
+ }] });
284
+
285
+ class TabService {
286
+ applicationRef;
287
+ location;
288
+ router;
289
+ //#region ViewChilds, Inputs, Outputs
290
+ //#endregion
291
+ //#region Variables
292
+ // Tab state is a tree of arrays mutated in place -- spliced, pushed, and with ITab objects
293
+ // whose titles are rewritten as they resolve. A signal over the array could not see any of
294
+ // that, so this counter stands in for it: every write below bumps it, and the public getters
295
+ // read it, which is what makes a template that reads them re-render. It is a bridge, not the
296
+ // destination -- the tab state itself still wants to become signals. See
297
+ // docs/guides/reactivity.md.
298
+ revision = signal(0, /* @ts-ignore */
299
+ ...(ngDevMode ? [{ debugName: "revision" }] : /* istanbul ignore next */ []));
300
+ activeTabIndex = -1;
301
+ customReuseStrategy;
302
+ displayTitleIndices = [];
303
+ openTabs = [];
304
+ //#endregion
305
+ //#region Properties
306
+ get activeTabHistory() {
307
+ this.revision();
308
+ return this.openTabs[this.activeTabIndex] ?? [];
309
+ }
310
+ get activeTabs() {
311
+ this.revision();
312
+ return this.openTabs.map((tabs) => tabs[0]) ?? [];
313
+ }
314
+ get activeTabsDisplayTitles() {
315
+ this.revision();
316
+ return this.openTabs.map((tabs, i) => {
317
+ const displayIndex = this.displayTitleIndices[i] ?? 0;
318
+ return tabs[displayIndex]?.title;
319
+ });
320
+ }
321
+ get activeTabsLoadingStates() {
322
+ this.revision();
323
+ return this.openTabs.map((tabs, i) => {
324
+ const displayIndex = this.displayTitleIndices[i] ?? 0;
325
+ return tabs[displayIndex]?.isTitleLoading ?? true;
326
+ });
327
+ }
328
+ get activeTab() {
329
+ if (this.activeTabIndex < 0) {
330
+ return null;
331
+ }
332
+ return this.openTabs[this.activeTabIndex];
333
+ }
334
+ //#endregion
335
+ //#region Constructor and Angular life cycle methods
336
+ constructor(applicationRef, location, router, routeReuseStrategy) {
337
+ this.applicationRef = applicationRef;
338
+ this.location = location;
339
+ this.router = router;
340
+ this.customReuseStrategy = routeReuseStrategy;
341
+ this.customReuseStrategy.tabService = this;
342
+ }
343
+ //#endregion
344
+ //#region Event handlers
345
+ //#endregion
346
+ //#region Public methods
347
+ activateTab(tab) {
348
+ this.activeTabIndex = this.activeTabs.indexOf(tab);
349
+ this.touched();
350
+ this.navigateTo(this.currentViewOf(this.activeTabIndex));
351
+ }
352
+ cloneCurrentTab(url) {
353
+ if (!this.activeTab) {
354
+ return;
355
+ }
356
+ const tab = this.activeTab[this.activeTab.length - 1];
357
+ this.customReuseStrategy.clones[url] = tab.url;
358
+ if (tab.clones.indexOf(url) === -1) {
359
+ tab.clones.push(url);
360
+ }
361
+ this.navigateTo(tab, url);
362
+ }
363
+ closeActiveTab() {
364
+ this.closeTab(this.activeTabIndex);
365
+ }
366
+ closeAllTabs() {
367
+ this.openTabs = [];
368
+ this.touched();
369
+ this.displayTitleIndices = [];
370
+ this.touched();
371
+ this.activeTabIndex = -1;
372
+ this.touched();
373
+ this.customReuseStrategy.clearAllHandles();
374
+ }
375
+ closeTab(index) {
376
+ if (index > this.activeTabs.length - 1 || this.activeTabs.length === 0) {
377
+ return;
378
+ }
379
+ const wasActive = index === this.activeTabIndex;
380
+ const closedTab = this.openTabs[index];
381
+ this.openTabs.splice(index, 1);
382
+ this.touched();
383
+ this.displayTitleIndices.splice(index, 1);
384
+ this.touched();
385
+ if (wasActive) {
386
+ // Closed the active tab — pick an adjacent tab to focus, and put it back on the view it was
387
+ // left on. This used to navigate to activeTabs[i], which is that tab's *root*: a tab opened
388
+ // as a list and then drilled into a record went back to the list, under the record's own
389
+ // tab, until the tab was clicked again.
390
+ if (this.activeTabIndex >= this.openTabs.length) {
391
+ this.activeTabIndex = this.openTabs.length - 1;
392
+ this.touched();
393
+ }
394
+ // Undefined once the last tab is gone, which navigateTo answers with the home URL.
395
+ this.navigateTo(this.currentViewOf(this.activeTabIndex));
396
+ }
397
+ else if (index < this.activeTabIndex) {
398
+ // Closed a tab before the active one — shift index to keep the same tab active.
399
+ this.activeTabIndex--;
400
+ }
401
+ closedTab.forEach((tab) => {
402
+ if (!this.isUrlOpen(tab.url)) {
403
+ // Tab is not in use by any other tab, we're clear to remove.
404
+ this.customReuseStrategy.clearHandle(tab.url);
405
+ }
406
+ });
407
+ }
408
+ isTabActive(tab) {
409
+ return this.activeTabs[this.activeTabIndex] === tab;
410
+ }
411
+ isUrlActive(url, checkForClones = false) {
412
+ if (!this.activeTab) {
413
+ return false;
414
+ }
415
+ const tab = this.activeTab[this.activeTab.length - 1];
416
+ if (tab.url === url) {
417
+ return true;
418
+ }
419
+ else if (checkForClones) {
420
+ return tab.clones.indexOf(url) !== -1;
421
+ }
422
+ return false;
423
+ }
424
+ isUrlOpen(url) {
425
+ const tabs = this.openTabs.flatMap((tabs) => tabs);
426
+ return this.isTabExists(tabs, url);
427
+ }
428
+ navigateBackOrCloseActiveTab() {
429
+ if (!this.activeTab || this.activeTab.length === 0) {
430
+ return;
431
+ }
432
+ if (this.activeTab.length <= 1) {
433
+ this.closeTab(this.activeTabIndex);
434
+ }
435
+ else {
436
+ const previousEntry = this.activeTab[this.activeTab.length - 2];
437
+ this.navigateCurrentTabBack(previousEntry);
438
+ }
439
+ }
440
+ navigateCurrentTab(tab) {
441
+ if (!this.activeTab) {
442
+ return;
443
+ }
444
+ // Check other tabs for entity match first, then fall back to current behavior.
445
+ if (tab.entityBaseUrl) {
446
+ const entityMatchIndex = this.findTabIndexByEntityMatch(tab.entityBaseUrl, this.activeTabIndex);
447
+ if (entityMatchIndex !== -1) {
448
+ this.activeTabIndex = entityMatchIndex;
449
+ this.touched();
450
+ const otherTab = this.openTabs[entityMatchIndex];
451
+ this.navigateTo(otherTab[otherTab.length - 1]);
452
+ return;
453
+ }
454
+ }
455
+ else {
456
+ // Fallback for tabs without entityBaseUrl (existing behavior).
457
+ const showingIndex = this.findTabIndexByCurrentView(tab.url, this.activeTabIndex, true);
458
+ if (showingIndex !== -1) {
459
+ this.activeTabIndex = showingIndex;
460
+ this.touched();
461
+ const otherTab = this.openTabs[showingIndex];
462
+ this.navigateTo(otherTab[otherTab.length - 1]);
463
+ return;
464
+ }
465
+ }
466
+ // Check if URL already in current tab's stack — navigate back.
467
+ const tabIsAlreadyOpen = this.findTab(this.activeTab, tab.url);
468
+ if (tabIsAlreadyOpen) {
469
+ this.navigateCurrentTabBack(tabIsAlreadyOpen);
470
+ return;
471
+ }
472
+ // Inherit title from an existing tab entry with the same URL, if available.
473
+ this.inheritTitleIfKnown(tab);
474
+ // Push new entry onto the stack.
475
+ let clonedUrl = undefined;
476
+ if (tab.clones.length > 0) {
477
+ clonedUrl = tab.clones[0];
478
+ this.customReuseStrategy.clones[clonedUrl] = tab.url;
479
+ }
480
+ this.activeTab.push(tab);
481
+ this.displayTitleIndices[this.activeTabIndex] = this.activeTab.length - 1;
482
+ this.touched();
483
+ this.navigateTo(tab, clonedUrl);
484
+ }
485
+ navigateCurrentTabBack(tab) {
486
+ if (!this.activeTab) {
487
+ return;
488
+ }
489
+ // If another tab is currently showing this URL, focus it instead.
490
+ const otherTabIndex = this.findTabIndexByCurrentView(tab.url, this.activeTabIndex);
491
+ if (otherTabIndex !== -1) {
492
+ this.activeTabIndex = otherTabIndex;
493
+ this.touched();
494
+ const otherTab = this.openTabs[otherTabIndex];
495
+ this.navigateTo(otherTab[otherTab.length - 1]);
496
+ return;
497
+ }
498
+ const lastIndex = this.activeTab.lastIndexOf(tab);
499
+ if (lastIndex !== -1) {
500
+ const removedEntries = this.activeTab.slice(lastIndex + 1);
501
+ this.openTabs[this.activeTabIndex] = this.activeTab.slice(0, lastIndex + 1);
502
+ this.touched();
503
+ if (this.displayTitleIndices[this.activeTabIndex] > lastIndex) {
504
+ this.displayTitleIndices[this.activeTabIndex] = lastIndex;
505
+ this.touched();
506
+ }
507
+ // Every open tab keeps its views in the reuse cache, the one it shows or one in its history,
508
+ // so a view leaves the cache only once no tab has its URL open. This cleared every removed
509
+ // URL unconditionally and could take a view out from under another tab; closeTab and
510
+ // replaceCurrentTabSubView already made this check. The history is cut first, so the removed
511
+ // entries no longer count as open themselves.
512
+ removedEntries.forEach((entry) => {
513
+ if (!this.isUrlOpen(entry.url)) {
514
+ this.customReuseStrategy.clearHandle(entry.url);
515
+ }
516
+ });
517
+ }
518
+ this.navigateTo(tab);
519
+ }
520
+ navigateTo(tab, clonedUrl) {
521
+ let url = tab?.url ?? '/';
522
+ if (clonedUrl) {
523
+ url = clonedUrl;
524
+ }
525
+ this.router.navigate([url], { queryParams: tab?.queryParams }).then((navigated) => {
526
+ this.customReuseStrategy.redirects = {};
527
+ // Clean up clone mapping after successful clone-based navigation.
528
+ if (navigated && this.customReuseStrategy.clones[url]) {
529
+ delete this.customReuseStrategy.clones[url];
530
+ }
531
+ this.applicationRef.tick();
532
+ });
533
+ }
534
+ openTab(tab) {
535
+ // 1. Exact URL match at top-of-stack — focus that tab.
536
+ const exactMatchIndex = this.findTabIndexByCurrentView(tab.url);
537
+ if (exactMatchIndex !== -1) {
538
+ this.activeTabIndex = exactMatchIndex;
539
+ this.touched();
540
+ const currentTab = this.openTabs[exactMatchIndex];
541
+ this.navigateTo(currentTab[currentTab.length - 1]);
542
+ return;
543
+ }
544
+ // 2. Entity match (only when entityBaseUrl is set) — focus that tab, keep current view.
545
+ if (tab.entityBaseUrl) {
546
+ const entityMatchIndex = this.findTabIndexByEntityMatch(tab.entityBaseUrl);
547
+ if (entityMatchIndex !== -1) {
548
+ this.activeTabIndex = entityMatchIndex;
549
+ this.touched();
550
+ const existingTab = this.openTabs[entityMatchIndex];
551
+ this.navigateTo(existingTab[existingTab.length - 1]);
552
+ return;
553
+ }
554
+ }
555
+ // 3. No match — open a new tab.
556
+ this.inheritTitleIfKnown(tab);
557
+ this.openTabs.push([tab]);
558
+ this.touched();
559
+ this.displayTitleIndices.push(0);
560
+ this.touched();
561
+ this.activeTabIndex = this.openTabs.length - 1;
562
+ this.touched();
563
+ this.navigateTo(tab);
564
+ }
565
+ replaceCurrentTabSubView(baseUrl, tab) {
566
+ if (!this.activeTab || this.activeTab.length === 0) {
567
+ return;
568
+ }
569
+ // Find the anchor entry (the detail view whose URL matches baseUrl).
570
+ // Keep all entries up to and including the anchor.
571
+ const anchorIndex = this.activeTab.findIndex((t) => t.url === baseUrl);
572
+ const keepUntil = anchorIndex !== -1 ? anchorIndex + 1 : 1;
573
+ const keptEntries = this.activeTab.slice(0, keepUntil);
574
+ const removedEntries = this.activeTab.slice(keepUntil);
575
+ // Update history: keep entries up to anchor, optionally add new sub-view
576
+ if (tab) {
577
+ // Propagate entityBaseUrl from the anchor so entity matching works on sub-views.
578
+ const anchor = keptEntries[anchorIndex !== -1 ? anchorIndex : 0];
579
+ if (!tab.entityBaseUrl && anchor?.entityBaseUrl) {
580
+ tab.entityBaseUrl = anchor.entityBaseUrl;
581
+ }
582
+ this.openTabs[this.activeTabIndex] = [...keptEntries, tab];
583
+ this.touched();
584
+ }
585
+ else {
586
+ this.openTabs[this.activeTabIndex] = keptEntries;
587
+ this.touched();
588
+ }
589
+ // Clear reuse-strategy handles for removed entries no longer open anywhere
590
+ removedEntries.forEach((entry) => {
591
+ if (!this.isUrlOpen(entry.url)) {
592
+ this.customReuseStrategy.clearHandle(entry.url);
593
+ }
594
+ });
595
+ // Navigate to the topmost entry
596
+ const current = this.openTabs[this.activeTabIndex];
597
+ this.navigateTo(current[current.length - 1]);
598
+ }
599
+ redirectCurrentTab(url, updateLocation = true) {
600
+ if (!this.activeTab) {
601
+ return;
602
+ }
603
+ const tab = this.activeTab[this.activeTab.length - 1];
604
+ // Update entityBaseUrl if it was pointing to the old URL (e.g., /new → /:id after save).
605
+ if (tab.entityBaseUrl === tab.url) {
606
+ tab.entityBaseUrl = url;
607
+ }
608
+ tab.url = url;
609
+ tab.queryParams = {};
610
+ if (updateLocation) {
611
+ this.location.replaceState(url);
612
+ }
613
+ }
614
+ setRouteRedirect(fromUrl, toUrl) {
615
+ this.customReuseStrategy.redirects[fromUrl] = toUrl;
616
+ }
617
+ updateActiveTabRootTitle(title) {
618
+ if (this.activeTab && this.activeTab.length > 0) {
619
+ this.activeTab[0].title = title;
620
+ this.touched();
621
+ }
622
+ }
623
+ updateTabTitle(url, title) {
624
+ this.openTabs
625
+ .flatMap((tabs) => tabs)
626
+ .filter((tab) => tab.url === url)
627
+ .forEach((tab) => {
628
+ tab.title = title;
629
+ this.touched();
630
+ });
631
+ }
632
+ //#endregion
633
+ //#region Private methods
634
+ /** Announces that the tab state changed, so views reading it re-render. */
635
+ touched() {
636
+ this.revision.update((revision) => revision + 1);
637
+ }
638
+ /**
639
+ * The view a tab is showing: the top of its history stack.
640
+ *
641
+ * Not `activeTabs[index]`, which is the stack's *root* -- the entry the strip takes each tab's
642
+ * label from. The two are the same only for a tab nobody has drilled into, which is why
643
+ * reaching for the wrong one survives most testing. Undefined for an index with no tab, which
644
+ * is what closing the last tab leaves behind.
645
+ */
646
+ currentViewOf(index) {
647
+ const history = this.openTabs[index];
648
+ return history?.[history.length - 1];
649
+ }
650
+ findTab(tabs, url) {
651
+ return tabs.find((tab) => this.matchTabUrl(tab, url));
652
+ }
653
+ findTabIndex(tabs, url) {
654
+ return tabs.findIndex((tab) => this.matchTabUrl(tab, url));
655
+ }
656
+ isTabExists(tabs, url) {
657
+ return tabs.some((tab) => this.matchTabUrl(tab, url));
658
+ }
659
+ findTabIndexByEntityMatch(entityBaseUrl, excludeIndex = -1) {
660
+ return this.openTabs.findIndex((tabStack, index) => {
661
+ if (index === excludeIndex) {
662
+ return false;
663
+ }
664
+ // Only check the current view (top of stack), not the full history.
665
+ const currentView = tabStack[tabStack.length - 1];
666
+ return currentView.entityBaseUrl === entityBaseUrl || currentView.url === entityBaseUrl;
667
+ });
668
+ }
669
+ findTabIndexByCurrentView(url, excludeIndex = -1, includeChildRoutes = false) {
670
+ return this.openTabs.findIndex((tabStack, index) => {
671
+ if (index === excludeIndex) {
672
+ return false;
673
+ }
674
+ const currentView = tabStack[tabStack.length - 1];
675
+ return this.matchTabUrl(currentView, url) || (includeChildRoutes && this.isChildRoute(currentView.url, url));
676
+ });
677
+ }
678
+ inheritTitleIfKnown(tab) {
679
+ if (tab.title || !tab.isTitleLoading) {
680
+ return;
681
+ }
682
+ const existing = this.openTabs
683
+ .flatMap((stack) => stack)
684
+ .find((entry) => entry.url === tab.url && !entry.isTitleLoading && !!entry.title);
685
+ if (existing) {
686
+ tab.title = existing.title;
687
+ this.touched();
688
+ tab.isTitleLoading = false;
689
+ this.touched();
690
+ }
691
+ }
692
+ isChildRoute(currentUrl, parentUrl) {
693
+ return currentUrl.startsWith(parentUrl + '/');
694
+ }
695
+ matchTabUrl(tab, url) {
696
+ return tab.url === url || tab.clones.indexOf(url) !== -1;
697
+ }
698
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: TabService, deps: [{ token: i0.ApplicationRef }, { token: i1.Location }, { token: i1$1.Router }, { token: i1$1.RouteReuseStrategy }], target: i0.ɵɵFactoryTarget.Injectable });
699
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: TabService, providedIn: 'root' });
700
+ }
701
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: TabService, decorators: [{
702
+ type: Injectable,
703
+ args: [{
704
+ providedIn: 'root',
705
+ }]
706
+ }], ctorParameters: () => [{ type: i0.ApplicationRef }, { type: i1.Location }, { type: i1$1.Router }, { type: i1$1.RouteReuseStrategy }] });
707
+
708
+ // No strategy on purpose: Angular never inherits `changeDetection`, so one declared here would
709
+ // describe a class that is never instantiated, while every subclass compiles to whatever it
710
+ // declares -- which in v22 means OnPush. Correctness comes from the state below notifying
711
+ // Angular, never from this decorator. See docs/guides/reactivity.md.
712
+ class BaseButton extends RibbonGroupChild {
713
+ //#region ViewChilds, Inputs, Outputs
714
+ button = viewChild(RibbonButtonComponent, /* @ts-ignore */
715
+ ...(ngDevMode ? [{ debugName: "button" }] : /* istanbul ignore next */ []));
716
+ allowedActions = input([], /* @ts-ignore */
717
+ ...(ngDevMode ? [{ debugName: "allowedActions" }] : /* istanbul ignore next */ []));
718
+ // Models rather than inputs: a subclass writes all three from a constructor, a lifecycle hook
719
+ // or a subscription -- the filters button disables itself until its grid has loaded, the
720
+ // refresh button drives its own spinner, and the export, save and views buttons build their own
721
+ // option lists. A plain field told Angular nothing.
722
+ disabled = model(false, /* @ts-ignore */
723
+ ...(ngDevMode ? [{ debugName: "disabled" }] : /* istanbul ignore next */ []));
724
+ loading = model(false, /* @ts-ignore */
725
+ ...(ngDevMode ? [{ debugName: "loading" }] : /* istanbul ignore next */ []));
726
+ options = model([], /* @ts-ignore */
727
+ ...(ngDevMode ? [{ debugName: "options" }] : /* istanbul ignore next */ []));
728
+ iconSize = input('large', /* @ts-ignore */
729
+ ...(ngDevMode ? [{ debugName: "iconSize" }] : /* istanbul ignore next */ []));
730
+ //#endregion
731
+ //#region Variables
732
+ authService = inject(AuthService);
733
+ destroy$ = new Subject();
734
+ /**
735
+ * Whether a button is allowed is one question asked of one input, so it is one stream rather
736
+ * than two fields written from a subscription. It also runs once: the old setter fired the
737
+ * check and the constructor fired it again, before any input had arrived.
738
+ *
739
+ * While the answer is outstanding the button is visible but not yet loaded, which is what
740
+ * `RibbonButtonComponent` already does with an option whose access has not come back.
741
+ */
742
+ access = toSignal(toObservable(this.allowedActions).pipe(switchMap((allowedActions) => this.checkAccessIsAllowed(allowedActions))), { initialValue: { isLoaded: true, isVisible: true } });
743
+ /**
744
+ * The options whose access check has already been sent.
745
+ *
746
+ * Resolving one replaces that option with a copy carrying the answer, so the list below sees a
747
+ * new array and the check runs again over it. The copy has an answer and is skipped; this set
748
+ * is what stops an option still in flight from being asked a second time.
749
+ */
750
+ requestedOptions = new WeakSet();
751
+ //#endregion
752
+ //#region Properties
753
+ isAccessLoaded = computed(() => this.access().isLoaded, /* @ts-ignore */
754
+ ...(ngDevMode ? [{ debugName: "isAccessLoaded" }] : /* istanbul ignore next */ []));
755
+ isVisible = computed(() => this.access().isVisible, /* @ts-ignore */
756
+ ...(ngDevMode ? [{ debugName: "isVisible" }] : /* istanbul ignore next */ []));
757
+ get isButtonDisabled() {
758
+ return this.disabled() || !this.isAccessLoaded();
759
+ }
760
+ //#endregion
761
+ //#region Constructor and Angular life cycle methods
762
+ /**
763
+ * Asks for the access behind every option that declares one and has not been asked yet.
764
+ *
765
+ * An effect rather than a setter: `options` is written by this component's own subclasses as
766
+ * well as bound by a host application, and both have to reach the check.
767
+ */
768
+ optionsAccess = effect(() => {
769
+ const pendingOptions = this.options().filter((option) => (option.allowedActions?.length ?? 0) > 0 &&
770
+ option.isAccessAllowed === undefined &&
771
+ !this.requestedOptions.has(option));
772
+ for (const option of pendingOptions) {
773
+ this.requestedOptions.add(option);
774
+ this.authService
775
+ .checkActionsAreAllowed(option.allowedActions)
776
+ .pipe(take(1))
777
+ .subscribe((result) => {
778
+ this.setOptionAccess(option, result.some((isAllowed) => isAllowed === true));
779
+ });
780
+ }
781
+ }, /* @ts-ignore */
782
+ ...(ngDevMode ? [{ debugName: "optionsAccess" }] : /* istanbul ignore next */ []));
783
+ ngOnDestroy() {
784
+ this.destroy$.next(true);
785
+ this.destroy$.complete();
786
+ }
787
+ //#endregion
788
+ //#region Event handlers
789
+ //#endregion
790
+ //#region Public methods
791
+ startLoading() {
792
+ this.button()?.startLoading();
793
+ }
794
+ finishLoading(status) {
795
+ this.button()?.finishLoading(status);
796
+ }
797
+ //#endregion
798
+ //#region Private methods
799
+ checkAccessIsAllowed(allowedActions) {
800
+ if (allowedActions.length === 0) {
801
+ return of({ isLoaded: true, isVisible: true });
802
+ }
803
+ return this.authService.checkActionsAreAllowed(allowedActions).pipe(take(1), map((result) => ({
804
+ isLoaded: true,
805
+ isVisible: result.some((isAllowed) => isAllowed === true),
806
+ })), startWith({ isLoaded: false, isVisible: true }));
807
+ }
808
+ /**
809
+ * Replaces one option with a copy carrying its answer, leaving the others as they are.
810
+ *
811
+ * The list has to be a new array for the ribbon button to see it at all, and the untouched
812
+ * options have to keep their identity so the dropdown does not rebuild every row.
813
+ */
814
+ setOptionAccess(option, isAccessAllowed) {
815
+ this.options.update((options) => options.map((candidate) => candidate === option ? { ...candidate, isAccessAllowed } : candidate));
816
+ }
817
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: BaseButton, deps: null, target: i0.ɵɵFactoryTarget.Component });
818
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "22.1.7", type: BaseButton, isStandalone: true, selector: "ng-component", inputs: { allowedActions: { classPropertyName: "allowedActions", publicName: "allowedActions", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, loading: { classPropertyName: "loading", publicName: "loading", isSignal: true, isRequired: false, transformFunction: null }, options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: false, transformFunction: null }, iconSize: { classPropertyName: "iconSize", publicName: "iconSize", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { disabled: "disabledChange", loading: "loadingChange", options: "optionsChange" }, viewQueries: [{ propertyName: "button", first: true, predicate: RibbonButtonComponent, descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: '', isInline: true });
819
+ }
820
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: BaseButton, decorators: [{
821
+ type: Component,
822
+ args: [{ template: '' }]
823
+ }], propDecorators: { button: [{ type: i0.ViewChild, args: [i0.forwardRef(() => RibbonButtonComponent), { isSignal: true }] }], allowedActions: [{ type: i0.Input, args: [{ isSignal: true, alias: "allowedActions", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }, { type: i0.Output, args: ["disabledChange"] }], loading: [{ type: i0.Input, args: [{ isSignal: true, alias: "loading", required: false }] }, { type: i0.Output, args: ["loadingChange"] }], options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: false }] }, { type: i0.Output, args: ["optionsChange"] }], iconSize: [{ type: i0.Input, args: [{ isSignal: true, alias: "iconSize", required: false }] }] } });
824
+
825
+ // No strategy on purpose: Angular never inherits `changeDetection`, so one declared here would
826
+ // describe a class that is never instantiated, while every subclass compiles to whatever it
827
+ // declares -- which in v22 means OnPush. Correctness comes from the state below notifying
828
+ // Angular, never from this decorator. See docs/guides/reactivity.md.
829
+ class BaseFormViewButton extends BaseButton {
830
+ //#region ViewChilds, Inputs, Outputs
831
+ //#endregion
832
+ //#region Variables
833
+ dataProviderService;
834
+ formService;
835
+ //#endregion
836
+ //#region Properties
837
+ get isButtonDisabled() {
838
+ return super.isButtonDisabled || this.formService.loading;
839
+ }
840
+ //#endregion
841
+ //#region Constructor and Angular life cycle methods
842
+ constructor() {
843
+ super();
844
+ this.dataProviderService = inject((DataProviderService));
845
+ this.formService = inject(FormService);
846
+ }
847
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: BaseFormViewButton, deps: [], target: i0.ɵɵFactoryTarget.Component });
848
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "22.1.7", type: BaseFormViewButton, isStandalone: true, selector: "ng-component", usesInheritance: true, ngImport: i0, template: '', isInline: true });
849
+ }
850
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: BaseFormViewButton, decorators: [{
851
+ type: Component,
852
+ args: [{ template: '' }]
853
+ }], ctorParameters: () => [] });
854
+
855
+ class BackendFormValidationHelper {
856
+ static validateAllFormFields(httpErrorResponse, formGroup) {
857
+ if (!httpErrorResponse || !formGroup) {
858
+ return;
859
+ }
860
+ // Error 400 = Validation issues
861
+ if (httpErrorResponse.status === 400 && httpErrorResponse.error && httpErrorResponse.error.errors) {
862
+ const controls = Object.keys(formGroup.controls);
863
+ const validationErrors = httpErrorResponse.error.errors;
864
+ Object.keys(validationErrors).forEach((fieldName) => {
865
+ setTimeout(() => {
866
+ const controlName = controls.find((controlName) => controlName.toLocaleLowerCase() === fieldName.toLocaleLowerCase());
867
+ if (!controlName || !formGroup.get(controlName)) {
868
+ return;
869
+ }
870
+ formGroup.get(controlName).setErrors({ [validationErrors[fieldName][0]]: true });
871
+ formGroup.get(controlName).markAsTouched();
872
+ });
873
+ });
874
+ }
875
+ }
876
+ }
877
+
878
+ class RouteHelper {
879
+ static getRouteByData(routeSnapshot, key, value) {
880
+ if (!routeSnapshot) {
881
+ return null;
882
+ }
883
+ return routeSnapshot.data[key] === value
884
+ ? routeSnapshot
885
+ : this.getRouteByData(routeSnapshot.firstChild, key, value);
886
+ }
887
+ static getRouteWithComponent(routeSnapshot, component) {
888
+ if (!routeSnapshot) {
889
+ return null;
890
+ }
891
+ return routeSnapshot.component === component
892
+ ? routeSnapshot
893
+ : this.getRouteWithComponent(routeSnapshot.firstChild, component);
894
+ }
895
+ static getRouteURL(route, rootLevel = false) {
896
+ let segments = [];
897
+ const buildRoute = (route) => {
898
+ if (route) {
899
+ if (route.url.length) {
900
+ segments.push(...route.url.map((segment) => segment.path));
901
+ }
902
+ buildRoute(rootLevel ? route.firstChild : route.parent);
903
+ }
904
+ };
905
+ buildRoute(route);
906
+ if (!rootLevel) {
907
+ segments = segments.reverse();
908
+ }
909
+ return `/${segments.join('/')}`;
910
+ }
911
+ }
912
+
913
+ class ConfirmModalComponent {
914
+ //#region ViewChilds, Inputs, Outputs
915
+ modal = viewChild(ModalComponent, /* @ts-ignore */
916
+ ...(ngDevMode ? [{ debugName: "modal" }] : /* istanbul ignore next */ []));
917
+ // Models rather than inputs: this component writes both while confirming, from callbacks a
918
+ // parent owns. As plain fields they landed with nothing marking this view dirty.
919
+ errorMessage = model('', /* @ts-ignore */
920
+ ...(ngDevMode ? [{ debugName: "errorMessage" }] : /* istanbul ignore next */ []));
921
+ isLoading = model(false, /* @ts-ignore */
922
+ ...(ngDevMode ? [{ debugName: "isLoading" }] : /* istanbul ignore next */ []));
923
+ message = input('', /* @ts-ignore */
924
+ ...(ngDevMode ? [{ debugName: "message" }] : /* istanbul ignore next */ []));
925
+ messageIcon = input('fa-regular fa-circle-question', /* @ts-ignore */
926
+ ...(ngDevMode ? [{ debugName: "messageIcon" }] : /* istanbul ignore next */ []));
927
+ messageIconColor = input('text-cyan-500', /* @ts-ignore */
928
+ ...(ngDevMode ? [{ debugName: "messageIconColor" }] : /* istanbul ignore next */ []));
929
+ showMessageIcon = input(true, /* @ts-ignore */
930
+ ...(ngDevMode ? [{ debugName: "showMessageIcon" }] : /* istanbul ignore next */ []));
931
+ size = input('xl', /* @ts-ignore */
932
+ ...(ngDevMode ? [{ debugName: "size" }] : /* istanbul ignore next */ []));
933
+ title = input('', /* @ts-ignore */
934
+ ...(ngDevMode ? [{ debugName: "title" }] : /* istanbul ignore next */ []));
935
+ //#endregion
936
+ //#region Variables
937
+ formService = inject(FormService, { optional: true }) ?? undefined;
938
+ //#endregion
939
+ //#region Properties
940
+ /**
941
+ * Delegates to the inner modal. A signal view query, not @ViewChild: a computed reading a plain
942
+ * query would never re-run when the query resolves, so the wrapper would answer against an
943
+ * undefined child for ever.
944
+ */
945
+ isOpen = computed(() => this.modal()?.isOpen() ?? false, /* @ts-ignore */
946
+ ...(ngDevMode ? [{ debugName: "isOpen" }] : /* istanbul ignore next */ []));
947
+ get hasErrorMessage() {
948
+ return !!this.errorMessage() && this.errorMessage().length > 0;
949
+ }
950
+ get hasMessage() {
951
+ return !!this.message() && this.message().length > 0;
952
+ }
953
+ controlContainer = inject(ControlContainer, { optional: true });
954
+ get formGroup() {
955
+ return this.controlContainer?.control ?? null;
956
+ }
957
+ //#endregion
958
+ //#region Constructor and Angular life cycle methods
959
+ //#endregion
960
+ //#region Event handlers
961
+ //#endregion
962
+ //#region Public methods
963
+ beginLoading() {
964
+ this.clearErrorMessage();
965
+ this.isLoading.set(true);
966
+ }
967
+ clearErrorMessage() {
968
+ this.errorMessage.set('');
969
+ }
970
+ closeModal() {
971
+ this.modal()?.closeModal();
972
+ }
973
+ finishLoading(closeModal) {
974
+ this.isLoading.set(false);
975
+ if (this.formGroup && this.formGroup.disabled) {
976
+ this.formGroup.enable();
977
+ }
978
+ if (closeModal) {
979
+ this.closeModal();
980
+ }
981
+ }
982
+ setErrorMessage(httpErrorResponse) {
983
+ this.finishLoading();
984
+ if (httpErrorResponse.status === 400 && !!this.formGroup) {
985
+ BackendFormValidationHelper.validateAllFormFields(httpErrorResponse, this.formGroup);
986
+ }
987
+ else {
988
+ if (typeof httpErrorResponse.error === 'string') {
989
+ this.errorMessage.set(httpErrorResponse.error);
990
+ }
991
+ else {
992
+ this.errorMessage.set('Modal-Failed-DefaultMessage');
993
+ }
994
+ }
995
+ }
996
+ toggleModal() {
997
+ if (!!this.formService && this.formService.loading) {
998
+ return;
999
+ }
1000
+ if (this.formGroup) {
1001
+ // TODO: TEST THIS CHANGE
1002
+ this.formGroup.reset();
1003
+ this.formService?.resetForm();
1004
+ }
1005
+ this.clearErrorMessage();
1006
+ this.modal()?.toggleModal();
1007
+ }
1008
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: ConfirmModalComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1009
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.7", type: ConfirmModalComponent, isStandalone: true, selector: "framework-confirm-modal", inputs: { errorMessage: { classPropertyName: "errorMessage", publicName: "errorMessage", isSignal: true, isRequired: false, transformFunction: null }, isLoading: { classPropertyName: "isLoading", publicName: "isLoading", isSignal: true, isRequired: false, transformFunction: null }, message: { classPropertyName: "message", publicName: "message", isSignal: true, isRequired: false, transformFunction: null }, messageIcon: { classPropertyName: "messageIcon", publicName: "messageIcon", isSignal: true, isRequired: false, transformFunction: null }, messageIconColor: { classPropertyName: "messageIconColor", publicName: "messageIconColor", isSignal: true, isRequired: false, transformFunction: null }, showMessageIcon: { classPropertyName: "showMessageIcon", publicName: "showMessageIcon", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { errorMessage: "errorMessageChange", isLoading: "isLoadingChange" }, viewQueries: [{ propertyName: "modal", first: true, predicate: ModalComponent, descendants: true, isSignal: true }], ngImport: i0, template: "<lib-modal\r\n position=\"top\"\r\n [closeButtonText]=\"'Button-Modal-Cancel' | translate\"\r\n [size]=\"size()\"\r\n [title]=\"title() | translate\"\r\n>\r\n <div body>\r\n @if (hasMessage) {\r\n <div class=\"message\">\r\n @if (showMessageIcon()) {\r\n <i class=\"fa-3x\" [ngClass]=\"[messageIcon(), messageIconColor()]\"></i>\r\n }\r\n <h4 [innerHTML]=\"message() | translate\"></h4>\r\n </div>\r\n }\r\n\r\n <ng-content></ng-content>\r\n\r\n @if (hasErrorMessage) {\r\n <div class=\"erro\">\r\n <h4>{{ errorMessage() | translate }}</h4>\r\n <span>{{ 'Modal-Failed-Administrator' | translate }}</span>\r\n </div>\r\n }\r\n </div>\r\n <div footer>\r\n <ng-content select=\"[footer]\"></ng-content>\r\n </div>\r\n</lib-modal>\r\n", styles: ["@layer properties;:host{display:contents}.message{display:flex;align-items:center;gap:calc(var(--spacing, .25rem) * 3)}.message h4{flex-grow:1;text-align:justify}.erro{margin-inline:calc(var(--spacing, .25rem) * -4);margin-top:calc(var(--spacing, .25rem) * 3);margin-bottom:calc(var(--spacing, .25rem) * -4);padding:calc(var(--spacing, .25rem) * 4);text-align:center;border-top-style:var(--tw-border-style);border-top-width:1px;border-top-color:var(--color-gray-300, oklch(87.2% .01 258.338));background-color:var(--color-red-500, oklch(63.7% .237 25.331));color:var(--color-white, #fff)}.erro h4{--tw-font-weight: var(--font-weight-bold, 700);font-weight:var(--font-weight-bold, 700)}.erro span{margin-top:var(--spacing, .25rem);font-size:var(--text-sm, .875rem);line-height:var(--tw-leading, var(--text-sm--line-height, calc(1.25 / .875)))}@property --tw-border-style{syntax: \"*\"; inherits: false; initial-value: solid;}@property --tw-font-weight{syntax: \"*\"; inherits: false;}@layer properties{@supports ((-webkit-hyphens: none) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-border-style: solid;--tw-font-weight: initial}}}\n/*! tailwindcss v4.3.3 | MIT License | https://tailwindcss.com */\n"], dependencies: [{ kind: "component", type: ModalComponent, selector: "lib-modal", inputs: ["closeButtonText", "dialog", "modalProcessing", "position", "size", "title"], outputs: ["closeButtonTextChange", "modalProcessingChange", "closed"] }, { kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }] });
1010
+ }
1011
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: ConfirmModalComponent, decorators: [{
1012
+ type: Component,
1013
+ args: [{ selector: 'framework-confirm-modal', imports: [ModalComponent, NgClass, TranslatePipe], template: "<lib-modal\r\n position=\"top\"\r\n [closeButtonText]=\"'Button-Modal-Cancel' | translate\"\r\n [size]=\"size()\"\r\n [title]=\"title() | translate\"\r\n>\r\n <div body>\r\n @if (hasMessage) {\r\n <div class=\"message\">\r\n @if (showMessageIcon()) {\r\n <i class=\"fa-3x\" [ngClass]=\"[messageIcon(), messageIconColor()]\"></i>\r\n }\r\n <h4 [innerHTML]=\"message() | translate\"></h4>\r\n </div>\r\n }\r\n\r\n <ng-content></ng-content>\r\n\r\n @if (hasErrorMessage) {\r\n <div class=\"erro\">\r\n <h4>{{ errorMessage() | translate }}</h4>\r\n <span>{{ 'Modal-Failed-Administrator' | translate }}</span>\r\n </div>\r\n }\r\n </div>\r\n <div footer>\r\n <ng-content select=\"[footer]\"></ng-content>\r\n </div>\r\n</lib-modal>\r\n", styles: ["@layer properties;:host{display:contents}.message{display:flex;align-items:center;gap:calc(var(--spacing, .25rem) * 3)}.message h4{flex-grow:1;text-align:justify}.erro{margin-inline:calc(var(--spacing, .25rem) * -4);margin-top:calc(var(--spacing, .25rem) * 3);margin-bottom:calc(var(--spacing, .25rem) * -4);padding:calc(var(--spacing, .25rem) * 4);text-align:center;border-top-style:var(--tw-border-style);border-top-width:1px;border-top-color:var(--color-gray-300, oklch(87.2% .01 258.338));background-color:var(--color-red-500, oklch(63.7% .237 25.331));color:var(--color-white, #fff)}.erro h4{--tw-font-weight: var(--font-weight-bold, 700);font-weight:var(--font-weight-bold, 700)}.erro span{margin-top:var(--spacing, .25rem);font-size:var(--text-sm, .875rem);line-height:var(--tw-leading, var(--text-sm--line-height, calc(1.25 / .875)))}@property --tw-border-style{syntax: \"*\"; inherits: false; initial-value: solid;}@property --tw-font-weight{syntax: \"*\"; inherits: false;}@layer properties{@supports ((-webkit-hyphens: none) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-border-style: solid;--tw-font-weight: initial}}}\n/*! tailwindcss v4.3.3 | MIT License | https://tailwindcss.com */\n"] }]
1014
+ }], propDecorators: { modal: [{ type: i0.ViewChild, args: [i0.forwardRef(() => ModalComponent), { isSignal: true }] }], errorMessage: [{ type: i0.Input, args: [{ isSignal: true, alias: "errorMessage", required: false }] }, { type: i0.Output, args: ["errorMessageChange"] }], isLoading: [{ type: i0.Input, args: [{ isSignal: true, alias: "isLoading", required: false }] }, { type: i0.Output, args: ["isLoadingChange"] }], message: [{ type: i0.Input, args: [{ isSignal: true, alias: "message", required: false }] }], messageIcon: [{ type: i0.Input, args: [{ isSignal: true, alias: "messageIcon", required: false }] }], messageIconColor: [{ type: i0.Input, args: [{ isSignal: true, alias: "messageIconColor", required: false }] }], showMessageIcon: [{ type: i0.Input, args: [{ isSignal: true, alias: "showMessageIcon", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: false }] }] } });
1015
+
1016
+ class ErrorModalComponent {
1017
+ //#region ViewChilds, Inputs, Outputs
1018
+ modal = viewChild(ModalComponent, /* @ts-ignore */
1019
+ ...(ngDevMode ? [{ debugName: "modal" }] : /* istanbul ignore next */ []));
1020
+ // A model rather than an input: showModal() writes it, and a parent calls that from its own
1021
+ // subscription. As a plain field the message landed with nothing marking this view dirty.
1022
+ errorMessage = model('', /* @ts-ignore */
1023
+ ...(ngDevMode ? [{ debugName: "errorMessage" }] : /* istanbul ignore next */ []));
1024
+ size = input('xl', /* @ts-ignore */
1025
+ ...(ngDevMode ? [{ debugName: "size" }] : /* istanbul ignore next */ []));
1026
+ // Models too: DefaultDetailsTabViewComponent sets both before showing the modal, so that a 404
1027
+ // reads as "record not found" rather than as a server error.
1028
+ subtitle = model('Modal-Failed-Administrator', /* @ts-ignore */
1029
+ ...(ngDevMode ? [{ debugName: "subtitle" }] : /* istanbul ignore next */ []));
1030
+ title = model('Modal-Failed-Title', /* @ts-ignore */
1031
+ ...(ngDevMode ? [{ debugName: "title" }] : /* istanbul ignore next */ []));
1032
+ closed = output();
1033
+ //#endregion
1034
+ //#region Variables
1035
+ //#endregion
1036
+ //#region Properties
1037
+ /**
1038
+ * Delegates to the inner modal. A signal view query, not @ViewChild: a computed reading a plain
1039
+ * query would never re-run when the query resolves, so the wrapper would answer against an
1040
+ * undefined child for ever.
1041
+ */
1042
+ isOpen = computed(() => this.modal()?.isOpen() ?? false, /* @ts-ignore */
1043
+ ...(ngDevMode ? [{ debugName: "isOpen" }] : /* istanbul ignore next */ []));
1044
+ //#endregion
1045
+ //#region Constructor and Angular life cycle methods
1046
+ //#endregion
1047
+ //#region Event handlers
1048
+ //#endregion
1049
+ //#region Public methods
1050
+ closeModal() {
1051
+ this.modal()?.closeModal();
1052
+ }
1053
+ showModal(error) {
1054
+ let errorMessage = 'Modal-Failed-DefaultMessage';
1055
+ if (typeof error === 'string') {
1056
+ errorMessage = error;
1057
+ }
1058
+ else if (typeof error.error === 'string') {
1059
+ errorMessage = error.error;
1060
+ }
1061
+ this.errorMessage.set(errorMessage);
1062
+ this.modal()?.toggleModal();
1063
+ }
1064
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: ErrorModalComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1065
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.7", type: ErrorModalComponent, isStandalone: true, selector: "framework-error-modal", inputs: { errorMessage: { classPropertyName: "errorMessage", publicName: "errorMessage", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, subtitle: { classPropertyName: "subtitle", publicName: "subtitle", isSignal: true, isRequired: false, transformFunction: null }, title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { errorMessage: "errorMessageChange", subtitle: "subtitleChange", title: "titleChange", closed: "closed" }, viewQueries: [{ propertyName: "modal", first: true, predicate: ModalComponent, descendants: true, isSignal: true }], ngImport: i0, template: "<lib-modal\r\n position=\"top\"\r\n [closeButtonText]=\"'Button-Modal-Close' | translate\"\r\n [size]=\"size()\"\r\n [title]=\"title() | translate\"\r\n (closed)=\"closed.emit()\"\r\n>\r\n <div body>\r\n <div class=\"erro\">\r\n <h4>{{ errorMessage() | translate }}</h4>\r\n @if (subtitle()) {\r\n <span>{{ subtitle() | translate }}</span>\r\n }\r\n </div>\r\n </div>\r\n</lib-modal>\r\n", styles: ["@layer properties;:host{display:contents}.erro{margin-inline:calc(var(--spacing, .25rem) * -4);margin-top:-1.1rem;margin-bottom:calc(var(--spacing, .25rem) * -4);padding-block:calc(var(--spacing, .25rem) * 4);text-align:center;border-top-style:var(--tw-border-style);border-top-width:1px;border-top-color:var(--color-gray-300, oklch(87.2% .01 258.338));background-color:var(--color-red-500, oklch(63.7% .237 25.331));color:var(--color-white, #fff)}.erro h4{font-size:var(--text-sm, .875rem);line-height:var(--tw-leading, var(--text-sm--line-height, calc(1.25 / .875)));--tw-font-weight: var(--font-weight-bold, 700);font-weight:var(--font-weight-bold, 700);white-space:pre-line}.erro span{margin-top:var(--spacing, .25rem);font-size:var(--text-xs, .75rem);line-height:var(--tw-leading, var(--text-xs--line-height, calc(1 / .75)))}@property --tw-border-style{syntax: \"*\"; inherits: false; initial-value: solid;}@property --tw-font-weight{syntax: \"*\"; inherits: false;}@layer properties{@supports ((-webkit-hyphens: none) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-border-style: solid;--tw-font-weight: initial}}}\n/*! tailwindcss v4.3.3 | MIT License | https://tailwindcss.com */\n"], dependencies: [{ kind: "component", type: ModalComponent, selector: "lib-modal", inputs: ["closeButtonText", "dialog", "modalProcessing", "position", "size", "title"], outputs: ["closeButtonTextChange", "modalProcessingChange", "closed"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }] });
1066
+ }
1067
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: ErrorModalComponent, decorators: [{
1068
+ type: Component,
1069
+ args: [{ selector: 'framework-error-modal', imports: [ModalComponent, TranslatePipe], template: "<lib-modal\r\n position=\"top\"\r\n [closeButtonText]=\"'Button-Modal-Close' | translate\"\r\n [size]=\"size()\"\r\n [title]=\"title() | translate\"\r\n (closed)=\"closed.emit()\"\r\n>\r\n <div body>\r\n <div class=\"erro\">\r\n <h4>{{ errorMessage() | translate }}</h4>\r\n @if (subtitle()) {\r\n <span>{{ subtitle() | translate }}</span>\r\n }\r\n </div>\r\n </div>\r\n</lib-modal>\r\n", styles: ["@layer properties;:host{display:contents}.erro{margin-inline:calc(var(--spacing, .25rem) * -4);margin-top:-1.1rem;margin-bottom:calc(var(--spacing, .25rem) * -4);padding-block:calc(var(--spacing, .25rem) * 4);text-align:center;border-top-style:var(--tw-border-style);border-top-width:1px;border-top-color:var(--color-gray-300, oklch(87.2% .01 258.338));background-color:var(--color-red-500, oklch(63.7% .237 25.331));color:var(--color-white, #fff)}.erro h4{font-size:var(--text-sm, .875rem);line-height:var(--tw-leading, var(--text-sm--line-height, calc(1.25 / .875)));--tw-font-weight: var(--font-weight-bold, 700);font-weight:var(--font-weight-bold, 700);white-space:pre-line}.erro span{margin-top:var(--spacing, .25rem);font-size:var(--text-xs, .75rem);line-height:var(--tw-leading, var(--text-xs--line-height, calc(1 / .75)))}@property --tw-border-style{syntax: \"*\"; inherits: false; initial-value: solid;}@property --tw-font-weight{syntax: \"*\"; inherits: false;}@layer properties{@supports ((-webkit-hyphens: none) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-border-style: solid;--tw-font-weight: initial}}}\n/*! tailwindcss v4.3.3 | MIT License | https://tailwindcss.com */\n"] }]
1070
+ }], propDecorators: { modal: [{ type: i0.ViewChild, args: [i0.forwardRef(() => ModalComponent), { isSignal: true }] }], errorMessage: [{ type: i0.Input, args: [{ isSignal: true, alias: "errorMessage", required: false }] }, { type: i0.Output, args: ["errorMessageChange"] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], subtitle: [{ type: i0.Input, args: [{ isSignal: true, alias: "subtitle", required: false }] }, { type: i0.Output, args: ["subtitleChange"] }], title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: false }] }, { type: i0.Output, args: ["titleChange"] }], closed: [{ type: i0.Output, args: ["closed"] }] } });
1071
+
1072
+ /**
1073
+ * @deprecated Use standalone component imports instead of FrameworkModalsModule.
1074
+ * Import individual components directly in your component's `imports` array.
1075
+ */
1076
+ class FrameworkModalsModule {
1077
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: FrameworkModalsModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
1078
+ static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "22.1.7", ngImport: i0, type: FrameworkModalsModule, imports: [ConfirmModalComponent, ErrorModalComponent], exports: [ConfirmModalComponent, ErrorModalComponent] });
1079
+ static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: FrameworkModalsModule, imports: [ConfirmModalComponent, ErrorModalComponent] });
1080
+ }
1081
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: FrameworkModalsModule, decorators: [{
1082
+ type: NgModule,
1083
+ args: [{
1084
+ declarations: [],
1085
+ imports: [ConfirmModalComponent, ErrorModalComponent],
1086
+ exports: [ConfirmModalComponent, ErrorModalComponent],
1087
+ }]
1088
+ }] });
1089
+
1090
+ class ButtonConfirmComponent extends BaseButton {
1091
+ //#region ViewChilds, Inputs, Outputs
1092
+ confirmModal = viewChild(ConfirmModalComponent, /* @ts-ignore */
1093
+ ...(ngDevMode ? [{ debugName: "confirmModal" }] : /* istanbul ignore next */ []));
1094
+ action = input.required(/* @ts-ignore */
1095
+ ...(ngDevMode ? [{ debugName: "action" }] : /* istanbul ignore next */ []));
1096
+ color = input('text-primary-500', /* @ts-ignore */
1097
+ ...(ngDevMode ? [{ debugName: "color" }] : /* istanbul ignore next */ []));
1098
+ icon = input('', /* @ts-ignore */
1099
+ ...(ngDevMode ? [{ debugName: "icon" }] : /* istanbul ignore next */ []));
1100
+ label = input('', /* @ts-ignore */
1101
+ ...(ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []));
1102
+ modalConfirmButtonColor = input('', /* @ts-ignore */
1103
+ ...(ngDevMode ? [{ debugName: "modalConfirmButtonColor" }] : /* istanbul ignore next */ []));
1104
+ modalConfirmButtonLabel = input('', /* @ts-ignore */
1105
+ ...(ngDevMode ? [{ debugName: "modalConfirmButtonLabel" }] : /* istanbul ignore next */ []));
1106
+ modalMessage = input('', /* @ts-ignore */
1107
+ ...(ngDevMode ? [{ debugName: "modalMessage" }] : /* istanbul ignore next */ []));
1108
+ modalMessageIcon = input('fa-solid fa-circle-question', /* @ts-ignore */
1109
+ ...(ngDevMode ? [{ debugName: "modalMessageIcon" }] : /* istanbul ignore next */ []));
1110
+ modalMessageIconColor = input('', /* @ts-ignore */
1111
+ ...(ngDevMode ? [{ debugName: "modalMessageIconColor" }] : /* istanbul ignore next */ []));
1112
+ modalTitle = input('', /* @ts-ignore */
1113
+ ...(ngDevMode ? [{ debugName: "modalTitle" }] : /* istanbul ignore next */ []));
1114
+ //#endregion
1115
+ //#region Variables
1116
+ gridDataset = inject(DataGridDataset);
1117
+ //#endregion
1118
+ //#region Properties
1119
+ //#endregion
1120
+ //#region Constructor and Angular life cycle methods
1121
+ constructor() {
1122
+ super();
1123
+ this.disabled.set(true);
1124
+ }
1125
+ //#endregion
1126
+ //#region Event handlers
1127
+ onButtonClicked() {
1128
+ this.confirmModal()?.toggleModal();
1129
+ }
1130
+ onDelete() {
1131
+ const confirmModal = this.confirmModal();
1132
+ if (!confirmModal || confirmModal.isLoading() || !confirmModal.isOpen()) {
1133
+ return;
1134
+ }
1135
+ confirmModal.beginLoading();
1136
+ this.action()
1137
+ .pipe(take(1))
1138
+ .subscribe({
1139
+ next: () => {
1140
+ this.finishLoading('success');
1141
+ confirmModal.finishLoading(true);
1142
+ this.gridDataset.refresh();
1143
+ },
1144
+ error: (e) => {
1145
+ confirmModal.setErrorMessage(e);
1146
+ },
1147
+ });
1148
+ }
1149
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: ButtonConfirmComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1150
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.7", type: ButtonConfirmComponent, isStandalone: true, selector: "framework-button-confirm", inputs: { action: { classPropertyName: "action", publicName: "action", isSignal: true, isRequired: true, transformFunction: null }, color: { classPropertyName: "color", publicName: "color", isSignal: true, isRequired: false, transformFunction: null }, icon: { classPropertyName: "icon", publicName: "icon", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, modalConfirmButtonColor: { classPropertyName: "modalConfirmButtonColor", publicName: "modalConfirmButtonColor", isSignal: true, isRequired: false, transformFunction: null }, modalConfirmButtonLabel: { classPropertyName: "modalConfirmButtonLabel", publicName: "modalConfirmButtonLabel", isSignal: true, isRequired: false, transformFunction: null }, modalMessage: { classPropertyName: "modalMessage", publicName: "modalMessage", isSignal: true, isRequired: false, transformFunction: null }, modalMessageIcon: { classPropertyName: "modalMessageIcon", publicName: "modalMessageIcon", isSignal: true, isRequired: false, transformFunction: null }, modalMessageIconColor: { classPropertyName: "modalMessageIconColor", publicName: "modalMessageIconColor", isSignal: true, isRequired: false, transformFunction: null }, modalTitle: { classPropertyName: "modalTitle", publicName: "modalTitle", isSignal: true, isRequired: false, transformFunction: null } }, providers: [{ provide: RibbonGroupChild, useExisting: forwardRef(() => ButtonConfirmComponent) }], viewQueries: [{ propertyName: "confirmModal", first: true, predicate: ConfirmModalComponent, descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: "@if (isVisible()) {\r\n <lib-ribbon-button\r\n [color]=\"color()\"\r\n [disabled]=\"disabled()\"\r\n [icon]=\"icon()\"\r\n [label]=\"label()\"\r\n [loading]=\"loading()\"\r\n (action)=\"onButtonClicked()\"\r\n >\r\n </lib-ribbon-button>\r\n}\r\n\r\n<framework-confirm-modal\r\n #modal\r\n [message]=\"modalMessage()\"\r\n [messageIcon]=\"modalMessageIcon()\"\r\n [messageIconColor]=\"modalMessageIconColor()\"\r\n [title]=\"modalTitle()\"\r\n>\r\n <div footer>\r\n <button\r\n class=\"btn {{ modalConfirmButtonColor() }}\"\r\n type=\"button\"\r\n [class.loading]=\"modal.isLoading()\"\r\n (click)=\"onDelete()\"\r\n >\r\n {{ modalConfirmButtonLabel() | translate }}\r\n </button>\r\n </div>\r\n</framework-confirm-modal>\r\n", dependencies: [{ kind: "component", type: ConfirmModalComponent, selector: "framework-confirm-modal", inputs: ["errorMessage", "isLoading", "message", "messageIcon", "messageIconColor", "showMessageIcon", "size", "title"], outputs: ["errorMessageChange", "isLoadingChange"] }, { kind: "component", type: RibbonButtonComponent, selector: "lib-ribbon-button", inputs: ["color", "defaultOption", "disabled", "icon", "iconSize", "label", "loading", "options", "tooltip"], outputs: ["loadingChange", "action"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }] });
1151
+ }
1152
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: ButtonConfirmComponent, decorators: [{
1153
+ type: Component,
1154
+ args: [{ selector: 'framework-button-confirm', imports: [ConfirmModalComponent, RibbonButtonComponent, TranslatePipe], providers: [{ provide: RibbonGroupChild, useExisting: forwardRef(() => ButtonConfirmComponent) }], template: "@if (isVisible()) {\r\n <lib-ribbon-button\r\n [color]=\"color()\"\r\n [disabled]=\"disabled()\"\r\n [icon]=\"icon()\"\r\n [label]=\"label()\"\r\n [loading]=\"loading()\"\r\n (action)=\"onButtonClicked()\"\r\n >\r\n </lib-ribbon-button>\r\n}\r\n\r\n<framework-confirm-modal\r\n #modal\r\n [message]=\"modalMessage()\"\r\n [messageIcon]=\"modalMessageIcon()\"\r\n [messageIconColor]=\"modalMessageIconColor()\"\r\n [title]=\"modalTitle()\"\r\n>\r\n <div footer>\r\n <button\r\n class=\"btn {{ modalConfirmButtonColor() }}\"\r\n type=\"button\"\r\n [class.loading]=\"modal.isLoading()\"\r\n (click)=\"onDelete()\"\r\n >\r\n {{ modalConfirmButtonLabel() | translate }}\r\n </button>\r\n </div>\r\n</framework-confirm-modal>\r\n" }]
1155
+ }], ctorParameters: () => [], propDecorators: { confirmModal: [{ type: i0.ViewChild, args: [i0.forwardRef(() => ConfirmModalComponent), { isSignal: true }] }], action: [{ type: i0.Input, args: [{ isSignal: true, alias: "action", required: true }] }], color: [{ type: i0.Input, args: [{ isSignal: true, alias: "color", required: false }] }], icon: [{ type: i0.Input, args: [{ isSignal: true, alias: "icon", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], modalConfirmButtonColor: [{ type: i0.Input, args: [{ isSignal: true, alias: "modalConfirmButtonColor", required: false }] }], modalConfirmButtonLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "modalConfirmButtonLabel", required: false }] }], modalMessage: [{ type: i0.Input, args: [{ isSignal: true, alias: "modalMessage", required: false }] }], modalMessageIcon: [{ type: i0.Input, args: [{ isSignal: true, alias: "modalMessageIcon", required: false }] }], modalMessageIconColor: [{ type: i0.Input, args: [{ isSignal: true, alias: "modalMessageIconColor", required: false }] }], modalTitle: [{ type: i0.Input, args: [{ isSignal: true, alias: "modalTitle", required: false }] }] } });
1156
+
1157
+ class ButtonDeleteComponent extends BaseButton {
1158
+ //#region ViewChilds, Inputs, Outputs
1159
+ confirmModal = viewChild(ConfirmModalComponent, /* @ts-ignore */
1160
+ ...(ngDevMode ? [{ debugName: "confirmModal" }] : /* istanbul ignore next */ []));
1161
+ action = input.required(/* @ts-ignore */
1162
+ ...(ngDevMode ? [{ debugName: "action" }] : /* istanbul ignore next */ []));
1163
+ label = input('Button-Delete', /* @ts-ignore */
1164
+ ...(ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []));
1165
+ modalConfirmButtonLabel = input('Modal-Delete-Confirm', /* @ts-ignore */
1166
+ ...(ngDevMode ? [{ debugName: "modalConfirmButtonLabel" }] : /* istanbul ignore next */ []));
1167
+ modalMessage = input('Modal-Delete-Message', /* @ts-ignore */
1168
+ ...(ngDevMode ? [{ debugName: "modalMessage" }] : /* istanbul ignore next */ []));
1169
+ modalTitle = input('Modal-Delete-Title', /* @ts-ignore */
1170
+ ...(ngDevMode ? [{ debugName: "modalTitle" }] : /* istanbul ignore next */ []));
1171
+ //#endregion
1172
+ //#region Variables
1173
+ gridDataset = inject(DataGridDataset);
1174
+ //#endregion
1175
+ //#region Properties
1176
+ //#endregion
1177
+ //#region Constructor and Angular life cycle methods
1178
+ constructor() {
1179
+ super();
1180
+ this.disabled.set(true);
1181
+ }
1182
+ //#endregion
1183
+ //#region Event handlers
1184
+ onButtonClicked() {
1185
+ this.confirmModal()?.toggleModal();
1186
+ }
1187
+ onDelete() {
1188
+ const confirmModal = this.confirmModal();
1189
+ if (!confirmModal || confirmModal.isLoading() || !confirmModal.isOpen()) {
1190
+ return;
1191
+ }
1192
+ confirmModal.beginLoading();
1193
+ this.action()
1194
+ .pipe(take(1))
1195
+ .subscribe({
1196
+ next: () => {
1197
+ this.finishLoading('success');
1198
+ confirmModal.finishLoading(true);
1199
+ this.gridDataset.refresh();
1200
+ },
1201
+ error: (e) => {
1202
+ confirmModal.setErrorMessage(e);
1203
+ },
1204
+ });
1205
+ }
1206
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: ButtonDeleteComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1207
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.7", type: ButtonDeleteComponent, isStandalone: true, selector: "framework-button-delete", inputs: { action: { classPropertyName: "action", publicName: "action", isSignal: true, isRequired: true, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, modalConfirmButtonLabel: { classPropertyName: "modalConfirmButtonLabel", publicName: "modalConfirmButtonLabel", isSignal: true, isRequired: false, transformFunction: null }, modalMessage: { classPropertyName: "modalMessage", publicName: "modalMessage", isSignal: true, isRequired: false, transformFunction: null }, modalTitle: { classPropertyName: "modalTitle", publicName: "modalTitle", isSignal: true, isRequired: false, transformFunction: null } }, providers: [{ provide: RibbonGroupChild, useExisting: forwardRef(() => ButtonDeleteComponent) }], viewQueries: [{ propertyName: "confirmModal", first: true, predicate: ConfirmModalComponent, descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: "@if (isVisible()) {\r\n <lib-ribbon-button\r\n color=\"text-red-500\"\r\n icon=\"fa-trash-alt\"\r\n [disabled]=\"disabled()\"\r\n [label]=\"label()\"\r\n [loading]=\"loading()\"\r\n (action)=\"onButtonClicked()\"\r\n >\r\n </lib-ribbon-button>\r\n}\r\n\r\n<framework-confirm-modal\r\n #modal\r\n messageIcon=\"fa-solid fa-circle-exclamation\"\r\n messageIconColor=\"text-red-500\"\r\n [message]=\"modalMessage()\"\r\n [title]=\"modalTitle()\"\r\n>\r\n <div footer>\r\n <button class=\"btn red-500\" type=\"button\" [class.loading]=\"modal.isLoading()\" (click)=\"onDelete()\">\r\n {{ modalConfirmButtonLabel() | translate }}\r\n </button>\r\n </div>\r\n</framework-confirm-modal>\r\n", dependencies: [{ kind: "component", type: ConfirmModalComponent, selector: "framework-confirm-modal", inputs: ["errorMessage", "isLoading", "message", "messageIcon", "messageIconColor", "showMessageIcon", "size", "title"], outputs: ["errorMessageChange", "isLoadingChange"] }, { kind: "component", type: RibbonButtonComponent, selector: "lib-ribbon-button", inputs: ["color", "defaultOption", "disabled", "icon", "iconSize", "label", "loading", "options", "tooltip"], outputs: ["loadingChange", "action"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }] });
1208
+ }
1209
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: ButtonDeleteComponent, decorators: [{
1210
+ type: Component,
1211
+ args: [{ selector: 'framework-button-delete', imports: [ConfirmModalComponent, RibbonButtonComponent, TranslatePipe], providers: [{ provide: RibbonGroupChild, useExisting: forwardRef(() => ButtonDeleteComponent) }], template: "@if (isVisible()) {\r\n <lib-ribbon-button\r\n color=\"text-red-500\"\r\n icon=\"fa-trash-alt\"\r\n [disabled]=\"disabled()\"\r\n [label]=\"label()\"\r\n [loading]=\"loading()\"\r\n (action)=\"onButtonClicked()\"\r\n >\r\n </lib-ribbon-button>\r\n}\r\n\r\n<framework-confirm-modal\r\n #modal\r\n messageIcon=\"fa-solid fa-circle-exclamation\"\r\n messageIconColor=\"text-red-500\"\r\n [message]=\"modalMessage()\"\r\n [title]=\"modalTitle()\"\r\n>\r\n <div footer>\r\n <button class=\"btn red-500\" type=\"button\" [class.loading]=\"modal.isLoading()\" (click)=\"onDelete()\">\r\n {{ modalConfirmButtonLabel() | translate }}\r\n </button>\r\n </div>\r\n</framework-confirm-modal>\r\n" }]
1212
+ }], ctorParameters: () => [], propDecorators: { confirmModal: [{ type: i0.ViewChild, args: [i0.forwardRef(() => ConfirmModalComponent), { isSignal: true }] }], action: [{ type: i0.Input, args: [{ isSignal: true, alias: "action", required: true }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], modalConfirmButtonLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "modalConfirmButtonLabel", required: false }] }], modalMessage: [{ type: i0.Input, args: [{ isSignal: true, alias: "modalMessage", required: false }] }], modalTitle: [{ type: i0.Input, args: [{ isSignal: true, alias: "modalTitle", required: false }] }] } });
1213
+
1214
+ class ButtonEditComponent extends BaseButton {
1215
+ //#region ViewChilds, Inputs, Outputs
1216
+ cancelColor = input('text-red-500', /* @ts-ignore */
1217
+ ...(ngDevMode ? [{ debugName: "cancelColor" }] : /* istanbul ignore next */ []));
1218
+ cancelIcon = input('fa-ban', /* @ts-ignore */
1219
+ ...(ngDevMode ? [{ debugName: "cancelIcon" }] : /* istanbul ignore next */ []));
1220
+ cancelLabel = input('Button-Cancel-Edit', /* @ts-ignore */
1221
+ ...(ngDevMode ? [{ debugName: "cancelLabel" }] : /* istanbul ignore next */ []));
1222
+ editIcon = input('fa-edit', /* @ts-ignore */
1223
+ ...(ngDevMode ? [{ debugName: "editIcon" }] : /* istanbul ignore next */ []));
1224
+ editLabel = input('Button-Edit', /* @ts-ignore */
1225
+ ...(ngDevMode ? [{ debugName: "editLabel" }] : /* istanbul ignore next */ []));
1226
+ modal = input(/* @ts-ignore */
1227
+ ...(ngDevMode ? [undefined, { debugName: "modal" }] : /* istanbul ignore next */ []));
1228
+ //#endregion
1229
+ //#region Variables
1230
+ // A signal rather than a field: the form cancelling an edit flips this from a subscription,
1231
+ // which swaps Edit back in for Cancel.
1232
+ isButtonCancelVisible = signal(false, /* @ts-ignore */
1233
+ ...(ngDevMode ? [{ debugName: "isButtonCancelVisible" }] : /* istanbul ignore next */ []));
1234
+ dataProviderService = inject(DataProviderService);
1235
+ formService = inject(FormService);
1236
+ //#endregion
1237
+ //#region Properties
1238
+ get hasDataProviderEntityID() {
1239
+ return this.dataProviderService?.hasEntityID ?? false;
1240
+ }
1241
+ get isFormLoading() {
1242
+ return this.formService.loading;
1243
+ }
1244
+ //#endregion
1245
+ //#region Constructor and Angular life cycle methods
1246
+ ngOnInit() {
1247
+ this.formService.editCanceled.pipe(takeUntil(this.destroy$)).subscribe(() => {
1248
+ this.isButtonCancelVisible.set(false);
1249
+ });
1250
+ }
1251
+ //#endregion
1252
+ //#region Event handlers
1253
+ onCancelClicked() {
1254
+ if (this.isButtonCancelVisible()) {
1255
+ this.isButtonCancelVisible.set(false);
1256
+ this.formService.cancelEdit();
1257
+ }
1258
+ }
1259
+ onEditClicked() {
1260
+ const modal = this.modal();
1261
+ if (modal) {
1262
+ modal.toggle();
1263
+ }
1264
+ else {
1265
+ this.isButtonCancelVisible.set(true);
1266
+ this.formService.beginEdit();
1267
+ }
1268
+ }
1269
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: ButtonEditComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
1270
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.7", type: ButtonEditComponent, isStandalone: true, selector: "framework-button-edit", inputs: { cancelColor: { classPropertyName: "cancelColor", publicName: "cancelColor", isSignal: true, isRequired: false, transformFunction: null }, cancelIcon: { classPropertyName: "cancelIcon", publicName: "cancelIcon", isSignal: true, isRequired: false, transformFunction: null }, cancelLabel: { classPropertyName: "cancelLabel", publicName: "cancelLabel", isSignal: true, isRequired: false, transformFunction: null }, editIcon: { classPropertyName: "editIcon", publicName: "editIcon", isSignal: true, isRequired: false, transformFunction: null }, editLabel: { classPropertyName: "editLabel", publicName: "editLabel", isSignal: true, isRequired: false, transformFunction: null }, modal: { classPropertyName: "modal", publicName: "modal", isSignal: true, isRequired: false, transformFunction: null } }, providers: [{ provide: RibbonGroupChild, useExisting: forwardRef(() => ButtonEditComponent) }], usesInheritance: true, ngImport: i0, template: "@if (isVisible() && !isButtonCancelVisible() && hasDataProviderEntityID) {\r\n <lib-ribbon-button\r\n [disabled]=\"disabled() || !isAccessLoaded() || isFormLoading\"\r\n [icon]=\"editIcon()\"\r\n [iconSize]=\"iconSize()\"\r\n [label]=\"editLabel()\"\r\n [loading]=\"loading()\"\r\n (action)=\"onEditClicked()\"\r\n >\r\n </lib-ribbon-button>\r\n}\r\n@if (isVisible() && isButtonCancelVisible() && hasDataProviderEntityID) {\r\n <lib-ribbon-button\r\n [color]=\"cancelColor()\"\r\n [disabled]=\"disabled() || !isAccessLoaded() || isFormLoading\"\r\n [icon]=\"cancelIcon()\"\r\n [label]=\"cancelLabel()\"\r\n [loading]=\"loading()\"\r\n (action)=\"onCancelClicked()\"\r\n >\r\n </lib-ribbon-button>\r\n}\r\n", dependencies: [{ kind: "component", type: RibbonButtonComponent, selector: "lib-ribbon-button", inputs: ["color", "defaultOption", "disabled", "icon", "iconSize", "label", "loading", "options", "tooltip"], outputs: ["loadingChange", "action"] }] });
1271
+ }
1272
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: ButtonEditComponent, decorators: [{
1273
+ type: Component,
1274
+ args: [{ selector: 'framework-button-edit', imports: [RibbonButtonComponent], providers: [{ provide: RibbonGroupChild, useExisting: forwardRef(() => ButtonEditComponent) }], template: "@if (isVisible() && !isButtonCancelVisible() && hasDataProviderEntityID) {\r\n <lib-ribbon-button\r\n [disabled]=\"disabled() || !isAccessLoaded() || isFormLoading\"\r\n [icon]=\"editIcon()\"\r\n [iconSize]=\"iconSize()\"\r\n [label]=\"editLabel()\"\r\n [loading]=\"loading()\"\r\n (action)=\"onEditClicked()\"\r\n >\r\n </lib-ribbon-button>\r\n}\r\n@if (isVisible() && isButtonCancelVisible() && hasDataProviderEntityID) {\r\n <lib-ribbon-button\r\n [color]=\"cancelColor()\"\r\n [disabled]=\"disabled() || !isAccessLoaded() || isFormLoading\"\r\n [icon]=\"cancelIcon()\"\r\n [label]=\"cancelLabel()\"\r\n [loading]=\"loading()\"\r\n (action)=\"onCancelClicked()\"\r\n >\r\n </lib-ribbon-button>\r\n}\r\n" }]
1275
+ }], propDecorators: { cancelColor: [{ type: i0.Input, args: [{ isSignal: true, alias: "cancelColor", required: false }] }], cancelIcon: [{ type: i0.Input, args: [{ isSignal: true, alias: "cancelIcon", required: false }] }], cancelLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "cancelLabel", required: false }] }], editIcon: [{ type: i0.Input, args: [{ isSignal: true, alias: "editIcon", required: false }] }], editLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "editLabel", required: false }] }], modal: [{ type: i0.Input, args: [{ isSignal: true, alias: "modal", required: false }] }] } });
1276
+
1277
+ const MaxExportRows = 50000;
1278
+ class ButtonExportComponent extends BaseButton {
1279
+ //#region ViewChilds, Inputs, Outputs
1280
+ /**
1281
+ * Index into `options` exported when the main button is clicked, defaulting to Excel.
1282
+ * `RibbonButtonComponent` treats -1 as "no default" and merely toggles the dropdown, which left
1283
+ * Export needing two clicks for the common case while Save (which sets 0) needed one. Set -1 to
1284
+ * restore the dropdown-only behaviour.
1285
+ */
1286
+ defaultOption = input(0, /* @ts-ignore */
1287
+ ...(ngDevMode ? [{ debugName: "defaultOption" }] : /* istanbul ignore next */ []));
1288
+ fileBaseName = input('export', /* @ts-ignore */
1289
+ ...(ngDevMode ? [{ debugName: "fileBaseName" }] : /* istanbul ignore next */ []));
1290
+ //#endregion
1291
+ //#region Variables
1292
+ dataGridDataset = inject(DataGridDataset, { optional: true });
1293
+ translateService = inject(TranslateService);
1294
+ //#endregion
1295
+ //#region Properties
1296
+ get isButtonDisabled() {
1297
+ return super.isButtonDisabled || this.isExceedingExportLimit;
1298
+ }
1299
+ get tooltip() {
1300
+ if (this.isExceedingExportLimit) {
1301
+ return this.translateService.instant('Button-Export-LimitExceeded', { max: MaxExportRows });
1302
+ }
1303
+ return '';
1304
+ }
1305
+ get isExceedingExportLimit() {
1306
+ const totalRows = this.dataGridDataset?.totalRows;
1307
+ return totalRows !== undefined && totalRows > MaxExportRows;
1308
+ }
1309
+ //#endregion
1310
+ //#region Constructor and Angular life cycle methods
1311
+ constructor() {
1312
+ super();
1313
+ this.options.set([
1314
+ { id: 'xlsx', label: 'Button-Export-Excel', icon: 'fa-file-excel' },
1315
+ { id: 'pdf', label: 'Button-Export-PDF', icon: 'fa-file-pdf' },
1316
+ { id: 'csv', label: 'Button-Export-CSV', icon: 'fa-file-csv' },
1317
+ { id: 'xml', label: 'Button-Export-XML', icon: 'fa-file-code' },
1318
+ { id: 'mhtml', label: 'Button-Export-MHTML', icon: 'fa-file-archive' },
1319
+ ]);
1320
+ }
1321
+ //#endregion
1322
+ //#region Event handlers
1323
+ onButtonClicked(format) {
1324
+ if (!this.dataGridDataset) {
1325
+ return;
1326
+ }
1327
+ const resolvedFormat = format ?? 'xlsx';
1328
+ this.startLoading();
1329
+ const parameters = {
1330
+ endRow: 0,
1331
+ filters: this.dataGridDataset.filters,
1332
+ startRow: 0,
1333
+ };
1334
+ this.dataGridDataset
1335
+ .export(resolvedFormat, parameters)
1336
+ .pipe(take(1))
1337
+ .subscribe({
1338
+ next: (response) => {
1339
+ this.download(response);
1340
+ this.finishLoading('success');
1341
+ },
1342
+ error: (_) => {
1343
+ this.finishLoading('failure');
1344
+ },
1345
+ });
1346
+ }
1347
+ //#endregion
1348
+ //#region Private methods
1349
+ download(response) {
1350
+ if (!response.body) {
1351
+ return;
1352
+ }
1353
+ const contentDisposition = response.headers.get('Content-Disposition') ?? '';
1354
+ const match = /filename\*?=(?:UTF-8'')?"?([^";]+)"?/i.exec(contentDisposition);
1355
+ const fileName = match ? decodeURIComponent(match[1]) : this.fileBaseName();
1356
+ const url = URL.createObjectURL(response.body);
1357
+ const anchor = document.createElement('a');
1358
+ anchor.href = url;
1359
+ anchor.download = fileName;
1360
+ anchor.click();
1361
+ URL.revokeObjectURL(url);
1362
+ }
1363
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: ButtonExportComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1364
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.7", type: ButtonExportComponent, isStandalone: true, selector: "framework-button-export", inputs: { defaultOption: { classPropertyName: "defaultOption", publicName: "defaultOption", isSignal: true, isRequired: false, transformFunction: null }, fileBaseName: { classPropertyName: "fileBaseName", publicName: "fileBaseName", isSignal: true, isRequired: false, transformFunction: null } }, providers: [{ provide: RibbonGroupChild, useExisting: forwardRef(() => ButtonExportComponent) }], usesInheritance: true, ngImport: i0, template: "@if (isVisible()) {\r\n <lib-ribbon-button\r\n label=\"Button-Export\"\r\n icon=\"fa-file-export\"\r\n [defaultOption]=\"defaultOption()\"\r\n [disabled]=\"isButtonDisabled\"\r\n [loading]=\"loading()\"\r\n [iconSize]=\"iconSize()\"\r\n [options]=\"options()\"\r\n [tooltip]=\"tooltip\"\r\n (action)=\"onButtonClicked($event)\"\r\n >\r\n </lib-ribbon-button>\r\n}\r\n", dependencies: [{ kind: "component", type: RibbonButtonComponent, selector: "lib-ribbon-button", inputs: ["color", "defaultOption", "disabled", "icon", "iconSize", "label", "loading", "options", "tooltip"], outputs: ["loadingChange", "action"] }] });
1365
+ }
1366
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: ButtonExportComponent, decorators: [{
1367
+ type: Component,
1368
+ args: [{ selector: 'framework-button-export', imports: [RibbonButtonComponent], providers: [{ provide: RibbonGroupChild, useExisting: forwardRef(() => ButtonExportComponent) }], template: "@if (isVisible()) {\r\n <lib-ribbon-button\r\n label=\"Button-Export\"\r\n icon=\"fa-file-export\"\r\n [defaultOption]=\"defaultOption()\"\r\n [disabled]=\"isButtonDisabled\"\r\n [loading]=\"loading()\"\r\n [iconSize]=\"iconSize()\"\r\n [options]=\"options()\"\r\n [tooltip]=\"tooltip\"\r\n (action)=\"onButtonClicked($event)\"\r\n >\r\n </lib-ribbon-button>\r\n}\r\n" }]
1369
+ }], ctorParameters: () => [], propDecorators: { defaultOption: [{ type: i0.Input, args: [{ isSignal: true, alias: "defaultOption", required: false }] }], fileBaseName: [{ type: i0.Input, args: [{ isSignal: true, alias: "fileBaseName", required: false }] }] } });
1370
+
1371
+ class ButtonFiltersComponent extends BaseButton {
1372
+ //#region ViewChilds, Inputs, Outputs
1373
+ modal = viewChild(ModalComponent, /* @ts-ignore */
1374
+ ...(ngDevMode ? [{ debugName: "modal" }] : /* istanbul ignore next */ []));
1375
+ modalSize = input('xl', /* @ts-ignore */
1376
+ ...(ngDevMode ? [{ debugName: "modalSize" }] : /* istanbul ignore next */ []));
1377
+ modalTitle = input('', /* @ts-ignore */
1378
+ ...(ngDevMode ? [{ debugName: "modalTitle" }] : /* istanbul ignore next */ []));
1379
+ validateFormFunction = input(/* @ts-ignore */
1380
+ ...(ngDevMode ? [undefined, { debugName: "validateFormFunction" }] : /* istanbul ignore next */ []));
1381
+ //#endregion
1382
+ //#region Variables
1383
+ gridDataset = inject(DataGridDataset);
1384
+ formGroup = inject(FormGroupDirective);
1385
+ // A signal rather than a field: submitting or clearing the filters changes which buttons the
1386
+ // ribbon shows, and both happen from a click handler deep inside the modal.
1387
+ filters = signal({}, /* @ts-ignore */
1388
+ ...(ngDevMode ? [{ debugName: "filters" }] : /* istanbul ignore next */ []));
1389
+ //#endregion
1390
+ //#region Properties
1391
+ get hasFiltersApplied() {
1392
+ return Object.keys(this.filters()).length > 0;
1393
+ }
1394
+ get singleColumn() {
1395
+ const modalSize = this.modalSize();
1396
+ return modalSize === 'sm' || modalSize === 'md' || modalSize === 'lg' || modalSize === 'xl';
1397
+ }
1398
+ //#endregion
1399
+ //#region Constructor and Angular life cycle methods
1400
+ constructor() {
1401
+ super();
1402
+ // This will force the screen to initialize with the filter disabled.
1403
+ this.disabled.set(true);
1404
+ }
1405
+ ngOnInit() {
1406
+ // The constructor disables the button and the two subscriptions below are what re-enable it,
1407
+ // which is right only for a button that exists before its grid first loads. A ribbon template
1408
+ // is re-created whenever a record's views are switched, so a button built then arrives at a
1409
+ // grid that finished loading long ago, waits for an event that has already passed, and stays
1410
+ // unclickable until something makes the grid load again. The dataset knows either way.
1411
+ this.disabled.set(this.gridDataset.isLoading || !this.gridDataset.hasBeenLoaded);
1412
+ this.gridDataset.loadStarted.pipe(takeUntil(this.destroy$)).subscribe(() => {
1413
+ this.disabled.set(true);
1414
+ this.finishLoadingIfStarted();
1415
+ });
1416
+ this.gridDataset.loadFinished.pipe(takeUntil(this.destroy$)).subscribe(() => {
1417
+ this.disabled.set(false);
1418
+ this.finishLoadingIfStarted();
1419
+ });
1420
+ }
1421
+ //#endregion
1422
+ //#region Event handlers
1423
+ onFiltersButtonClicked() {
1424
+ this.modal()?.toggleModal();
1425
+ this.formGroup.form.reset();
1426
+ this.formGroup.form.patchValue(this.filters(), { emitEvent: false });
1427
+ }
1428
+ onFiltersSubmitClicked() {
1429
+ const validateFormFunction = this.validateFormFunction();
1430
+ if (validateFormFunction) {
1431
+ validateFormFunction();
1432
+ }
1433
+ if (this.formGroup.form.valid) {
1434
+ this.startLoading();
1435
+ this.modal()?.toggleModal();
1436
+ const formValue = this.formGroup.form.getRawValue();
1437
+ const formFilters = Object.fromEntries(Object.entries(formValue).filter(([_, v]) => (!!v && v !== '') || v === 0));
1438
+ this.setFilters(formFilters);
1439
+ }
1440
+ }
1441
+ onResetButtonClicked() {
1442
+ this.startLoading();
1443
+ this.formGroup.form.reset();
1444
+ this.filters.set({});
1445
+ this.gridDataset.setFilters();
1446
+ }
1447
+ //#endregion
1448
+ //#region Public methods
1449
+ setFilters(filters) {
1450
+ // Kept whole, because reopening the modal patches this straight back into the form and a
1451
+ // catalog select backed by a search endpoint cannot recover its label from the identifier --
1452
+ // it only resolves a display from a local entries list. Dropping the labels here would leave
1453
+ // the field showing a selection with no text.
1454
+ this.filters.set(filters);
1455
+ if (this.hasFiltersApplied) {
1456
+ this.gridDataset.setFilters(this.withoutDisplayControls(filters));
1457
+ }
1458
+ else {
1459
+ this.gridDataset.setFilters();
1460
+ }
1461
+ }
1462
+ //#endregion
1463
+ //#region Private methods
1464
+ finishLoadingIfStarted() {
1465
+ if (this.button()?.loading()) {
1466
+ this.finishLoading('success');
1467
+ }
1468
+ }
1469
+ /**
1470
+ * Drops the controls that only hold a catalog selection's label.
1471
+ *
1472
+ * They are half of how `lib-catalog-select` works and are created by it, so a screen never asked
1473
+ * for them and a backend has no filter behind them. Sent anyway they are dead weight, and worse:
1474
+ * the day a service does filter by a name, it would receive the formatted label rather than the
1475
+ * stored value and quietly match nothing.
1476
+ *
1477
+ * @param filters The filters as submitted.
1478
+ * @returns The filters a backend should receive.
1479
+ */
1480
+ withoutDisplayControls(filters) {
1481
+ return Object.fromEntries(Object.entries(filters).filter(([key]) => !DisplayControls.isDisplayControl(this.formGroup.form.get(key))));
1482
+ }
1483
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: ButtonFiltersComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1484
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.7", type: ButtonFiltersComponent, isStandalone: true, selector: "framework-button-filters", inputs: { modalSize: { classPropertyName: "modalSize", publicName: "modalSize", isSignal: true, isRequired: false, transformFunction: null }, modalTitle: { classPropertyName: "modalTitle", publicName: "modalTitle", isSignal: true, isRequired: false, transformFunction: null }, validateFormFunction: { classPropertyName: "validateFormFunction", publicName: "validateFormFunction", isSignal: true, isRequired: false, transformFunction: null } }, providers: [FormService, { provide: RibbonGroupChild, useExisting: forwardRef(() => ButtonFiltersComponent) }], viewQueries: [{ propertyName: "modal", first: true, predicate: ModalComponent, descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: "<lib-ribbon-button\r\n [color]=\"hasFiltersApplied ? 'text-amber-500' : 'text-primary-500'\"\r\n [disabled]=\"disabled()\"\r\n [icon]=\"'fa-filter'\"\r\n [iconSize]=\"iconSize()\"\r\n [label]=\"'Button-Filters'\"\r\n [loading]=\"loading()\"\r\n (action)=\"onFiltersButtonClicked()\"\r\n>\r\n</lib-ribbon-button>\r\n\r\n@if (hasFiltersApplied) {\r\n <lib-ribbon-button\r\n [color]=\"'text-red-500'\"\r\n [disabled]=\"disabled()\"\r\n [icon]=\"'fa-broom'\"\r\n [iconSize]=\"iconSize()\"\r\n [label]=\"'Button-Filters-Clear'\"\r\n [loading]=\"loading()\"\r\n (action)=\"onResetButtonClicked()\"\r\n >\r\n </lib-ribbon-button>\r\n}\r\n\r\n<lib-modal\r\n position=\"top\"\r\n [size]=\"modalSize()\"\r\n [title]=\"modalTitle() | translate\"\r\n [closeButtonText]=\"'Button-Modal-Cancel' | translate\"\r\n>\r\n <ng-container body>\r\n <ng-content></ng-content>\r\n </ng-container>\r\n <div footer>\r\n <button type=\"submit\" class=\"btn blue-500\" (click)=\"onFiltersSubmitClicked()\">\r\n {{ 'Button-Filters-Modal-Confirm' | translate }}\r\n </button>\r\n </div>\r\n</lib-modal>\r\n", styles: [":host{display:grid;grid-auto-columns:auto;grid-auto-flow:column}\n/*! tailwindcss v4.3.3 | MIT License | https://tailwindcss.com */\n"], dependencies: [{ kind: "component", type: ModalComponent, selector: "lib-modal", inputs: ["closeButtonText", "dialog", "modalProcessing", "position", "size", "title"], outputs: ["closeButtonTextChange", "modalProcessingChange", "closed"] }, { kind: "component", type: RibbonButtonComponent, selector: "lib-ribbon-button", inputs: ["color", "defaultOption", "disabled", "icon", "iconSize", "label", "loading", "options", "tooltip"], outputs: ["loadingChange", "action"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }] });
1485
+ }
1486
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: ButtonFiltersComponent, decorators: [{
1487
+ type: Component,
1488
+ args: [{ selector: 'framework-button-filters', imports: [ModalComponent, RibbonButtonComponent, TranslatePipe], providers: [FormService, { provide: RibbonGroupChild, useExisting: forwardRef(() => ButtonFiltersComponent) }], template: "<lib-ribbon-button\r\n [color]=\"hasFiltersApplied ? 'text-amber-500' : 'text-primary-500'\"\r\n [disabled]=\"disabled()\"\r\n [icon]=\"'fa-filter'\"\r\n [iconSize]=\"iconSize()\"\r\n [label]=\"'Button-Filters'\"\r\n [loading]=\"loading()\"\r\n (action)=\"onFiltersButtonClicked()\"\r\n>\r\n</lib-ribbon-button>\r\n\r\n@if (hasFiltersApplied) {\r\n <lib-ribbon-button\r\n [color]=\"'text-red-500'\"\r\n [disabled]=\"disabled()\"\r\n [icon]=\"'fa-broom'\"\r\n [iconSize]=\"iconSize()\"\r\n [label]=\"'Button-Filters-Clear'\"\r\n [loading]=\"loading()\"\r\n (action)=\"onResetButtonClicked()\"\r\n >\r\n </lib-ribbon-button>\r\n}\r\n\r\n<lib-modal\r\n position=\"top\"\r\n [size]=\"modalSize()\"\r\n [title]=\"modalTitle() | translate\"\r\n [closeButtonText]=\"'Button-Modal-Cancel' | translate\"\r\n>\r\n <ng-container body>\r\n <ng-content></ng-content>\r\n </ng-container>\r\n <div footer>\r\n <button type=\"submit\" class=\"btn blue-500\" (click)=\"onFiltersSubmitClicked()\">\r\n {{ 'Button-Filters-Modal-Confirm' | translate }}\r\n </button>\r\n </div>\r\n</lib-modal>\r\n", styles: [":host{display:grid;grid-auto-columns:auto;grid-auto-flow:column}\n/*! tailwindcss v4.3.3 | MIT License | https://tailwindcss.com */\n"] }]
1489
+ }], ctorParameters: () => [], propDecorators: { modal: [{ type: i0.ViewChild, args: [i0.forwardRef(() => ModalComponent), { isSignal: true }] }], modalSize: [{ type: i0.Input, args: [{ isSignal: true, alias: "modalSize", required: false }] }], modalTitle: [{ type: i0.Input, args: [{ isSignal: true, alias: "modalTitle", required: false }] }], validateFormFunction: [{ type: i0.Input, args: [{ isSignal: true, alias: "validateFormFunction", required: false }] }] } });
1490
+
1491
+ /**
1492
+ * Key used in Angular route `data` to identify the framework tab view type.
1493
+ *
1494
+ * Usage in route config:
1495
+ * ```ts
1496
+ * {
1497
+ * path: ':id',
1498
+ * component: MyDetailsViewComponent,
1499
+ * data: { [FRAMEWORK_VIEW_TYPE]: FrameworkViewType.Details, ... },
1500
+ * }
1501
+ * ```
1502
+ */
1503
+ const FRAMEWORK_VIEW_TYPE = 'frameworkViewType';
1504
+ /**
1505
+ * Values for the {@link FRAMEWORK_VIEW_TYPE} route data key.
1506
+ */
1507
+ var FrameworkViewType;
1508
+ (function (FrameworkViewType) {
1509
+ /** A details/edit view with data provider and sub-views (breadcrumbs, save, etc.). */
1510
+ FrameworkViewType["Details"] = "details";
1511
+ /** A list/grid view (no data provider, no sub-views). */
1512
+ FrameworkViewType["List"] = "list";
1513
+ })(FrameworkViewType || (FrameworkViewType = {}));
1514
+
1515
+ class Tab {
1516
+ _title;
1517
+ clones = [];
1518
+ entityBaseUrl;
1519
+ isTitleLoading = true;
1520
+ queryParams;
1521
+ get title() {
1522
+ return this._title;
1523
+ }
1524
+ set title(value) {
1525
+ if (this._title !== value) {
1526
+ this._title = value;
1527
+ if (value) {
1528
+ this.isTitleLoading = false;
1529
+ }
1530
+ }
1531
+ }
1532
+ url = '';
1533
+ constructor(init) {
1534
+ Object.assign(this, init);
1535
+ }
1536
+ }
1537
+
1538
+ class ButtonNewComponent extends BaseButton {
1539
+ //#region ViewChilds, Inputs, Outputs
1540
+ endpoint = input('new', /* @ts-ignore */
1541
+ ...(ngDevMode ? [{ debugName: "endpoint" }] : /* istanbul ignore next */ []));
1542
+ parameters = input(/* @ts-ignore */
1543
+ ...(ngDevMode ? [undefined, { debugName: "parameters" }] : /* istanbul ignore next */ []));
1544
+ path = input(/* @ts-ignore */
1545
+ ...(ngDevMode ? [undefined, { debugName: "path" }] : /* istanbul ignore next */ []));
1546
+ //#endregion
1547
+ //#region Variables
1548
+ dataProviderService = inject(DataProviderService, { optional: true });
1549
+ router = inject(Router);
1550
+ tabService = inject(TabService);
1551
+ //#endregion
1552
+ //#region Properties
1553
+ get hasDataProvider() {
1554
+ return !!this.dataProviderService;
1555
+ }
1556
+ get hasDataProviderEntityID() {
1557
+ return this.dataProviderService?.hasEntityID ?? false;
1558
+ }
1559
+ //#endregion
1560
+ //#region Constructor and Angular life cycle methods
1561
+ //#endregion
1562
+ //#region Event handlers
1563
+ onButtonClicked(optionId) {
1564
+ let option = undefined;
1565
+ if (optionId) {
1566
+ option = this.options().find((o) => o.id === optionId);
1567
+ }
1568
+ let path = this.path();
1569
+ if (option && option.path && option.path.length > 0) {
1570
+ path = option.path ?? optionId;
1571
+ }
1572
+ let queryParameters = this.parameters();
1573
+ if (option && option.parameters && Object.keys(option.parameters).length > 0) {
1574
+ queryParameters = { ...queryParameters, ...option.parameters };
1575
+ }
1576
+ // Try to find the route with ':id' parameters; fall back to the current route.
1577
+ const targetRoute = RouteHelper.getRouteByData(this.router.routerState.root.snapshot, FRAMEWORK_VIEW_TYPE, FrameworkViewType.Details);
1578
+ let url = targetRoute
1579
+ ? RouteHelper.getRouteURL(targetRoute.parent)
1580
+ : RouteHelper.getRouteURL(this.router.routerState.root.snapshot, true);
1581
+ if (path && path.length > 0) {
1582
+ url += `/${path}`;
1583
+ }
1584
+ const entityUrl = `${url}/${this.endpoint()}`;
1585
+ const tab = new Tab({
1586
+ entityBaseUrl: entityUrl,
1587
+ queryParams: queryParameters,
1588
+ url: entityUrl,
1589
+ });
1590
+ this.tabService.navigateCurrentTab(tab);
1591
+ }
1592
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: ButtonNewComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
1593
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.7", type: ButtonNewComponent, isStandalone: true, selector: "framework-button-new", inputs: { endpoint: { classPropertyName: "endpoint", publicName: "endpoint", isSignal: true, isRequired: false, transformFunction: null }, parameters: { classPropertyName: "parameters", publicName: "parameters", isSignal: true, isRequired: false, transformFunction: null }, path: { classPropertyName: "path", publicName: "path", isSignal: true, isRequired: false, transformFunction: null } }, providers: [{ provide: RibbonGroupChild, useExisting: forwardRef(() => ButtonNewComponent) }], usesInheritance: true, ngImport: i0, template: "@if (isVisible() && (!hasDataProvider || hasDataProviderEntityID)) {\r\n <lib-ribbon-button\r\n icon=\"fa-plus\"\r\n label=\"Button-New\"\r\n [disabled]=\"disabled() || !isAccessLoaded()\"\r\n [loading]=\"loading()\"\r\n [options]=\"options()\"\r\n (action)=\"onButtonClicked($event)\"\r\n >\r\n </lib-ribbon-button>\r\n}\r\n", dependencies: [{ kind: "component", type: RibbonButtonComponent, selector: "lib-ribbon-button", inputs: ["color", "defaultOption", "disabled", "icon", "iconSize", "label", "loading", "options", "tooltip"], outputs: ["loadingChange", "action"] }] });
1594
+ }
1595
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: ButtonNewComponent, decorators: [{
1596
+ type: Component,
1597
+ args: [{ selector: 'framework-button-new', imports: [RibbonButtonComponent], providers: [{ provide: RibbonGroupChild, useExisting: forwardRef(() => ButtonNewComponent) }], template: "@if (isVisible() && (!hasDataProvider || hasDataProviderEntityID)) {\r\n <lib-ribbon-button\r\n icon=\"fa-plus\"\r\n label=\"Button-New\"\r\n [disabled]=\"disabled() || !isAccessLoaded()\"\r\n [loading]=\"loading()\"\r\n [options]=\"options()\"\r\n (action)=\"onButtonClicked($event)\"\r\n >\r\n </lib-ribbon-button>\r\n}\r\n" }]
1598
+ }], propDecorators: { endpoint: [{ type: i0.Input, args: [{ isSignal: true, alias: "endpoint", required: false }] }], parameters: [{ type: i0.Input, args: [{ isSignal: true, alias: "parameters", required: false }] }], path: [{ type: i0.Input, args: [{ isSignal: true, alias: "path", required: false }] }] } });
1599
+
1600
+ class ButtonOpenRecordComponent extends BaseButton {
1601
+ //#region ViewChilds, Inputs, Outputs
1602
+ elementPath = input(/* @ts-ignore */
1603
+ ...(ngDevMode ? [undefined, { debugName: "elementPath" }] : /* istanbul ignore next */ []));
1604
+ rootPath = input(/* @ts-ignore */
1605
+ ...(ngDevMode ? [undefined, { debugName: "rootPath" }] : /* istanbul ignore next */ []));
1606
+ //#endregion
1607
+ //#region Variables
1608
+ dataGridDataset = inject(DataGridDataset);
1609
+ router = inject(Router);
1610
+ // A signal rather than a field: the grid announces a selection change from a subscription, and
1611
+ // this is what enables the button.
1612
+ selectionCount = signal(0, /* @ts-ignore */
1613
+ ...(ngDevMode ? [{ debugName: "selectionCount" }] : /* istanbul ignore next */ []));
1614
+ tabService = inject(TabService);
1615
+ //#endregion
1616
+ //#region Properties
1617
+ get isSelectedOneRecord() {
1618
+ return this.selectionCount() === 1;
1619
+ }
1620
+ //#endregion
1621
+ //#region Constructor and Angular life cycle methods
1622
+ ngOnInit() {
1623
+ this.dataGridDataset.selectedRowsChanged.pipe(takeUntil(this.destroy$)).subscribe(() => {
1624
+ this.selectionCount.set(this.dataGridDataset.selectedRowKeys.length);
1625
+ });
1626
+ }
1627
+ //#endregion
1628
+ //#region Event handlers
1629
+ onButtonClicked() {
1630
+ if (this.disabled() || !this.isAccessLoaded() || !this.isSelectedOneRecord || !this.isVisible()) {
1631
+ return;
1632
+ }
1633
+ let path = this.rootPath() ?? this.router.url;
1634
+ const elementPath = this.elementPath();
1635
+ if (elementPath) {
1636
+ path += `/${elementPath}`;
1637
+ }
1638
+ const selectedKey = this.dataGridDataset.selectedRowKeys[0];
1639
+ const selectedID = this.dataGridDataset.getRowID(selectedKey);
1640
+ path += `/${selectedID}`;
1641
+ this.tabService.navigateCurrentTab(new Tab({ entityBaseUrl: path, url: path }));
1642
+ }
1643
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: ButtonOpenRecordComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
1644
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.7", type: ButtonOpenRecordComponent, isStandalone: true, selector: "framework-button-open-record", inputs: { elementPath: { classPropertyName: "elementPath", publicName: "elementPath", isSignal: true, isRequired: false, transformFunction: null }, rootPath: { classPropertyName: "rootPath", publicName: "rootPath", isSignal: true, isRequired: false, transformFunction: null } }, providers: [{ provide: RibbonGroupChild, useExisting: forwardRef(() => ButtonOpenRecordComponent) }], usesInheritance: true, ngImport: i0, template: "@if (isVisible()) {\r\n <lib-ribbon-button\r\n icon=\"fa-eye\"\r\n label=\"Button-Open-Record\"\r\n [disabled]=\"disabled() || !isAccessLoaded() || !isSelectedOneRecord\"\r\n [iconSize]=\"iconSize()\"\r\n [loading]=\"loading()\"\r\n (action)=\"onButtonClicked()\"\r\n >\r\n </lib-ribbon-button>\r\n}\r\n", dependencies: [{ kind: "component", type: RibbonButtonComponent, selector: "lib-ribbon-button", inputs: ["color", "defaultOption", "disabled", "icon", "iconSize", "label", "loading", "options", "tooltip"], outputs: ["loadingChange", "action"] }] });
1645
+ }
1646
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: ButtonOpenRecordComponent, decorators: [{
1647
+ type: Component,
1648
+ args: [{ selector: 'framework-button-open-record', imports: [RibbonButtonComponent], providers: [{ provide: RibbonGroupChild, useExisting: forwardRef(() => ButtonOpenRecordComponent) }], template: "@if (isVisible()) {\r\n <lib-ribbon-button\r\n icon=\"fa-eye\"\r\n label=\"Button-Open-Record\"\r\n [disabled]=\"disabled() || !isAccessLoaded() || !isSelectedOneRecord\"\r\n [iconSize]=\"iconSize()\"\r\n [loading]=\"loading()\"\r\n (action)=\"onButtonClicked()\"\r\n >\r\n </lib-ribbon-button>\r\n}\r\n" }]
1649
+ }], propDecorators: { elementPath: [{ type: i0.Input, args: [{ isSignal: true, alias: "elementPath", required: false }] }], rootPath: [{ type: i0.Input, args: [{ isSignal: true, alias: "rootPath", required: false }] }] } });
1650
+
1651
+ class ButtonRefreshComponent extends BaseButton {
1652
+ //#region ViewChilds, Inputs, Outputs
1653
+ //#endregion
1654
+ //#region Variables
1655
+ dataGridDataset = inject(DataGridDataset, { optional: true });
1656
+ //#endregion
1657
+ //#region Properties
1658
+ //#endregion
1659
+ //#region Constructor and Angular life cycle methods
1660
+ constructor() {
1661
+ super();
1662
+ this.disabled.set(false);
1663
+ }
1664
+ ngOnInit() {
1665
+ this.dataGridDataset?.loadFinished.pipe(takeUntil(this.destroy$)).subscribe(() => {
1666
+ this.loading.set(false);
1667
+ });
1668
+ }
1669
+ //#endregion
1670
+ //#region Event handlers
1671
+ onButtonClicked() {
1672
+ this.loading.set(true);
1673
+ this.dataGridDataset?.refresh();
1674
+ }
1675
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: ButtonRefreshComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1676
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "22.1.7", type: ButtonRefreshComponent, isStandalone: true, selector: "framework-button-refresh", providers: [{ provide: RibbonGroupChild, useExisting: forwardRef(() => ButtonRefreshComponent) }], usesInheritance: true, ngImport: i0, template: "<lib-ribbon-button\r\n label=\"Button-Refresh\"\r\n icon=\"fa-sync-alt\"\r\n [disabled]=\"disabled()\"\r\n [loading]=\"loading()\"\r\n [iconSize]=\"iconSize()\"\r\n (action)=\"onButtonClicked()\"\r\n></lib-ribbon-button>\r\n", dependencies: [{ kind: "component", type: RibbonButtonComponent, selector: "lib-ribbon-button", inputs: ["color", "defaultOption", "disabled", "icon", "iconSize", "label", "loading", "options", "tooltip"], outputs: ["loadingChange", "action"] }] });
1677
+ }
1678
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: ButtonRefreshComponent, decorators: [{
1679
+ type: Component,
1680
+ args: [{ selector: 'framework-button-refresh', imports: [RibbonButtonComponent], providers: [{ provide: RibbonGroupChild, useExisting: forwardRef(() => ButtonRefreshComponent) }], template: "<lib-ribbon-button\r\n label=\"Button-Refresh\"\r\n icon=\"fa-sync-alt\"\r\n [disabled]=\"disabled()\"\r\n [loading]=\"loading()\"\r\n [iconSize]=\"iconSize()\"\r\n (action)=\"onButtonClicked()\"\r\n></lib-ribbon-button>\r\n" }]
1681
+ }], ctorParameters: () => [] });
1682
+
1683
+ class ButtonSaveComponent extends BaseButton {
1684
+ //#region ViewChilds, Inputs, Outputs
1685
+ errorModal = viewChild(ErrorModalComponent, /* @ts-ignore */
1686
+ ...(ngDevMode ? [{ debugName: "errorModal" }] : /* istanbul ignore next */ []));
1687
+ defaultOption = input(0, /* @ts-ignore */
1688
+ ...(ngDevMode ? [{ debugName: "defaultOption" }] : /* istanbul ignore next */ []));
1689
+ //#endregion
1690
+ //#region Variables
1691
+ dataProviderService = inject(DataProviderService);
1692
+ formService = inject(FormService);
1693
+ router = inject(Router);
1694
+ tabService = inject(TabService);
1695
+ //#endregion
1696
+ //#region Properties
1697
+ get isFormEditMode() {
1698
+ return this.formService.isEditMode;
1699
+ }
1700
+ get isFormLoading() {
1701
+ return this.formService.loading;
1702
+ }
1703
+ //#endregion
1704
+ //#region Constructor and Angular life cycle methods
1705
+ constructor() {
1706
+ super();
1707
+ this.options.set([
1708
+ { id: 'save', label: 'Button-Save', icon: 'fa-save' },
1709
+ { id: 'save-and-close', label: 'Button-Save-And-Close', icon: 'fa-save' },
1710
+ { id: 'save-and-new', label: 'Button-Save-And-New', icon: 'fa-save' },
1711
+ ]);
1712
+ }
1713
+ //#endregion
1714
+ //#region Event handlers
1715
+ onButtonClicked(option) {
1716
+ this.startLoading();
1717
+ this.formService.markAllAsTouched();
1718
+ if (!this.formService.isValid) {
1719
+ this.finishLoading('warning');
1720
+ return;
1721
+ }
1722
+ this.saveModel()
1723
+ .pipe(take(1))
1724
+ .subscribe({
1725
+ next: (model) => {
1726
+ this.finishLoading('success');
1727
+ this.formService.enableForm();
1728
+ this.formService.model = model;
1729
+ this.formService.cancelEdit();
1730
+ switch (option) {
1731
+ case 'save-and-close':
1732
+ this.tabService.navigateBackOrCloseActiveTab();
1733
+ break;
1734
+ case 'save-and-new': {
1735
+ this.dataProviderService.updateModel(model);
1736
+ const detailsRoute = RouteHelper.getRouteByData(this.router.routerState.root.snapshot, FRAMEWORK_VIEW_TYPE, FrameworkViewType.Details);
1737
+ const savedEntityUrl = `${RouteHelper.getRouteURL(detailsRoute.parent)}/${model.id}`;
1738
+ const currentRouteUrl = RouteHelper.getRouteURL(detailsRoute);
1739
+ if (currentRouteUrl !== savedEntityUrl) {
1740
+ // Router URL differs from saved entity (new entity, or regular save
1741
+ // that only used location.replaceState). Redirect to sync the router
1742
+ // to /{id} invisibly, then navigate to /new normally. The real second
1743
+ // navigation detaches and caches the /{id} component so back-navigation
1744
+ // reattaches it instantly without reloading from the server.
1745
+ this.tabService.redirectCurrentTab(savedEntityUrl);
1746
+ this.tabService.setRouteRedirect(currentRouteUrl, savedEntityUrl);
1747
+ this.router.navigateByUrl(savedEntityUrl).then(() => {
1748
+ this.navigateToNew();
1749
+ });
1750
+ }
1751
+ else {
1752
+ // Router is already at /{id}. Navigate to /new directly.
1753
+ // The /{id} component is cached by the reuse strategy.
1754
+ this.navigateToNew();
1755
+ }
1756
+ break;
1757
+ }
1758
+ default: {
1759
+ this.dataProviderService.updateModel(model);
1760
+ const targetRoute = RouteHelper.getRouteByData(this.router.routerState.root.snapshot, FRAMEWORK_VIEW_TYPE, FrameworkViewType.Details);
1761
+ const url = `${RouteHelper.getRouteURL(targetRoute.parent)}/${model.id}`;
1762
+ if (!this.tabService.isUrlActive(url)) {
1763
+ const currentRoute = RouteHelper.getRouteURL(targetRoute);
1764
+ this.tabService.redirectCurrentTab(url);
1765
+ // Sync the Angular router to /{id} invisibly. Without this, the router
1766
+ // stays at the old URL (e.g., /new) and subsequent navigations to that
1767
+ // URL become no-ops (the New button would stop working after Save).
1768
+ if (currentRoute !== url) {
1769
+ this.tabService.setRouteRedirect(currentRoute, url);
1770
+ this.router.navigateByUrl(url);
1771
+ }
1772
+ }
1773
+ break;
1774
+ }
1775
+ }
1776
+ },
1777
+ error: (e) => {
1778
+ this.finishLoading('warning');
1779
+ this.formService.enableForm();
1780
+ if (e.status === 400) {
1781
+ this.formService.setValidationErrorsFromHttpResponse(e);
1782
+ }
1783
+ else {
1784
+ let errorMessage = 'Modal-Failed-DefaultMessage';
1785
+ if (typeof e.error === 'string') {
1786
+ errorMessage = e.error;
1787
+ }
1788
+ this.errorModal()?.showModal(errorMessage);
1789
+ }
1790
+ },
1791
+ complete: () => {
1792
+ this.formService.loading = false;
1793
+ },
1794
+ });
1795
+ }
1796
+ //#endregion
1797
+ //#region Public methods
1798
+ finishLoading(status) {
1799
+ super.finishLoading(status);
1800
+ }
1801
+ //#endregion
1802
+ //#region Private methods
1803
+ navigateToNew() {
1804
+ const targetRoute = RouteHelper.getRouteByData(this.router.routerState.root.snapshot, FRAMEWORK_VIEW_TYPE, FrameworkViewType.Details);
1805
+ const url = targetRoute
1806
+ ? RouteHelper.getRouteURL(targetRoute.parent)
1807
+ : RouteHelper.getRouteURL(this.router.routerState.root.snapshot, true);
1808
+ const tab = new Tab({ entityBaseUrl: `${url}/new`, url: `${url}/new` });
1809
+ this.tabService.navigateCurrentTab(tab);
1810
+ }
1811
+ saveModel() {
1812
+ this.formService.disableForm();
1813
+ const model = this.formService.getModelFromForm();
1814
+ return this.dataProviderService.saveModel(model);
1815
+ }
1816
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: ButtonSaveComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1817
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.7", type: ButtonSaveComponent, isStandalone: true, selector: "framework-button-save", inputs: { defaultOption: { classPropertyName: "defaultOption", publicName: "defaultOption", isSignal: true, isRequired: false, transformFunction: null } }, providers: [{ provide: RibbonGroupChild, useExisting: forwardRef(() => ButtonSaveComponent) }], viewQueries: [{ propertyName: "errorModal", first: true, predicate: ErrorModalComponent, descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: "@if (isVisible()) {\r\n <lib-ribbon-button\r\n label=\"Button-Save\"\r\n icon=\"fa-save\"\r\n [defaultOption]=\"defaultOption()\"\r\n [disabled]=\"disabled() || !isAccessLoaded() || !isFormEditMode || isFormLoading\"\r\n [loading]=\"loading()\"\r\n [options]=\"options()\"\r\n (action)=\"onButtonClicked($event)\"\r\n >\r\n </lib-ribbon-button>\r\n}\r\n\r\n<framework-error-modal></framework-error-modal>\r\n", dependencies: [{ kind: "component", type: ErrorModalComponent, selector: "framework-error-modal", inputs: ["errorMessage", "size", "subtitle", "title"], outputs: ["errorMessageChange", "subtitleChange", "titleChange", "closed"] }, { kind: "component", type: RibbonButtonComponent, selector: "lib-ribbon-button", inputs: ["color", "defaultOption", "disabled", "icon", "iconSize", "label", "loading", "options", "tooltip"], outputs: ["loadingChange", "action"] }] });
1818
+ }
1819
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: ButtonSaveComponent, decorators: [{
1820
+ type: Component,
1821
+ args: [{ selector: 'framework-button-save', imports: [ErrorModalComponent, RibbonButtonComponent], providers: [{ provide: RibbonGroupChild, useExisting: forwardRef(() => ButtonSaveComponent) }], template: "@if (isVisible()) {\r\n <lib-ribbon-button\r\n label=\"Button-Save\"\r\n icon=\"fa-save\"\r\n [defaultOption]=\"defaultOption()\"\r\n [disabled]=\"disabled() || !isAccessLoaded() || !isFormEditMode || isFormLoading\"\r\n [loading]=\"loading()\"\r\n [options]=\"options()\"\r\n (action)=\"onButtonClicked($event)\"\r\n >\r\n </lib-ribbon-button>\r\n}\r\n\r\n<framework-error-modal></framework-error-modal>\r\n" }]
1822
+ }], ctorParameters: () => [], propDecorators: { errorModal: [{ type: i0.ViewChild, args: [i0.forwardRef(() => ErrorModalComponent), { isSignal: true }] }], defaultOption: [{ type: i0.Input, args: [{ isSignal: true, alias: "defaultOption", required: false }] }] } });
1823
+
1824
+ class ButtonViewsComponent extends BaseButton {
1825
+ //#region ViewChilds, Inputs, Outputs
1826
+ detailsViewRoute = input(null, /* @ts-ignore */
1827
+ ...(ngDevMode ? [{ debugName: "detailsViewRoute" }] : /* istanbul ignore next */ []));
1828
+ //#endregion
1829
+ //#region Variables
1830
+ baseUrlPath;
1831
+ dataProviderService = inject(DataProviderService, { optional: true });
1832
+ isInternalNavigation = false;
1833
+ router = inject(Router);
1834
+ // A signal rather than a field: switching views changes which option the dropdown marks as the
1835
+ // current one, and the switch arrives from a router event.
1836
+ selectedViewId = signal(undefined, /* @ts-ignore */
1837
+ ...(ngDevMode ? [{ debugName: "selectedViewId" }] : /* istanbul ignore next */ []));
1838
+ tabService = inject(TabService);
1839
+ tabViewService = inject(TabViewService);
1840
+ //#endregion
1841
+ //#region Properties
1842
+ get hasOptions() {
1843
+ return this.options().some((option) => (option.isVisible ?? true) &&
1844
+ (option.allowedActions === undefined ||
1845
+ option.allowedActions.length === 0 ||
1846
+ option.isAccessAllowed === true ||
1847
+ (option.isAccessAllowed === undefined && option.allowedActions?.length > 0)));
1848
+ }
1849
+ get isNewEntity() {
1850
+ return !(this.dataProviderService?.hasEntityID ?? false);
1851
+ }
1852
+ get selectedView() {
1853
+ return this.options().find((option) => option.id === this.selectedViewId());
1854
+ }
1855
+ get defaultViewId() {
1856
+ return this.options()[0].id ?? '';
1857
+ }
1858
+ get selectedViewUrl() {
1859
+ let url = this.baseUrlPath ?? '';
1860
+ const selectedView = this.selectedView;
1861
+ if (selectedView && selectedView.id) {
1862
+ url += `/${selectedView.id}`;
1863
+ }
1864
+ return url;
1865
+ }
1866
+ //#endregion
1867
+ //#region Constructor and Angular life cycle methods
1868
+ ngOnInit() {
1869
+ const detailsViewRoute = this.detailsViewRoute();
1870
+ if (detailsViewRoute) {
1871
+ this.options.set(detailsViewRoute.routeConfig?.children
1872
+ ?.filter((route) => !!route.data && route.data['ignoreRoute'] !== true)
1873
+ ?.map((route) => {
1874
+ const data = route.data ?? {};
1875
+ const option = {
1876
+ id: route.path ?? '',
1877
+ isDisabled: false,
1878
+ isVisible: true,
1879
+ label: data['title'] ?? '',
1880
+ };
1881
+ const icon = data['icon'];
1882
+ if (icon) {
1883
+ option.icon = icon;
1884
+ }
1885
+ const allowedActions = data['allowedActions'];
1886
+ if (allowedActions && allowedActions.length > 0) {
1887
+ option.allowedActions = allowedActions;
1888
+ }
1889
+ return option;
1890
+ }) ?? []);
1891
+ const url = RouteHelper.getRouteURL(detailsViewRoute);
1892
+ this.baseUrlPath = url;
1893
+ const option = detailsViewRoute.children[0]?.url?.map((segment) => segment.path)?.join('/') ?? '';
1894
+ if (option === this.defaultViewId) {
1895
+ // We just need to check if the current option is the default, otherwise the router.events will handle the option selection.
1896
+ this.changeSelectedView(this.defaultViewId);
1897
+ }
1898
+ }
1899
+ this.router.events
1900
+ .pipe(filter((event) => event instanceof NavigationEnd), takeUntil(this.destroy$))
1901
+ .subscribe((event) => {
1902
+ if (this.detailsViewRoute()) {
1903
+ // Use the current router state to get the up-to-date URL, since the stored
1904
+ // detailsViewRoute snapshot is immutable and becomes stale after save redirects
1905
+ // (e.g., /new → /:id).
1906
+ const currentRoute = RouteHelper.getRouteByData(this.router.routerState.root.snapshot, FRAMEWORK_VIEW_TYPE, FrameworkViewType.Details);
1907
+ // Every details tab keeps this subscription alive, including the ones the reuse
1908
+ // strategy has detached, and the lookup above reads the *router*, which names
1909
+ // whichever tab the navigation went to. Without this guard a detached tab adopted
1910
+ // the base path of the tab being opened and then switched itself to that tab's view,
1911
+ // tearing its own ribbon down and rebuilding it -- which is how an active filter lost
1912
+ // its Clear filters button while the grid stayed filtered. Comparing the route
1913
+ // definition rather than the URL keeps the one navigation that legitimately changes
1914
+ // this tab's path: the redirect from /new to /:id after a save.
1915
+ if (currentRoute && currentRoute.routeConfig === this.detailsViewRoute().routeConfig) {
1916
+ const url = RouteHelper.getRouteURL(currentRoute);
1917
+ this.baseUrlPath = url;
1918
+ const activeViewId = this.getActiveOption(event.url);
1919
+ if (!this.isInternalNavigation) {
1920
+ if (this.selectedViewId() !== activeViewId) {
1921
+ this.changeSelectedView(activeViewId);
1922
+ }
1923
+ }
1924
+ else {
1925
+ this.isInternalNavigation = false;
1926
+ }
1927
+ }
1928
+ }
1929
+ });
1930
+ }
1931
+ //#endregion
1932
+ //#region Event handlers
1933
+ onViewClicked(selectedOption) {
1934
+ this.changeSelectedView(selectedOption);
1935
+ this.isInternalNavigation = true;
1936
+ if (selectedOption === this.defaultViewId) {
1937
+ // Default view: remove sub-view entry, navigate back to detail view
1938
+ this.tabService.replaceCurrentTabSubView(this.baseUrlPath);
1939
+ }
1940
+ else {
1941
+ const tab = new Tab({
1942
+ title: this.selectedView?.label,
1943
+ url: this.selectedViewUrl,
1944
+ });
1945
+ this.tabService.replaceCurrentTabSubView(this.baseUrlPath, tab);
1946
+ }
1947
+ }
1948
+ //#endregion
1949
+ //#region Public methods
1950
+ //#endregion
1951
+ //#region Private methods
1952
+ /**
1953
+ * Marks the chosen view as the current one, which is how the dropdown greys its own entry out.
1954
+ *
1955
+ * The options are rebuilt rather than edited in place: the ribbon button takes the list as an
1956
+ * input, so a field changed inside it is a change nothing can see.
1957
+ */
1958
+ changeSelectedView(viewId) {
1959
+ this.selectedViewId.set(viewId);
1960
+ this.tabViewService.setActiveView(viewId ?? '');
1961
+ this.options.update((options) => options.map((option) => option.isDisabled === (option.id === viewId) ? option : { ...option, isDisabled: option.id === viewId }));
1962
+ }
1963
+ getActiveOption(url) {
1964
+ const option = url.replace(this.baseUrlPath ?? '', '').substring(1);
1965
+ if (this.options().some((o) => o.id === option)) {
1966
+ return option;
1967
+ }
1968
+ return this.defaultViewId;
1969
+ }
1970
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: ButtonViewsComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
1971
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.7", type: ButtonViewsComponent, isStandalone: true, selector: "framework-button-views", inputs: { detailsViewRoute: { classPropertyName: "detailsViewRoute", publicName: "detailsViewRoute", isSignal: true, isRequired: false, transformFunction: null } }, providers: [{ provide: RibbonGroupChild, useExisting: forwardRef(() => ButtonViewsComponent) }], usesInheritance: true, ngImport: i0, template: "@if (hasOptions) {\r\n <lib-ribbon-button\r\n icon=\"fa-eye\"\r\n label=\"Button-Views\"\r\n [disabled]=\"!isAccessLoaded() || isNewEntity\"\r\n [options]=\"options()\"\r\n (action)=\"onViewClicked($event)\"\r\n >\r\n </lib-ribbon-button>\r\n}\r\n", dependencies: [{ kind: "component", type: RibbonButtonComponent, selector: "lib-ribbon-button", inputs: ["color", "defaultOption", "disabled", "icon", "iconSize", "label", "loading", "options", "tooltip"], outputs: ["loadingChange", "action"] }] });
1972
+ }
1973
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: ButtonViewsComponent, decorators: [{
1974
+ type: Component,
1975
+ args: [{ selector: 'framework-button-views', imports: [RibbonButtonComponent], providers: [{ provide: RibbonGroupChild, useExisting: forwardRef(() => ButtonViewsComponent) }], template: "@if (hasOptions) {\r\n <lib-ribbon-button\r\n icon=\"fa-eye\"\r\n label=\"Button-Views\"\r\n [disabled]=\"!isAccessLoaded() || isNewEntity\"\r\n [options]=\"options()\"\r\n (action)=\"onViewClicked($event)\"\r\n >\r\n </lib-ribbon-button>\r\n}\r\n" }]
1976
+ }], propDecorators: { detailsViewRoute: [{ type: i0.Input, args: [{ isSignal: true, alias: "detailsViewRoute", required: false }] }] } });
1977
+
1978
+ class ButtonComponent extends BaseButton {
1979
+ //#region ViewChilds, Inputs, Outputs
1980
+ color = input('text-primary-500', /* @ts-ignore */
1981
+ ...(ngDevMode ? [{ debugName: "color" }] : /* istanbul ignore next */ []));
1982
+ icon = input(/* @ts-ignore */
1983
+ ...(ngDevMode ? [undefined, { debugName: "icon" }] : /* istanbul ignore next */ []));
1984
+ label = input('', /* @ts-ignore */
1985
+ ...(ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []));
1986
+ action = output();
1987
+ //#endregion
1988
+ //#region Variables
1989
+ //#endregion
1990
+ //#region Properties
1991
+ //#endregion
1992
+ //#region Constructor and Angular life cycle methods
1993
+ //#endregion
1994
+ //#region Event handlers
1995
+ onButtonClicked() {
1996
+ if (this.disabled() || !this.isAccessLoaded() || !this.isVisible()) {
1997
+ return;
1998
+ }
1999
+ this.action.emit();
2000
+ }
2001
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: ButtonComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
2002
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.7", type: ButtonComponent, isStandalone: true, selector: "framework-button", inputs: { color: { classPropertyName: "color", publicName: "color", isSignal: true, isRequired: false, transformFunction: null }, icon: { classPropertyName: "icon", publicName: "icon", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { action: "action" }, providers: [{ provide: RibbonGroupChild, useExisting: forwardRef(() => ButtonComponent) }], usesInheritance: true, ngImport: i0, template: "@if (isVisible()) {\r\n <lib-ribbon-button\r\n [color]=\"color()\"\r\n [disabled]=\"disabled() || !isAccessLoaded()\"\r\n [icon]=\"icon()\"\r\n [iconSize]=\"iconSize()\"\r\n [label]=\"label()\"\r\n [loading]=\"loading()\"\r\n [options]=\"options()\"\r\n (action)=\"onButtonClicked()\"\r\n >\r\n </lib-ribbon-button>\r\n}\r\n", dependencies: [{ kind: "component", type: RibbonButtonComponent, selector: "lib-ribbon-button", inputs: ["color", "defaultOption", "disabled", "icon", "iconSize", "label", "loading", "options", "tooltip"], outputs: ["loadingChange", "action"] }] });
2003
+ }
2004
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: ButtonComponent, decorators: [{
2005
+ type: Component,
2006
+ args: [{ selector: 'framework-button', imports: [RibbonButtonComponent], providers: [{ provide: RibbonGroupChild, useExisting: forwardRef(() => ButtonComponent) }], template: "@if (isVisible()) {\r\n <lib-ribbon-button\r\n [color]=\"color()\"\r\n [disabled]=\"disabled() || !isAccessLoaded()\"\r\n [icon]=\"icon()\"\r\n [iconSize]=\"iconSize()\"\r\n [label]=\"label()\"\r\n [loading]=\"loading()\"\r\n [options]=\"options()\"\r\n (action)=\"onButtonClicked()\"\r\n >\r\n </lib-ribbon-button>\r\n}\r\n" }]
2007
+ }], propDecorators: { color: [{ type: i0.Input, args: [{ isSignal: true, alias: "color", required: false }] }], icon: [{ type: i0.Input, args: [{ isSignal: true, alias: "icon", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], action: [{ type: i0.Output, args: ["action"] }] } });
2008
+
2009
+ /**
2010
+ * @deprecated Use standalone {@link ButtonEditComponent} instead.
2011
+ * Migrate by replacing this legacy component with the standalone equivalent
2012
+ * and using inject() for dependency injection.
2013
+ */
2014
+ class ButtonEditLegacyComponent extends BaseButton {
2015
+ //#region ViewChilds, Inputs, Outputs
2016
+ cancelColor = input('text-red-500', /* @ts-ignore */
2017
+ ...(ngDevMode ? [{ debugName: "cancelColor" }] : /* istanbul ignore next */ []));
2018
+ cancelIcon = input('fa-ban', /* @ts-ignore */
2019
+ ...(ngDevMode ? [{ debugName: "cancelIcon" }] : /* istanbul ignore next */ []));
2020
+ cancelLabel = input('Button-Cancel-Edit', /* @ts-ignore */
2021
+ ...(ngDevMode ? [{ debugName: "cancelLabel" }] : /* istanbul ignore next */ []));
2022
+ editIcon = input('fa-edit', /* @ts-ignore */
2023
+ ...(ngDevMode ? [{ debugName: "editIcon" }] : /* istanbul ignore next */ []));
2024
+ editLabel = input('Button-Edit', /* @ts-ignore */
2025
+ ...(ngDevMode ? [{ debugName: "editLabel" }] : /* istanbul ignore next */ []));
2026
+ form = input(/* @ts-ignore */
2027
+ ...(ngDevMode ? [undefined, { debugName: "form" }] : /* istanbul ignore next */ []));
2028
+ modal = input(/* @ts-ignore */
2029
+ ...(ngDevMode ? [undefined, { debugName: "modal" }] : /* istanbul ignore next */ []));
2030
+ //#endregion
2031
+ //#region Variables
2032
+ //#endregion
2033
+ //#region Properties
2034
+ //#endregion
2035
+ //#region Constructor and Angular life cycle methods
2036
+ //#endregion
2037
+ //#region Event handlers
2038
+ onCancelAction() {
2039
+ this.form()?.cancelEdit();
2040
+ }
2041
+ onEditAction() {
2042
+ const form = this.form();
2043
+ if (form) {
2044
+ form.beginEdit();
2045
+ }
2046
+ else {
2047
+ this.modal()?.toggle();
2048
+ }
2049
+ }
2050
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: ButtonEditLegacyComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
2051
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.7", type: ButtonEditLegacyComponent, isStandalone: true, selector: "framework-button-edit-legacy", inputs: { cancelColor: { classPropertyName: "cancelColor", publicName: "cancelColor", isSignal: true, isRequired: false, transformFunction: null }, cancelIcon: { classPropertyName: "cancelIcon", publicName: "cancelIcon", isSignal: true, isRequired: false, transformFunction: null }, cancelLabel: { classPropertyName: "cancelLabel", publicName: "cancelLabel", isSignal: true, isRequired: false, transformFunction: null }, editIcon: { classPropertyName: "editIcon", publicName: "editIcon", isSignal: true, isRequired: false, transformFunction: null }, editLabel: { classPropertyName: "editLabel", publicName: "editLabel", isSignal: true, isRequired: false, transformFunction: null }, form: { classPropertyName: "form", publicName: "form", isSignal: true, isRequired: false, transformFunction: null }, modal: { classPropertyName: "modal", publicName: "modal", isSignal: true, isRequired: false, transformFunction: null } }, providers: [{ provide: RibbonGroupChild, useExisting: forwardRef(() => ButtonEditLegacyComponent) }], usesInheritance: true, ngImport: i0, template: "@let legacyForm = form();\r\n\r\n@if (isVisible() && (!legacyForm || legacyForm.viewMode !== 'edit')) {\r\n <lib-ribbon-button\r\n [disabled]=\"disabled() || (!!legacyForm && legacyForm.loading)\"\r\n [icon]=\"editIcon()\"\r\n [iconSize]=\"iconSize()\"\r\n [label]=\"editLabel()\"\r\n [loading]=\"loading()\"\r\n (action)=\"onEditAction()\"\r\n ></lib-ribbon-button>\r\n}\r\n@if (isVisible() && legacyForm && legacyForm.viewMode === 'edit' && !legacyForm.isNew) {\r\n <lib-ribbon-button\r\n [color]=\"cancelColor()\"\r\n [disabled]=\"disabled() || legacyForm.loading\"\r\n [icon]=\"cancelIcon()\"\r\n [label]=\"cancelLabel()\"\r\n [loading]=\"loading()\"\r\n (action)=\"onCancelAction()\"\r\n ></lib-ribbon-button>\r\n}\r\n", dependencies: [{ kind: "component", type: RibbonButtonComponent, selector: "lib-ribbon-button", inputs: ["color", "defaultOption", "disabled", "icon", "iconSize", "label", "loading", "options", "tooltip"], outputs: ["loadingChange", "action"] }] });
2052
+ }
2053
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: ButtonEditLegacyComponent, decorators: [{
2054
+ type: Component,
2055
+ args: [{ selector: 'framework-button-edit-legacy', imports: [RibbonButtonComponent], providers: [{ provide: RibbonGroupChild, useExisting: forwardRef(() => ButtonEditLegacyComponent) }], template: "@let legacyForm = form();\r\n\r\n@if (isVisible() && (!legacyForm || legacyForm.viewMode !== 'edit')) {\r\n <lib-ribbon-button\r\n [disabled]=\"disabled() || (!!legacyForm && legacyForm.loading)\"\r\n [icon]=\"editIcon()\"\r\n [iconSize]=\"iconSize()\"\r\n [label]=\"editLabel()\"\r\n [loading]=\"loading()\"\r\n (action)=\"onEditAction()\"\r\n ></lib-ribbon-button>\r\n}\r\n@if (isVisible() && legacyForm && legacyForm.viewMode === 'edit' && !legacyForm.isNew) {\r\n <lib-ribbon-button\r\n [color]=\"cancelColor()\"\r\n [disabled]=\"disabled() || legacyForm.loading\"\r\n [icon]=\"cancelIcon()\"\r\n [label]=\"cancelLabel()\"\r\n [loading]=\"loading()\"\r\n (action)=\"onCancelAction()\"\r\n ></lib-ribbon-button>\r\n}\r\n" }]
2056
+ }], propDecorators: { cancelColor: [{ type: i0.Input, args: [{ isSignal: true, alias: "cancelColor", required: false }] }], cancelIcon: [{ type: i0.Input, args: [{ isSignal: true, alias: "cancelIcon", required: false }] }], cancelLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "cancelLabel", required: false }] }], editIcon: [{ type: i0.Input, args: [{ isSignal: true, alias: "editIcon", required: false }] }], editLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "editLabel", required: false }] }], form: [{ type: i0.Input, args: [{ isSignal: true, alias: "form", required: false }] }], modal: [{ type: i0.Input, args: [{ isSignal: true, alias: "modal", required: false }] }] } });
2057
+
2058
+ /**
2059
+ * @deprecated Use standalone {@link ButtonNewComponent} instead.
2060
+ * Migrate by replacing this legacy component with the standalone equivalent
2061
+ * and using inject() for dependency injection.
2062
+ */
2063
+ class ButtonNewLegacyComponent extends BaseButton {
2064
+ //#region ViewChilds, Inputs, Outputs
2065
+ endpoint = input('new', /* @ts-ignore */
2066
+ ...(ngDevMode ? [{ debugName: "endpoint" }] : /* istanbul ignore next */ []));
2067
+ path = input('', /* @ts-ignore */
2068
+ ...(ngDevMode ? [{ debugName: "path" }] : /* istanbul ignore next */ []));
2069
+ tabView = input(/* @ts-ignore */
2070
+ ...(ngDevMode ? [undefined, { debugName: "tabView" }] : /* istanbul ignore next */ []));
2071
+ //#endregion
2072
+ //#region Variables
2073
+ tabService = inject(TabService);
2074
+ //#endregion
2075
+ //#region Properties
2076
+ //#endregion
2077
+ //#region Constructor and Angular life cycle methods
2078
+ //#endregion
2079
+ //#region Event handlers
2080
+ //#endregion
2081
+ //#region Public methods
2082
+ clicked() {
2083
+ this.tabService.navigateTo(new Tab({ url: `${this.path()}/${this.endpoint()}` }));
2084
+ // this.tabService.navigateTab(this.tabView, `${this.path}/${this.endpoint}`);
2085
+ }
2086
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: ButtonNewLegacyComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
2087
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.7", type: ButtonNewLegacyComponent, isStandalone: true, selector: "framework-button-new-legacy", inputs: { endpoint: { classPropertyName: "endpoint", publicName: "endpoint", isSignal: true, isRequired: false, transformFunction: null }, path: { classPropertyName: "path", publicName: "path", isSignal: true, isRequired: false, transformFunction: null }, tabView: { classPropertyName: "tabView", publicName: "tabView", isSignal: true, isRequired: false, transformFunction: null } }, providers: [{ provide: RibbonGroupChild, useExisting: forwardRef(() => ButtonNewLegacyComponent) }], usesInheritance: true, ngImport: i0, template: "@if (isVisible()) {\r\n <lib-ribbon-button\r\n [label]=\"'Button-New'\"\r\n [icon]=\"'fa-plus'\"\r\n [disabled]=\"disabled()\"\r\n [loading]=\"loading()\"\r\n (action)=\"clicked()\"\r\n ></lib-ribbon-button>\r\n}\r\n", dependencies: [{ kind: "component", type: RibbonButtonComponent, selector: "lib-ribbon-button", inputs: ["color", "defaultOption", "disabled", "icon", "iconSize", "label", "loading", "options", "tooltip"], outputs: ["loadingChange", "action"] }] });
2088
+ }
2089
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: ButtonNewLegacyComponent, decorators: [{
2090
+ type: Component,
2091
+ args: [{ selector: 'framework-button-new-legacy', imports: [RibbonButtonComponent], providers: [{ provide: RibbonGroupChild, useExisting: forwardRef(() => ButtonNewLegacyComponent) }], template: "@if (isVisible()) {\r\n <lib-ribbon-button\r\n [label]=\"'Button-New'\"\r\n [icon]=\"'fa-plus'\"\r\n [disabled]=\"disabled()\"\r\n [loading]=\"loading()\"\r\n (action)=\"clicked()\"\r\n ></lib-ribbon-button>\r\n}\r\n" }]
2092
+ }], propDecorators: { endpoint: [{ type: i0.Input, args: [{ isSignal: true, alias: "endpoint", required: false }] }], path: [{ type: i0.Input, args: [{ isSignal: true, alias: "path", required: false }] }], tabView: [{ type: i0.Input, args: [{ isSignal: true, alias: "tabView", required: false }] }] } });
2093
+
2094
+ /**
2095
+ * @deprecated Use standalone {@link ButtonOpenRecordComponent} instead.
2096
+ * Migrate by replacing this legacy component with the standalone equivalent
2097
+ * and using inject() for dependency injection.
2098
+ */
2099
+ class ButtonOpenRecordLegacyComponent extends BaseButton {
2100
+ //#region ViewChilds, Inputs, Outputs
2101
+ endpoint = input('new', /* @ts-ignore */
2102
+ ...(ngDevMode ? [{ debugName: "endpoint" }] : /* istanbul ignore next */ []));
2103
+ path = input('', /* @ts-ignore */
2104
+ ...(ngDevMode ? [{ debugName: "path" }] : /* istanbul ignore next */ []));
2105
+ tabView = input(/* @ts-ignore */
2106
+ ...(ngDevMode ? [undefined, { debugName: "tabView" }] : /* istanbul ignore next */ []));
2107
+ //#endregion
2108
+ //#region Variables
2109
+ dataGridDataset = inject(DataGridDataset);
2110
+ // A signal rather than a field: the grid announces a selection change from a subscription.
2111
+ selectionCount = signal(0, /* @ts-ignore */
2112
+ ...(ngDevMode ? [{ debugName: "selectionCount" }] : /* istanbul ignore next */ []));
2113
+ tabService = inject(TabService);
2114
+ //#endregion
2115
+ //#region Properties
2116
+ //#endregion
2117
+ //#region Constructor and Angular life cycle methods
2118
+ ngOnInit() {
2119
+ this.dataGridDataset.selectedRowsChanged.pipe(takeUntil(this.destroy$)).subscribe(() => {
2120
+ this.selectionCount.set(this.dataGridDataset.selectedRowKeys.length);
2121
+ });
2122
+ }
2123
+ //#endregion
2124
+ //#region Event handlers
2125
+ //#endregion
2126
+ //#region Public methods
2127
+ clicked() {
2128
+ if (this.dataGridDataset.hasSelectedRows && this.selectionCount() === 1) {
2129
+ const selectedKey = this.dataGridDataset.selectedRowKeys[0];
2130
+ const selectedID = this.dataGridDataset.getRowID(selectedKey);
2131
+ this.tabService.navigateTo(new Tab({ url: `${this.path()}/${selectedID}` }));
2132
+ // this.tabService.navigateTab(this.tabView, `${this.path}/${this.dataGridDataset.selectedRows[0]}`);
2133
+ }
2134
+ }
2135
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: ButtonOpenRecordLegacyComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
2136
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.7", type: ButtonOpenRecordLegacyComponent, isStandalone: true, selector: "framework-button-open-record-legacy", inputs: { endpoint: { classPropertyName: "endpoint", publicName: "endpoint", isSignal: true, isRequired: false, transformFunction: null }, path: { classPropertyName: "path", publicName: "path", isSignal: true, isRequired: false, transformFunction: null }, tabView: { classPropertyName: "tabView", publicName: "tabView", isSignal: true, isRequired: false, transformFunction: null } }, providers: [{ provide: RibbonGroupChild, useExisting: forwardRef(() => ButtonOpenRecordLegacyComponent) }], usesInheritance: true, ngImport: i0, template: "@if (isVisible()) {\r\n <lib-ribbon-button\r\n [label]=\"'Button-Open-Record'\"\r\n [icon]=\"'fa-eye'\"\r\n [disabled]=\"disabled() || selectionCount() !== 1\"\r\n [loading]=\"loading()\"\r\n (action)=\"clicked()\"\r\n ></lib-ribbon-button>\r\n}\r\n", dependencies: [{ kind: "component", type: RibbonButtonComponent, selector: "lib-ribbon-button", inputs: ["color", "defaultOption", "disabled", "icon", "iconSize", "label", "loading", "options", "tooltip"], outputs: ["loadingChange", "action"] }] });
2137
+ }
2138
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: ButtonOpenRecordLegacyComponent, decorators: [{
2139
+ type: Component,
2140
+ args: [{ selector: 'framework-button-open-record-legacy', imports: [RibbonButtonComponent], providers: [{ provide: RibbonGroupChild, useExisting: forwardRef(() => ButtonOpenRecordLegacyComponent) }], template: "@if (isVisible()) {\r\n <lib-ribbon-button\r\n [label]=\"'Button-Open-Record'\"\r\n [icon]=\"'fa-eye'\"\r\n [disabled]=\"disabled() || selectionCount() !== 1\"\r\n [loading]=\"loading()\"\r\n (action)=\"clicked()\"\r\n ></lib-ribbon-button>\r\n}\r\n" }]
2141
+ }], propDecorators: { endpoint: [{ type: i0.Input, args: [{ isSignal: true, alias: "endpoint", required: false }] }], path: [{ type: i0.Input, args: [{ isSignal: true, alias: "path", required: false }] }], tabView: [{ type: i0.Input, args: [{ isSignal: true, alias: "tabView", required: false }] }] } });
2142
+
2143
+ /**
2144
+ * @deprecated Use standalone {@link ButtonSaveComponent} instead.
2145
+ * Migrate by replacing this legacy component with the standalone equivalent
2146
+ * and using inject() for dependency injection.
2147
+ */
2148
+ class ButtonSaveLegacyComponent extends BaseButton {
2149
+ //#region ViewChilds, Inputs, Outputs
2150
+ errorModal = viewChild(ErrorModalComponent, /* @ts-ignore */
2151
+ ...(ngDevMode ? [{ debugName: "errorModal" }] : /* istanbul ignore next */ []));
2152
+ defaultOption = input(0, /* @ts-ignore */
2153
+ ...(ngDevMode ? [{ debugName: "defaultOption" }] : /* istanbul ignore next */ []));
2154
+ form = input(/* @ts-ignore */
2155
+ ...(ngDevMode ? [undefined, { debugName: "form" }] : /* istanbul ignore next */ []));
2156
+ tabView = input(/* @ts-ignore */
2157
+ ...(ngDevMode ? [undefined, { debugName: "tabView" }] : /* istanbul ignore next */ []));
2158
+ //#endregion
2159
+ //#region Variables
2160
+ formService = inject(FormService);
2161
+ tabService = inject(TabService);
2162
+ //#endregion
2163
+ //#region Properties
2164
+ //#endregion
2165
+ //#region Constructor and Angular life cycle methods
2166
+ constructor() {
2167
+ super();
2168
+ this.options.set([
2169
+ { id: 'save', label: 'Button-Save', icon: 'fa-save' },
2170
+ { id: 'save-and-close', label: 'Button-Save-And-Close', icon: 'fa-save' },
2171
+ { id: 'save-and-new', label: 'Button-Save-And-New', icon: 'fa-save' },
2172
+ ]);
2173
+ }
2174
+ //#endregion
2175
+ //#region Event handlers
2176
+ onButtonClick(option) {
2177
+ const form = this.form();
2178
+ if (!form) {
2179
+ return;
2180
+ }
2181
+ this.startLoading();
2182
+ this.setTabViewLoading(true);
2183
+ form
2184
+ .save()
2185
+ .pipe(take(1))
2186
+ .subscribe({
2187
+ next: (model) => {
2188
+ this.finishLoading('success');
2189
+ this.formService.model = model;
2190
+ form.cancelEdit();
2191
+ switch (option) {
2192
+ case 'save-and-close':
2193
+ this.tabService.closeActiveTab();
2194
+ // this.tabService.closeTab(this.tabView);
2195
+ break;
2196
+ case 'save-and-new':
2197
+ this.tabView()?.buttonNew?.clicked();
2198
+ break;
2199
+ }
2200
+ },
2201
+ error: (e) => {
2202
+ this.finishLoading('warning');
2203
+ form.beginEdit(false);
2204
+ if (e.status === 400) {
2205
+ BackendFormValidationHelper.validateAllFormFields(e, form.dataForm);
2206
+ }
2207
+ else {
2208
+ let errorMessage = 'Modal-Failed-DefaultMessage';
2209
+ if (typeof e.error === 'string') {
2210
+ errorMessage = e.error;
2211
+ }
2212
+ this.errorModal()?.showModal(errorMessage);
2213
+ }
2214
+ },
2215
+ });
2216
+ }
2217
+ //#endregion
2218
+ //#region Public methods
2219
+ finishLoading(status) {
2220
+ this.setTabViewLoading(false);
2221
+ super.finishLoading(status);
2222
+ }
2223
+ //#endregion
2224
+ //#region Private methods
2225
+ setTabViewLoading(isLoading) {
2226
+ const tabView = this.tabView();
2227
+ if (tabView) {
2228
+ tabView.loading = isLoading;
2229
+ }
2230
+ }
2231
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: ButtonSaveLegacyComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
2232
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.7", type: ButtonSaveLegacyComponent, isStandalone: true, selector: "framework-button-save-legacy", inputs: { defaultOption: { classPropertyName: "defaultOption", publicName: "defaultOption", isSignal: true, isRequired: false, transformFunction: null }, form: { classPropertyName: "form", publicName: "form", isSignal: true, isRequired: false, transformFunction: null }, tabView: { classPropertyName: "tabView", publicName: "tabView", isSignal: true, isRequired: false, transformFunction: null } }, providers: [{ provide: RibbonGroupChild, useExisting: forwardRef(() => ButtonSaveLegacyComponent) }], viewQueries: [{ propertyName: "errorModal", first: true, predicate: ErrorModalComponent, descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: "@let legacyForm = form();\r\n\r\n@if (isVisible()) {\r\n <lib-ribbon-button\r\n label=\"Button-Save\"\r\n icon=\"fa-save\"\r\n [defaultOption]=\"defaultOption()\"\r\n [disabled]=\"disabled() || legacyForm?.viewMode !== 'edit' || (legacyForm?.loading ?? false)\"\r\n [loading]=\"loading()\"\r\n [options]=\"options()\"\r\n (action)=\"onButtonClick($event)\"\r\n >\r\n </lib-ribbon-button>\r\n}\r\n\r\n<framework-error-modal></framework-error-modal>\r\n", dependencies: [{ kind: "component", type: ErrorModalComponent, selector: "framework-error-modal", inputs: ["errorMessage", "size", "subtitle", "title"], outputs: ["errorMessageChange", "subtitleChange", "titleChange", "closed"] }, { kind: "component", type: RibbonButtonComponent, selector: "lib-ribbon-button", inputs: ["color", "defaultOption", "disabled", "icon", "iconSize", "label", "loading", "options", "tooltip"], outputs: ["loadingChange", "action"] }] });
2233
+ }
2234
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: ButtonSaveLegacyComponent, decorators: [{
2235
+ type: Component,
2236
+ args: [{ selector: 'framework-button-save-legacy', imports: [ErrorModalComponent, RibbonButtonComponent], providers: [{ provide: RibbonGroupChild, useExisting: forwardRef(() => ButtonSaveLegacyComponent) }], template: "@let legacyForm = form();\r\n\r\n@if (isVisible()) {\r\n <lib-ribbon-button\r\n label=\"Button-Save\"\r\n icon=\"fa-save\"\r\n [defaultOption]=\"defaultOption()\"\r\n [disabled]=\"disabled() || legacyForm?.viewMode !== 'edit' || (legacyForm?.loading ?? false)\"\r\n [loading]=\"loading()\"\r\n [options]=\"options()\"\r\n (action)=\"onButtonClick($event)\"\r\n >\r\n </lib-ribbon-button>\r\n}\r\n\r\n<framework-error-modal></framework-error-modal>\r\n" }]
2237
+ }], ctorParameters: () => [], propDecorators: { errorModal: [{ type: i0.ViewChild, args: [i0.forwardRef(() => ErrorModalComponent), { isSignal: true }] }], defaultOption: [{ type: i0.Input, args: [{ isSignal: true, alias: "defaultOption", required: false }] }], form: [{ type: i0.Input, args: [{ isSignal: true, alias: "form", required: false }] }], tabView: [{ type: i0.Input, args: [{ isSignal: true, alias: "tabView", required: false }] }] } });
2238
+
2239
+ /**
2240
+ * @deprecated Use standalone {@link ButtonViewsComponent} instead.
2241
+ * Migrate by replacing this legacy component with the standalone equivalent
2242
+ * and using inject() for dependency injection.
2243
+ */
2244
+ class ButtonViewsLegacyComponent extends BaseButton {
2245
+ views = input([], /* @ts-ignore */
2246
+ ...(ngDevMode ? [{ debugName: "views" }] : /* istanbul ignore next */ []));
2247
+ viewChanged = output();
2248
+ clicked(option) {
2249
+ this.viewChanged.emit(option);
2250
+ }
2251
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: ButtonViewsLegacyComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
2252
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.1.7", type: ButtonViewsLegacyComponent, isStandalone: true, selector: "framework-button-views-legacy", inputs: { views: { classPropertyName: "views", publicName: "views", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { viewChanged: "viewChanged" }, providers: [{ provide: RibbonGroupChild, useExisting: forwardRef(() => ButtonViewsLegacyComponent) }], usesInheritance: true, ngImport: i0, template: "<lib-ribbon-button\r\n [label]=\"'Button-Views'\"\r\n [icon]=\"'fa-eye'\"\r\n [options]=\"views()\"\r\n (action)=\"clicked($event)\"\r\n></lib-ribbon-button>\r\n", dependencies: [{ kind: "component", type: RibbonButtonComponent, selector: "lib-ribbon-button", inputs: ["color", "defaultOption", "disabled", "icon", "iconSize", "label", "loading", "options", "tooltip"], outputs: ["loadingChange", "action"] }] });
2253
+ }
2254
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: ButtonViewsLegacyComponent, decorators: [{
2255
+ type: Component,
2256
+ args: [{ selector: 'framework-button-views-legacy', imports: [RibbonButtonComponent], providers: [{ provide: RibbonGroupChild, useExisting: forwardRef(() => ButtonViewsLegacyComponent) }], template: "<lib-ribbon-button\r\n [label]=\"'Button-Views'\"\r\n [icon]=\"'fa-eye'\"\r\n [options]=\"views()\"\r\n (action)=\"clicked($event)\"\r\n></lib-ribbon-button>\r\n" }]
2257
+ }], propDecorators: { views: [{ type: i0.Input, args: [{ isSignal: true, alias: "views", required: false }] }], viewChanged: [{ type: i0.Output, args: ["viewChanged"] }] } });
2258
+
2259
+ /**
2260
+ * @deprecated Use standalone component imports instead of FrameworkButtonsModule.
2261
+ * Import individual button components directly in your component's `imports` array.
2262
+ */
2263
+ class FrameworkButtonsModule {
2264
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: FrameworkButtonsModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
2265
+ static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "22.1.7", ngImport: i0, type: FrameworkButtonsModule, imports: [ButtonDeleteComponent,
2266
+ ButtonEditComponent,
2267
+ ButtonExportComponent,
2268
+ ButtonFiltersComponent,
2269
+ ButtonNewComponent,
2270
+ ButtonOpenRecordComponent,
2271
+ ButtonRefreshComponent,
2272
+ ButtonSaveComponent,
2273
+ ButtonViewsComponent,
2274
+ ButtonNewLegacyComponent,
2275
+ ButtonEditLegacyComponent,
2276
+ ButtonSaveLegacyComponent,
2277
+ ButtonViewsLegacyComponent,
2278
+ ButtonOpenRecordLegacyComponent], exports: [ButtonDeleteComponent,
2279
+ ButtonEditComponent,
2280
+ ButtonExportComponent,
2281
+ ButtonFiltersComponent,
2282
+ ButtonNewComponent,
2283
+ ButtonOpenRecordComponent,
2284
+ ButtonRefreshComponent,
2285
+ ButtonSaveComponent,
2286
+ ButtonViewsComponent,
2287
+ ButtonNewLegacyComponent,
2288
+ ButtonEditLegacyComponent,
2289
+ ButtonSaveLegacyComponent,
2290
+ ButtonViewsLegacyComponent,
2291
+ ButtonOpenRecordLegacyComponent] });
2292
+ static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: FrameworkButtonsModule, imports: [ButtonDeleteComponent,
2293
+ ButtonEditComponent,
2294
+ ButtonExportComponent,
2295
+ ButtonFiltersComponent,
2296
+ ButtonNewComponent,
2297
+ ButtonOpenRecordComponent,
2298
+ ButtonRefreshComponent,
2299
+ ButtonSaveComponent,
2300
+ ButtonViewsComponent,
2301
+ ButtonNewLegacyComponent,
2302
+ ButtonEditLegacyComponent,
2303
+ ButtonSaveLegacyComponent,
2304
+ ButtonViewsLegacyComponent,
2305
+ ButtonOpenRecordLegacyComponent] });
2306
+ }
2307
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: FrameworkButtonsModule, decorators: [{
2308
+ type: NgModule,
2309
+ args: [{
2310
+ declarations: [],
2311
+ imports: [
2312
+ ButtonDeleteComponent,
2313
+ ButtonEditComponent,
2314
+ ButtonExportComponent,
2315
+ ButtonFiltersComponent,
2316
+ ButtonNewComponent,
2317
+ ButtonOpenRecordComponent,
2318
+ ButtonRefreshComponent,
2319
+ ButtonSaveComponent,
2320
+ ButtonViewsComponent,
2321
+ ButtonNewLegacyComponent,
2322
+ ButtonEditLegacyComponent,
2323
+ ButtonSaveLegacyComponent,
2324
+ ButtonViewsLegacyComponent,
2325
+ ButtonOpenRecordLegacyComponent,
2326
+ ],
2327
+ exports: [
2328
+ ButtonDeleteComponent,
2329
+ ButtonEditComponent,
2330
+ ButtonExportComponent,
2331
+ ButtonFiltersComponent,
2332
+ ButtonNewComponent,
2333
+ ButtonOpenRecordComponent,
2334
+ ButtonRefreshComponent,
2335
+ ButtonSaveComponent,
2336
+ ButtonViewsComponent,
2337
+ ButtonNewLegacyComponent,
2338
+ ButtonEditLegacyComponent,
2339
+ ButtonSaveLegacyComponent,
2340
+ ButtonViewsLegacyComponent,
2341
+ ButtonOpenRecordLegacyComponent,
2342
+ ],
2343
+ }]
2344
+ }] });
2345
+
2346
+ class DefaultTabViewComponent {
2347
+ //#region ViewChilds, Inputs, Outputs
2348
+ //#endregion
2349
+ //#region Variables
2350
+ destroy$ = new Subject();
2351
+ ribbonTemplate = signal(undefined, /* @ts-ignore */
2352
+ ...(ngDevMode ? [{ debugName: "ribbonTemplate" }] : /* istanbul ignore next */ []));
2353
+ hasNamedAView = false;
2354
+ ribbonViewTemplate = {};
2355
+ //#endregion
2356
+ //#region Properties
2357
+ //#endregion
2358
+ //#region Constructor and Angular life cycle methods
2359
+ tabViewService = inject(TabViewService);
2360
+ ngOnDestroy() {
2361
+ this.destroy$.next(true);
2362
+ this.destroy$.complete();
2363
+ }
2364
+ ngOnInit() {
2365
+ this.tabViewService.onUpdateRibbonTemplate
2366
+ .pipe(takeUntil(this.destroy$))
2367
+ .subscribe((template) => {
2368
+ this.ribbonViewTemplate[this.tabViewService.activeView] = template;
2369
+ this.updateRibbonTemplate(template);
2370
+ });
2371
+ this.tabViewService.onViewChanged.pipe(takeUntil(this.destroy$)).subscribe((viewId) => {
2372
+ this.claimUnnamedRibbon(viewId);
2373
+ this.updateRibbonTemplate(this.ribbonViewTemplate[viewId]);
2374
+ });
2375
+ }
2376
+ //#endregion
2377
+ //#region Event handlers
2378
+ //#endregion
2379
+ //#region Public methods
2380
+ //#endregion
2381
+ //#region Private methods
2382
+ /**
2383
+ * Claims the ribbon published before the active view had a name.
2384
+ *
2385
+ * A child view publishes its ribbon from ngAfterViewInit, and the view is only named afterwards,
2386
+ * so the first template of a tab is always cached under an empty id. For a tab opened straight
2387
+ * at a non-default view that id is never looked up again, the ribbon is emptied, and nothing
2388
+ * refills it -- a tab being re-activated has its child re-attached rather than re-created, so
2389
+ * ngAfterViewInit does not run a second time.
2390
+ *
2391
+ * Only the very first naming may claim it. The empty id is not a spare bucket: it is the default
2392
+ * view's own id, because that view is the one whose URL carries no sub-path. Once the user
2393
+ * starts switching views the unnamed template belongs to the view being *left*, and handing it
2394
+ * to the view being entered showed the previous view's buttons on the new one and left the
2395
+ * previous one with no buttons at all.
2396
+ *
2397
+ * @param viewId The view that has just become active.
2398
+ */
2399
+ claimUnnamedRibbon(viewId) {
2400
+ const isFirstNaming = !this.hasNamedAView;
2401
+ this.hasNamedAView = true;
2402
+ if (!isFirstNaming ||
2403
+ !viewId ||
2404
+ this.ribbonViewTemplate[viewId] !== undefined ||
2405
+ this.ribbonViewTemplate[''] === undefined) {
2406
+ return;
2407
+ }
2408
+ this.ribbonViewTemplate[viewId] = this.ribbonViewTemplate[''];
2409
+ delete this.ribbonViewTemplate[''];
2410
+ }
2411
+ /**
2412
+ * One signal write, where there used to be a detectChanges()/markForCheck() pair.
2413
+ *
2414
+ * Those existed to render a real template at once while deferring a clear, so switching views
2415
+ * did not tear an already rendered ribbon down for a frame before the incoming view pushed its
2416
+ * own. A signal gets both for free: writes coalesce into the next pass, so a clear followed by
2417
+ * a set moments later is one render, not two. The NG0100 hazard goes with it -- a signal write
2418
+ * after the outlet has been checked schedules another pass instead of throwing.
2419
+ */
2420
+ updateRibbonTemplate(template) {
2421
+ this.ribbonTemplate.set(template);
2422
+ }
2423
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: DefaultTabViewComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
2424
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "22.1.7", type: DefaultTabViewComponent, isStandalone: true, selector: "framework-default-tab-view", providers: [{ provide: TabViewService }], ngImport: i0, template: "<lib-ribbon>\r\n <ng-container *ngTemplateOutlet=\"ribbonTemplate() ?? null\"></ng-container>\r\n</lib-ribbon>\r\n\r\n<router-outlet> </router-outlet>\r\n", styles: ["@charset \"UTF-8\";:host{display:flex;flex-grow:1;flex-direction:column;gap:calc(var(--spacing, .25rem) * 2);overflow:hidden}router-outlet{display:none}:host ::ng-deep .framework-view-list{display:flex;flex-grow:1;flex-direction:column;overflow:hidden;min-height:0}:host ::ng-deep .framework-view-list lib-data-grid{flex-grow:1}\n/*! tailwindcss v4.3.3 | MIT License | https://tailwindcss.com */\n"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: RibbonComponent, selector: "lib-ribbon" }, { kind: "ngmodule", type: RouterModule }, { kind: "directive", type: i1$1.RouterOutlet, selector: "router-outlet", inputs: ["name", "routerOutletData"], outputs: ["activate", "deactivate", "attach", "detach"], exportAs: ["outlet"] }] });
2425
+ }
2426
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: DefaultTabViewComponent, decorators: [{
2427
+ type: Component,
2428
+ args: [{ selector: 'framework-default-tab-view', imports: [NgTemplateOutlet, RibbonComponent, RouterModule], providers: [{ provide: TabViewService }], template: "<lib-ribbon>\r\n <ng-container *ngTemplateOutlet=\"ribbonTemplate() ?? null\"></ng-container>\r\n</lib-ribbon>\r\n\r\n<router-outlet> </router-outlet>\r\n", styles: ["@charset \"UTF-8\";:host{display:flex;flex-grow:1;flex-direction:column;gap:calc(var(--spacing, .25rem) * 2);overflow:hidden}router-outlet{display:none}:host ::ng-deep .framework-view-list{display:flex;flex-grow:1;flex-direction:column;overflow:hidden;min-height:0}:host ::ng-deep .framework-view-list lib-data-grid{flex-grow:1}\n/*! tailwindcss v4.3.3 | MIT License | https://tailwindcss.com */\n"] }]
2429
+ }] });
2430
+
2431
+ function dataProviderFactory(route) {
2432
+ return route.snapshot.data['dataProvider']();
2433
+ }
2434
+ class DefaultDetailsTabViewComponent extends DefaultTabViewComponent {
2435
+ //#region ViewChilds, Inputs, Outputs
2436
+ errorModal = viewChild(ErrorModalComponent, /* @ts-ignore */
2437
+ ...(ngDevMode ? [{ debugName: "errorModal" }] : /* istanbul ignore next */ []));
2438
+ //#endregion
2439
+ //#region Variables
2440
+ // Signals rather than fields: the record's title arrives from the data provider's stream,
2441
+ // and the route it is read from is recomputed in that same subscription. Both are rendered
2442
+ // here -- the container heading and the views button -- with nothing else marking this view.
2443
+ containerIcon = signal(undefined, /* @ts-ignore */
2444
+ ...(ngDevMode ? [{ debugName: "containerIcon" }] : /* istanbul ignore next */ []));
2445
+ containerTitle = signal(undefined, /* @ts-ignore */
2446
+ ...(ngDevMode ? [{ debugName: "containerTitle" }] : /* istanbul ignore next */ []));
2447
+ detailsViewRoute = signal(null, /* @ts-ignore */
2448
+ ...(ngDevMode ? [{ debugName: "detailsViewRoute" }] : /* istanbul ignore next */ []));
2449
+ dataProviderClassName;
2450
+ dataProviderService = inject(DataProviderService, { optional: true });
2451
+ hasEntityID = null;
2452
+ hasLoadError = false;
2453
+ router = inject(Router);
2454
+ tabService = inject(TabService);
2455
+ //#endregion
2456
+ //#region Properties
2457
+ get componentName() {
2458
+ return DefaultDetailsTabViewComponent.name;
2459
+ }
2460
+ //#endregion
2461
+ //#region Constructor and Angular life cycle methods
2462
+ ngOnInit() {
2463
+ super.ngOnInit();
2464
+ this.detailsViewRoute.set(RouteHelper.getRouteWithComponent(this.router.routerState.root.snapshot, DefaultDetailsTabViewComponent));
2465
+ this.hasEntityID = this.dataProviderService?.hasEntityID ?? null;
2466
+ this.dataProviderService
2467
+ ?.getModel$()
2468
+ .pipe(takeUntil(this.destroy$))
2469
+ .subscribe((model) => {
2470
+ if (this.hasEntityID !== this.dataProviderService.hasEntityID || !this.detailsViewRoute()) {
2471
+ // If the entity ID changes, we need update the detailsViewRoute to get the correct route URL later.
2472
+ this.hasEntityID = this.dataProviderService.hasEntityID;
2473
+ this.detailsViewRoute.set(RouteHelper.getRouteWithComponent(this.router.routerState.root.snapshot, DefaultDetailsTabViewComponent));
2474
+ }
2475
+ const detailsViewRoute = this.detailsViewRoute();
2476
+ if (detailsViewRoute) {
2477
+ let title = detailsViewRoute.data['defaultTitle'];
2478
+ if (this.dataProviderService.hasEntityID) {
2479
+ title = this.dataProviderService.getTitle(model);
2480
+ }
2481
+ this.containerIcon.set('');
2482
+ this.containerTitle.set(title);
2483
+ const url = RouteHelper.getRouteURL(detailsViewRoute);
2484
+ this.tabService.updateTabTitle(url, title);
2485
+ }
2486
+ });
2487
+ this.dataProviderService
2488
+ ?.getError$()
2489
+ .pipe(takeUntil(this.destroy$))
2490
+ .subscribe((error) => {
2491
+ this.hasLoadError = true;
2492
+ if (error.status === 404) {
2493
+ this.errorModal()?.title.set('Modal-NotFound-Title');
2494
+ this.errorModal()?.subtitle.set('');
2495
+ this.errorModal()?.showModal('Modal-NotFound-Message');
2496
+ }
2497
+ else {
2498
+ this.errorModal()?.title.set('Modal-Failed-Title');
2499
+ this.errorModal()?.subtitle.set('Modal-Failed-Administrator');
2500
+ this.errorModal()?.showModal(error);
2501
+ }
2502
+ });
2503
+ }
2504
+ //#endregion
2505
+ //#region Event handlers
2506
+ onErrorModalClosed() {
2507
+ if (!this.hasLoadError) {
2508
+ return;
2509
+ }
2510
+ const history = this.tabService.activeTabHistory;
2511
+ const entityBaseUrl = history[history.length - 1]?.entityBaseUrl;
2512
+ // Find the entry before the invalid entity in the tab history.
2513
+ if (entityBaseUrl && history.length > 1) {
2514
+ const entityIndex = history.findIndex((entry) => entry.entityBaseUrl === entityBaseUrl);
2515
+ if (entityIndex > 0) {
2516
+ this.tabService.navigateCurrentTabBack(history[entityIndex - 1]);
2517
+ return;
2518
+ }
2519
+ }
2520
+ this.tabService.closeActiveTab();
2521
+ }
2522
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: DefaultDetailsTabViewComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
2523
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "22.1.7", type: DefaultDetailsTabViewComponent, isStandalone: true, selector: "framework-default-details-tab-view", providers: [
2524
+ { provide: TabViewService },
2525
+ { provide: DataProviderService, useFactory: dataProviderFactory, deps: [ActivatedRoute] },
2526
+ // A record's views are siblings: the form is one route, a child list is another, and only the
2527
+ // form provides a FormService of its own. So anything a non-form view renders has to find one
2528
+ // here or not at all -- and framework-button-edit asks for FormService and DataProviderService
2529
+ // together, of which this shell supplied only the second. Opening a child-list view threw
2530
+ // NG0201 before the component rendered at all.
2531
+ //
2532
+ // A form view's own provider shadows this one inside its subtree, so nothing changes there.
2533
+ // What reaches this instance is a view that has no form of its own, where the service sits
2534
+ // idle: the edit button reads `loading` and listens for `editCanceled`, and in modal mode --
2535
+ // which is why a child list has that button -- it never begins an edit on it.
2536
+ { provide: FormService },
2537
+ ], viewQueries: [{ propertyName: "errorModal", first: true, predicate: ErrorModalComponent, descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: "<lib-ribbon>\r\n <ng-container *ngTemplateOutlet=\"ribbonTemplate() ?? null\"></ng-container>\r\n\r\n <div right>\r\n <lib-ribbon-group [label]=\"'RibbonGroup-Page' | translate\">\r\n <framework-button-views [detailsViewRoute]=\"detailsViewRoute()\"> </framework-button-views>\r\n </lib-ribbon-group>\r\n </div>\r\n</lib-ribbon>\r\n\r\n<lib-group-container [icon]=\"containerIcon()\" [title]=\"containerTitle()\">\r\n <router-outlet> </router-outlet>\r\n</lib-group-container>\r\n\r\n<framework-error-modal (closed)=\"onErrorModalClosed()\"></framework-error-modal>\r\n", styles: [":host{display:flex;flex-grow:1;flex-direction:column;gap:calc(var(--spacing, .25rem) * 2);overflow:hidden}router-outlet{display:none}\n/*! tailwindcss v4.3.3 | MIT License | https://tailwindcss.com */\n"], dependencies: [{ kind: "component", type: ButtonViewsComponent, selector: "framework-button-views", inputs: ["detailsViewRoute"] }, { kind: "component", type: ErrorModalComponent, selector: "framework-error-modal", inputs: ["errorMessage", "size", "subtitle", "title"], outputs: ["errorMessageChange", "subtitleChange", "titleChange", "closed"] }, { kind: "component", type: GroupContainerComponent, selector: "lib-group-container", inputs: ["title", "icon"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: RibbonComponent, selector: "lib-ribbon" }, { kind: "component", type: RibbonGroupComponent, selector: "lib-ribbon-group", inputs: ["label"] }, { kind: "ngmodule", type: RouterModule }, { kind: "directive", type: i1$1.RouterOutlet, selector: "router-outlet", inputs: ["name", "routerOutletData"], outputs: ["activate", "deactivate", "attach", "detach"], exportAs: ["outlet"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }] });
2538
+ }
2539
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: DefaultDetailsTabViewComponent, decorators: [{
2540
+ type: Component,
2541
+ args: [{ selector: 'framework-default-details-tab-view', imports: [
2542
+ ButtonViewsComponent,
2543
+ ErrorModalComponent,
2544
+ GroupContainerComponent,
2545
+ NgTemplateOutlet,
2546
+ RibbonComponent,
2547
+ RibbonGroupComponent,
2548
+ RouterModule,
2549
+ TranslatePipe,
2550
+ ], providers: [
2551
+ { provide: TabViewService },
2552
+ { provide: DataProviderService, useFactory: dataProviderFactory, deps: [ActivatedRoute] },
2553
+ // A record's views are siblings: the form is one route, a child list is another, and only the
2554
+ // form provides a FormService of its own. So anything a non-form view renders has to find one
2555
+ // here or not at all -- and framework-button-edit asks for FormService and DataProviderService
2556
+ // together, of which this shell supplied only the second. Opening a child-list view threw
2557
+ // NG0201 before the component rendered at all.
2558
+ //
2559
+ // A form view's own provider shadows this one inside its subtree, so nothing changes there.
2560
+ // What reaches this instance is a view that has no form of its own, where the service sits
2561
+ // idle: the edit button reads `loading` and listens for `editCanceled`, and in modal mode --
2562
+ // which is why a child list has that button -- it never begins an edit on it.
2563
+ { provide: FormService },
2564
+ ], template: "<lib-ribbon>\r\n <ng-container *ngTemplateOutlet=\"ribbonTemplate() ?? null\"></ng-container>\r\n\r\n <div right>\r\n <lib-ribbon-group [label]=\"'RibbonGroup-Page' | translate\">\r\n <framework-button-views [detailsViewRoute]=\"detailsViewRoute()\"> </framework-button-views>\r\n </lib-ribbon-group>\r\n </div>\r\n</lib-ribbon>\r\n\r\n<lib-group-container [icon]=\"containerIcon()\" [title]=\"containerTitle()\">\r\n <router-outlet> </router-outlet>\r\n</lib-group-container>\r\n\r\n<framework-error-modal (closed)=\"onErrorModalClosed()\"></framework-error-modal>\r\n", styles: [":host{display:flex;flex-grow:1;flex-direction:column;gap:calc(var(--spacing, .25rem) * 2);overflow:hidden}router-outlet{display:none}\n/*! tailwindcss v4.3.3 | MIT License | https://tailwindcss.com */\n"] }]
2565
+ }], propDecorators: { errorModal: [{ type: i0.ViewChild, args: [i0.forwardRef(() => ErrorModalComponent), { isSignal: true }] }] } });
2566
+
2567
+ class TabBreadcrumbsComponent {
2568
+ //#region ViewChilds, Inputs, Outputs
2569
+ //#endregion
2570
+ //#region Variables
2571
+ tabService = inject(TabService);
2572
+ //#endregion
2573
+ //#region Properties
2574
+ /**
2575
+ * Reads straight through to the service, which is the trap this component used to be the
2576
+ * example of: a getter over an array a service mutates elsewhere, with nothing to mark this
2577
+ * view dirty. TabService announces its own changes now, and that getter takes the dependency,
2578
+ * so no strategy is needed here.
2579
+ */
2580
+ get tabHistory() {
2581
+ return this.tabService.activeTabHistory;
2582
+ }
2583
+ //#endregion
2584
+ //#region Constructor and Angular life cycle methods
2585
+ //#endregion
2586
+ //#region Event handlers
2587
+ onTitleClick(tab) {
2588
+ this.tabService.navigateCurrentTabBack(tab);
2589
+ }
2590
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: TabBreadcrumbsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
2591
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.7", type: TabBreadcrumbsComponent, isStandalone: true, selector: "framework-tab-breadcrumbs", ngImport: i0, template: "<i class=\"fa-solid fa-angles-right\"></i>\r\n\r\n@for (history of tabHistory; track history) {\r\n <a\r\n role=\"button\"\r\n tabindex=\"0\"\r\n (click)=\"onTitleClick(history)\"\r\n (keydown.enter)=\"onTitleClick(history)\"\r\n (keydown.space)=\"onTitleClick(history); $event.preventDefault()\"\r\n >\r\n @if (!history.isTitleLoading) {\r\n <span>{{ history.title ?? '' | translate }}</span>\r\n }\r\n @if (history.isTitleLoading) {\r\n <svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\">\r\n <circle class=\"opacity-25\" cx=\"12\" cy=\"12\" r=\"10\" stroke=\"currentColor\" stroke-width=\"4\"></circle>\r\n <path\r\n class=\"opacity-75\"\r\n fill=\"currentColor\"\r\n d=\"M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z\"\r\n ></path>\r\n </svg>\r\n }\r\n </a>\r\n}\r\n", styles: ["@charset \"UTF-8\";@layer properties;:host{display:flex;flex-wrap:wrap;align-items:center;gap:var(--spacing, .25rem);-webkit-user-select:none;user-select:none;font-size:var(--text-sm, .875rem);line-height:var(--tw-leading, var(--text-sm--line-height, calc(1.25 / .875)));--tw-font-weight: var(--font-weight-semibold, 600);font-weight:var(--font-weight-semibold, 600);border-bottom:1px solid var(--tabs-border);color:var(--tabs-item-text)}:host>i{flex-shrink:0;font-size:var(--text-xs, .75rem);line-height:var(--tw-leading, var(--text-xs--line-height, calc(1 / .75)));color:var(--tabs-close)}:host a{display:flex;cursor:pointer;align-items:center;gap:var(--spacing, .25rem);border-radius:var(--radius-sm, .25rem);padding-inline:var(--spacing, .25rem);transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease, var(--default-transition-timing-function, cubic-bezier(.4, 0, .2, 1)));transition-duration:var(--tw-duration, var(--default-transition-duration, .15s));--tw-ease: var(--ease-in-out, cubic-bezier(.4, 0, .2, 1));transition-timing-function:var(--ease-in-out, cubic-bezier(.4, 0, .2, 1))}:host a:hover,:host a:focus-visible{background-color:var(--tabs-item-hover-bg);color:var(--tabs-item-active-text)}:host a:focus-visible{--tw-ring-shadow: var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color, currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-outline-style: none;outline-style:none;--tw-ring-color: var(--tabs-item-active-text)}@media(forced-colors:active){:host a:focus-visible{outline:2px solid transparent;outline-offset:2px}}:host a:not(:first-of-type):before{margin-right:var(--spacing, .25rem);flex-shrink:0;--tw-content: \"\";content:var(--tw-content);width:0;height:0;border-top:.25rem solid transparent;border-bottom:.25rem solid transparent;border-left:.3rem solid currentColor;opacity:.45}:host a:last-of-type{color:var(--tabs-item-active-text)}:host a svg{display:inline-block;height:calc(var(--spacing, .25rem) * 4);width:calc(var(--spacing, .25rem) * 4);animation:var(--animate-spin, spin 1s linear infinite)}@property --tw-font-weight{syntax: \"*\"; inherits: false;}@property --tw-ease{syntax: \"*\"; inherits: false;}@property --tw-shadow{syntax: \"*\"; inherits: false; initial-value: 0 0 #0000;}@property --tw-shadow-color{syntax: \"*\"; inherits: false;}@property --tw-shadow-alpha{syntax: \"<percentage>\"; inherits: false; initial-value: 100%;}@property --tw-inset-shadow{syntax: \"*\"; inherits: false; initial-value: 0 0 #0000;}@property --tw-inset-shadow-color{syntax: \"*\"; inherits: false;}@property --tw-inset-shadow-alpha{syntax: \"<percentage>\"; inherits: false; initial-value: 100%;}@property --tw-ring-color{syntax: \"*\"; inherits: false;}@property --tw-ring-shadow{syntax: \"*\"; inherits: false; initial-value: 0 0 #0000;}@property --tw-inset-ring-color{syntax: \"*\"; inherits: false;}@property --tw-inset-ring-shadow{syntax: \"*\"; inherits: false; initial-value: 0 0 #0000;}@property --tw-ring-inset{syntax: \"*\"; inherits: false;}@property --tw-ring-offset-width{syntax: \"<length>\"; inherits: false; initial-value: 0px;}@property --tw-ring-offset-color{syntax: \"*\"; inherits: false; initial-value: #fff;}@property --tw-ring-offset-shadow{syntax: \"*\"; inherits: false; initial-value: 0 0 #0000;}@property --tw-content{syntax: \"*\"; inherits: false; initial-value: \"\";}@keyframes spin{to{transform:rotate(360deg)}}@layer properties{@supports ((-webkit-hyphens: none) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-font-weight: initial;--tw-ease: initial;--tw-shadow: 0 0 #0000;--tw-shadow-color: initial;--tw-shadow-alpha: 100%;--tw-inset-shadow: 0 0 #0000;--tw-inset-shadow-color: initial;--tw-inset-shadow-alpha: 100%;--tw-ring-color: initial;--tw-ring-shadow: 0 0 #0000;--tw-inset-ring-color: initial;--tw-inset-ring-shadow: 0 0 #0000;--tw-ring-inset: initial;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-offset-shadow: 0 0 #0000;--tw-content: \"\"}}}\n/*! tailwindcss v4.3.3 | MIT License | https://tailwindcss.com */\n"], dependencies: [{ kind: "pipe", type: TranslatePipe, name: "translate" }] });
2592
+ }
2593
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: TabBreadcrumbsComponent, decorators: [{
2594
+ type: Component,
2595
+ args: [{ selector: 'framework-tab-breadcrumbs', imports: [TranslatePipe], template: "<i class=\"fa-solid fa-angles-right\"></i>\r\n\r\n@for (history of tabHistory; track history) {\r\n <a\r\n role=\"button\"\r\n tabindex=\"0\"\r\n (click)=\"onTitleClick(history)\"\r\n (keydown.enter)=\"onTitleClick(history)\"\r\n (keydown.space)=\"onTitleClick(history); $event.preventDefault()\"\r\n >\r\n @if (!history.isTitleLoading) {\r\n <span>{{ history.title ?? '' | translate }}</span>\r\n }\r\n @if (history.isTitleLoading) {\r\n <svg xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\">\r\n <circle class=\"opacity-25\" cx=\"12\" cy=\"12\" r=\"10\" stroke=\"currentColor\" stroke-width=\"4\"></circle>\r\n <path\r\n class=\"opacity-75\"\r\n fill=\"currentColor\"\r\n d=\"M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z\"\r\n ></path>\r\n </svg>\r\n }\r\n </a>\r\n}\r\n", styles: ["@charset \"UTF-8\";@layer properties;:host{display:flex;flex-wrap:wrap;align-items:center;gap:var(--spacing, .25rem);-webkit-user-select:none;user-select:none;font-size:var(--text-sm, .875rem);line-height:var(--tw-leading, var(--text-sm--line-height, calc(1.25 / .875)));--tw-font-weight: var(--font-weight-semibold, 600);font-weight:var(--font-weight-semibold, 600);border-bottom:1px solid var(--tabs-border);color:var(--tabs-item-text)}:host>i{flex-shrink:0;font-size:var(--text-xs, .75rem);line-height:var(--tw-leading, var(--text-xs--line-height, calc(1 / .75)));color:var(--tabs-close)}:host a{display:flex;cursor:pointer;align-items:center;gap:var(--spacing, .25rem);border-radius:var(--radius-sm, .25rem);padding-inline:var(--spacing, .25rem);transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease, var(--default-transition-timing-function, cubic-bezier(.4, 0, .2, 1)));transition-duration:var(--tw-duration, var(--default-transition-duration, .15s));--tw-ease: var(--ease-in-out, cubic-bezier(.4, 0, .2, 1));transition-timing-function:var(--ease-in-out, cubic-bezier(.4, 0, .2, 1))}:host a:hover,:host a:focus-visible{background-color:var(--tabs-item-hover-bg);color:var(--tabs-item-active-text)}:host a:focus-visible{--tw-ring-shadow: var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color, currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-outline-style: none;outline-style:none;--tw-ring-color: var(--tabs-item-active-text)}@media(forced-colors:active){:host a:focus-visible{outline:2px solid transparent;outline-offset:2px}}:host a:not(:first-of-type):before{margin-right:var(--spacing, .25rem);flex-shrink:0;--tw-content: \"\";content:var(--tw-content);width:0;height:0;border-top:.25rem solid transparent;border-bottom:.25rem solid transparent;border-left:.3rem solid currentColor;opacity:.45}:host a:last-of-type{color:var(--tabs-item-active-text)}:host a svg{display:inline-block;height:calc(var(--spacing, .25rem) * 4);width:calc(var(--spacing, .25rem) * 4);animation:var(--animate-spin, spin 1s linear infinite)}@property --tw-font-weight{syntax: \"*\"; inherits: false;}@property --tw-ease{syntax: \"*\"; inherits: false;}@property --tw-shadow{syntax: \"*\"; inherits: false; initial-value: 0 0 #0000;}@property --tw-shadow-color{syntax: \"*\"; inherits: false;}@property --tw-shadow-alpha{syntax: \"<percentage>\"; inherits: false; initial-value: 100%;}@property --tw-inset-shadow{syntax: \"*\"; inherits: false; initial-value: 0 0 #0000;}@property --tw-inset-shadow-color{syntax: \"*\"; inherits: false;}@property --tw-inset-shadow-alpha{syntax: \"<percentage>\"; inherits: false; initial-value: 100%;}@property --tw-ring-color{syntax: \"*\"; inherits: false;}@property --tw-ring-shadow{syntax: \"*\"; inherits: false; initial-value: 0 0 #0000;}@property --tw-inset-ring-color{syntax: \"*\"; inherits: false;}@property --tw-inset-ring-shadow{syntax: \"*\"; inherits: false; initial-value: 0 0 #0000;}@property --tw-ring-inset{syntax: \"*\"; inherits: false;}@property --tw-ring-offset-width{syntax: \"<length>\"; inherits: false; initial-value: 0px;}@property --tw-ring-offset-color{syntax: \"*\"; inherits: false; initial-value: #fff;}@property --tw-ring-offset-shadow{syntax: \"*\"; inherits: false; initial-value: 0 0 #0000;}@property --tw-content{syntax: \"*\"; inherits: false; initial-value: \"\";}@keyframes spin{to{transform:rotate(360deg)}}@layer properties{@supports ((-webkit-hyphens: none) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-font-weight: initial;--tw-ease: initial;--tw-shadow: 0 0 #0000;--tw-shadow-color: initial;--tw-shadow-alpha: 100%;--tw-inset-shadow: 0 0 #0000;--tw-inset-shadow-color: initial;--tw-inset-shadow-alpha: 100%;--tw-ring-color: initial;--tw-ring-shadow: 0 0 #0000;--tw-inset-ring-color: initial;--tw-inset-ring-shadow: 0 0 #0000;--tw-ring-inset: initial;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-offset-shadow: 0 0 #0000;--tw-content: \"\"}}}\n/*! tailwindcss v4.3.3 | MIT License | https://tailwindcss.com */\n"] }]
2596
+ }] });
2597
+
2598
+ /** Sub-pixel slack: scrollLeft is fractional, so an exact comparison never reaches the end. */
2599
+ const SCROLL_EPSILON = 1;
2600
+ /** How much of the visible strip one press of a scroll button travels. */
2601
+ const SCROLL_BUTTON_STEP = 0.8;
2602
+ class TabsComponent {
2603
+ ngZone;
2604
+ router;
2605
+ tabService;
2606
+ //#region ViewChilds, Inputs, Outputs
2607
+ tabsNav;
2608
+ //#endregion
2609
+ //#region Host listeners
2610
+ //#endregion
2611
+ //#region Variables
2612
+ static instances = 0;
2613
+ /** Unique per instance: the ids below are referenced by aria-controls/aria-labelledby. */
2614
+ instanceId = `framework-tabs-${TabsComponent.instances++}`;
2615
+ /**
2616
+ * Fingerprint of everything that can invalidate the strip's scroll position: which tab is
2617
+ * selected, how many there are, and the strip's own geometry. Deliberately does NOT include
2618
+ * scrollLeft -- the user scrolling by hand must not make us yank the strip back.
2619
+ */
2620
+ lastStripSignature = '';
2621
+ navResizeObserver;
2622
+ isDestroyed = false;
2623
+ /** Drive the scroll buttons. Only ever assigned outside a change-detection pass (see below). */
2624
+ canScrollLeft = signal(false, /* @ts-ignore */
2625
+ ...(ngDevMode ? [{ debugName: "canScrollLeft" }] : /* istanbul ignore next */ []));
2626
+ canScrollRight = signal(false, /* @ts-ignore */
2627
+ ...(ngDevMode ? [{ debugName: "canScrollRight" }] : /* istanbul ignore next */ []));
2628
+ /**
2629
+ * Whether the strip holds more tabs than it has room for. This decides that the buttons exist;
2630
+ * canScrollLeft and canScrollRight only decide whether each one is enabled.
2631
+ *
2632
+ * They used to decide both, so a button vanished the moment you reached the end it pointed at.
2633
+ * The strip then changed width under the pointer, and a click could land after the button had
2634
+ * already unmounted and do nothing — which is what "sometimes it does not move" was.
2635
+ */
2636
+ isOverflowing = signal(false, /* @ts-ignore */
2637
+ ...(ngDevMode ? [{ debugName: "isOverflowing" }] : /* istanbul ignore next */ []));
2638
+ /**
2639
+ * Whether anything reaches the strip's right edge: a tab, or the right scroll button. While
2640
+ * something does, the panel's top-right corner is square, because the card's corner then belongs
2641
+ * to the strip. Rounding both leaves whatever sits at the edge standing over the sweep where the
2642
+ * panel has curved away and is not there yet — a piece floating past the corner.
2643
+ */
2644
+ isFlushRight = signal(false, /* @ts-ignore */
2645
+ ...(ngDevMode ? [{ debugName: "isFlushRight" }] : /* istanbul ignore next */ []));
2646
+ //#endregion
2647
+ //#region Properties
2648
+ get activeHistory() {
2649
+ return null; // this.tabService.activeHistory;
2650
+ }
2651
+ get activeTab() {
2652
+ return null; // this.tabService.activeTab;
2653
+ }
2654
+ get hasTabs() {
2655
+ return this.tabService.activeTabs.length > 0;
2656
+ }
2657
+ get openTabs() {
2658
+ return this.tabService.activeTabs ?? [];
2659
+ }
2660
+ get tabDisplayTitles() {
2661
+ return this.tabService.activeTabsDisplayTitles;
2662
+ }
2663
+ get tabLoadingStates() {
2664
+ return this.tabService.activeTabsLoadingStates;
2665
+ }
2666
+ /** Id of the panel these tabs control; referenced by each tab's aria-controls. */
2667
+ get panelId() {
2668
+ return `${this.instanceId}-panel`;
2669
+ }
2670
+ /** Id of the selected tab, so the panel can name the tab that labels it. */
2671
+ get activeTabId() {
2672
+ const index = this.openTabs.findIndex((tab) => this.isTabActive(tab));
2673
+ return index === -1 ? null : this.tabId(index);
2674
+ }
2675
+ trackByFn = (_, tab) => tab.url;
2676
+ //#endregion
2677
+ //#region Constructor and Angular life cycle methods
2678
+ constructor(ngZone, router, tabService) {
2679
+ this.ngZone = ngZone;
2680
+ this.router = router;
2681
+ this.tabService = tabService;
2682
+ // Replaces ngAfterViewChecked + queueMicrotask. That pair only ever ran because zone.js
2683
+ // produced an ambient pass on every event; with no zone there is nothing to piggyback on and
2684
+ // the strip silently stopped re-measuring. afterEveryRender is the supported hook for reading
2685
+ // layout, and it runs after the bindings have been checked, so writing the affordance here
2686
+ // cannot raise NG0100 either.
2687
+ afterEveryRender(() => {
2688
+ const signature = this.stripSignature();
2689
+ if (signature === this.lastStripSignature) {
2690
+ return;
2691
+ }
2692
+ this.lastStripSignature = signature;
2693
+ this.remeasureStrip();
2694
+ });
2695
+ }
2696
+ ngAfterViewInit() {
2697
+ const nav = this.tabsNav?.nativeElement;
2698
+ if (!nav) {
2699
+ return;
2700
+ }
2701
+ // Outside Angular: these fire continuously while scrolling or dragging a window edge, and
2702
+ // none of them need a change-detection pass unless a button's state actually flips.
2703
+ this.ngZone.runOutsideAngular(() => {
2704
+ nav.addEventListener('scroll', this.onNavScroll, { passive: true });
2705
+ nav.addEventListener('wheel', this.onNavWheel, { passive: false });
2706
+ // Catches the window resizing and the sidebar collapsing -- both change how many tabs
2707
+ // fit without changing the tab list, so nothing else would tell us to re-check.
2708
+ if (typeof ResizeObserver === 'function') {
2709
+ this.navResizeObserver = new ResizeObserver(() => this.remeasureStrip());
2710
+ this.navResizeObserver.observe(nav);
2711
+ }
2712
+ });
2713
+ }
2714
+ ngOnDestroy() {
2715
+ this.isDestroyed = true;
2716
+ this.navResizeObserver?.disconnect();
2717
+ const nav = this.tabsNav?.nativeElement;
2718
+ nav?.removeEventListener('scroll', this.onNavScroll);
2719
+ nav?.removeEventListener('wheel', this.onNavWheel);
2720
+ }
2721
+ ngOnInit() {
2722
+ const activatedDetailsTabView = RouteHelper.getRouteByData(this.router.routerState.snapshot.root, FRAMEWORK_VIEW_TYPE, FrameworkViewType.Details);
2723
+ if (activatedDetailsTabView) {
2724
+ const shouldIgnoreFirstChildRoute = !!activatedDetailsTabView.firstChild &&
2725
+ activatedDetailsTabView.firstChild.data &&
2726
+ activatedDetailsTabView.firstChild.data['ignoreRoute'] === true;
2727
+ const url = RouteHelper.getRouteURL(activatedDetailsTabView);
2728
+ const clones = [];
2729
+ if (shouldIgnoreFirstChildRoute) {
2730
+ clones.push(RouteHelper.getRouteURL(activatedDetailsTabView.firstChild));
2731
+ }
2732
+ else if (activatedDetailsTabView.firstChild) {
2733
+ const childPath = activatedDetailsTabView.firstChild.url.map((s) => s.path).join('/');
2734
+ const defaultChildPath = activatedDetailsTabView.routeConfig?.children?.filter((route) => !!route.data && route.data['ignoreRoute'] !== true)?.[0]?.path ?? '';
2735
+ if (childPath !== defaultChildPath) {
2736
+ const entityID = Number(activatedDetailsTabView.paramMap.get('id'));
2737
+ if (!entityID) {
2738
+ // New entity: inner views are not available, redirect to base URL.
2739
+ this.router.navigate([url], { replaceUrl: true });
2740
+ }
2741
+ else {
2742
+ // Non-default child route (e.g. direct navigation to /audit): add sub-view to history for breadcrumbs.
2743
+ const childTitle = activatedDetailsTabView.firstChild?.data?.['title'] ?? '';
2744
+ queueMicrotask(() => {
2745
+ const childUrl = RouteHelper.getRouteURL(activatedDetailsTabView.firstChild);
2746
+ this.tabService.replaceCurrentTabSubView(url, new Tab({
2747
+ title: childTitle,
2748
+ url: childUrl,
2749
+ }));
2750
+ });
2751
+ }
2752
+ }
2753
+ }
2754
+ this.tabService.openTab(new Tab({
2755
+ clones: clones,
2756
+ entityBaseUrl: url,
2757
+ queryParams: activatedDetailsTabView.queryParams,
2758
+ url: url,
2759
+ }));
2760
+ }
2761
+ else {
2762
+ const activatedTabView = RouteHelper.getRouteByData(this.router.routerState.snapshot.root, FRAMEWORK_VIEW_TYPE, FrameworkViewType.List);
2763
+ if (activatedTabView) {
2764
+ const url = RouteHelper.getRouteURL(activatedTabView);
2765
+ this.tabService.openTab(new Tab({
2766
+ queryParams: activatedTabView.queryParams,
2767
+ url: url,
2768
+ }));
2769
+ }
2770
+ else {
2771
+ this.router.navigate(['/']);
2772
+ }
2773
+ }
2774
+ }
2775
+ //#endregion
2776
+ //#region Event handlers
2777
+ onActivateTab(tab) {
2778
+ this.tabService.activateTab(tab);
2779
+ }
2780
+ onCloseTab(index) {
2781
+ this.tabService.closeTab(index);
2782
+ }
2783
+ /** Middle click closes a tab, the way it does in a browser. */
2784
+ onTabAuxClick(event, index) {
2785
+ if (event.button !== 1) {
2786
+ return;
2787
+ }
2788
+ // Chromium starts autoscroll on middle click unless the default is prevented.
2789
+ event.preventDefault();
2790
+ this.onCloseTab(index);
2791
+ }
2792
+ /**
2793
+ * Roving focus. A tablist is a single tab stop: Tab reaches the selected tab, and the
2794
+ * arrow keys move between tabs from there. A delta of 0 means go to `from` exactly,
2795
+ * which is what Home and End want.
2796
+ */
2797
+ onMoveFocus(from, delta) {
2798
+ const count = this.openTabs.length;
2799
+ if (count === 0) {
2800
+ return;
2801
+ }
2802
+ const target = delta === 0 ? Math.min(Math.max(from, 0), count - 1) : (from + delta + count) % count;
2803
+ this.focusTab(target);
2804
+ }
2805
+ /** The scroll buttons at each end of the strip. `direction` is -1 for left, 1 for right. */
2806
+ onScrollStrip(direction) {
2807
+ const nav = this.tabsNav?.nativeElement;
2808
+ if (!nav) {
2809
+ return;
2810
+ }
2811
+ const step = nav.clientWidth * SCROLL_BUTTON_STEP * direction;
2812
+ if (typeof nav.scrollBy === 'function') {
2813
+ nav.scrollBy({ left: step, behavior: 'smooth' });
2814
+ }
2815
+ else {
2816
+ // jsdom, and any browser without smooth scrolling, still gets the movement.
2817
+ nav.scrollLeft += step;
2818
+ }
2819
+ }
2820
+ //#endregion
2821
+ //#region Public methods
2822
+ isTabActive(tab) {
2823
+ return this.tabService.isTabActive(tab);
2824
+ }
2825
+ //#endregion
2826
+ //#region Private methods
2827
+ /** Stable per index, so aria-controls and aria-labelledby have something to point at. */
2828
+ tabId(index) {
2829
+ return `${this.instanceId}-tab-${index}`;
2830
+ }
2831
+ tabElements() {
2832
+ return Array.from(this.tabsNav?.nativeElement.querySelectorAll('[role="tab"]') ?? []);
2833
+ }
2834
+ /** Moves focus AND selection: this tablist follows the automatic-activation pattern. */
2835
+ focusTab(index) {
2836
+ const element = this.tabElements()[index];
2837
+ if (!element) {
2838
+ return;
2839
+ }
2840
+ element.focus();
2841
+ const tab = this.openTabs[index];
2842
+ if (tab) {
2843
+ this.onActivateTab(tab);
2844
+ }
2845
+ }
2846
+ /**
2847
+ * Everything that invalidates the scroll position, in one cheap string. The geometry is part
2848
+ * of it because the window resizing or the sidebar collapsing can push the selected tab out
2849
+ * of sight without the tab list changing at all -- which is precisely how you end up looking
2850
+ * at a panel whose tab is nowhere on screen.
2851
+ */
2852
+ stripSignature() {
2853
+ const nav = this.tabsNav?.nativeElement;
2854
+ const index = this.openTabs.findIndex((tab) => this.isTabActive(tab));
2855
+ return `${index}|${this.openTabs.length}|${nav?.clientWidth ?? 0}|${nav?.scrollWidth ?? 0}`;
2856
+ }
2857
+ /** Keeps the selected tab on screen. Called from a microtask, never mid-change-detection. */
2858
+ scrollActiveTabIntoView() {
2859
+ const index = this.openTabs.findIndex((tab) => this.isTabActive(tab));
2860
+ if (index === -1) {
2861
+ return;
2862
+ }
2863
+ // Guarded: scrollIntoView is not implemented in jsdom, so an unguarded call throws in
2864
+ // any consumer's unit tests.
2865
+ const element = this.tabElements()[index];
2866
+ if (typeof element?.scrollIntoView === 'function') {
2867
+ element.scrollIntoView({ block: 'nearest', inline: 'nearest' });
2868
+ }
2869
+ }
2870
+ /**
2871
+ * Recomputes whether either scroll button applies. Reads first and only enters the zone when
2872
+ * a button actually flips, so an ordinary scroll costs one layout read and no render.
2873
+ */
2874
+ syncScrollAffordance() {
2875
+ const nav = this.tabsNav?.nativeElement;
2876
+ if (!nav || this.isDestroyed) {
2877
+ return;
2878
+ }
2879
+ const canLeft = nav.scrollLeft > SCROLL_EPSILON;
2880
+ const canRight = nav.scrollLeft + nav.clientWidth < nav.scrollWidth - SCROLL_EPSILON;
2881
+ const overflowing = nav.scrollWidth > nav.clientWidth + SCROLL_EPSILON;
2882
+ // Overflowing means the right button holds the edge. Short of that, the last tab does if it
2883
+ // ends within a pixel of the strip — the case where the tabs happen to fill it exactly.
2884
+ const lastTab = nav.lastElementChild;
2885
+ const flushRight = overflowing ||
2886
+ (lastTab !== null && lastTab.getBoundingClientRect().right >= nav.getBoundingClientRect().right - SCROLL_EPSILON);
2887
+ if (canLeft === this.canScrollLeft() &&
2888
+ canRight === this.canScrollRight() &&
2889
+ overflowing === this.isOverflowing() &&
2890
+ flushRight === this.isFlushRight()) {
2891
+ return;
2892
+ }
2893
+ this.canScrollLeft.set(canLeft);
2894
+ this.canScrollRight.set(canRight);
2895
+ this.isOverflowing.set(overflowing);
2896
+ this.isFlushRight.set(flushRight);
2897
+ }
2898
+ /** Everything a geometry change implies: the selected tab may be off-screen, and the scroll
2899
+ * buttons may have appeared or gone. */
2900
+ remeasureStrip() {
2901
+ this.scrollActiveTabIntoView();
2902
+ this.syncScrollAffordance();
2903
+ }
2904
+ /** Arrow function: registered and removed as a listener, so it must keep its `this`. */
2905
+ onNavScroll = () => {
2906
+ this.syncScrollAffordance();
2907
+ };
2908
+ /**
2909
+ * A tab strip is horizontal but a mouse wheel is vertical, so without this the only way to
2910
+ * reach an off-screen tab with a plain wheel mouse is the buttons. Trackpads and shift+wheel
2911
+ * already produce deltaX, so those are left alone.
2912
+ */
2913
+ onNavWheel = (event) => {
2914
+ const nav = this.tabsNav?.nativeElement;
2915
+ if (!nav || event.deltaX !== 0 || event.deltaY === 0) {
2916
+ return;
2917
+ }
2918
+ if (nav.scrollWidth <= nav.clientWidth) {
2919
+ return;
2920
+ }
2921
+ event.preventDefault();
2922
+ nav.scrollLeft += event.deltaY;
2923
+ };
2924
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: TabsComponent, deps: [{ token: i0.NgZone }, { token: i1$1.Router }, { token: TabService }], target: i0.ɵɵFactoryTarget.Component });
2925
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.7", type: TabsComponent, isStandalone: true, selector: "framework-tabs", viewQueries: [{ propertyName: "tabsNav", first: true, predicate: ["tabsNav"], descendants: true }], ngImport: i0, template: "@if (hasTabs) {\r\n <div class=\"tabs-container\" [class.can-scroll-left]=\"canScrollLeft()\" [class.can-scroll-right]=\"canScrollRight()\">\r\n <!-- Both buttons are present whenever the strip overflows, and disabled rather than removed at\r\n the end they point at. Rendering them per-direction meant the one being reached for\r\n disappeared on arrival: the strip changed width under the pointer and the click landed on\r\n nothing. tabindex -1 because the tablist is already one tab stop and the arrow keys reach\r\n every tab -- these are a pointer affordance, not a second keyboard path. -->\r\n @if (isOverflowing()) {\r\n <button\r\n class=\"tabs-scroll left\"\r\n type=\"button\"\r\n tabindex=\"-1\"\r\n [disabled]=\"!canScrollLeft()\"\r\n [attr.aria-label]=\"'Tabs-ScrollLeft' | translate\"\r\n (click)=\"onScrollStrip(-1)\"\r\n >\r\n <i class=\"fas fa-chevron-left\"></i>\r\n </button>\r\n }\r\n\r\n <!-- Horizontally scrollable. This used to be overflow:hidden, so once the tabs were\r\n wider than the bar the ones past the right edge could not be reached at all. -->\r\n <ul #tabsNav class=\"tabs-nav\" role=\"tablist\">\r\n @for (tab of openTabs; track trackByFn(i, tab); let i = $index) {\r\n <li role=\"presentation\" [class.active]=\"isTabActive(tab)\">\r\n <a\r\n class=\"tab-item\"\r\n role=\"tab\"\r\n [id]=\"tabId(i)\"\r\n [attr.aria-controls]=\"panelId\"\r\n [attr.aria-selected]=\"isTabActive(tab)\"\r\n [attr.title]=\"tabDisplayTitles[i] ?? tab.title ?? null\"\r\n [attr.tabindex]=\"isTabActive(tab) ? 0 : -1\"\r\n (click)=\"onActivateTab(tab)\"\r\n (auxclick)=\"onTabAuxClick($event, i)\"\r\n (keydown.enter)=\"onActivateTab(tab)\"\r\n (keydown.space)=\"onActivateTab(tab); $event.preventDefault()\"\r\n (keydown.arrowleft)=\"onMoveFocus(i, -1); $event.preventDefault()\"\r\n (keydown.arrowright)=\"onMoveFocus(i, 1); $event.preventDefault()\"\r\n (keydown.home)=\"onMoveFocus(0, 0); $event.preventDefault()\"\r\n (keydown.end)=\"onMoveFocus(openTabs.length - 1, 0); $event.preventDefault()\"\r\n (keydown.delete)=\"onCloseTab(i); $event.preventDefault()\"\r\n >\r\n <!-- Rendered whether or not the title has resolved, so the tab keeps its width\r\n and the strip does not reflow as each title arrives. -->\r\n <span class=\"label\">{{ tabDisplayTitles[i] ?? tab.title ?? '' | translate }}</span>\r\n\r\n @if (tabLoadingStates[i]) {\r\n <svg class=\"spinner\" xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\">\r\n <circle class=\"opacity-25\" cx=\"12\" cy=\"12\" r=\"10\" stroke=\"currentColor\" stroke-width=\"4\"></circle>\r\n <path\r\n class=\"opacity-75\"\r\n fill=\"currentColor\"\r\n d=\"M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z\"\r\n ></path>\r\n </svg>\r\n }\r\n </a>\r\n\r\n <!-- tabindex -1: the tablist is one tab stop, and the close control is reachable\r\n from the tab itself with Delete rather than by tabbing through every one. -->\r\n <button\r\n class=\"tab-close\"\r\n type=\"button\"\r\n tabindex=\"-1\"\r\n [attr.aria-label]=\"'Tabs-CloseTab' | translate: { title: tabDisplayTitles[i] ?? tab.title ?? '' }\"\r\n (click)=\"onCloseTab(i)\"\r\n >\r\n <i class=\"fas fa-times\"></i>\r\n </button>\r\n </li>\r\n }\r\n </ul>\r\n\r\n @if (isOverflowing()) {\r\n <button\r\n class=\"tabs-scroll right\"\r\n type=\"button\"\r\n tabindex=\"-1\"\r\n [disabled]=\"!canScrollRight()\"\r\n [attr.aria-label]=\"'Tabs-ScrollRight' | translate\"\r\n (click)=\"onScrollStrip(1)\"\r\n >\r\n <i class=\"fas fa-chevron-right\"></i>\r\n </button>\r\n }\r\n </div>\r\n}\r\n@if (hasTabs) {\r\n <div\r\n class=\"tab-content\"\r\n [class.flush-right]=\"isFlushRight()\"\r\n role=\"tabpanel\"\r\n [id]=\"panelId\"\r\n [attr.aria-labelledby]=\"activeTabId\"\r\n >\r\n <framework-tab-breadcrumbs></framework-tab-breadcrumbs>\r\n <ng-content></ng-content>\r\n </div>\r\n}\r\n", styles: ["@charset \"UTF-8\";@layer properties;:host{position:relative;display:block;height:100%;max-height:100%;max-width:100%}.tabs-container{position:absolute;top:0;z-index:1;display:flex;width:100%;max-width:100%;flex-direction:row;align-items:flex-end;height:var(--tabs-height);max-height:var(--tabs-height);--tabs-fade-start: 0px;--tabs-fade-end: 0px}.tabs-container.can-scroll-left{--tabs-fade-start: var(--tabs-fade-width)}.tabs-container.can-scroll-right{--tabs-fade-end: var(--tabs-fade-width)}.tabs-container .tabs-scroll{display:flex;flex-shrink:0;align-items:center;justify-content:center;align-self:stretch;cursor:pointer;--tw-border-style: none;border-style:none;padding:0;font-size:var(--text-xs, .75rem);line-height:var(--tw-leading, var(--text-xs--line-height, calc(1 / .75)));--tw-leading: 1;line-height:1;transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease, var(--default-transition-timing-function, cubic-bezier(.4, 0, .2, 1)));transition-duration:var(--tw-duration, var(--default-transition-duration, .15s));--tw-ease: var(--ease-in-out, cubic-bezier(.4, 0, .2, 1));transition-timing-function:var(--ease-in-out, cubic-bezier(.4, 0, .2, 1));width:var(--tabs-scroll-width);background-color:var(--tabs-item-bg);color:var(--tabs-item-text)}.tabs-container .tabs-scroll.left{border-top-left-radius:var(--tabs-item-radius)}.tabs-container .tabs-scroll.right{border-top-right-radius:var(--tabs-radius)}.tabs-container .tabs-scroll:hover:not(:disabled){background-color:var(--tabs-item-hover-bg);color:var(--tabs-item-active-text)}.tabs-container .tabs-scroll:disabled{cursor:default;opacity:40%}.tabs-container .tabs-nav{display:flex;height:100%;flex-direction:row;align-items:flex-end;min-width:0px;flex:1;overflow-x:auto;overflow-y:hidden;overscroll-behavior-x:contain;border-top-right-radius:var(--tabs-radius);scrollbar-width:none}.tabs-container .tabs-nav::-webkit-scrollbar{display:none}.tabs-container .tabs-nav{mask-image:linear-gradient(to right,transparent 0,black var(--tabs-fade-start),black calc(100% - var(--tabs-fade-end)),transparent 100%);-webkit-mask-image:linear-gradient(to right,transparent 0,black var(--tabs-fade-start),black calc(100% - var(--tabs-fade-end)),transparent 100%)}.tabs-container .tabs-nav li{position:relative;display:flex;flex-shrink:1;flex-grow:0;min-width:var(--tabs-item-min-width)}.tabs-container .tabs-nav li.active{z-index:1}.tabs-container .tabs-nav li .tab-item{display:flex;cursor:pointer;align-items:center;gap:calc(var(--spacing, .25rem) * 2);padding-block:calc(var(--spacing, .25rem) * 2);padding-right:calc(var(--spacing, .25rem) * 10);padding-left:calc(var(--spacing, .25rem) * 4);-webkit-user-select:none;user-select:none;overflow:hidden;font-size:var(--text-base, 1rem);line-height:var(--tw-leading, var(--text-base--line-height, 1.5 ));--tw-font-weight: var(--font-weight-medium, 500);font-weight:var(--font-weight-medium, 500);transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease, var(--default-transition-timing-function, cubic-bezier(.4, 0, .2, 1)));transition-duration:var(--tw-duration, var(--default-transition-duration, .15s));--tw-ease: var(--ease-in-out, cubic-bezier(.4, 0, .2, 1));transition-timing-function:var(--ease-in-out, cubic-bezier(.4, 0, .2, 1));min-width:0px;flex:1;max-width:var(--tabs-item-max-width);background-color:var(--tabs-item-bg);color:var(--tabs-item-text);border-top-left-radius:var(--tabs-item-radius);border-top-right-radius:var(--tabs-item-radius)}.tabs-container .tabs-nav li .tab-item .label{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.tabs-container .tabs-nav li .tab-item .spinner{height:calc(var(--spacing, .25rem) * 4);width:calc(var(--spacing, .25rem) * 4);flex-shrink:0;animation:var(--animate-spin, spin 1s linear infinite)}.tabs-container .tabs-nav li .tab-item:focus-visible{--tw-ring-shadow: var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color, currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-outline-style: none;outline-style:none;--tw-ring-inset: inset;--tw-ring-color: var(--tabs-item-active-text)}@media(forced-colors:active){.tabs-container .tabs-nav li .tab-item:focus-visible{outline:2px solid transparent;outline-offset:2px}}.tabs-container .tabs-nav li:hover .tab-item{background-color:var(--tabs-item-hover-bg)}.tabs-container .tabs-nav li.active .tab-item{background-color:var(--tabs-item-active-bg);color:var(--tabs-item-active-text);backdrop-filter:blur(var(--tabs-blur));-webkit-backdrop-filter:blur(var(--tabs-blur))}.tabs-container .tabs-nav li .tab-close{position:absolute;top:50%;right:calc(var(--spacing, .25rem) * 1.5);display:flex;--tw-translate-y: -50% ;translate:var(--tw-translate-x) var(--tw-translate-y);align-items:center;justify-content:center;height:calc(var(--spacing, .25rem) * 6);width:calc(var(--spacing, .25rem) * 6);cursor:pointer;border-radius:calc(infinity * 1px);font-size:var(--text-base, 1rem);line-height:var(--tw-leading, var(--text-base--line-height, 1.5 ));--tw-leading: 1;line-height:1;--tw-border-style: none;border-style:none;background-color:transparent;padding:0;transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease, var(--default-transition-timing-function, cubic-bezier(.4, 0, .2, 1)));transition-duration:var(--tw-duration, var(--default-transition-duration, .15s));--tw-ease: var(--ease-in-out, cubic-bezier(.4, 0, .2, 1));transition-timing-function:var(--ease-in-out, cubic-bezier(.4, 0, .2, 1));color:var(--tabs-close)}.tabs-container .tabs-nav li .tab-close:hover,.tabs-container .tabs-nav li .tab-close:focus-visible{background-color:var(--tabs-close-hover-bg);color:var(--tabs-close-hover)}.tab-content{position:absolute;bottom:0;display:flex;width:100%;max-width:100%;flex-direction:column;gap:calc(var(--spacing, .25rem) * 2);overflow-y:auto;padding:calc(var(--spacing, .25rem) * 2);top:var(--tabs-height)}.tab-content ::ng-deep router-outlet{display:none}.tab-content{border:1px solid var(--tabs-border);border-top:0;border-bottom-left-radius:var(--tabs-radius);border-bottom-right-radius:var(--tabs-radius);border-top-right-radius:var(--tabs-radius);box-shadow:var(--tabs-shadow)}.tab-content.flush-right{border-top-right-radius:0}.tab-content{--surface-radius: calc(var(--tabs-radius) - .5rem - 1px);isolation:isolate}.tab-content:before{pointer-events:none;position:absolute;inset:0;z-index:-1;--tw-content: \"\";content:var(--tw-content);background-color:var(--tabs-content-bg);border-radius:inherit;backdrop-filter:blur(var(--tabs-blur));-webkit-backdrop-filter:blur(var(--tabs-blur))}@property --tw-leading{syntax: \"*\"; inherits: false;}@property --tw-ease{syntax: \"*\"; inherits: false;}@property --tw-font-weight{syntax: \"*\"; inherits: false;}@property --tw-shadow{syntax: \"*\"; inherits: false; initial-value: 0 0 #0000;}@property --tw-shadow-color{syntax: \"*\"; inherits: false;}@property --tw-shadow-alpha{syntax: \"<percentage>\"; inherits: false; initial-value: 100%;}@property --tw-inset-shadow{syntax: \"*\"; inherits: false; initial-value: 0 0 #0000;}@property --tw-inset-shadow-color{syntax: \"*\"; inherits: false;}@property --tw-inset-shadow-alpha{syntax: \"<percentage>\"; inherits: false; initial-value: 100%;}@property --tw-ring-color{syntax: \"*\"; inherits: false;}@property --tw-ring-shadow{syntax: \"*\"; inherits: false; initial-value: 0 0 #0000;}@property --tw-inset-ring-color{syntax: \"*\"; inherits: false;}@property --tw-inset-ring-shadow{syntax: \"*\"; inherits: false; initial-value: 0 0 #0000;}@property --tw-ring-inset{syntax: \"*\"; inherits: false;}@property --tw-ring-offset-width{syntax: \"<length>\"; inherits: false; initial-value: 0px;}@property --tw-ring-offset-color{syntax: \"*\"; inherits: false; initial-value: #fff;}@property --tw-ring-offset-shadow{syntax: \"*\"; inherits: false; initial-value: 0 0 #0000;}@property --tw-translate-x{syntax: \"*\"; inherits: false; initial-value: 0;}@property --tw-translate-y{syntax: \"*\"; inherits: false; initial-value: 0;}@property --tw-translate-z{syntax: \"*\"; inherits: false; initial-value: 0;}@property --tw-content{syntax: \"*\"; inherits: false; initial-value: \"\";}@keyframes spin{to{transform:rotate(360deg)}}@layer properties{@supports ((-webkit-hyphens: none) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-leading: initial;--tw-ease: initial;--tw-font-weight: initial;--tw-shadow: 0 0 #0000;--tw-shadow-color: initial;--tw-shadow-alpha: 100%;--tw-inset-shadow: 0 0 #0000;--tw-inset-shadow-color: initial;--tw-inset-shadow-alpha: 100%;--tw-ring-color: initial;--tw-ring-shadow: 0 0 #0000;--tw-inset-ring-color: initial;--tw-inset-ring-shadow: 0 0 #0000;--tw-ring-inset: initial;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-offset-shadow: 0 0 #0000;--tw-translate-x: 0;--tw-translate-y: 0;--tw-translate-z: 0;--tw-content: \"\"}}}\n/*! tailwindcss v4.3.3 | MIT License | https://tailwindcss.com */\n"], dependencies: [{ kind: "component", type: TabBreadcrumbsComponent, selector: "framework-tab-breadcrumbs" }, { kind: "pipe", type: TranslatePipe, name: "translate" }] });
2926
+ }
2927
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: TabsComponent, decorators: [{
2928
+ type: Component,
2929
+ args: [{ selector: 'framework-tabs', imports: [TabBreadcrumbsComponent, TranslatePipe], template: "@if (hasTabs) {\r\n <div class=\"tabs-container\" [class.can-scroll-left]=\"canScrollLeft()\" [class.can-scroll-right]=\"canScrollRight()\">\r\n <!-- Both buttons are present whenever the strip overflows, and disabled rather than removed at\r\n the end they point at. Rendering them per-direction meant the one being reached for\r\n disappeared on arrival: the strip changed width under the pointer and the click landed on\r\n nothing. tabindex -1 because the tablist is already one tab stop and the arrow keys reach\r\n every tab -- these are a pointer affordance, not a second keyboard path. -->\r\n @if (isOverflowing()) {\r\n <button\r\n class=\"tabs-scroll left\"\r\n type=\"button\"\r\n tabindex=\"-1\"\r\n [disabled]=\"!canScrollLeft()\"\r\n [attr.aria-label]=\"'Tabs-ScrollLeft' | translate\"\r\n (click)=\"onScrollStrip(-1)\"\r\n >\r\n <i class=\"fas fa-chevron-left\"></i>\r\n </button>\r\n }\r\n\r\n <!-- Horizontally scrollable. This used to be overflow:hidden, so once the tabs were\r\n wider than the bar the ones past the right edge could not be reached at all. -->\r\n <ul #tabsNav class=\"tabs-nav\" role=\"tablist\">\r\n @for (tab of openTabs; track trackByFn(i, tab); let i = $index) {\r\n <li role=\"presentation\" [class.active]=\"isTabActive(tab)\">\r\n <a\r\n class=\"tab-item\"\r\n role=\"tab\"\r\n [id]=\"tabId(i)\"\r\n [attr.aria-controls]=\"panelId\"\r\n [attr.aria-selected]=\"isTabActive(tab)\"\r\n [attr.title]=\"tabDisplayTitles[i] ?? tab.title ?? null\"\r\n [attr.tabindex]=\"isTabActive(tab) ? 0 : -1\"\r\n (click)=\"onActivateTab(tab)\"\r\n (auxclick)=\"onTabAuxClick($event, i)\"\r\n (keydown.enter)=\"onActivateTab(tab)\"\r\n (keydown.space)=\"onActivateTab(tab); $event.preventDefault()\"\r\n (keydown.arrowleft)=\"onMoveFocus(i, -1); $event.preventDefault()\"\r\n (keydown.arrowright)=\"onMoveFocus(i, 1); $event.preventDefault()\"\r\n (keydown.home)=\"onMoveFocus(0, 0); $event.preventDefault()\"\r\n (keydown.end)=\"onMoveFocus(openTabs.length - 1, 0); $event.preventDefault()\"\r\n (keydown.delete)=\"onCloseTab(i); $event.preventDefault()\"\r\n >\r\n <!-- Rendered whether or not the title has resolved, so the tab keeps its width\r\n and the strip does not reflow as each title arrives. -->\r\n <span class=\"label\">{{ tabDisplayTitles[i] ?? tab.title ?? '' | translate }}</span>\r\n\r\n @if (tabLoadingStates[i]) {\r\n <svg class=\"spinner\" xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\">\r\n <circle class=\"opacity-25\" cx=\"12\" cy=\"12\" r=\"10\" stroke=\"currentColor\" stroke-width=\"4\"></circle>\r\n <path\r\n class=\"opacity-75\"\r\n fill=\"currentColor\"\r\n d=\"M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z\"\r\n ></path>\r\n </svg>\r\n }\r\n </a>\r\n\r\n <!-- tabindex -1: the tablist is one tab stop, and the close control is reachable\r\n from the tab itself with Delete rather than by tabbing through every one. -->\r\n <button\r\n class=\"tab-close\"\r\n type=\"button\"\r\n tabindex=\"-1\"\r\n [attr.aria-label]=\"'Tabs-CloseTab' | translate: { title: tabDisplayTitles[i] ?? tab.title ?? '' }\"\r\n (click)=\"onCloseTab(i)\"\r\n >\r\n <i class=\"fas fa-times\"></i>\r\n </button>\r\n </li>\r\n }\r\n </ul>\r\n\r\n @if (isOverflowing()) {\r\n <button\r\n class=\"tabs-scroll right\"\r\n type=\"button\"\r\n tabindex=\"-1\"\r\n [disabled]=\"!canScrollRight()\"\r\n [attr.aria-label]=\"'Tabs-ScrollRight' | translate\"\r\n (click)=\"onScrollStrip(1)\"\r\n >\r\n <i class=\"fas fa-chevron-right\"></i>\r\n </button>\r\n }\r\n </div>\r\n}\r\n@if (hasTabs) {\r\n <div\r\n class=\"tab-content\"\r\n [class.flush-right]=\"isFlushRight()\"\r\n role=\"tabpanel\"\r\n [id]=\"panelId\"\r\n [attr.aria-labelledby]=\"activeTabId\"\r\n >\r\n <framework-tab-breadcrumbs></framework-tab-breadcrumbs>\r\n <ng-content></ng-content>\r\n </div>\r\n}\r\n", styles: ["@charset \"UTF-8\";@layer properties;:host{position:relative;display:block;height:100%;max-height:100%;max-width:100%}.tabs-container{position:absolute;top:0;z-index:1;display:flex;width:100%;max-width:100%;flex-direction:row;align-items:flex-end;height:var(--tabs-height);max-height:var(--tabs-height);--tabs-fade-start: 0px;--tabs-fade-end: 0px}.tabs-container.can-scroll-left{--tabs-fade-start: var(--tabs-fade-width)}.tabs-container.can-scroll-right{--tabs-fade-end: var(--tabs-fade-width)}.tabs-container .tabs-scroll{display:flex;flex-shrink:0;align-items:center;justify-content:center;align-self:stretch;cursor:pointer;--tw-border-style: none;border-style:none;padding:0;font-size:var(--text-xs, .75rem);line-height:var(--tw-leading, var(--text-xs--line-height, calc(1 / .75)));--tw-leading: 1;line-height:1;transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease, var(--default-transition-timing-function, cubic-bezier(.4, 0, .2, 1)));transition-duration:var(--tw-duration, var(--default-transition-duration, .15s));--tw-ease: var(--ease-in-out, cubic-bezier(.4, 0, .2, 1));transition-timing-function:var(--ease-in-out, cubic-bezier(.4, 0, .2, 1));width:var(--tabs-scroll-width);background-color:var(--tabs-item-bg);color:var(--tabs-item-text)}.tabs-container .tabs-scroll.left{border-top-left-radius:var(--tabs-item-radius)}.tabs-container .tabs-scroll.right{border-top-right-radius:var(--tabs-radius)}.tabs-container .tabs-scroll:hover:not(:disabled){background-color:var(--tabs-item-hover-bg);color:var(--tabs-item-active-text)}.tabs-container .tabs-scroll:disabled{cursor:default;opacity:40%}.tabs-container .tabs-nav{display:flex;height:100%;flex-direction:row;align-items:flex-end;min-width:0px;flex:1;overflow-x:auto;overflow-y:hidden;overscroll-behavior-x:contain;border-top-right-radius:var(--tabs-radius);scrollbar-width:none}.tabs-container .tabs-nav::-webkit-scrollbar{display:none}.tabs-container .tabs-nav{mask-image:linear-gradient(to right,transparent 0,black var(--tabs-fade-start),black calc(100% - var(--tabs-fade-end)),transparent 100%);-webkit-mask-image:linear-gradient(to right,transparent 0,black var(--tabs-fade-start),black calc(100% - var(--tabs-fade-end)),transparent 100%)}.tabs-container .tabs-nav li{position:relative;display:flex;flex-shrink:1;flex-grow:0;min-width:var(--tabs-item-min-width)}.tabs-container .tabs-nav li.active{z-index:1}.tabs-container .tabs-nav li .tab-item{display:flex;cursor:pointer;align-items:center;gap:calc(var(--spacing, .25rem) * 2);padding-block:calc(var(--spacing, .25rem) * 2);padding-right:calc(var(--spacing, .25rem) * 10);padding-left:calc(var(--spacing, .25rem) * 4);-webkit-user-select:none;user-select:none;overflow:hidden;font-size:var(--text-base, 1rem);line-height:var(--tw-leading, var(--text-base--line-height, 1.5 ));--tw-font-weight: var(--font-weight-medium, 500);font-weight:var(--font-weight-medium, 500);transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease, var(--default-transition-timing-function, cubic-bezier(.4, 0, .2, 1)));transition-duration:var(--tw-duration, var(--default-transition-duration, .15s));--tw-ease: var(--ease-in-out, cubic-bezier(.4, 0, .2, 1));transition-timing-function:var(--ease-in-out, cubic-bezier(.4, 0, .2, 1));min-width:0px;flex:1;max-width:var(--tabs-item-max-width);background-color:var(--tabs-item-bg);color:var(--tabs-item-text);border-top-left-radius:var(--tabs-item-radius);border-top-right-radius:var(--tabs-item-radius)}.tabs-container .tabs-nav li .tab-item .label{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.tabs-container .tabs-nav li .tab-item .spinner{height:calc(var(--spacing, .25rem) * 4);width:calc(var(--spacing, .25rem) * 4);flex-shrink:0;animation:var(--animate-spin, spin 1s linear infinite)}.tabs-container .tabs-nav li .tab-item:focus-visible{--tw-ring-shadow: var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color, currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);--tw-outline-style: none;outline-style:none;--tw-ring-inset: inset;--tw-ring-color: var(--tabs-item-active-text)}@media(forced-colors:active){.tabs-container .tabs-nav li .tab-item:focus-visible{outline:2px solid transparent;outline-offset:2px}}.tabs-container .tabs-nav li:hover .tab-item{background-color:var(--tabs-item-hover-bg)}.tabs-container .tabs-nav li.active .tab-item{background-color:var(--tabs-item-active-bg);color:var(--tabs-item-active-text);backdrop-filter:blur(var(--tabs-blur));-webkit-backdrop-filter:blur(var(--tabs-blur))}.tabs-container .tabs-nav li .tab-close{position:absolute;top:50%;right:calc(var(--spacing, .25rem) * 1.5);display:flex;--tw-translate-y: -50% ;translate:var(--tw-translate-x) var(--tw-translate-y);align-items:center;justify-content:center;height:calc(var(--spacing, .25rem) * 6);width:calc(var(--spacing, .25rem) * 6);cursor:pointer;border-radius:calc(infinity * 1px);font-size:var(--text-base, 1rem);line-height:var(--tw-leading, var(--text-base--line-height, 1.5 ));--tw-leading: 1;line-height:1;--tw-border-style: none;border-style:none;background-color:transparent;padding:0;transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease, var(--default-transition-timing-function, cubic-bezier(.4, 0, .2, 1)));transition-duration:var(--tw-duration, var(--default-transition-duration, .15s));--tw-ease: var(--ease-in-out, cubic-bezier(.4, 0, .2, 1));transition-timing-function:var(--ease-in-out, cubic-bezier(.4, 0, .2, 1));color:var(--tabs-close)}.tabs-container .tabs-nav li .tab-close:hover,.tabs-container .tabs-nav li .tab-close:focus-visible{background-color:var(--tabs-close-hover-bg);color:var(--tabs-close-hover)}.tab-content{position:absolute;bottom:0;display:flex;width:100%;max-width:100%;flex-direction:column;gap:calc(var(--spacing, .25rem) * 2);overflow-y:auto;padding:calc(var(--spacing, .25rem) * 2);top:var(--tabs-height)}.tab-content ::ng-deep router-outlet{display:none}.tab-content{border:1px solid var(--tabs-border);border-top:0;border-bottom-left-radius:var(--tabs-radius);border-bottom-right-radius:var(--tabs-radius);border-top-right-radius:var(--tabs-radius);box-shadow:var(--tabs-shadow)}.tab-content.flush-right{border-top-right-radius:0}.tab-content{--surface-radius: calc(var(--tabs-radius) - .5rem - 1px);isolation:isolate}.tab-content:before{pointer-events:none;position:absolute;inset:0;z-index:-1;--tw-content: \"\";content:var(--tw-content);background-color:var(--tabs-content-bg);border-radius:inherit;backdrop-filter:blur(var(--tabs-blur));-webkit-backdrop-filter:blur(var(--tabs-blur))}@property --tw-leading{syntax: \"*\"; inherits: false;}@property --tw-ease{syntax: \"*\"; inherits: false;}@property --tw-font-weight{syntax: \"*\"; inherits: false;}@property --tw-shadow{syntax: \"*\"; inherits: false; initial-value: 0 0 #0000;}@property --tw-shadow-color{syntax: \"*\"; inherits: false;}@property --tw-shadow-alpha{syntax: \"<percentage>\"; inherits: false; initial-value: 100%;}@property --tw-inset-shadow{syntax: \"*\"; inherits: false; initial-value: 0 0 #0000;}@property --tw-inset-shadow-color{syntax: \"*\"; inherits: false;}@property --tw-inset-shadow-alpha{syntax: \"<percentage>\"; inherits: false; initial-value: 100%;}@property --tw-ring-color{syntax: \"*\"; inherits: false;}@property --tw-ring-shadow{syntax: \"*\"; inherits: false; initial-value: 0 0 #0000;}@property --tw-inset-ring-color{syntax: \"*\"; inherits: false;}@property --tw-inset-ring-shadow{syntax: \"*\"; inherits: false; initial-value: 0 0 #0000;}@property --tw-ring-inset{syntax: \"*\"; inherits: false;}@property --tw-ring-offset-width{syntax: \"<length>\"; inherits: false; initial-value: 0px;}@property --tw-ring-offset-color{syntax: \"*\"; inherits: false; initial-value: #fff;}@property --tw-ring-offset-shadow{syntax: \"*\"; inherits: false; initial-value: 0 0 #0000;}@property --tw-translate-x{syntax: \"*\"; inherits: false; initial-value: 0;}@property --tw-translate-y{syntax: \"*\"; inherits: false; initial-value: 0;}@property --tw-translate-z{syntax: \"*\"; inherits: false; initial-value: 0;}@property --tw-content{syntax: \"*\"; inherits: false; initial-value: \"\";}@keyframes spin{to{transform:rotate(360deg)}}@layer properties{@supports ((-webkit-hyphens: none) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-leading: initial;--tw-ease: initial;--tw-font-weight: initial;--tw-shadow: 0 0 #0000;--tw-shadow-color: initial;--tw-shadow-alpha: 100%;--tw-inset-shadow: 0 0 #0000;--tw-inset-shadow-color: initial;--tw-inset-shadow-alpha: 100%;--tw-ring-color: initial;--tw-ring-shadow: 0 0 #0000;--tw-inset-ring-color: initial;--tw-inset-ring-shadow: 0 0 #0000;--tw-ring-inset: initial;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-offset-shadow: 0 0 #0000;--tw-translate-x: 0;--tw-translate-y: 0;--tw-translate-z: 0;--tw-content: \"\"}}}\n/*! tailwindcss v4.3.3 | MIT License | https://tailwindcss.com */\n"] }]
2930
+ }], ctorParameters: () => [{ type: i0.NgZone }, { type: i1$1.Router }, { type: TabService }], propDecorators: { tabsNav: [{
2931
+ type: ViewChild,
2932
+ args: ['tabsNav']
2933
+ }] } });
2934
+
2935
+ /**
2936
+ * @deprecated Use standalone component imports instead of FrameworkViewsModule.
2937
+ * Import individual components directly in your component's `imports` array.
2938
+ */
2939
+ class FrameworkViewsModule {
2940
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: FrameworkViewsModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
2941
+ static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "22.1.7", ngImport: i0, type: FrameworkViewsModule, imports: [DefaultDetailsTabViewComponent, DefaultTabViewComponent, TabBreadcrumbsComponent, TabsComponent], exports: [DefaultDetailsTabViewComponent, DefaultTabViewComponent, TabsComponent] });
2942
+ static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: FrameworkViewsModule, imports: [DefaultDetailsTabViewComponent, DefaultTabViewComponent] });
2943
+ }
2944
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: FrameworkViewsModule, decorators: [{
2945
+ type: NgModule,
2946
+ args: [{
2947
+ declarations: [],
2948
+ imports: [DefaultDetailsTabViewComponent, DefaultTabViewComponent, TabBreadcrumbsComponent, TabsComponent],
2949
+ exports: [DefaultDetailsTabViewComponent, DefaultTabViewComponent, TabsComponent],
2950
+ }]
2951
+ }] });
2952
+
2953
+ /**
2954
+ * @deprecated Use standalone component imports instead of FrameworkComponentsModule.
2955
+ * Import individual components directly in your component's `imports` array.
2956
+ */
2957
+ class FrameworkComponentsModule {
2958
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: FrameworkComponentsModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
2959
+ static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "22.1.7", ngImport: i0, type: FrameworkComponentsModule, exports: [FrameworkButtonsModule, FrameworkModalsModule, FrameworkViewsModule] });
2960
+ static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: FrameworkComponentsModule, imports: [FrameworkButtonsModule, FrameworkModalsModule, FrameworkViewsModule] });
2961
+ }
2962
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: FrameworkComponentsModule, decorators: [{
2963
+ type: NgModule,
2964
+ args: [{
2965
+ declarations: [],
2966
+ imports: [],
2967
+ exports: [FrameworkButtonsModule, FrameworkModalsModule, FrameworkViewsModule],
2968
+ }]
2969
+ }] });
2970
+
2971
+ const APP_CONFIG = new InjectionToken('Manually constructed AppConfig', {
2972
+ providedIn: 'root',
2973
+ factory: () => new AppConfig(''),
2974
+ });
2975
+ class AppConfig {
2976
+ BASE_URL;
2977
+ /** Application name shown in the top bar brand block. */
2978
+ appName;
2979
+ /** Company name shown beneath the application name. */
2980
+ companyName;
2981
+ /** Environment key (e.g. 'DEV', 'QA', 'STG', 'PROD'). Empty string when unset. */
2982
+ environment;
2983
+ /** Optional brand logo URL shown in the top bar. */
2984
+ logoUrl;
2985
+ /** Whether the top-bar notifications feature (bell + badge) is enabled. */
2986
+ notificationsEnabled;
2987
+ /** SignalR hub URL the notifications service connects to when enabled. */
2988
+ notificationsUrl;
2989
+ /** Application version string (e.g. '1.4.0'), shown at the bottom of the navigation. */
2990
+ version;
2991
+ constructor(baseUrl, options) {
2992
+ this.BASE_URL = baseUrl;
2993
+ this.appName = options?.appName ?? '';
2994
+ this.companyName = options?.companyName ?? '';
2995
+ this.environment = options?.environment ?? '';
2996
+ this.logoUrl = options?.logoUrl;
2997
+ this.notificationsEnabled = options?.notificationsEnabled ?? false;
2998
+ this.notificationsUrl = options?.notificationsUrl ?? '';
2999
+ this.version = options?.version ?? '';
3000
+ }
3001
+ }
3002
+
3003
+ class FrameworkGridConfigsProvider extends DataGridConfigsProvider {
3004
+ //#region ViewChilds, Inputs, Outputs
3005
+ //#endregion
3006
+ //#region Variables
3007
+ //#endregion
3008
+ //#region Properties
3009
+ //#endregion
3010
+ //#region Constructor and Angular life cycle methods
3011
+ constructor() {
3012
+ super();
3013
+ Object.assign(this.configs, {
3014
+ loadingDisplayText: 'Grid-Loading',
3015
+ messageOnEmpty: 'Grid-Message-Empty',
3016
+ messageOnFailed: 'Grid-Message-Failed',
3017
+ messageOnLazyLoad: 'Grid-Message-LazyLoad',
3018
+ });
3019
+ }
3020
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: FrameworkGridConfigsProvider, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
3021
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: FrameworkGridConfigsProvider });
3022
+ }
3023
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: FrameworkGridConfigsProvider, decorators: [{
3024
+ type: Injectable
3025
+ }], ctorParameters: () => [] });
3026
+
3027
+ /**
3028
+ * @deprecated Use standalone component imports instead of FrameworkModule.
3029
+ * Import individual components directly in your component's `imports` array.
3030
+ */
3031
+ class FrameworkModule {
3032
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: FrameworkModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
3033
+ static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "22.1.7", ngImport: i0, type: FrameworkModule, exports: [FrameworkComponentsModule] });
3034
+ static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: FrameworkModule, imports: [FrameworkComponentsModule] });
3035
+ }
3036
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: FrameworkModule, decorators: [{
3037
+ type: NgModule,
3038
+ args: [{
3039
+ declarations: [],
3040
+ imports: [],
3041
+ exports: [FrameworkComponentsModule],
3042
+ }]
3043
+ }] });
3044
+
3045
+ const ZAMBON_FRAMEWORK_I18N_ASSET_PATH = 'assets/i18n/zambon-dev/framework';
3046
+ const ZAMBON_FRAMEWORK_I18N_ASSET = {
3047
+ glob: '**/*.json',
3048
+ input: 'node_modules/@zambon-dev/ui/i18n/framework',
3049
+ output: ZAMBON_FRAMEWORK_I18N_ASSET_PATH,
3050
+ };
3051
+ const ZAMBON_FRAMEWORK_I18N_RESOURCES = [
3052
+ { prefix: `/${ZAMBON_FRAMEWORK_I18N_ASSET_PATH}/`, suffix: '.json' },
3053
+ ];
3054
+ function createZambonFrameworkTranslateLoader(httpBackend, appResources = []) {
3055
+ return new MultiTranslateHttpLoader(httpBackend, [...ZAMBON_FRAMEWORK_I18N_RESOURCES, ...appResources]);
3056
+ }
3057
+ function provideZambonFrameworkTranslateLoader(appResources = []) {
3058
+ return {
3059
+ provide: TranslateLoader,
3060
+ useFactory: (httpBackend) => createZambonFrameworkTranslateLoader(httpBackend, appResources),
3061
+ deps: [HttpBackend],
3062
+ };
3063
+ }
3064
+ function provideZambonFrameworkTranslateService(config = {}) {
3065
+ return provideTranslateService({
3066
+ defaultLanguage: config.defaultLanguage,
3067
+ loader: provideZambonFrameworkTranslateLoader(config.appResources),
3068
+ });
3069
+ }
3070
+
3071
+ // No strategy on purpose: Angular never inherits `changeDetection`, so one declared here would
3072
+ // describe a class that is never instantiated, while every subclass compiles to whatever it
3073
+ // declares -- which in v22 means OnPush. Correctness comes from the state below notifying
3074
+ // Angular, never from this decorator. See docs/guides/reactivity.md.
3075
+ class ViewBase {
3076
+ //#region ViewChilds, Inputs, Outputs
3077
+ //#endregion
3078
+ //#region Variables
3079
+ destroy$ = new Subject();
3080
+ // A signal rather than a field: a view's own template reads `loading` to show its spinner,
3081
+ // and every assignment here happens inside a subscription. The accessors below keep their
3082
+ // old shape, so a subclass reading or writing `this.loading` needs no change; a subclass
3083
+ // template is better off reading `loadingState()` directly.
3084
+ loadingState = signal(false, /* @ts-ignore */
3085
+ ...(ngDevMode ? [{ debugName: "loadingState" }] : /* istanbul ignore next */ []));
3086
+ //#endregion
3087
+ //#region Properties
3088
+ get loading() {
3089
+ return this.loadingState();
3090
+ }
3091
+ set loading(value) {
3092
+ this.loadingState.set(value);
3093
+ }
3094
+ //#endregion
3095
+ //#region Constructor and Angular life cycle methods
3096
+ ngOnDestroy() {
3097
+ this.destroy$.next(true);
3098
+ this.destroy$.complete();
3099
+ }
3100
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: ViewBase, deps: [], target: i0.ɵɵFactoryTarget.Component });
3101
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "22.1.7", type: ViewBase, isStandalone: true, selector: "ng-component", ngImport: i0, template: '', isInline: true });
3102
+ }
3103
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: ViewBase, decorators: [{
3104
+ type: Component,
3105
+ args: [{ template: '' }]
3106
+ }] });
3107
+
3108
+ // No strategy on purpose: Angular never inherits `changeDetection`, so one declared here would
3109
+ // describe a class that is never instantiated, while every subclass compiles to whatever it
3110
+ // declares -- which in v22 means OnPush. Correctness comes from the state below notifying
3111
+ // Angular, never from this decorator. See docs/guides/reactivity.md.
3112
+ class ChildList extends ViewBase {
3113
+ //#region ViewChilds, Inputs, Outputs
3114
+ //#endregion
3115
+ //#region Variables
3116
+ dataGridDataset;
3117
+ dataProvider;
3118
+ resultDataset;
3119
+ selectionCount = 0;
3120
+ //#endregion
3121
+ //#region Properties
3122
+ get isLoading() {
3123
+ return this.loading;
3124
+ }
3125
+ //#endregion
3126
+ //#region Constructor and Angular life cycle methods
3127
+ constructor() {
3128
+ super();
3129
+ this.dataGridDataset = inject(DataGridDataset);
3130
+ this.dataProvider = inject(DataProviderService, { optional: true });
3131
+ this.resultDataset = inject(MultiSelectResultDataset, { optional: true });
3132
+ }
3133
+ ngOnInit() {
3134
+ this.dataGridDataset.loadStarted.pipe(takeUntil(this.destroy$)).subscribe(() => {
3135
+ this.loading = true;
3136
+ });
3137
+ this.dataGridDataset.loadFinished.pipe(takeUntil(this.destroy$)).subscribe(() => {
3138
+ this.loading = false;
3139
+ });
3140
+ this.dataGridDataset.selectedRowsChanged.pipe(takeUntil(this.destroy$)).subscribe(() => {
3141
+ this.selectedRowChanged();
3142
+ });
3143
+ this.resultDataset?.savedChanges?.pipe(takeUntil(this.destroy$))?.subscribe(() => {
3144
+ this.dataGridDataset.refresh();
3145
+ });
3146
+ this.dataProvider
3147
+ ?.getModel$()
3148
+ .pipe(skip(1), takeUntil(this.destroy$))
3149
+ .subscribe(() => {
3150
+ this.dataGridDataset.refresh();
3151
+ });
3152
+ if (this.dataProvider?.hasEntityID) {
3153
+ this.dataGridDataset.parentEntityId = this.dataProvider.entityID;
3154
+ if (!this.dataGridDataset.loadedRows) {
3155
+ this.dataGridDataset.refresh();
3156
+ }
3157
+ if (this.resultDataset) {
3158
+ this.resultDataset.parentEntityId = this.dataProvider.entityID;
3159
+ }
3160
+ }
3161
+ }
3162
+ //#endregion
3163
+ //#region Event handlers
3164
+ //#endregion
3165
+ //#region Public methods
3166
+ //#endregion
3167
+ //#region Private methods
3168
+ selectedRowChanged() {
3169
+ this.selectionCount = this.dataGridDataset.selectedRowKeys.length;
3170
+ }
3171
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: ChildList, deps: [], target: i0.ɵɵFactoryTarget.Component });
3172
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "22.1.7", type: ChildList, isStandalone: true, selector: "ng-component", usesInheritance: true, ngImport: i0, template: '', isInline: true });
3173
+ }
3174
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: ChildList, decorators: [{
3175
+ type: Component,
3176
+ args: [{ template: '' }]
3177
+ }], ctorParameters: () => [] });
3178
+
3179
+ /**
3180
+ * @deprecated Use standalone {@link ChildList} instead. Migrate by extending ChildList
3181
+ * and using standalone component imports with inject() for dependency injection.
3182
+ */
3183
+ // No strategy on purpose: Angular never inherits `changeDetection`, so one declared here would
3184
+ // describe a class that is never instantiated, while every subclass compiles to whatever it
3185
+ // declares -- which in v22 means OnPush. Correctness comes from the state below notifying
3186
+ // Angular, never from this decorator. See docs/guides/reactivity.md.
3187
+ class ChildListLegacy extends ViewBase {
3188
+ _entityId;
3189
+ selectionCount = 0;
3190
+ get entityId() {
3191
+ return this._entityId;
3192
+ }
3193
+ set entityId(value) {
3194
+ if (this._entityId !== value) {
3195
+ this._entityId = value;
3196
+ this.dataGridDataset.parentEntityId = value;
3197
+ if (!this.dataGridDataset.loadedRows) {
3198
+ this.dataGridDataset.refresh();
3199
+ }
3200
+ if (this.resultDataset) {
3201
+ this.resultDataset.parentEntityId = value;
3202
+ }
3203
+ }
3204
+ }
3205
+ disabled = false;
3206
+ dataGridDataset = inject(DataGridDataset);
3207
+ resultDataset = inject(MultiSelectResultDataset, { optional: true });
3208
+ constructor() {
3209
+ super();
3210
+ }
3211
+ ngOnInit() {
3212
+ this.dataGridDataset.loadStarted.pipe(takeUntil(this.destroy$)).subscribe(() => {
3213
+ this.loading = true;
3214
+ });
3215
+ this.dataGridDataset.loadFinished.pipe(takeUntil(this.destroy$)).subscribe(() => {
3216
+ this.loading = false;
3217
+ });
3218
+ if (!this.dataGridDataset.loadedRows) {
3219
+ this.loading = true;
3220
+ }
3221
+ this.dataGridDataset.selectedRowsChanged.pipe(takeUntil(this.destroy$)).subscribe(() => {
3222
+ this.selectedRowChanged();
3223
+ });
3224
+ }
3225
+ selectedRowChanged() {
3226
+ this.selectionCount = this.dataGridDataset.selectedRowKeys.length;
3227
+ }
3228
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: ChildListLegacy, deps: [], target: i0.ɵɵFactoryTarget.Component });
3229
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "22.1.7", type: ChildListLegacy, isStandalone: true, selector: "ng-component", inputs: { entityId: "entityId", disabled: "disabled" }, usesInheritance: true, ngImport: i0, template: '', isInline: true });
3230
+ }
3231
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: ChildListLegacy, decorators: [{
3232
+ type: Component,
3233
+ args: [{ template: '' }]
3234
+ }], ctorParameters: () => [], propDecorators: { entityId: [{
3235
+ type: Input
3236
+ }], disabled: [{
3237
+ type: Input
3238
+ }] } });
3239
+
3240
+ // No strategy on purpose: Angular never inherits `changeDetection`, so one declared here would
3241
+ // describe a class that is never instantiated, while every subclass compiles to whatever it
3242
+ // declares -- which in v22 means OnPush. Correctness comes from the state below notifying
3243
+ // Angular, never from this decorator. See docs/guides/reactivity.md.
3244
+ class TabViewBase extends ViewBase {
3245
+ //#region ViewChilds, Inputs, Outputs
3246
+ ribbonTemplate;
3247
+ //#endregion
3248
+ //#region Variables
3249
+ tabViewService;
3250
+ //#endregion
3251
+ //#region Properties
3252
+ //#endregion
3253
+ //#region Constructor and Angular life cycle methods
3254
+ constructor() {
3255
+ super();
3256
+ // This will force the screen to initialize with the buttons disabled.
3257
+ this.loading = true;
3258
+ this.tabViewService = inject(TabViewService, { optional: true });
3259
+ }
3260
+ ngAfterViewInit() {
3261
+ this.tabViewService?.updateRibbonTemplate(this.ribbonTemplate);
3262
+ }
3263
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: TabViewBase, deps: [], target: i0.ɵɵFactoryTarget.Component });
3264
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "22.1.7", type: TabViewBase, isStandalone: true, selector: "ng-component", viewQueries: [{ propertyName: "ribbonTemplate", first: true, predicate: ["ribbon"], descendants: true }], usesInheritance: true, ngImport: i0, template: '', isInline: true });
3265
+ }
3266
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: TabViewBase, decorators: [{
3267
+ type: Component,
3268
+ args: [{ template: '' }]
3269
+ }], ctorParameters: () => [], propDecorators: { ribbonTemplate: [{
3270
+ type: ViewChild,
3271
+ args: ['ribbon']
3272
+ }] } });
3273
+
3274
+ // No strategy on purpose: Angular never inherits `changeDetection`, so one declared here would
3275
+ // describe a class that is never instantiated, while every subclass compiles to whatever it
3276
+ // declares -- which in v22 means OnPush. Correctness comes from the state below notifying
3277
+ // Angular, never from this decorator. See docs/guides/reactivity.md.
3278
+ class FormView extends TabViewBase {
3279
+ //#region ViewChilds, Inputs, Outputs
3280
+ //#endregion
3281
+ //#region Variables
3282
+ dataForm;
3283
+ dataProvider;
3284
+ formBuilder;
3285
+ formService;
3286
+ //#endregion
3287
+ //#region Properties
3288
+ get hasEntityID() {
3289
+ return this.dataProvider?.hasEntityID ?? false;
3290
+ }
3291
+ get isLoading() {
3292
+ return this.formService.loading;
3293
+ }
3294
+ //#endregion
3295
+ //#region Constructor and Angular life cycle methods
3296
+ constructor() {
3297
+ super();
3298
+ this.dataProvider = inject(DataProviderService, { optional: true });
3299
+ this.formBuilder = inject(FormBuilder);
3300
+ this.formService = inject(FormService);
3301
+ }
3302
+ ngOnInit() {
3303
+ this.dataForm = this.formSetup();
3304
+ this.formService.initializeForm(this.dataForm);
3305
+ this.formService.loading = true;
3306
+ this.dataProvider
3307
+ ?.getModel$()
3308
+ .pipe(takeUntil(this.destroy$))
3309
+ .subscribe((model) => {
3310
+ this.formService.loading = false;
3311
+ this.formService.model = model;
3312
+ this.formService.resetForm();
3313
+ if (!this.dataProvider?.hasEntityID) {
3314
+ this.formService.beginEdit();
3315
+ }
3316
+ });
3317
+ }
3318
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: FormView, deps: [], target: i0.ɵɵFactoryTarget.Component });
3319
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "22.1.7", type: FormView, isStandalone: true, selector: "ng-component", usesInheritance: true, ngImport: i0, template: '', isInline: true });
3320
+ }
3321
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: FormView, decorators: [{
3322
+ type: Component,
3323
+ args: [{ template: '' }]
3324
+ }], ctorParameters: () => [] });
3325
+
3326
+ /**
3327
+ * @deprecated Use standalone {@link FormView} instead. Migrate by extending FormView
3328
+ * and using standalone component imports with inject() for dependency injection.
3329
+ */
3330
+ // No strategy on purpose: Angular never inherits `changeDetection`, so one declared here would
3331
+ // describe a class that is never instantiated, while every subclass compiles to whatever it
3332
+ // declares -- which in v22 means OnPush. Correctness comes from the state below notifying
3333
+ // Angular, never from this decorator. See docs/guides/reactivity.md.
3334
+ class LegacySubViewForm extends ViewBase {
3335
+ formBuilder;
3336
+ formService;
3337
+ entityId;
3338
+ set model(value) {
3339
+ if (this._model !== value) {
3340
+ this._model = value;
3341
+ if (this.dataForm) {
3342
+ this.dataForm.patchValue(this.model);
3343
+ }
3344
+ }
3345
+ }
3346
+ get loading() {
3347
+ return false;
3348
+ return this.formService.loading;
3349
+ }
3350
+ set loading(value) {
3351
+ this.formService.loading = value;
3352
+ }
3353
+ get isNew() {
3354
+ return !this.entityId;
3355
+ }
3356
+ // A signal rather than a field: the legacy edit and save buttons decide what to render from
3357
+ // `form.viewMode`, and this flips inside beginEdit/cancelEdit with nothing marking their views
3358
+ // dirty. The accessors keep the old shape.
3359
+ get viewMode() {
3360
+ return this._viewMode();
3361
+ }
3362
+ set viewMode(value) {
3363
+ this._viewMode.set(value);
3364
+ }
3365
+ get model() {
3366
+ return this._model;
3367
+ }
3368
+ dataForm;
3369
+ _model = null;
3370
+ _viewMode = signal('view', /* @ts-ignore */
3371
+ ...(ngDevMode ? [{ debugName: "_viewMode" }] : /* istanbul ignore next */ []));
3372
+ dataProvider;
3373
+ constructor(formBuilder, formService) {
3374
+ super();
3375
+ this.formBuilder = formBuilder;
3376
+ this.formService = formService;
3377
+ this.dataProvider = inject(DataProviderService, { optional: true });
3378
+ }
3379
+ ngOnInit() {
3380
+ this.dataForm = this.formSetup();
3381
+ this.updateModel(this.model);
3382
+ if (this.dataProvider) {
3383
+ this.formService.loading = true;
3384
+ this.dataProvider
3385
+ .getModel$()
3386
+ .pipe(take(1))
3387
+ .subscribe((_) => {
3388
+ this.formService.loading = false;
3389
+ });
3390
+ }
3391
+ }
3392
+ updateModel(model) {
3393
+ this._model = model;
3394
+ if (this.isNew) {
3395
+ this.beginEdit();
3396
+ }
3397
+ else {
3398
+ this.cancelEdit();
3399
+ }
3400
+ }
3401
+ beginEdit(resetValues = true) {
3402
+ setTimeout(() => {
3403
+ this.viewMode = 'edit';
3404
+ this.dataForm.enable();
3405
+ if (resetValues) {
3406
+ this.dataForm.patchValue(this.model);
3407
+ }
3408
+ });
3409
+ }
3410
+ cancelEdit() {
3411
+ this.viewMode = 'view';
3412
+ this.dataForm.patchValue(this.model);
3413
+ this.dataForm.disable();
3414
+ }
3415
+ /** Extension point: subclasses override this to validate before save. */
3416
+ // eslint-disable-next-line @typescript-eslint/no-empty-function, @typescript-eslint/no-unused-vars
3417
+ validate(_formModel) { }
3418
+ save() {
3419
+ this.validate(this.dataForm.getRawValue());
3420
+ if (this.dataForm.valid) {
3421
+ this.dataForm.disable();
3422
+ return this.saveModel().pipe(tap(() => this.dataForm.enable()));
3423
+ }
3424
+ else {
3425
+ this.dataForm.markAllAsTouched();
3426
+ return throwError(() => new HttpErrorResponse({ error: { message: 'Form invalid', errors: null }, status: 400 }));
3427
+ }
3428
+ }
3429
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: LegacySubViewForm, deps: [{ token: i1$2.FormBuilder }, { token: i1$3.FormService }], target: i0.ɵɵFactoryTarget.Component });
3430
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "22.1.7", type: LegacySubViewForm, isStandalone: true, selector: "ng-component", inputs: { entityId: "entityId", model: "model" }, usesInheritance: true, ngImport: i0, template: '', isInline: true });
3431
+ }
3432
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: LegacySubViewForm, decorators: [{
3433
+ type: Component,
3434
+ args: [{ template: '' }]
3435
+ }], ctorParameters: () => [{ type: i1$2.FormBuilder }, { type: i1$3.FormService }], propDecorators: { entityId: [{
3436
+ type: Input
3437
+ }], model: [{
3438
+ type: Input
3439
+ }] } });
3440
+
3441
+ /**
3442
+ * @deprecated Use standalone {@link TabViewBase} instead. Migrate by extending TabViewBase
3443
+ * and using standalone component imports with inject() for dependency injection.
3444
+ */
3445
+ // No strategy on purpose: Angular never inherits `changeDetection`, so one declared here would
3446
+ // describe a class that is never instantiated, while every subclass compiles to whatever it
3447
+ // declares -- which in v22 means OnPush. Correctness comes from the state below notifying
3448
+ // Angular, never from this decorator. See docs/guides/reactivity.md.
3449
+ class LegacyTabViewBase extends ViewBase {
3450
+ route;
3451
+ tabService;
3452
+ title = '';
3453
+ url;
3454
+ activeView;
3455
+ constructor(route, tabService) {
3456
+ super();
3457
+ this.route = route;
3458
+ this.tabService = tabService;
3459
+ this.url = RouterFormatter.getURL(this.route.snapshot);
3460
+ }
3461
+ ngOnInit() {
3462
+ this.openTabView();
3463
+ }
3464
+ openTabView() {
3465
+ // this.tabService.openView(this);
3466
+ }
3467
+ updateTitle(title) {
3468
+ this.title = title;
3469
+ this.tabService.updateTabTitle(this.url, title);
3470
+ }
3471
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: LegacyTabViewBase, deps: [{ token: i1$1.ActivatedRoute }, { token: TabService }], target: i0.ɵɵFactoryTarget.Component });
3472
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "22.1.7", type: LegacyTabViewBase, isStandalone: true, selector: "ng-component", usesInheritance: true, ngImport: i0, template: '', isInline: true });
3473
+ }
3474
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: LegacyTabViewBase, decorators: [{
3475
+ type: Component,
3476
+ args: [{ template: '' }]
3477
+ }], ctorParameters: () => [{ type: i1$1.ActivatedRoute }, { type: TabService }] });
3478
+
3479
+ /**
3480
+ * @deprecated Use standalone {@link TabViewBase} with {@link DefaultDetailsTabViewComponent} pattern instead.
3481
+ * Migrate by extending TabViewBase and composing detail/history views as child routes
3482
+ * with standalone component imports.
3483
+ */
3484
+ // No strategy on purpose: Angular never inherits `changeDetection`, so one declared here would
3485
+ // describe a class that is never instantiated, while every subclass compiles to whatever it
3486
+ // declares -- which in v22 means OnPush. Correctness comes from the state below notifying
3487
+ // Angular, never from this decorator. See docs/guides/reactivity.md.
3488
+ class LegacyTabViewDetails extends LegacyTabViewBase {
3489
+ formService;
3490
+ //#region ViewChilds, Inputs, Outputs
3491
+ // A forward reference: the legacy buttons import the views barrel, so this class is defined before
3492
+ // ButtonNewLegacyComponent -- and in the published bundle it sits further down the file, where
3493
+ // reading it eagerly throws the moment a JIT runtime such as Jest loads the package.
3494
+ buttonNew;
3495
+ //#endregion
3496
+ //#region Variables
3497
+ title = '';
3498
+ defaultView = 'details';
3499
+ entityId;
3500
+ views = [
3501
+ { id: 'details', label: 'Button-Views-Details', icon: 'fa-dice-d6' },
3502
+ { id: 'history', label: 'Button-Views-History', icon: 'fa-history' },
3503
+ ];
3504
+ //#endregion
3505
+ //#region Properties
3506
+ get loading() {
3507
+ return super.loading;
3508
+ }
3509
+ set loading(value) {
3510
+ super.loading = value;
3511
+ this.formService.loading = value;
3512
+ }
3513
+ get currentView() {
3514
+ return this.activeView ?? this.defaultView;
3515
+ }
3516
+ get model() {
3517
+ return this.formService.model;
3518
+ }
3519
+ //#endregion
3520
+ //#region Constructor and Angular life cycle methods
3521
+ constructor(route, tabService, formService) {
3522
+ super(route, tabService);
3523
+ this.formService = formService;
3524
+ }
3525
+ ngAfterViewInit() {
3526
+ this.onViewVisible();
3527
+ }
3528
+ ngOnInit() {
3529
+ this.refreshParameters();
3530
+ super.ngOnInit();
3531
+ this.formService.modelRefreshed.pipe(takeUntil(this.destroy$)).subscribe((_) => {
3532
+ const id = this.getModelId();
3533
+ if (id && this.entityId !== id) {
3534
+ this.entityId = id;
3535
+ this.tabService.redirectCurrentTab(`${this.url.substring(0, this.url.indexOf('/new'))}/${id}`);
3536
+ }
3537
+ this.updateTitle(this.getModelDescription());
3538
+ });
3539
+ this.refresh();
3540
+ }
3541
+ //#endregion
3542
+ //#region Event handlers
3543
+ //#endregion
3544
+ //#region Public methods
3545
+ openTabView() {
3546
+ super.openTabView();
3547
+ const activeView = this.views.find((view) => view.id === this.activeView);
3548
+ if (activeView) {
3549
+ activeView.isDisabled = true;
3550
+ }
3551
+ }
3552
+ refresh() {
3553
+ this.loading = true;
3554
+ this.title = '';
3555
+ this.formService.model = null;
3556
+ this.loadModel()
3557
+ .pipe(takeUntil(this.destroy$))
3558
+ .subscribe((model) => {
3559
+ this.loading = false;
3560
+ this.formService.model = model;
3561
+ });
3562
+ }
3563
+ refreshParameters() {
3564
+ const currentID = this.route.snapshot.paramMap.get('id');
3565
+ this.entityId = currentID ? Number(currentID) : undefined;
3566
+ const viewID = this.route.snapshot.paramMap.get('view');
3567
+ if (!viewID || !this.views.some((view) => view.id === viewID)) {
3568
+ this.switchView(this.defaultView);
3569
+ }
3570
+ else {
3571
+ this.activeView = viewID;
3572
+ }
3573
+ }
3574
+ switchView(viewId) {
3575
+ if (!viewId) {
3576
+ viewId = this.defaultView;
3577
+ }
3578
+ const activeView = this.views.find((view) => view.id === (this.activeView ?? this.defaultView));
3579
+ if (activeView) {
3580
+ activeView.isDisabled = false;
3581
+ }
3582
+ const newView = this.views.find((view) => view.id === viewId);
3583
+ if (newView) {
3584
+ newView.isDisabled = true;
3585
+ const urlViewPath = viewId === this.defaultView ? '' : `/${viewId}`;
3586
+ let url = this.url;
3587
+ if (this.activeView && url.endsWith(this.activeView)) {
3588
+ url = url.replace(`/${this.activeView}`, urlViewPath);
3589
+ }
3590
+ else {
3591
+ url += urlViewPath;
3592
+ }
3593
+ this.tabService.navigateCurrentTab(new Tab({ url }));
3594
+ // this.tabService.changeTabView(this, viewId !== this.defaultView ? viewId : undefined);
3595
+ this.activeView = newView.id;
3596
+ // if (!this.activeView) {
3597
+ // this.activeView = this.defaultView;
3598
+ // }
3599
+ }
3600
+ this.onViewVisible();
3601
+ }
3602
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: LegacyTabViewDetails, deps: [{ token: i1$1.ActivatedRoute }, { token: TabService }, { token: i1$3.FormService }], target: i0.ɵɵFactoryTarget.Component });
3603
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "22.1.7", type: LegacyTabViewDetails, isStandalone: true, selector: "ng-component", viewQueries: [{ propertyName: "buttonNew", first: true, predicate: i0.forwardRef(() => ButtonNewLegacyComponent), descendants: true }], usesInheritance: true, ngImport: i0, template: '', isInline: true });
3604
+ }
3605
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: LegacyTabViewDetails, decorators: [{
3606
+ type: Component,
3607
+ args: [{ template: '' }]
3608
+ }], ctorParameters: () => [{ type: i1$1.ActivatedRoute }, { type: TabService }, { type: i1$3.FormService }], propDecorators: { buttonNew: [{
3609
+ type: ViewChild,
3610
+ args: [forwardRef(() => ButtonNewLegacyComponent)]
3611
+ }] } });
3612
+
3613
+ /**
3614
+ * @deprecated Use standalone {@link TabViewList} instead. Migrate by extending TabViewList
3615
+ * and using standalone component imports with inject() for dependency injection.
3616
+ */
3617
+ // No strategy on purpose: Angular never inherits `changeDetection`, so one declared here would
3618
+ // describe a class that is never instantiated, while every subclass compiles to whatever it
3619
+ // declares -- which in v22 means OnPush. Correctness comes from the state below notifying
3620
+ // Angular, never from this decorator. See docs/guides/reactivity.md.
3621
+ class LegacyTabViewList extends LegacyTabViewBase {
3622
+ dataGridDataset;
3623
+ //#region ViewChilds, Inputs, Outputs
3624
+ //#endregion
3625
+ //#region Variables
3626
+ _selectedItem;
3627
+ selectedRows = 0;
3628
+ //#endregion
3629
+ //#region Properties
3630
+ get hasRowsSelected() {
3631
+ return this.selectedRows > 0;
3632
+ }
3633
+ get selectedItem() {
3634
+ return this._selectedItem;
3635
+ }
3636
+ //#endregion
3637
+ //#region Constructor and Angular life cycle methods
3638
+ constructor(dataGridDataset, route, tabService) {
3639
+ super(route, tabService);
3640
+ this.dataGridDataset = dataGridDataset;
3641
+ // This will force the screen to initialize with the buttons disabled.
3642
+ this.loading = true;
3643
+ }
3644
+ ngOnInit() {
3645
+ super.ngOnInit();
3646
+ this.dataGridDataset.loadStarted.pipe(takeUntil(this.destroy$)).subscribe(() => {
3647
+ this.loading = true;
3648
+ });
3649
+ this.dataGridDataset.loadFinished.pipe(takeUntil(this.destroy$)).subscribe(() => {
3650
+ setTimeout(() => (this.loading = false));
3651
+ });
3652
+ this.dataGridDataset.selectedRowsChanged.pipe(takeUntil(this.destroy$)).subscribe(() => {
3653
+ this.selectedRowChanged();
3654
+ });
3655
+ }
3656
+ //#endregion
3657
+ //#region Event handlers
3658
+ //#endregion
3659
+ //#region Public methods
3660
+ //#endregion
3661
+ //#region Private methods
3662
+ selectedRowChanged() {
3663
+ this.selectedRows = this.dataGridDataset.selectedRowKeys.length;
3664
+ this._selectedItem = undefined;
3665
+ if (this.selectedRows === 1) {
3666
+ this._selectedItem = this.dataGridDataset.getRowData(this.dataGridDataset.selectedRowKeys[0]);
3667
+ }
3668
+ }
3669
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: LegacyTabViewList, deps: [{ token: i1$3.DataGridDataset }, { token: i1$1.ActivatedRoute }, { token: TabService }], target: i0.ɵɵFactoryTarget.Component });
3670
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "22.1.7", type: LegacyTabViewList, isStandalone: true, selector: "ng-component", usesInheritance: true, ngImport: i0, template: '', isInline: true });
3671
+ }
3672
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: LegacyTabViewList, decorators: [{
3673
+ type: Component,
3674
+ args: [{ template: '' }]
3675
+ }], ctorParameters: () => [{ type: i1$3.DataGridDataset }, { type: i1$1.ActivatedRoute }, { type: TabService }] });
3676
+
3677
+ // The host class is inherited by every subclass (Angular merges hostAttrs from a base component
3678
+ // definition), which is what lets DefaultTabViewComponent give routed list screens a full-height
3679
+ // layout without each one declaring its own stylesheet. Plain class name, NOT Tailwind utilities:
3680
+ // library SCSS resolves @apply at library-build time, whereas a utility class named here would
3681
+ // have to be generated by each consumer's own Tailwind build.
3682
+ //
3683
+ // No strategy on purpose: Angular never inherits `changeDetection`, so one declared here would
3684
+ // describe a class that is never instantiated, while every subclass compiles to whatever it
3685
+ // declares -- which in v22 means OnPush. Correctness comes from the state below notifying
3686
+ // Angular, never from this decorator. See docs/guides/reactivity.md.
3687
+ class TabViewList extends TabViewBase {
3688
+ //#region ViewChilds, Inputs, Outputs
3689
+ //#endregion
3690
+ //#region Variables
3691
+ dataGridDataset;
3692
+ _selectedItem;
3693
+ selectionCount = 0;
3694
+ //#endregion
3695
+ //#region Properties
3696
+ get hasRowsSelected() {
3697
+ return this.selectionCount > 0;
3698
+ }
3699
+ get selectedItem() {
3700
+ return this._selectedItem;
3701
+ }
3702
+ //#endregion
3703
+ //#region Constructor and Angular life cycle methods
3704
+ constructor() {
3705
+ super();
3706
+ this.dataGridDataset = inject(DataGridDataset);
3707
+ }
3708
+ ngOnInit() {
3709
+ this.dataGridDataset.loadStarted.pipe(takeUntil(this.destroy$)).subscribe(() => {
3710
+ this.loading = true;
3711
+ });
3712
+ this.dataGridDataset.loadFinished.pipe(takeUntil(this.destroy$)).subscribe(() => {
3713
+ setTimeout(() => (this.loading = false));
3714
+ });
3715
+ this.dataGridDataset.selectedRowsChanged.pipe(takeUntil(this.destroy$)).subscribe(() => {
3716
+ this.selectedRowChanged();
3717
+ });
3718
+ }
3719
+ //#endregion
3720
+ //#region Event handlers
3721
+ //#endregion
3722
+ //#region Public methods
3723
+ //#endregion
3724
+ //#region Private methods
3725
+ selectedRowChanged() {
3726
+ this.selectionCount = this.dataGridDataset.selectedRowKeys.length;
3727
+ this._selectedItem = undefined;
3728
+ if (this.selectionCount === 1) {
3729
+ this._selectedItem = this.dataGridDataset.getRowData(this.dataGridDataset.selectedRowKeys[0]);
3730
+ }
3731
+ }
3732
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: TabViewList, deps: [], target: i0.ɵɵFactoryTarget.Component });
3733
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "22.1.7", type: TabViewList, isStandalone: true, selector: "ng-component", host: { classAttribute: "framework-view-list" }, usesInheritance: true, ngImport: i0, template: '', isInline: true });
3734
+ }
3735
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: TabViewList, decorators: [{
3736
+ type: Component,
3737
+ args: [{ host: { class: 'framework-view-list' }, template: '' }]
3738
+ }], ctorParameters: () => [] });
3739
+
3740
+ // No strategy on purpose: Angular never inherits `changeDetection`, so one declared here would
3741
+ // describe a class that is never instantiated, while every subclass compiles to whatever it
3742
+ // declares -- which in v22 means OnPush. Correctness comes from the state below notifying
3743
+ // Angular, never from this decorator. See docs/guides/reactivity.md.
3744
+ class ListView extends TabViewList {
3745
+ //#region ViewChilds, Inputs, Outputs
3746
+ //#endregion
3747
+ //#region Variables
3748
+ dataProvider;
3749
+ resultDataset;
3750
+ //#endregion
3751
+ //#region Properties
3752
+ //#endregion
3753
+ //#region Constructor and Angular life cycle methods
3754
+ constructor() {
3755
+ super();
3756
+ this.dataProvider = inject(DataProviderService, { optional: true });
3757
+ this.resultDataset = inject(MultiSelectResultDataset, { optional: true });
3758
+ }
3759
+ ngOnInit() {
3760
+ super.ngOnInit();
3761
+ if (this.dataProvider?.hasEntityID) {
3762
+ this.dataGridDataset.parentEntityId = this.dataProvider.entityID;
3763
+ if (!this.dataGridDataset.loadedRows) {
3764
+ this.dataGridDataset.refresh();
3765
+ }
3766
+ if (this.resultDataset) {
3767
+ this.resultDataset.parentEntityId = this.dataProvider.entityID;
3768
+ }
3769
+ }
3770
+ }
3771
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: ListView, deps: [], target: i0.ɵɵFactoryTarget.Component });
3772
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "22.1.7", type: ListView, isStandalone: true, selector: "ng-component", usesInheritance: true, ngImport: i0, template: '', isInline: true });
3773
+ }
3774
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: ListView, decorators: [{
3775
+ type: Component,
3776
+ args: [{ template: '' }]
3777
+ }], ctorParameters: () => [] });
3778
+
3779
+ class ModalBase extends ViewBase {
3780
+ }
3781
+
3782
+ // No strategy on purpose: Angular never inherits `changeDetection`, so one declared here would
3783
+ // describe a class that is never instantiated, while every subclass compiles to whatever it
3784
+ // declares -- which in v22 means OnPush. Correctness comes from the state below notifying
3785
+ // Angular, never from this decorator. See docs/guides/reactivity.md.
3786
+ class MultiEditorLegacyModal extends ModalBase {
3787
+ dataGridDataset;
3788
+ multiEditorDataset;
3789
+ formBuilder;
3790
+ _entityId;
3791
+ multiEditor;
3792
+ set entityId(value) {
3793
+ if (this._entityId !== value) {
3794
+ this._entityId = value;
3795
+ this.dataGridDataset.parentEntityId = value;
3796
+ this.multiEditorDataset.parentEntityId = value;
3797
+ }
3798
+ }
3799
+ savedChanges = new EventEmitter();
3800
+ form;
3801
+ constructor(dataGridDataset, multiEditorDataset, formBuilder) {
3802
+ super();
3803
+ this.dataGridDataset = dataGridDataset;
3804
+ this.multiEditorDataset = multiEditorDataset;
3805
+ this.formBuilder = formBuilder;
3806
+ }
3807
+ ngOnInit() {
3808
+ this.form = this.formSetup();
3809
+ this.multiEditorDataset.savedChanges.pipe(takeUntil(this.destroy$)).subscribe(() => {
3810
+ this.savedChanges.emit();
3811
+ });
3812
+ }
3813
+ toggle() {
3814
+ this.multiEditor.toggleModal();
3815
+ }
3816
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: MultiEditorLegacyModal, deps: [{ token: i1$3.DataGridDataset }, { token: i1$3.MultiEditorDataset }, { token: i1$2.FormBuilder }], target: i0.ɵɵFactoryTarget.Component });
3817
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "22.1.7", type: MultiEditorLegacyModal, isStandalone: true, selector: "ng-component", inputs: { entityId: "entityId" }, outputs: { savedChanges: "savedChanges" }, viewQueries: [{ propertyName: "multiEditor", first: true, predicate: MultiEditorComponent, descendants: true }], usesInheritance: true, ngImport: i0, template: '', isInline: true });
3818
+ }
3819
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: MultiEditorLegacyModal, decorators: [{
3820
+ type: Component,
3821
+ args: [{ template: '' }]
3822
+ }], ctorParameters: () => [{ type: i1$3.DataGridDataset }, { type: i1$3.MultiEditorDataset }, { type: i1$2.FormBuilder }], propDecorators: { multiEditor: [{
3823
+ type: ViewChild,
3824
+ args: [MultiEditorComponent]
3825
+ }], entityId: [{
3826
+ type: Input
3827
+ }], savedChanges: [{
3828
+ type: Output
3829
+ }] } });
3830
+
3831
+ // No strategy on purpose: Angular never inherits `changeDetection`, so one declared here would
3832
+ // describe a class that is never instantiated, while every subclass compiles to whatever it
3833
+ // declares -- which in v22 means OnPush. Correctness comes from the state below notifying
3834
+ // Angular, never from this decorator. See docs/guides/reactivity.md.
3835
+ class MultiEditorModal extends ModalBase {
3836
+ //#region ViewChilds, Inputs, Outputs
3837
+ multiEditor;
3838
+ overrideParentID;
3839
+ savedChanges = new EventEmitter();
3840
+ //#endregion
3841
+ //#region Variables
3842
+ dataForm;
3843
+ dataGridDataset;
3844
+ dataProvider;
3845
+ formBuilder;
3846
+ formService;
3847
+ multiEditorDataset;
3848
+ selectionCount = 0;
3849
+ //#endregion
3850
+ //#region Properties
3851
+ //#endregion
3852
+ //#region Constructor and Angular life cycle methods
3853
+ constructor() {
3854
+ super();
3855
+ this.dataGridDataset = inject(DataGridDataset);
3856
+ this.dataProvider = inject(DataProviderService, { optional: true });
3857
+ this.formBuilder = inject(FormBuilder);
3858
+ this.formService = inject(FormService);
3859
+ this.multiEditorDataset = inject(MultiEditorDataset);
3860
+ }
3861
+ ngOnInit() {
3862
+ this.dataForm = this.formSetup();
3863
+ this.formService.initializeForm(this.dataForm);
3864
+ this.multiEditorDataset.savedChanges.pipe(takeUntil(this.destroy$)).subscribe(() => {
3865
+ this.savedChanges.emit();
3866
+ });
3867
+ this.updateParentID();
3868
+ }
3869
+ //#endregion
3870
+ //#region Event handlers
3871
+ //#endregion
3872
+ //#region Public methods
3873
+ toggle() {
3874
+ if (!this.multiEditor.isOpen()) {
3875
+ this.updateParentID();
3876
+ }
3877
+ this.multiEditor.toggleModal();
3878
+ }
3879
+ updateParentID() {
3880
+ let parentID = this.dataProvider?.entityID;
3881
+ if (this.overrideParentID) {
3882
+ parentID = this.overrideParentID;
3883
+ }
3884
+ if (parentID) {
3885
+ this.dataGridDataset.parentEntityId = parentID;
3886
+ this.multiEditorDataset.parentEntityId = parentID;
3887
+ }
3888
+ }
3889
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: MultiEditorModal, deps: [], target: i0.ɵɵFactoryTarget.Component });
3890
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "22.1.7", type: MultiEditorModal, isStandalone: true, selector: "ng-component", inputs: { overrideParentID: "overrideParentID" }, outputs: { savedChanges: "savedChanges" }, viewQueries: [{ propertyName: "multiEditor", first: true, predicate: MultiEditorComponent, descendants: true }], usesInheritance: true, ngImport: i0, template: '', isInline: true });
3891
+ }
3892
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: MultiEditorModal, decorators: [{
3893
+ type: Component,
3894
+ args: [{ template: '' }]
3895
+ }], ctorParameters: () => [], propDecorators: { multiEditor: [{
3896
+ type: ViewChild,
3897
+ args: [MultiEditorComponent]
3898
+ }], overrideParentID: [{
3899
+ type: Input
3900
+ }], savedChanges: [{
3901
+ type: Output
3902
+ }] } });
3903
+
3904
+ // No strategy on purpose: Angular never inherits `changeDetection`, so one declared here would
3905
+ // describe a class that is never instantiated, while every subclass compiles to whatever it
3906
+ // declares -- which in v22 means OnPush. Correctness comes from the state below notifying
3907
+ // Angular, never from this decorator. See docs/guides/reactivity.md.
3908
+ class MultiSelectModal extends ModalBase {
3909
+ multiSelect;
3910
+ savedChanges = new EventEmitter();
3911
+ toggle() {
3912
+ this.multiSelect.toggleModal();
3913
+ }
3914
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: MultiSelectModal, deps: null, target: i0.ɵɵFactoryTarget.Component });
3915
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "22.1.7", type: MultiSelectModal, isStandalone: true, selector: "ng-component", outputs: { savedChanges: "savedChanges" }, viewQueries: [{ propertyName: "multiSelect", first: true, predicate: MultiSelectComponent, descendants: true }], usesInheritance: true, ngImport: i0, template: '', isInline: true });
3916
+ }
3917
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: MultiSelectModal, decorators: [{
3918
+ type: Component,
3919
+ args: [{ template: '' }]
3920
+ }], propDecorators: { multiSelect: [{
3921
+ type: ViewChild,
3922
+ args: [MultiSelectComponent]
3923
+ }], savedChanges: [{
3924
+ type: Output
3925
+ }] } });
3926
+
3927
+ // No strategy on purpose: Angular never inherits `changeDetection`, so one declared here would
3928
+ // describe a class that is never instantiated, while every subclass compiles to whatever it
3929
+ // declares -- which in v22 means OnPush. Correctness comes from the state below notifying
3930
+ // Angular, never from this decorator. See docs/guides/reactivity.md.
3931
+ class ViewModal extends ModalBase {
3932
+ //#region ViewChilds, Inputs, Outputs
3933
+ modalComponent;
3934
+ //#endregion
3935
+ //#region Variables
3936
+ beginEditOnEntityLoad = false;
3937
+ data$;
3938
+ dataForm;
3939
+ formBuilder;
3940
+ formService;
3941
+ selectionCount = 0;
3942
+ entityIdSubject = new Subject();
3943
+ //#endregion
3944
+ //#region Properties
3945
+ //#endregion
3946
+ //#region Constructor and Angular life cycle methods
3947
+ constructor() {
3948
+ super();
3949
+ this.formBuilder = inject(FormBuilder);
3950
+ this.formService = inject(FormService);
3951
+ this.data$ = this.entityIdSubject.pipe(switchMap((entityID) => this.loadData(entityID)));
3952
+ }
3953
+ ngOnInit() {
3954
+ this.dataForm = this.formSetup();
3955
+ this.formService.initializeForm(this.dataForm);
3956
+ this.data$.pipe(takeUntil(this.destroy$)).subscribe((data) => {
3957
+ this.formService.model = data;
3958
+ this.formService.loading = false;
3959
+ this.dataForm.patchValue(data);
3960
+ if (this.beginEditOnEntityLoad) {
3961
+ this.formService.beginEdit();
3962
+ }
3963
+ });
3964
+ }
3965
+ //#endregion
3966
+ //#region Event handlers
3967
+ //#endregion
3968
+ //#region Public methods
3969
+ toggle(entityID) {
3970
+ this.formService.model = null;
3971
+ this.formService.loading = true;
3972
+ this.dataForm.reset();
3973
+ if (!this.modalComponent.isOpen()) {
3974
+ this.entityIdSubject.next(entityID);
3975
+ }
3976
+ else {
3977
+ this.formService.resetForm();
3978
+ }
3979
+ this.modalComponent.toggleModal();
3980
+ }
3981
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: ViewModal, deps: [], target: i0.ɵɵFactoryTarget.Component });
3982
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "22.1.7", type: ViewModal, isStandalone: true, selector: "ng-component", viewQueries: [{ propertyName: "modalComponent", first: true, predicate: ["modal"], descendants: true }], usesInheritance: true, ngImport: i0, template: '', isInline: true });
3983
+ }
3984
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.7", ngImport: i0, type: ViewModal, decorators: [{
3985
+ type: Component,
3986
+ args: [{ template: '' }]
3987
+ }], ctorParameters: () => [], propDecorators: { modalComponent: [{
3988
+ type: ViewChild,
3989
+ args: ['modal']
3990
+ }] } });
3991
+
3992
+ /**
3993
+ * Generated bundle index. Do not edit.
3994
+ */
3995
+
3996
+ export { APP_CONFIG, AppConfig, AuthService, BackendFormValidationHelper, BaseButton, BaseFormViewButton, ButtonComponent, ButtonConfirmComponent, ButtonDeleteComponent, ButtonEditComponent, ButtonEditLegacyComponent, ButtonExportComponent, ButtonFiltersComponent, ButtonNewComponent, ButtonNewLegacyComponent, ButtonOpenRecordComponent, ButtonOpenRecordLegacyComponent, ButtonRefreshComponent, ButtonSaveComponent, ButtonSaveLegacyComponent, ButtonViewsComponent, ButtonViewsLegacyComponent, ChildList, ChildListLegacy, ConfirmModalComponent, CustomReuseStrategy, DefaultDetailsTabViewComponent, DefaultTabViewComponent, ErrorModalComponent, FRAMEWORK_VIEW_TYPE, FormView, FrameworkButtonsModule, FrameworkComponentsModule, FrameworkGridConfigsProvider, FrameworkModalsModule, FrameworkModule, FrameworkViewType, FrameworkViewsModule, LegacySubViewForm, LegacyTabViewDetails, LegacyTabViewList, ListView, MaxExportRows, ModalBase, MultiEditorLegacyModal, MultiEditorModal, MultiSelectModal, RouteHelper, Tab, TabBreadcrumbsComponent, TabService, TabViewBase, TabViewList, TabViewService, TabsComponent, ViewBase, ViewModal, ZAMBON_FRAMEWORK_I18N_ASSET, ZAMBON_FRAMEWORK_I18N_ASSET_PATH, ZAMBON_FRAMEWORK_I18N_RESOURCES, createZambonFrameworkTranslateLoader, provideZambonFrameworkTranslateLoader, provideZambonFrameworkTranslateService };
3997
+ //# sourceMappingURL=zambon-dev-ui-framework.mjs.map