@memberjunction/ng-explorer-settings 6.1.4 → 6.2.0-edge.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.
- package/dist/lib/account-info/account-info.component.d.ts +1 -1
- package/dist/lib/account-info/account-info.component.js +2 -2
- package/dist/lib/account-info/account-info.component.js.map +1 -1
- package/dist/lib/application-management/application-dialog/application-dialog.component.d.ts +93 -10
- package/dist/lib/application-management/application-dialog/application-dialog.component.d.ts.map +1 -1
- package/dist/lib/application-management/application-dialog/application-dialog.component.js +232 -80
- package/dist/lib/application-management/application-dialog/application-dialog.component.js.map +1 -1
- package/dist/lib/application-management/application-management.component.d.ts +94 -11
- package/dist/lib/application-management/application-management.component.d.ts.map +1 -1
- package/dist/lib/application-management/application-management.component.js +231 -67
- package/dist/lib/application-management/application-management.component.js.map +1 -1
- package/dist/lib/application-settings/application-settings.component.d.ts +4 -4
- package/dist/lib/application-settings/application-settings.component.js +10 -10
- package/dist/lib/application-settings/application-settings.component.js.map +1 -1
- package/dist/lib/entity-permissions/entity-permissions.component.d.ts +88 -10
- package/dist/lib/entity-permissions/entity-permissions.component.d.ts.map +1 -1
- package/dist/lib/entity-permissions/entity-permissions.component.js +219 -63
- package/dist/lib/entity-permissions/entity-permissions.component.js.map +1 -1
- package/dist/lib/entity-permissions/permission-dialog/permission-dialog.component.d.ts +44 -6
- package/dist/lib/entity-permissions/permission-dialog/permission-dialog.component.d.ts.map +1 -1
- package/dist/lib/entity-permissions/permission-dialog/permission-dialog.component.js +117 -43
- package/dist/lib/entity-permissions/permission-dialog/permission-dialog.component.js.map +1 -1
- package/dist/lib/notification-preferences/notification-preferences.component.d.ts +25 -3
- package/dist/lib/notification-preferences/notification-preferences.component.d.ts.map +1 -1
- package/dist/lib/notification-preferences/notification-preferences.component.js +62 -18
- package/dist/lib/notification-preferences/notification-preferences.component.js.map +1 -1
- package/dist/lib/role-management/role-dialog/role-dialog.component.d.ts +32 -4
- package/dist/lib/role-management/role-dialog/role-dialog.component.d.ts.map +1 -1
- package/dist/lib/role-management/role-dialog/role-dialog.component.js +84 -30
- package/dist/lib/role-management/role-dialog/role-dialog.component.js.map +1 -1
- package/dist/lib/role-management/role-management-agent-context.d.ts +12 -4
- package/dist/lib/role-management/role-management-agent-context.d.ts.map +1 -1
- package/dist/lib/role-management/role-management-agent-context.js +26 -10
- package/dist/lib/role-management/role-management-agent-context.js.map +1 -1
- package/dist/lib/role-management/role-management.component.d.ts +95 -11
- package/dist/lib/role-management/role-management.component.d.ts.map +1 -1
- package/dist/lib/role-management/role-management.component.js +259 -91
- package/dist/lib/role-management/role-management.component.js.map +1 -1
- package/dist/lib/settings/settings.component.d.ts +87 -11
- package/dist/lib/settings/settings.component.d.ts.map +1 -1
- package/dist/lib/settings/settings.component.js +211 -65
- package/dist/lib/settings/settings.component.js.map +1 -1
- package/dist/lib/shared/settings-card.component.d.ts +24 -4
- package/dist/lib/shared/settings-card.component.d.ts.map +1 -1
- package/dist/lib/shared/settings-card.component.js +46 -6
- package/dist/lib/shared/settings-card.component.js.map +1 -1
- package/dist/lib/sql-logging/sql-logging.component.d.ts +172 -18
- package/dist/lib/sql-logging/sql-logging.component.d.ts.map +1 -1
- package/dist/lib/sql-logging/sql-logging.component.js +320 -108
- package/dist/lib/sql-logging/sql-logging.component.js.map +1 -1
- package/dist/lib/user-app-config/user-app-config-content.component.js +3 -3
- package/dist/lib/user-app-config/user-app-config-content.component.js.map +1 -1
- package/dist/lib/user-app-config/user-app-config-reorder.d.ts +4 -0
- package/dist/lib/user-app-config/user-app-config-reorder.d.ts.map +1 -1
- package/dist/lib/user-app-config/user-app-config-reorder.js +11 -3
- package/dist/lib/user-app-config/user-app-config-reorder.js.map +1 -1
- package/dist/lib/user-management/transaction-group-refusals.d.ts +45 -0
- package/dist/lib/user-management/transaction-group-refusals.d.ts.map +1 -0
- package/dist/lib/user-management/transaction-group-refusals.js +49 -0
- package/dist/lib/user-management/transaction-group-refusals.js.map +1 -0
- package/dist/lib/user-management/user-dialog/user-dialog.component.d.ts +44 -6
- package/dist/lib/user-management/user-dialog/user-dialog.component.d.ts.map +1 -1
- package/dist/lib/user-management/user-dialog/user-dialog.component.js +134 -45
- package/dist/lib/user-management/user-dialog/user-dialog.component.js.map +1 -1
- package/dist/lib/user-management/user-management-agent-context.d.ts +10 -0
- package/dist/lib/user-management/user-management-agent-context.d.ts.map +1 -1
- package/dist/lib/user-management/user-management-agent-context.js +26 -6
- package/dist/lib/user-management/user-management-agent-context.js.map +1 -1
- package/dist/lib/user-management/user-management.component.d.ts +174 -16
- package/dist/lib/user-management/user-management.component.d.ts.map +1 -1
- package/dist/lib/user-management/user-management.component.js +501 -177
- package/dist/lib/user-management/user-management.component.js.map +1 -1
- package/dist/lib/user-profile-settings/user-profile-settings.component.d.ts +97 -15
- package/dist/lib/user-profile-settings/user-profile-settings.component.d.ts.map +1 -1
- package/dist/lib/user-profile-settings/user-profile-settings.component.js +280 -116
- package/dist/lib/user-profile-settings/user-profile-settings.component.js.map +1 -1
- package/package.json +20 -20
|
@@ -94,9 +94,10 @@ function ApplicationDialogComponent_Conditional_0_Conditional_36_For_5_Template(
|
|
|
94
94
|
i0.ɵɵelementEnd()()();
|
|
95
95
|
} if (rf & 2) {
|
|
96
96
|
const config_r5 = ctx.$implicit;
|
|
97
|
+
const ctx_r1 = i0.ɵɵnextContext(3);
|
|
97
98
|
i0.ɵɵclassProp("is-new", config_r5.isNew)("has-changes", config_r5.hasChanges);
|
|
98
99
|
i0.ɵɵadvance(7);
|
|
99
|
-
i0.ɵɵtextInterpolate1(" ", config_r5.entity
|
|
100
|
+
i0.ɵɵtextInterpolate1(" ", ctx_r1.getEntityDisplayName(config_r5.entity), " ");
|
|
100
101
|
i0.ɵɵadvance();
|
|
101
102
|
i0.ɵɵconditional(config_r5.entity.Description ? 8 : -1);
|
|
102
103
|
i0.ɵɵadvance(4);
|
|
@@ -107,7 +108,7 @@ function ApplicationDialogComponent_Conditional_0_Conditional_36_For_5_Template(
|
|
|
107
108
|
i0.ɵɵadvance();
|
|
108
109
|
i0.ɵɵtextInterpolate(config_r5.sequence);
|
|
109
110
|
i0.ɵɵadvance(2);
|
|
110
|
-
i0.ɵɵattribute("aria-label", "Remove " + config_r5.entity
|
|
111
|
+
i0.ɵɵattribute("aria-label", "Remove " + ctx_r1.getEntityDisplayName(config_r5.entity));
|
|
111
112
|
} }
|
|
112
113
|
function ApplicationDialogComponent_Conditional_0_Conditional_36_Template(rf, ctx) { if (rf & 1) {
|
|
113
114
|
const _r3 = i0.ɵɵgetCurrentView();
|
|
@@ -153,9 +154,10 @@ function ApplicationDialogComponent_Conditional_0_Conditional_38_Conditional_8_F
|
|
|
153
154
|
i0.ɵɵelementEnd()()();
|
|
154
155
|
} if (rf & 2) {
|
|
155
156
|
const entity_r9 = ctx.$implicit;
|
|
156
|
-
i0.ɵɵ
|
|
157
|
+
const ctx_r1 = i0.ɵɵnextContext(4);
|
|
158
|
+
i0.ɵɵattribute("aria-label", "Add " + ctx_r1.getEntityDisplayName(entity_r9));
|
|
157
159
|
i0.ɵɵadvance(4);
|
|
158
|
-
i0.ɵɵtextInterpolate(entity_r9
|
|
160
|
+
i0.ɵɵtextInterpolate(ctx_r1.getEntityDisplayName(entity_r9));
|
|
159
161
|
} }
|
|
160
162
|
function ApplicationDialogComponent_Conditional_0_Conditional_38_Conditional_8_Template(rf, ctx) { if (rf & 1) {
|
|
161
163
|
i0.ɵɵelementStart(0, "div", 66);
|
|
@@ -375,33 +377,113 @@ function ApplicationDialogComponent_Conditional_0_Template(rf, ctx) { if (rf & 1
|
|
|
375
377
|
i0.ɵɵconditional(ctx_r1.isLoading ? 43 : 44);
|
|
376
378
|
} }
|
|
377
379
|
export class ApplicationDialogComponent extends BaseAngularComponent {
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
380
|
+
Data = null;
|
|
381
|
+
/** @deprecated Use {@link Data}. */
|
|
382
|
+
set data(value) {
|
|
383
|
+
this.Data = value;
|
|
384
|
+
}
|
|
385
|
+
/** @deprecated Use {@link Data}. */
|
|
386
|
+
get data() {
|
|
387
|
+
return this.Data;
|
|
388
|
+
}
|
|
389
|
+
Visible = false;
|
|
390
|
+
/** @deprecated Use {@link Visible}. */
|
|
391
|
+
set visible(value) {
|
|
392
|
+
this.Visible = value;
|
|
393
|
+
}
|
|
394
|
+
/** @deprecated Use {@link Visible}. */
|
|
395
|
+
get visible() {
|
|
396
|
+
return this.Visible;
|
|
397
|
+
}
|
|
398
|
+
Result = new EventEmitter();
|
|
399
|
+
/**
|
|
400
|
+
* @deprecated Use {@link Result}.
|
|
401
|
+
*
|
|
402
|
+
* The same emitter under the old binding name, so a template still binding
|
|
403
|
+
* (result) keeps working. Must stay AFTER Result: class fields
|
|
404
|
+
* initialise in order, and the other way round this captures undefined.
|
|
405
|
+
*/
|
|
406
|
+
result = this.Result;
|
|
381
407
|
fb = inject(FormBuilder);
|
|
382
408
|
cdr = inject(ChangeDetectorRef);
|
|
383
409
|
ngZone = inject(NgZone);
|
|
384
410
|
get metadata() { return this.ProviderToUse; }
|
|
385
|
-
|
|
411
|
+
ApplicationForm;
|
|
412
|
+
/** @deprecated Use {@link ApplicationForm}. */
|
|
413
|
+
get applicationForm() {
|
|
414
|
+
return this.ApplicationForm;
|
|
415
|
+
}
|
|
416
|
+
/** @deprecated Use {@link ApplicationForm}. */
|
|
417
|
+
set applicationForm(value) {
|
|
418
|
+
this.ApplicationForm = value;
|
|
419
|
+
}
|
|
386
420
|
isLoading = false;
|
|
387
421
|
error = null;
|
|
388
422
|
// Entity management
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
423
|
+
ApplicationEntities = [];
|
|
424
|
+
/** @deprecated Use {@link ApplicationEntities}. */
|
|
425
|
+
get applicationEntities() {
|
|
426
|
+
return this.ApplicationEntities;
|
|
427
|
+
}
|
|
428
|
+
/** @deprecated Use {@link ApplicationEntities}. */
|
|
429
|
+
set applicationEntities(value) {
|
|
430
|
+
this.ApplicationEntities = value;
|
|
431
|
+
}
|
|
432
|
+
AvailableEntities = [];
|
|
433
|
+
/** @deprecated Use {@link AvailableEntities}. */
|
|
434
|
+
get availableEntities() {
|
|
435
|
+
return this.AvailableEntities;
|
|
436
|
+
}
|
|
437
|
+
/** @deprecated Use {@link AvailableEntities}. */
|
|
438
|
+
set availableEntities(value) {
|
|
439
|
+
this.AvailableEntities = value;
|
|
440
|
+
}
|
|
441
|
+
AllEntities = [];
|
|
442
|
+
/** @deprecated Use {@link AllEntities}. */
|
|
443
|
+
get allEntities() {
|
|
444
|
+
return this.AllEntities;
|
|
445
|
+
}
|
|
446
|
+
/** @deprecated Use {@link AllEntities}. */
|
|
447
|
+
set allEntities(value) {
|
|
448
|
+
this.AllEntities = value;
|
|
449
|
+
}
|
|
392
450
|
// Search filter for available entities
|
|
393
|
-
|
|
451
|
+
EntitySearchTerm = '';
|
|
452
|
+
/** @deprecated Use {@link EntitySearchTerm}. */
|
|
453
|
+
get entitySearchTerm() {
|
|
454
|
+
return this.EntitySearchTerm;
|
|
455
|
+
}
|
|
456
|
+
/** @deprecated Use {@link EntitySearchTerm}. */
|
|
457
|
+
set entitySearchTerm(value) {
|
|
458
|
+
this.EntitySearchTerm = value;
|
|
459
|
+
}
|
|
394
460
|
// Section expansion state
|
|
395
|
-
|
|
461
|
+
SectionExpanded = {
|
|
396
462
|
basicInfo: true,
|
|
397
463
|
entities: true,
|
|
398
464
|
systemInfo: false
|
|
399
465
|
};
|
|
466
|
+
/** @deprecated Use {@link SectionExpanded}. */
|
|
467
|
+
get sectionExpanded() {
|
|
468
|
+
return this.SectionExpanded;
|
|
469
|
+
}
|
|
470
|
+
/** @deprecated Use {@link SectionExpanded}. */
|
|
471
|
+
set sectionExpanded(value) {
|
|
472
|
+
this.SectionExpanded = value;
|
|
473
|
+
}
|
|
400
474
|
// Fullscreen state
|
|
401
|
-
|
|
475
|
+
IsFullscreen = false;
|
|
476
|
+
/** @deprecated Use {@link IsFullscreen}. */
|
|
477
|
+
get isFullscreen() {
|
|
478
|
+
return this.IsFullscreen;
|
|
479
|
+
}
|
|
480
|
+
/** @deprecated Use {@link IsFullscreen}. */
|
|
481
|
+
set isFullscreen(value) {
|
|
482
|
+
this.IsFullscreen = value;
|
|
483
|
+
}
|
|
402
484
|
constructor() {
|
|
403
485
|
super();
|
|
404
|
-
this.
|
|
486
|
+
this.ApplicationForm = this.fb.group({
|
|
405
487
|
name: ['', [Validators.required, Validators.maxLength(100)]],
|
|
406
488
|
description: ['']
|
|
407
489
|
});
|
|
@@ -410,7 +492,7 @@ export class ApplicationDialogComponent extends BaseAngularComponent {
|
|
|
410
492
|
// Initial setup
|
|
411
493
|
}
|
|
412
494
|
ngOnChanges(changes) {
|
|
413
|
-
if (changes['visible'] && this.
|
|
495
|
+
if (changes['visible'] && this.Visible) {
|
|
414
496
|
this.initializeDialog();
|
|
415
497
|
}
|
|
416
498
|
}
|
|
@@ -418,14 +500,14 @@ export class ApplicationDialogComponent extends BaseAngularComponent {
|
|
|
418
500
|
// Cleanup if needed
|
|
419
501
|
}
|
|
420
502
|
async initializeDialog() {
|
|
421
|
-
if (!this.
|
|
503
|
+
if (!this.Visible)
|
|
422
504
|
return;
|
|
423
505
|
try {
|
|
424
506
|
this.isLoading = true;
|
|
425
507
|
this.error = null;
|
|
426
508
|
// Load all entities first
|
|
427
509
|
await this.loadAllEntities();
|
|
428
|
-
if (this.
|
|
510
|
+
if (this.Data?.application && this.IsEditMode) {
|
|
429
511
|
await this.loadApplicationData();
|
|
430
512
|
}
|
|
431
513
|
else {
|
|
@@ -453,34 +535,42 @@ export class ApplicationDialogComponent extends BaseAngularComponent {
|
|
|
453
535
|
ResultType: 'entity_object',
|
|
454
536
|
OrderBy: 'Name ASC'
|
|
455
537
|
});
|
|
456
|
-
this.
|
|
538
|
+
this.AllEntities = result.Success ? result.Results : [];
|
|
457
539
|
}
|
|
458
540
|
resetForm() {
|
|
459
|
-
this.
|
|
541
|
+
this.ApplicationForm.reset({
|
|
460
542
|
name: '',
|
|
461
543
|
description: ''
|
|
462
544
|
});
|
|
463
|
-
this.
|
|
464
|
-
this.
|
|
465
|
-
this.
|
|
545
|
+
this.ApplicationEntities = [];
|
|
546
|
+
this.AvailableEntities = [...this.AllEntities];
|
|
547
|
+
this.EntitySearchTerm = '';
|
|
466
548
|
this.error = null;
|
|
467
549
|
}
|
|
468
550
|
onEscapeKey() {
|
|
469
|
-
if (this.
|
|
551
|
+
if (this.Visible) {
|
|
470
552
|
this.onCancel();
|
|
471
553
|
}
|
|
472
554
|
}
|
|
555
|
+
get WindowTitle() {
|
|
556
|
+
return this.IsEditMode ? 'Edit Application' : 'Create New Application';
|
|
557
|
+
}
|
|
558
|
+
/** @deprecated Use {@link WindowTitle}. */
|
|
473
559
|
get windowTitle() {
|
|
474
|
-
return this.
|
|
560
|
+
return this.WindowTitle;
|
|
561
|
+
}
|
|
562
|
+
get IsEditMode() {
|
|
563
|
+
return this.Data?.mode === 'edit';
|
|
475
564
|
}
|
|
565
|
+
/** @deprecated Use {@link IsEditMode}. */
|
|
476
566
|
get isEditMode() {
|
|
477
|
-
return this.
|
|
567
|
+
return this.IsEditMode;
|
|
478
568
|
}
|
|
479
569
|
async loadApplicationData() {
|
|
480
|
-
if (!this.
|
|
570
|
+
if (!this.Data?.application)
|
|
481
571
|
return;
|
|
482
|
-
const app = this.
|
|
483
|
-
this.
|
|
572
|
+
const app = this.Data.application;
|
|
573
|
+
this.ApplicationForm.patchValue({
|
|
484
574
|
name: app.Name,
|
|
485
575
|
description: app.Description
|
|
486
576
|
});
|
|
@@ -497,12 +587,12 @@ export class ApplicationDialogComponent extends BaseAngularComponent {
|
|
|
497
587
|
OrderBy: 'Sequence ASC'
|
|
498
588
|
});
|
|
499
589
|
if (result.Success && result.Results) {
|
|
500
|
-
this.
|
|
590
|
+
this.ApplicationEntities = [];
|
|
501
591
|
const usedEntityIds = new Set();
|
|
502
592
|
for (const appEntity of result.Results) {
|
|
503
|
-
const entity = this.
|
|
593
|
+
const entity = this.AllEntities.find(e => UUIDsEqual(e.ID, appEntity.EntityID));
|
|
504
594
|
if (entity) {
|
|
505
|
-
this.
|
|
595
|
+
this.ApplicationEntities.push({
|
|
506
596
|
entity,
|
|
507
597
|
applicationEntity: appEntity,
|
|
508
598
|
sequence: appEntity.Sequence || 0,
|
|
@@ -514,19 +604,19 @@ export class ApplicationDialogComponent extends BaseAngularComponent {
|
|
|
514
604
|
}
|
|
515
605
|
}
|
|
516
606
|
// Set available entities (excluding already assigned ones)
|
|
517
|
-
this.
|
|
607
|
+
this.AvailableEntities = this.AllEntities.filter(e => !usedEntityIds.has(e.ID));
|
|
518
608
|
}
|
|
519
609
|
}
|
|
520
610
|
catch (error) {
|
|
521
611
|
console.warn('Failed to load application entities:', error);
|
|
522
|
-
this.
|
|
612
|
+
this.AvailableEntities = [...this.AllEntities];
|
|
523
613
|
}
|
|
524
614
|
}
|
|
525
|
-
|
|
615
|
+
AddEntity(entity) {
|
|
526
616
|
// Add entity to application
|
|
527
|
-
this.
|
|
617
|
+
this.ApplicationEntities.push({
|
|
528
618
|
entity,
|
|
529
|
-
sequence: this.
|
|
619
|
+
sequence: this.ApplicationEntities.length + 1,
|
|
530
620
|
defaultForNewUser: false,
|
|
531
621
|
isNew: true,
|
|
532
622
|
hasChanges: false
|
|
@@ -534,91 +624,143 @@ export class ApplicationDialogComponent extends BaseAngularComponent {
|
|
|
534
624
|
// Update all sequences to be consecutive
|
|
535
625
|
this.updateSequences();
|
|
536
626
|
// Remove from available entities
|
|
537
|
-
this.
|
|
627
|
+
this.AvailableEntities = this.AvailableEntities.filter(e => !UUIDsEqual(e.ID, entity.ID));
|
|
538
628
|
}
|
|
539
|
-
|
|
629
|
+
/** @deprecated Use {@link AddEntity}. */
|
|
630
|
+
addEntity(entity) {
|
|
631
|
+
return this.AddEntity(entity);
|
|
632
|
+
}
|
|
633
|
+
RemoveEntity(config) {
|
|
540
634
|
// Remove from application entities
|
|
541
|
-
this.
|
|
635
|
+
this.ApplicationEntities = this.ApplicationEntities.filter(ae => !UUIDsEqual(ae.entity.ID, config.entity.ID));
|
|
542
636
|
// Update all sequences to be consecutive
|
|
543
637
|
this.updateSequences();
|
|
544
638
|
// Add back to available entities if not already there
|
|
545
|
-
if (!this.
|
|
546
|
-
this.
|
|
547
|
-
this.
|
|
639
|
+
if (!this.AvailableEntities.find(e => UUIDsEqual(e.ID, config.entity.ID))) {
|
|
640
|
+
this.AvailableEntities.push(config.entity);
|
|
641
|
+
this.AvailableEntities.sort((a, b) => (a.Name || '').localeCompare(b.Name || ''));
|
|
548
642
|
}
|
|
549
643
|
}
|
|
550
|
-
|
|
644
|
+
/** @deprecated Use {@link RemoveEntity}. */
|
|
645
|
+
removeEntity(config) {
|
|
646
|
+
return this.RemoveEntity(config);
|
|
647
|
+
}
|
|
648
|
+
MoveEntityUp(index) {
|
|
551
649
|
if (index > 0) {
|
|
552
|
-
const temp = this.
|
|
553
|
-
this.
|
|
554
|
-
this.
|
|
650
|
+
const temp = this.ApplicationEntities[index];
|
|
651
|
+
this.ApplicationEntities[index] = this.ApplicationEntities[index - 1];
|
|
652
|
+
this.ApplicationEntities[index - 1] = temp;
|
|
555
653
|
this.updateSequences();
|
|
556
654
|
}
|
|
557
655
|
}
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
656
|
+
/** @deprecated Use {@link MoveEntityUp}. */
|
|
657
|
+
moveEntityUp(index) {
|
|
658
|
+
return this.MoveEntityUp(index);
|
|
659
|
+
}
|
|
660
|
+
MoveEntityDown(index) {
|
|
661
|
+
if (index < this.ApplicationEntities.length - 1) {
|
|
662
|
+
const temp = this.ApplicationEntities[index];
|
|
663
|
+
this.ApplicationEntities[index] = this.ApplicationEntities[index + 1];
|
|
664
|
+
this.ApplicationEntities[index + 1] = temp;
|
|
563
665
|
this.updateSequences();
|
|
564
666
|
}
|
|
565
667
|
}
|
|
668
|
+
/** @deprecated Use {@link MoveEntityDown}. */
|
|
669
|
+
moveEntityDown(index) {
|
|
670
|
+
return this.MoveEntityDown(index);
|
|
671
|
+
}
|
|
566
672
|
updateSequences() {
|
|
567
|
-
this.
|
|
673
|
+
this.ApplicationEntities.forEach((config, index) => {
|
|
568
674
|
config.sequence = index + 1;
|
|
569
675
|
if (!config.isNew) {
|
|
570
676
|
config.hasChanges = true;
|
|
571
677
|
}
|
|
572
678
|
});
|
|
573
679
|
}
|
|
574
|
-
|
|
680
|
+
OnDefaultForNewUserChange(config) {
|
|
575
681
|
if (!config.isNew) {
|
|
576
682
|
config.hasChanges = true;
|
|
577
683
|
}
|
|
578
684
|
}
|
|
685
|
+
/** @deprecated Use {@link OnDefaultForNewUserChange}. */
|
|
686
|
+
onDefaultForNewUserChange(config) {
|
|
687
|
+
return this.OnDefaultForNewUserChange(config);
|
|
688
|
+
}
|
|
689
|
+
get HasEntityChanges() {
|
|
690
|
+
return this.ApplicationEntities.some(ae => ae.isNew || ae.hasChanges);
|
|
691
|
+
}
|
|
692
|
+
/** @deprecated Use {@link HasEntityChanges}. */
|
|
579
693
|
get hasEntityChanges() {
|
|
580
|
-
return this.
|
|
694
|
+
return this.HasEntityChanges;
|
|
581
695
|
}
|
|
582
696
|
// Filtered available entities based on search term
|
|
583
|
-
get
|
|
584
|
-
if (!this.
|
|
585
|
-
return this.
|
|
697
|
+
get FilteredAvailableEntities() {
|
|
698
|
+
if (!this.EntitySearchTerm || !this.EntitySearchTerm.trim()) {
|
|
699
|
+
return this.AvailableEntities;
|
|
586
700
|
}
|
|
587
|
-
const searchLower = this.
|
|
588
|
-
return this.
|
|
701
|
+
const searchLower = this.EntitySearchTerm.toLowerCase().trim();
|
|
702
|
+
return this.AvailableEntities.filter(entity => (entity.DisplayName || '').toLowerCase().includes(searchLower) ||
|
|
703
|
+
(entity.Name || '').toLowerCase().includes(searchLower) ||
|
|
589
704
|
(entity.Description || '').toLowerCase().includes(searchLower));
|
|
590
705
|
}
|
|
591
|
-
|
|
706
|
+
/** @deprecated Use {@link FilteredAvailableEntities}. */
|
|
707
|
+
get filteredAvailableEntities() {
|
|
708
|
+
return this.FilteredAvailableEntities;
|
|
709
|
+
}
|
|
710
|
+
GetEntityDisplayName(entity) {
|
|
711
|
+
return entity.DisplayName || entity.Name;
|
|
712
|
+
}
|
|
713
|
+
/** @deprecated Use {@link GetEntityDisplayName}. */
|
|
714
|
+
getEntityDisplayName(entity) {
|
|
715
|
+
return this.GetEntityDisplayName(entity);
|
|
716
|
+
}
|
|
717
|
+
OnEntitySearchChange(event) {
|
|
592
718
|
const value = event.target.value;
|
|
593
|
-
this.
|
|
719
|
+
this.EntitySearchTerm = value;
|
|
720
|
+
}
|
|
721
|
+
/** @deprecated Use {@link OnEntitySearchChange}. */
|
|
722
|
+
onEntitySearchChange(event) {
|
|
723
|
+
return this.OnEntitySearchChange(event);
|
|
724
|
+
}
|
|
725
|
+
ClearEntitySearch() {
|
|
726
|
+
this.EntitySearchTerm = '';
|
|
594
727
|
}
|
|
728
|
+
/** @deprecated Use {@link ClearEntitySearch}. */
|
|
595
729
|
clearEntitySearch() {
|
|
596
|
-
this.
|
|
730
|
+
return this.ClearEntitySearch();
|
|
597
731
|
}
|
|
598
732
|
toggleSection(section) {
|
|
599
|
-
this.
|
|
733
|
+
this.SectionExpanded[section] = !this.SectionExpanded[section];
|
|
600
734
|
}
|
|
735
|
+
ToggleFullscreen() {
|
|
736
|
+
this.IsFullscreen = !this.IsFullscreen;
|
|
737
|
+
}
|
|
738
|
+
/** @deprecated Use {@link ToggleFullscreen}. */
|
|
601
739
|
toggleFullscreen() {
|
|
602
|
-
|
|
740
|
+
return this.ToggleFullscreen();
|
|
603
741
|
}
|
|
604
|
-
|
|
742
|
+
OnEntityDrop(event) {
|
|
605
743
|
if (event.previousIndex !== event.currentIndex) {
|
|
606
|
-
moveItemInArray(this.
|
|
744
|
+
moveItemInArray(this.ApplicationEntities, event.previousIndex, event.currentIndex);
|
|
607
745
|
this.updateSequences();
|
|
608
746
|
}
|
|
609
747
|
}
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
748
|
+
/** @deprecated Use {@link OnEntityDrop}. */
|
|
749
|
+
onEntityDrop(event) {
|
|
750
|
+
return this.OnEntityDrop(event);
|
|
751
|
+
}
|
|
752
|
+
async OnSubmit() {
|
|
753
|
+
if (this.ApplicationForm.invalid) {
|
|
754
|
+
this.markFormGroupTouched(this.ApplicationForm);
|
|
613
755
|
return;
|
|
614
756
|
}
|
|
615
757
|
this.isLoading = true;
|
|
616
758
|
this.error = null;
|
|
617
759
|
try {
|
|
618
760
|
let application;
|
|
619
|
-
if (this.
|
|
761
|
+
if (this.IsEditMode && this.Data?.application) {
|
|
620
762
|
// Edit existing application
|
|
621
|
-
application = this.
|
|
763
|
+
application = this.Data.application;
|
|
622
764
|
}
|
|
623
765
|
else {
|
|
624
766
|
// Create new application
|
|
@@ -626,7 +768,7 @@ export class ApplicationDialogComponent extends BaseAngularComponent {
|
|
|
626
768
|
application.NewRecord();
|
|
627
769
|
}
|
|
628
770
|
// Update application properties
|
|
629
|
-
const formValue = this.
|
|
771
|
+
const formValue = this.ApplicationForm.value;
|
|
630
772
|
application.Name = formValue.name;
|
|
631
773
|
application.Description = formValue.description || null;
|
|
632
774
|
// Save application
|
|
@@ -635,10 +777,10 @@ export class ApplicationDialogComponent extends BaseAngularComponent {
|
|
|
635
777
|
throw new Error(application.LatestResult?.Message || 'Failed to save application');
|
|
636
778
|
}
|
|
637
779
|
// Save application entities if there are changes
|
|
638
|
-
if (this.
|
|
780
|
+
if (this.HasEntityChanges) {
|
|
639
781
|
await this.saveApplicationEntities(application.ID);
|
|
640
782
|
}
|
|
641
|
-
this.
|
|
783
|
+
this.Result.emit({ action: 'save', application });
|
|
642
784
|
}
|
|
643
785
|
catch (error) {
|
|
644
786
|
console.error('Error saving application:', error);
|
|
@@ -654,9 +796,13 @@ export class ApplicationDialogComponent extends BaseAngularComponent {
|
|
|
654
796
|
});
|
|
655
797
|
}
|
|
656
798
|
}
|
|
799
|
+
/** @deprecated Use {@link OnSubmit}. */
|
|
800
|
+
async onSubmit() {
|
|
801
|
+
return this.OnSubmit();
|
|
802
|
+
}
|
|
657
803
|
async saveApplicationEntities(applicationId) {
|
|
658
804
|
// Save or update each MJApplicationEntity record
|
|
659
|
-
for (const config of this.
|
|
805
|
+
for (const config of this.ApplicationEntities) {
|
|
660
806
|
if (config.isNew || config.hasChanges) {
|
|
661
807
|
let appEntity;
|
|
662
808
|
if (config.isNew) {
|
|
@@ -683,7 +829,7 @@ export class ApplicationDialogComponent extends BaseAngularComponent {
|
|
|
683
829
|
}
|
|
684
830
|
}
|
|
685
831
|
onCancel() {
|
|
686
|
-
this.
|
|
832
|
+
this.Result.emit({ action: 'cancel' });
|
|
687
833
|
}
|
|
688
834
|
markFormGroupTouched(formGroup) {
|
|
689
835
|
Object.keys(formGroup.controls).forEach(key => {
|
|
@@ -694,7 +840,7 @@ export class ApplicationDialogComponent extends BaseAngularComponent {
|
|
|
694
840
|
static ɵfac = function ApplicationDialogComponent_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || ApplicationDialogComponent)(); };
|
|
695
841
|
static ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: ApplicationDialogComponent, selectors: [["mj-application-dialog"]], hostBindings: function ApplicationDialogComponent_HostBindings(rf, ctx) { if (rf & 1) {
|
|
696
842
|
i0.ɵɵlistener("keydown.escape", function ApplicationDialogComponent_keydown_escape_HostBindingHandler() { return ctx.onEscapeKey(); }, i0.ɵɵresolveDocument);
|
|
697
|
-
} }, inputs: { data: "data", visible: "visible" }, outputs: { result: "result" }, standalone: false, features: [i0.ɵɵInheritDefinitionFeature, i0.ɵɵNgOnChangesFeature], decls: 1, vars: 1, consts: [["role", "presentation", 1, "modal-backdrop"], ["role", "presentation", 1, "modal-backdrop", 3, "click"], ["role", "dialog", "aria-modal", "true", "aria-labelledby", "application-dialog-title", "aria-describedby", "application-dialog-description", 1, "modal-dialog", 3, "click"], [1, "modal-header"], [1, "modal-header-content"], ["id", "application-dialog-title", 1, "modal-title"], ["aria-hidden", "true", 1, "fa-solid"], ["id", "application-dialog-description", 1, "modal-subtitle"], [1, "modal-header-actions"], ["type", "button", 1, "modal-action-btn", 3, "click"], ["aria-label", "Close dialog", "type", "button", 1, "modal-close", 3, "click"], ["aria-hidden", "true", 1, "fa-solid", "fa-times"], [1, "modal-body"], [3, "ngSubmit", "formGroup"], [1, "form-section"], [1, "app-dialog-section", 3, "ExpandedChange", "Expanded"], ["mjAccordionTitle", ""], [1, "section-content"], [1, "form-grid"], [1, "form-field"], ["for", "app-name", 1, "field-label", "required"], ["id", "app-name", "type", "text", "formControlName", "name", "placeholder", "My Application", "aria-describedby", "app-name-error", 1, "field-input"], ["id", "app-name-error", "role", "alert", 1, "field-error"], ["for", "app-description", 1, "field-label"], ["id", "app-description", "formControlName", "description", "placeholder", "Describe the application's purpose and functionality...", "rows", "4", 1, "field-textarea"], [1, "field-hint"], [1, "assigned-entities"], [1, "no-entities"], [1, "available-entities"], [1, "all-assigned"], [1, "app-dialog-section", 3, "Expanded"], [1, "modal-footer"], ["type", "submit", "mjButton", "", "variant", "primary", 3, "click", "disabled"], ["type", "button", "mjButton", "", 3, "click"], ["Variant", "error", 3, "Message"], [1, "app-dialog-section-title"], [1, "section-title"], ["aria-hidden", "true", 1, "fa-solid", "fa-info-circle"], [1, "section-description"], ["aria-hidden", "true", 1, "fa-solid", "fa-exclamation-circle"], ["aria-hidden", "true", 1, "fa-solid", "fa-database"], [1, "subsection-title"], ["role", "list", "cdkDropList", "", 1, "entities-list", 3, "cdkDropListDropped"], ["role", "listitem", "cdkDrag", "", 1, "entity-item", 3, "is-new", "has-changes"], ["role", "listitem", "cdkDrag", "", 1, "entity-item"], ["aria-hidden", "true", 1, "fa-solid", "fa-grip-vertical", "drag-handle"], [1, "entity-info"], [1, "entity-icon"], ["aria-hidden", "true", 1, "fa-solid", "fa-table"], [1, "entity-details"], [1, "entity-name"], [1, "fa-solid", "fa-info-circle", "entity-info-icon", 3, "title"], [1, "entity-config"], [1, "config-item"], [1, "config-label"], ["type", "checkbox", 1, "config-checkbox", 3, "ngModelChange", "change", "ngModel", "ngModelOptions"], [1, "checkbox-text"], [1, "sequence-badge"], [1, "entity-actions"], ["type", "button", "title", "Remove entity", 1, "btn-icon", "btn-danger", 3, "click"], [1, "empty-subtext"], [1, "available-entities-header"], [1, "entity-search"], ["aria-hidden", "true", 1, "fa-solid", "fa-search", "entity-search-icon"], ["type", "text", "placeholder", "Search entities...", "aria-label", "Search available entities", 1, "entity-search-input", 3, "input", "value"], ["type", "button", "aria-label", "Clear search", 1, "entity-search-clear"], ["role", "list", 1, "entities-grid"], [1, "no-search-results"], ["type", "button", "aria-label", "Clear search", 1, "entity-search-clear", 3, "click"], ["type", "button", "role", "listitem", 1, "entity-chip"], ["type", "button", "role", "listitem", 1, "entity-chip", 3, "click"], [1, "chip-content"], ["aria-hidden", "true", 1, "fa-solid", "fa-plus"], [1, "chip-name"], ["aria-hidden", "true", 1, "fa-solid", "fa-search"], ["aria-hidden", "true", 1, "fa-solid", "fa-check-circle"], [1, "info-grid"], [1, "info-item"], [1, "info-label"], [1, "info-value"], ["aria-hidden", "true", 1, "fa-solid", "fa-spinner", "fa-spin"], ["aria-hidden", "true", 1, "fa-solid", "fa-save"]], template: function ApplicationDialogComponent_Template(rf, ctx) { if (rf & 1) {
|
|
843
|
+
} }, inputs: { Data: "Data", data: "data", Visible: "Visible", visible: "visible" }, outputs: { Result: "Result", result: "result" }, standalone: false, features: [i0.ɵɵInheritDefinitionFeature, i0.ɵɵNgOnChangesFeature], decls: 1, vars: 1, consts: [["role", "presentation", 1, "modal-backdrop"], ["role", "presentation", 1, "modal-backdrop", 3, "click"], ["role", "dialog", "aria-modal", "true", "aria-labelledby", "application-dialog-title", "aria-describedby", "application-dialog-description", 1, "modal-dialog", 3, "click"], [1, "modal-header"], [1, "modal-header-content"], ["id", "application-dialog-title", 1, "modal-title"], ["aria-hidden", "true", 1, "fa-solid"], ["id", "application-dialog-description", 1, "modal-subtitle"], [1, "modal-header-actions"], ["type", "button", 1, "modal-action-btn", 3, "click"], ["aria-label", "Close dialog", "type", "button", 1, "modal-close", 3, "click"], ["aria-hidden", "true", 1, "fa-solid", "fa-times"], [1, "modal-body"], [3, "ngSubmit", "formGroup"], [1, "form-section"], [1, "app-dialog-section", 3, "ExpandedChange", "Expanded"], ["mjAccordionTitle", ""], [1, "section-content"], [1, "form-grid"], [1, "form-field"], ["for", "app-name", 1, "field-label", "required"], ["id", "app-name", "type", "text", "formControlName", "name", "placeholder", "My Application", "aria-describedby", "app-name-error", 1, "field-input"], ["id", "app-name-error", "role", "alert", 1, "field-error"], ["for", "app-description", 1, "field-label"], ["id", "app-description", "formControlName", "description", "placeholder", "Describe the application's purpose and functionality...", "rows", "4", 1, "field-textarea"], [1, "field-hint"], [1, "assigned-entities"], [1, "no-entities"], [1, "available-entities"], [1, "all-assigned"], [1, "app-dialog-section", 3, "Expanded"], [1, "modal-footer"], ["type", "submit", "mjButton", "", "variant", "primary", 3, "click", "disabled"], ["type", "button", "mjButton", "", 3, "click"], ["Variant", "error", 3, "Message"], [1, "app-dialog-section-title"], [1, "section-title"], ["aria-hidden", "true", 1, "fa-solid", "fa-info-circle"], [1, "section-description"], ["aria-hidden", "true", 1, "fa-solid", "fa-exclamation-circle"], ["aria-hidden", "true", 1, "fa-solid", "fa-database"], [1, "subsection-title"], ["role", "list", "cdkDropList", "", 1, "entities-list", 3, "cdkDropListDropped"], ["role", "listitem", "cdkDrag", "", 1, "entity-item", 3, "is-new", "has-changes"], ["role", "listitem", "cdkDrag", "", 1, "entity-item"], ["aria-hidden", "true", 1, "fa-solid", "fa-grip-vertical", "drag-handle"], [1, "entity-info"], [1, "entity-icon"], ["aria-hidden", "true", 1, "fa-solid", "fa-table"], [1, "entity-details"], [1, "entity-name"], [1, "fa-solid", "fa-info-circle", "entity-info-icon", 3, "title"], [1, "entity-config"], [1, "config-item"], [1, "config-label"], ["type", "checkbox", 1, "config-checkbox", 3, "ngModelChange", "change", "ngModel", "ngModelOptions"], [1, "checkbox-text"], [1, "sequence-badge"], [1, "entity-actions"], ["type", "button", "title", "Remove entity", 1, "btn-icon", "btn-danger", 3, "click"], [1, "empty-subtext"], [1, "available-entities-header"], [1, "entity-search"], ["aria-hidden", "true", 1, "fa-solid", "fa-search", "entity-search-icon"], ["type", "text", "placeholder", "Search entities...", "aria-label", "Search available entities", 1, "entity-search-input", 3, "input", "value"], ["type", "button", "aria-label", "Clear search", 1, "entity-search-clear"], ["role", "list", 1, "entities-grid"], [1, "no-search-results"], ["type", "button", "aria-label", "Clear search", 1, "entity-search-clear", 3, "click"], ["type", "button", "role", "listitem", 1, "entity-chip"], ["type", "button", "role", "listitem", 1, "entity-chip", 3, "click"], [1, "chip-content"], ["aria-hidden", "true", 1, "fa-solid", "fa-plus"], [1, "chip-name"], ["aria-hidden", "true", 1, "fa-solid", "fa-search"], ["aria-hidden", "true", 1, "fa-solid", "fa-check-circle"], [1, "info-grid"], [1, "info-item"], [1, "info-label"], [1, "info-value"], ["aria-hidden", "true", 1, "fa-solid", "fa-spinner", "fa-spin"], ["aria-hidden", "true", 1, "fa-solid", "fa-save"]], template: function ApplicationDialogComponent_Template(rf, ctx) { if (rf & 1) {
|
|
698
844
|
i0.ɵɵconditionalCreate(0, ApplicationDialogComponent_Conditional_0_Template, 47, 31, "div", 0);
|
|
699
845
|
} if (rf & 2) {
|
|
700
846
|
i0.ɵɵconditional(ctx.visible ? 0 : -1);
|
|
@@ -702,11 +848,17 @@ export class ApplicationDialogComponent extends BaseAngularComponent {
|
|
|
702
848
|
}
|
|
703
849
|
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(ApplicationDialogComponent, [{
|
|
704
850
|
type: Component,
|
|
705
|
-
args: [{ standalone: false, selector: 'mj-application-dialog', template: "@if (visible) {\n <div class=\"modal-backdrop\" (click)=\"onCancel()\" role=\"presentation\">\n <div\n class=\"modal-dialog\"\n [class.fullscreen]=\"isFullscreen\"\n (click)=\"$event.stopPropagation()\"\n role=\"dialog\"\n aria-modal=\"true\"\n aria-labelledby=\"application-dialog-title\"\n aria-describedby=\"application-dialog-description\"\n >\n <!-- Modal Header -->\n <div class=\"modal-header\" [class.create-mode]=\"!isEditMode\" [class.edit-mode]=\"isEditMode\">\n <div class=\"modal-header-content\">\n <h2 class=\"modal-title\" id=\"application-dialog-title\">\n <i class=\"fa-solid\" [class.fa-plus]=\"!isEditMode\" [class.fa-layer-group]=\"isEditMode\" aria-hidden=\"true\"></i>\n {{ isEditMode ? 'Edit Application' : 'Create New Application' }}\n </h2>\n <p class=\"modal-subtitle\" id=\"application-dialog-description\">\n {{ isEditMode ? 'Update application information and settings' : 'Add a new application to the system' }}\n </p>\n </div>\n <div class=\"modal-header-actions\">\n <button\n class=\"modal-action-btn\"\n (click)=\"toggleFullscreen()\"\n [attr.aria-label]=\"isFullscreen ? 'Exit fullscreen' : 'Enter fullscreen'\"\n type=\"button\"\n >\n <i class=\"fa-solid\" [class.fa-compress]=\"isFullscreen\" [class.fa-expand]=\"!isFullscreen\" aria-hidden=\"true\"></i>\n </button>\n <button\n class=\"modal-close\"\n (click)=\"onCancel()\"\n aria-label=\"Close dialog\"\n type=\"button\"\n >\n <i class=\"fa-solid fa-times\" aria-hidden=\"true\"></i>\n </button>\n </div>\n </div>\n\n <!-- Modal Body -->\n <div class=\"modal-body\">\n <form [formGroup]=\"applicationForm\" (ngSubmit)=\"onSubmit()\">\n <!-- Error Alert -->\n @if (error) {\n <div class=\"form-section\">\n <mj-alert Variant=\"error\" [Message]=\"error\" />\n </div>\n }\n\n <!-- Basic Information Section -->\n <mj-accordion-panel class=\"app-dialog-section\" [Expanded]=\"sectionExpanded.basicInfo\" (ExpandedChange)=\"toggleSection('basicInfo')\">\n <ng-template mjAccordionTitle>\n <span class=\"app-dialog-section-title\">\n <span class=\"section-title\">\n <i class=\"fa-solid fa-info-circle\" aria-hidden=\"true\"></i>\n Application Information\n </span>\n <span class=\"section-description\">Define the application's basic properties</span>\n </span>\n </ng-template>\n\n <div class=\"section-content\">\n <div class=\"form-grid\">\n <div class=\"form-field\">\n <label class=\"field-label required\" for=\"app-name\">Application Name</label>\n <input\n id=\"app-name\"\n type=\"text\"\n class=\"field-input\"\n formControlName=\"name\"\n placeholder=\"My Application\"\n [class.error]=\"applicationForm.get('name')?.invalid && applicationForm.get('name')?.touched\"\n [attr.aria-invalid]=\"applicationForm.get('name')?.invalid && applicationForm.get('name')?.touched\"\n aria-describedby=\"app-name-error\"\n />\n @if (applicationForm.get('name')?.invalid && applicationForm.get('name')?.touched) {\n <div class=\"field-error\" id=\"app-name-error\" role=\"alert\">\n <i class=\"fa-solid fa-exclamation-circle\" aria-hidden=\"true\"></i>\n @if (applicationForm.get('name')?.errors?.['required']) {\n Application name is required\n }\n @if (applicationForm.get('name')?.errors?.['maxlength']) {\n Application name cannot exceed 100 characters\n }\n </div>\n }\n </div>\n\n <div class=\"form-field\">\n <label class=\"field-label\" for=\"app-description\">Description</label>\n <textarea\n id=\"app-description\"\n class=\"field-textarea\"\n formControlName=\"description\"\n placeholder=\"Describe the application's purpose and functionality...\"\n rows=\"4\"\n ></textarea>\n <div class=\"field-hint\">\n Provide a clear description of what this application does and its main features.\n </div>\n </div>\n </div>\n </div>\n </mj-accordion-panel>\n\n <!-- Entity Management Section -->\n <mj-accordion-panel class=\"app-dialog-section\" [Expanded]=\"sectionExpanded.entities\" (ExpandedChange)=\"toggleSection('entities')\">\n <ng-template mjAccordionTitle>\n <span class=\"app-dialog-section-title\">\n <span class=\"section-title\">\n <i class=\"fa-solid fa-database\" aria-hidden=\"true\"></i>\n Application Entities\n </span>\n <span class=\"section-description\">Manage which entities are included in this application</span>\n </span>\n </ng-template>\n\n <div class=\"section-content\">\n <div class=\"form-grid\">\n <!-- Assigned Entities -->\n @if (applicationEntities.length > 0) {\n <div class=\"assigned-entities\">\n <h4 class=\"subsection-title\">Assigned Entities ({{ applicationEntities.length }})</h4>\n <div class=\"entities-list\"\n role=\"list\"\n cdkDropList\n (cdkDropListDropped)=\"onEntityDrop($event)\">\n @for (config of applicationEntities; track config.entity.ID; let i = $index) {\n <div\n class=\"entity-item\"\n [class.is-new]=\"config.isNew\"\n [class.has-changes]=\"config.hasChanges\"\n role=\"listitem\"\n cdkDrag\n >\n <i class=\"fa-solid fa-grip-vertical drag-handle\" aria-hidden=\"true\"></i>\n <div class=\"entity-info\">\n <div class=\"entity-icon\">\n <i class=\"fa-solid fa-table\" aria-hidden=\"true\"></i>\n </div>\n <div class=\"entity-details\">\n <div class=\"entity-name\">\n {{ config.entity.Name }}\n @if (config.entity.Description) {\n <i\n class=\"fa-solid fa-info-circle entity-info-icon\"\n [title]=\"config.entity.Description\"\n [attr.aria-label]=\"'Entity description: ' + config.entity.Description\"\n ></i>\n }\n </div>\n </div>\n </div>\n\n <div class=\"entity-config\">\n <div class=\"config-item\">\n <label class=\"config-label\">\n <input\n type=\"checkbox\"\n [(ngModel)]=\"config.defaultForNewUser\"\n [ngModelOptions]=\"{standalone: true}\"\n (change)=\"onDefaultForNewUserChange(config)\"\n class=\"config-checkbox\"\n />\n <span class=\"checkbox-text\">Default for new users</span>\n </label>\n </div>\n <div class=\"sequence-badge\" [attr.aria-label]=\"'Sequence ' + config.sequence\">{{ config.sequence }}</div>\n </div>\n\n <div class=\"entity-actions\">\n <button\n type=\"button\"\n class=\"btn-icon btn-danger\"\n (click)=\"removeEntity(config)\"\n title=\"Remove entity\"\n [attr.aria-label]=\"'Remove ' + config.entity.Name\"\n >\n <i class=\"fa-solid fa-times\" aria-hidden=\"true\"></i>\n </button>\n </div>\n </div>\n }\n </div>\n </div>\n } @else {\n <div class=\"no-entities\">\n <i class=\"fa-solid fa-database\" aria-hidden=\"true\"></i>\n <p>No entities assigned to this application</p>\n <p class=\"empty-subtext\">Add entities from the available list below</p>\n </div>\n }\n\n <!-- Available Entities -->\n @if (availableEntities.length > 0) {\n <div class=\"available-entities\">\n <div class=\"available-entities-header\">\n <h4 class=\"subsection-title\">Available Entities ({{ filteredAvailableEntities.length }} of {{ availableEntities.length }})</h4>\n <div class=\"entity-search\">\n <i class=\"fa-solid fa-search entity-search-icon\" aria-hidden=\"true\"></i>\n <input\n type=\"text\"\n class=\"entity-search-input\"\n placeholder=\"Search entities...\"\n [value]=\"entitySearchTerm\"\n (input)=\"onEntitySearchChange($event)\"\n aria-label=\"Search available entities\"\n />\n @if (entitySearchTerm) {\n <button\n type=\"button\"\n class=\"entity-search-clear\"\n (click)=\"clearEntitySearch()\"\n aria-label=\"Clear search\"\n >\n <i class=\"fa-solid fa-times\" aria-hidden=\"true\"></i>\n </button>\n }\n </div>\n </div>\n @if (filteredAvailableEntities.length > 0) {\n <div class=\"entities-grid\" role=\"list\">\n @for (entity of filteredAvailableEntities; track entity.ID) {\n <button\n type=\"button\"\n class=\"entity-chip\"\n (click)=\"addEntity(entity)\"\n role=\"listitem\"\n [attr.aria-label]=\"'Add ' + entity.Name\"\n >\n <div class=\"chip-content\">\n <i class=\"fa-solid fa-plus\" aria-hidden=\"true\"></i>\n <span class=\"chip-name\">{{ entity.Name }}</span>\n </div>\n </button>\n }\n </div>\n } @else {\n <div class=\"no-search-results\">\n <i class=\"fa-solid fa-search\" aria-hidden=\"true\"></i>\n <p>No entities match \"{{ entitySearchTerm }}\"</p>\n </div>\n }\n </div>\n } @else {\n <div class=\"all-assigned\">\n <i class=\"fa-solid fa-check-circle\" aria-hidden=\"true\"></i>\n <p>All entities have been assigned to this application</p>\n </div>\n }\n </div>\n </div>\n </mj-accordion-panel>\n\n <!-- System Information Section (Edit Mode Only) -->\n @if (isEditMode) {\n <mj-accordion-panel class=\"app-dialog-section\" [Expanded]=\"sectionExpanded.systemInfo\" (ExpandedChange)=\"toggleSection('systemInfo')\">\n <ng-template mjAccordionTitle>\n <span class=\"app-dialog-section-title\">\n <span class=\"section-title\">\n <i class=\"fa-solid fa-info-circle\" aria-hidden=\"true\"></i>\n System Information\n </span>\n <span class=\"section-description\">Application metadata and audit information</span>\n </span>\n </ng-template>\n\n <div class=\"section-content\">\n <div class=\"form-grid\">\n <div class=\"info-grid\">\n <div class=\"info-item\">\n <span class=\"info-label\">Created</span>\n <span class=\"info-value\">{{ data?.application?.__mj_CreatedAt | date:'medium' }}</span>\n </div>\n <div class=\"info-item\">\n <span class=\"info-label\">Last Updated</span>\n <span class=\"info-value\">{{ data?.application?.__mj_UpdatedAt | date:'medium' }}</span>\n </div>\n <div class=\"info-item\">\n <span class=\"info-label\">Application ID</span>\n <span class=\"info-value\">{{ data?.application?.ID }}</span>\n </div>\n <div class=\"info-item\">\n <span class=\"info-label\">Total Entities</span>\n <span class=\"info-value\">{{ applicationEntities.length }}</span>\n </div>\n </div>\n </div>\n </div>\n </mj-accordion-panel>\n }\n </form>\n </div>\n\n <!-- Modal Footer - Primary and Cancel buttons on RIGHT -->\n <div class=\"modal-footer\">\n <button\n type=\"submit\"\n mjButton variant=\"primary\"\n [disabled]=\"applicationForm.invalid || isLoading\"\n (click)=\"onSubmit()\"\n [attr.aria-busy]=\"isLoading\"\n >\n @if (isLoading) {\n <i class=\"fa-solid fa-spinner fa-spin\" aria-hidden=\"true\"></i>\n Saving...\n } @else {\n <i class=\"fa-solid fa-save\" aria-hidden=\"true\"></i>\n {{ isEditMode ? 'Update Application' : 'Create Application' }}\n }\n </button>\n <button type=\"button\" mjButton (click)=\"onCancel()\">\n Cancel\n </button>\n </div>\n </div>\n </div>\n}\n", styles: ["/* =============================================================================\n Application Dialog Component\n Uses MJ Design Tokens (--mj-*)\n ============================================================================= */\n\n/* -----------------------------------------------------------------------------\n Modal Backdrop & Container\n ----------------------------------------------------------------------------- */\n.modal-backdrop {\n position: fixed;\n inset: 0;\n background: rgba(0, 0, 0, 0.4);\n backdrop-filter: blur(4px);\n display: flex;\n align-items: center;\n justify-content: center;\n z-index: 1000;\n animation: fadeIn 0.2s cubic-bezier(0, 0, 0.2, 1);\n padding: 1rem;\n}\n\n.modal-dialog {\n background: var(--mj-bg-page);\n border-radius: var(--mj-radius-2xl);\n box-shadow: var(--mj-shadow-2xl);\n max-width: 900px;\n width: 100%;\n max-height: 90vh;\n overflow: hidden;\n display: flex;\n flex-direction: column;\n animation: slideUp 0.3s cubic-bezier(0, 0, 0.2, 1);\n transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);\n}\n\n/* Fullscreen mode */\n.modal-dialog.fullscreen {\n max-width: 100vw;\n width: 100vw;\n max-height: 100vh;\n height: 100vh;\n border-radius: 0;\n margin: 0;\n}\n\n/* -----------------------------------------------------------------------------\n Modal Header\n ----------------------------------------------------------------------------- */\n.modal-header {\n display: flex;\n justify-content: space-between;\n align-items: flex-start;\n padding: 1.5rem 1.5rem 1rem 1.5rem;\n background: var(--mj-brand-primary);\n color: var(--mj-brand-on-primary);\n flex-shrink: 0;\n}\n\n.modal-header-content {\n flex: 1;\n min-width: 0;\n}\n\n.modal-header .modal-title {\n display: flex;\n align-items: center;\n gap: 0.75rem;\n font-size: 1.375rem;\n font-weight: 600;\n color: var(--mj-brand-on-primary);\n margin: 0;\n}\n\n.modal-header .modal-title i {\n width: 40px;\n height: 40px;\n display: flex;\n align-items: center;\n justify-content: center;\n background: rgba(255, 255, 255, 0.2);\n border-radius: var(--mj-radius-lg);\n font-size: 1.125rem;\n flex-shrink: 0;\n}\n\n/* Create mode - Plus icon with inverted styling */\n.modal-header.create-mode .modal-title i {\n background: var(--mj-brand-on-primary);\n color: var(--mj-brand-primary);\n box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);\n}\n\n/* Edit mode - Grid icon with semi-transparent background */\n.modal-header.edit-mode .modal-title i {\n background: rgba(255, 255, 255, 0.2);\n color: var(--mj-brand-on-primary);\n}\n\n/* Modal Header Actions */\n.modal-header-actions {\n display: flex;\n gap: 0.5rem;\n align-items: center;\n flex-shrink: 0;\n margin-left: 0.5rem;\n}\n\n.modal-action-btn {\n padding: 0.625rem;\n border: none;\n background: transparent;\n color: var(--mj-brand-on-primary);\n font-size: 1.125rem;\n cursor: pointer;\n border-radius: var(--mj-radius-full);\n transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);\n min-width: 44px;\n min-height: 44px;\n display: flex;\n align-items: center;\n justify-content: center;\n flex-shrink: 0;\n}\n\n.modal-action-btn:hover {\n background: rgba(255, 255, 255, 0.15);\n}\n\n.modal-action-btn:focus-visible {\n outline: 2px solid var(--mj-brand-on-primary);\n outline-offset: 2px;\n}\n\n.modal-header .modal-subtitle {\n margin: 0.375rem 0 0 0;\n font-size: 0.8125rem;\n color: var(--mj-brand-on-primary);\n opacity: 0.85;\n}\n\n.modal-header .modal-close {\n padding: 0.625rem;\n border: none;\n background: transparent;\n color: var(--mj-brand-on-primary);\n font-size: 1.25rem;\n cursor: pointer;\n border-radius: var(--mj-radius-full);\n transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);\n min-width: 44px;\n min-height: 44px;\n display: flex;\n align-items: center;\n justify-content: center;\n flex-shrink: 0;\n margin-left: 0.5rem;\n}\n\n.modal-header .modal-close:hover {\n background: rgba(255, 255, 255, 0.15);\n}\n\n.modal-header .modal-close:focus-visible {\n outline: 2px solid var(--mj-brand-on-primary);\n outline-offset: 2px;\n}\n\n/* -----------------------------------------------------------------------------\n Modal Body\n ----------------------------------------------------------------------------- */\n.modal-body {\n flex: 1;\n overflow-y: auto;\n -webkit-overflow-scrolling: touch;\n padding: 0;\n background: var(--mj-bg-page);\n}\n\n/* -----------------------------------------------------------------------------\n Modal Footer - Primary and Cancel buttons on RIGHT\n ----------------------------------------------------------------------------- */\n.modal-footer {\n display: flex;\n justify-content: flex-end;\n gap: 0.75rem;\n padding: 1.25rem 1.5rem;\n border-top: 1px solid var(--mj-border-default);\n background: var(--mj-bg-surface-sunken);\n flex-shrink: 0;\n}\n\n/* -----------------------------------------------------------------------------\n Form Sections (Fieldset)\n ----------------------------------------------------------------------------- */\n.form-section {\n border: none;\n margin: 0;\n padding: 1.5rem;\n border-bottom: 1px solid var(--mj-border-default);\n}\n\n.form-section:last-of-type {\n border-bottom: none;\n}\n\n/* -----------------------------------------------------------------------------\n Collapsible Sections \u2014 now <mj-accordion-panel>. The bespoke fieldset/legend\n chrome (.section-header / .section-title-row / .chevron-icon) was removed; the\n title (icon + title + description) is projected into the accordion title slot.\n ----------------------------------------------------------------------------- */\n.app-dialog-section-title {\n display: flex;\n flex-direction: column;\n gap: 0.25rem;\n min-width: 0;\n}\n\n.app-dialog-section-title .section-title {\n display: flex;\n align-items: center;\n gap: 0.5rem;\n font-size: 1.0625rem;\n font-weight: 600;\n color: var(--mj-text-primary);\n}\n\n.app-dialog-section-title .section-title i {\n color: var(--mj-brand-primary);\n font-size: 0.875rem;\n}\n\n.app-dialog-section-title .section-description {\n font-size: 0.8125rem;\n color: var(--mj-text-secondary);\n font-weight: normal;\n}\n\n.section-content {\n animation: expandSection 0.3s cubic-bezier(0.4, 0, 0.2, 1);\n}\n\n@keyframes expandSection {\n from {\n opacity: 0;\n transform: translateY(-8px);\n }\n to {\n opacity: 1;\n transform: translateY(0);\n }\n}\n\n/* -----------------------------------------------------------------------------\n Form Grid Layout\n ----------------------------------------------------------------------------- */\n.form-grid {\n display: grid;\n gap: 1.25rem;\n}\n\n.form-row {\n display: grid;\n grid-template-columns: 1fr 1fr;\n gap: 1.25rem;\n}\n\n/* -----------------------------------------------------------------------------\n Form Fields\n ----------------------------------------------------------------------------- */\n.form-field {\n display: flex;\n flex-direction: column;\n gap: 0.5rem;\n}\n\n.form-field .field-label {\n font-size: 0.8125rem;\n font-weight: 500;\n color: var(--mj-text-primary);\n}\n\n.form-field .field-label.required::after {\n content: ' *';\n color: var(--mj-status-error);\n}\n\n.form-field .field-input,\n.form-field .field-select,\n.form-field .field-textarea {\n width: 100%;\n min-height: 44px;\n padding: 0.75rem 1rem;\n border: 1px solid var(--mj-border-strong);\n border-radius: var(--mj-radius-md);\n font-size: 0.9375rem;\n background: var(--mj-bg-page);\n color: var(--mj-text-primary);\n transition: border-color 0.2s ease, box-shadow 0.2s ease;\n}\n\n.form-field .field-input:focus,\n.form-field .field-select:focus,\n.form-field .field-textarea:focus {\n outline: none;\n border-color: var(--mj-brand-primary);\n box-shadow: 0 0 0 3px color-mix(in srgb, var(--mj-brand-primary) 12%, transparent);\n}\n\n.form-field .field-input.error,\n.form-field .field-select.error,\n.form-field .field-textarea.error {\n border-color: var(--mj-status-error);\n}\n\n.form-field .field-input.error:focus,\n.form-field .field-select.error:focus,\n.form-field .field-textarea.error:focus {\n border-color: var(--mj-status-error);\n box-shadow: 0 0 0 3px color-mix(in srgb, var(--mj-status-error) 12%, transparent);\n}\n\n.form-field .field-input::placeholder,\n.form-field .field-textarea::placeholder {\n color: var(--mj-text-secondary);\n}\n\n.form-field .field-textarea {\n min-height: 100px;\n resize: vertical;\n line-height: 1.5;\n}\n\n.form-field .field-error {\n margin-top: 0.25rem;\n font-size: 0.8125rem;\n color: var(--mj-status-error);\n display: flex;\n align-items: center;\n gap: 0.25rem;\n}\n\n.form-field .field-error i {\n font-size: 0.625rem;\n}\n\n.form-field .field-hint {\n font-size: 0.8125rem;\n color: var(--mj-text-secondary);\n line-height: 1.4;\n}\n\n/* -----------------------------------------------------------------------------\n Entity Management Styles\n ----------------------------------------------------------------------------- */\n.subsection-title {\n font-size: 0.9375rem;\n font-weight: 600;\n color: var(--mj-text-primary);\n margin: 0 0 1rem 0;\n display: flex;\n align-items: center;\n gap: 0.5rem;\n}\n\n.assigned-entities {\n margin-bottom: 1.5rem;\n}\n\n.assigned-entities .entities-list {\n border: 1px solid var(--mj-border-default);\n border-radius: var(--mj-radius-md);\n overflow: hidden;\n background: var(--mj-bg-page);\n}\n\n.entity-item {\n display: flex;\n align-items: center;\n padding: 1rem 1.25rem;\n border-bottom: 1px solid var(--mj-bg-surface);\n transition: all 0.15s ease;\n}\n\n.entity-item:last-child {\n border-bottom: none;\n}\n\n.entity-item:hover {\n background: var(--mj-bg-surface-sunken);\n}\n\n.entity-item.is-new {\n background: color-mix(in srgb, var(--mj-status-success) 8%, transparent);\n border-left: 3px solid var(--mj-status-success);\n}\n\n.entity-item.has-changes {\n background: color-mix(in srgb, var(--mj-status-warning) 8%, transparent);\n border-left: 3px solid var(--mj-status-warning);\n}\n\n.entity-item .entity-info {\n display: flex;\n align-items: center;\n gap: 1rem;\n flex: 1;\n min-width: 0;\n}\n\n.entity-item .entity-info .entity-icon {\n width: 40px;\n height: 40px;\n background: var(--mj-brand-accent-subtle);\n border-radius: var(--mj-radius-md);\n display: flex;\n align-items: center;\n justify-content: center;\n color: var(--mj-brand-primary);\n flex-shrink: 0;\n}\n\n.entity-item .entity-info .entity-details {\n flex: 1;\n min-width: 0;\n}\n\n.entity-item .entity-info .entity-details .entity-name {\n font-size: 0.9375rem;\n font-weight: 600;\n color: var(--mj-text-primary);\n margin-bottom: 0.125rem;\n display: flex;\n align-items: center;\n gap: 0.5rem;\n}\n\n.entity-item .entity-info .entity-details .entity-name .entity-info-icon {\n color: var(--mj-status-warning);\n font-size: 0.875rem;\n cursor: help;\n opacity: 0.7;\n transition: all 0.15s ease;\n}\n\n.entity-item .entity-info .entity-details .entity-name .entity-info-icon:hover {\n opacity: 1;\n transform: scale(1.1);\n}\n\n.entity-item .entity-config {\n display: flex;\n align-items: center;\n gap: 1.5rem;\n margin: 0 1.5rem;\n}\n\n.entity-item .entity-config .config-item .config-label {\n display: flex;\n align-items: center;\n gap: 0.5rem;\n cursor: pointer;\n font-size: 0.8125rem;\n color: var(--mj-text-secondary);\n margin: 0;\n}\n\n.entity-item .entity-config .config-item .config-label .config-checkbox {\n width: 18px;\n height: 18px;\n cursor: pointer;\n accent-color: var(--mj-brand-primary);\n}\n\n.entity-item .entity-config .sequence-badge {\n background: var(--mj-brand-primary);\n color: var(--mj-brand-on-primary);\n padding: 0.25rem 0.5rem;\n border-radius: var(--mj-radius-sm);\n font-size: 0.75rem;\n font-weight: 600;\n min-width: 24px;\n text-align: center;\n}\n\n.entity-item .entity-actions {\n display: flex;\n gap: 0.25rem;\n}\n\n/* -----------------------------------------------------------------------------\n Drag and Drop Styles\n ----------------------------------------------------------------------------- */\n.drag-handle {\n color: var(--mj-text-secondary);\n cursor: grab;\n margin-right: 0.75rem;\n font-size: 1rem;\n transition: color 0.2s ease;\n flex-shrink: 0;\n}\n\n.drag-handle:hover {\n color: var(--mj-brand-primary);\n}\n\n.drag-handle:active {\n cursor: grabbing;\n}\n\n.entity-item.cdk-drag-preview {\n box-shadow: var(--mj-shadow-xl);\n transform: rotate(1deg);\n opacity: 0.9;\n border-color: var(--mj-brand-primary);\n background: var(--mj-bg-page);\n}\n\n.entity-item.cdk-drag-placeholder {\n opacity: 0.4;\n border-style: dashed;\n border-color: var(--mj-brand-primary);\n background: var(--mj-bg-surface-sunken);\n}\n\n.entity-item.cdk-drag-animating {\n transition: transform 250ms cubic-bezier(0, 0, 0.2, 1);\n}\n\n.entities-list.cdk-drop-list-dragging .entity-item:not(.cdk-drag-placeholder) {\n transition: transform 250ms cubic-bezier(0, 0, 0.2, 1);\n}\n\n/* -----------------------------------------------------------------------------\n Empty States\n ----------------------------------------------------------------------------- */\n.no-entities,\n.all-assigned {\n text-align: center;\n padding: 2rem;\n color: var(--mj-text-secondary);\n}\n\n.no-entities i,\n.all-assigned i {\n font-size: 2rem;\n color: var(--mj-border-default);\n margin-bottom: 1rem;\n display: block;\n}\n\n.no-entities p,\n.all-assigned p {\n margin: 0 0 0.25rem 0;\n font-size: 0.9375rem;\n}\n\n.no-entities .empty-subtext,\n.all-assigned .empty-subtext {\n font-size: 0.8125rem;\n color: var(--mj-text-secondary);\n margin: 0;\n}\n\n/* -----------------------------------------------------------------------------\n Available Entities Section\n ----------------------------------------------------------------------------- */\n.available-entities-header {\n display: flex;\n flex-direction: column;\n gap: 0.75rem;\n margin-bottom: 1rem;\n}\n\n@media (min-width: 640px) {\n .available-entities-header {\n flex-direction: row;\n justify-content: space-between;\n align-items: center;\n }\n}\n\n.available-entities-header .subsection-title {\n margin: 0;\n}\n\n/* Entity Search Bar - Enhanced prominent search */\n.entity-search {\n position: relative;\n min-width: 200px;\n max-width: 100%;\n width: 100%;\n}\n\n@media (min-width: 640px) {\n .entity-search {\n min-width: 280px;\n max-width: 400px;\n width: auto;\n }\n}\n\n@media (min-width: 768px) {\n .entity-search {\n min-width: 320px;\n max-width: 450px;\n }\n}\n\n.entity-search .entity-search-icon {\n position: absolute;\n left: 1rem;\n top: 50%;\n transform: translateY(-50%);\n color: var(--mj-text-secondary);\n font-size: 1rem;\n pointer-events: none;\n transition: color 0.2s ease;\n}\n\n@media (min-width: 768px) {\n .entity-search .entity-search-icon {\n left: 1.125rem;\n font-size: 1.125rem;\n }\n}\n\n.entity-search .entity-search-input {\n width: 100%;\n padding: 0.875rem 2.75rem 0.875rem 2.75rem;\n border: 2px solid var(--mj-border-strong);\n border-radius: var(--mj-radius-full);\n font-size: 1rem;\n background: var(--mj-bg-page);\n color: var(--mj-text-primary);\n transition: border-color 0.2s ease, box-shadow 0.2s ease, background-color 0.2s ease;\n min-height: 48px;\n box-shadow: var(--mj-shadow-sm);\n}\n\n@media (min-width: 768px) {\n .entity-search .entity-search-input {\n padding: 1rem 3rem 1rem 3rem;\n min-height: 52px;\n font-size: 1.0625rem;\n border-radius: var(--mj-radius-full);\n }\n}\n\n.entity-search .entity-search-input::placeholder {\n color: var(--mj-text-secondary);\n}\n\n.entity-search .entity-search-input:hover {\n border-color: var(--mj-brand-primary);\n background: var(--mj-bg-surface-sunken);\n}\n\n.entity-search .entity-search-input:focus {\n outline: none;\n border-color: var(--mj-brand-primary);\n box-shadow: var(--mj-shadow-md), 0 0 0 3px color-mix(in srgb, var(--mj-brand-primary) 20%, transparent);\n background: var(--mj-bg-page);\n}\n\n.entity-search:focus-within .entity-search-icon {\n color: var(--mj-brand-primary);\n}\n\n.entity-search .entity-search-clear {\n position: absolute;\n right: 0.75rem;\n top: 50%;\n transform: translateY(-50%);\n padding: 0.375rem;\n border: none;\n background: transparent;\n color: var(--mj-text-secondary);\n cursor: pointer;\n border-radius: var(--mj-radius-full);\n display: flex;\n align-items: center;\n justify-content: center;\n min-width: 28px;\n min-height: 28px;\n transition: all 0.15s ease;\n}\n\n@media (min-width: 768px) {\n .entity-search .entity-search-clear {\n right: 0.875rem;\n min-width: 32px;\n min-height: 32px;\n }\n}\n\n.entity-search .entity-search-clear:hover {\n background: var(--mj-brand-primary);\n color: var(--mj-brand-on-primary);\n}\n\n/* No Search Results State */\n.no-search-results {\n text-align: center;\n padding: 1.5rem;\n color: var(--mj-text-secondary);\n background: var(--mj-bg-surface-sunken);\n border-radius: var(--mj-radius-md);\n}\n\n.no-search-results i {\n font-size: 1.5rem;\n color: var(--mj-border-default);\n margin-bottom: 0.5rem;\n display: block;\n}\n\n.no-search-results p {\n margin: 0;\n font-size: 0.8125rem;\n}\n\n/* -----------------------------------------------------------------------------\n Available Entities Grid\n ----------------------------------------------------------------------------- */\n.available-entities .entities-grid {\n display: grid;\n grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));\n gap: 0.5rem;\n}\n\n.entity-chip {\n padding: 0.75rem 1rem;\n border: 1px solid var(--mj-border-default);\n border-radius: var(--mj-radius-md);\n background: var(--mj-bg-page);\n cursor: pointer;\n transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);\n}\n\n.entity-chip .chip-content {\n display: flex;\n align-items: center;\n gap: 0.5rem;\n}\n\n.entity-chip .chip-content i {\n color: var(--mj-brand-primary);\n font-size: 0.875rem;\n}\n\n.entity-chip .chip-content .chip-name {\n font-size: 0.8125rem;\n font-weight: 500;\n color: var(--mj-text-primary);\n}\n\n.entity-chip:hover {\n border-color: var(--mj-brand-primary);\n background: var(--mj-brand-accent-subtle);\n transform: translateY(-1px);\n box-shadow: var(--mj-shadow-sm);\n}\n\n/* -----------------------------------------------------------------------------\n Info Grid for System Information\n ----------------------------------------------------------------------------- */\n.info-grid {\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));\n gap: 1rem;\n}\n\n.info-grid .info-item {\n display: flex;\n flex-direction: column;\n gap: 0.25rem;\n}\n\n.info-grid .info-item .info-label {\n font-size: 0.6875rem;\n font-weight: 500;\n color: var(--mj-text-secondary);\n text-transform: uppercase;\n letter-spacing: 0.5px;\n}\n\n.info-grid .info-item .info-value {\n font-size: 0.9375rem;\n color: var(--mj-text-primary);\n}\n\n/* -----------------------------------------------------------------------------\n Button Icon Styles\n ----------------------------------------------------------------------------- */\n.btn-icon {\n width: 36px;\n height: 36px;\n border: none;\n border-radius: var(--mj-radius-full);\n cursor: pointer;\n transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);\n display: flex;\n align-items: center;\n justify-content: center;\n font-size: 0.875rem;\n}\n\n.btn-icon:disabled {\n opacity: 0.38;\n cursor: not-allowed;\n}\n\n.btn-icon.btn-neutral {\n background: var(--mj-bg-surface);\n color: var(--mj-text-secondary);\n}\n\n.btn-icon.btn-neutral:hover:not(:disabled) {\n background: var(--mj-brand-primary);\n color: var(--mj-brand-on-primary);\n}\n\n.btn-icon.btn-danger {\n background: var(--mj-status-error-bg);\n color: var(--mj-status-error);\n}\n\n.btn-icon.btn-danger:hover:not(:disabled) {\n background: var(--mj-status-error);\n color: var(--mj-text-inverse);\n}\n\n/* Add/Create Icon Button */\n.btn-icon.btn-add {\n background: var(--mj-brand-primary);\n color: var(--mj-brand-on-primary);\n box-shadow: var(--mj-shadow-sm);\n}\n\n.btn-icon.btn-add:hover:not(:disabled) {\n background: var(--mj-brand-primary-light);\n box-shadow: var(--mj-shadow-md);\n}\n\n.btn-icon.btn-add:active:not(:disabled) {\n background: var(--mj-brand-primary-light);\n transform: scale(0.95);\n}\n\n/* -----------------------------------------------------------------------------\n Icon Wrapper - Prominent display icons\n ----------------------------------------------------------------------------- */\n.icon-wrapper {\n display: flex;\n align-items: center;\n justify-content: center;\n flex-shrink: 0;\n}\n\n.icon-wrapper-sm {\n width: 32px;\n height: 32px;\n border-radius: var(--mj-radius-md);\n font-size: 0.875rem;\n}\n\n.icon-wrapper-md {\n width: 40px;\n height: 40px;\n border-radius: var(--mj-radius-md);\n font-size: 1rem;\n}\n\n.icon-wrapper-lg {\n width: 48px;\n height: 48px;\n border-radius: var(--mj-radius-lg);\n font-size: 1.25rem;\n}\n\n.icon-wrapper-xl {\n width: 56px;\n height: 56px;\n border-radius: var(--mj-radius-lg);\n font-size: 1.5rem;\n}\n\n/* Icon wrapper color variants */\n.icon-wrapper-primary {\n background: var(--mj-brand-accent-subtle);\n color: var(--mj-brand-primary);\n}\n\n.icon-wrapper-secondary {\n background: var(--mj-status-warning-bg);\n color: var(--mj-status-warning);\n}\n\n.icon-wrapper-tertiary {\n background: var(--mj-status-success-bg);\n color: var(--mj-status-success);\n}\n\n.icon-wrapper-error {\n background: var(--mj-status-error-bg);\n color: var(--mj-status-error);\n}\n\n/* Inverted icon wrapper (for dark backgrounds) */\n.icon-wrapper-inverted {\n background: var(--mj-brand-on-primary);\n color: var(--mj-brand-primary);\n}\n\n/* Buttons use the global `mjButton` directive (variant/size inputs) \u2014 no\n component-scoped `.mj-btn` overrides (they would collide with the directive). */\n\n/* -----------------------------------------------------------------------------\n Animations\n ----------------------------------------------------------------------------- */\n@keyframes fadeIn {\n from {\n opacity: 0;\n }\n to {\n opacity: 1;\n }\n}\n\n@keyframes slideUp {\n from {\n transform: translateY(20px);\n opacity: 0;\n }\n to {\n transform: translateY(0);\n opacity: 1;\n }\n}\n\n/* -----------------------------------------------------------------------------\n Focus Indicators - Accessibility\n ----------------------------------------------------------------------------- */\n.field-input:focus-visible,\n.field-select:focus-visible,\n.field-textarea:focus-visible,\n.btn-icon:focus-visible,\n.entity-chip:focus-visible {\n outline: 2px solid var(--mj-brand-primary);\n outline-offset: 2px;\n}\n\n/* -----------------------------------------------------------------------------\n Reduced Motion Support\n ----------------------------------------------------------------------------- */\n@media (prefers-reduced-motion: reduce) {\n *,\n *::before,\n *::after {\n animation-duration: 0.01ms !important;\n animation-iteration-count: 1 !important;\n transition-duration: 0.01ms !important;\n }\n}\n\n/* -----------------------------------------------------------------------------\n Responsive Design - Mobile (< 640px)\n ----------------------------------------------------------------------------- */\n@media (max-width: 639px) {\n .modal-backdrop {\n padding: 0.5rem;\n }\n\n .modal-dialog {\n border-radius: var(--mj-radius-2xl);\n max-height: 95vh;\n }\n\n /* Fullscreen mode on mobile */\n .modal-dialog.fullscreen {\n border-radius: 0;\n max-height: 100vh;\n }\n\n .modal-header {\n padding: 1.25rem 1rem 1rem 1rem;\n }\n\n .modal-header .modal-title {\n font-size: 1.125rem;\n }\n\n .modal-header .modal-close {\n border-radius: var(--mj-radius-full);\n }\n\n /* Modal title icon on mobile */\n .modal-header .modal-title i {\n width: 36px;\n height: 36px;\n font-size: 1rem;\n }\n\n /* Icon wrappers on mobile - slightly smaller */\n .icon-wrapper-lg {\n width: 40px;\n height: 40px;\n font-size: 1rem;\n }\n\n .icon-wrapper-xl {\n width: 48px;\n height: 48px;\n font-size: 1.25rem;\n }\n\n /* Add button on mobile */\n .btn-icon.btn-add {\n border-radius: var(--mj-radius-full);\n }\n\n .form-section {\n padding: 1.25rem 1rem;\n }\n\n .form-row {\n grid-template-columns: 1fr;\n gap: 1rem;\n }\n\n .modal-footer {\n padding: 1rem;\n flex-direction: column;\n }\n\n /* Full-width footer buttons on mobile \u2014 wrapper-scoped layout only, so the\n global mjButton directive still owns each button's appearance/shape. */\n .modal-footer button {\n width: 100%;\n justify-content: center;\n }\n\n /* Entity item mobile layout */\n .entity-item {\n flex-direction: column;\n align-items: stretch;\n gap: 0.75rem;\n padding: 0.875rem 1rem;\n position: relative;\n }\n\n /* Drag handle on mobile - Keep visible and touch-friendly */\n .drag-handle {\n position: static;\n margin-right: 0.75rem;\n margin-bottom: 0.5rem;\n font-size: 1.25rem;\n color: var(--mj-brand-primary);\n display: flex;\n align-items: center;\n justify-content: center;\n width: 100%;\n padding: 0.5rem;\n background: var(--mj-bg-surface-sunken);\n border-radius: var(--mj-radius-md);\n cursor: grab;\n }\n\n .drag-handle:active {\n cursor: grabbing;\n background: var(--mj-brand-accent-subtle);\n }\n\n .entity-item .entity-info {\n gap: 0.75rem;\n }\n\n .entity-item .entity-info .entity-icon {\n width: 36px;\n height: 36px;\n font-size: 0.875rem;\n border-radius: var(--mj-radius-md);\n }\n\n .entity-item .entity-info .entity-details .entity-name {\n font-size: 0.875rem;\n word-break: break-word;\n overflow-wrap: break-word;\n line-height: 1.3;\n flex-wrap: wrap;\n }\n\n .entity-item .entity-config {\n margin: 0;\n width: 100%;\n justify-content: space-between;\n flex-wrap: wrap;\n gap: 0.75rem;\n }\n\n .entity-item .entity-config .config-item .config-label {\n font-size: 0.8125rem;\n }\n\n .entity-item .entity-actions {\n width: 100%;\n justify-content: flex-end;\n gap: 0.5rem;\n }\n\n /* Entity chips in mobile */\n .available-entities .entities-grid {\n grid-template-columns: 1fr;\n }\n\n .entity-chip {\n padding: 0.625rem 0.875rem;\n border-radius: var(--mj-radius-md);\n }\n\n .entity-chip .chip-content .chip-name {\n font-size: 0.75rem;\n word-break: break-word;\n }\n\n /* Entity search on mobile - still prominent */\n .entity-search {\n max-width: none;\n width: 100%;\n }\n\n .entity-search .entity-search-input {\n padding: 0.75rem 2.5rem 0.75rem 2.5rem;\n min-height: 44px;\n font-size: 0.9375rem;\n border-radius: var(--mj-radius-full);\n }\n\n .entity-search .entity-search-icon {\n left: 0.875rem;\n font-size: 0.875rem;\n }\n\n .entity-search .entity-search-clear {\n right: 0.625rem;\n border-radius: var(--mj-radius-full);\n }\n\n .info-grid {\n grid-template-columns: 1fr 1fr;\n }\n\n /* Icon buttons on mobile - keep rounded */\n .btn-icon {\n border-radius: var(--mj-radius-full);\n }\n}\n\n/* Tablet Portrait (640px - 767px) */\n@media (min-width: 640px) {\n .modal-dialog {\n max-width: 650px;\n }\n\n .available-entities .entities-grid {\n grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));\n }\n}\n\n/* Tablet Landscape (768px - 1023px) */\n@media (min-width: 768px) {\n .modal-dialog {\n max-width: 800px;\n }\n\n .modal-header {\n padding: 1.5rem 2rem 1.25rem 2rem;\n }\n\n .form-section {\n padding: 1.75rem 2rem;\n }\n\n .modal-footer {\n padding: 1.25rem 2rem;\n }\n}\n\n/* Laptop (1024px+) */\n@media (min-width: 1024px) {\n .modal-dialog {\n max-width: 900px;\n }\n\n .available-entities .entities-grid {\n grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));\n }\n}\n"] }]
|
|
706
|
-
}], () => [], {
|
|
851
|
+
args: [{ standalone: false, selector: 'mj-application-dialog', template: "@if (visible) {\n <div class=\"modal-backdrop\" (click)=\"onCancel()\" role=\"presentation\">\n <div\n class=\"modal-dialog\"\n [class.fullscreen]=\"isFullscreen\"\n (click)=\"$event.stopPropagation()\"\n role=\"dialog\"\n aria-modal=\"true\"\n aria-labelledby=\"application-dialog-title\"\n aria-describedby=\"application-dialog-description\"\n >\n <!-- Modal Header -->\n <div class=\"modal-header\" [class.create-mode]=\"!isEditMode\" [class.edit-mode]=\"isEditMode\">\n <div class=\"modal-header-content\">\n <h2 class=\"modal-title\" id=\"application-dialog-title\">\n <i class=\"fa-solid\" [class.fa-plus]=\"!isEditMode\" [class.fa-layer-group]=\"isEditMode\" aria-hidden=\"true\"></i>\n {{ isEditMode ? 'Edit Application' : 'Create New Application' }}\n </h2>\n <p class=\"modal-subtitle\" id=\"application-dialog-description\">\n {{ isEditMode ? 'Update application information and settings' : 'Add a new application to the system' }}\n </p>\n </div>\n <div class=\"modal-header-actions\">\n <button\n class=\"modal-action-btn\"\n (click)=\"toggleFullscreen()\"\n [attr.aria-label]=\"isFullscreen ? 'Exit fullscreen' : 'Enter fullscreen'\"\n type=\"button\"\n >\n <i class=\"fa-solid\" [class.fa-compress]=\"isFullscreen\" [class.fa-expand]=\"!isFullscreen\" aria-hidden=\"true\"></i>\n </button>\n <button\n class=\"modal-close\"\n (click)=\"onCancel()\"\n aria-label=\"Close dialog\"\n type=\"button\"\n >\n <i class=\"fa-solid fa-times\" aria-hidden=\"true\"></i>\n </button>\n </div>\n </div>\n\n <!-- Modal Body -->\n <div class=\"modal-body\">\n <form [formGroup]=\"applicationForm\" (ngSubmit)=\"onSubmit()\">\n <!-- Error Alert -->\n @if (error) {\n <div class=\"form-section\">\n <mj-alert Variant=\"error\" [Message]=\"error\" />\n </div>\n }\n\n <!-- Basic Information Section -->\n <mj-accordion-panel class=\"app-dialog-section\" [Expanded]=\"sectionExpanded.basicInfo\" (ExpandedChange)=\"toggleSection('basicInfo')\">\n <ng-template mjAccordionTitle>\n <span class=\"app-dialog-section-title\">\n <span class=\"section-title\">\n <i class=\"fa-solid fa-info-circle\" aria-hidden=\"true\"></i>\n Application Information\n </span>\n <span class=\"section-description\">Define the application's basic properties</span>\n </span>\n </ng-template>\n\n <div class=\"section-content\">\n <div class=\"form-grid\">\n <div class=\"form-field\">\n <label class=\"field-label required\" for=\"app-name\">Application Name</label>\n <input\n id=\"app-name\"\n type=\"text\"\n class=\"field-input\"\n formControlName=\"name\"\n placeholder=\"My Application\"\n [class.error]=\"applicationForm.get('name')?.invalid && applicationForm.get('name')?.touched\"\n [attr.aria-invalid]=\"applicationForm.get('name')?.invalid && applicationForm.get('name')?.touched\"\n aria-describedby=\"app-name-error\"\n />\n @if (applicationForm.get('name')?.invalid && applicationForm.get('name')?.touched) {\n <div class=\"field-error\" id=\"app-name-error\" role=\"alert\">\n <i class=\"fa-solid fa-exclamation-circle\" aria-hidden=\"true\"></i>\n @if (applicationForm.get('name')?.errors?.['required']) {\n Application name is required\n }\n @if (applicationForm.get('name')?.errors?.['maxlength']) {\n Application name cannot exceed 100 characters\n }\n </div>\n }\n </div>\n\n <div class=\"form-field\">\n <label class=\"field-label\" for=\"app-description\">Description</label>\n <textarea\n id=\"app-description\"\n class=\"field-textarea\"\n formControlName=\"description\"\n placeholder=\"Describe the application's purpose and functionality...\"\n rows=\"4\"\n ></textarea>\n <div class=\"field-hint\">\n Provide a clear description of what this application does and its main features.\n </div>\n </div>\n </div>\n </div>\n </mj-accordion-panel>\n\n <!-- Entity Management Section -->\n <mj-accordion-panel class=\"app-dialog-section\" [Expanded]=\"sectionExpanded.entities\" (ExpandedChange)=\"toggleSection('entities')\">\n <ng-template mjAccordionTitle>\n <span class=\"app-dialog-section-title\">\n <span class=\"section-title\">\n <i class=\"fa-solid fa-database\" aria-hidden=\"true\"></i>\n Application Entities\n </span>\n <span class=\"section-description\">Manage which entities are included in this application</span>\n </span>\n </ng-template>\n\n <div class=\"section-content\">\n <div class=\"form-grid\">\n <!-- Assigned Entities -->\n @if (applicationEntities.length > 0) {\n <div class=\"assigned-entities\">\n <h4 class=\"subsection-title\">Assigned Entities ({{ applicationEntities.length }})</h4>\n <div class=\"entities-list\"\n role=\"list\"\n cdkDropList\n (cdkDropListDropped)=\"onEntityDrop($event)\">\n @for (config of applicationEntities; track config.entity.ID; let i = $index) {\n <div\n class=\"entity-item\"\n [class.is-new]=\"config.isNew\"\n [class.has-changes]=\"config.hasChanges\"\n role=\"listitem\"\n cdkDrag\n >\n <i class=\"fa-solid fa-grip-vertical drag-handle\" aria-hidden=\"true\"></i>\n <div class=\"entity-info\">\n <div class=\"entity-icon\">\n <i class=\"fa-solid fa-table\" aria-hidden=\"true\"></i>\n </div>\n <div class=\"entity-details\">\n <div class=\"entity-name\">\n {{ getEntityDisplayName(config.entity) }}\n @if (config.entity.Description) {\n <i\n class=\"fa-solid fa-info-circle entity-info-icon\"\n [title]=\"config.entity.Description\"\n [attr.aria-label]=\"'Entity description: ' + config.entity.Description\"\n ></i>\n }\n </div>\n </div>\n </div>\n\n <div class=\"entity-config\">\n <div class=\"config-item\">\n <label class=\"config-label\">\n <input\n type=\"checkbox\"\n [(ngModel)]=\"config.defaultForNewUser\"\n [ngModelOptions]=\"{standalone: true}\"\n (change)=\"onDefaultForNewUserChange(config)\"\n class=\"config-checkbox\"\n />\n <span class=\"checkbox-text\">Default for new users</span>\n </label>\n </div>\n <div class=\"sequence-badge\" [attr.aria-label]=\"'Sequence ' + config.sequence\">{{ config.sequence }}</div>\n </div>\n\n <div class=\"entity-actions\">\n <button\n type=\"button\"\n class=\"btn-icon btn-danger\"\n (click)=\"removeEntity(config)\"\n title=\"Remove entity\"\n [attr.aria-label]=\"'Remove ' + getEntityDisplayName(config.entity)\"\n >\n <i class=\"fa-solid fa-times\" aria-hidden=\"true\"></i>\n </button>\n </div>\n </div>\n }\n </div>\n </div>\n } @else {\n <div class=\"no-entities\">\n <i class=\"fa-solid fa-database\" aria-hidden=\"true\"></i>\n <p>No entities assigned to this application</p>\n <p class=\"empty-subtext\">Add entities from the available list below</p>\n </div>\n }\n\n <!-- Available Entities -->\n @if (availableEntities.length > 0) {\n <div class=\"available-entities\">\n <div class=\"available-entities-header\">\n <h4 class=\"subsection-title\">Available Entities ({{ filteredAvailableEntities.length }} of {{ availableEntities.length }})</h4>\n <div class=\"entity-search\">\n <i class=\"fa-solid fa-search entity-search-icon\" aria-hidden=\"true\"></i>\n <input\n type=\"text\"\n class=\"entity-search-input\"\n placeholder=\"Search entities...\"\n [value]=\"entitySearchTerm\"\n (input)=\"onEntitySearchChange($event)\"\n aria-label=\"Search available entities\"\n />\n @if (entitySearchTerm) {\n <button\n type=\"button\"\n class=\"entity-search-clear\"\n (click)=\"clearEntitySearch()\"\n aria-label=\"Clear search\"\n >\n <i class=\"fa-solid fa-times\" aria-hidden=\"true\"></i>\n </button>\n }\n </div>\n </div>\n @if (filteredAvailableEntities.length > 0) {\n <div class=\"entities-grid\" role=\"list\">\n @for (entity of filteredAvailableEntities; track entity.ID) {\n <button\n type=\"button\"\n class=\"entity-chip\"\n (click)=\"addEntity(entity)\"\n role=\"listitem\"\n [attr.aria-label]=\"'Add ' + getEntityDisplayName(entity)\"\n >\n <div class=\"chip-content\">\n <i class=\"fa-solid fa-plus\" aria-hidden=\"true\"></i>\n <span class=\"chip-name\">{{ getEntityDisplayName(entity) }}</span>\n </div>\n </button>\n }\n </div>\n } @else {\n <div class=\"no-search-results\">\n <i class=\"fa-solid fa-search\" aria-hidden=\"true\"></i>\n <p>No entities match \"{{ entitySearchTerm }}\"</p>\n </div>\n }\n </div>\n } @else {\n <div class=\"all-assigned\">\n <i class=\"fa-solid fa-check-circle\" aria-hidden=\"true\"></i>\n <p>All entities have been assigned to this application</p>\n </div>\n }\n </div>\n </div>\n </mj-accordion-panel>\n\n <!-- System Information Section (Edit Mode Only) -->\n @if (isEditMode) {\n <mj-accordion-panel class=\"app-dialog-section\" [Expanded]=\"sectionExpanded.systemInfo\" (ExpandedChange)=\"toggleSection('systemInfo')\">\n <ng-template mjAccordionTitle>\n <span class=\"app-dialog-section-title\">\n <span class=\"section-title\">\n <i class=\"fa-solid fa-info-circle\" aria-hidden=\"true\"></i>\n System Information\n </span>\n <span class=\"section-description\">Application metadata and audit information</span>\n </span>\n </ng-template>\n\n <div class=\"section-content\">\n <div class=\"form-grid\">\n <div class=\"info-grid\">\n <div class=\"info-item\">\n <span class=\"info-label\">Created</span>\n <span class=\"info-value\">{{ data?.application?.__mj_CreatedAt | date:'medium' }}</span>\n </div>\n <div class=\"info-item\">\n <span class=\"info-label\">Last Updated</span>\n <span class=\"info-value\">{{ data?.application?.__mj_UpdatedAt | date:'medium' }}</span>\n </div>\n <div class=\"info-item\">\n <span class=\"info-label\">Application ID</span>\n <span class=\"info-value\">{{ data?.application?.ID }}</span>\n </div>\n <div class=\"info-item\">\n <span class=\"info-label\">Total Entities</span>\n <span class=\"info-value\">{{ applicationEntities.length }}</span>\n </div>\n </div>\n </div>\n </div>\n </mj-accordion-panel>\n }\n </form>\n </div>\n\n <!-- Modal Footer - Primary and Cancel buttons on RIGHT -->\n <div class=\"modal-footer\">\n <button\n type=\"submit\"\n mjButton variant=\"primary\"\n [disabled]=\"applicationForm.invalid || isLoading\"\n (click)=\"onSubmit()\"\n [attr.aria-busy]=\"isLoading\"\n >\n @if (isLoading) {\n <i class=\"fa-solid fa-spinner fa-spin\" aria-hidden=\"true\"></i>\n Saving...\n } @else {\n <i class=\"fa-solid fa-save\" aria-hidden=\"true\"></i>\n {{ isEditMode ? 'Update Application' : 'Create Application' }}\n }\n </button>\n <button type=\"button\" mjButton (click)=\"onCancel()\">\n Cancel\n </button>\n </div>\n </div>\n </div>\n}\n", styles: ["/* =============================================================================\n Application Dialog Component\n Uses MJ Design Tokens (--mj-*)\n ============================================================================= */\n\n/* -----------------------------------------------------------------------------\n Modal Backdrop & Container\n ----------------------------------------------------------------------------- */\n.modal-backdrop {\n position: fixed;\n inset: 0;\n background: rgba(0, 0, 0, 0.4);\n backdrop-filter: blur(4px);\n display: flex;\n align-items: center;\n justify-content: center;\n z-index: 1000;\n animation: fadeIn 0.2s cubic-bezier(0, 0, 0.2, 1);\n padding: 1rem;\n}\n\n.modal-dialog {\n background: var(--mj-bg-page);\n border-radius: var(--mj-radius-2xl);\n box-shadow: var(--mj-shadow-2xl);\n max-width: 900px;\n width: 100%;\n max-height: 90vh;\n overflow: hidden;\n display: flex;\n flex-direction: column;\n animation: slideUp 0.3s cubic-bezier(0, 0, 0.2, 1);\n transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);\n}\n\n/* Fullscreen mode */\n.modal-dialog.fullscreen {\n max-width: 100vw;\n width: 100vw;\n max-height: 100vh;\n height: 100vh;\n border-radius: 0;\n margin: 0;\n}\n\n/* -----------------------------------------------------------------------------\n Modal Header\n ----------------------------------------------------------------------------- */\n.modal-header {\n display: flex;\n justify-content: space-between;\n align-items: flex-start;\n padding: 1.5rem 1.5rem 1rem 1.5rem;\n background: var(--mj-brand-primary);\n color: var(--mj-brand-on-primary);\n flex-shrink: 0;\n}\n\n.modal-header-content {\n flex: 1;\n min-width: 0;\n}\n\n.modal-header .modal-title {\n display: flex;\n align-items: center;\n gap: 0.75rem;\n font-size: 1.375rem;\n font-weight: 600;\n color: var(--mj-brand-on-primary);\n margin: 0;\n}\n\n.modal-header .modal-title i {\n width: 40px;\n height: 40px;\n display: flex;\n align-items: center;\n justify-content: center;\n background: rgba(255, 255, 255, 0.2);\n border-radius: var(--mj-radius-lg);\n font-size: 1.125rem;\n flex-shrink: 0;\n}\n\n/* Create mode - Plus icon with inverted styling */\n.modal-header.create-mode .modal-title i {\n background: var(--mj-brand-on-primary);\n color: var(--mj-brand-primary);\n box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);\n}\n\n/* Edit mode - Grid icon with semi-transparent background */\n.modal-header.edit-mode .modal-title i {\n background: rgba(255, 255, 255, 0.2);\n color: var(--mj-brand-on-primary);\n}\n\n/* Modal Header Actions */\n.modal-header-actions {\n display: flex;\n gap: 0.5rem;\n align-items: center;\n flex-shrink: 0;\n margin-left: 0.5rem;\n}\n\n.modal-action-btn {\n padding: 0.625rem;\n border: none;\n background: transparent;\n color: var(--mj-brand-on-primary);\n font-size: 1.125rem;\n cursor: pointer;\n border-radius: var(--mj-radius-full);\n transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);\n min-width: 44px;\n min-height: 44px;\n display: flex;\n align-items: center;\n justify-content: center;\n flex-shrink: 0;\n}\n\n.modal-action-btn:hover {\n background: rgba(255, 255, 255, 0.15);\n}\n\n.modal-action-btn:focus-visible {\n outline: 2px solid var(--mj-brand-on-primary);\n outline-offset: 2px;\n}\n\n.modal-header .modal-subtitle {\n margin: 0.375rem 0 0 0;\n font-size: 0.8125rem;\n color: var(--mj-brand-on-primary);\n opacity: 0.85;\n}\n\n.modal-header .modal-close {\n padding: 0.625rem;\n border: none;\n background: transparent;\n color: var(--mj-brand-on-primary);\n font-size: 1.25rem;\n cursor: pointer;\n border-radius: var(--mj-radius-full);\n transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);\n min-width: 44px;\n min-height: 44px;\n display: flex;\n align-items: center;\n justify-content: center;\n flex-shrink: 0;\n margin-left: 0.5rem;\n}\n\n.modal-header .modal-close:hover {\n background: rgba(255, 255, 255, 0.15);\n}\n\n.modal-header .modal-close:focus-visible {\n outline: 2px solid var(--mj-brand-on-primary);\n outline-offset: 2px;\n}\n\n/* -----------------------------------------------------------------------------\n Modal Body\n ----------------------------------------------------------------------------- */\n.modal-body {\n flex: 1;\n overflow-y: auto;\n -webkit-overflow-scrolling: touch;\n padding: 0;\n background: var(--mj-bg-page);\n}\n\n/* -----------------------------------------------------------------------------\n Modal Footer - Primary and Cancel buttons on RIGHT\n ----------------------------------------------------------------------------- */\n.modal-footer {\n display: flex;\n justify-content: flex-end;\n gap: 0.75rem;\n padding: 1.25rem 1.5rem;\n border-top: 1px solid var(--mj-border-default);\n background: var(--mj-bg-surface-sunken);\n flex-shrink: 0;\n}\n\n/* -----------------------------------------------------------------------------\n Form Sections (Fieldset)\n ----------------------------------------------------------------------------- */\n.form-section {\n border: none;\n margin: 0;\n padding: 1.5rem;\n border-bottom: 1px solid var(--mj-border-default);\n}\n\n.form-section:last-of-type {\n border-bottom: none;\n}\n\n/* -----------------------------------------------------------------------------\n Collapsible Sections \u2014 now <mj-accordion-panel>. The bespoke fieldset/legend\n chrome (.section-header / .section-title-row / .chevron-icon) was removed; the\n title (icon + title + description) is projected into the accordion title slot.\n ----------------------------------------------------------------------------- */\n.app-dialog-section-title {\n display: flex;\n flex-direction: column;\n gap: 0.25rem;\n min-width: 0;\n}\n\n.app-dialog-section-title .section-title {\n display: flex;\n align-items: center;\n gap: 0.5rem;\n font-size: 1.0625rem;\n font-weight: 600;\n color: var(--mj-text-primary);\n}\n\n.app-dialog-section-title .section-title i {\n color: var(--mj-brand-primary);\n font-size: 0.875rem;\n}\n\n.app-dialog-section-title .section-description {\n font-size: 0.8125rem;\n color: var(--mj-text-secondary);\n font-weight: normal;\n}\n\n.section-content {\n animation: expandSection 0.3s cubic-bezier(0.4, 0, 0.2, 1);\n}\n\n@keyframes expandSection {\n from {\n opacity: 0;\n transform: translateY(-8px);\n }\n to {\n opacity: 1;\n transform: translateY(0);\n }\n}\n\n/* -----------------------------------------------------------------------------\n Form Grid Layout\n ----------------------------------------------------------------------------- */\n.form-grid {\n display: grid;\n gap: 1.25rem;\n}\n\n.form-row {\n display: grid;\n grid-template-columns: 1fr 1fr;\n gap: 1.25rem;\n}\n\n/* -----------------------------------------------------------------------------\n Form Fields\n ----------------------------------------------------------------------------- */\n.form-field {\n display: flex;\n flex-direction: column;\n gap: 0.5rem;\n}\n\n.form-field .field-label {\n font-size: 0.8125rem;\n font-weight: 500;\n color: var(--mj-text-primary);\n}\n\n.form-field .field-label.required::after {\n content: ' *';\n color: var(--mj-status-error);\n}\n\n.form-field .field-input,\n.form-field .field-select,\n.form-field .field-textarea {\n width: 100%;\n min-height: 44px;\n padding: 0.75rem 1rem;\n border: 1px solid var(--mj-border-strong);\n border-radius: var(--mj-radius-md);\n font-size: 0.9375rem;\n background: var(--mj-bg-page);\n color: var(--mj-text-primary);\n transition: border-color 0.2s ease, box-shadow 0.2s ease;\n}\n\n.form-field .field-input:focus,\n.form-field .field-select:focus,\n.form-field .field-textarea:focus {\n outline: none;\n border-color: var(--mj-brand-primary);\n box-shadow: 0 0 0 3px color-mix(in srgb, var(--mj-brand-primary) 12%, transparent);\n}\n\n.form-field .field-input.error,\n.form-field .field-select.error,\n.form-field .field-textarea.error {\n border-color: var(--mj-status-error);\n}\n\n.form-field .field-input.error:focus,\n.form-field .field-select.error:focus,\n.form-field .field-textarea.error:focus {\n border-color: var(--mj-status-error);\n box-shadow: 0 0 0 3px color-mix(in srgb, var(--mj-status-error) 12%, transparent);\n}\n\n.form-field .field-input::placeholder,\n.form-field .field-textarea::placeholder {\n color: var(--mj-text-secondary);\n}\n\n.form-field .field-textarea {\n min-height: 100px;\n resize: vertical;\n line-height: 1.5;\n}\n\n.form-field .field-error {\n margin-top: 0.25rem;\n font-size: 0.8125rem;\n color: var(--mj-status-error);\n display: flex;\n align-items: center;\n gap: 0.25rem;\n}\n\n.form-field .field-error i {\n font-size: 0.625rem;\n}\n\n.form-field .field-hint {\n font-size: 0.8125rem;\n color: var(--mj-text-secondary);\n line-height: 1.4;\n}\n\n/* -----------------------------------------------------------------------------\n Entity Management Styles\n ----------------------------------------------------------------------------- */\n.subsection-title {\n font-size: 0.9375rem;\n font-weight: 600;\n color: var(--mj-text-primary);\n margin: 0 0 1rem 0;\n display: flex;\n align-items: center;\n gap: 0.5rem;\n}\n\n.assigned-entities {\n margin-bottom: 1.5rem;\n}\n\n.assigned-entities .entities-list {\n border: 1px solid var(--mj-border-default);\n border-radius: var(--mj-radius-md);\n overflow: hidden;\n background: var(--mj-bg-page);\n}\n\n.entity-item {\n display: flex;\n align-items: center;\n padding: 1rem 1.25rem;\n border-bottom: 1px solid var(--mj-bg-surface);\n transition: all 0.15s ease;\n}\n\n.entity-item:last-child {\n border-bottom: none;\n}\n\n.entity-item:hover {\n background: var(--mj-bg-surface-sunken);\n}\n\n.entity-item.is-new {\n background: color-mix(in srgb, var(--mj-status-success) 8%, transparent);\n border-left: 3px solid var(--mj-status-success);\n}\n\n.entity-item.has-changes {\n background: color-mix(in srgb, var(--mj-status-warning) 8%, transparent);\n border-left: 3px solid var(--mj-status-warning);\n}\n\n.entity-item .entity-info {\n display: flex;\n align-items: center;\n gap: 1rem;\n flex: 1;\n min-width: 0;\n}\n\n.entity-item .entity-info .entity-icon {\n width: 40px;\n height: 40px;\n background: var(--mj-brand-accent-subtle);\n border-radius: var(--mj-radius-md);\n display: flex;\n align-items: center;\n justify-content: center;\n color: var(--mj-brand-primary);\n flex-shrink: 0;\n}\n\n.entity-item .entity-info .entity-details {\n flex: 1;\n min-width: 0;\n}\n\n.entity-item .entity-info .entity-details .entity-name {\n font-size: 0.9375rem;\n font-weight: 600;\n color: var(--mj-text-primary);\n margin-bottom: 0.125rem;\n display: flex;\n align-items: center;\n gap: 0.5rem;\n}\n\n.entity-item .entity-info .entity-details .entity-name .entity-info-icon {\n color: var(--mj-status-warning);\n font-size: 0.875rem;\n cursor: help;\n opacity: 0.7;\n transition: all 0.15s ease;\n}\n\n.entity-item .entity-info .entity-details .entity-name .entity-info-icon:hover {\n opacity: 1;\n transform: scale(1.1);\n}\n\n.entity-item .entity-config {\n display: flex;\n align-items: center;\n gap: 1.5rem;\n margin: 0 1.5rem;\n}\n\n.entity-item .entity-config .config-item .config-label {\n display: flex;\n align-items: center;\n gap: 0.5rem;\n cursor: pointer;\n font-size: 0.8125rem;\n color: var(--mj-text-secondary);\n margin: 0;\n}\n\n.entity-item .entity-config .config-item .config-label .config-checkbox {\n width: 18px;\n height: 18px;\n cursor: pointer;\n accent-color: var(--mj-brand-primary);\n}\n\n.entity-item .entity-config .sequence-badge {\n background: var(--mj-brand-primary);\n color: var(--mj-brand-on-primary);\n padding: 0.25rem 0.5rem;\n border-radius: var(--mj-radius-sm);\n font-size: 0.75rem;\n font-weight: 600;\n min-width: 24px;\n text-align: center;\n}\n\n.entity-item .entity-actions {\n display: flex;\n gap: 0.25rem;\n}\n\n/* -----------------------------------------------------------------------------\n Drag and Drop Styles\n ----------------------------------------------------------------------------- */\n.drag-handle {\n color: var(--mj-text-secondary);\n cursor: grab;\n margin-right: 0.75rem;\n font-size: 1rem;\n transition: color 0.2s ease;\n flex-shrink: 0;\n}\n\n.drag-handle:hover {\n color: var(--mj-brand-primary);\n}\n\n.drag-handle:active {\n cursor: grabbing;\n}\n\n.entity-item.cdk-drag-preview {\n box-shadow: var(--mj-shadow-xl);\n transform: rotate(1deg);\n opacity: 0.9;\n border-color: var(--mj-brand-primary);\n background: var(--mj-bg-page);\n}\n\n.entity-item.cdk-drag-placeholder {\n opacity: 0.4;\n border-style: dashed;\n border-color: var(--mj-brand-primary);\n background: var(--mj-bg-surface-sunken);\n}\n\n.entity-item.cdk-drag-animating {\n transition: transform 250ms cubic-bezier(0, 0, 0.2, 1);\n}\n\n.entities-list.cdk-drop-list-dragging .entity-item:not(.cdk-drag-placeholder) {\n transition: transform 250ms cubic-bezier(0, 0, 0.2, 1);\n}\n\n/* -----------------------------------------------------------------------------\n Empty States\n ----------------------------------------------------------------------------- */\n.no-entities,\n.all-assigned {\n text-align: center;\n padding: 2rem;\n color: var(--mj-text-secondary);\n}\n\n.no-entities i,\n.all-assigned i {\n font-size: 2rem;\n color: var(--mj-border-default);\n margin-bottom: 1rem;\n display: block;\n}\n\n.no-entities p,\n.all-assigned p {\n margin: 0 0 0.25rem 0;\n font-size: 0.9375rem;\n}\n\n.no-entities .empty-subtext,\n.all-assigned .empty-subtext {\n font-size: 0.8125rem;\n color: var(--mj-text-secondary);\n margin: 0;\n}\n\n/* -----------------------------------------------------------------------------\n Available Entities Section\n ----------------------------------------------------------------------------- */\n.available-entities-header {\n display: flex;\n flex-direction: column;\n gap: 0.75rem;\n margin-bottom: 1rem;\n}\n\n@media (min-width: 640px) {\n .available-entities-header {\n flex-direction: row;\n justify-content: space-between;\n align-items: center;\n }\n}\n\n.available-entities-header .subsection-title {\n margin: 0;\n}\n\n/* Entity Search Bar - Enhanced prominent search */\n.entity-search {\n position: relative;\n min-width: 200px;\n max-width: 100%;\n width: 100%;\n}\n\n@media (min-width: 640px) {\n .entity-search {\n min-width: 280px;\n max-width: 400px;\n width: auto;\n }\n}\n\n@media (min-width: 768px) {\n .entity-search {\n min-width: 320px;\n max-width: 450px;\n }\n}\n\n.entity-search .entity-search-icon {\n position: absolute;\n left: 1rem;\n top: 50%;\n transform: translateY(-50%);\n color: var(--mj-text-secondary);\n font-size: 1rem;\n pointer-events: none;\n transition: color 0.2s ease;\n}\n\n@media (min-width: 768px) {\n .entity-search .entity-search-icon {\n left: 1.125rem;\n font-size: 1.125rem;\n }\n}\n\n.entity-search .entity-search-input {\n width: 100%;\n padding: 0.875rem 2.75rem 0.875rem 2.75rem;\n border: 2px solid var(--mj-border-strong);\n border-radius: var(--mj-radius-full);\n font-size: 1rem;\n background: var(--mj-bg-page);\n color: var(--mj-text-primary);\n transition: border-color 0.2s ease, box-shadow 0.2s ease, background-color 0.2s ease;\n min-height: 48px;\n box-shadow: var(--mj-shadow-sm);\n}\n\n@media (min-width: 768px) {\n .entity-search .entity-search-input {\n padding: 1rem 3rem 1rem 3rem;\n min-height: 52px;\n font-size: 1.0625rem;\n border-radius: var(--mj-radius-full);\n }\n}\n\n.entity-search .entity-search-input::placeholder {\n color: var(--mj-text-secondary);\n}\n\n.entity-search .entity-search-input:hover {\n border-color: var(--mj-brand-primary);\n background: var(--mj-bg-surface-sunken);\n}\n\n.entity-search .entity-search-input:focus {\n outline: none;\n border-color: var(--mj-brand-primary);\n box-shadow: var(--mj-shadow-md), 0 0 0 3px color-mix(in srgb, var(--mj-brand-primary) 20%, transparent);\n background: var(--mj-bg-page);\n}\n\n.entity-search:focus-within .entity-search-icon {\n color: var(--mj-brand-primary);\n}\n\n.entity-search .entity-search-clear {\n position: absolute;\n right: 0.75rem;\n top: 50%;\n transform: translateY(-50%);\n padding: 0.375rem;\n border: none;\n background: transparent;\n color: var(--mj-text-secondary);\n cursor: pointer;\n border-radius: var(--mj-radius-full);\n display: flex;\n align-items: center;\n justify-content: center;\n min-width: 28px;\n min-height: 28px;\n transition: all 0.15s ease;\n}\n\n@media (min-width: 768px) {\n .entity-search .entity-search-clear {\n right: 0.875rem;\n min-width: 32px;\n min-height: 32px;\n }\n}\n\n.entity-search .entity-search-clear:hover {\n background: var(--mj-brand-primary);\n color: var(--mj-brand-on-primary);\n}\n\n/* No Search Results State */\n.no-search-results {\n text-align: center;\n padding: 1.5rem;\n color: var(--mj-text-secondary);\n background: var(--mj-bg-surface-sunken);\n border-radius: var(--mj-radius-md);\n}\n\n.no-search-results i {\n font-size: 1.5rem;\n color: var(--mj-border-default);\n margin-bottom: 0.5rem;\n display: block;\n}\n\n.no-search-results p {\n margin: 0;\n font-size: 0.8125rem;\n}\n\n/* -----------------------------------------------------------------------------\n Available Entities Grid\n ----------------------------------------------------------------------------- */\n.available-entities .entities-grid {\n display: grid;\n grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));\n gap: 0.5rem;\n}\n\n.entity-chip {\n padding: 0.75rem 1rem;\n border: 1px solid var(--mj-border-default);\n border-radius: var(--mj-radius-md);\n background: var(--mj-bg-page);\n cursor: pointer;\n transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);\n}\n\n.entity-chip .chip-content {\n display: flex;\n align-items: center;\n gap: 0.5rem;\n}\n\n.entity-chip .chip-content i {\n color: var(--mj-brand-primary);\n font-size: 0.875rem;\n}\n\n.entity-chip .chip-content .chip-name {\n font-size: 0.8125rem;\n font-weight: 500;\n color: var(--mj-text-primary);\n}\n\n.entity-chip:hover {\n border-color: var(--mj-brand-primary);\n background: var(--mj-brand-accent-subtle);\n transform: translateY(-1px);\n box-shadow: var(--mj-shadow-sm);\n}\n\n/* -----------------------------------------------------------------------------\n Info Grid for System Information\n ----------------------------------------------------------------------------- */\n.info-grid {\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));\n gap: 1rem;\n}\n\n.info-grid .info-item {\n display: flex;\n flex-direction: column;\n gap: 0.25rem;\n}\n\n.info-grid .info-item .info-label {\n font-size: 0.6875rem;\n font-weight: 500;\n color: var(--mj-text-secondary);\n text-transform: uppercase;\n letter-spacing: 0.5px;\n}\n\n.info-grid .info-item .info-value {\n font-size: 0.9375rem;\n color: var(--mj-text-primary);\n}\n\n/* -----------------------------------------------------------------------------\n Button Icon Styles\n ----------------------------------------------------------------------------- */\n.btn-icon {\n width: 36px;\n height: 36px;\n border: none;\n border-radius: var(--mj-radius-full);\n cursor: pointer;\n transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);\n display: flex;\n align-items: center;\n justify-content: center;\n font-size: 0.875rem;\n}\n\n.btn-icon:disabled {\n opacity: 0.38;\n cursor: not-allowed;\n}\n\n.btn-icon.btn-neutral {\n background: var(--mj-bg-surface);\n color: var(--mj-text-secondary);\n}\n\n.btn-icon.btn-neutral:hover:not(:disabled) {\n background: var(--mj-brand-primary);\n color: var(--mj-brand-on-primary);\n}\n\n.btn-icon.btn-danger {\n background: var(--mj-status-error-bg);\n color: var(--mj-status-error);\n}\n\n.btn-icon.btn-danger:hover:not(:disabled) {\n background: var(--mj-status-error);\n color: var(--mj-text-inverse);\n}\n\n/* Add/Create Icon Button */\n.btn-icon.btn-add {\n background: var(--mj-brand-primary);\n color: var(--mj-brand-on-primary);\n box-shadow: var(--mj-shadow-sm);\n}\n\n.btn-icon.btn-add:hover:not(:disabled) {\n background: var(--mj-brand-primary-light);\n box-shadow: var(--mj-shadow-md);\n}\n\n.btn-icon.btn-add:active:not(:disabled) {\n background: var(--mj-brand-primary-light);\n transform: scale(0.95);\n}\n\n/* -----------------------------------------------------------------------------\n Icon Wrapper - Prominent display icons\n ----------------------------------------------------------------------------- */\n.icon-wrapper {\n display: flex;\n align-items: center;\n justify-content: center;\n flex-shrink: 0;\n}\n\n.icon-wrapper-sm {\n width: 32px;\n height: 32px;\n border-radius: var(--mj-radius-md);\n font-size: 0.875rem;\n}\n\n.icon-wrapper-md {\n width: 40px;\n height: 40px;\n border-radius: var(--mj-radius-md);\n font-size: 1rem;\n}\n\n.icon-wrapper-lg {\n width: 48px;\n height: 48px;\n border-radius: var(--mj-radius-lg);\n font-size: 1.25rem;\n}\n\n.icon-wrapper-xl {\n width: 56px;\n height: 56px;\n border-radius: var(--mj-radius-lg);\n font-size: 1.5rem;\n}\n\n/* Icon wrapper color variants */\n.icon-wrapper-primary {\n background: var(--mj-brand-accent-subtle);\n color: var(--mj-brand-primary);\n}\n\n.icon-wrapper-secondary {\n background: var(--mj-status-warning-bg);\n color: var(--mj-status-warning);\n}\n\n.icon-wrapper-tertiary {\n background: var(--mj-status-success-bg);\n color: var(--mj-status-success);\n}\n\n.icon-wrapper-error {\n background: var(--mj-status-error-bg);\n color: var(--mj-status-error);\n}\n\n/* Inverted icon wrapper (for dark backgrounds) */\n.icon-wrapper-inverted {\n background: var(--mj-brand-on-primary);\n color: var(--mj-brand-primary);\n}\n\n/* Buttons use the global `mjButton` directive (variant/size inputs) \u2014 no\n component-scoped `.mj-btn` overrides (they would collide with the directive). */\n\n/* -----------------------------------------------------------------------------\n Animations\n ----------------------------------------------------------------------------- */\n@keyframes fadeIn {\n from {\n opacity: 0;\n }\n to {\n opacity: 1;\n }\n}\n\n@keyframes slideUp {\n from {\n transform: translateY(20px);\n opacity: 0;\n }\n to {\n transform: translateY(0);\n opacity: 1;\n }\n}\n\n/* -----------------------------------------------------------------------------\n Focus Indicators - Accessibility\n ----------------------------------------------------------------------------- */\n.field-input:focus-visible,\n.field-select:focus-visible,\n.field-textarea:focus-visible,\n.btn-icon:focus-visible,\n.entity-chip:focus-visible {\n outline: 2px solid var(--mj-brand-primary);\n outline-offset: 2px;\n}\n\n/* -----------------------------------------------------------------------------\n Reduced Motion Support\n ----------------------------------------------------------------------------- */\n@media (prefers-reduced-motion: reduce) {\n *,\n *::before,\n *::after {\n animation-duration: 0.01ms !important;\n animation-iteration-count: 1 !important;\n transition-duration: 0.01ms !important;\n }\n}\n\n/* -----------------------------------------------------------------------------\n Responsive Design - Mobile (< 640px)\n ----------------------------------------------------------------------------- */\n@media (max-width: 639px) {\n .modal-backdrop {\n padding: 0.5rem;\n }\n\n .modal-dialog {\n border-radius: var(--mj-radius-2xl);\n max-height: 95vh;\n }\n\n /* Fullscreen mode on mobile */\n .modal-dialog.fullscreen {\n border-radius: 0;\n max-height: 100vh;\n }\n\n .modal-header {\n padding: 1.25rem 1rem 1rem 1rem;\n }\n\n .modal-header .modal-title {\n font-size: 1.125rem;\n }\n\n .modal-header .modal-close {\n border-radius: var(--mj-radius-full);\n }\n\n /* Modal title icon on mobile */\n .modal-header .modal-title i {\n width: 36px;\n height: 36px;\n font-size: 1rem;\n }\n\n /* Icon wrappers on mobile - slightly smaller */\n .icon-wrapper-lg {\n width: 40px;\n height: 40px;\n font-size: 1rem;\n }\n\n .icon-wrapper-xl {\n width: 48px;\n height: 48px;\n font-size: 1.25rem;\n }\n\n /* Add button on mobile */\n .btn-icon.btn-add {\n border-radius: var(--mj-radius-full);\n }\n\n .form-section {\n padding: 1.25rem 1rem;\n }\n\n .form-row {\n grid-template-columns: 1fr;\n gap: 1rem;\n }\n\n .modal-footer {\n padding: 1rem;\n flex-direction: column;\n }\n\n /* Full-width footer buttons on mobile \u2014 wrapper-scoped layout only, so the\n global mjButton directive still owns each button's appearance/shape. */\n .modal-footer button {\n width: 100%;\n justify-content: center;\n }\n\n /* Entity item mobile layout */\n .entity-item {\n flex-direction: column;\n align-items: stretch;\n gap: 0.75rem;\n padding: 0.875rem 1rem;\n position: relative;\n }\n\n /* Drag handle on mobile - Keep visible and touch-friendly */\n .drag-handle {\n position: static;\n margin-right: 0.75rem;\n margin-bottom: 0.5rem;\n font-size: 1.25rem;\n color: var(--mj-brand-primary);\n display: flex;\n align-items: center;\n justify-content: center;\n width: 100%;\n padding: 0.5rem;\n background: var(--mj-bg-surface-sunken);\n border-radius: var(--mj-radius-md);\n cursor: grab;\n }\n\n .drag-handle:active {\n cursor: grabbing;\n background: var(--mj-brand-accent-subtle);\n }\n\n .entity-item .entity-info {\n gap: 0.75rem;\n }\n\n .entity-item .entity-info .entity-icon {\n width: 36px;\n height: 36px;\n font-size: 0.875rem;\n border-radius: var(--mj-radius-md);\n }\n\n .entity-item .entity-info .entity-details .entity-name {\n font-size: 0.875rem;\n word-break: break-word;\n overflow-wrap: break-word;\n line-height: 1.3;\n flex-wrap: wrap;\n }\n\n .entity-item .entity-config {\n margin: 0;\n width: 100%;\n justify-content: space-between;\n flex-wrap: wrap;\n gap: 0.75rem;\n }\n\n .entity-item .entity-config .config-item .config-label {\n font-size: 0.8125rem;\n }\n\n .entity-item .entity-actions {\n width: 100%;\n justify-content: flex-end;\n gap: 0.5rem;\n }\n\n /* Entity chips in mobile */\n .available-entities .entities-grid {\n grid-template-columns: 1fr;\n }\n\n .entity-chip {\n padding: 0.625rem 0.875rem;\n border-radius: var(--mj-radius-md);\n }\n\n .entity-chip .chip-content .chip-name {\n font-size: 0.75rem;\n word-break: break-word;\n }\n\n /* Entity search on mobile - still prominent */\n .entity-search {\n max-width: none;\n width: 100%;\n }\n\n .entity-search .entity-search-input {\n padding: 0.75rem 2.5rem 0.75rem 2.5rem;\n min-height: 44px;\n font-size: 0.9375rem;\n border-radius: var(--mj-radius-full);\n }\n\n .entity-search .entity-search-icon {\n left: 0.875rem;\n font-size: 0.875rem;\n }\n\n .entity-search .entity-search-clear {\n right: 0.625rem;\n border-radius: var(--mj-radius-full);\n }\n\n .info-grid {\n grid-template-columns: 1fr 1fr;\n }\n\n /* Icon buttons on mobile - keep rounded */\n .btn-icon {\n border-radius: var(--mj-radius-full);\n }\n}\n\n/* Tablet Portrait (640px - 767px) */\n@media (min-width: 640px) {\n .modal-dialog {\n max-width: 650px;\n }\n\n .available-entities .entities-grid {\n grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));\n }\n}\n\n/* Tablet Landscape (768px - 1023px) */\n@media (min-width: 768px) {\n .modal-dialog {\n max-width: 800px;\n }\n\n .modal-header {\n padding: 1.5rem 2rem 1.25rem 2rem;\n }\n\n .form-section {\n padding: 1.75rem 2rem;\n }\n\n .modal-footer {\n padding: 1.25rem 2rem;\n }\n}\n\n/* Laptop (1024px+) */\n@media (min-width: 1024px) {\n .modal-dialog {\n max-width: 900px;\n }\n\n .available-entities .entities-grid {\n grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));\n }\n}\n"] }]
|
|
852
|
+
}], () => [], { Data: [{
|
|
853
|
+
type: Input
|
|
854
|
+
}], data: [{
|
|
855
|
+
type: Input
|
|
856
|
+
}], Visible: [{
|
|
707
857
|
type: Input
|
|
708
858
|
}], visible: [{
|
|
709
859
|
type: Input
|
|
860
|
+
}], Result: [{
|
|
861
|
+
type: Output
|
|
710
862
|
}], result: [{
|
|
711
863
|
type: Output
|
|
712
864
|
}], onEscapeKey: [{
|