@maro-tech/card 0.0.1

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.
@@ -0,0 +1,612 @@
1
+ import * as i1 from '@angular/common';
2
+ import { CommonModule } from '@angular/common';
3
+ import * as i0 from '@angular/core';
4
+ import { input, inject, DestroyRef, signal, Component, computed, output, effect, HostListener, InjectionToken, forwardRef } from '@angular/core';
5
+ import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
6
+ import { EventBus, UiButtonComponent } from '@maro-tech/core';
7
+ import { ActivatedRoute, Router } from '@angular/router';
8
+
9
+ const CARD_ACTION_REQUESTED = 'card.action.requested';
10
+ const CARD_ACTION_STARTED = 'card.action.started';
11
+ const CARD_ACTION_COMPLETED = 'card.action.completed';
12
+ const CARD_ACTION_FINISHED = 'card.action.finished';
13
+ const ENTITY_UPDATED = 'entity.updated';
14
+
15
+ class CardSectionHeaderComponent {
16
+ variant = input('panels', ...(ngDevMode ? [{ debugName: "variant" }] : /* istanbul ignore next */ []));
17
+ title = input('', ...(ngDevMode ? [{ debugName: "title" }] : /* istanbul ignore next */ []));
18
+ actions = input([], ...(ngDevMode ? [{ debugName: "actions" }] : /* istanbul ignore next */ []));
19
+ entity = input.required(...(ngDevMode ? [{ debugName: "entity" }] : /* istanbul ignore next */ []));
20
+ recordId = input('', ...(ngDevMode ? [{ debugName: "recordId" }] : /* istanbul ignore next */ []));
21
+ data = input(null, ...(ngDevMode ? [{ debugName: "data" }] : /* istanbul ignore next */ []));
22
+ eventBus = inject(EventBus);
23
+ destroyRef = inject(DestroyRef);
24
+ pendingActionIdSignal = signal('', ...(ngDevMode ? [{ debugName: "pendingActionIdSignal" }] : /* istanbul ignore next */ []));
25
+ constructor() {
26
+ this.eventBus.on(CARD_ACTION_FINISHED)
27
+ .pipe(takeUntilDestroyed(this.destroyRef))
28
+ .subscribe((event) => {
29
+ if (this.matchesContext(event.context)) {
30
+ this.pendingActionIdSignal.set('');
31
+ }
32
+ });
33
+ }
34
+ trackAction(_, action) {
35
+ return action.id || `${action.label}:${action.type}:${action.api || ''}:${action.formId || ''}`;
36
+ }
37
+ isActionPending(action) {
38
+ const pendingId = String(this.pendingActionIdSignal() || '').trim();
39
+ const actionId = String(action.id || '').trim();
40
+ return !!pendingId && !!actionId && pendingId === actionId;
41
+ }
42
+ onActionClick(action) {
43
+ const context = {
44
+ entity: this.entity(),
45
+ recordId: this.recordId() || undefined,
46
+ data: this.data(),
47
+ };
48
+ this.pendingActionIdSignal.set(String(action.id || '').trim());
49
+ this.eventBus.emit(CARD_ACTION_REQUESTED, { action, context });
50
+ }
51
+ matchesContext(context) {
52
+ return context.entity === this.entity()
53
+ && (!context.recordId || context.recordId === this.recordId());
54
+ }
55
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.20", ngImport: i0, type: CardSectionHeaderComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
56
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.20", type: CardSectionHeaderComponent, isStandalone: true, selector: "app-card-section-header", inputs: { variant: { classPropertyName: "variant", publicName: "variant", isSignal: true, isRequired: false, transformFunction: null }, title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null }, actions: { classPropertyName: "actions", publicName: "actions", isSignal: true, isRequired: false, transformFunction: null }, entity: { classPropertyName: "entity", publicName: "entity", isSignal: true, isRequired: true, transformFunction: null }, recordId: { classPropertyName: "recordId", publicName: "recordId", isSignal: true, isRequired: false, transformFunction: null }, data: { classPropertyName: "data", publicName: "data", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
57
+ @if (variant() === 'tabs') {
58
+ <header class="mb-4 flex justify-end">
59
+ <div class="flex flex-wrap gap-2 md:justify-end">
60
+ @for (action of actions(); track trackAction($index, action); let i = $index) {
61
+ <ui-button
62
+ [variant]="i === 0 ? 'primary' : 'ghost'"
63
+ [icon]="action.icon || ''"
64
+ size="sm"
65
+ [disabled]="isActionPending(action)"
66
+ (click)="onActionClick(action)"
67
+ >
68
+ {{ isActionPending(action) ? 'Зачекайте...' : action.label }}
69
+ </ui-button>
70
+ }
71
+ </div>
72
+ </header>
73
+ } @else {
74
+ <header class="mb-4 flex flex-col gap-3 border-b border-slate-100 pb-3 md:flex-row md:items-center md:justify-between">
75
+ @if (title()) {
76
+ <h3 class="text-lg font-semibold text-slate-900">{{ title() }}</h3>
77
+ } @else {
78
+ <span></span>
79
+ }
80
+
81
+ @if (actions().length) {
82
+ <div class="flex flex-wrap gap-2 md:justify-end">
83
+ @for (action of actions(); track trackAction($index, action); let i = $index) {
84
+ <ui-button
85
+ [variant]="i === 0 ? 'primary' : 'ghost'"
86
+ [icon]="action.icon || ''"
87
+ size="sm"
88
+ [disabled]="isActionPending(action)"
89
+ (click)="onActionClick(action)"
90
+ >
91
+ {{ isActionPending(action) ? 'Зачекайте...' : action.label }}
92
+ </ui-button>
93
+ }
94
+ </div>
95
+ }
96
+ </header>
97
+ }
98
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: UiButtonComponent, selector: "ui-button", inputs: ["variant", "size", "disabled", "fullWidth", "type", "withShadow", "icon", "iconOnly", "ariaLabel"] }] });
99
+ }
100
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.20", ngImport: i0, type: CardSectionHeaderComponent, decorators: [{
101
+ type: Component,
102
+ args: [{
103
+ selector: 'app-card-section-header',
104
+ standalone: true,
105
+ imports: [CommonModule, UiButtonComponent],
106
+ template: `
107
+ @if (variant() === 'tabs') {
108
+ <header class="mb-4 flex justify-end">
109
+ <div class="flex flex-wrap gap-2 md:justify-end">
110
+ @for (action of actions(); track trackAction($index, action); let i = $index) {
111
+ <ui-button
112
+ [variant]="i === 0 ? 'primary' : 'ghost'"
113
+ [icon]="action.icon || ''"
114
+ size="sm"
115
+ [disabled]="isActionPending(action)"
116
+ (click)="onActionClick(action)"
117
+ >
118
+ {{ isActionPending(action) ? 'Зачекайте...' : action.label }}
119
+ </ui-button>
120
+ }
121
+ </div>
122
+ </header>
123
+ } @else {
124
+ <header class="mb-4 flex flex-col gap-3 border-b border-slate-100 pb-3 md:flex-row md:items-center md:justify-between">
125
+ @if (title()) {
126
+ <h3 class="text-lg font-semibold text-slate-900">{{ title() }}</h3>
127
+ } @else {
128
+ <span></span>
129
+ }
130
+
131
+ @if (actions().length) {
132
+ <div class="flex flex-wrap gap-2 md:justify-end">
133
+ @for (action of actions(); track trackAction($index, action); let i = $index) {
134
+ <ui-button
135
+ [variant]="i === 0 ? 'primary' : 'ghost'"
136
+ [icon]="action.icon || ''"
137
+ size="sm"
138
+ [disabled]="isActionPending(action)"
139
+ (click)="onActionClick(action)"
140
+ >
141
+ {{ isActionPending(action) ? 'Зачекайте...' : action.label }}
142
+ </ui-button>
143
+ }
144
+ </div>
145
+ }
146
+ </header>
147
+ }
148
+ `,
149
+ }]
150
+ }], ctorParameters: () => [], propDecorators: { variant: [{ type: i0.Input, args: [{ isSignal: true, alias: "variant", required: false }] }], title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: false }] }], actions: [{ type: i0.Input, args: [{ isSignal: true, alias: "actions", required: false }] }], entity: [{ type: i0.Input, args: [{ isSignal: true, alias: "entity", required: true }] }], recordId: [{ type: i0.Input, args: [{ isSignal: true, alias: "recordId", required: false }] }], data: [{ type: i0.Input, args: [{ isSignal: true, alias: "data", required: false }] }] } });
151
+
152
+ class CardHtmlTabComponent {
153
+ entity = input.required(...(ngDevMode ? [{ debugName: "entity" }] : /* istanbul ignore next */ []));
154
+ recordId = input('', ...(ngDevMode ? [{ debugName: "recordId" }] : /* istanbul ignore next */ []));
155
+ tab = input(null, ...(ngDevMode ? [{ debugName: "tab" }] : /* istanbul ignore next */ []));
156
+ data = input(null, ...(ngDevMode ? [{ debugName: "data" }] : /* istanbul ignore next */ []));
157
+ eventBus = inject(EventBus);
158
+ html = computed(() => this.tab()?.html || '', ...(ngDevMode ? [{ debugName: "html" }] : /* istanbul ignore next */ []));
159
+ onClick(event) {
160
+ this.dispatchFromEvent(event);
161
+ }
162
+ onKeydown(event) {
163
+ this.dispatchFromEvent(event);
164
+ }
165
+ dispatchFromEvent(event) {
166
+ const target = event.target;
167
+ const actionElement = target?.closest?.('[class*="card-action-"]');
168
+ if (!actionElement)
169
+ return;
170
+ const action = this.resolveAction(actionElement);
171
+ if (!action)
172
+ return;
173
+ event.preventDefault();
174
+ event.stopPropagation();
175
+ this.eventBus.emit(CARD_ACTION_REQUESTED, {
176
+ action,
177
+ context: {
178
+ entity: this.entity(),
179
+ recordId: this.recordId() || undefined,
180
+ data: this.data(),
181
+ },
182
+ });
183
+ }
184
+ resolveAction(actionElement) {
185
+ const tab = this.tab();
186
+ const actions = Array.isArray(tab?.actions) ? tab.actions : [];
187
+ if (!actions.length)
188
+ return null;
189
+ const actionId = Array.from(actionElement?.classList || [])
190
+ .find((className) => className.startsWith('card-action-'))
191
+ ?.slice('card-action-'.length)
192
+ .trim();
193
+ if (!actionId)
194
+ return null;
195
+ return actions.find((item) => String(item.id || '').trim() === actionId) || null;
196
+ }
197
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.20", ngImport: i0, type: CardHtmlTabComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
198
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.20", type: CardHtmlTabComponent, isStandalone: true, selector: "app-card-html-tab", inputs: { entity: { classPropertyName: "entity", publicName: "entity", isSignal: true, isRequired: true, transformFunction: null }, recordId: { classPropertyName: "recordId", publicName: "recordId", isSignal: true, isRequired: false, transformFunction: null }, tab: { classPropertyName: "tab", publicName: "tab", isSignal: true, isRequired: false, transformFunction: null }, data: { classPropertyName: "data", publicName: "data", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<section\n [attr.data-card-tab-id]=\"tab()?.id || ''\"\n (click)=\"onClick($event)\"\n (keydown.enter)=\"onKeydown($event)\"\n (keydown.space)=\"onKeydown($event)\"\n>\n <div [innerHTML]=\"html()\"></div>\n</section>\n", dependencies: [{ kind: "ngmodule", type: CommonModule }] });
199
+ }
200
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.20", ngImport: i0, type: CardHtmlTabComponent, decorators: [{
201
+ type: Component,
202
+ args: [{ selector: 'app-card-html-tab', standalone: true, imports: [CommonModule], template: "<section\n [attr.data-card-tab-id]=\"tab()?.id || ''\"\n (click)=\"onClick($event)\"\n (keydown.enter)=\"onKeydown($event)\"\n (keydown.space)=\"onKeydown($event)\"\n>\n <div [innerHTML]=\"html()\"></div>\n</section>\n" }]
203
+ }], propDecorators: { entity: [{ type: i0.Input, args: [{ isSignal: true, alias: "entity", required: true }] }], recordId: [{ type: i0.Input, args: [{ isSignal: true, alias: "recordId", required: false }] }], tab: [{ type: i0.Input, args: [{ isSignal: true, alias: "tab", required: false }] }], data: [{ type: i0.Input, args: [{ isSignal: true, alias: "data", required: false }] }] } });
204
+
205
+ class ImagesViewerComponent {
206
+ urls = input([], ...(ngDevMode ? [{ debugName: "urls" }] : /* istanbul ignore next */ []));
207
+ initialIndex = input(0, ...(ngDevMode ? [{ debugName: "initialIndex" }] : /* istanbul ignore next */ []));
208
+ currentIndex = signal(0, ...(ngDevMode ? [{ debugName: "currentIndex" }] : /* istanbul ignore next */ []));
209
+ closed = output();
210
+ constructor() {
211
+ effect(() => {
212
+ const urls = this.urls();
213
+ if (!urls.length) {
214
+ this.currentIndex.set(0);
215
+ return;
216
+ }
217
+ const maxIndex = urls.length - 1;
218
+ const startIndex = Math.max(0, Math.min(this.initialIndex(), maxIndex));
219
+ this.currentIndex.set(startIndex);
220
+ });
221
+ }
222
+ onEscape() {
223
+ this.closed.emit();
224
+ }
225
+ onArrowLeft() {
226
+ this.showPreviousImage();
227
+ }
228
+ onArrowRight() {
229
+ this.showNextImage();
230
+ }
231
+ closeViewer() {
232
+ this.closed.emit();
233
+ }
234
+ showPreviousImage() {
235
+ const total = this.urls().length;
236
+ if (total <= 1)
237
+ return;
238
+ this.currentIndex.set((this.currentIndex() - 1 + total) % total);
239
+ }
240
+ showNextImage() {
241
+ const total = this.urls().length;
242
+ if (total <= 1)
243
+ return;
244
+ this.currentIndex.set((this.currentIndex() + 1) % total);
245
+ }
246
+ get imageViewerTransform() {
247
+ return `translate3d(-${this.currentIndex() * 100}%, 0, 0)`;
248
+ }
249
+ get imageViewerTransitionStyle() {
250
+ return 'transform 320ms cubic-bezier(0.22, 1, 0.36, 1)';
251
+ }
252
+ getThumbnailUrl(url) {
253
+ const match = String(url || '').match(/^(.*\/api\/files\/[^/?#]+)(?:\?.*)?$/i);
254
+ return match ? `${match[1]}/preview?scale=25` : url;
255
+ }
256
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.20", ngImport: i0, type: ImagesViewerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
257
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.20", type: ImagesViewerComponent, isStandalone: true, selector: "app-images-viewer", inputs: { urls: { classPropertyName: "urls", publicName: "urls", isSignal: true, isRequired: false, transformFunction: null }, initialIndex: { classPropertyName: "initialIndex", publicName: "initialIndex", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { closed: "closed" }, host: { listeners: { "document:keydown.escape": "onEscape()", "document:keydown.arrowleft": "onArrowLeft()", "document:keydown.arrowright": "onArrowRight()" } }, ngImport: i0, template: "<div class=\"fixed inset-0 z-[2300] flex items-center justify-center bg-black/80 p-3 md:p-4\" role=\"dialog\" aria-modal=\"true\" aria-label=\"\u041F\u0435\u0440\u0435\u0433\u043B\u044F\u0434 \u0444\u043E\u0442\u043E\">\n <button type=\"button\" class=\"absolute inset-0\" aria-label=\"\u0417\u0430\u043A\u0440\u0438\u0442\u0438 \u043F\u0435\u0440\u0435\u0433\u043B\u044F\u0434\" (click)=\"closeViewer()\"></button>\n <div class=\"relative z-[1] flex h-full w-full max-w-[1500px] flex-col items-center justify-center gap-4\" (click)=\"$event.stopPropagation()\">\n <button\n type=\"button\"\n class=\"absolute right-2 top-2 z-20 inline-flex h-11 w-11 items-center justify-center rounded-full bg-white/90 text-slate-900 shadow-[0_12px_28px_rgba(15,23,42,0.22)] transition hover:bg-white md:right-3 md:top-3\"\n aria-label=\"\u0417\u0430\u043A\u0440\u0438\u0442\u0438\"\n (click)=\"closeViewer()\">\n \u2715\n </button>\n\n <div class=\"relative flex min-h-0 w-full flex-1 items-center justify-center overflow-hidden rounded-[18px]\">\n <div class=\"h-full w-full overflow-hidden rounded-[18px]\">\n <div\n class=\"flex h-full w-full\"\n style=\"will-change: transform; backface-visibility: hidden;\"\n [style.transition]=\"imageViewerTransitionStyle\"\n [style.transform]=\"imageViewerTransform\">\n @for (url of urls(); track $index; let idx = $index) {\n <div class=\"flex h-full w-full min-w-full shrink-0 items-center justify-center overflow-hidden\">\n <img\n [src]=\"idx === currentIndex() ? url : getThumbnailUrl(url)\"\n [attr.alt]=\"'\u0424\u043E\u0442\u043E ' + (idx + 1)\"\n [attr.loading]=\"idx === currentIndex() ? 'eager' : 'lazy'\"\n decoding=\"async\"\n class=\"block max-h-[74vh] max-w-[96vw] object-contain md:max-h-[78vh]\">\n </div>\n }\n </div>\n </div>\n\n @if (urls().length > 1) {\n <button type=\"button\" class=\"absolute left-0 top-0 z-10 flex h-full w-1/4 min-w-12 items-center justify-start pl-2 md:pl-3\" aria-label=\"\u041F\u043E\u043F\u0435\u0440\u0435\u0434\u043D\u0454 \u0444\u043E\u0442\u043E\" (click)=\"showPreviousImage()\">\n <span class=\"inline-flex h-11 w-11 items-center justify-center rounded-full bg-white/90 text-slate-900 shadow-[0_12px_28px_rgba(15,23,42,0.22)] transition hover:bg-white\">\u2039</span>\n </button>\n <button type=\"button\" class=\"absolute right-0 top-0 z-10 flex h-full w-1/4 min-w-12 items-center justify-end pr-2 md:pr-3\" aria-label=\"\u041D\u0430\u0441\u0442\u0443\u043F\u043D\u0435 \u0444\u043E\u0442\u043E\" (click)=\"showNextImage()\">\n <span class=\"inline-flex h-11 w-11 items-center justify-center rounded-full bg-white/90 text-slate-900 shadow-[0_12px_28px_rgba(15,23,42,0.22)] transition hover:bg-white\">\u203A</span>\n </button>\n }\n </div>\n\n @if (urls().length > 1) {\n <div class=\"flex w-full flex-col items-center gap-3 pb-1 md:pb-2\">\n <div class=\"flex max-w-[92vw] flex-wrap justify-center gap-2 md:max-w-[80vw]\">\n @for (url of urls(); track url; let idx = $index) {\n <button\n type=\"button\"\n class=\"h-14 w-14 overflow-hidden rounded-xl border bg-white p-1 transition\"\n [class.border-slate-900]=\"idx === currentIndex()\"\n [class.shadow-[0_0_0_2px_rgba(15,23,42,0.16)]]=\"idx === currentIndex()\"\n [class.border-slate-300]=\"idx !== currentIndex()\"\n [attr.aria-label]=\"'\u0424\u043E\u0442\u043E ' + (idx + 1)\"\n (click)=\"currentIndex.set(idx)\">\n <img class=\"block aspect-square w-full rounded-lg object-contain\" [src]=\"getThumbnailUrl(url)\" [attr.alt]=\"'\u041C\u0456\u043D\u0456\u0430\u0442\u044E\u0440\u0430 ' + (idx + 1)\" loading=\"lazy\" decoding=\"async\">\n </button>\n }\n </div>\n <div class=\"rounded-full bg-white/95 px-3 py-1.5 text-xs font-semibold text-slate-900 shadow-[0_12px_28px_rgba(15,23,42,0.2)]\">\n {{ currentIndex() + 1 }} / {{ urls().length }}\n </div>\n </div>\n }\n </div>\n</div>\n", dependencies: [{ kind: "ngmodule", type: CommonModule }] });
258
+ }
259
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.20", ngImport: i0, type: ImagesViewerComponent, decorators: [{
260
+ type: Component,
261
+ args: [{ selector: 'app-images-viewer', standalone: true, imports: [CommonModule], template: "<div class=\"fixed inset-0 z-[2300] flex items-center justify-center bg-black/80 p-3 md:p-4\" role=\"dialog\" aria-modal=\"true\" aria-label=\"\u041F\u0435\u0440\u0435\u0433\u043B\u044F\u0434 \u0444\u043E\u0442\u043E\">\n <button type=\"button\" class=\"absolute inset-0\" aria-label=\"\u0417\u0430\u043A\u0440\u0438\u0442\u0438 \u043F\u0435\u0440\u0435\u0433\u043B\u044F\u0434\" (click)=\"closeViewer()\"></button>\n <div class=\"relative z-[1] flex h-full w-full max-w-[1500px] flex-col items-center justify-center gap-4\" (click)=\"$event.stopPropagation()\">\n <button\n type=\"button\"\n class=\"absolute right-2 top-2 z-20 inline-flex h-11 w-11 items-center justify-center rounded-full bg-white/90 text-slate-900 shadow-[0_12px_28px_rgba(15,23,42,0.22)] transition hover:bg-white md:right-3 md:top-3\"\n aria-label=\"\u0417\u0430\u043A\u0440\u0438\u0442\u0438\"\n (click)=\"closeViewer()\">\n \u2715\n </button>\n\n <div class=\"relative flex min-h-0 w-full flex-1 items-center justify-center overflow-hidden rounded-[18px]\">\n <div class=\"h-full w-full overflow-hidden rounded-[18px]\">\n <div\n class=\"flex h-full w-full\"\n style=\"will-change: transform; backface-visibility: hidden;\"\n [style.transition]=\"imageViewerTransitionStyle\"\n [style.transform]=\"imageViewerTransform\">\n @for (url of urls(); track $index; let idx = $index) {\n <div class=\"flex h-full w-full min-w-full shrink-0 items-center justify-center overflow-hidden\">\n <img\n [src]=\"idx === currentIndex() ? url : getThumbnailUrl(url)\"\n [attr.alt]=\"'\u0424\u043E\u0442\u043E ' + (idx + 1)\"\n [attr.loading]=\"idx === currentIndex() ? 'eager' : 'lazy'\"\n decoding=\"async\"\n class=\"block max-h-[74vh] max-w-[96vw] object-contain md:max-h-[78vh]\">\n </div>\n }\n </div>\n </div>\n\n @if (urls().length > 1) {\n <button type=\"button\" class=\"absolute left-0 top-0 z-10 flex h-full w-1/4 min-w-12 items-center justify-start pl-2 md:pl-3\" aria-label=\"\u041F\u043E\u043F\u0435\u0440\u0435\u0434\u043D\u0454 \u0444\u043E\u0442\u043E\" (click)=\"showPreviousImage()\">\n <span class=\"inline-flex h-11 w-11 items-center justify-center rounded-full bg-white/90 text-slate-900 shadow-[0_12px_28px_rgba(15,23,42,0.22)] transition hover:bg-white\">\u2039</span>\n </button>\n <button type=\"button\" class=\"absolute right-0 top-0 z-10 flex h-full w-1/4 min-w-12 items-center justify-end pr-2 md:pr-3\" aria-label=\"\u041D\u0430\u0441\u0442\u0443\u043F\u043D\u0435 \u0444\u043E\u0442\u043E\" (click)=\"showNextImage()\">\n <span class=\"inline-flex h-11 w-11 items-center justify-center rounded-full bg-white/90 text-slate-900 shadow-[0_12px_28px_rgba(15,23,42,0.22)] transition hover:bg-white\">\u203A</span>\n </button>\n }\n </div>\n\n @if (urls().length > 1) {\n <div class=\"flex w-full flex-col items-center gap-3 pb-1 md:pb-2\">\n <div class=\"flex max-w-[92vw] flex-wrap justify-center gap-2 md:max-w-[80vw]\">\n @for (url of urls(); track url; let idx = $index) {\n <button\n type=\"button\"\n class=\"h-14 w-14 overflow-hidden rounded-xl border bg-white p-1 transition\"\n [class.border-slate-900]=\"idx === currentIndex()\"\n [class.shadow-[0_0_0_2px_rgba(15,23,42,0.16)]]=\"idx === currentIndex()\"\n [class.border-slate-300]=\"idx !== currentIndex()\"\n [attr.aria-label]=\"'\u0424\u043E\u0442\u043E ' + (idx + 1)\"\n (click)=\"currentIndex.set(idx)\">\n <img class=\"block aspect-square w-full rounded-lg object-contain\" [src]=\"getThumbnailUrl(url)\" [attr.alt]=\"'\u041C\u0456\u043D\u0456\u0430\u0442\u044E\u0440\u0430 ' + (idx + 1)\" loading=\"lazy\" decoding=\"async\">\n </button>\n }\n </div>\n <div class=\"rounded-full bg-white/95 px-3 py-1.5 text-xs font-semibold text-slate-900 shadow-[0_12px_28px_rgba(15,23,42,0.2)]\">\n {{ currentIndex() + 1 }} / {{ urls().length }}\n </div>\n </div>\n }\n </div>\n</div>\n" }]
262
+ }], ctorParameters: () => [], propDecorators: { urls: [{ type: i0.Input, args: [{ isSignal: true, alias: "urls", required: false }] }], initialIndex: [{ type: i0.Input, args: [{ isSignal: true, alias: "initialIndex", required: false }] }], closed: [{ type: i0.Output, args: ["closed"] }], onEscape: [{
263
+ type: HostListener,
264
+ args: ['document:keydown.escape']
265
+ }], onArrowLeft: [{
266
+ type: HostListener,
267
+ args: ['document:keydown.arrowleft']
268
+ }], onArrowRight: [{
269
+ type: HostListener,
270
+ args: ['document:keydown.arrowright']
271
+ }] } });
272
+
273
+ class DetailsGridComponent {
274
+ tab = input(null, ...(ngDevMode ? [{ debugName: "tab" }] : /* istanbul ignore next */ []));
275
+ hasExplicitColumns = computed(() => this.resolveFields().some((field) => this.isValidColumnSpan(field.col)), ...(ngDevMode ? [{ debugName: "hasExplicitColumns" }] : /* istanbul ignore next */ []));
276
+ displayFields = computed(() => this.resolveFields().map((field) => ({
277
+ ...field,
278
+ displayValue: this.formatFieldValue(field),
279
+ imageSrc: this.getImageSrc(field),
280
+ })), ...(ngDevMode ? [{ debugName: "displayFields" }] : /* istanbul ignore next */ []));
281
+ selectedImageSrc = signal(null, ...(ngDevMode ? [{ debugName: "selectedImageSrc" }] : /* istanbul ignore next */ []));
282
+ openViewer(imageSrc) {
283
+ this.selectedImageSrc.set(imageSrc);
284
+ }
285
+ closeViewer() {
286
+ this.selectedImageSrc.set(null);
287
+ }
288
+ resolveFields() {
289
+ const value = this.tab()?.props?.['fields'];
290
+ return Array.isArray(value) ? value : [];
291
+ }
292
+ getFieldColumnClass(field) {
293
+ if (!this.isValidColumnSpan(field.col))
294
+ return '';
295
+ const span = Math.min(12, Math.trunc(Number(field.col)));
296
+ const classes = {
297
+ 1: 'sm:col-span-1',
298
+ 2: 'sm:col-span-2',
299
+ 3: 'sm:col-span-3',
300
+ 4: 'sm:col-span-4',
301
+ 5: 'sm:col-span-5',
302
+ 6: 'sm:col-span-6',
303
+ 7: 'sm:col-span-7',
304
+ 8: 'sm:col-span-8',
305
+ 9: 'sm:col-span-9',
306
+ 10: 'sm:col-span-10',
307
+ 11: 'sm:col-span-11',
308
+ 12: 'sm:col-span-12',
309
+ };
310
+ return classes[span] || '';
311
+ }
312
+ isValidColumnSpan(value) {
313
+ const span = Number(value);
314
+ return Number.isFinite(span) && span > 0;
315
+ }
316
+ formatFieldValue(field) {
317
+ const value = field.value;
318
+ if (value == null)
319
+ return '-';
320
+ if (typeof value === 'boolean')
321
+ return value ? 'Так' : 'Ні';
322
+ const raw = String(value).trim();
323
+ if (!raw)
324
+ return '-';
325
+ if (field.type === 'date' || field.type === 'datetime') {
326
+ return this.formatDate(raw, field.type === 'datetime');
327
+ }
328
+ if (field.type === 'enum') {
329
+ return this.formatEnum(raw, field.enumMap);
330
+ }
331
+ return raw;
332
+ }
333
+ getImageSrc(field) {
334
+ if (field.type !== 'image')
335
+ return null;
336
+ const value = field.value;
337
+ if (value == null)
338
+ return null;
339
+ const raw = String(value).trim();
340
+ if (!raw)
341
+ return null;
342
+ if (/^https?:\/\//i.test(raw) || raw.startsWith('/')) {
343
+ return raw;
344
+ }
345
+ const base = String(field.imageBaseUrl || '/api/files').trim().replace(/\/+$/, '');
346
+ return `${base}/${encodeURIComponent(raw)}`;
347
+ }
348
+ formatEnum(raw, enumMap) {
349
+ if (enumMap?.[raw]) {
350
+ return enumMap[raw];
351
+ }
352
+ if (raw === 'true')
353
+ return 'Так';
354
+ if (raw === 'false')
355
+ return 'Ні';
356
+ return raw;
357
+ }
358
+ formatDate(value, withTime) {
359
+ const date = new Date(value);
360
+ if (Number.isNaN(date.getTime())) {
361
+ return value;
362
+ }
363
+ return new Intl.DateTimeFormat('uk-UA', withTime
364
+ ? { day: '2-digit', month: '2-digit', year: 'numeric', hour: '2-digit', minute: '2-digit' }
365
+ : { day: '2-digit', month: '2-digit', year: 'numeric' }).format(date);
366
+ }
367
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.20", ngImport: i0, type: DetailsGridComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
368
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.20", type: DetailsGridComponent, isStandalone: true, selector: "app-details-grid", inputs: { tab: { classPropertyName: "tab", publicName: "tab", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<div class=\"grid gap-4\" [class.grid-cols-1]=\"!hasExplicitColumns()\" [class.sm:grid-cols-3]=\"!hasExplicitColumns()\" [class.sm:grid-cols-12]=\"hasExplicitColumns()\">\n @for (field of displayFields(); track $index) {\n <div class=\"rounded-2xl border border-slate-100 bg-slate-200 p-4\" [ngClass]=\"getFieldColumnClass(field)\" [class.flex]=\"field.type === 'image'\" [class.flex-col]=\"field.type === 'image'\" [class.min-h-64]=\"field.type === 'image'\">\n <p class=\"text-xs uppercase tracking-[0.18em] text-slate-500\">{{ field.label }}</p>\n @if (field.type === 'image') {\n @if (field.imageSrc) {\n <button type=\"button\" class=\"mt-2 block flex-1 cursor-zoom-in overflow-hidden rounded-xl border border-slate-200 bg-white\" [attr.aria-label]=\"'\u041F\u0435\u0440\u0435\u0433\u043B\u044F\u043D\u0443\u0442\u0438 ' + field.label\" (click)=\"openViewer(field.imageSrc)\">\n <img [src]=\"field.imageSrc\" [alt]=\"field.label\" class=\"h-full w-full object-cover\" />\n </button>\n } @else {\n <p class=\"mt-2 text-base font-semibold text-slate-900\">-</p>\n }\n } @else {\n <p class=\"mt-2 text-base font-semibold text-slate-900\">{{ field.displayValue ?? '-' }}</p>\n }\n </div>\n }\n</div>\n\n@if (selectedImageSrc(); as imageSrc) {\n <app-images-viewer [urls]=\"[imageSrc]\" (closed)=\"closeViewer()\" />\n}\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "component", type: ImagesViewerComponent, selector: "app-images-viewer", inputs: ["urls", "initialIndex"], outputs: ["closed"] }] });
369
+ }
370
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.20", ngImport: i0, type: DetailsGridComponent, decorators: [{
371
+ type: Component,
372
+ args: [{ selector: 'app-details-grid', standalone: true, imports: [CommonModule, ImagesViewerComponent], template: "<div class=\"grid gap-4\" [class.grid-cols-1]=\"!hasExplicitColumns()\" [class.sm:grid-cols-3]=\"!hasExplicitColumns()\" [class.sm:grid-cols-12]=\"hasExplicitColumns()\">\n @for (field of displayFields(); track $index) {\n <div class=\"rounded-2xl border border-slate-100 bg-slate-200 p-4\" [ngClass]=\"getFieldColumnClass(field)\" [class.flex]=\"field.type === 'image'\" [class.flex-col]=\"field.type === 'image'\" [class.min-h-64]=\"field.type === 'image'\">\n <p class=\"text-xs uppercase tracking-[0.18em] text-slate-500\">{{ field.label }}</p>\n @if (field.type === 'image') {\n @if (field.imageSrc) {\n <button type=\"button\" class=\"mt-2 block flex-1 cursor-zoom-in overflow-hidden rounded-xl border border-slate-200 bg-white\" [attr.aria-label]=\"'\u041F\u0435\u0440\u0435\u0433\u043B\u044F\u043D\u0443\u0442\u0438 ' + field.label\" (click)=\"openViewer(field.imageSrc)\">\n <img [src]=\"field.imageSrc\" [alt]=\"field.label\" class=\"h-full w-full object-cover\" />\n </button>\n } @else {\n <p class=\"mt-2 text-base font-semibold text-slate-900\">-</p>\n }\n } @else {\n <p class=\"mt-2 text-base font-semibold text-slate-900\">{{ field.displayValue ?? '-' }}</p>\n }\n </div>\n }\n</div>\n\n@if (selectedImageSrc(); as imageSrc) {\n <app-images-viewer [urls]=\"[imageSrc]\" (closed)=\"closeViewer()\" />\n}\n" }]
373
+ }], propDecorators: { tab: [{ type: i0.Input, args: [{ isSignal: true, alias: "tab", required: false }] }] } });
374
+
375
+ class ImagesComponent {
376
+ tab = input(null, ...(ngDevMode ? [{ debugName: "tab" }] : /* istanbul ignore next */ []));
377
+ data = input(null, ...(ngDevMode ? [{ debugName: "data" }] : /* istanbul ignore next */ []));
378
+ isViewerOpen = signal(false, ...(ngDevMode ? [{ debugName: "isViewerOpen" }] : /* istanbul ignore next */ []));
379
+ viewerStartIndex = signal(0, ...(ngDevMode ? [{ debugName: "viewerStartIndex" }] : /* istanbul ignore next */ []));
380
+ resolvedImageIds = computed(() => {
381
+ const tab = this.tab();
382
+ const props = tab?.props || {};
383
+ const explicit = this.normalizeIds(props['imageIds']);
384
+ if (explicit.length)
385
+ return explicit;
386
+ const key = String(props['images'] || 'images').trim();
387
+ const value = key ? this.data()?.[key] : null;
388
+ return this.normalizeIds(value);
389
+ }, ...(ngDevMode ? [{ debugName: "resolvedImageIds" }] : /* istanbul ignore next */ []));
390
+ resolvedImageUrls = computed(() => {
391
+ const tab = this.tab();
392
+ const props = tab?.props || {};
393
+ const explicit = Array.isArray(props['imageUrls'])
394
+ ? props['imageUrls'].filter((item) => typeof item === 'string')
395
+ : [];
396
+ if (explicit.length)
397
+ return explicit;
398
+ return this.resolvedImageIds().map((id) => this.resolveImageUrl(id)).filter((item) => !!item);
399
+ }, ...(ngDevMode ? [{ debugName: "resolvedImageUrls" }] : /* istanbul ignore next */ []));
400
+ resolvedImagePreviewUrls = computed(() => this.resolvedImageUrls().map((url) => this.resolvePreviewUrl(url)), ...(ngDevMode ? [{ debugName: "resolvedImagePreviewUrls" }] : /* istanbul ignore next */ []));
401
+ resolvedImageMaxHeight = computed(() => {
402
+ const value = Number(this.tab()?.props?.['maxHeight']);
403
+ return Number.isFinite(value) && value > 0 ? value : 400;
404
+ }, ...(ngDevMode ? [{ debugName: "resolvedImageMaxHeight" }] : /* istanbul ignore next */ []));
405
+ openViewer(index) {
406
+ this.viewerStartIndex.set(Math.max(0, index));
407
+ if (this.resolvedImageUrls().length)
408
+ this.isViewerOpen.set(true);
409
+ }
410
+ closeViewer() {
411
+ this.isViewerOpen.set(false);
412
+ }
413
+ resolveImageUrl(imageIdOrUrl) {
414
+ const value = String(imageIdOrUrl || '').trim();
415
+ if (!value) {
416
+ return '';
417
+ }
418
+ if (value.startsWith('http://') || value.startsWith('https://') || value.startsWith('data:') || value.startsWith('/')) {
419
+ return value;
420
+ }
421
+ const base = String(this.tab()?.props?.['imageBaseUrl'] || '/api/files').trim().replace(/\/+$/, '');
422
+ return `${base}/${encodeURIComponent(value)}`;
423
+ }
424
+ resolvePreviewUrl(url) {
425
+ const match = url.match(/^(.*\/api\/files\/[^/?#]+)(?:\?.*)?$/i);
426
+ return match ? `${match[1]}/preview?scale=25` : url;
427
+ }
428
+ normalizeIds(value) {
429
+ if (!Array.isArray(value)) {
430
+ return [];
431
+ }
432
+ return Array.from(new Set(value
433
+ .map((item) => String(item || '').trim())
434
+ .filter(Boolean)));
435
+ }
436
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.20", ngImport: i0, type: ImagesComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
437
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.20", type: ImagesComponent, isStandalone: true, selector: "app-images", inputs: { tab: { classPropertyName: "tab", publicName: "tab", isSignal: true, isRequired: false, transformFunction: null }, data: { classPropertyName: "data", publicName: "data", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "@if (resolvedImageUrls().length === 0) {\n <p class=\"text-sm text-slate-500\">\u0424\u043E\u0442\u043E \u0432\u0456\u0434\u0441\u0443\u0442\u043D\u0456</p>\n} @else {\n <div\n class=\"grid gap-3\"\n [ngClass]=\"resolvedImageUrls().length === 1 ? 'grid-cols-1' : 'sm:grid-cols-2 lg:grid-cols-4'\"\n >\n @for (image of resolvedImagePreviewUrls(); track image; let i = $index) {\n <button\n type=\"button\"\n class=\"group block overflow-hidden rounded-2xl border border-slate-200 bg-slate-50 text-left\"\n [attr.aria-label]=\"'\u041F\u0435\u0440\u0435\u0433\u043B\u044F\u043D\u0443\u0442\u0438 \u0444\u043E\u0442\u043E ' + (i + 1)\"\n (click)=\"openViewer(i)\"\n >\n <img\n [src]=\"image\"\n [alt]=\"'\u0424\u043E\u0442\u043E ' + (i + 1)\"\n loading=\"lazy\"\n decoding=\"async\"\n class=\"w-full object-cover transition duration-200 group-hover:scale-[1.02]\"\n [style.max-height.px]=\"resolvedImageMaxHeight()\"\n [ngClass]=\"resolvedImageUrls().length === 1 ? 'h-auto' : 'h-48'\"\n />\n </button>\n }\n </div>\n\n @if (isViewerOpen()) {\n <app-images-viewer\n [urls]=\"resolvedImageUrls()\"\n [initialIndex]=\"viewerStartIndex()\"\n (closed)=\"closeViewer()\"\n ></app-images-viewer>\n }\n}\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "component", type: ImagesViewerComponent, selector: "app-images-viewer", inputs: ["urls", "initialIndex"], outputs: ["closed"] }] });
438
+ }
439
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.20", ngImport: i0, type: ImagesComponent, decorators: [{
440
+ type: Component,
441
+ args: [{ selector: 'app-images', standalone: true, imports: [CommonModule, ImagesViewerComponent], template: "@if (resolvedImageUrls().length === 0) {\n <p class=\"text-sm text-slate-500\">\u0424\u043E\u0442\u043E \u0432\u0456\u0434\u0441\u0443\u0442\u043D\u0456</p>\n} @else {\n <div\n class=\"grid gap-3\"\n [ngClass]=\"resolvedImageUrls().length === 1 ? 'grid-cols-1' : 'sm:grid-cols-2 lg:grid-cols-4'\"\n >\n @for (image of resolvedImagePreviewUrls(); track image; let i = $index) {\n <button\n type=\"button\"\n class=\"group block overflow-hidden rounded-2xl border border-slate-200 bg-slate-50 text-left\"\n [attr.aria-label]=\"'\u041F\u0435\u0440\u0435\u0433\u043B\u044F\u043D\u0443\u0442\u0438 \u0444\u043E\u0442\u043E ' + (i + 1)\"\n (click)=\"openViewer(i)\"\n >\n <img\n [src]=\"image\"\n [alt]=\"'\u0424\u043E\u0442\u043E ' + (i + 1)\"\n loading=\"lazy\"\n decoding=\"async\"\n class=\"w-full object-cover transition duration-200 group-hover:scale-[1.02]\"\n [style.max-height.px]=\"resolvedImageMaxHeight()\"\n [ngClass]=\"resolvedImageUrls().length === 1 ? 'h-auto' : 'h-48'\"\n />\n </button>\n }\n </div>\n\n @if (isViewerOpen()) {\n <app-images-viewer\n [urls]=\"resolvedImageUrls()\"\n [initialIndex]=\"viewerStartIndex()\"\n (closed)=\"closeViewer()\"\n ></app-images-viewer>\n }\n}\n" }]
442
+ }], propDecorators: { tab: [{ type: i0.Input, args: [{ isSignal: true, alias: "tab", required: false }] }], data: [{ type: i0.Input, args: [{ isSignal: true, alias: "data", required: false }] }] } });
443
+
444
+ const UI_CARD_TAB_REGISTRY = new InjectionToken('UI_CARD_TAB_REGISTRY');
445
+
446
+ class UiCardPanelsLayoutComponent {
447
+ registry = inject(UI_CARD_TAB_REGISTRY, { optional: true }) || [];
448
+ entity = input.required(...(ngDevMode ? [{ debugName: "entity" }] : /* istanbul ignore next */ []));
449
+ recordId = input('', ...(ngDevMode ? [{ debugName: "recordId" }] : /* istanbul ignore next */ []));
450
+ tabs = input([], ...(ngDevMode ? [{ debugName: "tabs" }] : /* istanbul ignore next */ []));
451
+ data = input(null, ...(ngDevMode ? [{ debugName: "data" }] : /* istanbul ignore next */ []));
452
+ embedded = input(false, ...(ngDevMode ? [{ debugName: "embedded" }] : /* istanbul ignore next */ []));
453
+ firstTab = computed(() => this.tabs()[0] || null, ...(ngDevMode ? [{ debugName: "firstTab" }] : /* istanbul ignore next */ []));
454
+ getActions(tab) {
455
+ const value = tab.actions;
456
+ return Array.isArray(value) ? value : [];
457
+ }
458
+ getVisibleActions(tab) {
459
+ return this.getActions(tab).filter((action) => !action.hidden);
460
+ }
461
+ shouldShowPanelHeader(tab) {
462
+ return !this.embedded() && !tab.html && tab.component !== 'html' && tab.header !== false;
463
+ }
464
+ getColSpanClass(tab) {
465
+ if (tab.col == null)
466
+ return 'md:col-span-12';
467
+ const raw = Number(tab.col);
468
+ const span = !Number.isFinite(raw) || raw <= 0 ? 12 : Math.min(12, Math.trunc(raw));
469
+ const classes = {
470
+ 1: 'md:col-span-1',
471
+ 2: 'md:col-span-2',
472
+ 3: 'md:col-span-3',
473
+ 4: 'md:col-span-4',
474
+ 5: 'md:col-span-5',
475
+ 6: 'md:col-span-6',
476
+ 7: 'md:col-span-7',
477
+ 8: 'md:col-span-8',
478
+ 9: 'md:col-span-9',
479
+ 10: 'md:col-span-10',
480
+ 11: 'md:col-span-11',
481
+ 12: 'md:col-span-12',
482
+ };
483
+ return classes[span] || 'md:col-span-12';
484
+ }
485
+ getNestedCardConfig(tab) {
486
+ if (Array.isArray(tab.tabs) && tab.tabs.length) {
487
+ return { tabs: tab.tabs, layout: tab.layout || 'panels' };
488
+ }
489
+ return null;
490
+ }
491
+ getNestedPanels(tab) {
492
+ return Array.isArray(tab.panels) && tab.panels.length ? tab.panels : null;
493
+ }
494
+ getRegisteredComponent(tab) {
495
+ const key = tab.component;
496
+ if (!key || key === 'images' || key === 'detailsGrid' || key === 'html')
497
+ return null;
498
+ const found = this.registry.find((item) => item.key === key);
499
+ return found?.component || null;
500
+ }
501
+ getRegisteredInputs(tab) {
502
+ const key = tab.component;
503
+ if (!key)
504
+ return { tab };
505
+ const found = this.registry.find((item) => item.key === key);
506
+ if (!found?.resolveInputs)
507
+ return { tab };
508
+ return found.resolveInputs(tab, this.data());
509
+ }
510
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.20", ngImport: i0, type: UiCardPanelsLayoutComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
511
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.20", type: UiCardPanelsLayoutComponent, isStandalone: true, selector: "app-ui-card-panels-layout", inputs: { entity: { classPropertyName: "entity", publicName: "entity", isSignal: true, isRequired: true, transformFunction: null }, recordId: { classPropertyName: "recordId", publicName: "recordId", isSignal: true, isRequired: false, transformFunction: null }, tabs: { classPropertyName: "tabs", publicName: "tabs", isSignal: true, isRequired: false, transformFunction: null }, data: { classPropertyName: "data", publicName: "data", isSignal: true, isRequired: false, transformFunction: null }, embedded: { classPropertyName: "embedded", publicName: "embedded", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "@if (tabs().length) {\n @if (!embedded()) {\n <section class=\"space-y-4\">\n <ng-container *ngTemplateOutlet=\"panelGrid\"></ng-container>\n </section>\n } @else {\n <ng-container *ngTemplateOutlet=\"panelGrid\"></ng-container>\n }\n}\n\n<ng-template #panelGrid>\n <div class=\"grid grid-cols-1 gap-4 md:grid-cols-12\">\n @for (tab of tabs(); track tab.id) {\n <section\n [class.rounded-3xl]=\"!embedded()\"\n [class.border]=\"!embedded()\"\n [class.border-slate-200]=\"!embedded()\"\n [class.bg-white]=\"!embedded()\"\n [class.p-6]=\"!embedded()\"\n [class.min-w-0]=\"embedded()\"\n [ngClass]=\"getColSpanClass(tab)\"\n [attr.data-card-panel-id]=\"tab.id\"\n >\n @if (shouldShowPanelHeader(tab)) {\n <app-card-section-header\n [entity]=\"entity()\"\n [recordId]=\"recordId()\"\n [data]=\"data()\"\n [title]=\"tab.title\"\n [actions]=\"getVisibleActions(tab)\"\n ></app-card-section-header>\n }\n\n @if (getNestedCardConfig(tab); as nestedCard) {\n <app-card [entity]=\"entity()\" [recordId]=\"recordId()\" [tabs]=\"nestedCard.tabs\" [data]=\"data()\" [layout]=\"nestedCard.layout\"></app-card>\n } @else if (getNestedPanels(tab); as nestedPanels) {\n <app-card [entity]=\"entity()\" [recordId]=\"recordId()\" [tabs]=\"nestedPanels\" [data]=\"data()\" layout=\"panels\" [embedded]=\"true\"></app-card>\n } @else if (tab.component === 'detailsGrid') {\n <app-details-grid [tab]=\"tab\"></app-details-grid>\n } @else if (tab.component === 'images') {\n <app-images [tab]=\"tab\" [data]=\"data()\"></app-images>\n } @else if (tab.component === 'html') {\n <app-card-html-tab [tab]=\"tab\" [entity]=\"entity()\" [recordId]=\"recordId()\" [data]=\"data()\"></app-card-html-tab>\n } @else if (getRegisteredComponent(tab); as customComponent) {\n <ng-container *ngComponentOutlet=\"customComponent; inputs: getRegisteredInputs(tab)\"></ng-container>\n }\n </section>\n }\n </div>\n</ng-template>\n", dependencies: [{ kind: "ngmodule", type: i0.forwardRef(() => CommonModule) }, { kind: "directive", type: i0.forwardRef(() => i1.NgClass), selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i0.forwardRef(() => i1.NgComponentOutlet), selector: "[ngComponentOutlet]", inputs: ["ngComponentOutlet", "ngComponentOutletInputs", "ngComponentOutletInjector", "ngComponentOutletEnvironmentInjector", "ngComponentOutletContent", "ngComponentOutletNgModule"], exportAs: ["ngComponentOutlet"] }, { kind: "directive", type: i0.forwardRef(() => i1.NgTemplateOutlet), selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: i0.forwardRef(() => CardComponent), selector: "app-card", inputs: ["entity", "recordId", "tabs", "data", "layout", "embedded"] }, { kind: "component", type: i0.forwardRef(() => CardSectionHeaderComponent), selector: "app-card-section-header", inputs: ["variant", "title", "actions", "entity", "recordId", "data"] }, { kind: "component", type: i0.forwardRef(() => CardHtmlTabComponent), selector: "app-card-html-tab", inputs: ["entity", "recordId", "tab", "data"] }, { kind: "component", type: i0.forwardRef(() => DetailsGridComponent), selector: "app-details-grid", inputs: ["tab"] }, { kind: "component", type: i0.forwardRef(() => ImagesComponent), selector: "app-images", inputs: ["tab", "data"] }] });
512
+ }
513
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.20", ngImport: i0, type: UiCardPanelsLayoutComponent, decorators: [{
514
+ type: Component,
515
+ args: [{ selector: 'app-ui-card-panels-layout', standalone: true, imports: [CommonModule, forwardRef(() => CardComponent), CardSectionHeaderComponent, CardHtmlTabComponent, DetailsGridComponent, ImagesComponent], template: "@if (tabs().length) {\n @if (!embedded()) {\n <section class=\"space-y-4\">\n <ng-container *ngTemplateOutlet=\"panelGrid\"></ng-container>\n </section>\n } @else {\n <ng-container *ngTemplateOutlet=\"panelGrid\"></ng-container>\n }\n}\n\n<ng-template #panelGrid>\n <div class=\"grid grid-cols-1 gap-4 md:grid-cols-12\">\n @for (tab of tabs(); track tab.id) {\n <section\n [class.rounded-3xl]=\"!embedded()\"\n [class.border]=\"!embedded()\"\n [class.border-slate-200]=\"!embedded()\"\n [class.bg-white]=\"!embedded()\"\n [class.p-6]=\"!embedded()\"\n [class.min-w-0]=\"embedded()\"\n [ngClass]=\"getColSpanClass(tab)\"\n [attr.data-card-panel-id]=\"tab.id\"\n >\n @if (shouldShowPanelHeader(tab)) {\n <app-card-section-header\n [entity]=\"entity()\"\n [recordId]=\"recordId()\"\n [data]=\"data()\"\n [title]=\"tab.title\"\n [actions]=\"getVisibleActions(tab)\"\n ></app-card-section-header>\n }\n\n @if (getNestedCardConfig(tab); as nestedCard) {\n <app-card [entity]=\"entity()\" [recordId]=\"recordId()\" [tabs]=\"nestedCard.tabs\" [data]=\"data()\" [layout]=\"nestedCard.layout\"></app-card>\n } @else if (getNestedPanels(tab); as nestedPanels) {\n <app-card [entity]=\"entity()\" [recordId]=\"recordId()\" [tabs]=\"nestedPanels\" [data]=\"data()\" layout=\"panels\" [embedded]=\"true\"></app-card>\n } @else if (tab.component === 'detailsGrid') {\n <app-details-grid [tab]=\"tab\"></app-details-grid>\n } @else if (tab.component === 'images') {\n <app-images [tab]=\"tab\" [data]=\"data()\"></app-images>\n } @else if (tab.component === 'html') {\n <app-card-html-tab [tab]=\"tab\" [entity]=\"entity()\" [recordId]=\"recordId()\" [data]=\"data()\"></app-card-html-tab>\n } @else if (getRegisteredComponent(tab); as customComponent) {\n <ng-container *ngComponentOutlet=\"customComponent; inputs: getRegisteredInputs(tab)\"></ng-container>\n }\n </section>\n }\n </div>\n</ng-template>\n" }]
516
+ }], propDecorators: { entity: [{ type: i0.Input, args: [{ isSignal: true, alias: "entity", required: true }] }], recordId: [{ type: i0.Input, args: [{ isSignal: true, alias: "recordId", required: false }] }], tabs: [{ type: i0.Input, args: [{ isSignal: true, alias: "tabs", required: false }] }], data: [{ type: i0.Input, args: [{ isSignal: true, alias: "data", required: false }] }], embedded: [{ type: i0.Input, args: [{ isSignal: true, alias: "embedded", required: false }] }] } });
517
+
518
+ class UiCardTabsLayoutComponent {
519
+ registry = inject(UI_CARD_TAB_REGISTRY, { optional: true }) || [];
520
+ destroyRef = inject(DestroyRef);
521
+ route = inject(ActivatedRoute);
522
+ router = inject(Router);
523
+ entity = input.required(...(ngDevMode ? [{ debugName: "entity" }] : /* istanbul ignore next */ []));
524
+ recordId = input('', ...(ngDevMode ? [{ debugName: "recordId" }] : /* istanbul ignore next */ []));
525
+ tabs = input([], ...(ngDevMode ? [{ debugName: "tabs" }] : /* istanbul ignore next */ []));
526
+ data = input(null, ...(ngDevMode ? [{ debugName: "data" }] : /* istanbul ignore next */ []));
527
+ activeTabId = signal('', ...(ngDevMode ? [{ debugName: "activeTabId" }] : /* istanbul ignore next */ []));
528
+ ngOnInit() {
529
+ this.route.queryParamMap.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((params) => {
530
+ const tabId = params.get('tab');
531
+ if (tabId)
532
+ this.activeTabId.set(tabId);
533
+ });
534
+ }
535
+ activeTab = computed(() => {
536
+ const items = this.tabs() || [];
537
+ if (!items.length)
538
+ return null;
539
+ const currentId = this.activeTabId();
540
+ return items.find((item) => item.id === currentId) || items[0];
541
+ }, ...(ngDevMode ? [{ debugName: "activeTab" }] : /* istanbul ignore next */ []));
542
+ setActiveTab(id) {
543
+ this.activeTabId.set(id);
544
+ void this.router.navigate([], {
545
+ relativeTo: this.route,
546
+ queryParams: { tab: id },
547
+ queryParamsHandling: 'merge',
548
+ replaceUrl: true,
549
+ });
550
+ }
551
+ getActions(tab) {
552
+ const value = tab.actions;
553
+ return Array.isArray(value) ? value : [];
554
+ }
555
+ getVisibleActions(tab) {
556
+ return this.getActions(tab).filter((action) => !action.hidden);
557
+ }
558
+ getColSpan(tab) {
559
+ if (tab.col == null)
560
+ return 12;
561
+ const raw = Number(tab.col);
562
+ if (!Number.isFinite(raw) || raw <= 0)
563
+ return 12;
564
+ return Math.min(12, Math.trunc(raw));
565
+ }
566
+ getRegisteredComponent(tab) {
567
+ const key = tab.component;
568
+ if (!key || key === 'images' || key === 'detailsGrid' || key === 'html')
569
+ return null;
570
+ const found = this.registry.find((item) => item.key === key);
571
+ return found?.component || null;
572
+ }
573
+ getRegisteredInputs(tab) {
574
+ const key = tab.component;
575
+ if (!key)
576
+ return { tab };
577
+ const found = this.registry.find((item) => item.key === key);
578
+ if (!found?.resolveInputs)
579
+ return { tab };
580
+ return found.resolveInputs(tab, this.data());
581
+ }
582
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.20", ngImport: i0, type: UiCardTabsLayoutComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
583
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.20", type: UiCardTabsLayoutComponent, isStandalone: true, selector: "app-ui-card-tabs-layout", inputs: { entity: { classPropertyName: "entity", publicName: "entity", isSignal: true, isRequired: true, transformFunction: null }, recordId: { classPropertyName: "recordId", publicName: "recordId", isSignal: true, isRequired: false, transformFunction: null }, tabs: { classPropertyName: "tabs", publicName: "tabs", isSignal: true, isRequired: false, transformFunction: null }, data: { classPropertyName: "data", publicName: "data", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "@if (tabs().length) {\n <section class=\"space-y-4\">\n <nav class=\"flex flex-wrap gap-2\" aria-label=\"Card tabs\">\n @for (tab of tabs(); track tab.id) {\n <button\n type=\"button\"\n class=\"rounded-xl border px-4 py-2 text-sm font-medium transition\"\n [class.border-slate-900]=\"activeTab()?.id === tab.id\"\n [class.bg-slate-900]=\"activeTab()?.id === tab.id\"\n [class.text-white]=\"activeTab()?.id === tab.id\"\n [class.border-slate-200]=\"activeTab()?.id !== tab.id\"\n [class.bg-white]=\"activeTab()?.id !== tab.id\"\n [class.text-slate-700]=\"activeTab()?.id !== tab.id\"\n (click)=\"setActiveTab(tab.id)\"\n >\n {{ tab.title }}\n </button>\n }\n </nav>\n\n @if (activeTab(); as tab) {\n <section\n [attr.data-card-tab-id]=\"tab.id\"\n >\n @if (getVisibleActions(tab).length) {\n <app-card-section-header\n variant=\"tabs\"\n [entity]=\"entity()\"\n [recordId]=\"recordId()\"\n [data]=\"data()\"\n [actions]=\"getVisibleActions(tab)\"\n ></app-card-section-header>\n }\n\n @if (tab.tabs?.length) {\n <app-card [entity]=\"entity()\" [recordId]=\"recordId()\" [tabs]=\"tab.tabs || []\" [data]=\"data()\" [layout]=\"tab.layout || 'panels'\"></app-card>\n } @else if (tab.component === 'images') {\n <app-images\n [tab]=\"tab\"\n [data]=\"data()\"\n ></app-images>\n } @else if (tab.component === 'detailsGrid') {\n <app-details-grid [tab]=\"tab\"></app-details-grid>\n } @else if (tab.component === 'html') {\n <app-card-html-tab [tab]=\"tab\" [entity]=\"entity()\" [recordId]=\"recordId()\" [data]=\"data()\"></app-card-html-tab>\n } @else if (getRegisteredComponent(tab); as customComponent) {\n <ng-container *ngComponentOutlet=\"customComponent; inputs: getRegisteredInputs(tab)\"></ng-container>\n }\n </section>\n }\n </section>\n}\n", dependencies: [{ kind: "ngmodule", type: i0.forwardRef(() => CommonModule) }, { kind: "directive", type: i0.forwardRef(() => i1.NgComponentOutlet), selector: "[ngComponentOutlet]", inputs: ["ngComponentOutlet", "ngComponentOutletInputs", "ngComponentOutletInjector", "ngComponentOutletEnvironmentInjector", "ngComponentOutletContent", "ngComponentOutletNgModule"], exportAs: ["ngComponentOutlet"] }, { kind: "component", type: i0.forwardRef(() => CardComponent), selector: "app-card", inputs: ["entity", "recordId", "tabs", "data", "layout", "embedded"] }, { kind: "component", type: i0.forwardRef(() => CardSectionHeaderComponent), selector: "app-card-section-header", inputs: ["variant", "title", "actions", "entity", "recordId", "data"] }, { kind: "component", type: i0.forwardRef(() => CardHtmlTabComponent), selector: "app-card-html-tab", inputs: ["entity", "recordId", "tab", "data"] }, { kind: "component", type: i0.forwardRef(() => ImagesComponent), selector: "app-images", inputs: ["tab", "data"] }, { kind: "component", type: i0.forwardRef(() => DetailsGridComponent), selector: "app-details-grid", inputs: ["tab"] }] });
584
+ }
585
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.20", ngImport: i0, type: UiCardTabsLayoutComponent, decorators: [{
586
+ type: Component,
587
+ args: [{ selector: 'app-ui-card-tabs-layout', standalone: true, imports: [CommonModule, forwardRef(() => CardComponent), CardSectionHeaderComponent, CardHtmlTabComponent, ImagesComponent, DetailsGridComponent], template: "@if (tabs().length) {\n <section class=\"space-y-4\">\n <nav class=\"flex flex-wrap gap-2\" aria-label=\"Card tabs\">\n @for (tab of tabs(); track tab.id) {\n <button\n type=\"button\"\n class=\"rounded-xl border px-4 py-2 text-sm font-medium transition\"\n [class.border-slate-900]=\"activeTab()?.id === tab.id\"\n [class.bg-slate-900]=\"activeTab()?.id === tab.id\"\n [class.text-white]=\"activeTab()?.id === tab.id\"\n [class.border-slate-200]=\"activeTab()?.id !== tab.id\"\n [class.bg-white]=\"activeTab()?.id !== tab.id\"\n [class.text-slate-700]=\"activeTab()?.id !== tab.id\"\n (click)=\"setActiveTab(tab.id)\"\n >\n {{ tab.title }}\n </button>\n }\n </nav>\n\n @if (activeTab(); as tab) {\n <section\n [attr.data-card-tab-id]=\"tab.id\"\n >\n @if (getVisibleActions(tab).length) {\n <app-card-section-header\n variant=\"tabs\"\n [entity]=\"entity()\"\n [recordId]=\"recordId()\"\n [data]=\"data()\"\n [actions]=\"getVisibleActions(tab)\"\n ></app-card-section-header>\n }\n\n @if (tab.tabs?.length) {\n <app-card [entity]=\"entity()\" [recordId]=\"recordId()\" [tabs]=\"tab.tabs || []\" [data]=\"data()\" [layout]=\"tab.layout || 'panels'\"></app-card>\n } @else if (tab.component === 'images') {\n <app-images\n [tab]=\"tab\"\n [data]=\"data()\"\n ></app-images>\n } @else if (tab.component === 'detailsGrid') {\n <app-details-grid [tab]=\"tab\"></app-details-grid>\n } @else if (tab.component === 'html') {\n <app-card-html-tab [tab]=\"tab\" [entity]=\"entity()\" [recordId]=\"recordId()\" [data]=\"data()\"></app-card-html-tab>\n } @else if (getRegisteredComponent(tab); as customComponent) {\n <ng-container *ngComponentOutlet=\"customComponent; inputs: getRegisteredInputs(tab)\"></ng-container>\n }\n </section>\n }\n </section>\n}\n" }]
588
+ }], propDecorators: { entity: [{ type: i0.Input, args: [{ isSignal: true, alias: "entity", required: true }] }], recordId: [{ type: i0.Input, args: [{ isSignal: true, alias: "recordId", required: false }] }], tabs: [{ type: i0.Input, args: [{ isSignal: true, alias: "tabs", required: false }] }], data: [{ type: i0.Input, args: [{ isSignal: true, alias: "data", required: false }] }] } });
589
+
590
+ class CardComponent {
591
+ entity = input.required(...(ngDevMode ? [{ debugName: "entity" }] : /* istanbul ignore next */ []));
592
+ recordId = input('', ...(ngDevMode ? [{ debugName: "recordId" }] : /* istanbul ignore next */ []));
593
+ tabs = input([], ...(ngDevMode ? [{ debugName: "tabs" }] : /* istanbul ignore next */ []));
594
+ data = input(null, ...(ngDevMode ? [{ debugName: "data" }] : /* istanbul ignore next */ []));
595
+ layout = input('panels', ...(ngDevMode ? [{ debugName: "layout" }] : /* istanbul ignore next */ []));
596
+ embedded = input(false, ...(ngDevMode ? [{ debugName: "embedded" }] : /* istanbul ignore next */ []));
597
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.20", ngImport: i0, type: CardComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
598
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.20", type: CardComponent, isStandalone: true, selector: "app-card", inputs: { entity: { classPropertyName: "entity", publicName: "entity", isSignal: true, isRequired: true, transformFunction: null }, recordId: { classPropertyName: "recordId", publicName: "recordId", isSignal: true, isRequired: false, transformFunction: null }, tabs: { classPropertyName: "tabs", publicName: "tabs", isSignal: true, isRequired: false, transformFunction: null }, data: { classPropertyName: "data", publicName: "data", isSignal: true, isRequired: false, transformFunction: null }, layout: { classPropertyName: "layout", publicName: "layout", isSignal: true, isRequired: false, transformFunction: null }, embedded: { classPropertyName: "embedded", publicName: "embedded", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "@if (layout() === 'panels') {\n <app-ui-card-panels-layout\n [entity]=\"entity()\"\n [recordId]=\"recordId()\"\n [tabs]=\"tabs()\"\n [data]=\"data()\"\n [embedded]=\"embedded()\"\n ></app-ui-card-panels-layout>\n} @else {\n <app-ui-card-tabs-layout\n [entity]=\"entity()\"\n [recordId]=\"recordId()\"\n [tabs]=\"tabs()\"\n [data]=\"data()\"\n ></app-ui-card-tabs-layout>\n}\n", dependencies: [{ kind: "ngmodule", type: i0.forwardRef(() => CommonModule) }, { kind: "component", type: i0.forwardRef(() => UiCardTabsLayoutComponent), selector: "app-ui-card-tabs-layout", inputs: ["entity", "recordId", "tabs", "data"] }, { kind: "component", type: i0.forwardRef(() => UiCardPanelsLayoutComponent), selector: "app-ui-card-panels-layout", inputs: ["entity", "recordId", "tabs", "data", "embedded"] }] });
599
+ }
600
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.20", ngImport: i0, type: CardComponent, decorators: [{
601
+ type: Component,
602
+ args: [{ selector: 'app-card', standalone: true, imports: [CommonModule, forwardRef(() => UiCardTabsLayoutComponent), forwardRef(() => UiCardPanelsLayoutComponent)], template: "@if (layout() === 'panels') {\n <app-ui-card-panels-layout\n [entity]=\"entity()\"\n [recordId]=\"recordId()\"\n [tabs]=\"tabs()\"\n [data]=\"data()\"\n [embedded]=\"embedded()\"\n ></app-ui-card-panels-layout>\n} @else {\n <app-ui-card-tabs-layout\n [entity]=\"entity()\"\n [recordId]=\"recordId()\"\n [tabs]=\"tabs()\"\n [data]=\"data()\"\n ></app-ui-card-tabs-layout>\n}\n" }]
603
+ }], propDecorators: { entity: [{ type: i0.Input, args: [{ isSignal: true, alias: "entity", required: true }] }], recordId: [{ type: i0.Input, args: [{ isSignal: true, alias: "recordId", required: false }] }], tabs: [{ type: i0.Input, args: [{ isSignal: true, alias: "tabs", required: false }] }], data: [{ type: i0.Input, args: [{ isSignal: true, alias: "data", required: false }] }], layout: [{ type: i0.Input, args: [{ isSignal: true, alias: "layout", required: false }] }], embedded: [{ type: i0.Input, args: [{ isSignal: true, alias: "embedded", required: false }] }] } });
604
+
605
+ const UI_ENTITY_CARD_HOST = new InjectionToken('UI_ENTITY_CARD_HOST');
606
+
607
+ /**
608
+ * Generated bundle index. Do not edit.
609
+ */
610
+
611
+ export { CARD_ACTION_COMPLETED, CARD_ACTION_FINISHED, CARD_ACTION_REQUESTED, CARD_ACTION_STARTED, CardComponent, CardSectionHeaderComponent, ENTITY_UPDATED, UI_CARD_TAB_REGISTRY, UI_ENTITY_CARD_HOST };
612
+ //# sourceMappingURL=maro-tech-card.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"maro-tech-card.mjs","sources":["../../../src/lib/card-events.ts","../../../src/lib/components/card-section-header.component.ts","../../../src/lib/tabs/html/html-tab.component.ts","../../../src/lib/tabs/html/html-tab.component.html","../../../src/lib/tabs/images/images-viewer/images-viewer.component.ts","../../../src/lib/tabs/images/images-viewer/images-viewer.component.html","../../../src/lib/tabs/details-grid/details-grid.component.ts","../../../src/lib/tabs/details-grid/details-grid.component.html","../../../src/lib/tabs/images/images.component.ts","../../../src/lib/tabs/images/images.component.html","../../../src/lib/card-tab-registry.token.ts","../../../src/lib/layouts/panels/ui-card-panels-layout.component.ts","../../../src/lib/layouts/panels/ui-card-panels-layout.component.html","../../../src/lib/layouts/tabs/ui-card-tabs-layout.component.ts","../../../src/lib/layouts/tabs/ui-card-tabs-layout.component.html","../../../src/lib/card.component.ts","../../../src/lib/card.component.html","../../../src/lib/card-host.token.ts","../../../src/maro-tech-card.ts"],"sourcesContent":["import type { UiEntityCardAction } from './card-host.token';\n\nexport interface CardActionContext {\n entity: string;\n recordId?: string;\n data?: Record<string, unknown> | null;\n}\n\nexport const CARD_ACTION_REQUESTED = 'card.action.requested';\nexport const CARD_ACTION_STARTED = 'card.action.started';\nexport const CARD_ACTION_COMPLETED = 'card.action.completed';\nexport const CARD_ACTION_FINISHED = 'card.action.finished';\nexport const ENTITY_UPDATED = 'entity.updated';\n\nexport interface CardActionRequestedEvent {\n action: UiEntityCardAction;\n context: CardActionContext;\n}\n\nexport interface CardActionLifecycleEvent {\n actionId: string;\n context: CardActionContext;\n}\n\nexport interface EntityUpdatedEvent {\n entity?: string;\n recordId?: string;\n source?: string;\n}\n","import { CommonModule } from '@angular/common';\nimport { Component, DestroyRef, inject, input, signal } from '@angular/core';\nimport { takeUntilDestroyed } from '@angular/core/rxjs-interop';\nimport { EventBus, UiButtonComponent } from '@maro-tech/core';\nimport type { UiEntityCardAction } from '../card-host.token';\nimport {\n CARD_ACTION_FINISHED,\n CARD_ACTION_REQUESTED,\n CardActionLifecycleEvent,\n CardActionRequestedEvent,\n} from '../card-events';\n\n@Component({\n selector: 'app-card-section-header',\n standalone: true,\n imports: [CommonModule, UiButtonComponent],\n template: `\n @if (variant() === 'tabs') {\n <header class=\"mb-4 flex justify-end\">\n <div class=\"flex flex-wrap gap-2 md:justify-end\">\n @for (action of actions(); track trackAction($index, action); let i = $index) {\n <ui-button\n [variant]=\"i === 0 ? 'primary' : 'ghost'\"\n [icon]=\"action.icon || ''\"\n size=\"sm\"\n [disabled]=\"isActionPending(action)\"\n (click)=\"onActionClick(action)\"\n >\n {{ isActionPending(action) ? 'Зачекайте...' : action.label }}\n </ui-button>\n }\n </div>\n </header>\n } @else {\n <header class=\"mb-4 flex flex-col gap-3 border-b border-slate-100 pb-3 md:flex-row md:items-center md:justify-between\">\n @if (title()) {\n <h3 class=\"text-lg font-semibold text-slate-900\">{{ title() }}</h3>\n } @else {\n <span></span>\n }\n\n @if (actions().length) {\n <div class=\"flex flex-wrap gap-2 md:justify-end\">\n @for (action of actions(); track trackAction($index, action); let i = $index) {\n <ui-button\n [variant]=\"i === 0 ? 'primary' : 'ghost'\"\n [icon]=\"action.icon || ''\"\n size=\"sm\"\n [disabled]=\"isActionPending(action)\"\n (click)=\"onActionClick(action)\"\n >\n {{ isActionPending(action) ? 'Зачекайте...' : action.label }}\n </ui-button>\n }\n </div>\n }\n </header>\n }\n `,\n})\nexport class CardSectionHeaderComponent {\n readonly variant = input<'tabs' | 'panels'>('panels');\n readonly title = input<string>('');\n readonly actions = input<UiEntityCardAction[]>([]);\n readonly entity = input.required<string>();\n readonly recordId = input<string>('');\n readonly data = input<Record<string, unknown> | null>(null);\n\n private readonly eventBus = inject(EventBus);\n private readonly destroyRef = inject(DestroyRef);\n private readonly pendingActionIdSignal = signal('');\n\n constructor() {\n this.eventBus.on<CardActionLifecycleEvent>(CARD_ACTION_FINISHED)\n .pipe(takeUntilDestroyed(this.destroyRef))\n .subscribe((event) => {\n if (this.matchesContext(event.context)) {\n this.pendingActionIdSignal.set('');\n }\n });\n }\n\n trackAction(_: number, action: UiEntityCardAction): string {\n return action.id || `${action.label}:${action.type}:${action.api || ''}:${action.formId || ''}`;\n }\n\n isActionPending(action: UiEntityCardAction): boolean {\n const pendingId = String(this.pendingActionIdSignal() || '').trim();\n const actionId = String(action.id || '').trim();\n return !!pendingId && !!actionId && pendingId === actionId;\n }\n\n onActionClick(action: UiEntityCardAction): void {\n const context = {\n entity: this.entity(),\n recordId: this.recordId() || undefined,\n data: this.data(),\n };\n this.pendingActionIdSignal.set(String(action.id || '').trim());\n this.eventBus.emit<CardActionRequestedEvent>(CARD_ACTION_REQUESTED, { action, context });\n }\n\n private matchesContext(context: { entity: string; recordId?: string }): boolean {\n return context.entity === this.entity()\n && (!context.recordId || context.recordId === this.recordId());\n }\n}\n","import { CommonModule } from '@angular/common';\nimport { Component, computed, inject, input } from '@angular/core';\nimport { EventBus } from '@maro-tech/core';\nimport { UiCardTabConfig } from '../../card.models';\nimport type { UiEntityCardAction } from '../../card-host.token';\nimport { CARD_ACTION_REQUESTED, CardActionRequestedEvent } from '../../card-events';\n\n@Component({\n selector: 'app-card-html-tab',\n standalone: true,\n imports: [CommonModule],\n templateUrl: './html-tab.component.html',\n})\nexport class CardHtmlTabComponent {\n readonly entity = input.required<string>();\n readonly recordId = input<string>('');\n readonly tab = input<UiCardTabConfig | null>(null);\n readonly data = input<Record<string, unknown> | null>(null);\n\n private readonly eventBus = inject(EventBus);\n\n readonly html = computed(() => this.tab()?.html || '');\n\n onClick(event: MouseEvent): void {\n this.dispatchFromEvent(event);\n }\n\n onKeydown(event: Event): void {\n this.dispatchFromEvent(event as KeyboardEvent);\n }\n\n private dispatchFromEvent(event: MouseEvent | KeyboardEvent): void {\n const target = event.target as HTMLElement | null;\n const actionElement = target?.closest?.('[class*=\"card-action-\"]') as HTMLElement | null;\n if (!actionElement) return;\n\n const action = this.resolveAction(actionElement);\n if (!action) return;\n\n event.preventDefault();\n event.stopPropagation();\n this.eventBus.emit<CardActionRequestedEvent>(CARD_ACTION_REQUESTED, {\n action,\n context: {\n entity: this.entity(),\n recordId: this.recordId() || undefined,\n data: this.data(),\n },\n });\n }\n\n private resolveAction(actionElement: HTMLElement | null): UiEntityCardAction | null {\n const tab = this.tab();\n const actions = Array.isArray(tab?.actions) ? tab.actions : [];\n if (!actions.length) return null;\n\n const actionId = Array.from(actionElement?.classList || [])\n .find((className) => className.startsWith('card-action-'))\n ?.slice('card-action-'.length)\n .trim();\n if (!actionId) return null;\n\n return actions.find((item) => String(item.id || '').trim() === actionId) || null;\n }\n}\n","<section\n [attr.data-card-tab-id]=\"tab()?.id || ''\"\n (click)=\"onClick($event)\"\n (keydown.enter)=\"onKeydown($event)\"\n (keydown.space)=\"onKeydown($event)\"\n>\n <div [innerHTML]=\"html()\"></div>\n</section>\n","import { CommonModule } from '@angular/common';\nimport { Component, HostListener, effect, input, output, signal } from '@angular/core';\n\n@Component({\n selector: 'app-images-viewer',\n standalone: true,\n imports: [CommonModule],\n templateUrl: './images-viewer.component.html',\n})\nexport class ImagesViewerComponent {\n readonly urls = input<string[]>([]);\n readonly initialIndex = input(0);\n readonly currentIndex = signal(0);\n\n readonly closed = output<void>();\n\n constructor() {\n effect(() => {\n const urls = this.urls();\n if (!urls.length) {\n this.currentIndex.set(0);\n return;\n }\n\n const maxIndex = urls.length - 1;\n const startIndex = Math.max(0, Math.min(this.initialIndex(), maxIndex));\n this.currentIndex.set(startIndex);\n });\n }\n\n @HostListener('document:keydown.escape')\n onEscape(): void {\n this.closed.emit();\n }\n\n @HostListener('document:keydown.arrowleft')\n onArrowLeft(): void {\n this.showPreviousImage();\n }\n\n @HostListener('document:keydown.arrowright')\n onArrowRight(): void {\n this.showNextImage();\n }\n\n closeViewer(): void {\n this.closed.emit();\n }\n\n showPreviousImage(): void {\n const total = this.urls().length;\n if (total <= 1) return;\n this.currentIndex.set((this.currentIndex() - 1 + total) % total);\n }\n\n showNextImage(): void {\n const total = this.urls().length;\n if (total <= 1) return;\n this.currentIndex.set((this.currentIndex() + 1) % total);\n }\n\n get imageViewerTransform(): string {\n return `translate3d(-${this.currentIndex() * 100}%, 0, 0)`;\n }\n\n get imageViewerTransitionStyle(): string {\n return 'transform 320ms cubic-bezier(0.22, 1, 0.36, 1)';\n }\n\n getThumbnailUrl(url: string): string {\n const match = String(url || '').match(/^(.*\\/api\\/files\\/[^/?#]+)(?:\\?.*)?$/i);\n return match ? `${match[1]}/preview?scale=25` : url;\n }\n}\n","<div class=\"fixed inset-0 z-[2300] flex items-center justify-center bg-black/80 p-3 md:p-4\" role=\"dialog\" aria-modal=\"true\" aria-label=\"Перегляд фото\">\n <button type=\"button\" class=\"absolute inset-0\" aria-label=\"Закрити перегляд\" (click)=\"closeViewer()\"></button>\n <div class=\"relative z-[1] flex h-full w-full max-w-[1500px] flex-col items-center justify-center gap-4\" (click)=\"$event.stopPropagation()\">\n <button\n type=\"button\"\n class=\"absolute right-2 top-2 z-20 inline-flex h-11 w-11 items-center justify-center rounded-full bg-white/90 text-slate-900 shadow-[0_12px_28px_rgba(15,23,42,0.22)] transition hover:bg-white md:right-3 md:top-3\"\n aria-label=\"Закрити\"\n (click)=\"closeViewer()\">\n ✕\n </button>\n\n <div class=\"relative flex min-h-0 w-full flex-1 items-center justify-center overflow-hidden rounded-[18px]\">\n <div class=\"h-full w-full overflow-hidden rounded-[18px]\">\n <div\n class=\"flex h-full w-full\"\n style=\"will-change: transform; backface-visibility: hidden;\"\n [style.transition]=\"imageViewerTransitionStyle\"\n [style.transform]=\"imageViewerTransform\">\n @for (url of urls(); track $index; let idx = $index) {\n <div class=\"flex h-full w-full min-w-full shrink-0 items-center justify-center overflow-hidden\">\n <img\n [src]=\"idx === currentIndex() ? url : getThumbnailUrl(url)\"\n [attr.alt]=\"'Фото ' + (idx + 1)\"\n [attr.loading]=\"idx === currentIndex() ? 'eager' : 'lazy'\"\n decoding=\"async\"\n class=\"block max-h-[74vh] max-w-[96vw] object-contain md:max-h-[78vh]\">\n </div>\n }\n </div>\n </div>\n\n @if (urls().length > 1) {\n <button type=\"button\" class=\"absolute left-0 top-0 z-10 flex h-full w-1/4 min-w-12 items-center justify-start pl-2 md:pl-3\" aria-label=\"Попереднє фото\" (click)=\"showPreviousImage()\">\n <span class=\"inline-flex h-11 w-11 items-center justify-center rounded-full bg-white/90 text-slate-900 shadow-[0_12px_28px_rgba(15,23,42,0.22)] transition hover:bg-white\">‹</span>\n </button>\n <button type=\"button\" class=\"absolute right-0 top-0 z-10 flex h-full w-1/4 min-w-12 items-center justify-end pr-2 md:pr-3\" aria-label=\"Наступне фото\" (click)=\"showNextImage()\">\n <span class=\"inline-flex h-11 w-11 items-center justify-center rounded-full bg-white/90 text-slate-900 shadow-[0_12px_28px_rgba(15,23,42,0.22)] transition hover:bg-white\">›</span>\n </button>\n }\n </div>\n\n @if (urls().length > 1) {\n <div class=\"flex w-full flex-col items-center gap-3 pb-1 md:pb-2\">\n <div class=\"flex max-w-[92vw] flex-wrap justify-center gap-2 md:max-w-[80vw]\">\n @for (url of urls(); track url; let idx = $index) {\n <button\n type=\"button\"\n class=\"h-14 w-14 overflow-hidden rounded-xl border bg-white p-1 transition\"\n [class.border-slate-900]=\"idx === currentIndex()\"\n [class.shadow-[0_0_0_2px_rgba(15,23,42,0.16)]]=\"idx === currentIndex()\"\n [class.border-slate-300]=\"idx !== currentIndex()\"\n [attr.aria-label]=\"'Фото ' + (idx + 1)\"\n (click)=\"currentIndex.set(idx)\">\n <img class=\"block aspect-square w-full rounded-lg object-contain\" [src]=\"getThumbnailUrl(url)\" [attr.alt]=\"'Мініатюра ' + (idx + 1)\" loading=\"lazy\" decoding=\"async\">\n </button>\n }\n </div>\n <div class=\"rounded-full bg-white/95 px-3 py-1.5 text-xs font-semibold text-slate-900 shadow-[0_12px_28px_rgba(15,23,42,0.2)]\">\n {{ currentIndex() + 1 }} / {{ urls().length }}\n </div>\n </div>\n }\n </div>\n</div>\n","import { CommonModule } from '@angular/common';\nimport { Component, computed, input, signal } from '@angular/core';\n\nimport { UiCardTabConfig, UiCardTabField } from '../../card.models';\nimport { ImagesViewerComponent } from '../images/images-viewer/images-viewer.component';\n\ninterface UiDetailsGridDisplayField extends UiCardTabField {\n displayValue: string;\n imageSrc?: string | null;\n}\n\n@Component({\n selector: 'app-details-grid',\n standalone: true,\n imports: [CommonModule, ImagesViewerComponent],\n templateUrl: './details-grid.component.html',\n})\nexport class DetailsGridComponent {\n readonly tab = input<UiCardTabConfig | null>(null);\n readonly hasExplicitColumns = computed(() => this.resolveFields().some((field) => this.isValidColumnSpan(field.col)));\n readonly displayFields = computed<UiDetailsGridDisplayField[]>(() =>\n this.resolveFields().map((field) => ({\n ...field,\n displayValue: this.formatFieldValue(field),\n imageSrc: this.getImageSrc(field),\n }))\n );\n readonly selectedImageSrc = signal<string | null>(null);\n\n openViewer(imageSrc: string): void {\n this.selectedImageSrc.set(imageSrc);\n }\n\n closeViewer(): void {\n this.selectedImageSrc.set(null);\n }\n\n private resolveFields(): UiCardTabField[] {\n const value = this.tab()?.props?.['fields'];\n return Array.isArray(value) ? (value as UiCardTabField[]) : [];\n }\n\n getFieldColumnClass(field: UiCardTabField): string {\n if (!this.isValidColumnSpan(field.col)) return '';\n const span = Math.min(12, Math.trunc(Number(field.col)));\n const classes: Record<number, string> = {\n 1: 'sm:col-span-1',\n 2: 'sm:col-span-2',\n 3: 'sm:col-span-3',\n 4: 'sm:col-span-4',\n 5: 'sm:col-span-5',\n 6: 'sm:col-span-6',\n 7: 'sm:col-span-7',\n 8: 'sm:col-span-8',\n 9: 'sm:col-span-9',\n 10: 'sm:col-span-10',\n 11: 'sm:col-span-11',\n 12: 'sm:col-span-12',\n };\n return classes[span] || '';\n }\n\n private isValidColumnSpan(value: number | undefined): boolean {\n const span = Number(value);\n return Number.isFinite(span) && span > 0;\n }\n\n private formatFieldValue(field: UiCardTabField): string {\n const value = field.value;\n if (value == null) return '-';\n if (typeof value === 'boolean') return value ? 'Так' : 'Ні';\n const raw = String(value).trim();\n if (!raw) return '-';\n\n if (field.type === 'date' || field.type === 'datetime') {\n return this.formatDate(raw, field.type === 'datetime');\n }\n if (field.type === 'enum') {\n return this.formatEnum(raw, field.enumMap);\n }\n\n return raw;\n }\n\n private getImageSrc(field: UiCardTabField): string | null {\n if (field.type !== 'image') return null;\n\n const value = field.value;\n if (value == null) return null;\n\n const raw = String(value).trim();\n if (!raw) return null;\n if (/^https?:\\/\\//i.test(raw) || raw.startsWith('/')) {\n return raw;\n }\n\n const base = String(field.imageBaseUrl || '/api/files').trim().replace(/\\/+$/, '');\n return `${base}/${encodeURIComponent(raw)}`;\n }\n\n private formatEnum(raw: string, enumMap?: Record<string, string>): string {\n if (enumMap?.[raw]) {\n return enumMap[raw];\n }\n if (raw === 'true') return 'Так';\n if (raw === 'false') return 'Ні';\n return raw;\n }\n\n private formatDate(value: string, withTime: boolean): string {\n const date = new Date(value);\n if (Number.isNaN(date.getTime())) {\n return value;\n }\n\n return new Intl.DateTimeFormat('uk-UA', withTime\n ? { day: '2-digit', month: '2-digit', year: 'numeric', hour: '2-digit', minute: '2-digit' }\n : { day: '2-digit', month: '2-digit', year: 'numeric' }).format(date);\n }\n}\n","<div class=\"grid gap-4\" [class.grid-cols-1]=\"!hasExplicitColumns()\" [class.sm:grid-cols-3]=\"!hasExplicitColumns()\" [class.sm:grid-cols-12]=\"hasExplicitColumns()\">\n @for (field of displayFields(); track $index) {\n <div class=\"rounded-2xl border border-slate-100 bg-slate-200 p-4\" [ngClass]=\"getFieldColumnClass(field)\" [class.flex]=\"field.type === 'image'\" [class.flex-col]=\"field.type === 'image'\" [class.min-h-64]=\"field.type === 'image'\">\n <p class=\"text-xs uppercase tracking-[0.18em] text-slate-500\">{{ field.label }}</p>\n @if (field.type === 'image') {\n @if (field.imageSrc) {\n <button type=\"button\" class=\"mt-2 block flex-1 cursor-zoom-in overflow-hidden rounded-xl border border-slate-200 bg-white\" [attr.aria-label]=\"'Переглянути ' + field.label\" (click)=\"openViewer(field.imageSrc)\">\n <img [src]=\"field.imageSrc\" [alt]=\"field.label\" class=\"h-full w-full object-cover\" />\n </button>\n } @else {\n <p class=\"mt-2 text-base font-semibold text-slate-900\">-</p>\n }\n } @else {\n <p class=\"mt-2 text-base font-semibold text-slate-900\">{{ field.displayValue ?? '-' }}</p>\n }\n </div>\n }\n</div>\n\n@if (selectedImageSrc(); as imageSrc) {\n <app-images-viewer [urls]=\"[imageSrc]\" (closed)=\"closeViewer()\" />\n}\n","import { CommonModule } from '@angular/common';\nimport { Component, computed, input, signal } from '@angular/core';\nimport { UiCardTabConfig } from '../../card.models';\nimport { ImagesViewerComponent } from './images-viewer/images-viewer.component';\n\n@Component({\n selector: 'app-images',\n standalone: true,\n imports: [CommonModule, ImagesViewerComponent],\n templateUrl: './images.component.html',\n})\nexport class ImagesComponent {\n readonly tab = input<UiCardTabConfig | null>(null);\n readonly data = input<Record<string, unknown> | null>(null);\n\n readonly isViewerOpen = signal(false);\n readonly viewerStartIndex = signal(0);\n\n readonly resolvedImageIds = computed(() => {\n const tab = this.tab();\n const props = tab?.props || {};\n const explicit = this.normalizeIds(props['imageIds']);\n if (explicit.length) return explicit;\n\n const key = String(props['images'] || 'images').trim();\n const value = key ? this.data()?.[key] : null;\n return this.normalizeIds(value);\n });\n\n readonly resolvedImageUrls = computed(() => {\n const tab = this.tab();\n const props = tab?.props || {};\n const explicit = Array.isArray(props['imageUrls'])\n ? props['imageUrls'].filter((item): item is string => typeof item === 'string')\n : [];\n if (explicit.length) return explicit;\n return this.resolvedImageIds().map((id) => this.resolveImageUrl(id)).filter((item): item is string => !!item);\n });\n\n readonly resolvedImagePreviewUrls = computed(() => this.resolvedImageUrls().map((url) => this.resolvePreviewUrl(url)));\n\n readonly resolvedImageMaxHeight = computed(() => {\n const value = Number(this.tab()?.props?.['maxHeight']);\n return Number.isFinite(value) && value > 0 ? value : 400;\n });\n\n openViewer(index: number): void {\n this.viewerStartIndex.set(Math.max(0, index));\n if (this.resolvedImageUrls().length) this.isViewerOpen.set(true);\n }\n\n closeViewer(): void {\n this.isViewerOpen.set(false);\n }\n\n private resolveImageUrl(imageIdOrUrl: string): string {\n const value = String(imageIdOrUrl || '').trim();\n if (!value) {\n return '';\n }\n if (value.startsWith('http://') || value.startsWith('https://') || value.startsWith('data:') || value.startsWith('/')) {\n return value;\n }\n\n const base = String(this.tab()?.props?.['imageBaseUrl'] || '/api/files').trim().replace(/\\/+$/, '');\n return `${base}/${encodeURIComponent(value)}`;\n }\n\n private resolvePreviewUrl(url: string): string {\n const match = url.match(/^(.*\\/api\\/files\\/[^/?#]+)(?:\\?.*)?$/i);\n return match ? `${match[1]}/preview?scale=25` : url;\n }\n\n private normalizeIds(value: unknown): string[] {\n if (!Array.isArray(value)) {\n return [];\n }\n\n return Array.from(new Set(\n value\n .map((item) => String(item || '').trim())\n .filter(Boolean)\n ));\n }\n}\n","@if (resolvedImageUrls().length === 0) {\n <p class=\"text-sm text-slate-500\">Фото відсутні</p>\n} @else {\n <div\n class=\"grid gap-3\"\n [ngClass]=\"resolvedImageUrls().length === 1 ? 'grid-cols-1' : 'sm:grid-cols-2 lg:grid-cols-4'\"\n >\n @for (image of resolvedImagePreviewUrls(); track image; let i = $index) {\n <button\n type=\"button\"\n class=\"group block overflow-hidden rounded-2xl border border-slate-200 bg-slate-50 text-left\"\n [attr.aria-label]=\"'Переглянути фото ' + (i + 1)\"\n (click)=\"openViewer(i)\"\n >\n <img\n [src]=\"image\"\n [alt]=\"'Фото ' + (i + 1)\"\n loading=\"lazy\"\n decoding=\"async\"\n class=\"w-full object-cover transition duration-200 group-hover:scale-[1.02]\"\n [style.max-height.px]=\"resolvedImageMaxHeight()\"\n [ngClass]=\"resolvedImageUrls().length === 1 ? 'h-auto' : 'h-48'\"\n />\n </button>\n }\n </div>\n\n @if (isViewerOpen()) {\n <app-images-viewer\n [urls]=\"resolvedImageUrls()\"\n [initialIndex]=\"viewerStartIndex()\"\n (closed)=\"closeViewer()\"\n ></app-images-viewer>\n }\n}\n","import { InjectionToken, Type } from '@angular/core';\nimport { UiCardTabConfig } from './card.models';\n\nexport interface UiCardTabRegistration {\n key: string;\n component: Type<unknown>;\n resolveInputs?: (tab: UiCardTabConfig, data?: Record<string, unknown> | null) => Record<string, unknown>;\n}\n\nexport const UI_CARD_TAB_REGISTRY = new InjectionToken<UiCardTabRegistration[]>('UI_CARD_TAB_REGISTRY');\n","import { CommonModule } from '@angular/common';\nimport { Component, computed, forwardRef, inject, input, signal, Type } from '@angular/core';\n\nimport { CardComponent } from '../../card.component';\nimport { CardSectionHeaderComponent } from '../../components/card-section-header.component';\nimport { CardHtmlTabComponent } from '../../tabs/html/html-tab.component';\nimport { DetailsGridComponent } from '../../tabs/details-grid/details-grid.component';\nimport { ImagesComponent } from '../../tabs/images/images.component';\nimport type { UiEntityCardAction } from '../../card-host.token';\nimport { UiCardTabConfig, UiCardLayout } from '../../card.models';\nimport { UI_CARD_TAB_REGISTRY, UiCardTabRegistration } from '../../card-tab-registry.token';\n\ntype NestedCardConfig = {\n tabs: UiCardTabConfig[];\n layout: UiCardLayout;\n};\n\n@Component({\n selector: 'app-ui-card-panels-layout',\n standalone: true,\n imports: [CommonModule, forwardRef(() => CardComponent), CardSectionHeaderComponent, CardHtmlTabComponent, DetailsGridComponent, ImagesComponent],\n templateUrl: './ui-card-panels-layout.component.html',\n})\nexport class UiCardPanelsLayoutComponent {\n private readonly registry: UiCardTabRegistration[] = inject(UI_CARD_TAB_REGISTRY, { optional: true }) || [];\n\n readonly entity = input.required<string>();\n readonly recordId = input<string>('');\n readonly tabs = input<UiCardTabConfig[]>([]);\n readonly data = input<Record<string, unknown> | null>(null);\n readonly embedded = input(false);\n\n readonly firstTab = computed(() => this.tabs()[0] || null);\n\n getActions(tab: UiCardTabConfig): UiEntityCardAction[] {\n const value = tab.actions;\n return Array.isArray(value) ? value : [];\n }\n\n getVisibleActions(tab: UiCardTabConfig): UiEntityCardAction[] {\n return this.getActions(tab).filter((action) => !action.hidden);\n }\n\n shouldShowPanelHeader(tab: UiCardTabConfig): boolean {\n return !this.embedded() && !tab.html && tab.component !== 'html' && tab.header !== false;\n }\n\n getColSpanClass(tab: UiCardTabConfig): string {\n if (tab.col == null) return 'md:col-span-12';\n\n const raw = Number(tab.col);\n const span = !Number.isFinite(raw) || raw <= 0 ? 12 : Math.min(12, Math.trunc(raw));\n const classes: Record<number, string> = {\n 1: 'md:col-span-1',\n 2: 'md:col-span-2',\n 3: 'md:col-span-3',\n 4: 'md:col-span-4',\n 5: 'md:col-span-5',\n 6: 'md:col-span-6',\n 7: 'md:col-span-7',\n 8: 'md:col-span-8',\n 9: 'md:col-span-9',\n 10: 'md:col-span-10',\n 11: 'md:col-span-11',\n 12: 'md:col-span-12',\n };\n return classes[span] || 'md:col-span-12';\n }\n\n getNestedCardConfig(tab: UiCardTabConfig): NestedCardConfig | null {\n if (Array.isArray(tab.tabs) && tab.tabs.length) {\n return { tabs: tab.tabs, layout: tab.layout || 'panels' };\n }\n return null;\n }\n\n getNestedPanels(tab: UiCardTabConfig): UiCardTabConfig[] | null {\n return Array.isArray(tab.panels) && tab.panels.length ? tab.panels : null;\n }\n\n getRegisteredComponent(tab: UiCardTabConfig): Type<unknown> | null {\n const key = tab.component;\n if (!key || key === 'images' || key === 'detailsGrid' || key === 'html') return null;\n const found = this.registry.find((item) => item.key === key);\n return found?.component || null;\n }\n\n getRegisteredInputs(tab: UiCardTabConfig): Record<string, unknown> {\n const key = tab.component;\n if (!key) return { tab };\n const found: UiCardTabRegistration | undefined = this.registry.find((item) => item.key === key);\n if (!found?.resolveInputs) return { tab };\n return found.resolveInputs(tab, this.data());\n }\n}\n","@if (tabs().length) {\n @if (!embedded()) {\n <section class=\"space-y-4\">\n <ng-container *ngTemplateOutlet=\"panelGrid\"></ng-container>\n </section>\n } @else {\n <ng-container *ngTemplateOutlet=\"panelGrid\"></ng-container>\n }\n}\n\n<ng-template #panelGrid>\n <div class=\"grid grid-cols-1 gap-4 md:grid-cols-12\">\n @for (tab of tabs(); track tab.id) {\n <section\n [class.rounded-3xl]=\"!embedded()\"\n [class.border]=\"!embedded()\"\n [class.border-slate-200]=\"!embedded()\"\n [class.bg-white]=\"!embedded()\"\n [class.p-6]=\"!embedded()\"\n [class.min-w-0]=\"embedded()\"\n [ngClass]=\"getColSpanClass(tab)\"\n [attr.data-card-panel-id]=\"tab.id\"\n >\n @if (shouldShowPanelHeader(tab)) {\n <app-card-section-header\n [entity]=\"entity()\"\n [recordId]=\"recordId()\"\n [data]=\"data()\"\n [title]=\"tab.title\"\n [actions]=\"getVisibleActions(tab)\"\n ></app-card-section-header>\n }\n\n @if (getNestedCardConfig(tab); as nestedCard) {\n <app-card [entity]=\"entity()\" [recordId]=\"recordId()\" [tabs]=\"nestedCard.tabs\" [data]=\"data()\" [layout]=\"nestedCard.layout\"></app-card>\n } @else if (getNestedPanels(tab); as nestedPanels) {\n <app-card [entity]=\"entity()\" [recordId]=\"recordId()\" [tabs]=\"nestedPanels\" [data]=\"data()\" layout=\"panels\" [embedded]=\"true\"></app-card>\n } @else if (tab.component === 'detailsGrid') {\n <app-details-grid [tab]=\"tab\"></app-details-grid>\n } @else if (tab.component === 'images') {\n <app-images [tab]=\"tab\" [data]=\"data()\"></app-images>\n } @else if (tab.component === 'html') {\n <app-card-html-tab [tab]=\"tab\" [entity]=\"entity()\" [recordId]=\"recordId()\" [data]=\"data()\"></app-card-html-tab>\n } @else if (getRegisteredComponent(tab); as customComponent) {\n <ng-container *ngComponentOutlet=\"customComponent; inputs: getRegisteredInputs(tab)\"></ng-container>\n }\n </section>\n }\n </div>\n</ng-template>\n","import { CommonModule } from '@angular/common';\nimport { Component, computed, DestroyRef, forwardRef, inject, input, OnInit, signal, Type } from '@angular/core';\nimport { ActivatedRoute, Router } from '@angular/router';\nimport { takeUntilDestroyed } from '@angular/core/rxjs-interop';\n\nimport { CardComponent } from '../../card.component';\nimport { CardSectionHeaderComponent } from '../../components/card-section-header.component';\nimport { CardHtmlTabComponent } from '../../tabs/html/html-tab.component';\nimport { DetailsGridComponent } from '../../tabs/details-grid/details-grid.component';\nimport { ImagesComponent } from '../../tabs/images/images.component';\nimport type { UiEntityCardAction } from '../../card-host.token';\nimport { UiCardTabConfig } from '../../card.models';\nimport { UI_CARD_TAB_REGISTRY, UiCardTabRegistration } from '../../card-tab-registry.token';\n\n@Component({\n selector: 'app-ui-card-tabs-layout',\n standalone: true,\n imports: [CommonModule, forwardRef(() => CardComponent), CardSectionHeaderComponent, CardHtmlTabComponent, ImagesComponent, DetailsGridComponent],\n templateUrl: './ui-card-tabs-layout.component.html',\n})\nexport class UiCardTabsLayoutComponent implements OnInit {\n private readonly registry: UiCardTabRegistration[] = inject(UI_CARD_TAB_REGISTRY, { optional: true }) || [];\n private readonly destroyRef = inject(DestroyRef);\n private readonly route = inject(ActivatedRoute);\n private readonly router = inject(Router);\n\n readonly entity = input.required<string>();\n readonly recordId = input<string>('');\n readonly tabs = input<UiCardTabConfig[]>([]);\n readonly data = input<Record<string, unknown> | null>(null);\n readonly activeTabId = signal<string>('');\n\n ngOnInit(): void {\n this.route.queryParamMap.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((params) => {\n const tabId = params.get('tab');\n if (tabId) this.activeTabId.set(tabId);\n });\n }\n\n readonly activeTab = computed(() => {\n const items = this.tabs() || [];\n if (!items.length) return null;\n const currentId = this.activeTabId();\n return items.find((item) => item.id === currentId) || items[0];\n });\n\n setActiveTab(id: string): void {\n this.activeTabId.set(id);\n void this.router.navigate([], {\n relativeTo: this.route,\n queryParams: { tab: id },\n queryParamsHandling: 'merge',\n replaceUrl: true,\n });\n }\n\n getActions(tab: UiCardTabConfig): UiEntityCardAction[] {\n const value = tab.actions;\n return Array.isArray(value) ? value : [];\n }\n\n getVisibleActions(tab: UiCardTabConfig): UiEntityCardAction[] {\n return this.getActions(tab).filter((action) => !action.hidden);\n }\n\n getColSpan(tab: UiCardTabConfig): number {\n if (tab.col == null) return 12;\n\n const raw = Number(tab.col);\n if (!Number.isFinite(raw) || raw <= 0) return 12;\n return Math.min(12, Math.trunc(raw));\n }\n\n getRegisteredComponent(tab: UiCardTabConfig): Type<unknown> | null {\n const key = tab.component;\n if (!key || key === 'images' || key === 'detailsGrid' || key === 'html') return null;\n const found = this.registry.find((item) => item.key === key);\n return found?.component || null;\n }\n\n getRegisteredInputs(tab: UiCardTabConfig): Record<string, unknown> {\n const key = tab.component;\n if (!key) return { tab };\n const found: UiCardTabRegistration | undefined = this.registry.find((item) => item.key === key);\n if (!found?.resolveInputs) return { tab };\n return found.resolveInputs(tab, this.data());\n }\n}\n","@if (tabs().length) {\n <section class=\"space-y-4\">\n <nav class=\"flex flex-wrap gap-2\" aria-label=\"Card tabs\">\n @for (tab of tabs(); track tab.id) {\n <button\n type=\"button\"\n class=\"rounded-xl border px-4 py-2 text-sm font-medium transition\"\n [class.border-slate-900]=\"activeTab()?.id === tab.id\"\n [class.bg-slate-900]=\"activeTab()?.id === tab.id\"\n [class.text-white]=\"activeTab()?.id === tab.id\"\n [class.border-slate-200]=\"activeTab()?.id !== tab.id\"\n [class.bg-white]=\"activeTab()?.id !== tab.id\"\n [class.text-slate-700]=\"activeTab()?.id !== tab.id\"\n (click)=\"setActiveTab(tab.id)\"\n >\n {{ tab.title }}\n </button>\n }\n </nav>\n\n @if (activeTab(); as tab) {\n <section\n [attr.data-card-tab-id]=\"tab.id\"\n >\n @if (getVisibleActions(tab).length) {\n <app-card-section-header\n variant=\"tabs\"\n [entity]=\"entity()\"\n [recordId]=\"recordId()\"\n [data]=\"data()\"\n [actions]=\"getVisibleActions(tab)\"\n ></app-card-section-header>\n }\n\n @if (tab.tabs?.length) {\n <app-card [entity]=\"entity()\" [recordId]=\"recordId()\" [tabs]=\"tab.tabs || []\" [data]=\"data()\" [layout]=\"tab.layout || 'panels'\"></app-card>\n } @else if (tab.component === 'images') {\n <app-images\n [tab]=\"tab\"\n [data]=\"data()\"\n ></app-images>\n } @else if (tab.component === 'detailsGrid') {\n <app-details-grid [tab]=\"tab\"></app-details-grid>\n } @else if (tab.component === 'html') {\n <app-card-html-tab [tab]=\"tab\" [entity]=\"entity()\" [recordId]=\"recordId()\" [data]=\"data()\"></app-card-html-tab>\n } @else if (getRegisteredComponent(tab); as customComponent) {\n <ng-container *ngComponentOutlet=\"customComponent; inputs: getRegisteredInputs(tab)\"></ng-container>\n }\n </section>\n }\n </section>\n}\n","import { CommonModule } from '@angular/common';\nimport { Component, forwardRef, input } from '@angular/core';\nimport { UiCardPanelsLayoutComponent } from './layouts/panels/ui-card-panels-layout.component';\nimport { UiCardTabsLayoutComponent } from './layouts/tabs/ui-card-tabs-layout.component';\nimport { UiCardLayoutInput, UiCardTabConfig } from './card.models';\n\n@Component({\n selector: 'app-card',\n standalone: true,\n imports: [CommonModule, forwardRef(() => UiCardTabsLayoutComponent), forwardRef(() => UiCardPanelsLayoutComponent)],\n templateUrl: './card.component.html',\n})\nexport class CardComponent {\n readonly entity = input.required<string>();\n readonly recordId = input<string>('');\n readonly tabs = input<UiCardTabConfig[]>([]);\n readonly data = input<Record<string, unknown> | null>(null);\n readonly layout = input<UiCardLayoutInput>('panels');\n readonly embedded = input(false);\n\n}\n","@if (layout() === 'panels') {\n <app-ui-card-panels-layout\n [entity]=\"entity()\"\n [recordId]=\"recordId()\"\n [tabs]=\"tabs()\"\n [data]=\"data()\"\n [embedded]=\"embedded()\"\n ></app-ui-card-panels-layout>\n} @else {\n <app-ui-card-tabs-layout\n [entity]=\"entity()\"\n [recordId]=\"recordId()\"\n [tabs]=\"tabs()\"\n [data]=\"data()\"\n ></app-ui-card-tabs-layout>\n}\n","import { InjectionToken } from '@angular/core';\nimport { Observable } from 'rxjs';\nimport type { UiCardLayoutInput } from './card.models';\n\nexport type ApiPluginMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';\nexport interface ApiPluginConfig {\n api: string;\n method?: ApiPluginMethod;\n payload?: Record<string, unknown>;\n [key: string]: unknown;\n}\n\nexport interface UiEntityCardAction {\n id?: string;\n label: string;\n type: string;\n icon?: string;\n hidden?: boolean;\n html?: string;\n component?: string;\n formId?: string;\n size?: 'sm' | 'md' | 'lg';\n api?: string;\n template?: string;\n method?: ApiPluginMethod;\n title?: string;\n text?: string;\n submit?: boolean | string;\n payload?: Record<string, unknown>;\n redirectEntity?: string;\n condition?: string;\n}\n\nexport interface UiEntityCardTab {\n id: string;\n title: string;\n col?: number;\n layout?: UiCardLayoutInput;\n component?: string;\n html?: string;\n tabs?: UiEntityCardTab[];\n panels?: UiEntityCardTab[];\n actions?: UiEntityCardAction[];\n props?: Record<string, unknown> & {\n imageUrls?: string[];\n imageIds?: string[];\n images?: string;\n recordId?: string;\n recordIdKey?: string;\n imageBaseUrl?: string;\n fields?: Array<Record<string, unknown>>;\n warehouseId?: number | string;\n [key: string]: unknown;\n };\n}\n\nexport interface UiEntityCardResponse {\n meta?: Record<string, unknown>;\n title: string;\n layout?: UiCardLayoutInput;\n actions?: UiEntityCardAction[];\n viewActions?: {\n read: boolean;\n create: boolean;\n edit: boolean;\n delete: boolean;\n hasFormTemplate: boolean;\n };\n tabs?: UiEntityCardTab[];\n data?: Record<string, unknown>;\n}\n\nexport interface UiEntityCardHost {\n getCardView(entity: string, id: string): Observable<UiEntityCardResponse>;\n getFormView(entity: string, id: string): Observable<Record<string, unknown>>;\n callApi(config: ApiPluginConfig): Observable<unknown>;\n deleteCard(entity: string, id: string): Observable<void>;\n notify(message: string, variant: 'success' | 'error' | 'warning', durationMs?: number): void;\n goBack(entity: string, queryParams?: Record<string, unknown>, returnUrl?: string): void;\n goToEdit(entity: string, id: string): void;\n}\n\nexport const UI_ENTITY_CARD_HOST = new InjectionToken<UiEntityCardHost>('UI_ENTITY_CARD_HOST');\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":";;;;;;;;AAQO,MAAM,qBAAqB,GAAG;AAC9B,MAAM,mBAAmB,GAAG;AAC5B,MAAM,qBAAqB,GAAG;AAC9B,MAAM,oBAAoB,GAAG;AAC7B,MAAM,cAAc,GAAG;;MCgDjB,0BAA0B,CAAA;AAC5B,IAAA,OAAO,GAAG,KAAK,CAAoB,QAAQ,8EAAC;AAC5C,IAAA,KAAK,GAAG,KAAK,CAAS,EAAE,4EAAC;AACzB,IAAA,OAAO,GAAG,KAAK,CAAuB,EAAE,8EAAC;AACzC,IAAA,MAAM,GAAG,KAAK,CAAC,QAAQ,4EAAU;AACjC,IAAA,QAAQ,GAAG,KAAK,CAAS,EAAE,+EAAC;AAC5B,IAAA,IAAI,GAAG,KAAK,CAAiC,IAAI,2EAAC;AAE1C,IAAA,QAAQ,GAAG,MAAM,CAAC,QAAQ,CAAC;AAC3B,IAAA,UAAU,GAAG,MAAM,CAAC,UAAU,CAAC;AAC/B,IAAA,qBAAqB,GAAG,MAAM,CAAC,EAAE,4FAAC;AAEnD,IAAA,WAAA,GAAA;AACE,QAAA,IAAI,CAAC,QAAQ,CAAC,EAAE,CAA2B,oBAAoB;AAC5D,aAAA,IAAI,CAAC,kBAAkB,CAAC,IAAI,CAAC,UAAU,CAAC;AACxC,aAAA,SAAS,CAAC,CAAC,KAAK,KAAI;YACnB,IAAI,IAAI,CAAC,cAAc,CAAC,KAAK,CAAC,OAAO,CAAC,EAAE;AACtC,gBAAA,IAAI,CAAC,qBAAqB,CAAC,GAAG,CAAC,EAAE,CAAC;YACpC;AACF,QAAA,CAAC,CAAC;IACN;IAEA,WAAW,CAAC,CAAS,EAAE,MAA0B,EAAA;QAC/C,OAAO,MAAM,CAAC,EAAE,IAAI,CAAA,EAAG,MAAM,CAAC,KAAK,CAAA,CAAA,EAAI,MAAM,CAAC,IAAI,IAAI,MAAM,CAAC,GAAG,IAAI,EAAE,CAAA,CAAA,EAAI,MAAM,CAAC,MAAM,IAAI,EAAE,CAAA,CAAE;IACjG;AAEA,IAAA,eAAe,CAAC,MAA0B,EAAA;AACxC,QAAA,MAAM,SAAS,GAAG,MAAM,CAAC,IAAI,CAAC,qBAAqB,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE;AACnE,QAAA,MAAM,QAAQ,GAAG,MAAM,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE;QAC/C,OAAO,CAAC,CAAC,SAAS,IAAI,CAAC,CAAC,QAAQ,IAAI,SAAS,KAAK,QAAQ;IAC5D;AAEA,IAAA,aAAa,CAAC,MAA0B,EAAA;AACtC,QAAA,MAAM,OAAO,GAAG;AACd,YAAA,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE;AACrB,YAAA,QAAQ,EAAE,IAAI,CAAC,QAAQ,EAAE,IAAI,SAAS;AACtC,YAAA,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE;SAClB;AACD,QAAA,IAAI,CAAC,qBAAqB,CAAC,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;AAC9D,QAAA,IAAI,CAAC,QAAQ,CAAC,IAAI,CAA2B,qBAAqB,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC;IAC1F;AAEQ,IAAA,cAAc,CAAC,OAA8C,EAAA;AACnE,QAAA,OAAO,OAAO,CAAC,MAAM,KAAK,IAAI,CAAC,MAAM;AAChC,gBAAC,CAAC,OAAO,CAAC,QAAQ,IAAI,OAAO,CAAC,QAAQ,KAAK,IAAI,CAAC,QAAQ,EAAE,CAAC;IAClE;wGA7CW,0BAA0B,EAAA,IAAA,EAAA,EAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,SAAA,EAAA,CAAA;AAA1B,IAAA,OAAA,IAAA,GAAA,EAAA,CAAA,oBAAA,CAAA,EAAA,UAAA,EAAA,QAAA,EAAA,OAAA,EAAA,SAAA,EAAA,IAAA,EAAA,0BAA0B,EAAA,YAAA,EAAA,IAAA,EAAA,QAAA,EAAA,yBAAA,EAAA,MAAA,EAAA,EAAA,OAAA,EAAA,EAAA,iBAAA,EAAA,SAAA,EAAA,UAAA,EAAA,SAAA,EAAA,QAAA,EAAA,IAAA,EAAA,UAAA,EAAA,KAAA,EAAA,iBAAA,EAAA,IAAA,EAAA,EAAA,KAAA,EAAA,EAAA,iBAAA,EAAA,OAAA,EAAA,UAAA,EAAA,OAAA,EAAA,QAAA,EAAA,IAAA,EAAA,UAAA,EAAA,KAAA,EAAA,iBAAA,EAAA,IAAA,EAAA,EAAA,OAAA,EAAA,EAAA,iBAAA,EAAA,SAAA,EAAA,UAAA,EAAA,SAAA,EAAA,QAAA,EAAA,IAAA,EAAA,UAAA,EAAA,KAAA,EAAA,iBAAA,EAAA,IAAA,EAAA,EAAA,MAAA,EAAA,EAAA,iBAAA,EAAA,QAAA,EAAA,UAAA,EAAA,QAAA,EAAA,QAAA,EAAA,IAAA,EAAA,UAAA,EAAA,IAAA,EAAA,iBAAA,EAAA,IAAA,EAAA,EAAA,QAAA,EAAA,EAAA,iBAAA,EAAA,UAAA,EAAA,UAAA,EAAA,UAAA,EAAA,QAAA,EAAA,IAAA,EAAA,UAAA,EAAA,KAAA,EAAA,iBAAA,EAAA,IAAA,EAAA,EAAA,IAAA,EAAA,EAAA,iBAAA,EAAA,MAAA,EAAA,UAAA,EAAA,MAAA,EAAA,QAAA,EAAA,IAAA,EAAA,UAAA,EAAA,KAAA,EAAA,iBAAA,EAAA,IAAA,EAAA,EAAA,EAAA,QAAA,EAAA,EAAA,EAAA,QAAA,EA5C3B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0CT,EAAA,QAAA,EAAA,IAAA,EAAA,YAAA,EAAA,CAAA,EAAA,IAAA,EAAA,UAAA,EAAA,IAAA,EA3CS,YAAY,+BAAE,iBAAiB,EAAA,QAAA,EAAA,WAAA,EAAA,MAAA,EAAA,CAAA,SAAA,EAAA,MAAA,EAAA,UAAA,EAAA,WAAA,EAAA,MAAA,EAAA,YAAA,EAAA,MAAA,EAAA,UAAA,EAAA,WAAA,CAAA,EAAA,CAAA,EAAA,CAAA;;4FA6C9B,0BAA0B,EAAA,UAAA,EAAA,CAAA;kBAhDtC,SAAS;AAAC,YAAA,IAAA,EAAA,CAAA;AACT,oBAAA,QAAQ,EAAE,yBAAyB;AACnC,oBAAA,UAAU,EAAE,IAAI;AAChB,oBAAA,OAAO,EAAE,CAAC,YAAY,EAAE,iBAAiB,CAAC;AAC1C,oBAAA,QAAQ,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA0CT,EAAA,CAAA;AACF,iBAAA;;;MC9CY,oBAAoB,CAAA;AACtB,IAAA,MAAM,GAAG,KAAK,CAAC,QAAQ,4EAAU;AACjC,IAAA,QAAQ,GAAG,KAAK,CAAS,EAAE,+EAAC;AAC5B,IAAA,GAAG,GAAG,KAAK,CAAyB,IAAI,0EAAC;AACzC,IAAA,IAAI,GAAG,KAAK,CAAiC,IAAI,2EAAC;AAE1C,IAAA,QAAQ,GAAG,MAAM,CAAC,QAAQ,CAAC;AAEnC,IAAA,IAAI,GAAG,QAAQ,CAAC,MAAM,IAAI,CAAC,GAAG,EAAE,EAAE,IAAI,IAAI,EAAE,2EAAC;AAEtD,IAAA,OAAO,CAAC,KAAiB,EAAA;AACvB,QAAA,IAAI,CAAC,iBAAiB,CAAC,KAAK,CAAC;IAC/B;AAEA,IAAA,SAAS,CAAC,KAAY,EAAA;AACpB,QAAA,IAAI,CAAC,iBAAiB,CAAC,KAAsB,CAAC;IAChD;AAEQ,IAAA,iBAAiB,CAAC,KAAiC,EAAA;AACzD,QAAA,MAAM,MAAM,GAAG,KAAK,CAAC,MAA4B;QACjD,MAAM,aAAa,GAAG,MAAM,EAAE,OAAO,GAAG,yBAAyB,CAAuB;AACxF,QAAA,IAAI,CAAC,aAAa;YAAE;QAEpB,MAAM,MAAM,GAAG,IAAI,CAAC,aAAa,CAAC,aAAa,CAAC;AAChD,QAAA,IAAI,CAAC,MAAM;YAAE;QAEb,KAAK,CAAC,cAAc,EAAE;QACtB,KAAK,CAAC,eAAe,EAAE;AACvB,QAAA,IAAI,CAAC,QAAQ,CAAC,IAAI,CAA2B,qBAAqB,EAAE;YAClE,MAAM;AACN,YAAA,OAAO,EAAE;AACP,gBAAA,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE;AACrB,gBAAA,QAAQ,EAAE,IAAI,CAAC,QAAQ,EAAE,IAAI,SAAS;AACtC,gBAAA,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE;AAClB,aAAA;AACF,SAAA,CAAC;IACJ;AAEQ,IAAA,aAAa,CAAC,aAAiC,EAAA;AACrD,QAAA,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE;QACtB,MAAM,OAAO,GAAG,KAAK,CAAC,OAAO,CAAC,GAAG,EAAE,OAAO,CAAC,GAAG,GAAG,CAAC,OAAO,GAAG,EAAE;QAC9D,IAAI,CAAC,OAAO,CAAC,MAAM;AAAE,YAAA,OAAO,IAAI;QAEhC,MAAM,QAAQ,GAAG,KAAK,CAAC,IAAI,CAAC,aAAa,EAAE,SAAS,IAAI,EAAE;AACvD,aAAA,IAAI,CAAC,CAAC,SAAS,KAAK,SAAS,CAAC,UAAU,CAAC,cAAc,CAAC;AACzD,cAAE,KAAK,CAAC,cAAc,CAAC,MAAM;AAC5B,aAAA,IAAI,EAAE;AACT,QAAA,IAAI,CAAC,QAAQ;AAAE,YAAA,OAAO,IAAI;QAE1B,OAAO,OAAO,CAAC,IAAI,CAAC,CAAC,IAAI,KAAK,MAAM,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,KAAK,QAAQ,CAAC,IAAI,IAAI;IAClF;wGAlDW,oBAAoB,EAAA,IAAA,EAAA,EAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,SAAA,EAAA,CAAA;4FAApB,oBAAoB,EAAA,YAAA,EAAA,IAAA,EAAA,QAAA,EAAA,mBAAA,EAAA,MAAA,EAAA,EAAA,MAAA,EAAA,EAAA,iBAAA,EAAA,QAAA,EAAA,UAAA,EAAA,QAAA,EAAA,QAAA,EAAA,IAAA,EAAA,UAAA,EAAA,IAAA,EAAA,iBAAA,EAAA,IAAA,EAAA,EAAA,QAAA,EAAA,EAAA,iBAAA,EAAA,UAAA,EAAA,UAAA,EAAA,UAAA,EAAA,QAAA,EAAA,IAAA,EAAA,UAAA,EAAA,KAAA,EAAA,iBAAA,EAAA,IAAA,EAAA,EAAA,GAAA,EAAA,EAAA,iBAAA,EAAA,KAAA,EAAA,UAAA,EAAA,KAAA,EAAA,QAAA,EAAA,IAAA,EAAA,UAAA,EAAA,KAAA,EAAA,iBAAA,EAAA,IAAA,EAAA,EAAA,IAAA,EAAA,EAAA,iBAAA,EAAA,MAAA,EAAA,UAAA,EAAA,MAAA,EAAA,QAAA,EAAA,IAAA,EAAA,UAAA,EAAA,KAAA,EAAA,iBAAA,EAAA,IAAA,EAAA,EAAA,EAAA,QAAA,EAAA,EAAA,EAAA,QAAA,ECbjC,iOAQA,EAAA,YAAA,EAAA,CAAA,EAAA,IAAA,EAAA,UAAA,EAAA,IAAA,EDEY,YAAY,EAAA,CAAA,EAAA,CAAA;;4FAGX,oBAAoB,EAAA,UAAA,EAAA,CAAA;kBANhC,SAAS;AACE,YAAA,IAAA,EAAA,CAAA,EAAA,QAAA,EAAA,mBAAmB,EAAA,UAAA,EACjB,IAAI,EAAA,OAAA,EACP,CAAC,YAAY,CAAC,EAAA,QAAA,EAAA,iOAAA,EAAA;;;MEDZ,qBAAqB,CAAA;AACvB,IAAA,IAAI,GAAG,KAAK,CAAW,EAAE,2EAAC;AAC1B,IAAA,YAAY,GAAG,KAAK,CAAC,CAAC,mFAAC;AACvB,IAAA,YAAY,GAAG,MAAM,CAAC,CAAC,mFAAC;IAExB,MAAM,GAAG,MAAM,EAAQ;AAEhC,IAAA,WAAA,GAAA;QACE,MAAM,CAAC,MAAK;AACV,YAAA,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,EAAE;AACxB,YAAA,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE;AAChB,gBAAA,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,CAAC,CAAC;gBACxB;YACF;AAEA,YAAA,MAAM,QAAQ,GAAG,IAAI,CAAC,MAAM,GAAG,CAAC;YAChC,MAAM,UAAU,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,YAAY,EAAE,EAAE,QAAQ,CAAC,CAAC;AACvE,YAAA,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,UAAU,CAAC;AACnC,QAAA,CAAC,CAAC;IACJ;IAGA,QAAQ,GAAA;AACN,QAAA,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE;IACpB;IAGA,WAAW,GAAA;QACT,IAAI,CAAC,iBAAiB,EAAE;IAC1B;IAGA,YAAY,GAAA;QACV,IAAI,CAAC,aAAa,EAAE;IACtB;IAEA,WAAW,GAAA;AACT,QAAA,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE;IACpB;IAEA,iBAAiB,GAAA;QACf,MAAM,KAAK,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC,MAAM;QAChC,IAAI,KAAK,IAAI,CAAC;YAAE;AAChB,QAAA,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,YAAY,EAAE,GAAG,CAAC,GAAG,KAAK,IAAI,KAAK,CAAC;IAClE;IAEA,aAAa,GAAA;QACX,MAAM,KAAK,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC,MAAM;QAChC,IAAI,KAAK,IAAI,CAAC;YAAE;AAChB,QAAA,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,YAAY,EAAE,GAAG,CAAC,IAAI,KAAK,CAAC;IAC1D;AAEA,IAAA,IAAI,oBAAoB,GAAA;QACtB,OAAO,CAAA,aAAA,EAAgB,IAAI,CAAC,YAAY,EAAE,GAAG,GAAG,UAAU;IAC5D;AAEA,IAAA,IAAI,0BAA0B,GAAA;AAC5B,QAAA,OAAO,gDAAgD;IACzD;AAEA,IAAA,eAAe,CAAC,GAAW,EAAA;AACzB,QAAA,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,uCAAuC,CAAC;AAC9E,QAAA,OAAO,KAAK,GAAG,GAAG,KAAK,CAAC,CAAC,CAAC,CAAA,iBAAA,CAAmB,GAAG,GAAG;IACrD;wGA/DW,qBAAqB,EAAA,IAAA,EAAA,EAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,SAAA,EAAA,CAAA;4FAArB,qBAAqB,EAAA,YAAA,EAAA,IAAA,EAAA,QAAA,EAAA,mBAAA,EAAA,MAAA,EAAA,EAAA,IAAA,EAAA,EAAA,iBAAA,EAAA,MAAA,EAAA,UAAA,EAAA,MAAA,EAAA,QAAA,EAAA,IAAA,EAAA,UAAA,EAAA,KAAA,EAAA,iBAAA,EAAA,IAAA,EAAA,EAAA,YAAA,EAAA,EAAA,iBAAA,EAAA,cAAA,EAAA,UAAA,EAAA,cAAA,EAAA,QAAA,EAAA,IAAA,EAAA,UAAA,EAAA,KAAA,EAAA,iBAAA,EAAA,IAAA,EAAA,EAAA,EAAA,OAAA,EAAA,EAAA,MAAA,EAAA,QAAA,EAAA,EAAA,IAAA,EAAA,EAAA,SAAA,EAAA,EAAA,yBAAA,EAAA,YAAA,EAAA,4BAAA,EAAA,eAAA,EAAA,6BAAA,EAAA,gBAAA,EAAA,EAAA,EAAA,QAAA,EAAA,EAAA,EAAA,QAAA,ECTlC,utIAgEA,EAAA,YAAA,EAAA,CAAA,EAAA,IAAA,EAAA,UAAA,EAAA,IAAA,ED1DY,YAAY,EAAA,CAAA,EAAA,CAAA;;4FAGX,qBAAqB,EAAA,UAAA,EAAA,CAAA;kBANjC,SAAS;AACE,YAAA,IAAA,EAAA,CAAA,EAAA,QAAA,EAAA,mBAAmB,EAAA,UAAA,EACjB,IAAI,EAAA,OAAA,EACP,CAAC,YAAY,CAAC,EAAA,QAAA,EAAA,utIAAA,EAAA;;sBAwBtB,YAAY;uBAAC,yBAAyB;;sBAKtC,YAAY;uBAAC,4BAA4B;;sBAKzC,YAAY;uBAAC,6BAA6B;;;MEvBhC,oBAAoB,CAAA;AACtB,IAAA,GAAG,GAAG,KAAK,CAAyB,IAAI,0EAAC;AACzC,IAAA,kBAAkB,GAAG,QAAQ,CAAC,MAAM,IAAI,CAAC,aAAa,EAAE,CAAC,IAAI,CAAC,CAAC,KAAK,KAAK,IAAI,CAAC,iBAAiB,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,EAAA,IAAA,SAAA,GAAA,CAAA,EAAA,SAAA,EAAA,oBAAA,EAAA,CAAA,8BAAA,EAAA,CAAA,CAAC;AAC5G,IAAA,aAAa,GAAG,QAAQ,CAA8B,MAC7D,IAAI,CAAC,aAAa,EAAE,CAAC,GAAG,CAAC,CAAC,KAAK,MAAM;AACnC,QAAA,GAAG,KAAK;AACR,QAAA,YAAY,EAAE,IAAI,CAAC,gBAAgB,CAAC,KAAK,CAAC;AAC1C,QAAA,QAAQ,EAAE,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC;KAClC,CAAC,CAAC,oFACJ;AACQ,IAAA,gBAAgB,GAAG,MAAM,CAAgB,IAAI,uFAAC;AAEvD,IAAA,UAAU,CAAC,QAAgB,EAAA;AACzB,QAAA,IAAI,CAAC,gBAAgB,CAAC,GAAG,CAAC,QAAQ,CAAC;IACrC;IAEA,WAAW,GAAA;AACT,QAAA,IAAI,CAAC,gBAAgB,CAAC,GAAG,CAAC,IAAI,CAAC;IACjC;IAEQ,aAAa,GAAA;AACnB,QAAA,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,EAAE,EAAE,KAAK,GAAG,QAAQ,CAAC;AAC3C,QAAA,OAAO,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,GAAI,KAA0B,GAAG,EAAE;IAChE;AAEA,IAAA,mBAAmB,CAAC,KAAqB,EAAA;QACvC,IAAI,CAAC,IAAI,CAAC,iBAAiB,CAAC,KAAK,CAAC,GAAG,CAAC;AAAE,YAAA,OAAO,EAAE;QACjD,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC;AACxD,QAAA,MAAM,OAAO,GAA2B;AACtC,YAAA,CAAC,EAAE,eAAe;AAClB,YAAA,CAAC,EAAE,eAAe;AAClB,YAAA,CAAC,EAAE,eAAe;AAClB,YAAA,CAAC,EAAE,eAAe;AAClB,YAAA,CAAC,EAAE,eAAe;AAClB,YAAA,CAAC,EAAE,eAAe;AAClB,YAAA,CAAC,EAAE,eAAe;AAClB,YAAA,CAAC,EAAE,eAAe;AAClB,YAAA,CAAC,EAAE,eAAe;AAClB,YAAA,EAAE,EAAE,gBAAgB;AACpB,YAAA,EAAE,EAAE,gBAAgB;AACpB,YAAA,EAAE,EAAE,gBAAgB;SACrB;AACD,QAAA,OAAO,OAAO,CAAC,IAAI,CAAC,IAAI,EAAE;IAC5B;AAEQ,IAAA,iBAAiB,CAAC,KAAyB,EAAA;AACjD,QAAA,MAAM,IAAI,GAAG,MAAM,CAAC,KAAK,CAAC;QAC1B,OAAO,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,IAAI,GAAG,CAAC;IAC1C;AAEQ,IAAA,gBAAgB,CAAC,KAAqB,EAAA;AAC5C,QAAA,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK;QACzB,IAAI,KAAK,IAAI,IAAI;AAAE,YAAA,OAAO,GAAG;QAC7B,IAAI,OAAO,KAAK,KAAK,SAAS;YAAE,OAAO,KAAK,GAAG,KAAK,GAAG,IAAI;QAC3D,MAAM,GAAG,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,IAAI,EAAE;AAChC,QAAA,IAAI,CAAC,GAAG;AAAE,YAAA,OAAO,GAAG;AAEpB,QAAA,IAAI,KAAK,CAAC,IAAI,KAAK,MAAM,IAAI,KAAK,CAAC,IAAI,KAAK,UAAU,EAAE;AACtD,YAAA,OAAO,IAAI,CAAC,UAAU,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,KAAK,UAAU,CAAC;QACxD;AACA,QAAA,IAAI,KAAK,CAAC,IAAI,KAAK,MAAM,EAAE;YACzB,OAAO,IAAI,CAAC,UAAU,CAAC,GAAG,EAAE,KAAK,CAAC,OAAO,CAAC;QAC5C;AAEA,QAAA,OAAO,GAAG;IACZ;AAEQ,IAAA,WAAW,CAAC,KAAqB,EAAA;AACvC,QAAA,IAAI,KAAK,CAAC,IAAI,KAAK,OAAO;AAAE,YAAA,OAAO,IAAI;AAEvC,QAAA,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK;QACzB,IAAI,KAAK,IAAI,IAAI;AAAE,YAAA,OAAO,IAAI;QAE9B,MAAM,GAAG,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,IAAI,EAAE;AAChC,QAAA,IAAI,CAAC,GAAG;AAAE,YAAA,OAAO,IAAI;AACrB,QAAA,IAAI,eAAe,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE;AACpD,YAAA,OAAO,GAAG;QACZ;QAEA,MAAM,IAAI,GAAG,MAAM,CAAC,KAAK,CAAC,YAAY,IAAI,YAAY,CAAC,CAAC,IAAI,EAAE,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC;QAClF,OAAO,CAAA,EAAG,IAAI,CAAA,CAAA,EAAI,kBAAkB,CAAC,GAAG,CAAC,EAAE;IAC7C;IAEQ,UAAU,CAAC,GAAW,EAAE,OAAgC,EAAA;AAC9D,QAAA,IAAI,OAAO,GAAG,GAAG,CAAC,EAAE;AAClB,YAAA,OAAO,OAAO,CAAC,GAAG,CAAC;QACrB;QACA,IAAI,GAAG,KAAK,MAAM;AAAE,YAAA,OAAO,KAAK;QAChC,IAAI,GAAG,KAAK,OAAO;AAAE,YAAA,OAAO,IAAI;AAChC,QAAA,OAAO,GAAG;IACZ;IAEQ,UAAU,CAAC,KAAa,EAAE,QAAiB,EAAA;AACjD,QAAA,MAAM,IAAI,GAAG,IAAI,IAAI,CAAC,KAAK,CAAC;QAC5B,IAAI,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,EAAE;AAChC,YAAA,OAAO,KAAK;QACd;AAEA,QAAA,OAAO,IAAI,IAAI,CAAC,cAAc,CAAC,OAAO,EAAE;cACpC,EAAE,GAAG,EAAE,SAAS,EAAE,KAAK,EAAE,SAAS,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,SAAS;cACvF,EAAE,GAAG,EAAE,SAAS,EAAE,KAAK,EAAE,SAAS,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC;IACzE;wGArGW,oBAAoB,EAAA,IAAA,EAAA,EAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,SAAA,EAAA,CAAA;AAApB,IAAA,OAAA,IAAA,GAAA,EAAA,CAAA,oBAAA,CAAA,EAAA,UAAA,EAAA,QAAA,EAAA,OAAA,EAAA,SAAA,EAAA,IAAA,EAAA,oBAAoB,EAAA,YAAA,EAAA,IAAA,EAAA,QAAA,EAAA,kBAAA,EAAA,MAAA,EAAA,EAAA,GAAA,EAAA,EAAA,iBAAA,EAAA,KAAA,EAAA,UAAA,EAAA,KAAA,EAAA,QAAA,EAAA,IAAA,EAAA,UAAA,EAAA,KAAA,EAAA,iBAAA,EAAA,IAAA,EAAA,EAAA,EAAA,QAAA,EAAA,EAAA,EAAA,QAAA,ECjBjC,04CAsBA,EAAA,YAAA,EAAA,CAAA,EAAA,IAAA,EAAA,UAAA,EAAA,IAAA,EDRY,YAAY,6HAAE,qBAAqB,EAAA,QAAA,EAAA,mBAAA,EAAA,MAAA,EAAA,CAAA,MAAA,EAAA,cAAA,CAAA,EAAA,OAAA,EAAA,CAAA,QAAA,CAAA,EAAA,CAAA,EAAA,CAAA;;4FAGlC,oBAAoB,EAAA,UAAA,EAAA,CAAA;kBANhC,SAAS;AACE,YAAA,IAAA,EAAA,CAAA,EAAA,QAAA,EAAA,kBAAkB,cAChB,IAAI,EAAA,OAAA,EACP,CAAC,YAAY,EAAE,qBAAqB,CAAC,EAAA,QAAA,EAAA,04CAAA,EAAA;;;MEHnC,eAAe,CAAA;AACjB,IAAA,GAAG,GAAG,KAAK,CAAyB,IAAI,0EAAC;AACzC,IAAA,IAAI,GAAG,KAAK,CAAiC,IAAI,2EAAC;AAElD,IAAA,YAAY,GAAG,MAAM,CAAC,KAAK,mFAAC;AAC5B,IAAA,gBAAgB,GAAG,MAAM,CAAC,CAAC,uFAAC;AAE5B,IAAA,gBAAgB,GAAG,QAAQ,CAAC,MAAK;AACxC,QAAA,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE;AACtB,QAAA,MAAM,KAAK,GAAG,GAAG,EAAE,KAAK,IAAI,EAAE;QAC9B,MAAM,QAAQ,GAAG,IAAI,CAAC,YAAY,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC;QACrD,IAAI,QAAQ,CAAC,MAAM;AAAE,YAAA,OAAO,QAAQ;AAEpC,QAAA,MAAM,GAAG,GAAG,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,IAAI,QAAQ,CAAC,CAAC,IAAI,EAAE;AACtD,QAAA,MAAM,KAAK,GAAG,GAAG,GAAG,IAAI,CAAC,IAAI,EAAE,GAAG,GAAG,CAAC,GAAG,IAAI;AAC7C,QAAA,OAAO,IAAI,CAAC,YAAY,CAAC,KAAK,CAAC;AACjC,IAAA,CAAC,uFAAC;AAEO,IAAA,iBAAiB,GAAG,QAAQ,CAAC,MAAK;AACzC,QAAA,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE;AACtB,QAAA,MAAM,KAAK,GAAG,GAAG,EAAE,KAAK,IAAI,EAAE;QAC9B,MAAM,QAAQ,GAAG,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,WAAW,CAAC;AAC/C,cAAE,KAAK,CAAC,WAAW,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,KAAqB,OAAO,IAAI,KAAK,QAAQ;cAC5E,EAAE;QACN,IAAI,QAAQ,CAAC,MAAM;AAAE,YAAA,OAAO,QAAQ;AACpC,QAAA,OAAO,IAAI,CAAC,gBAAgB,EAAE,CAAC,GAAG,CAAC,CAAC,EAAE,KAAK,IAAI,CAAC,eAAe,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,KAAqB,CAAC,CAAC,IAAI,CAAC;AAC/G,IAAA,CAAC,wFAAC;IAEO,wBAAwB,GAAG,QAAQ,CAAC,MAAM,IAAI,CAAC,iBAAiB,EAAE,CAAC,GAAG,CAAC,CAAC,GAAG,KAAK,IAAI,CAAC,iBAAiB,CAAC,GAAG,CAAC,CAAC,EAAA,IAAA,SAAA,GAAA,CAAA,EAAA,SAAA,EAAA,0BAAA,EAAA,CAAA,8BAAA,EAAA,CAAA,CAAC;AAE7G,IAAA,sBAAsB,GAAG,QAAQ,CAAC,MAAK;AAC9C,QAAA,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE,EAAE,KAAK,GAAG,WAAW,CAAC,CAAC;AACtD,QAAA,OAAO,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,KAAK,GAAG,CAAC,GAAG,KAAK,GAAG,GAAG;AAC1D,IAAA,CAAC,6FAAC;AAEF,IAAA,UAAU,CAAC,KAAa,EAAA;AACtB,QAAA,IAAI,CAAC,gBAAgB,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC;AAC7C,QAAA,IAAI,IAAI,CAAC,iBAAiB,EAAE,CAAC,MAAM;AAAE,YAAA,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,IAAI,CAAC;IAClE;IAEA,WAAW,GAAA;AACT,QAAA,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,KAAK,CAAC;IAC9B;AAEQ,IAAA,eAAe,CAAC,YAAoB,EAAA;QAC1C,MAAM,KAAK,GAAG,MAAM,CAAC,YAAY,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE;QAC/C,IAAI,CAAC,KAAK,EAAE;AACV,YAAA,OAAO,EAAE;QACX;AACA,QAAA,IAAI,KAAK,CAAC,UAAU,CAAC,SAAS,CAAC,IAAI,KAAK,CAAC,UAAU,CAAC,UAAU,CAAC,IAAI,KAAK,CAAC,UAAU,CAAC,OAAO,CAAC,IAAI,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE;AACrH,YAAA,OAAO,KAAK;QACd;QAEA,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE,EAAE,KAAK,GAAG,cAAc,CAAC,IAAI,YAAY,CAAC,CAAC,IAAI,EAAE,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC;QACnG,OAAO,CAAA,EAAG,IAAI,CAAA,CAAA,EAAI,kBAAkB,CAAC,KAAK,CAAC,EAAE;IAC/C;AAEQ,IAAA,iBAAiB,CAAC,GAAW,EAAA;QACnC,MAAM,KAAK,GAAG,GAAG,CAAC,KAAK,CAAC,uCAAuC,CAAC;AAChE,QAAA,OAAO,KAAK,GAAG,GAAG,KAAK,CAAC,CAAC,CAAC,CAAA,iBAAA,CAAmB,GAAG,GAAG;IACrD;AAEQ,IAAA,YAAY,CAAC,KAAc,EAAA;QACjC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE;AACzB,YAAA,OAAO,EAAE;QACX;AAEA,QAAA,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,GAAG,CACvB;AACG,aAAA,GAAG,CAAC,CAAC,IAAI,KAAK,MAAM,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE;AACvC,aAAA,MAAM,CAAC,OAAO,CAAC,CACnB,CAAC;IACJ;wGAxEW,eAAe,EAAA,IAAA,EAAA,EAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,SAAA,EAAA,CAAA;AAAf,IAAA,OAAA,IAAA,GAAA,EAAA,CAAA,oBAAA,CAAA,EAAA,UAAA,EAAA,QAAA,EAAA,OAAA,EAAA,SAAA,EAAA,IAAA,EAAA,eAAe,EAAA,YAAA,EAAA,IAAA,EAAA,QAAA,EAAA,YAAA,EAAA,MAAA,EAAA,EAAA,GAAA,EAAA,EAAA,iBAAA,EAAA,KAAA,EAAA,UAAA,EAAA,KAAA,EAAA,QAAA,EAAA,IAAA,EAAA,UAAA,EAAA,KAAA,EAAA,iBAAA,EAAA,IAAA,EAAA,EAAA,IAAA,EAAA,EAAA,iBAAA,EAAA,MAAA,EAAA,UAAA,EAAA,MAAA,EAAA,QAAA,EAAA,IAAA,EAAA,UAAA,EAAA,KAAA,EAAA,iBAAA,EAAA,IAAA,EAAA,EAAA,EAAA,QAAA,EAAA,EAAA,EAAA,QAAA,ECX5B,20CAmCA,EAAA,YAAA,EAAA,CAAA,EAAA,IAAA,EAAA,UAAA,EAAA,IAAA,ED3BY,YAAY,6HAAE,qBAAqB,EAAA,QAAA,EAAA,mBAAA,EAAA,MAAA,EAAA,CAAA,MAAA,EAAA,cAAA,CAAA,EAAA,OAAA,EAAA,CAAA,QAAA,CAAA,EAAA,CAAA,EAAA,CAAA;;4FAGlC,eAAe,EAAA,UAAA,EAAA,CAAA;kBAN3B,SAAS;AACE,YAAA,IAAA,EAAA,CAAA,EAAA,QAAA,EAAA,YAAY,cACV,IAAI,EAAA,OAAA,EACP,CAAC,YAAY,EAAE,qBAAqB,CAAC,EAAA,QAAA,EAAA,20CAAA,EAAA;;;MECnC,oBAAoB,GAAG,IAAI,cAAc,CAA0B,sBAAsB;;MCczF,2BAA2B,CAAA;AACrB,IAAA,QAAQ,GAA4B,MAAM,CAAC,oBAAoB,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,IAAI,EAAE;AAElG,IAAA,MAAM,GAAG,KAAK,CAAC,QAAQ,4EAAU;AACjC,IAAA,QAAQ,GAAG,KAAK,CAAS,EAAE,+EAAC;AAC5B,IAAA,IAAI,GAAG,KAAK,CAAoB,EAAE,2EAAC;AACnC,IAAA,IAAI,GAAG,KAAK,CAAiC,IAAI,2EAAC;AAClD,IAAA,QAAQ,GAAG,KAAK,CAAC,KAAK,+EAAC;AAEvB,IAAA,QAAQ,GAAG,QAAQ,CAAC,MAAM,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,IAAI,IAAI,+EAAC;AAE1D,IAAA,UAAU,CAAC,GAAoB,EAAA;AAC7B,QAAA,MAAM,KAAK,GAAG,GAAG,CAAC,OAAO;AACzB,QAAA,OAAO,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,GAAG,KAAK,GAAG,EAAE;IAC1C;AAEA,IAAA,iBAAiB,CAAC,GAAoB,EAAA;AACpC,QAAA,OAAO,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,CAAC,MAAM,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC;IAChE;AAEA,IAAA,qBAAqB,CAAC,GAAoB,EAAA;QACxC,OAAO,CAAC,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,GAAG,CAAC,IAAI,IAAI,GAAG,CAAC,SAAS,KAAK,MAAM,IAAI,GAAG,CAAC,MAAM,KAAK,KAAK;IAC1F;AAEA,IAAA,eAAe,CAAC,GAAoB,EAAA;AAClC,QAAA,IAAI,GAAG,CAAC,GAAG,IAAI,IAAI;AAAE,YAAA,OAAO,gBAAgB;QAE5C,MAAM,GAAG,GAAG,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC;AAC3B,QAAA,MAAM,IAAI,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;AACnF,QAAA,MAAM,OAAO,GAA2B;AACtC,YAAA,CAAC,EAAE,eAAe;AAClB,YAAA,CAAC,EAAE,eAAe;AAClB,YAAA,CAAC,EAAE,eAAe;AAClB,YAAA,CAAC,EAAE,eAAe;AAClB,YAAA,CAAC,EAAE,eAAe;AAClB,YAAA,CAAC,EAAE,eAAe;AAClB,YAAA,CAAC,EAAE,eAAe;AAClB,YAAA,CAAC,EAAE,eAAe;AAClB,YAAA,CAAC,EAAE,eAAe;AAClB,YAAA,EAAE,EAAE,gBAAgB;AACpB,YAAA,EAAE,EAAE,gBAAgB;AACpB,YAAA,EAAE,EAAE,gBAAgB;SACrB;AACD,QAAA,OAAO,OAAO,CAAC,IAAI,CAAC,IAAI,gBAAgB;IAC1C;AAEA,IAAA,mBAAmB,CAAC,GAAoB,EAAA;AACtC,QAAA,IAAI,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,GAAG,CAAC,IAAI,CAAC,MAAM,EAAE;AAC9C,YAAA,OAAO,EAAE,IAAI,EAAE,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,CAAC,MAAM,IAAI,QAAQ,EAAE;QAC3D;AACA,QAAA,OAAO,IAAI;IACb;AAEA,IAAA,eAAe,CAAC,GAAoB,EAAA;QAClC,OAAO,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,GAAG,CAAC,MAAM,CAAC,MAAM,GAAG,GAAG,CAAC,MAAM,GAAG,IAAI;IAC3E;AAEA,IAAA,sBAAsB,CAAC,GAAoB,EAAA;AACzC,QAAA,MAAM,GAAG,GAAG,GAAG,CAAC,SAAS;AACzB,QAAA,IAAI,CAAC,GAAG,IAAI,GAAG,KAAK,QAAQ,IAAI,GAAG,KAAK,aAAa,IAAI,GAAG,KAAK,MAAM;AAAE,YAAA,OAAO,IAAI;AACpF,QAAA,MAAM,KAAK,GAAG,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,IAAI,KAAK,IAAI,CAAC,GAAG,KAAK,GAAG,CAAC;AAC5D,QAAA,OAAO,KAAK,EAAE,SAAS,IAAI,IAAI;IACjC;AAEA,IAAA,mBAAmB,CAAC,GAAoB,EAAA;AACtC,QAAA,MAAM,GAAG,GAAG,GAAG,CAAC,SAAS;AACzB,QAAA,IAAI,CAAC,GAAG;YAAE,OAAO,EAAE,GAAG,EAAE;AACxB,QAAA,MAAM,KAAK,GAAsC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,IAAI,KAAK,IAAI,CAAC,GAAG,KAAK,GAAG,CAAC;QAC/F,IAAI,CAAC,KAAK,EAAE,aAAa;YAAE,OAAO,EAAE,GAAG,EAAE;QACzC,OAAO,KAAK,CAAC,aAAa,CAAC,GAAG,EAAE,IAAI,CAAC,IAAI,EAAE,CAAC;IAC9C;wGAtEW,2BAA2B,EAAA,IAAA,EAAA,EAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,SAAA,EAAA,CAAA;AAA3B,IAAA,OAAA,IAAA,GAAA,EAAA,CAAA,oBAAA,CAAA,EAAA,UAAA,EAAA,QAAA,EAAA,OAAA,EAAA,SAAA,EAAA,IAAA,EAAA,2BAA2B,EAAA,YAAA,EAAA,IAAA,EAAA,QAAA,EAAA,2BAAA,EAAA,MAAA,EAAA,EAAA,MAAA,EAAA,EAAA,iBAAA,EAAA,QAAA,EAAA,UAAA,EAAA,QAAA,EAAA,QAAA,EAAA,IAAA,EAAA,UAAA,EAAA,IAAA,EAAA,iBAAA,EAAA,IAAA,EAAA,EAAA,QAAA,EAAA,EAAA,iBAAA,EAAA,UAAA,EAAA,UAAA,EAAA,UAAA,EAAA,QAAA,EAAA,IAAA,EAAA,UAAA,EAAA,KAAA,EAAA,iBAAA,EAAA,IAAA,EAAA,EAAA,IAAA,EAAA,EAAA,iBAAA,EAAA,MAAA,EAAA,UAAA,EAAA,MAAA,EAAA,QAAA,EAAA,IAAA,EAAA,UAAA,EAAA,KAAA,EAAA,iBAAA,EAAA,IAAA,EAAA,EAAA,IAAA,EAAA,EAAA,iBAAA,EAAA,MAAA,EAAA,UAAA,EAAA,MAAA,EAAA,QAAA,EAAA,IAAA,EAAA,UAAA,EAAA,KAAA,EAAA,iBAAA,EAAA,IAAA,EAAA,EAAA,QAAA,EAAA,EAAA,iBAAA,EAAA,UAAA,EAAA,UAAA,EAAA,UAAA,EAAA,QAAA,EAAA,IAAA,EAAA,UAAA,EAAA,KAAA,EAAA,iBAAA,EAAA,IAAA,EAAA,EAAA,EAAA,QAAA,EAAA,EAAA,EAAA,QAAA,ECvBxC,yrEAkDA,EAAA,YAAA,EAAA,CAAA,EAAA,IAAA,EAAA,UAAA,EAAA,IAAA,EAAA,EAAA,CAAA,UAAA,CAAA,MD9BY,YAAY,CAAA,EAAA,EAAA,EAAA,IAAA,EAAA,WAAA,EAAA,IAAA,EAAA,EAAA,CAAA,UAAA,CAAA,MAAA,EAAA,CAAA,OAAA,CAAA,EAAA,QAAA,EAAA,WAAA,EAAA,MAAA,EAAA,CAAA,OAAA,EAAA,SAAA,CAAA,EAAA,EAAA,EAAA,IAAA,EAAA,WAAA,EAAA,IAAA,EAAA,EAAA,CAAA,UAAA,CAAA,MAAA,EAAA,CAAA,iBAAA,CAAA,EAAA,QAAA,EAAA,qBAAA,EAAA,MAAA,EAAA,CAAA,mBAAA,EAAA,yBAAA,EAAA,2BAAA,EAAA,sCAAA,EAAA,0BAAA,EAAA,2BAAA,CAAA,EAAA,QAAA,EAAA,CAAA,mBAAA,CAAA,EAAA,EAAA,EAAA,IAAA,EAAA,WAAA,EAAA,IAAA,EAAA,EAAA,CAAA,UAAA,CAAA,MAAA,EAAA,CAAA,gBAAA,CAAA,EAAA,QAAA,EAAA,oBAAA,EAAA,MAAA,EAAA,CAAA,yBAAA,EAAA,kBAAA,EAAA,0BAAA,CAAA,EAAA,EAAA,EAAA,IAAA,EAAA,WAAA,EAAA,IAAA,EAAA,EAAA,CAAA,UAAA,CAAA,MAAmB,aAAa,CAAA,EAAA,QAAA,EAAA,UAAA,EAAA,MAAA,EAAA,CAAA,QAAA,EAAA,UAAA,EAAA,MAAA,EAAA,MAAA,EAAA,QAAA,EAAA,UAAA,CAAA,EAAA,EAAA,EAAA,IAAA,EAAA,WAAA,EAAA,IAAA,EAAA,EAAA,CAAA,UAAA,CAAA,MAAG,0BAA0B,CAAA,EAAA,QAAA,EAAA,yBAAA,EAAA,MAAA,EAAA,CAAA,SAAA,EAAA,OAAA,EAAA,SAAA,EAAA,QAAA,EAAA,UAAA,EAAA,MAAA,CAAA,EAAA,EAAA,EAAA,IAAA,EAAA,WAAA,EAAA,IAAA,EAAA,EAAA,CAAA,UAAA,CAAA,MAAE,oBAAoB,CAAA,EAAA,QAAA,EAAA,mBAAA,EAAA,MAAA,EAAA,CAAA,QAAA,EAAA,UAAA,EAAA,KAAA,EAAA,MAAA,CAAA,EAAA,EAAA,EAAA,IAAA,EAAA,WAAA,EAAA,IAAA,EAAA,EAAA,CAAA,UAAA,CAAA,MAAE,oBAAoB,mGAAE,eAAe,CAAA,EAAA,QAAA,EAAA,YAAA,EAAA,MAAA,EAAA,CAAA,KAAA,EAAA,MAAA,CAAA,EAAA,CAAA,EAAA,CAAA;;4FAGrI,2BAA2B,EAAA,UAAA,EAAA,CAAA;kBANvC,SAAS;+BACE,2BAA2B,EAAA,UAAA,EACzB,IAAI,EAAA,OAAA,EACP,CAAC,YAAY,EAAE,UAAU,CAAC,MAAM,aAAa,CAAC,EAAE,0BAA0B,EAAE,oBAAoB,EAAE,oBAAoB,EAAE,eAAe,CAAC,EAAA,QAAA,EAAA,yrEAAA,EAAA;;;MEAtI,yBAAyB,CAAA;AACnB,IAAA,QAAQ,GAA4B,MAAM,CAAC,oBAAoB,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,IAAI,EAAE;AAC1F,IAAA,UAAU,GAAG,MAAM,CAAC,UAAU,CAAC;AAC/B,IAAA,KAAK,GAAG,MAAM,CAAC,cAAc,CAAC;AAC9B,IAAA,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC;AAE/B,IAAA,MAAM,GAAG,KAAK,CAAC,QAAQ,4EAAU;AACjC,IAAA,QAAQ,GAAG,KAAK,CAAS,EAAE,+EAAC;AAC5B,IAAA,IAAI,GAAG,KAAK,CAAoB,EAAE,2EAAC;AACnC,IAAA,IAAI,GAAG,KAAK,CAAiC,IAAI,2EAAC;AAClD,IAAA,WAAW,GAAG,MAAM,CAAS,EAAE,kFAAC;IAEzC,QAAQ,GAAA;QACN,IAAI,CAAC,KAAK,CAAC,aAAa,CAAC,IAAI,CAAC,kBAAkB,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,MAAM,KAAI;YACtF,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC;AAC/B,YAAA,IAAI,KAAK;AAAE,gBAAA,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,KAAK,CAAC;AACxC,QAAA,CAAC,CAAC;IACJ;AAES,IAAA,SAAS,GAAG,QAAQ,CAAC,MAAK;QACjC,MAAM,KAAK,GAAG,IAAI,CAAC,IAAI,EAAE,IAAI,EAAE;QAC/B,IAAI,CAAC,KAAK,CAAC,MAAM;AAAE,YAAA,OAAO,IAAI;AAC9B,QAAA,MAAM,SAAS,GAAG,IAAI,CAAC,WAAW,EAAE;QACpC,OAAO,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,KAAK,IAAI,CAAC,EAAE,KAAK,SAAS,CAAC,IAAI,KAAK,CAAC,CAAC,CAAC;AAChE,IAAA,CAAC,gFAAC;AAEF,IAAA,YAAY,CAAC,EAAU,EAAA;AACrB,QAAA,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,EAAE,CAAC;AACxB,QAAA,KAAK,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,EAAE,EAAE;YAC5B,UAAU,EAAE,IAAI,CAAC,KAAK;AACtB,YAAA,WAAW,EAAE,EAAE,GAAG,EAAE,EAAE,EAAE;AACxB,YAAA,mBAAmB,EAAE,OAAO;AAC5B,YAAA,UAAU,EAAE,IAAI;AACjB,SAAA,CAAC;IACJ;AAEA,IAAA,UAAU,CAAC,GAAoB,EAAA;AAC7B,QAAA,MAAM,KAAK,GAAG,GAAG,CAAC,OAAO;AACzB,QAAA,OAAO,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,GAAG,KAAK,GAAG,EAAE;IAC1C;AAEA,IAAA,iBAAiB,CAAC,GAAoB,EAAA;AACpC,QAAA,OAAO,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,CAAC,MAAM,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC;IAChE;AAEA,IAAA,UAAU,CAAC,GAAoB,EAAA;AAC7B,QAAA,IAAI,GAAG,CAAC,GAAG,IAAI,IAAI;AAAE,YAAA,OAAO,EAAE;QAE9B,MAAM,GAAG,GAAG,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC;QAC3B,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,GAAG,IAAI,CAAC;AAAE,YAAA,OAAO,EAAE;AAChD,QAAA,OAAO,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IACtC;AAEA,IAAA,sBAAsB,CAAC,GAAoB,EAAA;AACzC,QAAA,MAAM,GAAG,GAAG,GAAG,CAAC,SAAS;AACzB,QAAA,IAAI,CAAC,GAAG,IAAI,GAAG,KAAK,QAAQ,IAAI,GAAG,KAAK,aAAa,IAAI,GAAG,KAAK,MAAM;AAAE,YAAA,OAAO,IAAI;AACpF,QAAA,MAAM,KAAK,GAAG,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,IAAI,KAAK,IAAI,CAAC,GAAG,KAAK,GAAG,CAAC;AAC5D,QAAA,OAAO,KAAK,EAAE,SAAS,IAAI,IAAI;IACjC;AAEA,IAAA,mBAAmB,CAAC,GAAoB,EAAA;AACtC,QAAA,MAAM,GAAG,GAAG,GAAG,CAAC,SAAS;AACzB,QAAA,IAAI,CAAC,GAAG;YAAE,OAAO,EAAE,GAAG,EAAE;AACxB,QAAA,MAAM,KAAK,GAAsC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,IAAI,KAAK,IAAI,CAAC,GAAG,KAAK,GAAG,CAAC;QAC/F,IAAI,CAAC,KAAK,EAAE,aAAa;YAAE,OAAO,EAAE,GAAG,EAAE;QACzC,OAAO,KAAK,CAAC,aAAa,CAAC,GAAG,EAAE,IAAI,CAAC,IAAI,EAAE,CAAC;IAC9C;wGAlEW,yBAAyB,EAAA,IAAA,EAAA,EAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,SAAA,EAAA,CAAA;AAAzB,IAAA,OAAA,IAAA,GAAA,EAAA,CAAA,oBAAA,CAAA,EAAA,UAAA,EAAA,QAAA,EAAA,OAAA,EAAA,SAAA,EAAA,IAAA,EAAA,yBAAyB,EAAA,YAAA,EAAA,IAAA,EAAA,QAAA,EAAA,yBAAA,EAAA,MAAA,EAAA,EAAA,MAAA,EAAA,EAAA,iBAAA,EAAA,QAAA,EAAA,UAAA,EAAA,QAAA,EAAA,QAAA,EAAA,IAAA,EAAA,UAAA,EAAA,IAAA,EAAA,iBAAA,EAAA,IAAA,EAAA,EAAA,QAAA,EAAA,EAAA,iBAAA,EAAA,UAAA,EAAA,UAAA,EAAA,UAAA,EAAA,QAAA,EAAA,IAAA,EAAA,UAAA,EAAA,KAAA,EAAA,iBAAA,EAAA,IAAA,EAAA,EAAA,IAAA,EAAA,EAAA,iBAAA,EAAA,MAAA,EAAA,UAAA,EAAA,MAAA,EAAA,QAAA,EAAA,IAAA,EAAA,UAAA,EAAA,KAAA,EAAA,iBAAA,EAAA,IAAA,EAAA,EAAA,IAAA,EAAA,EAAA,iBAAA,EAAA,MAAA,EAAA,UAAA,EAAA,MAAA,EAAA,QAAA,EAAA,IAAA,EAAA,UAAA,EAAA,KAAA,EAAA,iBAAA,EAAA,IAAA,EAAA,EAAA,EAAA,QAAA,EAAA,EAAA,EAAA,QAAA,ECpBtC,gkEAoDA,EAAA,YAAA,EAAA,CAAA,EAAA,IAAA,EAAA,UAAA,EAAA,IAAA,EAAA,EAAA,CAAA,UAAA,CAAA,MDnCY,YAAY,CAAA,EAAA,EAAA,EAAA,IAAA,EAAA,WAAA,EAAA,IAAA,EAAA,EAAA,CAAA,UAAA,CAAA,MAAA,EAAA,CAAA,iBAAA,CAAA,EAAA,QAAA,EAAA,qBAAA,EAAA,MAAA,EAAA,CAAA,mBAAA,EAAA,yBAAA,EAAA,2BAAA,EAAA,sCAAA,EAAA,0BAAA,EAAA,2BAAA,CAAA,EAAA,QAAA,EAAA,CAAA,mBAAA,CAAA,EAAA,EAAA,EAAA,IAAA,EAAA,WAAA,EAAA,IAAA,EAAA,EAAA,CAAA,UAAA,CAAA,MAAmB,aAAa,CAAA,EAAA,QAAA,EAAA,UAAA,EAAA,MAAA,EAAA,CAAA,QAAA,EAAA,UAAA,EAAA,MAAA,EAAA,MAAA,EAAA,QAAA,EAAA,UAAA,CAAA,EAAA,EAAA,EAAA,IAAA,EAAA,WAAA,EAAA,IAAA,EAAA,EAAA,CAAA,UAAA,CAAA,MAAG,0BAA0B,CAAA,EAAA,QAAA,EAAA,yBAAA,EAAA,MAAA,EAAA,CAAA,SAAA,EAAA,OAAA,EAAA,SAAA,EAAA,QAAA,EAAA,UAAA,EAAA,MAAA,CAAA,EAAA,EAAA,EAAA,IAAA,EAAA,WAAA,EAAA,IAAA,EAAA,EAAA,CAAA,UAAA,CAAA,MAAE,oBAAoB,CAAA,EAAA,QAAA,EAAA,mBAAA,EAAA,MAAA,EAAA,CAAA,QAAA,EAAA,UAAA,EAAA,KAAA,EAAA,MAAA,CAAA,EAAA,EAAA,EAAA,IAAA,EAAA,WAAA,EAAA,IAAA,EAAA,EAAA,CAAA,UAAA,CAAA,MAAE,eAAe,qGAAE,oBAAoB,CAAA,EAAA,QAAA,EAAA,kBAAA,EAAA,MAAA,EAAA,CAAA,KAAA,CAAA,EAAA,CAAA,EAAA,CAAA;;4FAGrI,yBAAyB,EAAA,UAAA,EAAA,CAAA;kBANrC,SAAS;+BACE,yBAAyB,EAAA,UAAA,EACvB,IAAI,EAAA,OAAA,EACP,CAAC,YAAY,EAAE,UAAU,CAAC,MAAM,aAAa,CAAC,EAAE,0BAA0B,EAAE,oBAAoB,EAAE,eAAe,EAAE,oBAAoB,CAAC,EAAA,QAAA,EAAA,gkEAAA,EAAA;;;MELtI,aAAa,CAAA;AACf,IAAA,MAAM,GAAG,KAAK,CAAC,QAAQ,4EAAU;AACjC,IAAA,QAAQ,GAAG,KAAK,CAAS,EAAE,+EAAC;AAC5B,IAAA,IAAI,GAAG,KAAK,CAAoB,EAAE,2EAAC;AACnC,IAAA,IAAI,GAAG,KAAK,CAAiC,IAAI,2EAAC;AAClD,IAAA,MAAM,GAAG,KAAK,CAAoB,QAAQ,6EAAC;AAC3C,IAAA,QAAQ,GAAG,KAAK,CAAC,KAAK,+EAAC;wGANrB,aAAa,EAAA,IAAA,EAAA,EAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,SAAA,EAAA,CAAA;AAAb,IAAA,OAAA,IAAA,GAAA,EAAA,CAAA,oBAAA,CAAA,EAAA,UAAA,EAAA,QAAA,EAAA,OAAA,EAAA,SAAA,EAAA,IAAA,EAAA,aAAa,izBCZ1B,wZAgBA,EAAA,YAAA,EAAA,CAAA,EAAA,IAAA,EAAA,UAAA,EAAA,IAAA,EAAA,EAAA,CAAA,UAAA,CAAA,MDPY,YAAY,CAAA,EAAA,EAAA,EAAA,IAAA,EAAA,WAAA,EAAA,IAAA,EAAA,EAAA,CAAA,UAAA,CAAA,MAAmB,yBAAyB,yIAAoB,2BAA2B,CAAA,EAAA,QAAA,EAAA,2BAAA,EAAA,MAAA,EAAA,CAAA,QAAA,EAAA,UAAA,EAAA,MAAA,EAAA,MAAA,EAAA,UAAA,CAAA,EAAA,CAAA,EAAA,CAAA;;4FAGtG,aAAa,EAAA,UAAA,EAAA,CAAA;kBANzB,SAAS;+BACE,UAAU,EAAA,UAAA,EACR,IAAI,EAAA,OAAA,EACP,CAAC,YAAY,EAAE,UAAU,CAAC,MAAM,yBAAyB,CAAC,EAAE,UAAU,CAAC,MAAM,2BAA2B,CAAC,CAAC,EAAA,QAAA,EAAA,wZAAA,EAAA;;;MEyExG,mBAAmB,GAAG,IAAI,cAAc,CAAmB,qBAAqB;;AClF7F;;AAEG;;;;"}
package/package.json ADDED
@@ -0,0 +1,29 @@
1
+ {
2
+ "name": "@maro-tech/card",
3
+ "version": "0.0.1",
4
+ "private": false,
5
+ "peerDependencies": {
6
+ "@angular/common": "^21.2.0",
7
+ "@angular/core": "^21.2.0",
8
+ "@angular/router": "^21.2.0",
9
+ "@maro-tech/core": "^0.0.4",
10
+ "@maro-tech/table": "^0.0.2",
11
+ "rxjs": "~7.8.0"
12
+ },
13
+ "dependencies": {
14
+ "tslib": "^2.3.0"
15
+ },
16
+ "module": "fesm2022/maro-tech-card.mjs",
17
+ "typings": "types/maro-tech-card.d.ts",
18
+ "exports": {
19
+ "./package.json": {
20
+ "default": "./package.json"
21
+ },
22
+ ".": {
23
+ "types": "./types/maro-tech-card.d.ts",
24
+ "default": "./fesm2022/maro-tech-card.mjs"
25
+ }
26
+ },
27
+ "sideEffects": false,
28
+ "type": "module"
29
+ }
@@ -0,0 +1,179 @@
1
+ import * as _angular_core from '@angular/core';
2
+ import { InjectionToken, Type } from '@angular/core';
3
+ import { Observable } from 'rxjs';
4
+
5
+ type ApiPluginMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
6
+ interface ApiPluginConfig {
7
+ api: string;
8
+ method?: ApiPluginMethod;
9
+ payload?: Record<string, unknown>;
10
+ [key: string]: unknown;
11
+ }
12
+ interface UiEntityCardAction {
13
+ id?: string;
14
+ label: string;
15
+ type: string;
16
+ icon?: string;
17
+ hidden?: boolean;
18
+ html?: string;
19
+ component?: string;
20
+ formId?: string;
21
+ size?: 'sm' | 'md' | 'lg';
22
+ api?: string;
23
+ template?: string;
24
+ method?: ApiPluginMethod;
25
+ title?: string;
26
+ text?: string;
27
+ submit?: boolean | string;
28
+ payload?: Record<string, unknown>;
29
+ redirectEntity?: string;
30
+ condition?: string;
31
+ }
32
+ interface UiEntityCardTab {
33
+ id: string;
34
+ title: string;
35
+ col?: number;
36
+ layout?: UiCardLayoutInput;
37
+ component?: string;
38
+ html?: string;
39
+ tabs?: UiEntityCardTab[];
40
+ panels?: UiEntityCardTab[];
41
+ actions?: UiEntityCardAction[];
42
+ props?: Record<string, unknown> & {
43
+ imageUrls?: string[];
44
+ imageIds?: string[];
45
+ images?: string;
46
+ recordId?: string;
47
+ recordIdKey?: string;
48
+ imageBaseUrl?: string;
49
+ fields?: Array<Record<string, unknown>>;
50
+ warehouseId?: number | string;
51
+ [key: string]: unknown;
52
+ };
53
+ }
54
+ interface UiEntityCardResponse {
55
+ meta?: Record<string, unknown>;
56
+ title: string;
57
+ layout?: UiCardLayoutInput;
58
+ actions?: UiEntityCardAction[];
59
+ viewActions?: {
60
+ read: boolean;
61
+ create: boolean;
62
+ edit: boolean;
63
+ delete: boolean;
64
+ hasFormTemplate: boolean;
65
+ };
66
+ tabs?: UiEntityCardTab[];
67
+ data?: Record<string, unknown>;
68
+ }
69
+ interface UiEntityCardHost {
70
+ getCardView(entity: string, id: string): Observable<UiEntityCardResponse>;
71
+ getFormView(entity: string, id: string): Observable<Record<string, unknown>>;
72
+ callApi(config: ApiPluginConfig): Observable<unknown>;
73
+ deleteCard(entity: string, id: string): Observable<void>;
74
+ notify(message: string, variant: 'success' | 'error' | 'warning', durationMs?: number): void;
75
+ goBack(entity: string, queryParams?: Record<string, unknown>, returnUrl?: string): void;
76
+ goToEdit(entity: string, id: string): void;
77
+ }
78
+ declare const UI_ENTITY_CARD_HOST: InjectionToken<UiEntityCardHost>;
79
+
80
+ type UiCardLayout = 'tabs' | 'panels';
81
+ type UiCardLayoutInput = UiCardLayout | null | undefined;
82
+ type UiCardTabComponent = 'images' | 'detailsGrid' | 'table' | 'html' | (string & {});
83
+ interface UiCardTabField {
84
+ id: string;
85
+ label: string;
86
+ col?: number;
87
+ value?: string | number | boolean | null | undefined;
88
+ type: 'text' | 'number' | 'boolean' | 'date' | 'datetime' | 'enum' | 'image';
89
+ enumMap?: Record<string, string>;
90
+ imageBaseUrl?: string;
91
+ }
92
+ interface UiCardTabConfig {
93
+ id: string;
94
+ title: string;
95
+ header?: boolean;
96
+ col?: number;
97
+ layout?: UiCardLayoutInput;
98
+ component?: UiCardTabComponent;
99
+ html?: string;
100
+ tabs?: UiCardTabConfig[];
101
+ panels?: UiCardTabConfig[];
102
+ actions?: UiEntityCardAction[];
103
+ props?: {
104
+ imageUrls?: string[];
105
+ imageIds?: string[];
106
+ images?: string;
107
+ maxHeight?: number;
108
+ recordId?: string;
109
+ recordIdKey?: string;
110
+ imageBaseUrl?: string;
111
+ fields?: UiCardTabField[];
112
+ warehouseId?: number | string;
113
+ [key: string]: unknown;
114
+ };
115
+ }
116
+
117
+ declare class CardComponent {
118
+ readonly entity: _angular_core.InputSignal<string>;
119
+ readonly recordId: _angular_core.InputSignal<string>;
120
+ readonly tabs: _angular_core.InputSignal<UiCardTabConfig[]>;
121
+ readonly data: _angular_core.InputSignal<Record<string, unknown> | null>;
122
+ readonly layout: _angular_core.InputSignal<UiCardLayoutInput>;
123
+ readonly embedded: _angular_core.InputSignal<boolean>;
124
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<CardComponent, never>;
125
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<CardComponent, "app-card", never, { "entity": { "alias": "entity"; "required": true; "isSignal": true; }; "recordId": { "alias": "recordId"; "required": false; "isSignal": true; }; "tabs": { "alias": "tabs"; "required": false; "isSignal": true; }; "data": { "alias": "data"; "required": false; "isSignal": true; }; "layout": { "alias": "layout"; "required": false; "isSignal": true; }; "embedded": { "alias": "embedded"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
126
+ }
127
+
128
+ interface CardActionContext {
129
+ entity: string;
130
+ recordId?: string;
131
+ data?: Record<string, unknown> | null;
132
+ }
133
+ declare const CARD_ACTION_REQUESTED = "card.action.requested";
134
+ declare const CARD_ACTION_STARTED = "card.action.started";
135
+ declare const CARD_ACTION_COMPLETED = "card.action.completed";
136
+ declare const CARD_ACTION_FINISHED = "card.action.finished";
137
+ declare const ENTITY_UPDATED = "entity.updated";
138
+ interface CardActionRequestedEvent {
139
+ action: UiEntityCardAction;
140
+ context: CardActionContext;
141
+ }
142
+ interface CardActionLifecycleEvent {
143
+ actionId: string;
144
+ context: CardActionContext;
145
+ }
146
+ interface EntityUpdatedEvent {
147
+ entity?: string;
148
+ recordId?: string;
149
+ source?: string;
150
+ }
151
+
152
+ interface UiCardTabRegistration {
153
+ key: string;
154
+ component: Type<unknown>;
155
+ resolveInputs?: (tab: UiCardTabConfig, data?: Record<string, unknown> | null) => Record<string, unknown>;
156
+ }
157
+ declare const UI_CARD_TAB_REGISTRY: InjectionToken<UiCardTabRegistration[]>;
158
+
159
+ declare class CardSectionHeaderComponent {
160
+ readonly variant: _angular_core.InputSignal<"tabs" | "panels">;
161
+ readonly title: _angular_core.InputSignal<string>;
162
+ readonly actions: _angular_core.InputSignal<UiEntityCardAction[]>;
163
+ readonly entity: _angular_core.InputSignal<string>;
164
+ readonly recordId: _angular_core.InputSignal<string>;
165
+ readonly data: _angular_core.InputSignal<Record<string, unknown> | null>;
166
+ private readonly eventBus;
167
+ private readonly destroyRef;
168
+ private readonly pendingActionIdSignal;
169
+ constructor();
170
+ trackAction(_: number, action: UiEntityCardAction): string;
171
+ isActionPending(action: UiEntityCardAction): boolean;
172
+ onActionClick(action: UiEntityCardAction): void;
173
+ private matchesContext;
174
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<CardSectionHeaderComponent, never>;
175
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<CardSectionHeaderComponent, "app-card-section-header", never, { "variant": { "alias": "variant"; "required": false; "isSignal": true; }; "title": { "alias": "title"; "required": false; "isSignal": true; }; "actions": { "alias": "actions"; "required": false; "isSignal": true; }; "entity": { "alias": "entity"; "required": true; "isSignal": true; }; "recordId": { "alias": "recordId"; "required": false; "isSignal": true; }; "data": { "alias": "data"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
176
+ }
177
+
178
+ export { CARD_ACTION_COMPLETED, CARD_ACTION_FINISHED, CARD_ACTION_REQUESTED, CARD_ACTION_STARTED, CardComponent, CardSectionHeaderComponent, ENTITY_UPDATED, UI_CARD_TAB_REGISTRY, UI_ENTITY_CARD_HOST };
179
+ export type { ApiPluginConfig, ApiPluginMethod, CardActionContext, CardActionLifecycleEvent, CardActionRequestedEvent, EntityUpdatedEvent, UiCardLayout, UiCardLayoutInput, UiCardTabComponent, UiCardTabConfig, UiCardTabField, UiCardTabRegistration, UiEntityCardAction, UiEntityCardHost, UiEntityCardResponse, UiEntityCardTab };