@memberjunction/ng-base-forms 6.1.0-edge.5 → 6.1.0-edge.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. package/dist/lib/base-form-component.d.ts +23 -0
  2. package/dist/lib/base-form-component.d.ts.map +1 -1
  3. package/dist/lib/base-form-component.js +56 -13
  4. package/dist/lib/base-form-component.js.map +1 -1
  5. package/dist/lib/chrome/form-chrome-rail-pref.d.ts +22 -0
  6. package/dist/lib/chrome/form-chrome-rail-pref.d.ts.map +1 -1
  7. package/dist/lib/chrome/form-chrome-rail-pref.js +29 -0
  8. package/dist/lib/chrome/form-chrome-rail-pref.js.map +1 -1
  9. package/dist/lib/chrome/form-chrome.d.ts +19 -0
  10. package/dist/lib/chrome/form-chrome.d.ts.map +1 -1
  11. package/dist/lib/chrome/form-chrome.js +26 -0
  12. package/dist/lib/chrome/form-chrome.js.map +1 -1
  13. package/dist/lib/container/record-form-container.component.d.ts +52 -2
  14. package/dist/lib/container/record-form-container.component.d.ts.map +1 -1
  15. package/dist/lib/container/record-form-container.component.js +485 -161
  16. package/dist/lib/container/record-form-container.component.js.map +1 -1
  17. package/dist/lib/explorer-entity-data-grid.component.d.ts +36 -0
  18. package/dist/lib/explorer-entity-data-grid.component.d.ts.map +1 -1
  19. package/dist/lib/explorer-entity-data-grid.component.js +112 -3
  20. package/dist/lib/explorer-entity-data-grid.component.js.map +1 -1
  21. package/dist/lib/field/form-field.component.d.ts +128 -0
  22. package/dist/lib/field/form-field.component.d.ts.map +1 -1
  23. package/dist/lib/field/form-field.component.js +871 -306
  24. package/dist/lib/field/form-field.component.js.map +1 -1
  25. package/dist/lib/isa-related-panel/isa-related-card.component.d.ts.map +1 -1
  26. package/dist/lib/isa-related-panel/isa-related-card.component.js +7 -1
  27. package/dist/lib/isa-related-panel/isa-related-card.component.js.map +1 -1
  28. package/dist/lib/overlays/base-form-overlay.d.ts.map +1 -1
  29. package/dist/lib/overlays/base-form-overlay.js +3 -1
  30. package/dist/lib/overlays/base-form-overlay.js.map +1 -1
  31. package/dist/lib/overlays/form-slide-in.component.js +10 -11
  32. package/dist/lib/overlays/form-slide-in.component.js.map +1 -1
  33. package/dist/lib/panel/collapsible-panel.component.d.ts +94 -3
  34. package/dist/lib/panel/collapsible-panel.component.d.ts.map +1 -1
  35. package/dist/lib/panel/collapsible-panel.component.js +278 -37
  36. package/dist/lib/panel/collapsible-panel.component.js.map +1 -1
  37. package/dist/lib/panel-slot/base-form-panel.d.ts +12 -0
  38. package/dist/lib/panel-slot/base-form-panel.d.ts.map +1 -1
  39. package/dist/lib/panel-slot/base-form-panel.js.map +1 -1
  40. package/dist/lib/related-grid-height.d.ts +48 -11
  41. package/dist/lib/related-grid-height.d.ts.map +1 -1
  42. package/dist/lib/related-grid-height.js +53 -12
  43. package/dist/lib/related-grid-height.js.map +1 -1
  44. package/dist/lib/section-indicators/form-section-indicator-coordinator.service.d.ts +80 -0
  45. package/dist/lib/section-indicators/form-section-indicator-coordinator.service.d.ts.map +1 -0
  46. package/dist/lib/section-indicators/form-section-indicator-coordinator.service.js +99 -0
  47. package/dist/lib/section-indicators/form-section-indicator-coordinator.service.js.map +1 -0
  48. package/dist/lib/section-indicators/form-section-indicators.d.ts +97 -0
  49. package/dist/lib/section-indicators/form-section-indicators.d.ts.map +1 -0
  50. package/dist/lib/section-indicators/form-section-indicators.js +132 -0
  51. package/dist/lib/section-indicators/form-section-indicators.js.map +1 -0
  52. package/dist/lib/section-manager/section-manager.component.js +2 -2
  53. package/dist/lib/section-manager/section-manager.component.js.map +1 -1
  54. package/dist/lib/toolbar/form-toolbar.component.d.ts +9 -1
  55. package/dist/lib/toolbar/form-toolbar.component.d.ts.map +1 -1
  56. package/dist/lib/toolbar/form-toolbar.component.js +14 -6
  57. package/dist/lib/toolbar/form-toolbar.component.js.map +1 -1
  58. package/dist/lib/types/form-types.d.ts +8 -0
  59. package/dist/lib/types/form-types.d.ts.map +1 -1
  60. package/dist/lib/types/form-types.js.map +1 -1
  61. package/dist/public-api.d.ts +2 -0
  62. package/dist/public-api.d.ts.map +1 -1
  63. package/dist/public-api.js +3 -0
  64. package/dist/public-api.js.map +1 -1
  65. package/package.json +25 -27
@@ -6,47 +6,80 @@ import { MjFormFieldComponent } from '../field/form-field.component';
6
6
  import { CompositeKey } from '@memberjunction/core';
7
7
  import { EscapeHTML, HighlightSearchMatches } from '@memberjunction/global';
8
8
  import { FormChromeCoordinator } from '../chrome/form-chrome-coordinator.service';
9
+ import { FormSectionIndicatorCoordinator } from '../section-indicators/form-section-indicator-coordinator.service';
10
+ import { ClaimedCollectionErrors, DescribeSectionDirty, DescribeSectionErrors, DescribeSectionWarnings, SectionOwnsValidationSource, SumSectionIndicators, TallyValidationErrors, } from '../section-indicators/form-section-indicators';
9
11
  import * as i0 from "@angular/core";
10
12
  const _c0 = ["panelContent"];
11
13
  const _c1 = ["*"];
12
14
  function MjCollapsiblePanelComponent_Conditional_2_Template(rf, ctx) { if (rf & 1) {
13
- const _r2 = i0.ɵɵgetCurrentView();
14
- i0.ɵɵelementStart(0, "div", 13);
15
- i0.ɵɵlistener("dragstart", function MjCollapsiblePanelComponent_Conditional_2_Template_div_dragstart_0_listener($event) { i0.ɵɵrestoreView(_r2); const ctx_r2 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r2.OnDragStart($event)); })("dragend", function MjCollapsiblePanelComponent_Conditional_2_Template_div_dragend_0_listener() { i0.ɵɵrestoreView(_r2); const ctx_r2 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r2.OnDragEnd()); })("click", function MjCollapsiblePanelComponent_Conditional_2_Template_div_click_0_listener($event) { i0.ɵɵrestoreView(_r2); return i0.ɵɵresetView($event.stopPropagation()); });
16
- i0.ɵɵelement(1, "i", 14);
15
+ const _r1 = i0.ɵɵgetCurrentView();
16
+ i0.ɵɵelementStart(0, "div", 16);
17
+ i0.ɵɵlistener("dragstart", function MjCollapsiblePanelComponent_Conditional_2_Template_div_dragstart_0_listener($event) { i0.ɵɵrestoreView(_r1); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.OnDragStart($event)); })("dragend", function MjCollapsiblePanelComponent_Conditional_2_Template_div_dragend_0_listener() { i0.ɵɵrestoreView(_r1); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.OnDragEnd()); })("click", function MjCollapsiblePanelComponent_Conditional_2_Template_div_click_0_listener($event) { return $event.stopPropagation(); });
18
+ i0.ɵɵelement(1, "i", 17);
17
19
  i0.ɵɵelementEnd();
18
20
  } }
19
21
  function MjCollapsiblePanelComponent_Conditional_7_Template(rf, ctx) { if (rf & 1) {
20
- i0.ɵɵelementStart(0, "span", 15);
22
+ i0.ɵɵelement(0, "span", 8);
23
+ } if (rf & 2) {
24
+ const ctx_r1 = i0.ɵɵnextContext();
25
+ i0.ɵɵproperty("title", ctx_r1.SectionDirtyTitle);
26
+ i0.ɵɵattribute("aria-label", ctx_r1.SectionDirtyTitle);
27
+ } }
28
+ function MjCollapsiblePanelComponent_Conditional_8_Template(rf, ctx) { if (rf & 1) {
29
+ i0.ɵɵelementStart(0, "span", 9);
30
+ i0.ɵɵelement(1, "i", 18);
31
+ i0.ɵɵtext(2);
32
+ i0.ɵɵelementEnd();
33
+ } if (rf & 2) {
34
+ const ctx_r1 = i0.ɵɵnextContext();
35
+ i0.ɵɵproperty("title", ctx_r1.SectionErrorTitle);
36
+ i0.ɵɵattribute("aria-label", ctx_r1.SectionErrorTitle);
37
+ i0.ɵɵadvance(2);
38
+ i0.ɵɵtextInterpolate1("", ctx_r1.SectionErrorCount, " ");
39
+ } }
40
+ function MjCollapsiblePanelComponent_Conditional_9_Template(rf, ctx) { if (rf & 1) {
41
+ i0.ɵɵelementStart(0, "span", 10);
42
+ i0.ɵɵelement(1, "i", 19);
43
+ i0.ɵɵtext(2);
44
+ i0.ɵɵelementEnd();
45
+ } if (rf & 2) {
46
+ const ctx_r1 = i0.ɵɵnextContext();
47
+ i0.ɵɵproperty("title", ctx_r1.SectionWarningTitle);
48
+ i0.ɵɵattribute("aria-label", ctx_r1.SectionWarningTitle);
49
+ i0.ɵɵadvance(2);
50
+ i0.ɵɵtextInterpolate1("", ctx_r1.SectionWarningCount, " ");
51
+ } }
52
+ function MjCollapsiblePanelComponent_Conditional_10_Template(rf, ctx) { if (rf & 1) {
53
+ i0.ɵɵelementStart(0, "span", 20);
21
54
  i0.ɵɵtext(1);
22
55
  i0.ɵɵelementEnd();
23
56
  } if (rf & 2) {
24
- const ctx_r2 = i0.ɵɵnextContext();
25
- i0.ɵɵclassProp("mj-forms-row-count-badge--zero", ctx_r2.BadgeCount === 0);
57
+ const ctx_r1 = i0.ɵɵnextContext();
58
+ i0.ɵɵclassProp("mj-forms-row-count-badge--zero", ctx_r1.BadgeCount === 0);
26
59
  i0.ɵɵadvance();
27
- i0.ɵɵtextInterpolate1(" ", ctx_r2.BadgeCount, " ");
60
+ i0.ɵɵtextInterpolate1(" ", ctx_r1.BadgeCount, " ");
28
61
  } }
29
- function MjCollapsiblePanelComponent_Conditional_8_Template(rf, ctx) { if (rf & 1) {
30
- const _r4 = i0.ɵɵgetCurrentView();
31
- i0.ɵɵelementStart(0, "a", 16);
32
- i0.ɵɵlistener("click", function MjCollapsiblePanelComponent_Conditional_8_Template_a_click_0_listener($event) { i0.ɵɵrestoreView(_r4); const ctx_r2 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r2.OnInheritedBadgeClick($event)); });
33
- i0.ɵɵelement(1, "i", 17);
62
+ function MjCollapsiblePanelComponent_Conditional_11_Template(rf, ctx) { if (rf & 1) {
63
+ const _r3 = i0.ɵɵgetCurrentView();
64
+ i0.ɵɵelementStart(0, "a", 21);
65
+ i0.ɵɵlistener("click", function MjCollapsiblePanelComponent_Conditional_11_Template_a_click_0_listener($event) { i0.ɵɵrestoreView(_r3); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.OnInheritedBadgeClick($event)); });
66
+ i0.ɵɵelement(1, "i", 22);
34
67
  i0.ɵɵtext(2);
35
68
  i0.ɵɵelementEnd();
36
69
  } if (rf & 2) {
37
- const ctx_r2 = i0.ɵɵnextContext();
38
- i0.ɵɵproperty("title", "View parent record: " + ctx_r2.InheritedFromEntity);
70
+ const ctx_r1 = i0.ɵɵnextContext();
71
+ i0.ɵɵproperty("title", "View parent record: " + ctx_r1.InheritedFromEntity);
39
72
  i0.ɵɵadvance(2);
40
- i0.ɵɵtextInterpolate1(" Inherited from ", ctx_r2.InheritedFromEntity, " ");
73
+ i0.ɵɵtextInterpolate1(" Inherited from ", ctx_r1.InheritedFromEntity, " ");
41
74
  } }
42
- function MjCollapsiblePanelComponent_Conditional_9_Template(rf, ctx) { if (rf & 1) {
43
- i0.ɵɵelementStart(0, "div", 10);
75
+ function MjCollapsiblePanelComponent_Conditional_12_Template(rf, ctx) { if (rf & 1) {
76
+ i0.ɵɵelementStart(0, "div", 13);
44
77
  i0.ɵɵelement(1, "i");
45
78
  i0.ɵɵelementEnd();
46
79
  } if (rf & 2) {
47
- const ctx_r2 = i0.ɵɵnextContext();
80
+ const ctx_r1 = i0.ɵɵnextContext();
48
81
  i0.ɵɵadvance();
49
- i0.ɵɵclassMap(ctx_r2.Expanded ? "fa fa-chevron-up" : "fa fa-chevron-down");
82
+ i0.ɵɵclassMap(ctx_r1.Expanded ? "fa fa-chevron-up" : "fa fa-chevron-down");
50
83
  } }
51
84
  /**
52
85
  * Reusable collapsible panel for form sections.
@@ -62,6 +95,10 @@ function MjCollapsiblePanelComponent_Conditional_9_Template(rf, ctx) { if (rf &
62
95
  * - Drag-to-reorder sections
63
96
  * - Inheritable "Inherited from X" badge with navigation event
64
97
  * - Row count badge for related entity sections
98
+ * - Section indicators: an unsaved-changes dot and an invalid-field count derived
99
+ * live from the projected `mj-form-field`s (plus anything a custom section
100
+ * supplies through `[Indicators]`), published to the form's chrome rail
101
+ * through {@link FormSectionIndicatorCoordinator}
65
102
  *
66
103
  * @example
67
104
  * ```html
@@ -83,8 +120,33 @@ export class MjCollapsiblePanelComponent {
83
120
  elementRef = inject(ElementRef);
84
121
  ngZone = inject(NgZone);
85
122
  chrome = inject(FormChromeCoordinator, { optional: true });
123
+ indicators = inject(FormSectionIndicatorCoordinator, { optional: true });
86
124
  /** Unique key for state persistence */
87
125
  SectionKey = '';
126
+ /**
127
+ * Indicator counts a custom section supplies for content that is NOT rendered by
128
+ * `mj-form-field` — a designer, an inline grid, a hand-built editor. Added to the
129
+ * counts this panel derives from its own fields, so a section that mixes both
130
+ * reports the union. Omit for ordinary field panels: their state is derived.
131
+ *
132
+ * ```html
133
+ * <mj-collapsible-panel SectionKey="lines" [Indicators]="{ DirtyCount: LineEditor.EditedRows, ErrorCount: LineEditor.InvalidRows }">
134
+ * ```
135
+ */
136
+ Indicators;
137
+ /**
138
+ * Graph-path collection names whose validation failures belong to THIS panel —
139
+ * `ValidationSources="Modifications"` claims `Modifications[2].ProvisionID`.
140
+ *
141
+ * Only needed when a panel hosts a child collection whose path name differs from
142
+ * its `SectionKey`: plain field failures are joined automatically from the panel's
143
+ * `mj-form-field` children, and a graph path whose leading segment already matches
144
+ * the `SectionKey` is claimed without any declaration. Declared HERE, on the panel
145
+ * that owns the collection, rather than in an app-side `fieldName -> sectionKey`
146
+ * map that duplicates a fact the panel owns and drifts silently when a
147
+ * `SectionKey` is renamed. Accepts a single name or a list.
148
+ */
149
+ ValidationSources;
88
150
  /** Display name shown in the panel header */
89
151
  SectionName = '';
90
152
  /** Font Awesome icon class for the panel header */
@@ -163,6 +225,16 @@ export class MjCollapsiblePanelComponent {
163
225
  get HostIcon() {
164
226
  return this.Icon;
165
227
  }
228
+ /**
229
+ * Indicator counts mirrored onto the host element, so CSS and any DOM sweep can
230
+ * read a section's state without a component reference.
231
+ */
232
+ get HostDirtyCount() {
233
+ return this.SectionIndicators.DirtyCount;
234
+ }
235
+ get HostErrorCount() {
236
+ return this.SectionIndicators.ErrorCount;
237
+ }
166
238
  get HostClass() {
167
239
  const classes = [`mj-panel--${this.Variant}`];
168
240
  if (!this.IsVisible)
@@ -171,6 +243,13 @@ export class MjCollapsiblePanelComponent {
171
243
  classes.push('mj-dragging');
172
244
  if (this.IsDragOver)
173
245
  classes.push('mj-drag-over');
246
+ const indicators = this.SectionIndicators;
247
+ if (indicators.DirtyCount > 0)
248
+ classes.push('mj-panel-dirty');
249
+ if (indicators.ErrorCount > 0)
250
+ classes.push('mj-panel-has-errors');
251
+ else if (indicators.WarningCount > 0)
252
+ classes.push('mj-panel-has-warnings');
174
253
  if (this.chrome?.Spec.RelatedRoles.get(this.SectionKey) === 'Detail') {
175
254
  classes.push('mj-form-role-detail');
176
255
  }
@@ -245,12 +324,121 @@ export class MjCollapsiblePanelComponent {
245
324
  const formRef = this.Form;
246
325
  return formRef?.IsSectionExpanded ? formRef.IsSectionExpanded(this.SectionKey, this.DefaultExpanded) : true;
247
326
  }
327
+ // ---- Section indicators (unsaved-changes dot + invalid-field count) ----
328
+ /**
329
+ * Live state of this section: fields edited since the last save, fields that are
330
+ * invalid or required-and-empty, and fields carrying warnings. Derived from the
331
+ * projected `mj-form-field`s on every read — the same state those fields use for
332
+ * their own amber dot and red underline, so the section can never disagree with
333
+ * its fields — plus graph-path validation failures this section claims, plus
334
+ * whatever a custom section supplies through `[Indicators]`.
335
+ *
336
+ * Read during change detection (host bindings, the header template, the rail via
337
+ * the coordinator). Pure and cheap: one pass over the field list.
338
+ */
339
+ get SectionIndicators() {
340
+ return SumSectionIndicators(this.fieldIndicators(), TallyValidationErrors(this.claimedGraphErrors()), this.Indicators);
341
+ }
342
+ get SectionDirtyCount() {
343
+ return this.SectionIndicators.DirtyCount;
344
+ }
345
+ get SectionErrorCount() {
346
+ return this.SectionIndicators.ErrorCount;
347
+ }
348
+ get SectionWarningCount() {
349
+ return this.SectionIndicators.WarningCount;
350
+ }
351
+ get SectionDirtyTitle() {
352
+ return DescribeSectionDirty(this.SectionDirtyCount);
353
+ }
354
+ get SectionErrorTitle() {
355
+ return DescribeSectionErrors(this.SectionErrorCount);
356
+ }
357
+ get SectionWarningTitle() {
358
+ return DescribeSectionWarnings(this.SectionWarningCount);
359
+ }
360
+ /** {@link FormSectionIndicatorSource} — the rail reads this through the coordinator. */
361
+ GetSectionIndicators() {
362
+ return this.SectionIndicators;
363
+ }
364
+ /** {@link FormSectionIndicatorSource} — whether a form-level error belongs to this section. */
365
+ OwnsValidationSource(source) {
366
+ return SectionOwnsValidationSource(source, this.SectionKey, this.validationScope());
367
+ }
368
+ /** Normalized {@link ValidationSources}. */
369
+ get ValidationCollectionNames() {
370
+ const raw = this.ValidationSources;
371
+ if (!raw)
372
+ return [];
373
+ return (typeof raw === 'string' ? [raw] : [...raw]).map((n) => n.trim()).filter((n) => n.length > 0);
374
+ }
375
+ /** Field names this panel renders — the join between a plain-field error and this section. */
376
+ renderedFieldNames() {
377
+ const names = [];
378
+ this.FieldComponents?.forEach((field) => {
379
+ if (field.FieldName)
380
+ names.push(field.FieldName);
381
+ });
382
+ return names;
383
+ }
384
+ validationScope() {
385
+ return { FieldNames: this.renderedFieldNames(), CollectionNames: this.ValidationCollectionNames };
386
+ }
387
+ /**
388
+ * Counts of FIELDS (not messages) in each state. A field is invalid when it shows a
389
+ * failure OR is required and empty in edit mode — exactly the two conditions that
390
+ * paint its underline red. Warnings count only on fields with no failure, mirroring
391
+ * `MjFormFieldComponent.ShowWarnings`.
392
+ */
393
+ fieldIndicators() {
394
+ let DirtyCount = 0;
395
+ let ErrorCount = 0;
396
+ let WarningCount = 0;
397
+ this.FieldComponents?.forEach((field) => {
398
+ if (field.IsDirty)
399
+ DirtyCount++;
400
+ // Only a field that renders an EDITOR can be invalid: a read-only field renders its
401
+ // value as text and never paints an underline, so it is skipped even when
402
+ // `IsRequiredEmpty` would say otherwise (a NOT NULL `__mj_CreatedAt` on a new record
403
+ // is empty and read-only — the user cannot fix it, and it fills itself on save).
404
+ if (!field.EditMode || field.IsFieldReadOnly)
405
+ return;
406
+ if (field.ShowErrors || field.IsRequiredEmpty)
407
+ ErrorCount++;
408
+ // The field paints its amber underline on EITHER of these — a stored date the input
409
+ // cannot parse warns on its own, outside the validation pipeline.
410
+ else if (field.ShowWarnings || field.StoredDateIsUnreadable)
411
+ WarningCount++;
412
+ });
413
+ return { DirtyCount, ErrorCount, WarningCount };
414
+ }
415
+ /**
416
+ * Graph-path failures (`Lines[2].Amount`) this section owns, once the form has asked
417
+ * for validation to show. Plain field-name failures are NOT counted here — the field
418
+ * that renders them already reports through {@link fieldIndicators}.
419
+ */
420
+ claimedGraphErrors() {
421
+ const ctx = this.FormContext;
422
+ if (!ctx?.showValidation || !ctx.validationErrors?.length)
423
+ return [];
424
+ return ClaimedCollectionErrors(ctx.validationErrors, this.SectionKey, this.validationScope());
425
+ }
426
+ /** (Re)declare this section with the container-scoped registry the rail reads. */
427
+ registerIndicatorSource(previousKey) {
428
+ if (!this.indicators)
429
+ return;
430
+ if (previousKey && previousKey !== this.SectionKey) {
431
+ this.indicators.Unregister(this, previousKey);
432
+ }
433
+ this.indicators.Register(this);
434
+ }
248
435
  // ---- Lifecycle ----
249
436
  ngOnInit() {
250
437
  this.DisplayName = this.SectionName;
251
438
  this.chrome?.Changes.pipe(takeUntil(this.destroy$)).subscribe(() => {
252
439
  this.UpdateVisibilityAndHighlighting();
253
440
  });
441
+ this.registerIndicatorSource();
254
442
  }
255
443
  ngAfterContentInit() {
256
444
  this.UpdateFieldNames();
@@ -258,12 +446,18 @@ export class MjCollapsiblePanelComponent {
258
446
  this.FieldComponents.changes.subscribe(() => {
259
447
  this.UpdateFieldNames();
260
448
  this.SubscribeToFieldNavigateEvents();
449
+ // The set of fields changed, so the section's counts (and which errors it
450
+ // claims) may have too — let the rail re-read.
451
+ this.indicators?.NotifyChanged();
261
452
  });
262
453
  }
263
454
  ngOnChanges(changes) {
264
455
  if (changes['SectionName']) {
265
456
  this.DisplayName = this.SectionName;
266
457
  }
458
+ if (changes['SectionKey'] && !changes['SectionKey'].firstChange) {
459
+ this.registerIndicatorSource(changes['SectionKey'].previousValue);
460
+ }
267
461
  if (changes['SectionName'] || changes['FormContext']) {
268
462
  this.UpdateVisibilityAndHighlighting();
269
463
  }
@@ -277,6 +471,7 @@ export class MjCollapsiblePanelComponent {
277
471
  this.SetupResizeObserver();
278
472
  }
279
473
  ngOnDestroy() {
474
+ this.indicators?.Unregister(this);
280
475
  this.fieldNavReset$.next();
281
476
  this.fieldNavReset$.complete();
282
477
  this.destroy$.next();
@@ -425,7 +620,9 @@ export class MjCollapsiblePanelComponent {
425
620
  }
426
621
  /**
427
622
  * Subscribes to Navigate events from all child form-field components
428
- * and relays them through this panel's Navigate output.
623
+ * and relays them through this panel's Navigate output. Also listens for
624
+ * value edits so the section indicators (and the rail reading them) refresh
625
+ * on the same tick as the keystroke rather than on the container's next poll.
429
626
  */
430
627
  SubscribeToFieldNavigateEvents() {
431
628
  this.fieldNavReset$.next(); // tear down previous subscriptions
@@ -433,6 +630,10 @@ export class MjCollapsiblePanelComponent {
433
630
  field.Navigate.pipe(takeUntil(this.fieldNavReset$)).subscribe((event) => {
434
631
  this.Navigate.emit(event);
435
632
  });
633
+ field.ValueChange.pipe(takeUntil(this.fieldNavReset$)).subscribe(() => {
634
+ this.cdr.markForCheck();
635
+ this.indicators?.NotifyChanged();
636
+ });
436
637
  });
437
638
  }
438
639
  /**
@@ -484,6 +685,22 @@ export class MjCollapsiblePanelComponent {
484
685
  formRef?.SetSectionPanelHeight?.(this.SectionKey, height);
485
686
  }, 500);
486
687
  }
688
+ /**
689
+ * True when this panel projects form fields and field-level security denies the user EVERY one
690
+ * of them.
691
+ *
692
+ * Deliberately false when the panel projects no `mj-form-field` at all — related-entity grids,
693
+ * IS-A cards and slot-injected panels all legitimately have none, and hiding those would remove
694
+ * sections field security has nothing to say about. "No fields" and "no readable fields" are
695
+ * different states and only the second one should hide the section.
696
+ */
697
+ get AllProjectedFieldsDenied() {
698
+ const fields = this.FieldComponents;
699
+ if (!fields || fields.length === 0) {
700
+ return false;
701
+ }
702
+ return fields.toArray().every(f => !f.IsFieldReadableByUser);
703
+ }
487
704
  UpdateVisibilityAndHighlighting() {
488
705
  // Hard hide takes precedence over search state. Driven by an explicit
489
706
  // `Hidden` input OR the form config's section-visibility rules carried on
@@ -495,6 +712,15 @@ export class MjCollapsiblePanelComponent {
495
712
  this.cdr.markForCheck();
496
713
  return;
497
714
  }
715
+ // A section whose every field is denied by field-level security renders as a heading over
716
+ // nothing — the fields hide themselves individually, leaving an empty card that reads like a
717
+ // broken screen rather than a permissions boundary. Hide the section instead.
718
+ if (this.AllProjectedFieldsDenied) {
719
+ this.IsVisible = false;
720
+ this.DisplayName = EscapeHTML(this.SectionName);
721
+ this.cdr.markForCheck();
722
+ return;
723
+ }
498
724
  const searchTerm = (this.FormContext?.sectionFilter || '').toLowerCase().trim();
499
725
  if (!searchTerm) {
500
726
  this.IsVisible = this.hasRenderableContent();
@@ -523,29 +749,30 @@ export class MjCollapsiblePanelComponent {
523
749
  } if (rf & 2) {
524
750
  let _t;
525
751
  i0.ɵɵqueryRefresh(_t = i0.ɵɵloadQuery()) && (ctx.panelContentRef = _t.first);
526
- } }, hostVars: 7, hostBindings: function MjCollapsiblePanelComponent_HostBindings(rf, ctx) { if (rf & 1) {
752
+ } }, hostVars: 9, hostBindings: function MjCollapsiblePanelComponent_HostBindings(rf, ctx) { if (rf & 1) {
527
753
  i0.ɵɵlistener("dragover", function MjCollapsiblePanelComponent_dragover_HostBindingHandler($event) { return ctx.OnDragOver($event); })("dragleave", function MjCollapsiblePanelComponent_dragleave_HostBindingHandler($event) { return ctx.OnDragLeave($event); })("drop", function MjCollapsiblePanelComponent_drop_HostBindingHandler($event) { return ctx.OnDrop($event); });
528
754
  } if (rf & 2) {
529
- i0.ɵɵattribute("data-section-key", ctx.HostSectionKey)("data-variant", ctx.HostVariant)("data-icon", ctx.HostIcon);
755
+ i0.ɵɵattribute("data-section-key", ctx.HostSectionKey)("data-variant", ctx.HostVariant)("data-icon", ctx.HostIcon)("data-dirty-count", ctx.HostDirtyCount)("data-error-count", ctx.HostErrorCount);
530
756
  i0.ɵɵclassMap(ctx.HostClass);
531
757
  i0.ɵɵstyleProp("order", ctx.CssOrder);
532
- } }, inputs: { SectionKey: "SectionKey", SectionName: "SectionName", Icon: "Icon", Form: "Form", FormContext: "FormContext", Variant: "Variant", BadgeCount: "BadgeCount", DefaultExpanded: "DefaultExpanded", InheritedFromEntity: "InheritedFromEntity", InheritedRecordPrimaryKey: "InheritedRecordPrimaryKey", Hidden: "Hidden", _deprecatedSectionKey: [0, "sectionKey", "_deprecatedSectionKey"], _deprecatedSectionName: [0, "sectionName", "_deprecatedSectionName"], _deprecatedIcon: [0, "icon", "_deprecatedIcon"], _deprecatedForm: [0, "form", "_deprecatedForm"], _deprecatedFormContext: [0, "formContext", "_deprecatedFormContext"] }, outputs: { DragStarted: "DragStarted", DragEnded: "DragEnded", PanelDrop: "PanelDrop", Navigate: "Navigate" }, standalone: false, features: [i0.ɵɵNgOnChangesFeature], ngContentSelectors: _c1, decls: 14, vars: 20, consts: [["panelContent", ""], [1, "mj-forms-panel"], [1, "mj-forms-panel-header", 3, "click"], ["title", "Drag to reorder sections", "draggable", "true", 1, "mj-forms-drag-handle"], [1, "mj-forms-panel-title-group"], [1, "mj-forms-panel-icon"], [1, "mj-forms-panel-title"], [3, "innerHTML"], [1, "mj-forms-row-count-badge", 3, "mj-forms-row-count-badge--zero"], [1, "mj-forms-inherited-badge", 3, "title"], [1, "mj-forms-panel-chevron"], [1, "mj-forms-panel-body"], [1, "mj-forms-panel-content"], ["title", "Drag to reorder sections", "draggable", "true", 1, "mj-forms-drag-handle", 3, "dragstart", "dragend", "click"], [1, "fa-solid", "fa-grip-vertical"], [1, "mj-forms-row-count-badge"], [1, "mj-forms-inherited-badge", 3, "click", "title"], [1, "fa-solid", "fa-arrow-up"]], template: function MjCollapsiblePanelComponent_Template(rf, ctx) { if (rf & 1) {
533
- const _r1 = i0.ɵɵgetCurrentView();
758
+ } }, inputs: { SectionKey: "SectionKey", Indicators: "Indicators", ValidationSources: "ValidationSources", SectionName: "SectionName", Icon: "Icon", Form: "Form", FormContext: "FormContext", Variant: "Variant", BadgeCount: "BadgeCount", DefaultExpanded: "DefaultExpanded", InheritedFromEntity: "InheritedFromEntity", InheritedRecordPrimaryKey: "InheritedRecordPrimaryKey", Hidden: "Hidden", _deprecatedSectionKey: [0, "sectionKey", "_deprecatedSectionKey"], _deprecatedSectionName: [0, "sectionName", "_deprecatedSectionName"], _deprecatedIcon: [0, "icon", "_deprecatedIcon"], _deprecatedForm: [0, "form", "_deprecatedForm"], _deprecatedFormContext: [0, "formContext", "_deprecatedFormContext"] }, outputs: { DragStarted: "DragStarted", DragEnded: "DragEnded", PanelDrop: "PanelDrop", Navigate: "Navigate" }, standalone: false, features: [i0.ɵɵNgOnChangesFeature], ngContentSelectors: _c1, decls: 17, vars: 22, consts: [["panelContent", ""], [1, "mj-forms-panel"], [1, "mj-forms-panel-header", 3, "click"], ["title", "Drag to reorder sections", "draggable", "true", 1, "mj-forms-drag-handle"], [1, "mj-forms-panel-title-group"], [1, "mj-forms-panel-icon"], [1, "mj-forms-panel-title"], [3, "innerHTML"], ["role", "img", 1, "mj-forms-panel-dirty", 3, "title"], ["role", "img", 1, "mj-forms-panel-error-badge", 3, "title"], ["role", "img", 1, "mj-forms-panel-warning-badge", 3, "title"], [1, "mj-forms-row-count-badge", 3, "mj-forms-row-count-badge--zero"], [1, "mj-forms-inherited-badge", 3, "title"], [1, "mj-forms-panel-chevron"], [1, "mj-forms-panel-body"], [1, "mj-forms-panel-content"], ["title", "Drag to reorder sections", "draggable", "true", 1, "mj-forms-drag-handle", 3, "dragstart", "dragend", "click"], [1, "fa-solid", "fa-grip-vertical"], ["aria-hidden", "true", 1, "fa-solid", "fa-circle-exclamation"], ["aria-hidden", "true", 1, "fa-solid", "fa-triangle-exclamation"], [1, "mj-forms-row-count-badge"], [1, "mj-forms-inherited-badge", 3, "click", "title"], [1, "fa-solid", "fa-arrow-up"]], template: function MjCollapsiblePanelComponent_Template(rf, ctx) { if (rf & 1) {
534
759
  i0.ɵɵprojectionDef();
535
760
  i0.ɵɵelementStart(0, "div", 1)(1, "div", 2);
536
- i0.ɵɵlistener("click", function MjCollapsiblePanelComponent_Template_div_click_1_listener() { i0.ɵɵrestoreView(_r1); return i0.ɵɵresetView(ctx.Toggle()); });
761
+ i0.ɵɵlistener("click", function MjCollapsiblePanelComponent_Template_div_click_1_listener() { return ctx.Toggle(); });
537
762
  i0.ɵɵconditionalCreate(2, MjCollapsiblePanelComponent_Conditional_2_Template, 2, 0, "div", 3);
538
763
  i0.ɵɵelementStart(3, "div", 4);
539
764
  i0.ɵɵelement(4, "i", 5);
540
765
  i0.ɵɵelementStart(5, "h3", 6);
541
766
  i0.ɵɵelement(6, "span", 7);
542
- i0.ɵɵconditionalCreate(7, MjCollapsiblePanelComponent_Conditional_7_Template, 2, 3, "span", 8);
767
+ i0.ɵɵconditionalCreate(7, MjCollapsiblePanelComponent_Conditional_7_Template, 1, 2, "span", 8);
768
+ i0.ɵɵconditionalCreate(8, MjCollapsiblePanelComponent_Conditional_8_Template, 3, 3, "span", 9)(9, MjCollapsiblePanelComponent_Conditional_9_Template, 3, 3, "span", 10);
769
+ i0.ɵɵconditionalCreate(10, MjCollapsiblePanelComponent_Conditional_10_Template, 2, 3, "span", 11);
543
770
  i0.ɵɵelementEnd()();
544
- i0.ɵɵconditionalCreate(8, MjCollapsiblePanelComponent_Conditional_8_Template, 3, 2, "a", 9);
545
- i0.ɵɵconditionalCreate(9, MjCollapsiblePanelComponent_Conditional_9_Template, 2, 2, "div", 10);
771
+ i0.ɵɵconditionalCreate(11, MjCollapsiblePanelComponent_Conditional_11_Template, 3, 2, "a", 12);
772
+ i0.ɵɵconditionalCreate(12, MjCollapsiblePanelComponent_Conditional_12_Template, 2, 2, "div", 13);
546
773
  i0.ɵɵelementEnd();
547
- i0.ɵɵelementStart(10, "div", 11)(11, "div", 12, 0);
548
- i0.ɵɵprojection(13);
774
+ i0.ɵɵelementStart(13, "div", 14)(14, "div", 15, 0);
775
+ i0.ɵɵprojection(16);
549
776
  i0.ɵɵelementEnd()()();
550
777
  } if (rf & 2) {
551
778
  i0.ɵɵclassProp("mj-forms-panel--related", ctx.Variant === "related-entity")("mj-forms-panel--inherited", ctx.Variant === "inherited");
@@ -560,22 +787,30 @@ export class MjCollapsiblePanelComponent {
560
787
  i0.ɵɵadvance(2);
561
788
  i0.ɵɵproperty("innerHTML", ctx.DisplayName, i0.ɵɵsanitizeHtml);
562
789
  i0.ɵɵadvance();
563
- i0.ɵɵconditional(ctx.BadgeCount !== undefined ? 7 : -1);
790
+ i0.ɵɵconditional(ctx.SectionDirtyCount > 0 ? 7 : -1);
791
+ i0.ɵɵadvance();
792
+ i0.ɵɵconditional(ctx.SectionErrorCount > 0 ? 8 : ctx.SectionWarningCount > 0 ? 9 : -1);
793
+ i0.ɵɵadvance(2);
794
+ i0.ɵɵconditional(ctx.BadgeCount !== undefined ? 10 : -1);
564
795
  i0.ɵɵadvance();
565
- i0.ɵɵconditional(ctx.Variant === "inherited" && ctx.InheritedFromEntity ? 8 : -1);
796
+ i0.ɵɵconditional(ctx.Variant === "inherited" && ctx.InheritedFromEntity ? 11 : -1);
566
797
  i0.ɵɵadvance();
567
- i0.ɵɵconditional(ctx.Collapsible ? 9 : -1);
798
+ i0.ɵɵconditional(ctx.Collapsible ? 12 : -1);
568
799
  i0.ɵɵadvance();
569
800
  i0.ɵɵclassProp("mj-forms-panel-body--collapsed", !ctx.Expanded);
570
801
  i0.ɵɵadvance();
571
802
  i0.ɵɵstyleProp("height", ctx.PanelContentHeight, "px");
572
- } }, styles: ["mj-collapsible-panel {\n display: block;\n}\n\nmj-collapsible-panel.mj-search-hidden {\n display: none;\n}\n\nmj-collapsible-panel.mj-drag-over {\n border: 2px dashed var(--mj-brand-primary);\n border-radius: var(--mj-radius-md);\n}\n\n/* ============================================\n PANEL CARD\n ============================================ */\n.mj-forms-panel {\n background: var(--mj-bg-surface-card);\n border-radius: var(--mj-radius-md);\n box-shadow: var(--mj-shadow-sm);\n border: 1px solid var(--mj-border-default);\n overflow: hidden;\n transition: all 0.3s ease;\n}\n\n/* ============================================\n HEADER\n ============================================ */\n.mj-forms-panel-header {\n display: flex;\n align-items: center;\n gap: 12px;\n padding: 16px 20px;\n background: var(--mj-bg-surface-elevated);\n border-bottom: 2px solid var(--mj-border-default);\n cursor: pointer;\n user-select: none;\n transition: all 0.3s ease;\n}\n\n.mj-forms-panel-header:hover {\n background: var(--mj-bg-surface-hover);\n border-bottom-color: var(--mj-brand-primary);\n}\n\n/* Static header \u2014 sections locked open (Config.collapsibleSections === false) */\n.mj-forms-panel-header--static {\n cursor: default;\n}\n\n.mj-forms-panel-header--static:hover {\n background: var(--mj-bg-surface-elevated);\n border-bottom-color: var(--mj-border-default);\n}\n\n/* Drag handle */\n.mj-forms-drag-handle {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 0;\n height: 24px;\n color: var(--mj-text-muted);\n cursor: grab;\n opacity: 0;\n overflow: hidden;\n transition: width var(--mj-transition-base) ease, opacity var(--mj-transition-base) ease;\n flex-shrink: 0;\n}\n\n.mj-forms-panel-header:hover .mj-forms-drag-handle {\n width: 24px;\n opacity: 1;\n}\n\n.mj-forms-drag-handle:hover {\n color: var(--mj-brand-primary);\n}\n\n.mj-forms-drag-handle:active {\n cursor: grabbing;\n}\n\nmj-collapsible-panel.mj-dragging .mj-forms-drag-handle {\n width: 24px;\n opacity: 1;\n color: var(--mj-brand-primary);\n}\n\n/* Title group */\n.mj-forms-panel-title-group {\n display: flex;\n align-items: center;\n gap: 12px;\n flex: 1;\n}\n\n.mj-forms-panel-icon {\n font-size: 18px;\n color: var(--mj-brand-primary);\n width: 24px;\n text-align: center;\n}\n\n.mj-forms-panel-title {\n margin: 0;\n font-size: 16px;\n font-weight: 600;\n color: var(--mj-text-primary);\n}\n\n.mj-forms-panel-chevron {\n color: var(--mj-text-secondary);\n font-size: 14px;\n transition: transform 0.3s ease;\n padding: 4px;\n}\n\n/* Row count badge */\n.mj-forms-row-count-badge {\n background: var(--mj-status-success);\n color: var(--mj-text-inverse);\n padding: 2px 8px;\n border-radius: var(--mj-radius-full);\n font-size: 12px;\n font-weight: 600;\n margin-left: 8px;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n min-width: 24px;\n line-height: 1;\n}\n\n.mj-forms-row-count-badge--zero {\n background: var(--mj-text-muted);\n}\n\n/* ============================================\n INHERITED VARIANT (IS-A parent fields)\n ============================================ */\n.mj-forms-panel--inherited {\n border-left: 3px solid var(--mj-brand-primary);\n}\n\n.mj-forms-panel--inherited .mj-forms-panel-header {\n background: color-mix(in srgb, var(--mj-brand-primary) 8%, var(--mj-bg-surface-elevated));\n}\n\n.mj-forms-panel--inherited .mj-forms-panel-header:hover {\n background: color-mix(in srgb, var(--mj-brand-primary) 14%, var(--mj-bg-surface-elevated));\n border-bottom-color: var(--mj-brand-primary);\n}\n\n.mj-forms-panel--inherited .mj-forms-panel-icon {\n color: var(--mj-brand-primary);\n}\n\n.mj-forms-inherited-badge {\n display: inline-flex;\n align-items: center;\n gap: 4px;\n font-size: 11px;\n font-weight: 600;\n padding: 3px 10px;\n border-radius: 4px;\n background: color-mix(in srgb, var(--mj-brand-primary) 10%, var(--mj-bg-surface));\n color: var(--mj-brand-primary);\n border: 1px solid color-mix(in srgb, var(--mj-brand-primary) 25%, transparent);\n cursor: pointer;\n transition: all var(--mj-transition-base) ease;\n text-decoration: none;\n}\n\n.mj-forms-inherited-badge:hover {\n background: color-mix(in srgb, var(--mj-brand-primary) 18%, var(--mj-bg-surface));\n border-color: var(--mj-brand-primary);\n box-shadow: 0 2px 6px color-mix(in srgb, var(--mj-brand-primary) 20%, transparent);\n}\n\n.mj-forms-inherited-badge i {\n font-size: 10px;\n}\n\n/* ============================================\n RELATED ENTITY VARIANT\n ============================================ */\n.mj-forms-panel--related {\n border-left: 3px solid var(--mj-brand-primary);\n}\n\n.mj-forms-panel--related .mj-forms-panel-header {\n background: color-mix(in srgb, var(--mj-brand-primary) 8%, var(--mj-bg-surface-elevated));\n}\n\n.mj-forms-panel--related .mj-forms-panel-header:hover {\n background: color-mix(in srgb, var(--mj-brand-primary) 14%, var(--mj-bg-surface-elevated));\n border-bottom-color: var(--mj-brand-primary);\n}\n\n.mj-forms-panel--related .mj-forms-panel-icon {\n color: var(--mj-brand-primary);\n}\n\n/* Related entity content hugs the grid (toolbar + header + rows).\n A fixed 400px box left a hollow slab when AG Grid's viewport was 0,\n and a persisted 0/toolbar height clipped the rows after expand. */\n.mj-forms-panel--related .mj-forms-panel-content {\n height: auto;\n padding: 0;\n overflow: hidden;\n resize: vertical;\n min-height: 0;\n max-height: 800px;\n}\n\n/* AG Grid must fill the 400px / leftover box. Do not stretch every\n descendant \u2014 custom widgets (relationship list) put their section\n header as the first child, and height:100% on it parks the title\n mid-column and hides the list below the fold. */\n.mj-forms-panel--related .mj-forms-panel-content > mj-explorer-entity-data-grid,\n.mj-forms-panel--related .mj-forms-panel-content > .ng-star-inserted > mj-explorer-entity-data-grid {\n height: 100%;\n}\n\n/* Related entity panels need the body to not clip the resize handle */\n.mj-forms-panel--related .mj-forms-panel-body {\n overflow: visible;\n}\n\n/* ============================================\n BODY (collapsible)\n ============================================ */\n.mj-forms-panel-body {\n max-height: 2000px;\n overflow: hidden;\n transition: max-height 0.4s ease, padding 0.4s ease, opacity 0.3s ease;\n opacity: 1;\n}\n\n.mj-forms-panel-body--collapsed {\n max-height: 0 !important;\n padding: 0 !important;\n opacity: 0 !important;\n overflow: hidden !important;\n pointer-events: none;\n}\n\n.mj-forms-panel-body--collapsed .mj-forms-panel-content {\n padding-top: 0 !important;\n padding-bottom: 0 !important;\n max-height: 0 !important;\n overflow: hidden !important;\n}\n\n.mj-forms-panel-content {\n padding: 20px;\n overflow: auto;\n max-height: 600px;\n}\n\n/* ============================================\n SEARCH HIGHLIGHTING\n ============================================ */\n.mj-forms-panel-title ::ng-deep .mj-forms-search-highlight {\n /* Muted, theme-aware highlight (semantic token) \u2014 consistent with the field\n label + FK dropdown highlights. Replaces the old bright-yellow primitive. */\n background-color: var(--mj-search-highlight-bg);\n color: var(--mj-search-highlight-text);\n border-radius: 2px;\n padding: 0 1px;\n font-weight: 600;\n}\n"], encapsulation: 2, changeDetection: 0 });
803
+ } }, styles: ["mj-collapsible-panel {\n display: block;\n}\n\nmj-collapsible-panel.mj-search-hidden {\n display: none;\n}\n\nmj-collapsible-panel.mj-drag-over {\n border: 2px dashed var(--mj-brand-primary);\n border-radius: var(--mj-radius-md);\n}\n\n/* ============================================\n PANEL CARD\n ============================================ */\n.mj-forms-panel {\n background: var(--mj-bg-surface-card);\n border-radius: var(--mj-radius-md);\n box-shadow: var(--mj-shadow-sm);\n border: 1px solid var(--mj-border-default);\n overflow: hidden;\n transition: all 0.3s ease;\n}\n\n/* ============================================\n HEADER\n ============================================ */\n.mj-forms-panel-header {\n display: flex;\n align-items: center;\n gap: 12px;\n padding: 16px 20px;\n background: var(--mj-bg-surface-elevated);\n border-bottom: 2px solid var(--mj-border-default);\n cursor: pointer;\n user-select: none;\n transition: all 0.3s ease;\n}\n\n.mj-forms-panel-header:hover {\n background: var(--mj-bg-surface-hover);\n border-bottom-color: var(--mj-brand-primary);\n}\n\n/* Static header \u2014 sections locked open (Config.collapsibleSections === false) */\n.mj-forms-panel-header--static {\n cursor: default;\n}\n\n.mj-forms-panel-header--static:hover {\n background: var(--mj-bg-surface-elevated);\n border-bottom-color: var(--mj-border-default);\n}\n\n/* Drag handle */\n.mj-forms-drag-handle {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 0;\n height: 24px;\n color: var(--mj-text-muted);\n cursor: grab;\n opacity: 0;\n overflow: hidden;\n transition: width var(--mj-transition-base) ease, opacity var(--mj-transition-base) ease;\n flex-shrink: 0;\n}\n\n.mj-forms-panel-header:hover .mj-forms-drag-handle {\n width: 24px;\n opacity: 1;\n}\n\n.mj-forms-drag-handle:hover {\n color: var(--mj-brand-primary);\n}\n\n.mj-forms-drag-handle:active {\n cursor: grabbing;\n}\n\nmj-collapsible-panel.mj-dragging .mj-forms-drag-handle {\n width: 24px;\n opacity: 1;\n color: var(--mj-brand-primary);\n}\n\n/* Title group */\n.mj-forms-panel-title-group {\n display: flex;\n align-items: center;\n gap: 12px;\n flex: 1;\n}\n\n.mj-forms-panel-icon {\n font-size: 18px;\n color: var(--mj-brand-primary);\n width: 24px;\n text-align: center;\n}\n\n.mj-forms-panel-title {\n margin: 0;\n font-size: 16px;\n font-weight: 600;\n color: var(--mj-text-primary);\n}\n\n.mj-forms-panel-chevron {\n color: var(--mj-text-secondary);\n font-size: 14px;\n transition: transform 0.3s ease;\n padding: 4px;\n}\n\n/* Row count badge */\n.mj-forms-row-count-badge {\n background: var(--mj-status-success);\n color: var(--mj-text-inverse);\n padding: 2px 8px;\n border-radius: var(--mj-radius-full);\n font-size: 12px;\n font-weight: 600;\n margin-left: 8px;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n min-width: 24px;\n line-height: 1;\n}\n\n.mj-forms-row-count-badge--zero {\n background: var(--mj-text-muted);\n}\n\n/* ============================================\n SECTION INDICATORS\n Unsaved-changes dot + invalid-field count, derived from the section's own\n fields so a collapsed accordion header (or the left-nav rail, which reads\n the same counts) says WHERE the edited / failing field is.\n ============================================ */\n\n/* Same 6px amber dot an edited field shows after its label. */\n.mj-forms-panel-dirty {\n display: inline-block;\n width: var(--mj-space-1-5);\n height: var(--mj-space-1-5);\n border-radius: var(--mj-radius-full);\n background: var(--mj-status-warning);\n margin-left: var(--mj-space-2);\n vertical-align: middle;\n}\n\n.mj-forms-panel-error-badge,\n.mj-forms-panel-warning-badge {\n padding: var(--mj-space-0-5) var(--mj-space-2);\n border-radius: var(--mj-radius-full);\n font-size: var(--mj-text-xs);\n font-weight: var(--mj-font-semibold);\n margin-left: var(--mj-space-2);\n display: inline-flex;\n align-items: center;\n gap: var(--mj-space-1);\n justify-content: center;\n min-width: var(--mj-space-6);\n line-height: 1;\n vertical-align: middle;\n}\n\n/* Same colour as the failing field's underline. */\n.mj-forms-panel-error-badge {\n background: var(--mj-status-error);\n color: var(--mj-text-inverse);\n}\n\n.mj-forms-panel-warning-badge {\n background: var(--mj-status-warning-bg);\n color: var(--mj-status-warning-text);\n border: 1px solid var(--mj-status-warning-border);\n}\n\n.mj-forms-panel-error-badge i,\n.mj-forms-panel-warning-badge i {\n font-size: var(--mj-text-xs);\n}\n\n/* Card edge + header tint while the section holds an invalid field \u2014 the same\n left-edge treatment the related / inherited variants use. Not applied to the\n selected left-nav section (`.mj-chrome-show`): there the rail is the header\n and the card chrome is stripped. */\nmj-collapsible-panel.mj-panel-has-errors:not(.mj-chrome-show) > .mj-forms-panel {\n border-left: 3px solid var(--mj-status-error);\n}\n\nmj-collapsible-panel.mj-panel-has-errors:not(.mj-chrome-show) > .mj-forms-panel > .mj-forms-panel-header {\n background: color-mix(in srgb, var(--mj-status-error) 8%, var(--mj-bg-surface-elevated));\n}\n\nmj-collapsible-panel.mj-panel-has-warnings:not(.mj-chrome-show) > .mj-forms-panel {\n border-left: 3px solid var(--mj-status-warning);\n}\n\n/* ============================================\n INHERITED VARIANT (IS-A parent fields)\n ============================================ */\n.mj-forms-panel--inherited {\n border-left: 3px solid var(--mj-brand-primary);\n}\n\n.mj-forms-panel--inherited .mj-forms-panel-header {\n background: color-mix(in srgb, var(--mj-brand-primary) 8%, var(--mj-bg-surface-elevated));\n}\n\n.mj-forms-panel--inherited .mj-forms-panel-header:hover {\n background: color-mix(in srgb, var(--mj-brand-primary) 14%, var(--mj-bg-surface-elevated));\n border-bottom-color: var(--mj-brand-primary);\n}\n\n.mj-forms-panel--inherited .mj-forms-panel-icon {\n color: var(--mj-brand-primary);\n}\n\n.mj-forms-inherited-badge {\n display: inline-flex;\n align-items: center;\n gap: 4px;\n font-size: 11px;\n font-weight: 600;\n padding: 3px 10px;\n border-radius: 4px;\n background: color-mix(in srgb, var(--mj-brand-primary) 10%, var(--mj-bg-surface));\n color: var(--mj-brand-primary);\n border: 1px solid color-mix(in srgb, var(--mj-brand-primary) 25%, transparent);\n cursor: pointer;\n transition: all var(--mj-transition-base) ease;\n text-decoration: none;\n}\n\n.mj-forms-inherited-badge:hover {\n background: color-mix(in srgb, var(--mj-brand-primary) 18%, var(--mj-bg-surface));\n border-color: var(--mj-brand-primary);\n box-shadow: 0 2px 6px color-mix(in srgb, var(--mj-brand-primary) 20%, transparent);\n}\n\n.mj-forms-inherited-badge i {\n font-size: 10px;\n}\n\n/* ============================================\n RELATED ENTITY VARIANT\n ============================================ */\n.mj-forms-panel--related {\n border-left: 3px solid var(--mj-brand-primary);\n}\n\n.mj-forms-panel--related .mj-forms-panel-header {\n background: color-mix(in srgb, var(--mj-brand-primary) 8%, var(--mj-bg-surface-elevated));\n}\n\n.mj-forms-panel--related .mj-forms-panel-header:hover {\n background: color-mix(in srgb, var(--mj-brand-primary) 14%, var(--mj-bg-surface-elevated));\n border-bottom-color: var(--mj-brand-primary);\n}\n\n.mj-forms-panel--related .mj-forms-panel-icon {\n color: var(--mj-brand-primary);\n}\n\n/* Related entity content hugs the grid (toolbar + header + rows).\n A fixed 400px box left a hollow slab when AG Grid's viewport was 0,\n and a persisted 0/toolbar height clipped the rows after expand. */\n.mj-forms-panel--related .mj-forms-panel-content {\n height: auto;\n padding: 0;\n overflow: hidden;\n resize: vertical;\n min-height: 0;\n max-height: 800px;\n}\n\n/* AG Grid must fill the 400px / leftover box. Do not stretch every\n descendant \u2014 custom widgets (relationship list) put their section\n header as the first child, and height:100% on it parks the title\n mid-column and hides the list below the fold. */\n.mj-forms-panel--related .mj-forms-panel-content > mj-explorer-entity-data-grid,\n.mj-forms-panel--related .mj-forms-panel-content > .ng-star-inserted > mj-explorer-entity-data-grid {\n height: 100%;\n}\n\n/* Related entity panels need the body to not clip the resize handle */\n.mj-forms-panel--related .mj-forms-panel-body {\n overflow: visible;\n}\n\n/* ============================================\n BODY (collapsible)\n ============================================ */\n.mj-forms-panel-body {\n max-height: 2000px;\n overflow: hidden;\n transition: max-height 0.4s ease, padding 0.4s ease, opacity 0.3s ease;\n opacity: 1;\n}\n\n.mj-forms-panel-body--collapsed {\n max-height: 0 !important;\n padding: 0 !important;\n opacity: 0 !important;\n overflow: hidden !important;\n pointer-events: none;\n}\n\n.mj-forms-panel-body--collapsed .mj-forms-panel-content {\n padding-top: 0 !important;\n padding-bottom: 0 !important;\n max-height: 0 !important;\n overflow: hidden !important;\n}\n\n.mj-forms-panel-content {\n padding: 20px;\n overflow: auto;\n max-height: 600px;\n}\n\n/* ============================================\n SEARCH HIGHLIGHTING\n ============================================ */\n.mj-forms-panel-title ::ng-deep .mj-forms-search-highlight {\n /* Muted, theme-aware highlight (semantic token) \u2014 consistent with the field\n label + FK dropdown highlights. Replaces the old bright-yellow primitive. */\n background-color: var(--mj-search-highlight-bg);\n color: var(--mj-search-highlight-text);\n border-radius: 2px;\n padding: 0 1px;\n font-weight: 600;\n}\n"], encapsulation: 2, changeDetection: 0 });
573
804
  }
574
805
  (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(MjCollapsiblePanelComponent, [{
575
806
  type: Component,
576
- args: [{ standalone: false, selector: 'mj-collapsible-panel', changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, template: "<div class=\"mj-forms-panel\"\n [class.mj-forms-panel--related]=\"Variant === 'related-entity'\"\n [class.mj-forms-panel--inherited]=\"Variant === 'inherited'\"\n [attr.data-section-key]=\"SectionKey\">\n\n <!-- Panel Header -->\n <div class=\"mj-forms-panel-header\"\n [class.mj-forms-panel-header--static]=\"!Collapsible\"\n [attr.role]=\"Collapsible ? 'button' : null\"\n [attr.tabindex]=\"Collapsible ? 0 : null\"\n (click)=\"Toggle()\">\n <!-- Drag Handle (hidden when reorder disabled) -->\n @if (ReorderAllowed) {\n <div class=\"mj-forms-drag-handle\"\n title=\"Drag to reorder sections\"\n draggable=\"true\"\n (dragstart)=\"OnDragStart($event)\"\n (dragend)=\"OnDragEnd()\"\n (click)=\"$event.stopPropagation()\">\n <i class=\"fa-solid fa-grip-vertical\"></i>\n </div>\n }\n\n <!-- Icon + Title -->\n <div class=\"mj-forms-panel-title-group\">\n <i [class]=\"Icon\" class=\"mj-forms-panel-icon\"></i>\n <h3 class=\"mj-forms-panel-title\">\n <span [innerHTML]=\"DisplayName\"></span>\n <!-- Row count badge for related entities -->\n @if (BadgeCount !== undefined) {\n <span class=\"mj-forms-row-count-badge\" [class.mj-forms-row-count-badge--zero]=\"BadgeCount === 0\">\n {{ BadgeCount }}\n </span>\n }\n </h3>\n </div>\n\n <!-- Inherited From badge (IS-A) -->\n @if (Variant === 'inherited' && InheritedFromEntity) {\n <a class=\"mj-forms-inherited-badge\" (click)=\"OnInheritedBadgeClick($event)\"\n [title]=\"'View parent record: ' + InheritedFromEntity\">\n <i class=\"fa-solid fa-arrow-up\"></i>\n Inherited from {{ InheritedFromEntity }}\n </a>\n }\n\n <!-- Chevron (hidden when sections are locked open) -->\n @if (Collapsible) {\n <div class=\"mj-forms-panel-chevron\">\n <i [class]=\"Expanded ? 'fa fa-chevron-up' : 'fa fa-chevron-down'\"></i>\n </div>\n }\n </div>\n\n <!-- Panel Body -->\n <div class=\"mj-forms-panel-body\" [class.mj-forms-panel-body--collapsed]=\"!Expanded\">\n <div class=\"mj-forms-panel-content\"\n #panelContent\n [style.height.px]=\"PanelContentHeight\">\n <ng-content></ng-content>\n </div>\n </div>\n</div>\n", styles: ["mj-collapsible-panel {\n display: block;\n}\n\nmj-collapsible-panel.mj-search-hidden {\n display: none;\n}\n\nmj-collapsible-panel.mj-drag-over {\n border: 2px dashed var(--mj-brand-primary);\n border-radius: var(--mj-radius-md);\n}\n\n/* ============================================\n PANEL CARD\n ============================================ */\n.mj-forms-panel {\n background: var(--mj-bg-surface-card);\n border-radius: var(--mj-radius-md);\n box-shadow: var(--mj-shadow-sm);\n border: 1px solid var(--mj-border-default);\n overflow: hidden;\n transition: all 0.3s ease;\n}\n\n/* ============================================\n HEADER\n ============================================ */\n.mj-forms-panel-header {\n display: flex;\n align-items: center;\n gap: 12px;\n padding: 16px 20px;\n background: var(--mj-bg-surface-elevated);\n border-bottom: 2px solid var(--mj-border-default);\n cursor: pointer;\n user-select: none;\n transition: all 0.3s ease;\n}\n\n.mj-forms-panel-header:hover {\n background: var(--mj-bg-surface-hover);\n border-bottom-color: var(--mj-brand-primary);\n}\n\n/* Static header \u2014 sections locked open (Config.collapsibleSections === false) */\n.mj-forms-panel-header--static {\n cursor: default;\n}\n\n.mj-forms-panel-header--static:hover {\n background: var(--mj-bg-surface-elevated);\n border-bottom-color: var(--mj-border-default);\n}\n\n/* Drag handle */\n.mj-forms-drag-handle {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 0;\n height: 24px;\n color: var(--mj-text-muted);\n cursor: grab;\n opacity: 0;\n overflow: hidden;\n transition: width var(--mj-transition-base) ease, opacity var(--mj-transition-base) ease;\n flex-shrink: 0;\n}\n\n.mj-forms-panel-header:hover .mj-forms-drag-handle {\n width: 24px;\n opacity: 1;\n}\n\n.mj-forms-drag-handle:hover {\n color: var(--mj-brand-primary);\n}\n\n.mj-forms-drag-handle:active {\n cursor: grabbing;\n}\n\nmj-collapsible-panel.mj-dragging .mj-forms-drag-handle {\n width: 24px;\n opacity: 1;\n color: var(--mj-brand-primary);\n}\n\n/* Title group */\n.mj-forms-panel-title-group {\n display: flex;\n align-items: center;\n gap: 12px;\n flex: 1;\n}\n\n.mj-forms-panel-icon {\n font-size: 18px;\n color: var(--mj-brand-primary);\n width: 24px;\n text-align: center;\n}\n\n.mj-forms-panel-title {\n margin: 0;\n font-size: 16px;\n font-weight: 600;\n color: var(--mj-text-primary);\n}\n\n.mj-forms-panel-chevron {\n color: var(--mj-text-secondary);\n font-size: 14px;\n transition: transform 0.3s ease;\n padding: 4px;\n}\n\n/* Row count badge */\n.mj-forms-row-count-badge {\n background: var(--mj-status-success);\n color: var(--mj-text-inverse);\n padding: 2px 8px;\n border-radius: var(--mj-radius-full);\n font-size: 12px;\n font-weight: 600;\n margin-left: 8px;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n min-width: 24px;\n line-height: 1;\n}\n\n.mj-forms-row-count-badge--zero {\n background: var(--mj-text-muted);\n}\n\n/* ============================================\n INHERITED VARIANT (IS-A parent fields)\n ============================================ */\n.mj-forms-panel--inherited {\n border-left: 3px solid var(--mj-brand-primary);\n}\n\n.mj-forms-panel--inherited .mj-forms-panel-header {\n background: color-mix(in srgb, var(--mj-brand-primary) 8%, var(--mj-bg-surface-elevated));\n}\n\n.mj-forms-panel--inherited .mj-forms-panel-header:hover {\n background: color-mix(in srgb, var(--mj-brand-primary) 14%, var(--mj-bg-surface-elevated));\n border-bottom-color: var(--mj-brand-primary);\n}\n\n.mj-forms-panel--inherited .mj-forms-panel-icon {\n color: var(--mj-brand-primary);\n}\n\n.mj-forms-inherited-badge {\n display: inline-flex;\n align-items: center;\n gap: 4px;\n font-size: 11px;\n font-weight: 600;\n padding: 3px 10px;\n border-radius: 4px;\n background: color-mix(in srgb, var(--mj-brand-primary) 10%, var(--mj-bg-surface));\n color: var(--mj-brand-primary);\n border: 1px solid color-mix(in srgb, var(--mj-brand-primary) 25%, transparent);\n cursor: pointer;\n transition: all var(--mj-transition-base) ease;\n text-decoration: none;\n}\n\n.mj-forms-inherited-badge:hover {\n background: color-mix(in srgb, var(--mj-brand-primary) 18%, var(--mj-bg-surface));\n border-color: var(--mj-brand-primary);\n box-shadow: 0 2px 6px color-mix(in srgb, var(--mj-brand-primary) 20%, transparent);\n}\n\n.mj-forms-inherited-badge i {\n font-size: 10px;\n}\n\n/* ============================================\n RELATED ENTITY VARIANT\n ============================================ */\n.mj-forms-panel--related {\n border-left: 3px solid var(--mj-brand-primary);\n}\n\n.mj-forms-panel--related .mj-forms-panel-header {\n background: color-mix(in srgb, var(--mj-brand-primary) 8%, var(--mj-bg-surface-elevated));\n}\n\n.mj-forms-panel--related .mj-forms-panel-header:hover {\n background: color-mix(in srgb, var(--mj-brand-primary) 14%, var(--mj-bg-surface-elevated));\n border-bottom-color: var(--mj-brand-primary);\n}\n\n.mj-forms-panel--related .mj-forms-panel-icon {\n color: var(--mj-brand-primary);\n}\n\n/* Related entity content hugs the grid (toolbar + header + rows).\n A fixed 400px box left a hollow slab when AG Grid's viewport was 0,\n and a persisted 0/toolbar height clipped the rows after expand. */\n.mj-forms-panel--related .mj-forms-panel-content {\n height: auto;\n padding: 0;\n overflow: hidden;\n resize: vertical;\n min-height: 0;\n max-height: 800px;\n}\n\n/* AG Grid must fill the 400px / leftover box. Do not stretch every\n descendant \u2014 custom widgets (relationship list) put their section\n header as the first child, and height:100% on it parks the title\n mid-column and hides the list below the fold. */\n.mj-forms-panel--related .mj-forms-panel-content > mj-explorer-entity-data-grid,\n.mj-forms-panel--related .mj-forms-panel-content > .ng-star-inserted > mj-explorer-entity-data-grid {\n height: 100%;\n}\n\n/* Related entity panels need the body to not clip the resize handle */\n.mj-forms-panel--related .mj-forms-panel-body {\n overflow: visible;\n}\n\n/* ============================================\n BODY (collapsible)\n ============================================ */\n.mj-forms-panel-body {\n max-height: 2000px;\n overflow: hidden;\n transition: max-height 0.4s ease, padding 0.4s ease, opacity 0.3s ease;\n opacity: 1;\n}\n\n.mj-forms-panel-body--collapsed {\n max-height: 0 !important;\n padding: 0 !important;\n opacity: 0 !important;\n overflow: hidden !important;\n pointer-events: none;\n}\n\n.mj-forms-panel-body--collapsed .mj-forms-panel-content {\n padding-top: 0 !important;\n padding-bottom: 0 !important;\n max-height: 0 !important;\n overflow: hidden !important;\n}\n\n.mj-forms-panel-content {\n padding: 20px;\n overflow: auto;\n max-height: 600px;\n}\n\n/* ============================================\n SEARCH HIGHLIGHTING\n ============================================ */\n.mj-forms-panel-title ::ng-deep .mj-forms-search-highlight {\n /* Muted, theme-aware highlight (semantic token) \u2014 consistent with the field\n label + FK dropdown highlights. Replaces the old bright-yellow primitive. */\n background-color: var(--mj-search-highlight-bg);\n color: var(--mj-search-highlight-text);\n border-radius: 2px;\n padding: 0 1px;\n font-weight: 600;\n}\n"] }]
807
+ args: [{ standalone: false, selector: 'mj-collapsible-panel', changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, template: "<div class=\"mj-forms-panel\"\n [class.mj-forms-panel--related]=\"Variant === 'related-entity'\"\n [class.mj-forms-panel--inherited]=\"Variant === 'inherited'\"\n [attr.data-section-key]=\"SectionKey\">\n\n <!-- Panel Header -->\n <div class=\"mj-forms-panel-header\"\n [class.mj-forms-panel-header--static]=\"!Collapsible\"\n [attr.role]=\"Collapsible ? 'button' : null\"\n [attr.tabindex]=\"Collapsible ? 0 : null\"\n (click)=\"Toggle()\">\n <!-- Drag Handle (hidden when reorder disabled) -->\n @if (ReorderAllowed) {\n <div class=\"mj-forms-drag-handle\"\n title=\"Drag to reorder sections\"\n draggable=\"true\"\n (dragstart)=\"OnDragStart($event)\"\n (dragend)=\"OnDragEnd()\"\n (click)=\"$event.stopPropagation()\">\n <i class=\"fa-solid fa-grip-vertical\"></i>\n </div>\n }\n\n <!-- Icon + Title -->\n <div class=\"mj-forms-panel-title-group\">\n <i [class]=\"Icon\" class=\"mj-forms-panel-icon\"></i>\n <h3 class=\"mj-forms-panel-title\">\n <span [innerHTML]=\"DisplayName\"></span>\n <!-- Section indicators: unsaved-changes dot (same idiom as an edited field's\n label dot) and invalid-field count (same colour as the field's red underline).\n Derived from the projected fields \u2014 see SectionIndicators. -->\n @if (SectionDirtyCount > 0) {\n <span class=\"mj-forms-panel-dirty\"\n role=\"img\"\n [title]=\"SectionDirtyTitle\"\n [attr.aria-label]=\"SectionDirtyTitle\"></span>\n }\n @if (SectionErrorCount > 0) {\n <span class=\"mj-forms-panel-error-badge\"\n role=\"img\"\n [title]=\"SectionErrorTitle\"\n [attr.aria-label]=\"SectionErrorTitle\">\n <i class=\"fa-solid fa-circle-exclamation\" aria-hidden=\"true\"></i>{{ SectionErrorCount }}\n </span>\n } @else if (SectionWarningCount > 0) {\n <span class=\"mj-forms-panel-warning-badge\"\n role=\"img\"\n [title]=\"SectionWarningTitle\"\n [attr.aria-label]=\"SectionWarningTitle\">\n <i class=\"fa-solid fa-triangle-exclamation\" aria-hidden=\"true\"></i>{{ SectionWarningCount }}\n </span>\n }\n <!-- Row count badge for related entities -->\n @if (BadgeCount !== undefined) {\n <span class=\"mj-forms-row-count-badge\" [class.mj-forms-row-count-badge--zero]=\"BadgeCount === 0\">\n {{ BadgeCount }}\n </span>\n }\n </h3>\n </div>\n\n <!-- Inherited From badge (IS-A) -->\n @if (Variant === 'inherited' && InheritedFromEntity) {\n <a class=\"mj-forms-inherited-badge\" (click)=\"OnInheritedBadgeClick($event)\"\n [title]=\"'View parent record: ' + InheritedFromEntity\">\n <i class=\"fa-solid fa-arrow-up\"></i>\n Inherited from {{ InheritedFromEntity }}\n </a>\n }\n\n <!-- Chevron (hidden when sections are locked open) -->\n @if (Collapsible) {\n <div class=\"mj-forms-panel-chevron\">\n <i [class]=\"Expanded ? 'fa fa-chevron-up' : 'fa fa-chevron-down'\"></i>\n </div>\n }\n </div>\n\n <!-- Panel Body -->\n <div class=\"mj-forms-panel-body\" [class.mj-forms-panel-body--collapsed]=\"!Expanded\">\n <div class=\"mj-forms-panel-content\"\n #panelContent\n [style.height.px]=\"PanelContentHeight\">\n <ng-content></ng-content>\n </div>\n </div>\n</div>\n", styles: ["mj-collapsible-panel {\n display: block;\n}\n\nmj-collapsible-panel.mj-search-hidden {\n display: none;\n}\n\nmj-collapsible-panel.mj-drag-over {\n border: 2px dashed var(--mj-brand-primary);\n border-radius: var(--mj-radius-md);\n}\n\n/* ============================================\n PANEL CARD\n ============================================ */\n.mj-forms-panel {\n background: var(--mj-bg-surface-card);\n border-radius: var(--mj-radius-md);\n box-shadow: var(--mj-shadow-sm);\n border: 1px solid var(--mj-border-default);\n overflow: hidden;\n transition: all 0.3s ease;\n}\n\n/* ============================================\n HEADER\n ============================================ */\n.mj-forms-panel-header {\n display: flex;\n align-items: center;\n gap: 12px;\n padding: 16px 20px;\n background: var(--mj-bg-surface-elevated);\n border-bottom: 2px solid var(--mj-border-default);\n cursor: pointer;\n user-select: none;\n transition: all 0.3s ease;\n}\n\n.mj-forms-panel-header:hover {\n background: var(--mj-bg-surface-hover);\n border-bottom-color: var(--mj-brand-primary);\n}\n\n/* Static header \u2014 sections locked open (Config.collapsibleSections === false) */\n.mj-forms-panel-header--static {\n cursor: default;\n}\n\n.mj-forms-panel-header--static:hover {\n background: var(--mj-bg-surface-elevated);\n border-bottom-color: var(--mj-border-default);\n}\n\n/* Drag handle */\n.mj-forms-drag-handle {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 0;\n height: 24px;\n color: var(--mj-text-muted);\n cursor: grab;\n opacity: 0;\n overflow: hidden;\n transition: width var(--mj-transition-base) ease, opacity var(--mj-transition-base) ease;\n flex-shrink: 0;\n}\n\n.mj-forms-panel-header:hover .mj-forms-drag-handle {\n width: 24px;\n opacity: 1;\n}\n\n.mj-forms-drag-handle:hover {\n color: var(--mj-brand-primary);\n}\n\n.mj-forms-drag-handle:active {\n cursor: grabbing;\n}\n\nmj-collapsible-panel.mj-dragging .mj-forms-drag-handle {\n width: 24px;\n opacity: 1;\n color: var(--mj-brand-primary);\n}\n\n/* Title group */\n.mj-forms-panel-title-group {\n display: flex;\n align-items: center;\n gap: 12px;\n flex: 1;\n}\n\n.mj-forms-panel-icon {\n font-size: 18px;\n color: var(--mj-brand-primary);\n width: 24px;\n text-align: center;\n}\n\n.mj-forms-panel-title {\n margin: 0;\n font-size: 16px;\n font-weight: 600;\n color: var(--mj-text-primary);\n}\n\n.mj-forms-panel-chevron {\n color: var(--mj-text-secondary);\n font-size: 14px;\n transition: transform 0.3s ease;\n padding: 4px;\n}\n\n/* Row count badge */\n.mj-forms-row-count-badge {\n background: var(--mj-status-success);\n color: var(--mj-text-inverse);\n padding: 2px 8px;\n border-radius: var(--mj-radius-full);\n font-size: 12px;\n font-weight: 600;\n margin-left: 8px;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n min-width: 24px;\n line-height: 1;\n}\n\n.mj-forms-row-count-badge--zero {\n background: var(--mj-text-muted);\n}\n\n/* ============================================\n SECTION INDICATORS\n Unsaved-changes dot + invalid-field count, derived from the section's own\n fields so a collapsed accordion header (or the left-nav rail, which reads\n the same counts) says WHERE the edited / failing field is.\n ============================================ */\n\n/* Same 6px amber dot an edited field shows after its label. */\n.mj-forms-panel-dirty {\n display: inline-block;\n width: var(--mj-space-1-5);\n height: var(--mj-space-1-5);\n border-radius: var(--mj-radius-full);\n background: var(--mj-status-warning);\n margin-left: var(--mj-space-2);\n vertical-align: middle;\n}\n\n.mj-forms-panel-error-badge,\n.mj-forms-panel-warning-badge {\n padding: var(--mj-space-0-5) var(--mj-space-2);\n border-radius: var(--mj-radius-full);\n font-size: var(--mj-text-xs);\n font-weight: var(--mj-font-semibold);\n margin-left: var(--mj-space-2);\n display: inline-flex;\n align-items: center;\n gap: var(--mj-space-1);\n justify-content: center;\n min-width: var(--mj-space-6);\n line-height: 1;\n vertical-align: middle;\n}\n\n/* Same colour as the failing field's underline. */\n.mj-forms-panel-error-badge {\n background: var(--mj-status-error);\n color: var(--mj-text-inverse);\n}\n\n.mj-forms-panel-warning-badge {\n background: var(--mj-status-warning-bg);\n color: var(--mj-status-warning-text);\n border: 1px solid var(--mj-status-warning-border);\n}\n\n.mj-forms-panel-error-badge i,\n.mj-forms-panel-warning-badge i {\n font-size: var(--mj-text-xs);\n}\n\n/* Card edge + header tint while the section holds an invalid field \u2014 the same\n left-edge treatment the related / inherited variants use. Not applied to the\n selected left-nav section (`.mj-chrome-show`): there the rail is the header\n and the card chrome is stripped. */\nmj-collapsible-panel.mj-panel-has-errors:not(.mj-chrome-show) > .mj-forms-panel {\n border-left: 3px solid var(--mj-status-error);\n}\n\nmj-collapsible-panel.mj-panel-has-errors:not(.mj-chrome-show) > .mj-forms-panel > .mj-forms-panel-header {\n background: color-mix(in srgb, var(--mj-status-error) 8%, var(--mj-bg-surface-elevated));\n}\n\nmj-collapsible-panel.mj-panel-has-warnings:not(.mj-chrome-show) > .mj-forms-panel {\n border-left: 3px solid var(--mj-status-warning);\n}\n\n/* ============================================\n INHERITED VARIANT (IS-A parent fields)\n ============================================ */\n.mj-forms-panel--inherited {\n border-left: 3px solid var(--mj-brand-primary);\n}\n\n.mj-forms-panel--inherited .mj-forms-panel-header {\n background: color-mix(in srgb, var(--mj-brand-primary) 8%, var(--mj-bg-surface-elevated));\n}\n\n.mj-forms-panel--inherited .mj-forms-panel-header:hover {\n background: color-mix(in srgb, var(--mj-brand-primary) 14%, var(--mj-bg-surface-elevated));\n border-bottom-color: var(--mj-brand-primary);\n}\n\n.mj-forms-panel--inherited .mj-forms-panel-icon {\n color: var(--mj-brand-primary);\n}\n\n.mj-forms-inherited-badge {\n display: inline-flex;\n align-items: center;\n gap: 4px;\n font-size: 11px;\n font-weight: 600;\n padding: 3px 10px;\n border-radius: 4px;\n background: color-mix(in srgb, var(--mj-brand-primary) 10%, var(--mj-bg-surface));\n color: var(--mj-brand-primary);\n border: 1px solid color-mix(in srgb, var(--mj-brand-primary) 25%, transparent);\n cursor: pointer;\n transition: all var(--mj-transition-base) ease;\n text-decoration: none;\n}\n\n.mj-forms-inherited-badge:hover {\n background: color-mix(in srgb, var(--mj-brand-primary) 18%, var(--mj-bg-surface));\n border-color: var(--mj-brand-primary);\n box-shadow: 0 2px 6px color-mix(in srgb, var(--mj-brand-primary) 20%, transparent);\n}\n\n.mj-forms-inherited-badge i {\n font-size: 10px;\n}\n\n/* ============================================\n RELATED ENTITY VARIANT\n ============================================ */\n.mj-forms-panel--related {\n border-left: 3px solid var(--mj-brand-primary);\n}\n\n.mj-forms-panel--related .mj-forms-panel-header {\n background: color-mix(in srgb, var(--mj-brand-primary) 8%, var(--mj-bg-surface-elevated));\n}\n\n.mj-forms-panel--related .mj-forms-panel-header:hover {\n background: color-mix(in srgb, var(--mj-brand-primary) 14%, var(--mj-bg-surface-elevated));\n border-bottom-color: var(--mj-brand-primary);\n}\n\n.mj-forms-panel--related .mj-forms-panel-icon {\n color: var(--mj-brand-primary);\n}\n\n/* Related entity content hugs the grid (toolbar + header + rows).\n A fixed 400px box left a hollow slab when AG Grid's viewport was 0,\n and a persisted 0/toolbar height clipped the rows after expand. */\n.mj-forms-panel--related .mj-forms-panel-content {\n height: auto;\n padding: 0;\n overflow: hidden;\n resize: vertical;\n min-height: 0;\n max-height: 800px;\n}\n\n/* AG Grid must fill the 400px / leftover box. Do not stretch every\n descendant \u2014 custom widgets (relationship list) put their section\n header as the first child, and height:100% on it parks the title\n mid-column and hides the list below the fold. */\n.mj-forms-panel--related .mj-forms-panel-content > mj-explorer-entity-data-grid,\n.mj-forms-panel--related .mj-forms-panel-content > .ng-star-inserted > mj-explorer-entity-data-grid {\n height: 100%;\n}\n\n/* Related entity panels need the body to not clip the resize handle */\n.mj-forms-panel--related .mj-forms-panel-body {\n overflow: visible;\n}\n\n/* ============================================\n BODY (collapsible)\n ============================================ */\n.mj-forms-panel-body {\n max-height: 2000px;\n overflow: hidden;\n transition: max-height 0.4s ease, padding 0.4s ease, opacity 0.3s ease;\n opacity: 1;\n}\n\n.mj-forms-panel-body--collapsed {\n max-height: 0 !important;\n padding: 0 !important;\n opacity: 0 !important;\n overflow: hidden !important;\n pointer-events: none;\n}\n\n.mj-forms-panel-body--collapsed .mj-forms-panel-content {\n padding-top: 0 !important;\n padding-bottom: 0 !important;\n max-height: 0 !important;\n overflow: hidden !important;\n}\n\n.mj-forms-panel-content {\n padding: 20px;\n overflow: auto;\n max-height: 600px;\n}\n\n/* ============================================\n SEARCH HIGHLIGHTING\n ============================================ */\n.mj-forms-panel-title ::ng-deep .mj-forms-search-highlight {\n /* Muted, theme-aware highlight (semantic token) \u2014 consistent with the field\n label + FK dropdown highlights. Replaces the old bright-yellow primitive. */\n background-color: var(--mj-search-highlight-bg);\n color: var(--mj-search-highlight-text);\n border-radius: 2px;\n padding: 0 1px;\n font-weight: 600;\n}\n"] }]
577
808
  }], null, { SectionKey: [{
578
809
  type: Input
810
+ }], Indicators: [{
811
+ type: Input
812
+ }], ValidationSources: [{
813
+ type: Input
579
814
  }], SectionName: [{
580
815
  type: Input
581
816
  }], Icon: [{
@@ -631,6 +866,12 @@ export class MjCollapsiblePanelComponent {
631
866
  }], HostIcon: [{
632
867
  type: HostBinding,
633
868
  args: ['attr.data-icon']
869
+ }], HostDirtyCount: [{
870
+ type: HostBinding,
871
+ args: ['attr.data-dirty-count']
872
+ }], HostErrorCount: [{
873
+ type: HostBinding,
874
+ args: ['attr.data-error-count']
634
875
  }], HostClass: [{
635
876
  type: HostBinding,
636
877
  args: ['class']
@@ -650,5 +891,5 @@ export class MjCollapsiblePanelComponent {
650
891
  type: HostListener,
651
892
  args: ['drop', ['$event']]
652
893
  }] }); })();
653
- (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(MjCollapsiblePanelComponent, { className: "MjCollapsiblePanelComponent", filePath: "src/lib/panel/collapsible-panel.component.ts", lineNumber: 56 }); })();
894
+ (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(MjCollapsiblePanelComponent, { className: "MjCollapsiblePanelComponent", filePath: "src/lib/panel/collapsible-panel.component.ts", lineNumber: 73 }); })();
654
895
  //# sourceMappingURL=collapsible-panel.component.js.map