@memberjunction/ng-base-forms 5.38.0 → 5.39.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. package/README.md +53 -0
  2. package/dist/lib/base-form-component.d.ts +18 -0
  3. package/dist/lib/base-form-component.d.ts.map +1 -1
  4. package/dist/lib/base-form-component.js +31 -2
  5. package/dist/lib/base-form-component.js.map +1 -1
  6. package/dist/lib/container/record-form-container.component.d.ts +14 -0
  7. package/dist/lib/container/record-form-container.component.d.ts.map +1 -1
  8. package/dist/lib/container/record-form-container.component.js +59 -33
  9. package/dist/lib/container/record-form-container.component.js.map +1 -1
  10. package/dist/lib/field/fk-search-utils.d.ts +31 -0
  11. package/dist/lib/field/fk-search-utils.d.ts.map +1 -0
  12. package/dist/lib/field/fk-search-utils.js +47 -0
  13. package/dist/lib/field/fk-search-utils.js.map +1 -0
  14. package/dist/lib/field/form-field.component.d.ts +401 -6
  15. package/dist/lib/field/form-field.component.d.ts.map +1 -1
  16. package/dist/lib/field/form-field.component.js +1646 -214
  17. package/dist/lib/field/form-field.component.js.map +1 -1
  18. package/dist/lib/field/linked-field-options.d.ts +76 -0
  19. package/dist/lib/field/linked-field-options.d.ts.map +1 -0
  20. package/dist/lib/field/linked-field-options.js +80 -0
  21. package/dist/lib/field/linked-field-options.js.map +1 -0
  22. package/dist/lib/host/entity-form-host.component.d.ts +159 -0
  23. package/dist/lib/host/entity-form-host.component.d.ts.map +1 -0
  24. package/dist/lib/host/entity-form-host.component.js +539 -0
  25. package/dist/lib/host/entity-form-host.component.js.map +1 -0
  26. package/dist/lib/isa-related-panel/isa-related-card.component.js +2 -2
  27. package/dist/lib/overlays/base-form-overlay.d.ts +114 -0
  28. package/dist/lib/overlays/base-form-overlay.d.ts.map +1 -0
  29. package/dist/lib/overlays/base-form-overlay.js +227 -0
  30. package/dist/lib/overlays/base-form-overlay.js.map +1 -0
  31. package/dist/lib/overlays/form-dialog.component.d.ts +35 -0
  32. package/dist/lib/overlays/form-dialog.component.d.ts.map +1 -0
  33. package/dist/lib/overlays/form-dialog.component.js +92 -0
  34. package/dist/lib/overlays/form-dialog.component.js.map +1 -0
  35. package/dist/lib/overlays/form-presenter.service.d.ts +37 -0
  36. package/dist/lib/overlays/form-presenter.service.d.ts.map +1 -0
  37. package/dist/lib/overlays/form-presenter.service.js +130 -0
  38. package/dist/lib/overlays/form-presenter.service.js.map +1 -0
  39. package/dist/lib/overlays/form-presenter.types.d.ts +65 -0
  40. package/dist/lib/overlays/form-presenter.types.d.ts.map +1 -0
  41. package/dist/lib/overlays/form-presenter.types.js +25 -0
  42. package/dist/lib/overlays/form-presenter.types.js.map +1 -0
  43. package/dist/lib/overlays/form-slide-in.component.d.ts +38 -0
  44. package/dist/lib/overlays/form-slide-in.component.d.ts.map +1 -0
  45. package/dist/lib/overlays/form-slide-in.component.js +106 -0
  46. package/dist/lib/overlays/form-slide-in.component.js.map +1 -0
  47. package/dist/lib/overlays/form-window.component.d.ts +32 -0
  48. package/dist/lib/overlays/form-window.component.d.ts.map +1 -0
  49. package/dist/lib/overlays/form-window.component.js +93 -0
  50. package/dist/lib/overlays/form-window.component.js.map +1 -0
  51. package/dist/lib/panel/collapsible-panel.component.d.ts +19 -2
  52. package/dist/lib/panel/collapsible-panel.component.d.ts.map +1 -1
  53. package/dist/lib/panel/collapsible-panel.component.js +70 -13
  54. package/dist/lib/panel/collapsible-panel.component.js.map +1 -1
  55. package/dist/lib/resolver/form-resolver.service.d.ts +139 -0
  56. package/dist/lib/resolver/form-resolver.service.d.ts.map +1 -0
  57. package/dist/lib/resolver/form-resolver.service.js +235 -0
  58. package/dist/lib/resolver/form-resolver.service.js.map +1 -0
  59. package/dist/lib/section-manager/section-manager.component.js +2 -2
  60. package/dist/lib/toolbar/form-toolbar.component.js +2 -2
  61. package/dist/lib/types/entity-form-config.d.ts +125 -0
  62. package/dist/lib/types/entity-form-config.d.ts.map +1 -0
  63. package/dist/lib/types/entity-form-config.js +75 -0
  64. package/dist/lib/types/entity-form-config.js.map +1 -0
  65. package/dist/lib/types/form-types.d.ts +24 -0
  66. package/dist/lib/types/form-types.d.ts.map +1 -1
  67. package/dist/lib/types/form-types.js.map +1 -1
  68. package/dist/lib/types/navigation-events.d.ts +46 -2
  69. package/dist/lib/types/navigation-events.d.ts.map +1 -1
  70. package/dist/lib/types/navigation-events.js.map +1 -1
  71. package/dist/module.d.ts +12 -8
  72. package/dist/module.d.ts.map +1 -1
  73. package/dist/module.js +26 -7
  74. package/dist/module.js.map +1 -1
  75. package/dist/public-api.d.ts +11 -0
  76. package/dist/public-api.d.ts.map +1 -1
  77. package/dist/public-api.js +14 -0
  78. package/dist/public-api.js.map +1 -1
  79. package/package.json +17 -13
@@ -0,0 +1 @@
1
+ {"version":3,"file":"form-resolver.service.d.ts","sourceRoot":"","sources":["../../../src/lib/resolver/form-resolver.service.ts"],"names":[],"mappings":"AAAA,OAAO,EAAc,IAAI,EAAE,MAAM,eAAe,CAAC;AACjD,OAAO,EAAE,iBAAiB,EAAE,QAAQ,EAAE,UAAU,EAAY,MAAM,sBAAsB,CAAC;AAGzF,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;;AAG3D;;;;;;GAMG;AACH,MAAM,WAAW,qBAAqB;IAClC,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,QAAQ,CAAC;IAClC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,QAAQ,GAAG,UAAU,GAAG,SAAS,CAAC;IAC1C,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC/B;AAED,kEAAkE;AAClE,MAAM,MAAM,cAAc,GACpB;IAAE,IAAI,EAAE,aAAa,CAAC;IAAC,QAAQ,EAAE,qBAAqB,CAAC;IAAC,QAAQ,EAAE,qBAAqB,EAAE,CAAA;CAAE,GAC3F;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,QAAQ,EAAE,IAAI,CAAC,iBAAiB,CAAC,CAAC;IAAC,QAAQ,EAAE,qBAAqB,EAAE,CAAA;CAAE,GACvF;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,qBAAqB,EAAE,CAAA;CAAE,CAAC;AAiB1D;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,qBACa,mBAAmB;IAEtB,oBAAoB,CACtB,MAAM,EAAE,UAAU,EAClB,IAAI,EAAE,QAAQ,EACd,QAAQ,EAAE,iBAAiB,GAC5B,OAAO,CAAC,cAAc,CAAC;IAc1B;;;;;OAKG;IACG,qBAAqB,CACvB,MAAM,EAAE,UAAU,EAClB,IAAI,EAAE,QAAQ,EACd,QAAQ,EAAE,iBAAiB,GAC5B,OAAO,CAAC,qBAAqB,EAAE,CAAC;IAInC;;;;;;;;;;;OAWG;IACH,gBAAuB,yBAAyB,yBAAyB;IAEzE;;;OAGG;IACH,OAAO,CAAC,UAAU;IAIlB;;;;;;OAMG;IACH,kBAAkB,CAAC,UAAU,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,IAAI;IAIhE;;;;;OAKG;IACH,kBAAkB,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI;IAO5C;;;;;OAKG;IACH,oBAAoB,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI;IAK9C;;;;;OAKG;IACH,kBAAkB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI;YAMvC,sBAAsB;IAwEpC;;;;;;;;;;;;;OAaG;IACH,OAAO,CAAC,UAAU;yCA7LT,mBAAmB;6CAAnB,mBAAmB;CA6M/B"}
@@ -0,0 +1,235 @@
1
+ import { Injectable } from '@angular/core';
2
+ import { LogError } from '@memberjunction/core';
3
+ import { InteractiveFormsEngine } from '@memberjunction/core-entities';
4
+ import { MJGlobal, UUIDsEqual } from '@memberjunction/global';
5
+ import { BaseFormComponent } from '../base-form-component';
6
+ import { UserInfoEngine } from '@memberjunction/core-entities';
7
+ import * as i0 from "@angular/core";
8
+ /**
9
+ * UserInfoEngine setting-key prefix for per-user, per-entity form-variant
10
+ * preferences. Persisted to `MJ: User Settings` so the choice follows the
11
+ * user across browsers and devices — localStorage is intentionally NOT used.
12
+ *
13
+ * Key format: `mj.formVariant.<entityname-lowercased>`.
14
+ *
15
+ * Value is one of:
16
+ * - an `EntityFormOverride.ID` (UUID) → use that specific override
17
+ * - {@link FormResolverService.EXPLICIT_DEFAULT_SENTINEL} → user picked
18
+ * the CodeGen Angular fallback explicitly; resolver skips all overrides
19
+ * - (key absent) → no preference, apply auto-pick rules
20
+ */
21
+ const VARIANT_SETTING_PREFIX = 'mj.formVariant.';
22
+ /**
23
+ * Picks the form to render for an entity record and exposes the full list of
24
+ * applicable variants so the toolbar's variant switcher can offer alternates.
25
+ *
26
+ * Tier order for the **default** pick:
27
+ * 1. `EntityFormOverride` row matching the caller's User/Role/Global scope,
28
+ * Status='Active', ordered by scope tier (User > Role > Global), then
29
+ * Priority DESC, then `__mj_CreatedAt DESC`. First row wins.
30
+ * 2. Existing `ClassFactory.GetRegistration(BaseFormComponent, entityName)` —
31
+ * the @RegisterClass + CodeGen-generated path used today.
32
+ * 3. None — caller surfaces the "no form registered" error.
33
+ *
34
+ * **Session selection.** If the user previously chose a non-default variant
35
+ * via the variant switcher and that choice is still applicable + Active, that
36
+ * choice wins over the default. Choice is keyed by entity name in
37
+ * `localStorage`. Honoring this is part of the variant-switcher contract;
38
+ * without it, switching variants would only last for the lifetime of the
39
+ * record-form view.
40
+ *
41
+ * **Performance.** Backed by `InteractiveFormsEngine` (in MJCoreEntities)
42
+ * — overrides are cached in memory with BaseEntity event-driven
43
+ * invalidation, so resolution is an O(N) JS filter against a small
44
+ * set instead of a per-LoadForm RunView round-trip. Cold latency
45
+ * dropped from ~50ms to <1ms after the first load. The engine is
46
+ * lazy-Config'd here on first resolution; users who never open a
47
+ * record pay nothing.
48
+ */
49
+ export class FormResolverService {
50
+ async ResolveFormForEntity(entity, user, provider) {
51
+ const variants = await this.listApplicableVariants(entity, user, provider);
52
+ const active = this.pickActive(entity, variants);
53
+ if (active) {
54
+ return { kind: 'interactive', override: active, variants };
55
+ }
56
+ const reg = MJGlobal.Instance.ClassFactory.GetRegistration(BaseFormComponent, entity.Name);
57
+ return reg
58
+ ? { kind: 'class', subClass: reg.SubClass, variants }
59
+ : { kind: 'none', variants };
60
+ }
61
+ /**
62
+ * Public list-API for the variant switcher UI. Returns all variants
63
+ * applicable to (entity, user). Includes Active, Pending, and Inactive
64
+ * rows so the picker can offer "switch back to v1.0.0" alongside the
65
+ * current active variant.
66
+ */
67
+ async ListVariantsForEntity(entity, user, provider) {
68
+ return this.listApplicableVariants(entity, user, provider);
69
+ }
70
+ /**
71
+ * Sentinel stored in localStorage when the user **explicitly picks the
72
+ * "Default form"** option from the variant picker. Distinct from a
73
+ * missing localStorage key (= "no preference, use auto-pick rules")
74
+ * and from an override UUID. Without this sentinel, picking "Default"
75
+ * cleared the preference and `pickActive` re-applied the auto-pick —
76
+ * which always selects the first Active override, making the
77
+ * CodeGen/Angular fallback unreachable from the UI.
78
+ *
79
+ * Format: a leading `__` makes it visually distinct from a UUID and
80
+ * impossible to collide with one (UUIDs don't contain underscores).
81
+ */
82
+ static EXPLICIT_DEFAULT_SENTINEL = '__codegen-default__';
83
+ /**
84
+ * Build the per-entity setting key. Lowercased so case variants of
85
+ * the entity name collapse onto a single record in `MJ: User Settings`.
86
+ */
87
+ settingKey(entityName) {
88
+ return VARIANT_SETTING_PREFIX + entityName.toLowerCase();
89
+ }
90
+ /**
91
+ * Persist a specific override choice. Pass the override UUID. Writes
92
+ * via `UserInfoEngine.SetSettingDebounced` so rapid successive picks
93
+ * don't hammer the DB. Use {@link SetExplicitDefault} to record "user
94
+ * wants the CodeGen Angular fallback" and {@link ClearSelectedVariant}
95
+ * to wipe the preference entirely (revert to auto-pick).
96
+ */
97
+ SetSelectedVariant(entityName, overrideID) {
98
+ UserInfoEngine.Instance.SetSettingDebounced(this.settingKey(entityName), overrideID);
99
+ }
100
+ /**
101
+ * Record that the user explicitly picked the "Default form" row in
102
+ * the picker. `pickActive` reads this sentinel and returns null even
103
+ * when Active overrides exist for the entity, so the form-loading
104
+ * path falls back to CodeGen's `@RegisterClass` lookup.
105
+ */
106
+ SetExplicitDefault(entityName) {
107
+ UserInfoEngine.Instance.SetSettingDebounced(this.settingKey(entityName), FormResolverService.EXPLICIT_DEFAULT_SENTINEL);
108
+ }
109
+ /**
110
+ * Wipe the user's stored preference for this entity. Next load
111
+ * applies the auto-pick rules (first Active override in tier order).
112
+ * Called internally when a saved override ID is no longer valid.
113
+ * Fire-and-forget — the resolver doesn't need to await the delete.
114
+ */
115
+ ClearSelectedVariant(entityName) {
116
+ void UserInfoEngine.Instance.DeleteSetting(this.settingKey(entityName))
117
+ .catch(err => LogError(`FormResolverService.ClearSelectedVariant: ${err instanceof Error ? err.message : String(err)}`));
118
+ }
119
+ /**
120
+ * Read the user's previously-saved variant choice for the entity.
121
+ * Synchronous because `UserInfoEngine` keeps the user-settings table
122
+ * in memory after bootstrap. Returns the stored UUID, the explicit-
123
+ * default sentinel, or null when no preference exists.
124
+ */
125
+ GetSelectedVariant(entityName) {
126
+ return UserInfoEngine.Instance.GetSetting(this.settingKey(entityName)) ?? null;
127
+ }
128
+ // ── internals ────────────────────────────────────────────────────────
129
+ async listApplicableVariants(entity, user, provider) {
130
+ // Lazy-load the form-metadata engine on first call. No-op when
131
+ // already loaded. BaseEngine's event subscription keeps the
132
+ // cache fresh through save / delete / remote-invalidate — we
133
+ // don't need `BypassCache: true` (the previous RunView path's
134
+ // escape hatch) because the engine's invalidation IS that
135
+ // freshness guarantee. See `InteractiveFormsEngine` doc-block.
136
+ try {
137
+ await InteractiveFormsEngine.Instance.Config(false, user, provider);
138
+ }
139
+ catch (err) {
140
+ LogError(`FormResolverService: InteractiveFormsEngine.Config failed: ${err instanceof Error ? err.message : String(err)}`);
141
+ return [];
142
+ }
143
+ const userRoleIds = new Set((user.UserRoles ?? []).map(r => r.RoleID).filter((id) => !!id));
144
+ // Filter cached overrides for this (entity, user, roles) tuple.
145
+ // Includes Active + Pending + Inactive (the variant picker shows
146
+ // all three; pickActive() filters to Active separately). Replaces
147
+ // a per-LoadForm RunView with an in-memory predicate — sub-ms.
148
+ const applicable = InteractiveFormsEngine.Instance.Overrides.filter(o => {
149
+ if (!o.EntityID || !UUIDsEqual(o.EntityID, entity.ID))
150
+ return false;
151
+ if (o.Scope === 'User')
152
+ return !!o.UserID && o.UserID.toLowerCase() === user.ID.toLowerCase();
153
+ if (o.Scope === 'Role')
154
+ return !!o.RoleID && userRoleIds.has(o.RoleID);
155
+ if (o.Scope === 'Global')
156
+ return true;
157
+ return false;
158
+ });
159
+ // Sort: User > Role > Global, then Priority DESC (higher beats),
160
+ // then newest first. Matches the prior SQL ORDER BY semantics so
161
+ // pickActive() / the variant switcher see the same order.
162
+ const scopeRank = (s) => {
163
+ if (s === 'User')
164
+ return 1;
165
+ if (s === 'Role')
166
+ return 2;
167
+ if (s === 'Global')
168
+ return 3;
169
+ return 4;
170
+ };
171
+ applicable.sort((a, b) => {
172
+ const sa = scopeRank(a.Scope);
173
+ const sb = scopeRank(b.Scope);
174
+ if (sa !== sb)
175
+ return sa - sb;
176
+ const pa = a.Priority ?? 0;
177
+ const pb = b.Priority ?? 0;
178
+ if (pa !== pb)
179
+ return pb - pa; // Priority DESC
180
+ const ca = a.__mj_CreatedAt instanceof Date ? a.__mj_CreatedAt.getTime() : 0;
181
+ const cb = b.__mj_CreatedAt instanceof Date ? b.__mj_CreatedAt.getTime() : 0;
182
+ return cb - ca; // newest first
183
+ });
184
+ // Project to the slim row shape the resolver returns. Keeps the
185
+ // contract identical to the old RunView return so callers don't
186
+ // change shape.
187
+ return applicable.map(o => ({
188
+ ID: o.ID,
189
+ EntityID: o.EntityID,
190
+ ComponentID: o.ComponentID,
191
+ Scope: o.Scope,
192
+ UserID: o.UserID,
193
+ RoleID: o.RoleID,
194
+ Priority: o.Priority,
195
+ Status: o.Status,
196
+ Name: o.Name,
197
+ Description: o.Description,
198
+ }));
199
+ }
200
+ /**
201
+ * Pick the override that should actually render given the variant list
202
+ * and the user's stored selection (if any).
203
+ *
204
+ * Selection rules:
205
+ * - If the user explicitly picked the "Default form" row in the
206
+ * variant picker (sentinel in localStorage) → return null so the
207
+ * form-loading path falls back to CodeGen's `@RegisterClass` lookup.
208
+ * This is what makes the Angular fallback reachable from the UI.
209
+ * - Else if the user has a saved variant ID AND that variant is in
210
+ * the applicable list AND it's Active → use it.
211
+ * - Else → first Active row in tier+priority order (auto-pick).
212
+ * - Else → null (fall back to CodeGen/@RegisterClass path).
213
+ */
214
+ pickActive(entity, variants) {
215
+ const selectedID = this.GetSelectedVariant(entity.Name);
216
+ if (selectedID === FormResolverService.EXPLICIT_DEFAULT_SENTINEL) {
217
+ return null;
218
+ }
219
+ if (selectedID) {
220
+ const sel = variants.find(v => v.Status === 'Active' && UUIDsEqual(v.ID, selectedID));
221
+ if (sel)
222
+ return sel;
223
+ // Selection no longer valid — wipe it so future loads auto-pick.
224
+ this.ClearSelectedVariant(entity.Name);
225
+ }
226
+ return variants.find(v => v.Status === 'Active') ?? null;
227
+ }
228
+ static ɵfac = function FormResolverService_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || FormResolverService)(); };
229
+ static ɵprov = /*@__PURE__*/ i0.ɵɵdefineInjectable({ token: FormResolverService, factory: FormResolverService.ɵfac, providedIn: 'root' });
230
+ }
231
+ (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(FormResolverService, [{
232
+ type: Injectable,
233
+ args: [{ providedIn: 'root' }]
234
+ }], null, null); })();
235
+ //# sourceMappingURL=form-resolver.service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"form-resolver.service.js","sourceRoot":"","sources":["../../../src/lib/resolver/form-resolver.service.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAQ,MAAM,eAAe,CAAC;AACjD,OAAO,EAA2C,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AACzF,OAAO,EAAE,sBAAsB,EAAE,MAAM,+BAA+B,CAAC;AACvE,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AAC9D,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAC3D,OAAO,EAAE,cAAc,EAAE,MAAM,+BAA+B,CAAC;;AA4B/D;;;;;;;;;;;;GAYG;AACH,MAAM,sBAAsB,GAAG,iBAAiB,CAAC;AAEjD;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,MAAM,OAAO,mBAAmB;IAE5B,KAAK,CAAC,oBAAoB,CACtB,MAAkB,EAClB,IAAc,EACd,QAA2B;QAE3B,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,sBAAsB,CAAC,MAAM,EAAE,IAAI,EAAE,QAAQ,CAAC,CAAC;QAC3E,MAAM,MAAM,GAAG,IAAI,CAAC,UAAU,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;QAEjD,IAAI,MAAM,EAAE,CAAC;YACT,OAAO,EAAE,IAAI,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC;QAC/D,CAAC;QAED,MAAM,GAAG,GAAG,QAAQ,CAAC,QAAQ,CAAC,YAAY,CAAC,eAAe,CAAC,iBAAiB,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC;QAC3F,OAAO,GAAG;YACN,CAAC,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,QAAQ,EAAE,GAAG,CAAC,QAAmC,EAAE,QAAQ,EAAE;YAChF,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC;IACrC,CAAC;IAED;;;;;OAKG;IACH,KAAK,CAAC,qBAAqB,CACvB,MAAkB,EAClB,IAAc,EACd,QAA2B;QAE3B,OAAO,IAAI,CAAC,sBAAsB,CAAC,MAAM,EAAE,IAAI,EAAE,QAAQ,CAAC,CAAC;IAC/D,CAAC;IAED;;;;;;;;;;;OAWG;IACI,MAAM,CAAU,yBAAyB,GAAG,qBAAqB,CAAC;IAEzE;;;OAGG;IACK,UAAU,CAAC,UAAkB;QACjC,OAAO,sBAAsB,GAAG,UAAU,CAAC,WAAW,EAAE,CAAC;IAC7D,CAAC;IAED;;;;;;OAMG;IACH,kBAAkB,CAAC,UAAkB,EAAE,UAAkB;QACrD,cAAc,CAAC,QAAQ,CAAC,mBAAmB,CAAC,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,UAAU,CAAC,CAAC;IACzF,CAAC;IAED;;;;;OAKG;IACH,kBAAkB,CAAC,UAAkB;QACjC,cAAc,CAAC,QAAQ,CAAC,mBAAmB,CACvC,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC,EAC3B,mBAAmB,CAAC,yBAAyB,CAChD,CAAC;IACN,CAAC;IAED;;;;;OAKG;IACH,oBAAoB,CAAC,UAAkB;QACnC,KAAK,cAAc,CAAC,QAAQ,CAAC,aAAa,CAAC,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC,CAAC;aAClE,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC,QAAQ,CAAC,6CAA6C,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC;IACjI,CAAC;IAED;;;;;OAKG;IACH,kBAAkB,CAAC,UAAkB;QACjC,OAAO,cAAc,CAAC,QAAQ,CAAC,UAAU,CAAC,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC,CAAC,IAAI,IAAI,CAAC;IACnF,CAAC;IAED,wEAAwE;IAEhE,KAAK,CAAC,sBAAsB,CAChC,MAAkB,EAClB,IAAc,EACd,QAA2B;QAE3B,+DAA+D;QAC/D,4DAA4D;QAC5D,6DAA6D;QAC7D,8DAA8D;QAC9D,0DAA0D;QAC1D,+DAA+D;QAC/D,IAAI,CAAC;YACD,MAAM,sBAAsB,CAAC,QAAQ,CAAC,MAAM,CAAC,KAAK,EAAE,IAAI,EAAE,QAAQ,CAAC,CAAC;QACxE,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACX,QAAQ,CAAC,8DAA8D,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;YAC3H,OAAO,EAAE,CAAC;QACd,CAAC;QAED,MAAM,WAAW,GAAG,IAAI,GAAG,CACvB,CAAC,IAAI,CAAC,SAAS,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,CAAC,EAAE,EAAgB,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAC/E,CAAC;QAEF,gEAAgE;QAChE,iEAAiE;QACjE,kEAAkE;QAClE,+DAA+D;QAC/D,MAAM,UAAU,GAAG,sBAAsB,CAAC,QAAQ,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE;YACpE,IAAI,CAAC,CAAC,CAAC,QAAQ,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,QAAQ,EAAE,MAAM,CAAC,EAAE,CAAC;gBAAE,OAAO,KAAK,CAAC;YACpE,IAAI,CAAC,CAAC,KAAK,KAAK,MAAM;gBAAI,OAAO,CAAC,CAAC,CAAC,CAAC,MAAM,IAAI,CAAC,CAAC,MAAM,CAAC,WAAW,EAAE,KAAK,IAAI,CAAC,EAAE,CAAC,WAAW,EAAE,CAAC;YAChG,IAAI,CAAC,CAAC,KAAK,KAAK,MAAM;gBAAI,OAAO,CAAC,CAAC,CAAC,CAAC,MAAM,IAAI,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;YACzE,IAAI,CAAC,CAAC,KAAK,KAAK,QAAQ;gBAAE,OAAO,IAAI,CAAC;YACtC,OAAO,KAAK,CAAC;QACjB,CAAC,CAAC,CAAC;QAEH,iEAAiE;QACjE,iEAAiE;QACjE,0DAA0D;QAC1D,MAAM,SAAS,GAAG,CAAC,CAA4B,EAAU,EAAE;YACvD,IAAI,CAAC,KAAK,MAAM;gBAAI,OAAO,CAAC,CAAC;YAC7B,IAAI,CAAC,KAAK,MAAM;gBAAI,OAAO,CAAC,CAAC;YAC7B,IAAI,CAAC,KAAK,QAAQ;gBAAE,OAAO,CAAC,CAAC;YAC7B,OAAO,CAAC,CAAC;QACb,CAAC,CAAC;QACF,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE;YACrB,MAAM,EAAE,GAAG,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;YAC9B,MAAM,EAAE,GAAG,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;YAC9B,IAAI,EAAE,KAAK,EAAE;gBAAE,OAAO,EAAE,GAAG,EAAE,CAAC;YAC9B,MAAM,EAAE,GAAG,CAAC,CAAC,QAAQ,IAAI,CAAC,CAAC;YAC3B,MAAM,EAAE,GAAG,CAAC,CAAC,QAAQ,IAAI,CAAC,CAAC;YAC3B,IAAI,EAAE,KAAK,EAAE;gBAAE,OAAO,EAAE,GAAG,EAAE,CAAC,CAAE,gBAAgB;YAChD,MAAM,EAAE,GAAG,CAAC,CAAC,cAAc,YAAY,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,cAAc,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;YAC7E,MAAM,EAAE,GAAG,CAAC,CAAC,cAAc,YAAY,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,cAAc,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;YAC7E,OAAO,EAAE,GAAG,EAAE,CAAC,CAAE,eAAe;QACpC,CAAC,CAAC,CAAC;QAEH,gEAAgE;QAChE,gEAAgE;QAChE,gBAAgB;QAChB,OAAO,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;YACxB,EAAE,EAAE,CAAC,CAAC,EAAE;YACR,QAAQ,EAAE,CAAC,CAAC,QAAQ;YACpB,WAAW,EAAE,CAAC,CAAC,WAAW;YAC1B,KAAK,EAAE,CAAC,CAAC,KAAmC;YAC5C,MAAM,EAAE,CAAC,CAAC,MAAM;YAChB,MAAM,EAAE,CAAC,CAAC,MAAM;YAChB,QAAQ,EAAE,CAAC,CAAC,QAAQ;YACpB,MAAM,EAAE,CAAC,CAAC,MAA2C;YACrD,IAAI,EAAE,CAAC,CAAC,IAAI;YACZ,WAAW,EAAE,CAAC,CAAC,WAAW;SAC7B,CAAC,CAAC,CAAC;IACR,CAAC;IAED;;;;;;;;;;;;;OAaG;IACK,UAAU,CACd,MAAkB,EAClB,QAAiC;QAEjC,MAAM,UAAU,GAAG,IAAI,CAAC,kBAAkB,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QACxD,IAAI,UAAU,KAAK,mBAAmB,CAAC,yBAAyB,EAAE,CAAC;YAC/D,OAAO,IAAI,CAAC;QAChB,CAAC;QACD,IAAI,UAAU,EAAE,CAAC;YACb,MAAM,GAAG,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,QAAQ,IAAI,UAAU,CAAC,CAAC,CAAC,EAAE,EAAE,UAAU,CAAC,CAAC,CAAC;YACtF,IAAI,GAAG;gBAAE,OAAO,GAAG,CAAC;YACpB,iEAAiE;YACjE,IAAI,CAAC,oBAAoB,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QAC3C,CAAC;QACD,OAAO,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,QAAQ,CAAC,IAAI,IAAI,CAAC;IAC7D,CAAC;6GA5MQ,mBAAmB;gEAAnB,mBAAmB,WAAnB,mBAAmB,mBADN,MAAM;;iFACnB,mBAAmB;cAD/B,UAAU;eAAC,EAAE,UAAU,EAAE,MAAM,EAAE","sourcesContent":["import { Injectable, Type } from '@angular/core';\nimport { IMetadataProvider, UserInfo, EntityInfo, LogError } from '@memberjunction/core';\nimport { InteractiveFormsEngine } from '@memberjunction/core-entities';\nimport { MJGlobal, UUIDsEqual } from '@memberjunction/global';\nimport { BaseFormComponent } from '../base-form-component';\nimport { UserInfoEngine } from '@memberjunction/core-entities';\n\n/**\n * Slim row shape for an `EntityFormOverride` lookup. Resolution doesn't need\n * the full BaseEntity object — `simple` ResultType is faster and avoids a\n * compile-time dependency on the generated entity class. The row is only ever\n * read by the resolver; mutation goes through the generated entity in other\n * code paths (Studio, AI authoring).\n */\nexport interface EntityFormOverrideRow {\n ID: string;\n EntityID: string;\n ComponentID: string;\n Scope: 'User' | 'Role' | 'Global';\n UserID: string | null;\n RoleID: string | null;\n Priority: number;\n Status: 'Active' | 'Inactive' | 'Pending';\n Name?: string;\n Description?: string | null;\n}\n\n/** Resolved form kind for a given (entity, user, roles) tuple. */\nexport type FormResolution =\n | { kind: 'interactive'; override: EntityFormOverrideRow; variants: EntityFormOverrideRow[] }\n | { kind: 'class'; subClass: Type<BaseFormComponent>; variants: EntityFormOverrideRow[] }\n | { kind: 'none'; variants: EntityFormOverrideRow[] };\n\n/**\n * UserInfoEngine setting-key prefix for per-user, per-entity form-variant\n * preferences. Persisted to `MJ: User Settings` so the choice follows the\n * user across browsers and devices — localStorage is intentionally NOT used.\n *\n * Key format: `mj.formVariant.<entityname-lowercased>`.\n *\n * Value is one of:\n * - an `EntityFormOverride.ID` (UUID) → use that specific override\n * - {@link FormResolverService.EXPLICIT_DEFAULT_SENTINEL} → user picked\n * the CodeGen Angular fallback explicitly; resolver skips all overrides\n * - (key absent) → no preference, apply auto-pick rules\n */\nconst VARIANT_SETTING_PREFIX = 'mj.formVariant.';\n\n/**\n * Picks the form to render for an entity record and exposes the full list of\n * applicable variants so the toolbar's variant switcher can offer alternates.\n *\n * Tier order for the **default** pick:\n * 1. `EntityFormOverride` row matching the caller's User/Role/Global scope,\n * Status='Active', ordered by scope tier (User > Role > Global), then\n * Priority DESC, then `__mj_CreatedAt DESC`. First row wins.\n * 2. Existing `ClassFactory.GetRegistration(BaseFormComponent, entityName)` —\n * the @RegisterClass + CodeGen-generated path used today.\n * 3. None — caller surfaces the \"no form registered\" error.\n *\n * **Session selection.** If the user previously chose a non-default variant\n * via the variant switcher and that choice is still applicable + Active, that\n * choice wins over the default. Choice is keyed by entity name in\n * `localStorage`. Honoring this is part of the variant-switcher contract;\n * without it, switching variants would only last for the lifetime of the\n * record-form view.\n *\n * **Performance.** Backed by `InteractiveFormsEngine` (in MJCoreEntities)\n * — overrides are cached in memory with BaseEntity event-driven\n * invalidation, so resolution is an O(N) JS filter against a small\n * set instead of a per-LoadForm RunView round-trip. Cold latency\n * dropped from ~50ms to <1ms after the first load. The engine is\n * lazy-Config'd here on first resolution; users who never open a\n * record pay nothing.\n */\n@Injectable({ providedIn: 'root' })\nexport class FormResolverService {\n\n async ResolveFormForEntity(\n entity: EntityInfo,\n user: UserInfo,\n provider: IMetadataProvider,\n ): Promise<FormResolution> {\n const variants = await this.listApplicableVariants(entity, user, provider);\n const active = this.pickActive(entity, variants);\n\n if (active) {\n return { kind: 'interactive', override: active, variants };\n }\n\n const reg = MJGlobal.Instance.ClassFactory.GetRegistration(BaseFormComponent, entity.Name);\n return reg\n ? { kind: 'class', subClass: reg.SubClass as Type<BaseFormComponent>, variants }\n : { kind: 'none', variants };\n }\n\n /**\n * Public list-API for the variant switcher UI. Returns all variants\n * applicable to (entity, user). Includes Active, Pending, and Inactive\n * rows so the picker can offer \"switch back to v1.0.0\" alongside the\n * current active variant.\n */\n async ListVariantsForEntity(\n entity: EntityInfo,\n user: UserInfo,\n provider: IMetadataProvider,\n ): Promise<EntityFormOverrideRow[]> {\n return this.listApplicableVariants(entity, user, provider);\n }\n\n /**\n * Sentinel stored in localStorage when the user **explicitly picks the\n * \"Default form\"** option from the variant picker. Distinct from a\n * missing localStorage key (= \"no preference, use auto-pick rules\")\n * and from an override UUID. Without this sentinel, picking \"Default\"\n * cleared the preference and `pickActive` re-applied the auto-pick —\n * which always selects the first Active override, making the\n * CodeGen/Angular fallback unreachable from the UI.\n *\n * Format: a leading `__` makes it visually distinct from a UUID and\n * impossible to collide with one (UUIDs don't contain underscores).\n */\n public static readonly EXPLICIT_DEFAULT_SENTINEL = '__codegen-default__';\n\n /**\n * Build the per-entity setting key. Lowercased so case variants of\n * the entity name collapse onto a single record in `MJ: User Settings`.\n */\n private settingKey(entityName: string): string {\n return VARIANT_SETTING_PREFIX + entityName.toLowerCase();\n }\n\n /**\n * Persist a specific override choice. Pass the override UUID. Writes\n * via `UserInfoEngine.SetSettingDebounced` so rapid successive picks\n * don't hammer the DB. Use {@link SetExplicitDefault} to record \"user\n * wants the CodeGen Angular fallback\" and {@link ClearSelectedVariant}\n * to wipe the preference entirely (revert to auto-pick).\n */\n SetSelectedVariant(entityName: string, overrideID: string): void {\n UserInfoEngine.Instance.SetSettingDebounced(this.settingKey(entityName), overrideID);\n }\n\n /**\n * Record that the user explicitly picked the \"Default form\" row in\n * the picker. `pickActive` reads this sentinel and returns null even\n * when Active overrides exist for the entity, so the form-loading\n * path falls back to CodeGen's `@RegisterClass` lookup.\n */\n SetExplicitDefault(entityName: string): void {\n UserInfoEngine.Instance.SetSettingDebounced(\n this.settingKey(entityName),\n FormResolverService.EXPLICIT_DEFAULT_SENTINEL,\n );\n }\n\n /**\n * Wipe the user's stored preference for this entity. Next load\n * applies the auto-pick rules (first Active override in tier order).\n * Called internally when a saved override ID is no longer valid.\n * Fire-and-forget — the resolver doesn't need to await the delete.\n */\n ClearSelectedVariant(entityName: string): void {\n void UserInfoEngine.Instance.DeleteSetting(this.settingKey(entityName))\n .catch(err => LogError(`FormResolverService.ClearSelectedVariant: ${err instanceof Error ? err.message : String(err)}`));\n }\n\n /**\n * Read the user's previously-saved variant choice for the entity.\n * Synchronous because `UserInfoEngine` keeps the user-settings table\n * in memory after bootstrap. Returns the stored UUID, the explicit-\n * default sentinel, or null when no preference exists.\n */\n GetSelectedVariant(entityName: string): string | null {\n return UserInfoEngine.Instance.GetSetting(this.settingKey(entityName)) ?? null;\n }\n\n // ── internals ────────────────────────────────────────────────────────\n\n private async listApplicableVariants(\n entity: EntityInfo,\n user: UserInfo,\n provider: IMetadataProvider,\n ): Promise<EntityFormOverrideRow[]> {\n // Lazy-load the form-metadata engine on first call. No-op when\n // already loaded. BaseEngine's event subscription keeps the\n // cache fresh through save / delete / remote-invalidate — we\n // don't need `BypassCache: true` (the previous RunView path's\n // escape hatch) because the engine's invalidation IS that\n // freshness guarantee. See `InteractiveFormsEngine` doc-block.\n try {\n await InteractiveFormsEngine.Instance.Config(false, user, provider);\n } catch (err) {\n LogError(`FormResolverService: InteractiveFormsEngine.Config failed: ${err instanceof Error ? err.message : String(err)}`);\n return [];\n }\n\n const userRoleIds = new Set(\n (user.UserRoles ?? []).map(r => r.RoleID).filter((id): id is string => !!id),\n );\n\n // Filter cached overrides for this (entity, user, roles) tuple.\n // Includes Active + Pending + Inactive (the variant picker shows\n // all three; pickActive() filters to Active separately). Replaces\n // a per-LoadForm RunView with an in-memory predicate — sub-ms.\n const applicable = InteractiveFormsEngine.Instance.Overrides.filter(o => {\n if (!o.EntityID || !UUIDsEqual(o.EntityID, entity.ID)) return false;\n if (o.Scope === 'User') return !!o.UserID && o.UserID.toLowerCase() === user.ID.toLowerCase();\n if (o.Scope === 'Role') return !!o.RoleID && userRoleIds.has(o.RoleID);\n if (o.Scope === 'Global') return true;\n return false;\n });\n\n // Sort: User > Role > Global, then Priority DESC (higher beats),\n // then newest first. Matches the prior SQL ORDER BY semantics so\n // pickActive() / the variant switcher see the same order.\n const scopeRank = (s: string | null | undefined): number => {\n if (s === 'User') return 1;\n if (s === 'Role') return 2;\n if (s === 'Global') return 3;\n return 4;\n };\n applicable.sort((a, b) => {\n const sa = scopeRank(a.Scope);\n const sb = scopeRank(b.Scope);\n if (sa !== sb) return sa - sb;\n const pa = a.Priority ?? 0;\n const pb = b.Priority ?? 0;\n if (pa !== pb) return pb - pa; // Priority DESC\n const ca = a.__mj_CreatedAt instanceof Date ? a.__mj_CreatedAt.getTime() : 0;\n const cb = b.__mj_CreatedAt instanceof Date ? b.__mj_CreatedAt.getTime() : 0;\n return cb - ca; // newest first\n });\n\n // Project to the slim row shape the resolver returns. Keeps the\n // contract identical to the old RunView return so callers don't\n // change shape.\n return applicable.map(o => ({\n ID: o.ID,\n EntityID: o.EntityID,\n ComponentID: o.ComponentID,\n Scope: o.Scope as 'User' | 'Role' | 'Global',\n UserID: o.UserID,\n RoleID: o.RoleID,\n Priority: o.Priority,\n Status: o.Status as 'Active' | 'Inactive' | 'Pending',\n Name: o.Name,\n Description: o.Description,\n }));\n }\n\n /**\n * Pick the override that should actually render given the variant list\n * and the user's stored selection (if any).\n *\n * Selection rules:\n * - If the user explicitly picked the \"Default form\" row in the\n * variant picker (sentinel in localStorage) → return null so the\n * form-loading path falls back to CodeGen's `@RegisterClass` lookup.\n * This is what makes the Angular fallback reachable from the UI.\n * - Else if the user has a saved variant ID AND that variant is in\n * the applicable list AND it's Active → use it.\n * - Else → first Active row in tier+priority order (auto-pick).\n * - Else → null (fall back to CodeGen/@RegisterClass path).\n */\n private pickActive(\n entity: EntityInfo,\n variants: EntityFormOverrideRow[],\n ): EntityFormOverrideRow | null {\n const selectedID = this.GetSelectedVariant(entity.Name);\n if (selectedID === FormResolverService.EXPLICIT_DEFAULT_SENTINEL) {\n return null;\n }\n if (selectedID) {\n const sel = variants.find(v => v.Status === 'Active' && UUIDsEqual(v.ID, selectedID));\n if (sel) return sel;\n // Selection no longer valid — wipe it so future loads auto-pick.\n this.ClearSelectedVariant(entity.Name);\n }\n return variants.find(v => v.Status === 'Active') ?? null;\n }\n}\n"]}
@@ -186,11 +186,11 @@ export class MjSectionManagerComponent {
186
186
  i0.ɵɵconditionalCreate(0, MjSectionManagerComponent_Conditional_0_Template, 15, 0, "div", 0);
187
187
  } if (rf & 2) {
188
188
  i0.ɵɵconditional(ctx.Visible ? 0 : -1);
189
- } }, styles: ["\n\n\n\n[_nghost-%COMP%] {\n display: block;\n}\n\n\n\n\n\n.mj-sm-overlay[_ngcontent-%COMP%] {\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: var(--mj-bg-overlay);\n z-index: 1000;\n animation: _ngcontent-%COMP%_mj-sm-fade-in 150ms ease;\n}\n\n\n\n\n\n.mj-sm-drawer[_ngcontent-%COMP%] {\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n width: 340px;\n max-width: 90vw;\n background: var(--mj-bg-surface);\n box-shadow: var(--mj-shadow-lg);\n display: flex;\n flex-direction: column;\n animation: _ngcontent-%COMP%_mj-sm-slide-in 200ms ease;\n}\n\n\n\n\n\n.mj-sm-header[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 16px 20px;\n background: var(--mj-bg-surface-elevated);\n border-bottom: 1px solid var(--mj-border-default);\n flex-shrink: 0;\n}\n\n.mj-sm-title[_ngcontent-%COMP%] {\n margin: 0;\n font-size: 15px;\n font-weight: var(--mj-font-semibold);\n color: var(--mj-text-primary);\n display: flex;\n align-items: center;\n gap: 8px;\n}\n\n.mj-sm-title[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n color: var(--mj-brand-primary);\n font-size: var(--mj-text-sm);\n}\n\n.mj-sm-close[_ngcontent-%COMP%] {\n background: none;\n border: none;\n color: var(--mj-text-muted);\n cursor: pointer;\n padding: 6px;\n font-size: var(--mj-text-base);\n border-radius: var(--mj-radius-sm);\n transition: all var(--mj-transition-base);\n line-height: 1;\n}\n\n.mj-sm-close[_ngcontent-%COMP%]:hover {\n color: var(--mj-text-primary);\n background: color-mix(in srgb, var(--mj-text-primary) 5%, transparent);\n}\n\n\n\n\n\n.mj-sm-list[_ngcontent-%COMP%] {\n flex: 1;\n overflow-y: auto;\n padding: 8px 0;\n}\n\n.mj-sm-row[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 10px 20px;\n border-bottom: 1px solid var(--mj-bg-surface-hover);\n transition: background var(--mj-transition-base);\n}\n\n.mj-sm-row[_ngcontent-%COMP%]:hover {\n background: var(--mj-bg-surface-card);\n}\n\n.mj-sm-row-info[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 10px;\n min-width: 0;\n flex: 1;\n}\n\n.mj-sm-row-icon[_ngcontent-%COMP%] {\n font-size: 13px;\n color: var(--mj-text-secondary);\n width: 18px;\n text-align: center;\n flex-shrink: 0;\n}\n\n.mj-sm-row-name[_ngcontent-%COMP%] {\n font-size: 13px;\n font-weight: var(--mj-font-medium);\n color: var(--mj-text-primary);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n\n\n.mj-sm-row-badge[_ngcontent-%COMP%] {\n font-size: 10px;\n font-weight: var(--mj-font-semibold);\n padding: 2px 6px;\n border-radius: var(--mj-radius-sm);\n flex-shrink: 0;\n text-transform: uppercase;\n letter-spacing: 0.3px;\n}\n\n.mj-sm-row-badge--default[_ngcontent-%COMP%] {\n background: var(--mj-bg-surface-hover);\n color: var(--mj-text-secondary);\n}\n\n.mj-sm-row-badge--related-entity[_ngcontent-%COMP%] {\n background: color-mix(in srgb, var(--mj-status-info) 10%, var(--mj-bg-surface));\n color: var(--mj-status-info);\n}\n\n.mj-sm-row-badge--inherited[_ngcontent-%COMP%] {\n background: color-mix(in srgb, var(--mj-color-indigo-500) 10%, var(--mj-bg-surface));\n color: var(--mj-color-indigo-500);\n}\n\n\n\n.mj-sm-row-actions[_ngcontent-%COMP%] {\n display: flex;\n gap: 2px;\n flex-shrink: 0;\n margin-left: 8px;\n}\n\n.mj-sm-arrow[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 28px;\n height: 28px;\n border: 1px solid var(--mj-border-default);\n background: var(--mj-bg-surface);\n border-radius: var(--mj-radius-sm);\n cursor: pointer;\n color: var(--mj-text-secondary);\n font-size: 11px;\n transition: all var(--mj-transition-base);\n}\n\n.mj-sm-arrow[_ngcontent-%COMP%]:hover:not(:disabled) {\n background: var(--mj-brand-primary);\n border-color: var(--mj-brand-primary);\n color: var(--mj-text-inverse);\n}\n\n.mj-sm-arrow[_ngcontent-%COMP%]:disabled {\n opacity: 0.3;\n cursor: not-allowed;\n}\n\n\n\n\n\n.mj-sm-footer[_ngcontent-%COMP%] {\n padding: 14px 20px;\n border-top: 1px solid var(--mj-border-default);\n flex-shrink: 0;\n}\n\n.mj-sm-reset[_ngcontent-%COMP%] {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n padding: 7px 14px;\n font-size: var(--mj-text-xs);\n font-weight: var(--mj-font-medium);\n color: var(--mj-text-secondary);\n background: var(--mj-bg-surface);\n border: 1px solid var(--mj-border-default);\n border-radius: var(--mj-radius-sm);\n cursor: pointer;\n transition: all var(--mj-transition-base);\n font-family: inherit;\n}\n\n.mj-sm-reset[_ngcontent-%COMP%]:hover {\n background: var(--mj-bg-surface-hover);\n border-color: var(--mj-border-strong);\n color: var(--mj-text-primary);\n}\n\n.mj-sm-reset[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n font-size: var(--mj-text-xs);\n}\n\n\n\n\n\n@keyframes _ngcontent-%COMP%_mj-sm-fade-in {\n from { opacity: 0; }\n to { opacity: 1; }\n}\n\n@keyframes _ngcontent-%COMP%_mj-sm-slide-in {\n from { transform: translateX(100%); }\n to { transform: translateX(0); }\n}"], changeDetection: 0 });
189
+ } }, styles: ["\n\n\n\n[_nghost-%COMP%] {\n display: block;\n}\n\n\n\n\n\n.mj-sm-overlay[_ngcontent-%COMP%] {\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: var(--mj-bg-overlay);\n z-index: 1000;\n animation: _ngcontent-%COMP%_mj-sm-fade-in 150ms ease;\n}\n\n\n\n\n\n.mj-sm-drawer[_ngcontent-%COMP%] {\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n width: 340px;\n max-width: 90vw;\n background: var(--mj-bg-surface);\n box-shadow: var(--mj-shadow-lg);\n display: flex;\n flex-direction: column;\n animation: _ngcontent-%COMP%_mj-sm-slide-in 200ms ease;\n}\n\n\n\n\n\n.mj-sm-header[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 16px 20px;\n background: var(--mj-bg-surface-elevated);\n border-bottom: 1px solid var(--mj-border-default);\n flex-shrink: 0;\n}\n\n.mj-sm-title[_ngcontent-%COMP%] {\n margin: 0;\n font-size: 15px;\n font-weight: var(--mj-font-semibold);\n color: var(--mj-text-primary);\n display: flex;\n align-items: center;\n gap: 8px;\n}\n\n.mj-sm-title[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n color: var(--mj-brand-primary);\n font-size: var(--mj-text-sm);\n}\n\n.mj-sm-close[_ngcontent-%COMP%] {\n background: none;\n border: none;\n color: var(--mj-text-muted);\n cursor: pointer;\n padding: 6px;\n font-size: var(--mj-text-base);\n border-radius: var(--mj-radius-sm);\n transition: all var(--mj-transition-base);\n line-height: 1;\n}\n\n.mj-sm-close[_ngcontent-%COMP%]:hover {\n color: var(--mj-text-primary);\n background: color-mix(in srgb, var(--mj-text-primary) 5%, transparent);\n}\n\n\n\n\n\n.mj-sm-list[_ngcontent-%COMP%] {\n flex: 1;\n overflow-y: auto;\n padding: 8px 0;\n}\n\n.mj-sm-row[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 10px 20px;\n border-bottom: 1px solid var(--mj-bg-surface-hover);\n transition: background var(--mj-transition-base);\n}\n\n.mj-sm-row[_ngcontent-%COMP%]:hover {\n background: var(--mj-bg-surface-card);\n}\n\n.mj-sm-row-info[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 10px;\n min-width: 0;\n flex: 1;\n}\n\n.mj-sm-row-icon[_ngcontent-%COMP%] {\n font-size: 13px;\n color: var(--mj-text-secondary);\n width: 18px;\n text-align: center;\n flex-shrink: 0;\n}\n\n.mj-sm-row-name[_ngcontent-%COMP%] {\n font-size: 13px;\n font-weight: var(--mj-font-medium);\n color: var(--mj-text-primary);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n\n\n.mj-sm-row-badge[_ngcontent-%COMP%] {\n font-size: 10px;\n font-weight: var(--mj-font-semibold);\n padding: 2px 6px;\n border-radius: var(--mj-radius-sm);\n flex-shrink: 0;\n text-transform: uppercase;\n letter-spacing: 0.3px;\n}\n\n.mj-sm-row-badge--default[_ngcontent-%COMP%] {\n background: var(--mj-bg-surface-hover);\n color: var(--mj-text-secondary);\n}\n\n.mj-sm-row-badge--related-entity[_ngcontent-%COMP%] {\n background: color-mix(in srgb, var(--mj-status-info) 10%, var(--mj-bg-surface));\n color: var(--mj-status-info);\n}\n\n.mj-sm-row-badge--inherited[_ngcontent-%COMP%] {\n background: color-mix(in srgb, var(--mj-brand-accent) 10%, var(--mj-bg-surface));\n color: var(--mj-brand-accent);\n}\n\n\n\n.mj-sm-row-actions[_ngcontent-%COMP%] {\n display: flex;\n gap: 2px;\n flex-shrink: 0;\n margin-left: 8px;\n}\n\n.mj-sm-arrow[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 28px;\n height: 28px;\n border: 1px solid var(--mj-border-default);\n background: var(--mj-bg-surface);\n border-radius: var(--mj-radius-sm);\n cursor: pointer;\n color: var(--mj-text-secondary);\n font-size: 11px;\n transition: all var(--mj-transition-base);\n}\n\n.mj-sm-arrow[_ngcontent-%COMP%]:hover:not(:disabled) {\n background: var(--mj-brand-primary);\n border-color: var(--mj-brand-primary);\n color: var(--mj-text-inverse);\n}\n\n.mj-sm-arrow[_ngcontent-%COMP%]:disabled {\n opacity: 0.3;\n cursor: not-allowed;\n}\n\n\n\n\n\n.mj-sm-footer[_ngcontent-%COMP%] {\n padding: 14px 20px;\n border-top: 1px solid var(--mj-border-default);\n flex-shrink: 0;\n}\n\n.mj-sm-reset[_ngcontent-%COMP%] {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n padding: 7px 14px;\n font-size: var(--mj-text-xs);\n font-weight: var(--mj-font-medium);\n color: var(--mj-text-secondary);\n background: var(--mj-bg-surface);\n border: 1px solid var(--mj-border-default);\n border-radius: var(--mj-radius-sm);\n cursor: pointer;\n transition: all var(--mj-transition-base);\n font-family: inherit;\n}\n\n.mj-sm-reset[_ngcontent-%COMP%]:hover {\n background: var(--mj-bg-surface-hover);\n border-color: var(--mj-border-strong);\n color: var(--mj-text-primary);\n}\n\n.mj-sm-reset[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n font-size: var(--mj-text-xs);\n}\n\n\n\n\n\n@keyframes _ngcontent-%COMP%_mj-sm-fade-in {\n from { opacity: 0; }\n to { opacity: 1; }\n}\n\n@keyframes _ngcontent-%COMP%_mj-sm-slide-in {\n from { transform: translateX(100%); }\n to { transform: translateX(0); }\n}"], changeDetection: 0 });
190
190
  }
191
191
  (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(MjSectionManagerComponent, [{
192
192
  type: Component,
193
- args: [{ standalone: false, selector: 'mj-section-manager', changeDetection: ChangeDetectionStrategy.OnPush, template: "<!-- Overlay backdrop -->\n@if (Visible) {\n <div class=\"mj-sm-overlay\" (click)=\"OnOverlayClick($event)\">\n <!-- Drawer panel -->\n <div class=\"mj-sm-drawer\" (click)=\"$event.stopPropagation()\">\n <!-- Header -->\n <div class=\"mj-sm-header\">\n <h3 class=\"mj-sm-title\">\n <i class=\"fa-solid fa-bars-staggered\"></i>\n Manage Sections\n </h3>\n <button class=\"mj-sm-close\" (click)=\"OnClose()\" title=\"Close\">\n <i class=\"fa-solid fa-xmark\"></i>\n </button>\n </div>\n\n <!-- Section list -->\n <div class=\"mj-sm-list\">\n @for (section of OrderedSections; track section.SectionKey; let i = $index) {\n <div class=\"mj-sm-row\">\n <div class=\"mj-sm-row-info\">\n <i [class]=\"section.Icon\" class=\"mj-sm-row-icon\"></i>\n <span class=\"mj-sm-row-name\">{{ section.SectionName }}</span>\n <span class=\"mj-sm-row-badge mj-sm-row-badge--{{ section.Variant }}\">\n {{ GetVariantLabel(section.Variant) }}\n </span>\n </div>\n <div class=\"mj-sm-row-actions\">\n <button class=\"mj-sm-arrow\"\n [disabled]=\"i === 0\"\n (click)=\"OnMoveUp(i)\"\n title=\"Move up\">\n <i class=\"fa-solid fa-chevron-up\"></i>\n </button>\n <button class=\"mj-sm-arrow\"\n [disabled]=\"i === OrderedSections.length - 1\"\n (click)=\"OnMoveDown(i)\"\n title=\"Move down\">\n <i class=\"fa-solid fa-chevron-down\"></i>\n </button>\n </div>\n </div>\n }\n </div>\n\n <!-- Footer -->\n <div class=\"mj-sm-footer\">\n <button class=\"mj-sm-reset\" (click)=\"OnReset()\">\n <i class=\"fa-solid fa-arrow-rotate-left\"></i>\n Reset to Default Order\n </button>\n </div>\n </div>\n </div>\n}\n", styles: ["/* ============================================\n SECTION MANAGER \u2014 uses global --mj-* design tokens\n ============================================ */\n:host {\n display: block;\n}\n\n/* ============================================\n OVERLAY\n ============================================ */\n.mj-sm-overlay {\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: var(--mj-bg-overlay);\n z-index: 1000;\n animation: mj-sm-fade-in 150ms ease;\n}\n\n/* ============================================\n DRAWER\n ============================================ */\n.mj-sm-drawer {\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n width: 340px;\n max-width: 90vw;\n background: var(--mj-bg-surface);\n box-shadow: var(--mj-shadow-lg);\n display: flex;\n flex-direction: column;\n animation: mj-sm-slide-in 200ms ease;\n}\n\n/* ============================================\n HEADER\n ============================================ */\n.mj-sm-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 16px 20px;\n background: var(--mj-bg-surface-elevated);\n border-bottom: 1px solid var(--mj-border-default);\n flex-shrink: 0;\n}\n\n.mj-sm-title {\n margin: 0;\n font-size: 15px;\n font-weight: var(--mj-font-semibold);\n color: var(--mj-text-primary);\n display: flex;\n align-items: center;\n gap: 8px;\n}\n\n.mj-sm-title i {\n color: var(--mj-brand-primary);\n font-size: var(--mj-text-sm);\n}\n\n.mj-sm-close {\n background: none;\n border: none;\n color: var(--mj-text-muted);\n cursor: pointer;\n padding: 6px;\n font-size: var(--mj-text-base);\n border-radius: var(--mj-radius-sm);\n transition: all var(--mj-transition-base);\n line-height: 1;\n}\n\n.mj-sm-close:hover {\n color: var(--mj-text-primary);\n background: color-mix(in srgb, var(--mj-text-primary) 5%, transparent);\n}\n\n/* ============================================\n SECTION LIST\n ============================================ */\n.mj-sm-list {\n flex: 1;\n overflow-y: auto;\n padding: 8px 0;\n}\n\n.mj-sm-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 10px 20px;\n border-bottom: 1px solid var(--mj-bg-surface-hover);\n transition: background var(--mj-transition-base);\n}\n\n.mj-sm-row:hover {\n background: var(--mj-bg-surface-card);\n}\n\n.mj-sm-row-info {\n display: flex;\n align-items: center;\n gap: 10px;\n min-width: 0;\n flex: 1;\n}\n\n.mj-sm-row-icon {\n font-size: 13px;\n color: var(--mj-text-secondary);\n width: 18px;\n text-align: center;\n flex-shrink: 0;\n}\n\n.mj-sm-row-name {\n font-size: 13px;\n font-weight: var(--mj-font-medium);\n color: var(--mj-text-primary);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n/* Variant badges */\n.mj-sm-row-badge {\n font-size: 10px;\n font-weight: var(--mj-font-semibold);\n padding: 2px 6px;\n border-radius: var(--mj-radius-sm);\n flex-shrink: 0;\n text-transform: uppercase;\n letter-spacing: 0.3px;\n}\n\n.mj-sm-row-badge--default {\n background: var(--mj-bg-surface-hover);\n color: var(--mj-text-secondary);\n}\n\n.mj-sm-row-badge--related-entity {\n background: color-mix(in srgb, var(--mj-status-info) 10%, var(--mj-bg-surface));\n color: var(--mj-status-info);\n}\n\n.mj-sm-row-badge--inherited {\n background: color-mix(in srgb, var(--mj-color-indigo-500) 10%, var(--mj-bg-surface));\n color: var(--mj-color-indigo-500);\n}\n\n/* Arrow buttons */\n.mj-sm-row-actions {\n display: flex;\n gap: 2px;\n flex-shrink: 0;\n margin-left: 8px;\n}\n\n.mj-sm-arrow {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 28px;\n height: 28px;\n border: 1px solid var(--mj-border-default);\n background: var(--mj-bg-surface);\n border-radius: var(--mj-radius-sm);\n cursor: pointer;\n color: var(--mj-text-secondary);\n font-size: 11px;\n transition: all var(--mj-transition-base);\n}\n\n.mj-sm-arrow:hover:not(:disabled) {\n background: var(--mj-brand-primary);\n border-color: var(--mj-brand-primary);\n color: var(--mj-text-inverse);\n}\n\n.mj-sm-arrow:disabled {\n opacity: 0.3;\n cursor: not-allowed;\n}\n\n/* ============================================\n FOOTER\n ============================================ */\n.mj-sm-footer {\n padding: 14px 20px;\n border-top: 1px solid var(--mj-border-default);\n flex-shrink: 0;\n}\n\n.mj-sm-reset {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n padding: 7px 14px;\n font-size: var(--mj-text-xs);\n font-weight: var(--mj-font-medium);\n color: var(--mj-text-secondary);\n background: var(--mj-bg-surface);\n border: 1px solid var(--mj-border-default);\n border-radius: var(--mj-radius-sm);\n cursor: pointer;\n transition: all var(--mj-transition-base);\n font-family: inherit;\n}\n\n.mj-sm-reset:hover {\n background: var(--mj-bg-surface-hover);\n border-color: var(--mj-border-strong);\n color: var(--mj-text-primary);\n}\n\n.mj-sm-reset i {\n font-size: var(--mj-text-xs);\n}\n\n/* ============================================\n ANIMATIONS\n ============================================ */\n@keyframes mj-sm-fade-in {\n from { opacity: 0; }\n to { opacity: 1; }\n}\n\n@keyframes mj-sm-slide-in {\n from { transform: translateX(100%); }\n to { transform: translateX(0); }\n}\n"] }]
193
+ args: [{ standalone: false, selector: 'mj-section-manager', changeDetection: ChangeDetectionStrategy.OnPush, template: "<!-- Overlay backdrop -->\n@if (Visible) {\n <div class=\"mj-sm-overlay\" (click)=\"OnOverlayClick($event)\">\n <!-- Drawer panel -->\n <div class=\"mj-sm-drawer\" (click)=\"$event.stopPropagation()\">\n <!-- Header -->\n <div class=\"mj-sm-header\">\n <h3 class=\"mj-sm-title\">\n <i class=\"fa-solid fa-bars-staggered\"></i>\n Manage Sections\n </h3>\n <button class=\"mj-sm-close\" (click)=\"OnClose()\" title=\"Close\">\n <i class=\"fa-solid fa-xmark\"></i>\n </button>\n </div>\n\n <!-- Section list -->\n <div class=\"mj-sm-list\">\n @for (section of OrderedSections; track section.SectionKey; let i = $index) {\n <div class=\"mj-sm-row\">\n <div class=\"mj-sm-row-info\">\n <i [class]=\"section.Icon\" class=\"mj-sm-row-icon\"></i>\n <span class=\"mj-sm-row-name\">{{ section.SectionName }}</span>\n <span class=\"mj-sm-row-badge mj-sm-row-badge--{{ section.Variant }}\">\n {{ GetVariantLabel(section.Variant) }}\n </span>\n </div>\n <div class=\"mj-sm-row-actions\">\n <button class=\"mj-sm-arrow\"\n [disabled]=\"i === 0\"\n (click)=\"OnMoveUp(i)\"\n title=\"Move up\">\n <i class=\"fa-solid fa-chevron-up\"></i>\n </button>\n <button class=\"mj-sm-arrow\"\n [disabled]=\"i === OrderedSections.length - 1\"\n (click)=\"OnMoveDown(i)\"\n title=\"Move down\">\n <i class=\"fa-solid fa-chevron-down\"></i>\n </button>\n </div>\n </div>\n }\n </div>\n\n <!-- Footer -->\n <div class=\"mj-sm-footer\">\n <button class=\"mj-sm-reset\" (click)=\"OnReset()\">\n <i class=\"fa-solid fa-arrow-rotate-left\"></i>\n Reset to Default Order\n </button>\n </div>\n </div>\n </div>\n}\n", styles: ["/* ============================================\n SECTION MANAGER \u2014 uses global --mj-* design tokens\n ============================================ */\n:host {\n display: block;\n}\n\n/* ============================================\n OVERLAY\n ============================================ */\n.mj-sm-overlay {\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: var(--mj-bg-overlay);\n z-index: 1000;\n animation: mj-sm-fade-in 150ms ease;\n}\n\n/* ============================================\n DRAWER\n ============================================ */\n.mj-sm-drawer {\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n width: 340px;\n max-width: 90vw;\n background: var(--mj-bg-surface);\n box-shadow: var(--mj-shadow-lg);\n display: flex;\n flex-direction: column;\n animation: mj-sm-slide-in 200ms ease;\n}\n\n/* ============================================\n HEADER\n ============================================ */\n.mj-sm-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 16px 20px;\n background: var(--mj-bg-surface-elevated);\n border-bottom: 1px solid var(--mj-border-default);\n flex-shrink: 0;\n}\n\n.mj-sm-title {\n margin: 0;\n font-size: 15px;\n font-weight: var(--mj-font-semibold);\n color: var(--mj-text-primary);\n display: flex;\n align-items: center;\n gap: 8px;\n}\n\n.mj-sm-title i {\n color: var(--mj-brand-primary);\n font-size: var(--mj-text-sm);\n}\n\n.mj-sm-close {\n background: none;\n border: none;\n color: var(--mj-text-muted);\n cursor: pointer;\n padding: 6px;\n font-size: var(--mj-text-base);\n border-radius: var(--mj-radius-sm);\n transition: all var(--mj-transition-base);\n line-height: 1;\n}\n\n.mj-sm-close:hover {\n color: var(--mj-text-primary);\n background: color-mix(in srgb, var(--mj-text-primary) 5%, transparent);\n}\n\n/* ============================================\n SECTION LIST\n ============================================ */\n.mj-sm-list {\n flex: 1;\n overflow-y: auto;\n padding: 8px 0;\n}\n\n.mj-sm-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 10px 20px;\n border-bottom: 1px solid var(--mj-bg-surface-hover);\n transition: background var(--mj-transition-base);\n}\n\n.mj-sm-row:hover {\n background: var(--mj-bg-surface-card);\n}\n\n.mj-sm-row-info {\n display: flex;\n align-items: center;\n gap: 10px;\n min-width: 0;\n flex: 1;\n}\n\n.mj-sm-row-icon {\n font-size: 13px;\n color: var(--mj-text-secondary);\n width: 18px;\n text-align: center;\n flex-shrink: 0;\n}\n\n.mj-sm-row-name {\n font-size: 13px;\n font-weight: var(--mj-font-medium);\n color: var(--mj-text-primary);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n/* Variant badges */\n.mj-sm-row-badge {\n font-size: 10px;\n font-weight: var(--mj-font-semibold);\n padding: 2px 6px;\n border-radius: var(--mj-radius-sm);\n flex-shrink: 0;\n text-transform: uppercase;\n letter-spacing: 0.3px;\n}\n\n.mj-sm-row-badge--default {\n background: var(--mj-bg-surface-hover);\n color: var(--mj-text-secondary);\n}\n\n.mj-sm-row-badge--related-entity {\n background: color-mix(in srgb, var(--mj-status-info) 10%, var(--mj-bg-surface));\n color: var(--mj-status-info);\n}\n\n.mj-sm-row-badge--inherited {\n background: color-mix(in srgb, var(--mj-brand-accent) 10%, var(--mj-bg-surface));\n color: var(--mj-brand-accent);\n}\n\n/* Arrow buttons */\n.mj-sm-row-actions {\n display: flex;\n gap: 2px;\n flex-shrink: 0;\n margin-left: 8px;\n}\n\n.mj-sm-arrow {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 28px;\n height: 28px;\n border: 1px solid var(--mj-border-default);\n background: var(--mj-bg-surface);\n border-radius: var(--mj-radius-sm);\n cursor: pointer;\n color: var(--mj-text-secondary);\n font-size: 11px;\n transition: all var(--mj-transition-base);\n}\n\n.mj-sm-arrow:hover:not(:disabled) {\n background: var(--mj-brand-primary);\n border-color: var(--mj-brand-primary);\n color: var(--mj-text-inverse);\n}\n\n.mj-sm-arrow:disabled {\n opacity: 0.3;\n cursor: not-allowed;\n}\n\n/* ============================================\n FOOTER\n ============================================ */\n.mj-sm-footer {\n padding: 14px 20px;\n border-top: 1px solid var(--mj-border-default);\n flex-shrink: 0;\n}\n\n.mj-sm-reset {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n padding: 7px 14px;\n font-size: var(--mj-text-xs);\n font-weight: var(--mj-font-medium);\n color: var(--mj-text-secondary);\n background: var(--mj-bg-surface);\n border: 1px solid var(--mj-border-default);\n border-radius: var(--mj-radius-sm);\n cursor: pointer;\n transition: all var(--mj-transition-base);\n font-family: inherit;\n}\n\n.mj-sm-reset:hover {\n background: var(--mj-bg-surface-hover);\n border-color: var(--mj-border-strong);\n color: var(--mj-text-primary);\n}\n\n.mj-sm-reset i {\n font-size: var(--mj-text-xs);\n}\n\n/* ============================================\n ANIMATIONS\n ============================================ */\n@keyframes mj-sm-fade-in {\n from { opacity: 0; }\n to { opacity: 1; }\n}\n\n@keyframes mj-sm-slide-in {\n from { transform: translateX(100%); }\n to { transform: translateX(0); }\n}\n"] }]
194
194
  }], null, { Sections: [{
195
195
  type: Input
196
196
  }], SectionOrder: [{
@@ -1181,11 +1181,11 @@ export class MjFormToolbarComponent {
1181
1181
  i0.ɵɵconditional(ctx.ShowDiscardDialog ? 2 : -1);
1182
1182
  i0.ɵɵadvance();
1183
1183
  i0.ɵɵconditional(ctx.ShowDeleteDialog ? 3 : -1);
1184
- } }, dependencies: [i1.NgTemplateOutlet], styles: ["\n\n\n\n[_nghost-%COMP%] {\n display: block;\n}\n\n\n\n\n\n.mj-forms-edit-banner[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 10px;\n padding: 16px 16px;\n background: color-mix(in srgb, var(--mj-brand-primary) 10%, var(--mj-bg-surface));\n border-bottom: 2px solid var(--mj-brand-primary);\n font-size: 13px;\n color: var(--mj-brand-primary);\n font-weight: var(--mj-font-medium);\n}\n\n.mj-forms-edit-banner[_ngcontent-%COMP%] > i[_ngcontent-%COMP%] {\n font-size: var(--mj-text-sm);\n}\n\n.mj-forms-edit-banner-icon--dirty[_ngcontent-%COMP%] {\n color: var(--mj-status-warning);\n font-size: var(--mj-text-base);\n}\n\n.mj-forms-edit-banner-text[_ngcontent-%COMP%] {\n font-size: var(--mj-text-sm);\n}\n\n.mj-forms-edit-banner-dirty[_ngcontent-%COMP%] {\n font-size: var(--mj-text-sm);\n color: var(--mj-text-primary);\n}\n\n.mj-forms-edit-banner-dirty[_ngcontent-%COMP%] strong[_ngcontent-%COMP%] {\n color: var(--mj-status-warning);\n font-weight: var(--mj-font-bold);\n}\n\n.mj-forms-edit-banner-actions[_ngcontent-%COMP%] {\n display: flex;\n gap: 8px;\n}\n\n\n\n\n\n.mj-forms-toolbar[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 22px 16px;\n background: var(--mj-bg-surface-card);\n border-bottom: 2px solid var(--mj-border-default);\n flex-wrap: wrap;\n z-index: 40;\n}\n\n.mj-forms-toolbar--sticky[_ngcontent-%COMP%] {\n position: sticky;\n top: 0;\n}\n\n.mj-forms-toolbar--disabled[_ngcontent-%COMP%] {\n pointer-events: none;\n opacity: 0.7;\n}\n\n.mj-forms-toolbar-group[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 6px;\n}\n\n.mj-forms-toolbar-separator[_ngcontent-%COMP%] {\n width: 1px;\n height: 24px;\n background: var(--mj-border-default);\n margin: 0 4px;\n}\n\n.mj-forms-toolbar-spacer[_ngcontent-%COMP%] {\n flex: 1;\n}\n\n\n\n\n\n.mj-forms-btn[_ngcontent-%COMP%] {\n position: relative;\n display: inline-flex;\n align-items: center;\n gap: 6px;\n padding: 8px 16px;\n font-size: 13px;\n font-weight: var(--mj-font-medium);\n border: 1px solid var(--mj-border-default);\n background: var(--mj-bg-surface);\n color: var(--mj-text-primary);\n border-radius: var(--mj-radius-sm);\n cursor: pointer;\n transition: all var(--mj-transition-base) ease;\n white-space: nowrap;\n font-family: inherit;\n line-height: 1;\n}\n\n.mj-forms-btn[_ngcontent-%COMP%]:hover {\n background: var(--mj-bg-surface-hover);\n border-color: var(--mj-border-strong);\n box-shadow: var(--mj-shadow-sm);\n}\n\n.mj-forms-btn[_ngcontent-%COMP%]:active {\n transform: translateY(0);\n box-shadow: none;\n}\n\n.mj-forms-btn[_ngcontent-%COMP%]:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n}\n\n.mj-forms-btn[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n font-size: 15px;\n line-height: 1;\n}\n\n.mj-forms-btn--primary[_ngcontent-%COMP%] {\n background: var(--mj-brand-primary);\n color: var(--mj-text-inverse);\n border-color: var(--mj-brand-primary);\n}\n\n.mj-forms-btn--primary[_ngcontent-%COMP%]:hover {\n background: var(--mj-brand-primary-hover);\n border-color: var(--mj-brand-primary-hover);\n box-shadow: 0 2px 8px color-mix(in srgb, var(--mj-brand-primary) 30%, transparent);\n}\n\n.mj-forms-btn--danger[_ngcontent-%COMP%] {\n color: var(--mj-status-error);\n border-color: color-mix(in srgb, var(--mj-status-error) 30%, transparent);\n background: color-mix(in srgb, var(--mj-status-error) 5%, var(--mj-bg-surface));\n}\n\n.mj-forms-btn--danger[_ngcontent-%COMP%]:hover {\n background: color-mix(in srgb, var(--mj-status-error) 10%, var(--mj-bg-surface));\n border-color: var(--mj-status-error);\n}\n\n.mj-forms-btn--ghost[_ngcontent-%COMP%] {\n border-color: transparent;\n background: transparent;\n}\n\n.mj-forms-btn--ghost[_ngcontent-%COMP%]:hover {\n background: color-mix(in srgb, var(--mj-brand-primary) 8%, transparent);\n border-color: transparent;\n}\n\n.mj-forms-btn--icon-only[_ngcontent-%COMP%] {\n padding: 7px 9px;\n}\n\n.mj-forms-btn--sm[_ngcontent-%COMP%] {\n padding: 5px 12px;\n font-size: var(--mj-text-xs);\n}\n\n.mj-forms-btn--sm[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n font-size: var(--mj-text-xs);\n}\n\n.mj-forms-btn-text[_ngcontent-%COMP%] {\n margin-left: 2px;\n}\n\n.mj-icon--favorite[_ngcontent-%COMP%] {\n color: var(--mj-status-warning);\n}\n\n\n\n.mj-forms-btn--list[_ngcontent-%COMP%] {\n position: relative;\n}\n\n\n\n.mj-forms-btn--history[_ngcontent-%COMP%] {\n position: relative;\n}\n\n.mj-version-count-badge[_ngcontent-%COMP%] {\n position: absolute;\n top: -6px;\n right: -10px;\n padding: 0 5px;\n font-size: 10px;\n font-weight: var(--mj-font-bold);\n color: var(--mj-text-inverse);\n background: linear-gradient(135deg, var(--mj-status-success) 0%, color-mix(in srgb, var(--mj-status-success) 85%, black) 100%);\n border-radius: var(--mj-radius-full);\n display: flex;\n align-items: center;\n justify-content: center;\n height: 16px;\n border: 2px solid var(--mj-bg-surface-card);\n white-space: nowrap;\n line-height: 1;\n}\n\n.mj-list-count-badge[_ngcontent-%COMP%] {\n position: absolute;\n top: -6px;\n right: -6px;\n min-width: 16px;\n height: 16px;\n padding: 0 4px;\n font-size: 10px;\n font-weight: var(--mj-font-bold);\n color: var(--mj-text-inverse);\n background: linear-gradient(135deg, var(--mj-status-info) 0%, color-mix(in srgb, var(--mj-status-info) 85%, black) 100%);\n border-radius: var(--mj-radius-full);\n display: flex;\n align-items: center;\n justify-content: center;\n border: 2px solid var(--mj-bg-surface-card);\n}\n\n\n\n\n\n.mj-entity-hierarchy[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 4px;\n}\n\n.mj-entity-badge[_ngcontent-%COMP%] {\n display: inline-flex;\n align-items: center;\n gap: 5px;\n padding: 4px 10px;\n font-size: var(--mj-text-xs);\n font-weight: var(--mj-font-semibold);\n border-radius: var(--mj-radius-sm);\n cursor: pointer;\n transition: all var(--mj-transition-base) ease;\n text-decoration: none;\n border: 1px solid transparent;\n}\n\n.mj-entity-badge[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n font-size: 11px;\n}\n\n.mj-entity-badge--parent[_ngcontent-%COMP%] {\n background: color-mix(in srgb, var(--mj-color-indigo-500) 10%, var(--mj-bg-surface));\n color: var(--mj-color-indigo-500);\n border-color: color-mix(in srgb, var(--mj-color-indigo-500) 25%, transparent);\n}\n\n.mj-entity-badge--parent[_ngcontent-%COMP%]:hover {\n background: color-mix(in srgb, var(--mj-color-indigo-500) 18%, var(--mj-bg-surface));\n border-color: var(--mj-color-indigo-500);\n box-shadow: 0 2px 6px color-mix(in srgb, var(--mj-color-indigo-500) 15%, transparent);\n}\n\n.mj-entity-badge--current[_ngcontent-%COMP%] {\n font-weight: var(--mj-font-semibold);\n font-size: 13px;\n color: var(--mj-text-primary);\n padding: 4px 8px;\n background: var(--mj-bg-surface-hover);\n border-radius: var(--mj-radius-sm);\n cursor: default;\n}\n\n.mj-entity-badge--child[_ngcontent-%COMP%] {\n background: color-mix(in srgb, var(--mj-status-info) 10%, var(--mj-bg-surface));\n color: var(--mj-status-info);\n border-color: color-mix(in srgb, var(--mj-status-info) 25%, transparent);\n}\n\n.mj-entity-badge--child[_ngcontent-%COMP%]:hover {\n background: color-mix(in srgb, var(--mj-status-info) 18%, var(--mj-bg-surface));\n border-color: var(--mj-status-info);\n box-shadow: 0 2px 6px color-mix(in srgb, var(--mj-status-info) 15%, transparent);\n}\n\n.mj-entity-badge--overlapping[_ngcontent-%COMP%] {\n background: color-mix(in srgb, var(--mj-status-warning) 10%, var(--mj-bg-surface));\n color: color-mix(in srgb, var(--mj-status-warning) 85%, black);\n border-color: color-mix(in srgb, var(--mj-status-warning) 40%, transparent);\n}\n\n.mj-entity-badge--overlapping[_ngcontent-%COMP%]:hover {\n background: color-mix(in srgb, var(--mj-status-warning) 18%, var(--mj-bg-surface));\n border-color: color-mix(in srgb, var(--mj-status-warning) 85%, black);\n box-shadow: 0 2px 6px color-mix(in srgb, var(--mj-status-warning) 15%, transparent);\n}\n\n.mj-entity-badge-group[_ngcontent-%COMP%] {\n display: inline-flex;\n align-items: center;\n gap: 4px;\n flex-wrap: wrap;\n}\n\n.mj-child-count[_ngcontent-%COMP%] {\n background: var(--mj-status-info);\n color: var(--mj-text-inverse);\n font-size: 10px;\n padding: 1px 5px;\n border-radius: var(--mj-radius-full);\n font-weight: var(--mj-font-bold);\n}\n\n.mj-entity-badge--leaf[_ngcontent-%COMP%] {\n border-style: dashed;\n}\n\n.mj-entity-badge--root[_ngcontent-%COMP%] {\n border-style: dashed;\n}\n\n.mj-hierarchy-arrow[_ngcontent-%COMP%] {\n color: var(--mj-text-muted);\n font-size: 10px;\n}\n\n\n\n.mj-descendant-chains[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n gap: 6px;\n position: relative;\n padding-left: 20px;\n}\n\n\n\n.mj-descendant-chains[_ngcontent-%COMP%]::before {\n content: '';\n position: absolute;\n left: -4px;\n top: 50%;\n width: 4px;\n border-top: 1.5px solid var(--mj-text-muted);\n}\n\n.mj-descendant-chain[_ngcontent-%COMP%] {\n display: inline-flex;\n align-items: center;\n gap: 4px;\n position: relative;\n padding: 2px 0;\n}\n\n\n\n.mj-descendant-chain[_ngcontent-%COMP%]::before {\n content: '';\n position: absolute;\n left: -20px;\n top: 50%;\n width: 18px;\n border-top: 1.5px solid var(--mj-text-muted);\n}\n\n\n\n.mj-descendant-chain[_ngcontent-%COMP%]::after {\n content: '';\n position: absolute;\n left: -20px;\n top: 0;\n bottom: 0;\n border-left: 1.5px solid var(--mj-text-muted);\n}\n\n\n\n.mj-descendant-chain[_ngcontent-%COMP%]:first-child::after {\n top: 50%;\n}\n\n\n\n.mj-descendant-chain[_ngcontent-%COMP%]:last-child::after {\n bottom: 50%;\n}\n\n\n\n\n\n\n\n\n.mj-section-controls[_ngcontent-%COMP%] {\n gap: 6px;\n}\n\n.mj-forms-search-wrapper[_ngcontent-%COMP%] {\n position: relative;\n display: inline-flex;\n align-items: center;\n}\n\n.mj-section-search[_ngcontent-%COMP%] {\n max-width: 180px;\n font-size: 13px;\n padding: 6px 28px 6px 8px;\n border: 1px solid var(--mj-border-default);\n border-radius: var(--mj-radius-sm);\n background: var(--mj-bg-surface);\n outline: none;\n font-family: inherit;\n transition: all var(--mj-transition-base);\n}\n\n.mj-section-search[_ngcontent-%COMP%]:focus {\n border-color: var(--mj-brand-primary);\n box-shadow: 0 0 0 2px color-mix(in srgb, var(--mj-brand-primary) 10%, transparent);\n}\n\n\n\n.mj-section-search--active[_ngcontent-%COMP%] {\n background-color: color-mix(in srgb, var(--mj-status-warning) 40%, var(--mj-bg-surface));\n border-color: var(--mj-status-warning);\n color: color-mix(in srgb, var(--mj-status-warning) 80%, black);\n font-weight: var(--mj-font-semibold);\n box-shadow: 0 0 0 2px color-mix(in srgb, var(--mj-status-warning) 25%, transparent);\n}\n\n.mj-section-search[_ngcontent-%COMP%]::placeholder {\n color: var(--mj-text-muted);\n font-style: italic;\n}\n\n.mj-clear-search[_ngcontent-%COMP%] {\n position: absolute;\n right: 4px;\n padding: 4px;\n background: none;\n border: none;\n color: var(--mj-text-muted);\n cursor: pointer;\n font-size: var(--mj-text-xs);\n line-height: 1;\n}\n\n.mj-clear-search[_ngcontent-%COMP%]:hover {\n color: var(--mj-text-secondary);\n}\n\n\n\n\n\n.mj-dialog-overlay[_ngcontent-%COMP%] {\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: var(--mj-bg-overlay);\n display: flex;\n align-items: center;\n justify-content: center;\n z-index: 1000;\n animation: _ngcontent-%COMP%_mj-fade-in 150ms ease;\n}\n\n.mj-dialog[_ngcontent-%COMP%] {\n background: var(--mj-bg-surface-card);\n border-radius: var(--mj-radius-lg);\n box-shadow: var(--mj-shadow-lg);\n min-width: 400px;\n max-width: 550px;\n animation: _ngcontent-%COMP%_mj-slide-up 200ms ease;\n}\n\n.mj-dialog-header[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 16px 20px;\n border-bottom: 1px solid var(--mj-border-default);\n}\n\n.mj-dialog-header[_ngcontent-%COMP%] h3[_ngcontent-%COMP%] {\n margin: 0;\n font-size: var(--mj-text-base);\n font-weight: var(--mj-font-semibold);\n}\n\n.mj-dialog-close[_ngcontent-%COMP%] {\n background: none;\n border: none;\n color: var(--mj-text-muted);\n cursor: pointer;\n padding: 4px;\n font-size: var(--mj-text-base);\n transition: color var(--mj-transition-base);\n}\n\n.mj-dialog-close[_ngcontent-%COMP%]:hover {\n color: var(--mj-text-primary);\n}\n\n.mj-dialog-body[_ngcontent-%COMP%] {\n padding: 20px;\n font-size: var(--mj-text-sm);\n color: var(--mj-text-secondary);\n line-height: 1.6;\n}\n\n.mj-dialog-body[_ngcontent-%COMP%] p[_ngcontent-%COMP%] {\n margin: 0;\n}\n\n.mj-dialog-footer[_ngcontent-%COMP%] {\n display: flex;\n gap: 8px;\n padding: 16px 20px;\n border-top: 1px solid var(--mj-border-default);\n}\n\n@keyframes _ngcontent-%COMP%_mj-fade-in {\n from { opacity: 0; }\n to { opacity: 1; }\n}\n\n@keyframes _ngcontent-%COMP%_mj-slide-up {\n from { transform: translateY(8px); opacity: 0; }\n to { transform: translateY(0); opacity: 1; }\n}\n\n\n\n\n\n\n\n\n.mj-form-variant-picker-host[_ngcontent-%COMP%] {\n position: relative;\n display: inline-flex;\n}\n\n.mj-form-variant-picker-trigger[_ngcontent-%COMP%] {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n max-width: 240px;\n}\n\n.mj-form-variant-picker-trigger[_ngcontent-%COMP%] .chev[_ngcontent-%COMP%] {\n font-size: 10px;\n opacity: 0.7;\n}\n\n.mj-form-variant-picker-current-label[_ngcontent-%COMP%] {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n max-width: 160px;\n font-size: 12px;\n font-weight: 500;\n}\n\n.mj-form-variant-picker-host.open[_ngcontent-%COMP%] .mj-form-variant-picker-trigger[_ngcontent-%COMP%] {\n background: var(--mj-bg-surface-hover);\n border-color: var(--mj-border-focus);\n}\n\n.mj-form-variant-picker-menu[_ngcontent-%COMP%] {\n position: absolute;\n top: calc(100% + 4px);\n right: 0;\n z-index: 50;\n min-width: 280px;\n max-height: 360px;\n overflow-y: auto;\n background: var(--mj-bg-surface-elevated);\n border: 1px solid var(--mj-border-default);\n border-radius: 6px;\n box-shadow: 0 4px 16px rgba(0, 0, 0, 0.12);\n padding: 4px 0;\n}\n\n.mj-form-variant-picker-row[_ngcontent-%COMP%] {\n display: flex;\n align-items: flex-start;\n gap: 8px;\n padding: 8px 12px;\n cursor: pointer;\n color: var(--mj-text-primary);\n font-size: 13px;\n transition: background 120ms;\n}\n\n.mj-form-variant-picker-row[_ngcontent-%COMP%]:hover {\n background: var(--mj-bg-surface-hover);\n}\n\n.mj-form-variant-picker-row.current[_ngcontent-%COMP%] {\n background: color-mix(in srgb, var(--mj-brand-primary) 10%, transparent);\n}\n\n.mj-form-variant-picker-row.inactive[_ngcontent-%COMP%] {\n opacity: 0.6;\n}\n\n.mj-form-variant-picker-row-main[_ngcontent-%COMP%] {\n flex: 1;\n display: flex;\n flex-direction: column;\n gap: 2px;\n}\n\n.mj-form-variant-picker-row-title[_ngcontent-%COMP%] {\n font-weight: 600;\n}\n\n.mj-form-variant-picker-row-sub[_ngcontent-%COMP%] {\n font-size: 11px;\n color: var(--mj-text-muted);\n}\n\n.mj-form-variant-picker-default-tag[_ngcontent-%COMP%] {\n font-size: 10px;\n font-weight: 500;\n color: var(--mj-text-muted);\n margin-left: 4px;\n}\n\n.mj-form-variant-picker-default[_ngcontent-%COMP%] {\n border-bottom: 1px solid var(--mj-border-subtle);\n}\n\n.mj-form-variant-picker-check[_ngcontent-%COMP%] {\n color: var(--mj-brand-primary);\n font-size: 12px;\n margin-top: 2px;\n}"], changeDetection: 0 });
1184
+ } }, dependencies: [i1.NgTemplateOutlet], styles: ["\n\n\n\n[_nghost-%COMP%] {\n display: block;\n}\n\n\n\n\n\n.mj-forms-edit-banner[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 10px;\n padding: 16px 16px;\n background: color-mix(in srgb, var(--mj-brand-primary) 10%, var(--mj-bg-surface));\n border-bottom: 2px solid var(--mj-brand-primary);\n font-size: 13px;\n color: var(--mj-brand-primary);\n font-weight: var(--mj-font-medium);\n}\n\n.mj-forms-edit-banner[_ngcontent-%COMP%] > i[_ngcontent-%COMP%] {\n font-size: var(--mj-text-sm);\n}\n\n.mj-forms-edit-banner-icon--dirty[_ngcontent-%COMP%] {\n color: var(--mj-status-warning);\n font-size: var(--mj-text-base);\n}\n\n.mj-forms-edit-banner-text[_ngcontent-%COMP%] {\n font-size: var(--mj-text-sm);\n}\n\n.mj-forms-edit-banner-dirty[_ngcontent-%COMP%] {\n font-size: var(--mj-text-sm);\n color: var(--mj-text-primary);\n}\n\n.mj-forms-edit-banner-dirty[_ngcontent-%COMP%] strong[_ngcontent-%COMP%] {\n color: var(--mj-status-warning);\n font-weight: var(--mj-font-bold);\n}\n\n.mj-forms-edit-banner-actions[_ngcontent-%COMP%] {\n display: flex;\n gap: 8px;\n}\n\n\n\n\n\n.mj-forms-toolbar[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 22px 16px;\n background: var(--mj-bg-surface-card);\n border-bottom: 2px solid var(--mj-border-default);\n flex-wrap: wrap;\n z-index: 40;\n}\n\n.mj-forms-toolbar--sticky[_ngcontent-%COMP%] {\n position: sticky;\n top: 0;\n}\n\n.mj-forms-toolbar--disabled[_ngcontent-%COMP%] {\n pointer-events: none;\n opacity: 0.7;\n}\n\n.mj-forms-toolbar-group[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 6px;\n}\n\n.mj-forms-toolbar-separator[_ngcontent-%COMP%] {\n width: 1px;\n height: 24px;\n background: var(--mj-border-default);\n margin: 0 4px;\n}\n\n.mj-forms-toolbar-spacer[_ngcontent-%COMP%] {\n flex: 1;\n}\n\n\n\n\n\n.mj-forms-btn[_ngcontent-%COMP%] {\n position: relative;\n display: inline-flex;\n align-items: center;\n gap: 6px;\n padding: 8px 16px;\n font-size: 13px;\n font-weight: var(--mj-font-medium);\n border: 1px solid var(--mj-border-default);\n background: var(--mj-bg-surface);\n color: var(--mj-text-primary);\n border-radius: var(--mj-radius-sm);\n cursor: pointer;\n transition: all var(--mj-transition-base) ease;\n white-space: nowrap;\n font-family: inherit;\n line-height: 1;\n}\n\n.mj-forms-btn[_ngcontent-%COMP%]:hover {\n background: var(--mj-bg-surface-hover);\n border-color: var(--mj-border-strong);\n box-shadow: var(--mj-shadow-sm);\n}\n\n.mj-forms-btn[_ngcontent-%COMP%]:active {\n transform: translateY(0);\n box-shadow: none;\n}\n\n.mj-forms-btn[_ngcontent-%COMP%]:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n}\n\n.mj-forms-btn[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n font-size: 15px;\n line-height: 1;\n}\n\n.mj-forms-btn--primary[_ngcontent-%COMP%] {\n background: var(--mj-brand-primary);\n color: var(--mj-text-inverse);\n border-color: var(--mj-brand-primary);\n}\n\n.mj-forms-btn--primary[_ngcontent-%COMP%]:hover {\n background: var(--mj-brand-primary-hover);\n border-color: var(--mj-brand-primary-hover);\n box-shadow: 0 2px 8px color-mix(in srgb, var(--mj-brand-primary) 30%, transparent);\n}\n\n.mj-forms-btn--danger[_ngcontent-%COMP%] {\n color: var(--mj-status-error);\n border-color: color-mix(in srgb, var(--mj-status-error) 30%, transparent);\n background: color-mix(in srgb, var(--mj-status-error) 5%, var(--mj-bg-surface));\n}\n\n.mj-forms-btn--danger[_ngcontent-%COMP%]:hover {\n background: color-mix(in srgb, var(--mj-status-error) 10%, var(--mj-bg-surface));\n border-color: var(--mj-status-error);\n}\n\n.mj-forms-btn--ghost[_ngcontent-%COMP%] {\n border-color: transparent;\n background: transparent;\n}\n\n.mj-forms-btn--ghost[_ngcontent-%COMP%]:hover {\n background: color-mix(in srgb, var(--mj-brand-primary) 8%, transparent);\n border-color: transparent;\n}\n\n.mj-forms-btn--icon-only[_ngcontent-%COMP%] {\n padding: 7px 9px;\n}\n\n.mj-forms-btn--sm[_ngcontent-%COMP%] {\n padding: 5px 12px;\n font-size: var(--mj-text-xs);\n}\n\n.mj-forms-btn--sm[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n font-size: var(--mj-text-xs);\n}\n\n.mj-forms-btn-text[_ngcontent-%COMP%] {\n margin-left: 2px;\n}\n\n.mj-icon--favorite[_ngcontent-%COMP%] {\n color: var(--mj-status-warning);\n}\n\n\n\n.mj-forms-btn--list[_ngcontent-%COMP%] {\n position: relative;\n}\n\n\n\n.mj-forms-btn--history[_ngcontent-%COMP%] {\n position: relative;\n}\n\n.mj-version-count-badge[_ngcontent-%COMP%] {\n position: absolute;\n top: -6px;\n right: -10px;\n padding: 0 5px;\n font-size: 10px;\n font-weight: var(--mj-font-bold);\n color: var(--mj-text-inverse);\n background: linear-gradient(135deg, var(--mj-status-success) 0%, color-mix(in srgb, var(--mj-status-success) 85%, black) 100%);\n border-radius: var(--mj-radius-full);\n display: flex;\n align-items: center;\n justify-content: center;\n height: 16px;\n border: 2px solid var(--mj-bg-surface-card);\n white-space: nowrap;\n line-height: 1;\n}\n\n.mj-list-count-badge[_ngcontent-%COMP%] {\n position: absolute;\n top: -6px;\n right: -6px;\n min-width: 16px;\n height: 16px;\n padding: 0 4px;\n font-size: 10px;\n font-weight: var(--mj-font-bold);\n color: var(--mj-text-inverse);\n background: linear-gradient(135deg, var(--mj-status-info) 0%, color-mix(in srgb, var(--mj-status-info) 85%, black) 100%);\n border-radius: var(--mj-radius-full);\n display: flex;\n align-items: center;\n justify-content: center;\n border: 2px solid var(--mj-bg-surface-card);\n}\n\n\n\n\n\n.mj-entity-hierarchy[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 4px;\n}\n\n.mj-entity-badge[_ngcontent-%COMP%] {\n display: inline-flex;\n align-items: center;\n gap: 5px;\n padding: 4px 10px;\n font-size: var(--mj-text-xs);\n font-weight: var(--mj-font-semibold);\n border-radius: var(--mj-radius-sm);\n cursor: pointer;\n transition: all var(--mj-transition-base) ease;\n text-decoration: none;\n border: 1px solid transparent;\n}\n\n.mj-entity-badge[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n font-size: 11px;\n}\n\n.mj-entity-badge--parent[_ngcontent-%COMP%] {\n background: color-mix(in srgb, var(--mj-brand-accent) 10%, var(--mj-bg-surface));\n color: var(--mj-brand-accent);\n border-color: color-mix(in srgb, var(--mj-brand-accent) 25%, transparent);\n}\n\n.mj-entity-badge--parent[_ngcontent-%COMP%]:hover {\n background: color-mix(in srgb, var(--mj-brand-accent) 18%, var(--mj-bg-surface));\n border-color: var(--mj-brand-accent);\n box-shadow: 0 2px 6px color-mix(in srgb, var(--mj-brand-accent) 15%, transparent);\n}\n\n.mj-entity-badge--current[_ngcontent-%COMP%] {\n font-weight: var(--mj-font-semibold);\n font-size: 13px;\n color: var(--mj-text-primary);\n padding: 4px 8px;\n background: var(--mj-bg-surface-hover);\n border-radius: var(--mj-radius-sm);\n cursor: default;\n}\n\n.mj-entity-badge--child[_ngcontent-%COMP%] {\n background: color-mix(in srgb, var(--mj-status-info) 10%, var(--mj-bg-surface));\n color: var(--mj-status-info);\n border-color: color-mix(in srgb, var(--mj-status-info) 25%, transparent);\n}\n\n.mj-entity-badge--child[_ngcontent-%COMP%]:hover {\n background: color-mix(in srgb, var(--mj-status-info) 18%, var(--mj-bg-surface));\n border-color: var(--mj-status-info);\n box-shadow: 0 2px 6px color-mix(in srgb, var(--mj-status-info) 15%, transparent);\n}\n\n.mj-entity-badge--overlapping[_ngcontent-%COMP%] {\n background: color-mix(in srgb, var(--mj-status-warning) 10%, var(--mj-bg-surface));\n color: color-mix(in srgb, var(--mj-status-warning) 85%, black);\n border-color: color-mix(in srgb, var(--mj-status-warning) 40%, transparent);\n}\n\n.mj-entity-badge--overlapping[_ngcontent-%COMP%]:hover {\n background: color-mix(in srgb, var(--mj-status-warning) 18%, var(--mj-bg-surface));\n border-color: color-mix(in srgb, var(--mj-status-warning) 85%, black);\n box-shadow: 0 2px 6px color-mix(in srgb, var(--mj-status-warning) 15%, transparent);\n}\n\n.mj-entity-badge-group[_ngcontent-%COMP%] {\n display: inline-flex;\n align-items: center;\n gap: 4px;\n flex-wrap: wrap;\n}\n\n.mj-child-count[_ngcontent-%COMP%] {\n background: var(--mj-status-info);\n color: var(--mj-text-inverse);\n font-size: 10px;\n padding: 1px 5px;\n border-radius: var(--mj-radius-full);\n font-weight: var(--mj-font-bold);\n}\n\n.mj-entity-badge--leaf[_ngcontent-%COMP%] {\n border-style: dashed;\n}\n\n.mj-entity-badge--root[_ngcontent-%COMP%] {\n border-style: dashed;\n}\n\n.mj-hierarchy-arrow[_ngcontent-%COMP%] {\n color: var(--mj-text-muted);\n font-size: 10px;\n}\n\n\n\n.mj-descendant-chains[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n gap: 6px;\n position: relative;\n padding-left: 20px;\n}\n\n\n\n.mj-descendant-chains[_ngcontent-%COMP%]::before {\n content: '';\n position: absolute;\n left: -4px;\n top: 50%;\n width: 4px;\n border-top: 1.5px solid var(--mj-text-muted);\n}\n\n.mj-descendant-chain[_ngcontent-%COMP%] {\n display: inline-flex;\n align-items: center;\n gap: 4px;\n position: relative;\n padding: 2px 0;\n}\n\n\n\n.mj-descendant-chain[_ngcontent-%COMP%]::before {\n content: '';\n position: absolute;\n left: -20px;\n top: 50%;\n width: 18px;\n border-top: 1.5px solid var(--mj-text-muted);\n}\n\n\n\n.mj-descendant-chain[_ngcontent-%COMP%]::after {\n content: '';\n position: absolute;\n left: -20px;\n top: 0;\n bottom: 0;\n border-left: 1.5px solid var(--mj-text-muted);\n}\n\n\n\n.mj-descendant-chain[_ngcontent-%COMP%]:first-child::after {\n top: 50%;\n}\n\n\n\n.mj-descendant-chain[_ngcontent-%COMP%]:last-child::after {\n bottom: 50%;\n}\n\n\n\n\n\n\n\n\n.mj-section-controls[_ngcontent-%COMP%] {\n gap: 6px;\n}\n\n.mj-forms-search-wrapper[_ngcontent-%COMP%] {\n position: relative;\n display: inline-flex;\n align-items: center;\n}\n\n.mj-section-search[_ngcontent-%COMP%] {\n max-width: 180px;\n font-size: 13px;\n padding: 6px 28px 6px 8px;\n border: 1px solid var(--mj-border-default);\n border-radius: var(--mj-radius-sm);\n background: var(--mj-bg-surface);\n outline: none;\n font-family: inherit;\n transition: all var(--mj-transition-base);\n}\n\n.mj-section-search[_ngcontent-%COMP%]:focus {\n border-color: var(--mj-brand-primary);\n box-shadow: 0 0 0 2px color-mix(in srgb, var(--mj-brand-primary) 10%, transparent);\n}\n\n\n\n.mj-section-search--active[_ngcontent-%COMP%] {\n background-color: color-mix(in srgb, var(--mj-status-warning) 40%, var(--mj-bg-surface));\n border-color: var(--mj-status-warning);\n color: color-mix(in srgb, var(--mj-status-warning) 80%, black);\n font-weight: var(--mj-font-semibold);\n box-shadow: 0 0 0 2px color-mix(in srgb, var(--mj-status-warning) 25%, transparent);\n}\n\n.mj-section-search[_ngcontent-%COMP%]::placeholder {\n color: var(--mj-text-muted);\n font-style: italic;\n}\n\n.mj-clear-search[_ngcontent-%COMP%] {\n position: absolute;\n right: 4px;\n padding: 4px;\n background: none;\n border: none;\n color: var(--mj-text-muted);\n cursor: pointer;\n font-size: var(--mj-text-xs);\n line-height: 1;\n}\n\n.mj-clear-search[_ngcontent-%COMP%]:hover {\n color: var(--mj-text-secondary);\n}\n\n\n\n\n\n.mj-dialog-overlay[_ngcontent-%COMP%] {\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: var(--mj-bg-overlay);\n display: flex;\n align-items: center;\n justify-content: center;\n z-index: 1000;\n animation: _ngcontent-%COMP%_mj-fade-in 150ms ease;\n}\n\n.mj-dialog[_ngcontent-%COMP%] {\n background: var(--mj-bg-surface-card);\n border-radius: var(--mj-radius-lg);\n box-shadow: var(--mj-shadow-lg);\n min-width: 400px;\n max-width: 550px;\n animation: _ngcontent-%COMP%_mj-slide-up 200ms ease;\n}\n\n.mj-dialog-header[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 16px 20px;\n border-bottom: 1px solid var(--mj-border-default);\n}\n\n.mj-dialog-header[_ngcontent-%COMP%] h3[_ngcontent-%COMP%] {\n margin: 0;\n font-size: var(--mj-text-base);\n font-weight: var(--mj-font-semibold);\n}\n\n.mj-dialog-close[_ngcontent-%COMP%] {\n background: none;\n border: none;\n color: var(--mj-text-muted);\n cursor: pointer;\n padding: 4px;\n font-size: var(--mj-text-base);\n transition: color var(--mj-transition-base);\n}\n\n.mj-dialog-close[_ngcontent-%COMP%]:hover {\n color: var(--mj-text-primary);\n}\n\n.mj-dialog-body[_ngcontent-%COMP%] {\n padding: 20px;\n font-size: var(--mj-text-sm);\n color: var(--mj-text-secondary);\n line-height: 1.6;\n}\n\n.mj-dialog-body[_ngcontent-%COMP%] p[_ngcontent-%COMP%] {\n margin: 0;\n}\n\n.mj-dialog-footer[_ngcontent-%COMP%] {\n display: flex;\n gap: 8px;\n padding: 16px 20px;\n border-top: 1px solid var(--mj-border-default);\n}\n\n@keyframes _ngcontent-%COMP%_mj-fade-in {\n from { opacity: 0; }\n to { opacity: 1; }\n}\n\n@keyframes _ngcontent-%COMP%_mj-slide-up {\n from { transform: translateY(8px); opacity: 0; }\n to { transform: translateY(0); opacity: 1; }\n}\n\n\n\n\n\n\n\n\n.mj-form-variant-picker-host[_ngcontent-%COMP%] {\n position: relative;\n display: inline-flex;\n}\n\n.mj-form-variant-picker-trigger[_ngcontent-%COMP%] {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n max-width: 240px;\n}\n\n.mj-form-variant-picker-trigger[_ngcontent-%COMP%] .chev[_ngcontent-%COMP%] {\n font-size: 10px;\n opacity: 0.7;\n}\n\n.mj-form-variant-picker-current-label[_ngcontent-%COMP%] {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n max-width: 160px;\n font-size: 12px;\n font-weight: 500;\n}\n\n.mj-form-variant-picker-host.open[_ngcontent-%COMP%] .mj-form-variant-picker-trigger[_ngcontent-%COMP%] {\n background: var(--mj-bg-surface-hover);\n border-color: var(--mj-border-focus);\n}\n\n.mj-form-variant-picker-menu[_ngcontent-%COMP%] {\n position: absolute;\n top: calc(100% + 4px);\n right: 0;\n z-index: 50;\n min-width: 280px;\n max-height: 360px;\n overflow-y: auto;\n background: var(--mj-bg-surface-elevated);\n border: 1px solid var(--mj-border-default);\n border-radius: 6px;\n box-shadow: 0 4px 16px rgba(0, 0, 0, 0.12);\n padding: 4px 0;\n}\n\n.mj-form-variant-picker-row[_ngcontent-%COMP%] {\n display: flex;\n align-items: flex-start;\n gap: 8px;\n padding: 8px 12px;\n cursor: pointer;\n color: var(--mj-text-primary);\n font-size: 13px;\n transition: background 120ms;\n}\n\n.mj-form-variant-picker-row[_ngcontent-%COMP%]:hover {\n background: var(--mj-bg-surface-hover);\n}\n\n.mj-form-variant-picker-row.current[_ngcontent-%COMP%] {\n background: color-mix(in srgb, var(--mj-brand-primary) 10%, transparent);\n}\n\n.mj-form-variant-picker-row.inactive[_ngcontent-%COMP%] {\n opacity: 0.6;\n}\n\n.mj-form-variant-picker-row-main[_ngcontent-%COMP%] {\n flex: 1;\n display: flex;\n flex-direction: column;\n gap: 2px;\n}\n\n.mj-form-variant-picker-row-title[_ngcontent-%COMP%] {\n font-weight: 600;\n}\n\n.mj-form-variant-picker-row-sub[_ngcontent-%COMP%] {\n font-size: 11px;\n color: var(--mj-text-muted);\n}\n\n.mj-form-variant-picker-default-tag[_ngcontent-%COMP%] {\n font-size: 10px;\n font-weight: 500;\n color: var(--mj-text-muted);\n margin-left: 4px;\n}\n\n.mj-form-variant-picker-default[_ngcontent-%COMP%] {\n border-bottom: 1px solid var(--mj-border-subtle);\n}\n\n.mj-form-variant-picker-check[_ngcontent-%COMP%] {\n color: var(--mj-brand-primary);\n font-size: 12px;\n margin-top: 2px;\n}"], changeDetection: 0 });
1185
1185
  }
1186
1186
  (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(MjFormToolbarComponent, [{
1187
1187
  type: Component,
1188
- args: [{ standalone: false, selector: 'mj-form-toolbar', changeDetection: ChangeDetectionStrategy.OnPush, template: "<!-- ============================================\n EDIT MODE BANNER (shows above toolbar when editing)\n ============================================ -->\n@if (EditMode && Config.ShowEditBanner) {\n <div class=\"mj-forms-edit-banner\">\n @if (DirtyFieldDisplayNames.length > 0) {\n <i class=\"fa-solid fa-circle-exclamation mj-forms-edit-banner-icon--dirty\"></i>\n <span class=\"mj-forms-edit-banner-dirty\">\n <strong>{{ DirtyFieldDisplayNames.length }}</strong>\n field{{ DirtyFieldDisplayNames.length === 1 ? '' : 's' }} modified\n &mdash; {{ DirtyFieldDisplayNames.join(', ') }}\n </span>\n } @else {\n <i class=\"fa-solid fa-pen-to-square\"></i>\n <span class=\"mj-forms-edit-banner-text\">Editing: {{ RecordDisplayName }}</span>\n }\n <div class=\"mj-forms-toolbar-spacer\"></div>\n <div class=\"mj-forms-edit-banner-actions\">\n <button class=\"mj-forms-btn mj-forms-btn--primary\" (click)=\"OnSave()\" [disabled]=\"IsSaving\" title=\"Save Changes\">\n <i class=\"fa-solid fa-floppy-disk\"></i>\n <span class=\"mj-forms-btn-text\">Save Changes</span>\n </button>\n <button class=\"mj-forms-btn\" (click)=\"OnCancel()\" [disabled]=\"IsSaving\" title=\"Discard Changes\">\n <i class=\"fa-solid fa-rotate-left\"></i>\n <span class=\"mj-forms-btn-text\">Discard</span>\n </button>\n </div>\n </div>\n}\n\n<!-- ============================================\n MAIN TOOLBAR (read mode only \u2014 hidden in edit mode)\n ============================================ -->\n@if (!EditMode) {\n <div class=\"mj-forms-toolbar\" [class.mj-forms-toolbar--sticky]=\"Config.StickyToolbar\">\n\n <!-- Action buttons -->\n <div class=\"mj-forms-toolbar-group\">\n @if (Config.ShowEditButton && UserCanEdit) {\n <button class=\"mj-forms-btn\" (click)=\"OnEdit()\" title=\"Edit this Record\">\n <i class=\"fa-solid fa-pen-to-square\"></i>\n </button>\n }\n @if (Config.ShowDeleteButton && UserCanDelete) {\n <button class=\"mj-forms-btn\" (click)=\"OnDeleteClick()\" title=\"Delete this Record\">\n <i class=\"fa-regular fa-trash-can\"></i>\n </button>\n }\n @if (Config.ShowFavoriteButton && FavoriteInitDone) {\n <button class=\"mj-forms-btn\" (click)=\"OnFavoriteToggle()\" [title]=\"IsFavorite ? 'Remove Favorite' : 'Make Favorite'\">\n <i [class]=\"IsFavorite ? 'fa-solid fa-star mj-icon--favorite' : 'fa-regular fa-star'\"></i>\n </button>\n }\n @if (Config.ShowHistoryButton && TracksChanges) {\n <button class=\"mj-forms-btn mj-forms-btn--history\" (click)=\"OnHistory()\"\n [title]=\"VersionCount > 0 ? VersionCount + ' version(s) tracked' : 'Record Changes'\">\n <i class=\"fa-regular fa-clock\"></i>\n @if (VersionCount > 0) {\n <span class=\"mj-version-count-badge\">v{{ VersionCount }}</span>\n }\n </button>\n }\n @if (Config.ShowListButton) {\n <button class=\"mj-forms-btn mj-forms-btn--list\" (click)=\"OnListManagement()\"\n [title]=\"ListCount > 0 ? 'Member of ' + ListCount + ' list(s)' : 'Add to a list'\">\n <i class=\"fa-regular fa-bookmark\"></i>\n @if (ListCount > 0) {\n <span class=\"mj-list-count-badge\">{{ ListCount }}</span>\n }\n </button>\n }\n @if (Config.ShowTagsButton) {\n <button class=\"mj-forms-btn\" (click)=\"OnTagsPanel()\"\n [title]=\"TagCount > 0 ? TagCount + ' tag(s)' : 'View tags'\">\n <i class=\"fa-solid fa-tags\"></i>\n @if (TagCount > 0) {\n <span class=\"mj-list-count-badge\">{{ TagCount }}</span>\n }\n </button>\n }\n\n <!-- Custom toolbar buttons -->\n @for (button of Config.CustomButtons; track button.Key) {\n @if (button.Visible !== false) {\n <button class=\"mj-forms-btn\"\n [class]=\"button.CssClass || ''\"\n [disabled]=\"button.Disabled || false\"\n [title]=\"button.Description\"\n (click)=\"OnCustomButtonClick(button)\">\n <i [class]=\"button.Icon\"></i>\n </button>\n }\n }\n </div>\n\n <!-- Separator before hierarchy -->\n @if (Config.ShowEntityHierarchy && IsInHierarchy) {\n <div class=\"mj-forms-toolbar-separator\"></div>\n }\n\n <!-- IS-A Entity Hierarchy Breadcrumb (bidirectional: Root \u2192 ... \u2192 Current \u2192 ... \u2192 Leaf) -->\n @if (Config.ShowEntityHierarchy && IsInHierarchy) {\n <div class=\"mj-entity-hierarchy\">\n <!-- Parent chain (root \u2192 ... \u2192 immediate parent) -->\n @for (parent of ParentChain; track parent.ID) {\n <a class=\"mj-entity-badge mj-entity-badge--parent\"\n [title]=\"'Navigate to parent: ' + parent.Name\"\n (click)=\"OnParentBadgeClick(parent, $event)\">\n <i class=\"fa-solid fa-arrow-up\"></i>\n {{ parent.Name }}\n </a>\n <span class=\"mj-hierarchy-arrow\"><i class=\"fa-solid fa-chevron-right\"></i></span>\n }\n\n <!-- Current entity (always shown) -->\n <span class=\"mj-entity-badge mj-entity-badge--current\">{{ EntityInfo?.Name }}</span>\n\n <!-- Descendant tree (recursive: inline for single paths, branching for multiple) -->\n @if (DescendantTree.length > 0) {\n <ng-container [ngTemplateOutlet]=\"descendantTreeTpl\"\n [ngTemplateOutletContext]=\"{ items: DescendantTree }\"></ng-container>\n }\n </div>\n }\n\n <!-- Spacer pushes section controls to the right -->\n <div class=\"mj-forms-toolbar-spacer\"></div>\n\n <!-- Form-variant picker: right-side toolbar button. Opens a dropdown\n with the Default (CodeGen Angular) form + every variant the host\n supplied. Persists the user's pick via the host's resolver (which\n goes through UserInfoEngine settings, not localStorage). Hidden\n entirely when the host disables ShowFormVariantPicker or supplied\n fewer than 2 variants. -->\n @if (ShowVariantPickerButton) {\n <div class=\"mj-form-variant-picker-host\"\n [class.open]=\"VariantMenuOpen\">\n <button class=\"mj-forms-btn mj-form-variant-picker-trigger\"\n type=\"button\"\n (click)=\"ToggleVariantMenu()\"\n (blur)=\"CloseVariantMenu()\"\n [title]=\"'Form variant: ' + CurrentVariantLabel + ' \u2014 click to change'\">\n <i class=\"fa-solid fa-pen-ruler\"></i>\n <span class=\"mj-form-variant-picker-current-label\">{{ CurrentVariantLabel }}</span>\n <i class=\"fa-solid fa-chevron-down chev\"></i>\n </button>\n @if (VariantMenuOpen) {\n <div class=\"mj-form-variant-picker-menu\"\n (mousedown)=\"$event.preventDefault()\"\n (click)=\"$event.stopPropagation()\">\n <div class=\"mj-form-variant-picker-row mj-form-variant-picker-default\"\n [class.current]=\"!CurrentVariantID\"\n (click)=\"OnVariantClick(null)\">\n <div class=\"mj-form-variant-picker-row-main\">\n <span class=\"mj-form-variant-picker-row-title\">\n Default form <span class=\"mj-form-variant-picker-default-tag\">(default)</span>\n </span>\n <span class=\"mj-form-variant-picker-row-sub\">CodeGen-generated Angular fallback</span>\n </div>\n @if (!CurrentVariantID) {\n <i class=\"fa-solid fa-check mj-form-variant-picker-check\"></i>\n }\n </div>\n @for (v of Variants; track v.ID) {\n <div class=\"mj-form-variant-picker-row\"\n [class.current]=\"v.ID === CurrentVariantID\"\n [class.inactive]=\"v.Status !== 'Active'\"\n (click)=\"OnVariantClick(v.ID)\">\n <div class=\"mj-form-variant-picker-row-main\">\n <span class=\"mj-form-variant-picker-row-title\">{{ v.Label }}</span>\n <span class=\"mj-form-variant-picker-row-sub\">{{ VariantSubtitle(v) }}</span>\n </div>\n @if (v.ID === CurrentVariantID) {\n <i class=\"fa-solid fa-check mj-form-variant-picker-check\"></i>\n }\n </div>\n }\n </div>\n }\n </div>\n }\n\n <!-- Section Controls -->\n @if (Config.ShowSectionControls) {\n <div class=\"mj-forms-toolbar-group mj-section-controls\">\n @if (Config.ShowSectionFilter) {\n <div class=\"mj-forms-search-wrapper\">\n <input type=\"text\"\n class=\"mj-section-search\"\n [class.mj-section-search--active]=\"SearchFilter\"\n [value]=\"SearchFilter\"\n (input)=\"OnFilterInput($event)\"\n placeholder=\"Search sections...\">\n @if (SearchFilter) {\n <button class=\"mj-clear-search\" (click)=\"OnClearFilter()\" title=\"Clear search\">\n <i class=\"fa-solid fa-xmark\"></i>\n </button>\n }\n </div>\n }\n @if (Config.ShowExpandCollapseAllButtons) {\n <button class=\"mj-forms-btn mj-forms-btn--ghost mj-forms-btn--icon-only\"\n [disabled]=\"ExpandedSectionCount >= VisibleSectionCount\"\n (click)=\"OnExpandAll()\" title=\"Expand all sections\">\n <i class=\"fa-solid fa-angles-down\"></i>\n </button>\n <button class=\"mj-forms-btn mj-forms-btn--ghost mj-forms-btn--icon-only\"\n [disabled]=\"ExpandedSectionCount === 0\"\n (click)=\"OnCollapseAll()\" title=\"Collapse all sections\">\n <i class=\"fa-solid fa-angles-up\"></i>\n </button>\n }\n @if (HasCustomSectionOrder) {\n <button class=\"mj-forms-btn mj-forms-btn--ghost mj-forms-btn--icon-only\" (click)=\"OnResetSectionOrder()\" title=\"Reset section order\">\n <i class=\"fa-solid fa-arrow-rotate-left\"></i>\n </button>\n }\n @if (Config.ShowSectionManager) {\n <button class=\"mj-forms-btn mj-forms-btn--ghost mj-forms-btn--icon-only\"\n (click)=\"OnManageSections()\" title=\"Manage section order\">\n <i class=\"fa-solid fa-bars-staggered\"></i>\n </button>\n }\n @if (Config.ShowWidthToggle) {\n <button class=\"mj-forms-btn mj-forms-btn--ghost mj-forms-btn--icon-only\"\n (click)=\"OnToggleWidthMode()\"\n [title]=\"WidthMode === 'centered' ? 'Expand to full width' : 'Center content'\">\n <i [class]=\"WidthMode === 'centered' ? 'fa-solid fa-arrows-left-right-to-line' : 'fa-solid fa-compress'\"></i>\n </button>\n }\n </div>\n }\n\n <!-- Additional actions slot -->\n @if (AdditionalActionsTemplate) {\n <ng-container [ngTemplateOutlet]=\"AdditionalActionsTemplate\"></ng-container>\n }\n <ng-content select=\"[toolbar-additional-controls]\"></ng-content>\n </div>\n}\n\n<!-- ============================================\n DISCARD CHANGES CONFIRMATION DIALOG\n ============================================ -->\n@if (ShowDiscardDialog) {\n <div class=\"mj-dialog-overlay\" (click)=\"OnDiscardCancel()\">\n <div class=\"mj-dialog\" (click)=\"$event.stopPropagation()\">\n <div class=\"mj-dialog-header\">\n <h3>Discard Changes?</h3>\n <button class=\"mj-dialog-close\" (click)=\"OnDiscardCancel()\">\n <i class=\"fa-solid fa-xmark\"></i>\n </button>\n </div>\n <div class=\"mj-dialog-body\">\n <p>You have <strong>{{ DirtyFieldDisplayNames.length }}</strong> unsaved change{{ DirtyFieldDisplayNames.length === 1 ? '' : 's' }}. Are you sure you want to discard {{ DirtyFieldDisplayNames.length === 1 ? 'it' : 'them' }}?</p>\n </div>\n <div class=\"mj-dialog-footer\">\n <button class=\"mj-forms-btn mj-forms-btn--danger\" (click)=\"OnDiscardConfirm()\">\n <i class=\"fa-solid fa-rotate-left\"></i>\n Discard Changes\n </button>\n <button class=\"mj-forms-btn\" (click)=\"OnDiscardCancel()\">\n Keep Editing\n </button>\n </div>\n </div>\n </div>\n}\n\n<!-- ============================================\n DELETE CONFIRMATION DIALOG\n ============================================ -->\n@if (ShowDeleteDialog) {\n <div class=\"mj-dialog-overlay\" (click)=\"OnDeleteCancel()\">\n <div class=\"mj-dialog\" (click)=\"$event.stopPropagation()\">\n <div class=\"mj-dialog-header\">\n <h3>Confirm Deletion</h3>\n <button class=\"mj-dialog-close\" (click)=\"OnDeleteCancel()\">\n <i class=\"fa-solid fa-xmark\"></i>\n </button>\n </div>\n <div class=\"mj-dialog-body\">\n <p>Are you sure you want to delete this record? This action cannot be undone.</p>\n </div>\n <div class=\"mj-dialog-footer\">\n <button class=\"mj-forms-btn mj-forms-btn--danger\" (click)=\"OnDeleteConfirm()\">\n <i class=\"fa-solid fa-trash-can\"></i>\n Delete\n </button>\n <button class=\"mj-forms-btn\" (click)=\"OnDeleteCancel()\">\n Cancel\n </button>\n </div>\n </div>\n </div>\n}\n\n<!-- Recursive template for IS-A descendant tree rendering.\n Single child at a level \u2192 inline with chevron.\n Multiple children at a level \u2192 tree branch with CSS connector lines. -->\n<ng-template #descendantTreeTpl let-items=\"items\">\n @if (items.length === 1) {\n <span class=\"mj-hierarchy-arrow\"><i class=\"fa-solid fa-chevron-right\"></i></span>\n <a class=\"mj-entity-badge mj-entity-badge--child\"\n [title]=\"'Navigate to: ' + items[0].EntityName\"\n (click)=\"OnDescendantBadgeClick(items[0].EntityName, $event)\">\n <i class=\"fa-solid fa-arrow-down\"></i>\n {{ items[0].EntityName }}\n </a>\n @if (items[0].Children.length > 0) {\n <ng-container [ngTemplateOutlet]=\"descendantTreeTpl\"\n [ngTemplateOutletContext]=\"{ items: items[0].Children }\"></ng-container>\n }\n } @else if (items.length > 1) {\n <div class=\"mj-descendant-chains\">\n @for (item of items; track item.EntityName) {\n <div class=\"mj-descendant-chain\">\n <a class=\"mj-entity-badge mj-entity-badge--child\"\n [title]=\"'Navigate to: ' + item.EntityName\"\n (click)=\"OnDescendantBadgeClick(item.EntityName, $event)\">\n <i class=\"fa-solid fa-arrow-down\"></i>\n {{ item.EntityName }}\n </a>\n @if (item.Children.length > 0) {\n <ng-container [ngTemplateOutlet]=\"descendantTreeTpl\"\n [ngTemplateOutletContext]=\"{ items: item.Children }\"></ng-container>\n }\n </div>\n }\n </div>\n }\n</ng-template>\n", styles: ["/* ============================================\n FORM TOOLBAR \u2014 uses global --mj-* design tokens\n ============================================ */\n:host {\n display: block;\n}\n\n/* ============================================\n EDIT BANNER\n ============================================ */\n.mj-forms-edit-banner {\n display: flex;\n align-items: center;\n gap: 10px;\n padding: 16px 16px;\n background: color-mix(in srgb, var(--mj-brand-primary) 10%, var(--mj-bg-surface));\n border-bottom: 2px solid var(--mj-brand-primary);\n font-size: 13px;\n color: var(--mj-brand-primary);\n font-weight: var(--mj-font-medium);\n}\n\n.mj-forms-edit-banner > i {\n font-size: var(--mj-text-sm);\n}\n\n.mj-forms-edit-banner-icon--dirty {\n color: var(--mj-status-warning);\n font-size: var(--mj-text-base);\n}\n\n.mj-forms-edit-banner-text {\n font-size: var(--mj-text-sm);\n}\n\n.mj-forms-edit-banner-dirty {\n font-size: var(--mj-text-sm);\n color: var(--mj-text-primary);\n}\n\n.mj-forms-edit-banner-dirty strong {\n color: var(--mj-status-warning);\n font-weight: var(--mj-font-bold);\n}\n\n.mj-forms-edit-banner-actions {\n display: flex;\n gap: 8px;\n}\n\n/* ============================================\n MAIN TOOLBAR\n ============================================ */\n.mj-forms-toolbar {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 22px 16px;\n background: var(--mj-bg-surface-card);\n border-bottom: 2px solid var(--mj-border-default);\n flex-wrap: wrap;\n z-index: 40;\n}\n\n.mj-forms-toolbar--sticky {\n position: sticky;\n top: 0;\n}\n\n.mj-forms-toolbar--disabled {\n pointer-events: none;\n opacity: 0.7;\n}\n\n.mj-forms-toolbar-group {\n display: flex;\n align-items: center;\n gap: 6px;\n}\n\n.mj-forms-toolbar-separator {\n width: 1px;\n height: 24px;\n background: var(--mj-border-default);\n margin: 0 4px;\n}\n\n.mj-forms-toolbar-spacer {\n flex: 1;\n}\n\n/* ============================================\n BUTTONS\n ============================================ */\n.mj-forms-btn {\n position: relative;\n display: inline-flex;\n align-items: center;\n gap: 6px;\n padding: 8px 16px;\n font-size: 13px;\n font-weight: var(--mj-font-medium);\n border: 1px solid var(--mj-border-default);\n background: var(--mj-bg-surface);\n color: var(--mj-text-primary);\n border-radius: var(--mj-radius-sm);\n cursor: pointer;\n transition: all var(--mj-transition-base) ease;\n white-space: nowrap;\n font-family: inherit;\n line-height: 1;\n}\n\n.mj-forms-btn:hover {\n background: var(--mj-bg-surface-hover);\n border-color: var(--mj-border-strong);\n box-shadow: var(--mj-shadow-sm);\n}\n\n.mj-forms-btn:active {\n transform: translateY(0);\n box-shadow: none;\n}\n\n.mj-forms-btn:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n}\n\n.mj-forms-btn i {\n font-size: 15px;\n line-height: 1;\n}\n\n.mj-forms-btn--primary {\n background: var(--mj-brand-primary);\n color: var(--mj-text-inverse);\n border-color: var(--mj-brand-primary);\n}\n\n.mj-forms-btn--primary:hover {\n background: var(--mj-brand-primary-hover);\n border-color: var(--mj-brand-primary-hover);\n box-shadow: 0 2px 8px color-mix(in srgb, var(--mj-brand-primary) 30%, transparent);\n}\n\n.mj-forms-btn--danger {\n color: var(--mj-status-error);\n border-color: color-mix(in srgb, var(--mj-status-error) 30%, transparent);\n background: color-mix(in srgb, var(--mj-status-error) 5%, var(--mj-bg-surface));\n}\n\n.mj-forms-btn--danger:hover {\n background: color-mix(in srgb, var(--mj-status-error) 10%, var(--mj-bg-surface));\n border-color: var(--mj-status-error);\n}\n\n.mj-forms-btn--ghost {\n border-color: transparent;\n background: transparent;\n}\n\n.mj-forms-btn--ghost:hover {\n background: color-mix(in srgb, var(--mj-brand-primary) 8%, transparent);\n border-color: transparent;\n}\n\n.mj-forms-btn--icon-only {\n padding: 7px 9px;\n}\n\n.mj-forms-btn--sm {\n padding: 5px 12px;\n font-size: var(--mj-text-xs);\n}\n\n.mj-forms-btn--sm i {\n font-size: var(--mj-text-xs);\n}\n\n.mj-forms-btn-text {\n margin-left: 2px;\n}\n\n.mj-icon--favorite {\n color: var(--mj-status-warning);\n}\n\n/* List button with count badge */\n.mj-forms-btn--list {\n position: relative;\n}\n\n/* History button with version badge */\n.mj-forms-btn--history {\n position: relative;\n}\n\n.mj-version-count-badge {\n position: absolute;\n top: -6px;\n right: -10px;\n padding: 0 5px;\n font-size: 10px;\n font-weight: var(--mj-font-bold);\n color: var(--mj-text-inverse);\n background: linear-gradient(135deg, var(--mj-status-success) 0%, color-mix(in srgb, var(--mj-status-success) 85%, black) 100%);\n border-radius: var(--mj-radius-full);\n display: flex;\n align-items: center;\n justify-content: center;\n height: 16px;\n border: 2px solid var(--mj-bg-surface-card);\n white-space: nowrap;\n line-height: 1;\n}\n\n.mj-list-count-badge {\n position: absolute;\n top: -6px;\n right: -6px;\n min-width: 16px;\n height: 16px;\n padding: 0 4px;\n font-size: 10px;\n font-weight: var(--mj-font-bold);\n color: var(--mj-text-inverse);\n background: linear-gradient(135deg, var(--mj-status-info) 0%, color-mix(in srgb, var(--mj-status-info) 85%, black) 100%);\n border-radius: var(--mj-radius-full);\n display: flex;\n align-items: center;\n justify-content: center;\n border: 2px solid var(--mj-bg-surface-card);\n}\n\n/* ============================================\n IS-A ENTITY HIERARCHY\n ============================================ */\n.mj-entity-hierarchy {\n display: flex;\n align-items: center;\n gap: 4px;\n}\n\n.mj-entity-badge {\n display: inline-flex;\n align-items: center;\n gap: 5px;\n padding: 4px 10px;\n font-size: var(--mj-text-xs);\n font-weight: var(--mj-font-semibold);\n border-radius: var(--mj-radius-sm);\n cursor: pointer;\n transition: all var(--mj-transition-base) ease;\n text-decoration: none;\n border: 1px solid transparent;\n}\n\n.mj-entity-badge i {\n font-size: 11px;\n}\n\n.mj-entity-badge--parent {\n background: color-mix(in srgb, var(--mj-color-indigo-500) 10%, var(--mj-bg-surface));\n color: var(--mj-color-indigo-500);\n border-color: color-mix(in srgb, var(--mj-color-indigo-500) 25%, transparent);\n}\n\n.mj-entity-badge--parent:hover {\n background: color-mix(in srgb, var(--mj-color-indigo-500) 18%, var(--mj-bg-surface));\n border-color: var(--mj-color-indigo-500);\n box-shadow: 0 2px 6px color-mix(in srgb, var(--mj-color-indigo-500) 15%, transparent);\n}\n\n.mj-entity-badge--current {\n font-weight: var(--mj-font-semibold);\n font-size: 13px;\n color: var(--mj-text-primary);\n padding: 4px 8px;\n background: var(--mj-bg-surface-hover);\n border-radius: var(--mj-radius-sm);\n cursor: default;\n}\n\n.mj-entity-badge--child {\n background: color-mix(in srgb, var(--mj-status-info) 10%, var(--mj-bg-surface));\n color: var(--mj-status-info);\n border-color: color-mix(in srgb, var(--mj-status-info) 25%, transparent);\n}\n\n.mj-entity-badge--child:hover {\n background: color-mix(in srgb, var(--mj-status-info) 18%, var(--mj-bg-surface));\n border-color: var(--mj-status-info);\n box-shadow: 0 2px 6px color-mix(in srgb, var(--mj-status-info) 15%, transparent);\n}\n\n.mj-entity-badge--overlapping {\n background: color-mix(in srgb, var(--mj-status-warning) 10%, var(--mj-bg-surface));\n color: color-mix(in srgb, var(--mj-status-warning) 85%, black);\n border-color: color-mix(in srgb, var(--mj-status-warning) 40%, transparent);\n}\n\n.mj-entity-badge--overlapping:hover {\n background: color-mix(in srgb, var(--mj-status-warning) 18%, var(--mj-bg-surface));\n border-color: color-mix(in srgb, var(--mj-status-warning) 85%, black);\n box-shadow: 0 2px 6px color-mix(in srgb, var(--mj-status-warning) 15%, transparent);\n}\n\n.mj-entity-badge-group {\n display: inline-flex;\n align-items: center;\n gap: 4px;\n flex-wrap: wrap;\n}\n\n.mj-child-count {\n background: var(--mj-status-info);\n color: var(--mj-text-inverse);\n font-size: 10px;\n padding: 1px 5px;\n border-radius: var(--mj-radius-full);\n font-weight: var(--mj-font-bold);\n}\n\n.mj-entity-badge--leaf {\n border-style: dashed;\n}\n\n.mj-entity-badge--root {\n border-style: dashed;\n}\n\n.mj-hierarchy-arrow {\n color: var(--mj-text-muted);\n font-size: 10px;\n}\n\n/* Descendant chain tree (connecting lines when multiple chains branch) */\n.mj-descendant-chains {\n display: flex;\n flex-direction: column;\n gap: 6px;\n position: relative;\n padding-left: 20px;\n}\n\n/* Horizontal connector from parent entity badge to tree trunk */\n.mj-descendant-chains::before {\n content: '';\n position: absolute;\n left: -4px;\n top: 50%;\n width: 4px;\n border-top: 1.5px solid var(--mj-text-muted);\n}\n\n.mj-descendant-chain {\n display: inline-flex;\n align-items: center;\n gap: 4px;\n position: relative;\n padding: 2px 0;\n}\n\n/* Horizontal branch line from trunk to chain content */\n.mj-descendant-chain::before {\n content: '';\n position: absolute;\n left: -20px;\n top: 50%;\n width: 18px;\n border-top: 1.5px solid var(--mj-text-muted);\n}\n\n/* Vertical trunk segment (each chain draws its portion) */\n.mj-descendant-chain::after {\n content: '';\n position: absolute;\n left: -20px;\n top: 0;\n bottom: 0;\n border-left: 1.5px solid var(--mj-text-muted);\n}\n\n/* First chain: trunk starts from center */\n.mj-descendant-chain:first-child::after {\n top: 50%;\n}\n\n/* Last chain: trunk ends at center */\n.mj-descendant-chain:last-child::after {\n bottom: 50%;\n}\n\n/* (dirty summary removed \u2014 now inline in edit banner) */\n\n/* ============================================\n SECTION CONTROLS\n ============================================ */\n.mj-section-controls {\n gap: 6px;\n}\n\n.mj-forms-search-wrapper {\n position: relative;\n display: inline-flex;\n align-items: center;\n}\n\n.mj-section-search {\n max-width: 180px;\n font-size: 13px;\n padding: 6px 28px 6px 8px;\n border: 1px solid var(--mj-border-default);\n border-radius: var(--mj-radius-sm);\n background: var(--mj-bg-surface);\n outline: none;\n font-family: inherit;\n transition: all var(--mj-transition-base);\n}\n\n.mj-section-search:focus {\n border-color: var(--mj-brand-primary);\n box-shadow: 0 0 0 2px color-mix(in srgb, var(--mj-brand-primary) 10%, transparent);\n}\n\n/* Active filter highlight \u2014 matches the yellow used on matched panel/field names */\n.mj-section-search--active {\n background-color: color-mix(in srgb, var(--mj-status-warning) 40%, var(--mj-bg-surface));\n border-color: var(--mj-status-warning);\n color: color-mix(in srgb, var(--mj-status-warning) 80%, black);\n font-weight: var(--mj-font-semibold);\n box-shadow: 0 0 0 2px color-mix(in srgb, var(--mj-status-warning) 25%, transparent);\n}\n\n.mj-section-search::placeholder {\n color: var(--mj-text-muted);\n font-style: italic;\n}\n\n.mj-clear-search {\n position: absolute;\n right: 4px;\n padding: 4px;\n background: none;\n border: none;\n color: var(--mj-text-muted);\n cursor: pointer;\n font-size: var(--mj-text-xs);\n line-height: 1;\n}\n\n.mj-clear-search:hover {\n color: var(--mj-text-secondary);\n}\n\n/* ============================================\n DIALOG (lightweight inline)\n ============================================ */\n.mj-dialog-overlay {\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: var(--mj-bg-overlay);\n display: flex;\n align-items: center;\n justify-content: center;\n z-index: 1000;\n animation: mj-fade-in 150ms ease;\n}\n\n.mj-dialog {\n background: var(--mj-bg-surface-card);\n border-radius: var(--mj-radius-lg);\n box-shadow: var(--mj-shadow-lg);\n min-width: 400px;\n max-width: 550px;\n animation: mj-slide-up 200ms ease;\n}\n\n.mj-dialog-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 16px 20px;\n border-bottom: 1px solid var(--mj-border-default);\n}\n\n.mj-dialog-header h3 {\n margin: 0;\n font-size: var(--mj-text-base);\n font-weight: var(--mj-font-semibold);\n}\n\n.mj-dialog-close {\n background: none;\n border: none;\n color: var(--mj-text-muted);\n cursor: pointer;\n padding: 4px;\n font-size: var(--mj-text-base);\n transition: color var(--mj-transition-base);\n}\n\n.mj-dialog-close:hover {\n color: var(--mj-text-primary);\n}\n\n.mj-dialog-body {\n padding: 20px;\n font-size: var(--mj-text-sm);\n color: var(--mj-text-secondary);\n line-height: 1.6;\n}\n\n.mj-dialog-body p {\n margin: 0;\n}\n\n.mj-dialog-footer {\n display: flex;\n gap: 8px;\n padding: 16px 20px;\n border-top: 1px solid var(--mj-border-default);\n}\n\n@keyframes mj-fade-in {\n from { opacity: 0; }\n to { opacity: 1; }\n}\n\n@keyframes mj-slide-up {\n from { transform: translateY(8px); opacity: 0; }\n to { transform: translateY(0); opacity: 1; }\n}\n\n/* ============================================================ */\n/* FORM-VARIANT PICKER (toolbar-embedded; right-side) */\n/* ============================================================ */\n\n.mj-form-variant-picker-host {\n position: relative;\n display: inline-flex;\n}\n\n.mj-form-variant-picker-trigger {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n max-width: 240px;\n}\n\n.mj-form-variant-picker-trigger .chev {\n font-size: 10px;\n opacity: 0.7;\n}\n\n.mj-form-variant-picker-current-label {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n max-width: 160px;\n font-size: 12px;\n font-weight: 500;\n}\n\n.mj-form-variant-picker-host.open .mj-form-variant-picker-trigger {\n background: var(--mj-bg-surface-hover);\n border-color: var(--mj-border-focus);\n}\n\n.mj-form-variant-picker-menu {\n position: absolute;\n top: calc(100% + 4px);\n right: 0;\n z-index: 50;\n min-width: 280px;\n max-height: 360px;\n overflow-y: auto;\n background: var(--mj-bg-surface-elevated);\n border: 1px solid var(--mj-border-default);\n border-radius: 6px;\n box-shadow: 0 4px 16px rgba(0, 0, 0, 0.12);\n padding: 4px 0;\n}\n\n.mj-form-variant-picker-row {\n display: flex;\n align-items: flex-start;\n gap: 8px;\n padding: 8px 12px;\n cursor: pointer;\n color: var(--mj-text-primary);\n font-size: 13px;\n transition: background 120ms;\n}\n\n.mj-form-variant-picker-row:hover {\n background: var(--mj-bg-surface-hover);\n}\n\n.mj-form-variant-picker-row.current {\n background: color-mix(in srgb, var(--mj-brand-primary) 10%, transparent);\n}\n\n.mj-form-variant-picker-row.inactive {\n opacity: 0.6;\n}\n\n.mj-form-variant-picker-row-main {\n flex: 1;\n display: flex;\n flex-direction: column;\n gap: 2px;\n}\n\n.mj-form-variant-picker-row-title {\n font-weight: 600;\n}\n\n.mj-form-variant-picker-row-sub {\n font-size: 11px;\n color: var(--mj-text-muted);\n}\n\n.mj-form-variant-picker-default-tag {\n font-size: 10px;\n font-weight: 500;\n color: var(--mj-text-muted);\n margin-left: 4px;\n}\n\n.mj-form-variant-picker-default {\n border-bottom: 1px solid var(--mj-border-subtle);\n}\n\n.mj-form-variant-picker-check {\n color: var(--mj-brand-primary);\n font-size: 12px;\n margin-top: 2px;\n}\n"] }]
1188
+ args: [{ standalone: false, selector: 'mj-form-toolbar', changeDetection: ChangeDetectionStrategy.OnPush, template: "<!-- ============================================\n EDIT MODE BANNER (shows above toolbar when editing)\n ============================================ -->\n@if (EditMode && Config.ShowEditBanner) {\n <div class=\"mj-forms-edit-banner\">\n @if (DirtyFieldDisplayNames.length > 0) {\n <i class=\"fa-solid fa-circle-exclamation mj-forms-edit-banner-icon--dirty\"></i>\n <span class=\"mj-forms-edit-banner-dirty\">\n <strong>{{ DirtyFieldDisplayNames.length }}</strong>\n field{{ DirtyFieldDisplayNames.length === 1 ? '' : 's' }} modified\n &mdash; {{ DirtyFieldDisplayNames.join(', ') }}\n </span>\n } @else {\n <i class=\"fa-solid fa-pen-to-square\"></i>\n <span class=\"mj-forms-edit-banner-text\">Editing: {{ RecordDisplayName }}</span>\n }\n <div class=\"mj-forms-toolbar-spacer\"></div>\n <div class=\"mj-forms-edit-banner-actions\">\n <button class=\"mj-forms-btn mj-forms-btn--primary\" (click)=\"OnSave()\" [disabled]=\"IsSaving\" title=\"Save Changes\">\n <i class=\"fa-solid fa-floppy-disk\"></i>\n <span class=\"mj-forms-btn-text\">Save Changes</span>\n </button>\n <button class=\"mj-forms-btn\" (click)=\"OnCancel()\" [disabled]=\"IsSaving\" title=\"Discard Changes\">\n <i class=\"fa-solid fa-rotate-left\"></i>\n <span class=\"mj-forms-btn-text\">Discard</span>\n </button>\n </div>\n </div>\n}\n\n<!-- ============================================\n MAIN TOOLBAR (read mode only \u2014 hidden in edit mode)\n ============================================ -->\n@if (!EditMode) {\n <div class=\"mj-forms-toolbar\" [class.mj-forms-toolbar--sticky]=\"Config.StickyToolbar\">\n\n <!-- Action buttons -->\n <div class=\"mj-forms-toolbar-group\">\n @if (Config.ShowEditButton && UserCanEdit) {\n <button class=\"mj-forms-btn\" (click)=\"OnEdit()\" title=\"Edit this Record\">\n <i class=\"fa-solid fa-pen-to-square\"></i>\n </button>\n }\n @if (Config.ShowDeleteButton && UserCanDelete) {\n <button class=\"mj-forms-btn\" (click)=\"OnDeleteClick()\" title=\"Delete this Record\">\n <i class=\"fa-regular fa-trash-can\"></i>\n </button>\n }\n @if (Config.ShowFavoriteButton && FavoriteInitDone) {\n <button class=\"mj-forms-btn\" (click)=\"OnFavoriteToggle()\" [title]=\"IsFavorite ? 'Remove Favorite' : 'Make Favorite'\">\n <i [class]=\"IsFavorite ? 'fa-solid fa-star mj-icon--favorite' : 'fa-regular fa-star'\"></i>\n </button>\n }\n @if (Config.ShowHistoryButton && TracksChanges) {\n <button class=\"mj-forms-btn mj-forms-btn--history\" (click)=\"OnHistory()\"\n [title]=\"VersionCount > 0 ? VersionCount + ' version(s) tracked' : 'Record Changes'\">\n <i class=\"fa-regular fa-clock\"></i>\n @if (VersionCount > 0) {\n <span class=\"mj-version-count-badge\">v{{ VersionCount }}</span>\n }\n </button>\n }\n @if (Config.ShowListButton) {\n <button class=\"mj-forms-btn mj-forms-btn--list\" (click)=\"OnListManagement()\"\n [title]=\"ListCount > 0 ? 'Member of ' + ListCount + ' list(s)' : 'Add to a list'\">\n <i class=\"fa-regular fa-bookmark\"></i>\n @if (ListCount > 0) {\n <span class=\"mj-list-count-badge\">{{ ListCount }}</span>\n }\n </button>\n }\n @if (Config.ShowTagsButton) {\n <button class=\"mj-forms-btn\" (click)=\"OnTagsPanel()\"\n [title]=\"TagCount > 0 ? TagCount + ' tag(s)' : 'View tags'\">\n <i class=\"fa-solid fa-tags\"></i>\n @if (TagCount > 0) {\n <span class=\"mj-list-count-badge\">{{ TagCount }}</span>\n }\n </button>\n }\n\n <!-- Custom toolbar buttons -->\n @for (button of Config.CustomButtons; track button.Key) {\n @if (button.Visible !== false) {\n <button class=\"mj-forms-btn\"\n [class]=\"button.CssClass || ''\"\n [disabled]=\"button.Disabled || false\"\n [title]=\"button.Description\"\n (click)=\"OnCustomButtonClick(button)\">\n <i [class]=\"button.Icon\"></i>\n </button>\n }\n }\n </div>\n\n <!-- Separator before hierarchy -->\n @if (Config.ShowEntityHierarchy && IsInHierarchy) {\n <div class=\"mj-forms-toolbar-separator\"></div>\n }\n\n <!-- IS-A Entity Hierarchy Breadcrumb (bidirectional: Root \u2192 ... \u2192 Current \u2192 ... \u2192 Leaf) -->\n @if (Config.ShowEntityHierarchy && IsInHierarchy) {\n <div class=\"mj-entity-hierarchy\">\n <!-- Parent chain (root \u2192 ... \u2192 immediate parent) -->\n @for (parent of ParentChain; track parent.ID) {\n <a class=\"mj-entity-badge mj-entity-badge--parent\"\n [title]=\"'Navigate to parent: ' + parent.Name\"\n (click)=\"OnParentBadgeClick(parent, $event)\">\n <i class=\"fa-solid fa-arrow-up\"></i>\n {{ parent.Name }}\n </a>\n <span class=\"mj-hierarchy-arrow\"><i class=\"fa-solid fa-chevron-right\"></i></span>\n }\n\n <!-- Current entity (always shown) -->\n <span class=\"mj-entity-badge mj-entity-badge--current\">{{ EntityInfo?.Name }}</span>\n\n <!-- Descendant tree (recursive: inline for single paths, branching for multiple) -->\n @if (DescendantTree.length > 0) {\n <ng-container [ngTemplateOutlet]=\"descendantTreeTpl\"\n [ngTemplateOutletContext]=\"{ items: DescendantTree }\"></ng-container>\n }\n </div>\n }\n\n <!-- Spacer pushes section controls to the right -->\n <div class=\"mj-forms-toolbar-spacer\"></div>\n\n <!-- Form-variant picker: right-side toolbar button. Opens a dropdown\n with the Default (CodeGen Angular) form + every variant the host\n supplied. Persists the user's pick via the host's resolver (which\n goes through UserInfoEngine settings, not localStorage). Hidden\n entirely when the host disables ShowFormVariantPicker or supplied\n fewer than 2 variants. -->\n @if (ShowVariantPickerButton) {\n <div class=\"mj-form-variant-picker-host\"\n [class.open]=\"VariantMenuOpen\">\n <button class=\"mj-forms-btn mj-form-variant-picker-trigger\"\n type=\"button\"\n (click)=\"ToggleVariantMenu()\"\n (blur)=\"CloseVariantMenu()\"\n [title]=\"'Form variant: ' + CurrentVariantLabel + ' \u2014 click to change'\">\n <i class=\"fa-solid fa-pen-ruler\"></i>\n <span class=\"mj-form-variant-picker-current-label\">{{ CurrentVariantLabel }}</span>\n <i class=\"fa-solid fa-chevron-down chev\"></i>\n </button>\n @if (VariantMenuOpen) {\n <div class=\"mj-form-variant-picker-menu\"\n (mousedown)=\"$event.preventDefault()\"\n (click)=\"$event.stopPropagation()\">\n <div class=\"mj-form-variant-picker-row mj-form-variant-picker-default\"\n [class.current]=\"!CurrentVariantID\"\n (click)=\"OnVariantClick(null)\">\n <div class=\"mj-form-variant-picker-row-main\">\n <span class=\"mj-form-variant-picker-row-title\">\n Default form <span class=\"mj-form-variant-picker-default-tag\">(default)</span>\n </span>\n <span class=\"mj-form-variant-picker-row-sub\">CodeGen-generated Angular fallback</span>\n </div>\n @if (!CurrentVariantID) {\n <i class=\"fa-solid fa-check mj-form-variant-picker-check\"></i>\n }\n </div>\n @for (v of Variants; track v.ID) {\n <div class=\"mj-form-variant-picker-row\"\n [class.current]=\"v.ID === CurrentVariantID\"\n [class.inactive]=\"v.Status !== 'Active'\"\n (click)=\"OnVariantClick(v.ID)\">\n <div class=\"mj-form-variant-picker-row-main\">\n <span class=\"mj-form-variant-picker-row-title\">{{ v.Label }}</span>\n <span class=\"mj-form-variant-picker-row-sub\">{{ VariantSubtitle(v) }}</span>\n </div>\n @if (v.ID === CurrentVariantID) {\n <i class=\"fa-solid fa-check mj-form-variant-picker-check\"></i>\n }\n </div>\n }\n </div>\n }\n </div>\n }\n\n <!-- Section Controls -->\n @if (Config.ShowSectionControls) {\n <div class=\"mj-forms-toolbar-group mj-section-controls\">\n @if (Config.ShowSectionFilter) {\n <div class=\"mj-forms-search-wrapper\">\n <input type=\"text\"\n class=\"mj-section-search\"\n [class.mj-section-search--active]=\"SearchFilter\"\n [value]=\"SearchFilter\"\n (input)=\"OnFilterInput($event)\"\n placeholder=\"Search sections...\">\n @if (SearchFilter) {\n <button class=\"mj-clear-search\" (click)=\"OnClearFilter()\" title=\"Clear search\">\n <i class=\"fa-solid fa-xmark\"></i>\n </button>\n }\n </div>\n }\n @if (Config.ShowExpandCollapseAllButtons) {\n <button class=\"mj-forms-btn mj-forms-btn--ghost mj-forms-btn--icon-only\"\n [disabled]=\"ExpandedSectionCount >= VisibleSectionCount\"\n (click)=\"OnExpandAll()\" title=\"Expand all sections\">\n <i class=\"fa-solid fa-angles-down\"></i>\n </button>\n <button class=\"mj-forms-btn mj-forms-btn--ghost mj-forms-btn--icon-only\"\n [disabled]=\"ExpandedSectionCount === 0\"\n (click)=\"OnCollapseAll()\" title=\"Collapse all sections\">\n <i class=\"fa-solid fa-angles-up\"></i>\n </button>\n }\n @if (HasCustomSectionOrder) {\n <button class=\"mj-forms-btn mj-forms-btn--ghost mj-forms-btn--icon-only\" (click)=\"OnResetSectionOrder()\" title=\"Reset section order\">\n <i class=\"fa-solid fa-arrow-rotate-left\"></i>\n </button>\n }\n @if (Config.ShowSectionManager) {\n <button class=\"mj-forms-btn mj-forms-btn--ghost mj-forms-btn--icon-only\"\n (click)=\"OnManageSections()\" title=\"Manage section order\">\n <i class=\"fa-solid fa-bars-staggered\"></i>\n </button>\n }\n @if (Config.ShowWidthToggle) {\n <button class=\"mj-forms-btn mj-forms-btn--ghost mj-forms-btn--icon-only\"\n (click)=\"OnToggleWidthMode()\"\n [title]=\"WidthMode === 'centered' ? 'Expand to full width' : 'Center content'\">\n <i [class]=\"WidthMode === 'centered' ? 'fa-solid fa-arrows-left-right-to-line' : 'fa-solid fa-compress'\"></i>\n </button>\n }\n </div>\n }\n\n <!-- Additional actions slot -->\n @if (AdditionalActionsTemplate) {\n <ng-container [ngTemplateOutlet]=\"AdditionalActionsTemplate\"></ng-container>\n }\n <ng-content select=\"[toolbar-additional-controls]\"></ng-content>\n </div>\n}\n\n<!-- ============================================\n DISCARD CHANGES CONFIRMATION DIALOG\n ============================================ -->\n@if (ShowDiscardDialog) {\n <div class=\"mj-dialog-overlay\" (click)=\"OnDiscardCancel()\">\n <div class=\"mj-dialog\" (click)=\"$event.stopPropagation()\">\n <div class=\"mj-dialog-header\">\n <h3>Discard Changes?</h3>\n <button class=\"mj-dialog-close\" (click)=\"OnDiscardCancel()\">\n <i class=\"fa-solid fa-xmark\"></i>\n </button>\n </div>\n <div class=\"mj-dialog-body\">\n <p>You have <strong>{{ DirtyFieldDisplayNames.length }}</strong> unsaved change{{ DirtyFieldDisplayNames.length === 1 ? '' : 's' }}. Are you sure you want to discard {{ DirtyFieldDisplayNames.length === 1 ? 'it' : 'them' }}?</p>\n </div>\n <div class=\"mj-dialog-footer\">\n <button class=\"mj-forms-btn mj-forms-btn--danger\" (click)=\"OnDiscardConfirm()\">\n <i class=\"fa-solid fa-rotate-left\"></i>\n Discard Changes\n </button>\n <button class=\"mj-forms-btn\" (click)=\"OnDiscardCancel()\">\n Keep Editing\n </button>\n </div>\n </div>\n </div>\n}\n\n<!-- ============================================\n DELETE CONFIRMATION DIALOG\n ============================================ -->\n@if (ShowDeleteDialog) {\n <div class=\"mj-dialog-overlay\" (click)=\"OnDeleteCancel()\">\n <div class=\"mj-dialog\" (click)=\"$event.stopPropagation()\">\n <div class=\"mj-dialog-header\">\n <h3>Confirm Deletion</h3>\n <button class=\"mj-dialog-close\" (click)=\"OnDeleteCancel()\">\n <i class=\"fa-solid fa-xmark\"></i>\n </button>\n </div>\n <div class=\"mj-dialog-body\">\n <p>Are you sure you want to delete this record? This action cannot be undone.</p>\n </div>\n <div class=\"mj-dialog-footer\">\n <button class=\"mj-forms-btn mj-forms-btn--danger\" (click)=\"OnDeleteConfirm()\">\n <i class=\"fa-solid fa-trash-can\"></i>\n Delete\n </button>\n <button class=\"mj-forms-btn\" (click)=\"OnDeleteCancel()\">\n Cancel\n </button>\n </div>\n </div>\n </div>\n}\n\n<!-- Recursive template for IS-A descendant tree rendering.\n Single child at a level \u2192 inline with chevron.\n Multiple children at a level \u2192 tree branch with CSS connector lines. -->\n<ng-template #descendantTreeTpl let-items=\"items\">\n @if (items.length === 1) {\n <span class=\"mj-hierarchy-arrow\"><i class=\"fa-solid fa-chevron-right\"></i></span>\n <a class=\"mj-entity-badge mj-entity-badge--child\"\n [title]=\"'Navigate to: ' + items[0].EntityName\"\n (click)=\"OnDescendantBadgeClick(items[0].EntityName, $event)\">\n <i class=\"fa-solid fa-arrow-down\"></i>\n {{ items[0].EntityName }}\n </a>\n @if (items[0].Children.length > 0) {\n <ng-container [ngTemplateOutlet]=\"descendantTreeTpl\"\n [ngTemplateOutletContext]=\"{ items: items[0].Children }\"></ng-container>\n }\n } @else if (items.length > 1) {\n <div class=\"mj-descendant-chains\">\n @for (item of items; track item.EntityName) {\n <div class=\"mj-descendant-chain\">\n <a class=\"mj-entity-badge mj-entity-badge--child\"\n [title]=\"'Navigate to: ' + item.EntityName\"\n (click)=\"OnDescendantBadgeClick(item.EntityName, $event)\">\n <i class=\"fa-solid fa-arrow-down\"></i>\n {{ item.EntityName }}\n </a>\n @if (item.Children.length > 0) {\n <ng-container [ngTemplateOutlet]=\"descendantTreeTpl\"\n [ngTemplateOutletContext]=\"{ items: item.Children }\"></ng-container>\n }\n </div>\n }\n </div>\n }\n</ng-template>\n", styles: ["/* ============================================\n FORM TOOLBAR \u2014 uses global --mj-* design tokens\n ============================================ */\n:host {\n display: block;\n}\n\n/* ============================================\n EDIT BANNER\n ============================================ */\n.mj-forms-edit-banner {\n display: flex;\n align-items: center;\n gap: 10px;\n padding: 16px 16px;\n background: color-mix(in srgb, var(--mj-brand-primary) 10%, var(--mj-bg-surface));\n border-bottom: 2px solid var(--mj-brand-primary);\n font-size: 13px;\n color: var(--mj-brand-primary);\n font-weight: var(--mj-font-medium);\n}\n\n.mj-forms-edit-banner > i {\n font-size: var(--mj-text-sm);\n}\n\n.mj-forms-edit-banner-icon--dirty {\n color: var(--mj-status-warning);\n font-size: var(--mj-text-base);\n}\n\n.mj-forms-edit-banner-text {\n font-size: var(--mj-text-sm);\n}\n\n.mj-forms-edit-banner-dirty {\n font-size: var(--mj-text-sm);\n color: var(--mj-text-primary);\n}\n\n.mj-forms-edit-banner-dirty strong {\n color: var(--mj-status-warning);\n font-weight: var(--mj-font-bold);\n}\n\n.mj-forms-edit-banner-actions {\n display: flex;\n gap: 8px;\n}\n\n/* ============================================\n MAIN TOOLBAR\n ============================================ */\n.mj-forms-toolbar {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 22px 16px;\n background: var(--mj-bg-surface-card);\n border-bottom: 2px solid var(--mj-border-default);\n flex-wrap: wrap;\n z-index: 40;\n}\n\n.mj-forms-toolbar--sticky {\n position: sticky;\n top: 0;\n}\n\n.mj-forms-toolbar--disabled {\n pointer-events: none;\n opacity: 0.7;\n}\n\n.mj-forms-toolbar-group {\n display: flex;\n align-items: center;\n gap: 6px;\n}\n\n.mj-forms-toolbar-separator {\n width: 1px;\n height: 24px;\n background: var(--mj-border-default);\n margin: 0 4px;\n}\n\n.mj-forms-toolbar-spacer {\n flex: 1;\n}\n\n/* ============================================\n BUTTONS\n ============================================ */\n.mj-forms-btn {\n position: relative;\n display: inline-flex;\n align-items: center;\n gap: 6px;\n padding: 8px 16px;\n font-size: 13px;\n font-weight: var(--mj-font-medium);\n border: 1px solid var(--mj-border-default);\n background: var(--mj-bg-surface);\n color: var(--mj-text-primary);\n border-radius: var(--mj-radius-sm);\n cursor: pointer;\n transition: all var(--mj-transition-base) ease;\n white-space: nowrap;\n font-family: inherit;\n line-height: 1;\n}\n\n.mj-forms-btn:hover {\n background: var(--mj-bg-surface-hover);\n border-color: var(--mj-border-strong);\n box-shadow: var(--mj-shadow-sm);\n}\n\n.mj-forms-btn:active {\n transform: translateY(0);\n box-shadow: none;\n}\n\n.mj-forms-btn:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n}\n\n.mj-forms-btn i {\n font-size: 15px;\n line-height: 1;\n}\n\n.mj-forms-btn--primary {\n background: var(--mj-brand-primary);\n color: var(--mj-text-inverse);\n border-color: var(--mj-brand-primary);\n}\n\n.mj-forms-btn--primary:hover {\n background: var(--mj-brand-primary-hover);\n border-color: var(--mj-brand-primary-hover);\n box-shadow: 0 2px 8px color-mix(in srgb, var(--mj-brand-primary) 30%, transparent);\n}\n\n.mj-forms-btn--danger {\n color: var(--mj-status-error);\n border-color: color-mix(in srgb, var(--mj-status-error) 30%, transparent);\n background: color-mix(in srgb, var(--mj-status-error) 5%, var(--mj-bg-surface));\n}\n\n.mj-forms-btn--danger:hover {\n background: color-mix(in srgb, var(--mj-status-error) 10%, var(--mj-bg-surface));\n border-color: var(--mj-status-error);\n}\n\n.mj-forms-btn--ghost {\n border-color: transparent;\n background: transparent;\n}\n\n.mj-forms-btn--ghost:hover {\n background: color-mix(in srgb, var(--mj-brand-primary) 8%, transparent);\n border-color: transparent;\n}\n\n.mj-forms-btn--icon-only {\n padding: 7px 9px;\n}\n\n.mj-forms-btn--sm {\n padding: 5px 12px;\n font-size: var(--mj-text-xs);\n}\n\n.mj-forms-btn--sm i {\n font-size: var(--mj-text-xs);\n}\n\n.mj-forms-btn-text {\n margin-left: 2px;\n}\n\n.mj-icon--favorite {\n color: var(--mj-status-warning);\n}\n\n/* List button with count badge */\n.mj-forms-btn--list {\n position: relative;\n}\n\n/* History button with version badge */\n.mj-forms-btn--history {\n position: relative;\n}\n\n.mj-version-count-badge {\n position: absolute;\n top: -6px;\n right: -10px;\n padding: 0 5px;\n font-size: 10px;\n font-weight: var(--mj-font-bold);\n color: var(--mj-text-inverse);\n background: linear-gradient(135deg, var(--mj-status-success) 0%, color-mix(in srgb, var(--mj-status-success) 85%, black) 100%);\n border-radius: var(--mj-radius-full);\n display: flex;\n align-items: center;\n justify-content: center;\n height: 16px;\n border: 2px solid var(--mj-bg-surface-card);\n white-space: nowrap;\n line-height: 1;\n}\n\n.mj-list-count-badge {\n position: absolute;\n top: -6px;\n right: -6px;\n min-width: 16px;\n height: 16px;\n padding: 0 4px;\n font-size: 10px;\n font-weight: var(--mj-font-bold);\n color: var(--mj-text-inverse);\n background: linear-gradient(135deg, var(--mj-status-info) 0%, color-mix(in srgb, var(--mj-status-info) 85%, black) 100%);\n border-radius: var(--mj-radius-full);\n display: flex;\n align-items: center;\n justify-content: center;\n border: 2px solid var(--mj-bg-surface-card);\n}\n\n/* ============================================\n IS-A ENTITY HIERARCHY\n ============================================ */\n.mj-entity-hierarchy {\n display: flex;\n align-items: center;\n gap: 4px;\n}\n\n.mj-entity-badge {\n display: inline-flex;\n align-items: center;\n gap: 5px;\n padding: 4px 10px;\n font-size: var(--mj-text-xs);\n font-weight: var(--mj-font-semibold);\n border-radius: var(--mj-radius-sm);\n cursor: pointer;\n transition: all var(--mj-transition-base) ease;\n text-decoration: none;\n border: 1px solid transparent;\n}\n\n.mj-entity-badge i {\n font-size: 11px;\n}\n\n.mj-entity-badge--parent {\n background: color-mix(in srgb, var(--mj-brand-accent) 10%, var(--mj-bg-surface));\n color: var(--mj-brand-accent);\n border-color: color-mix(in srgb, var(--mj-brand-accent) 25%, transparent);\n}\n\n.mj-entity-badge--parent:hover {\n background: color-mix(in srgb, var(--mj-brand-accent) 18%, var(--mj-bg-surface));\n border-color: var(--mj-brand-accent);\n box-shadow: 0 2px 6px color-mix(in srgb, var(--mj-brand-accent) 15%, transparent);\n}\n\n.mj-entity-badge--current {\n font-weight: var(--mj-font-semibold);\n font-size: 13px;\n color: var(--mj-text-primary);\n padding: 4px 8px;\n background: var(--mj-bg-surface-hover);\n border-radius: var(--mj-radius-sm);\n cursor: default;\n}\n\n.mj-entity-badge--child {\n background: color-mix(in srgb, var(--mj-status-info) 10%, var(--mj-bg-surface));\n color: var(--mj-status-info);\n border-color: color-mix(in srgb, var(--mj-status-info) 25%, transparent);\n}\n\n.mj-entity-badge--child:hover {\n background: color-mix(in srgb, var(--mj-status-info) 18%, var(--mj-bg-surface));\n border-color: var(--mj-status-info);\n box-shadow: 0 2px 6px color-mix(in srgb, var(--mj-status-info) 15%, transparent);\n}\n\n.mj-entity-badge--overlapping {\n background: color-mix(in srgb, var(--mj-status-warning) 10%, var(--mj-bg-surface));\n color: color-mix(in srgb, var(--mj-status-warning) 85%, black);\n border-color: color-mix(in srgb, var(--mj-status-warning) 40%, transparent);\n}\n\n.mj-entity-badge--overlapping:hover {\n background: color-mix(in srgb, var(--mj-status-warning) 18%, var(--mj-bg-surface));\n border-color: color-mix(in srgb, var(--mj-status-warning) 85%, black);\n box-shadow: 0 2px 6px color-mix(in srgb, var(--mj-status-warning) 15%, transparent);\n}\n\n.mj-entity-badge-group {\n display: inline-flex;\n align-items: center;\n gap: 4px;\n flex-wrap: wrap;\n}\n\n.mj-child-count {\n background: var(--mj-status-info);\n color: var(--mj-text-inverse);\n font-size: 10px;\n padding: 1px 5px;\n border-radius: var(--mj-radius-full);\n font-weight: var(--mj-font-bold);\n}\n\n.mj-entity-badge--leaf {\n border-style: dashed;\n}\n\n.mj-entity-badge--root {\n border-style: dashed;\n}\n\n.mj-hierarchy-arrow {\n color: var(--mj-text-muted);\n font-size: 10px;\n}\n\n/* Descendant chain tree (connecting lines when multiple chains branch) */\n.mj-descendant-chains {\n display: flex;\n flex-direction: column;\n gap: 6px;\n position: relative;\n padding-left: 20px;\n}\n\n/* Horizontal connector from parent entity badge to tree trunk */\n.mj-descendant-chains::before {\n content: '';\n position: absolute;\n left: -4px;\n top: 50%;\n width: 4px;\n border-top: 1.5px solid var(--mj-text-muted);\n}\n\n.mj-descendant-chain {\n display: inline-flex;\n align-items: center;\n gap: 4px;\n position: relative;\n padding: 2px 0;\n}\n\n/* Horizontal branch line from trunk to chain content */\n.mj-descendant-chain::before {\n content: '';\n position: absolute;\n left: -20px;\n top: 50%;\n width: 18px;\n border-top: 1.5px solid var(--mj-text-muted);\n}\n\n/* Vertical trunk segment (each chain draws its portion) */\n.mj-descendant-chain::after {\n content: '';\n position: absolute;\n left: -20px;\n top: 0;\n bottom: 0;\n border-left: 1.5px solid var(--mj-text-muted);\n}\n\n/* First chain: trunk starts from center */\n.mj-descendant-chain:first-child::after {\n top: 50%;\n}\n\n/* Last chain: trunk ends at center */\n.mj-descendant-chain:last-child::after {\n bottom: 50%;\n}\n\n/* (dirty summary removed \u2014 now inline in edit banner) */\n\n/* ============================================\n SECTION CONTROLS\n ============================================ */\n.mj-section-controls {\n gap: 6px;\n}\n\n.mj-forms-search-wrapper {\n position: relative;\n display: inline-flex;\n align-items: center;\n}\n\n.mj-section-search {\n max-width: 180px;\n font-size: 13px;\n padding: 6px 28px 6px 8px;\n border: 1px solid var(--mj-border-default);\n border-radius: var(--mj-radius-sm);\n background: var(--mj-bg-surface);\n outline: none;\n font-family: inherit;\n transition: all var(--mj-transition-base);\n}\n\n.mj-section-search:focus {\n border-color: var(--mj-brand-primary);\n box-shadow: 0 0 0 2px color-mix(in srgb, var(--mj-brand-primary) 10%, transparent);\n}\n\n/* Active filter highlight \u2014 matches the yellow used on matched panel/field names */\n.mj-section-search--active {\n background-color: color-mix(in srgb, var(--mj-status-warning) 40%, var(--mj-bg-surface));\n border-color: var(--mj-status-warning);\n color: color-mix(in srgb, var(--mj-status-warning) 80%, black);\n font-weight: var(--mj-font-semibold);\n box-shadow: 0 0 0 2px color-mix(in srgb, var(--mj-status-warning) 25%, transparent);\n}\n\n.mj-section-search::placeholder {\n color: var(--mj-text-muted);\n font-style: italic;\n}\n\n.mj-clear-search {\n position: absolute;\n right: 4px;\n padding: 4px;\n background: none;\n border: none;\n color: var(--mj-text-muted);\n cursor: pointer;\n font-size: var(--mj-text-xs);\n line-height: 1;\n}\n\n.mj-clear-search:hover {\n color: var(--mj-text-secondary);\n}\n\n/* ============================================\n DIALOG (lightweight inline)\n ============================================ */\n.mj-dialog-overlay {\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: var(--mj-bg-overlay);\n display: flex;\n align-items: center;\n justify-content: center;\n z-index: 1000;\n animation: mj-fade-in 150ms ease;\n}\n\n.mj-dialog {\n background: var(--mj-bg-surface-card);\n border-radius: var(--mj-radius-lg);\n box-shadow: var(--mj-shadow-lg);\n min-width: 400px;\n max-width: 550px;\n animation: mj-slide-up 200ms ease;\n}\n\n.mj-dialog-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 16px 20px;\n border-bottom: 1px solid var(--mj-border-default);\n}\n\n.mj-dialog-header h3 {\n margin: 0;\n font-size: var(--mj-text-base);\n font-weight: var(--mj-font-semibold);\n}\n\n.mj-dialog-close {\n background: none;\n border: none;\n color: var(--mj-text-muted);\n cursor: pointer;\n padding: 4px;\n font-size: var(--mj-text-base);\n transition: color var(--mj-transition-base);\n}\n\n.mj-dialog-close:hover {\n color: var(--mj-text-primary);\n}\n\n.mj-dialog-body {\n padding: 20px;\n font-size: var(--mj-text-sm);\n color: var(--mj-text-secondary);\n line-height: 1.6;\n}\n\n.mj-dialog-body p {\n margin: 0;\n}\n\n.mj-dialog-footer {\n display: flex;\n gap: 8px;\n padding: 16px 20px;\n border-top: 1px solid var(--mj-border-default);\n}\n\n@keyframes mj-fade-in {\n from { opacity: 0; }\n to { opacity: 1; }\n}\n\n@keyframes mj-slide-up {\n from { transform: translateY(8px); opacity: 0; }\n to { transform: translateY(0); opacity: 1; }\n}\n\n/* ============================================================ */\n/* FORM-VARIANT PICKER (toolbar-embedded; right-side) */\n/* ============================================================ */\n\n.mj-form-variant-picker-host {\n position: relative;\n display: inline-flex;\n}\n\n.mj-form-variant-picker-trigger {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n max-width: 240px;\n}\n\n.mj-form-variant-picker-trigger .chev {\n font-size: 10px;\n opacity: 0.7;\n}\n\n.mj-form-variant-picker-current-label {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n max-width: 160px;\n font-size: 12px;\n font-weight: 500;\n}\n\n.mj-form-variant-picker-host.open .mj-form-variant-picker-trigger {\n background: var(--mj-bg-surface-hover);\n border-color: var(--mj-border-focus);\n}\n\n.mj-form-variant-picker-menu {\n position: absolute;\n top: calc(100% + 4px);\n right: 0;\n z-index: 50;\n min-width: 280px;\n max-height: 360px;\n overflow-y: auto;\n background: var(--mj-bg-surface-elevated);\n border: 1px solid var(--mj-border-default);\n border-radius: 6px;\n box-shadow: 0 4px 16px rgba(0, 0, 0, 0.12);\n padding: 4px 0;\n}\n\n.mj-form-variant-picker-row {\n display: flex;\n align-items: flex-start;\n gap: 8px;\n padding: 8px 12px;\n cursor: pointer;\n color: var(--mj-text-primary);\n font-size: 13px;\n transition: background 120ms;\n}\n\n.mj-form-variant-picker-row:hover {\n background: var(--mj-bg-surface-hover);\n}\n\n.mj-form-variant-picker-row.current {\n background: color-mix(in srgb, var(--mj-brand-primary) 10%, transparent);\n}\n\n.mj-form-variant-picker-row.inactive {\n opacity: 0.6;\n}\n\n.mj-form-variant-picker-row-main {\n flex: 1;\n display: flex;\n flex-direction: column;\n gap: 2px;\n}\n\n.mj-form-variant-picker-row-title {\n font-weight: 600;\n}\n\n.mj-form-variant-picker-row-sub {\n font-size: 11px;\n color: var(--mj-text-muted);\n}\n\n.mj-form-variant-picker-default-tag {\n font-size: 10px;\n font-weight: 500;\n color: var(--mj-text-muted);\n margin-left: 4px;\n}\n\n.mj-form-variant-picker-default {\n border-bottom: 1px solid var(--mj-border-subtle);\n}\n\n.mj-form-variant-picker-check {\n color: var(--mj-brand-primary);\n font-size: 12px;\n margin-top: 2px;\n}\n"] }]
1189
1189
  }], null, { Form: [{
1190
1190
  type: Input
1191
1191
  }], _deprecatedForm: [{