@memberjunction/ng-base-forms 6.1.0-edge.4 → 6.1.0-edge.6

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 (53) hide show
  1. package/dist/lib/chrome/form-chrome-rail-pref.d.ts +22 -0
  2. package/dist/lib/chrome/form-chrome-rail-pref.d.ts.map +1 -1
  3. package/dist/lib/chrome/form-chrome-rail-pref.js +29 -0
  4. package/dist/lib/chrome/form-chrome-rail-pref.js.map +1 -1
  5. package/dist/lib/chrome/form-chrome.d.ts +19 -0
  6. package/dist/lib/chrome/form-chrome.d.ts.map +1 -1
  7. package/dist/lib/chrome/form-chrome.js +26 -0
  8. package/dist/lib/chrome/form-chrome.js.map +1 -1
  9. package/dist/lib/container/record-form-container.component.d.ts +52 -2
  10. package/dist/lib/container/record-form-container.component.d.ts.map +1 -1
  11. package/dist/lib/container/record-form-container.component.js +485 -161
  12. package/dist/lib/container/record-form-container.component.js.map +1 -1
  13. package/dist/lib/explorer-entity-data-grid.component.d.ts +36 -0
  14. package/dist/lib/explorer-entity-data-grid.component.d.ts.map +1 -1
  15. package/dist/lib/explorer-entity-data-grid.component.js +112 -3
  16. package/dist/lib/explorer-entity-data-grid.component.js.map +1 -1
  17. package/dist/lib/field/form-field.component.d.ts +59 -0
  18. package/dist/lib/field/form-field.component.d.ts.map +1 -1
  19. package/dist/lib/field/form-field.component.js +710 -294
  20. package/dist/lib/field/form-field.component.js.map +1 -1
  21. package/dist/lib/overlays/base-form-overlay.d.ts.map +1 -1
  22. package/dist/lib/overlays/base-form-overlay.js +3 -1
  23. package/dist/lib/overlays/base-form-overlay.js.map +1 -1
  24. package/dist/lib/overlays/form-slide-in.component.js +10 -11
  25. package/dist/lib/overlays/form-slide-in.component.js.map +1 -1
  26. package/dist/lib/panel/collapsible-panel.component.d.ts +84 -3
  27. package/dist/lib/panel/collapsible-panel.component.d.ts.map +1 -1
  28. package/dist/lib/panel/collapsible-panel.component.js +253 -37
  29. package/dist/lib/panel/collapsible-panel.component.js.map +1 -1
  30. package/dist/lib/panel-slot/base-form-panel.d.ts +12 -0
  31. package/dist/lib/panel-slot/base-form-panel.d.ts.map +1 -1
  32. package/dist/lib/panel-slot/base-form-panel.js.map +1 -1
  33. package/dist/lib/related-grid-height.d.ts +48 -11
  34. package/dist/lib/related-grid-height.d.ts.map +1 -1
  35. package/dist/lib/related-grid-height.js +53 -12
  36. package/dist/lib/related-grid-height.js.map +1 -1
  37. package/dist/lib/section-indicators/form-section-indicator-coordinator.service.d.ts +80 -0
  38. package/dist/lib/section-indicators/form-section-indicator-coordinator.service.d.ts.map +1 -0
  39. package/dist/lib/section-indicators/form-section-indicator-coordinator.service.js +99 -0
  40. package/dist/lib/section-indicators/form-section-indicator-coordinator.service.js.map +1 -0
  41. package/dist/lib/section-indicators/form-section-indicators.d.ts +97 -0
  42. package/dist/lib/section-indicators/form-section-indicators.d.ts.map +1 -0
  43. package/dist/lib/section-indicators/form-section-indicators.js +132 -0
  44. package/dist/lib/section-indicators/form-section-indicators.js.map +1 -0
  45. package/dist/lib/section-manager/section-manager.component.js +2 -2
  46. package/dist/lib/section-manager/section-manager.component.js.map +1 -1
  47. package/dist/lib/toolbar/form-toolbar.component.js +4 -4
  48. package/dist/lib/toolbar/form-toolbar.component.js.map +1 -1
  49. package/dist/public-api.d.ts +2 -0
  50. package/dist/public-api.d.ts.map +1 -1
  51. package/dist/public-api.js +3 -0
  52. package/dist/public-api.js.map +1 -1
  53. package/package.json +25 -27
@@ -0,0 +1,99 @@
1
+ import { Injectable } from '@angular/core';
2
+ import { Subject } from 'rxjs';
3
+ import { EMPTY_SECTION_INDICATORS, ParseValidationSource, SumSectionIndicators, } from './form-section-indicators';
4
+ import * as i0 from "@angular/core";
5
+ /**
6
+ * Per-container registry of section indicator sources — the join between "a rail
7
+ * group" and "the sections it fronts", read live rather than pushed.
8
+ *
9
+ * **Pull, not push.** Every query asks the registered sources for their current
10
+ * counts. Nothing is cached here and nothing is written from inside a change-detection
11
+ * pass, which is what keeps the rail free of `ExpressionChangedAfterItHasBeenChecked`
12
+ * errors: within one pass a field's dirty / error state is constant (it mutates in
13
+ * event handlers), so every reader in that pass sees the same answer.
14
+ *
15
+ * Provided by `<mj-record-form-container>` via `providers` (not `viewProviders`), so
16
+ * projected panels and slot-mounted `BaseFormPanel`s reach the same instance.
17
+ */
18
+ export class FormSectionIndicatorCoordinator {
19
+ /** Insertion-ordered so totals are deterministic. */
20
+ sources = new Map();
21
+ /**
22
+ * Fires when a source registers / leaves, or when a source reports that its state
23
+ * changed (a field edit). The container marks itself for check on it so an `OnPush`
24
+ * rail re-reads the counts on the next pass instead of waiting for its poll.
25
+ */
26
+ Changes = new Subject();
27
+ /** Declare (or re-declare) a section. A second source for the same key replaces the first. */
28
+ Register(source) {
29
+ const key = source.SectionKey;
30
+ if (!key)
31
+ return;
32
+ if (this.sources.get(key) === source)
33
+ return;
34
+ this.sources.set(key, source);
35
+ this.Changes.next();
36
+ }
37
+ /** Remove a source. Keyed on identity, so a replaced source cannot evict its replacement. */
38
+ Unregister(source, previousKey) {
39
+ const key = previousKey ?? source.SectionKey;
40
+ if (this.sources.get(key) === source) {
41
+ this.sources.delete(key);
42
+ this.Changes.next();
43
+ }
44
+ }
45
+ /** A source's state changed (field edited, error cleared). Nudges OnPush readers. */
46
+ NotifyChanged() {
47
+ this.Changes.next();
48
+ }
49
+ /** Section keys currently registered, in registration order. */
50
+ get RegisteredSectionKeys() {
51
+ return [...this.sources.keys()];
52
+ }
53
+ /** Whether a section is registered. */
54
+ Has(sectionKey) {
55
+ return this.sources.has(sectionKey);
56
+ }
57
+ /** Current indicators for one section; empty when nothing is registered under that key. */
58
+ IndicatorsFor(sectionKey) {
59
+ const source = this.sources.get(sectionKey);
60
+ return source ? source.GetSectionIndicators() : { ...EMPTY_SECTION_INDICATORS };
61
+ }
62
+ /**
63
+ * Indicators summed over several section keys — what a rail group needs, since one
64
+ * group (notably `Details`) fronts many panels.
65
+ */
66
+ IndicatorsForKeys(sectionKeys) {
67
+ return SumSectionIndicators(...sectionKeys.map((key) => this.IndicatorsFor(key)));
68
+ }
69
+ /** Indicators summed over every registered section. */
70
+ get TotalIndicators() {
71
+ return SumSectionIndicators(...[...this.sources.values()].map((source) => source.GetSectionIndicators()));
72
+ }
73
+ /**
74
+ * Form-level errors no registered section claims. Surfaced deliberately: an error that
75
+ * vanished from every badge is worse than the no-badge behaviour this replaced.
76
+ *
77
+ * `reachableKeys`, when given, limits WHICH sources may claim: a section that is
78
+ * registered but sits in no rail group (hidden by `hiddenSectionKeys`, dropped by the
79
+ * chrome) has no badge anywhere, so letting it claim would make its failure vanish.
80
+ * Its errors stay unrouted and land in the whole-form total instead.
81
+ */
82
+ UnroutedValidationErrors(errors, reachableKeys) {
83
+ if (!errors?.length)
84
+ return [];
85
+ const sources = [...this.sources.values()].filter((source) => !reachableKeys || reachableKeys.has(source.SectionKey));
86
+ return errors.filter((error) => {
87
+ const parsed = ParseValidationSource(error.Source);
88
+ if (!parsed.Source)
89
+ return false;
90
+ return !sources.some((source) => source.OwnsValidationSource(parsed));
91
+ });
92
+ }
93
+ static ɵfac = function FormSectionIndicatorCoordinator_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || FormSectionIndicatorCoordinator)(); };
94
+ static ɵprov = /*@__PURE__*/ i0.ɵɵdefineInjectable({ token: FormSectionIndicatorCoordinator, factory: FormSectionIndicatorCoordinator.ɵfac });
95
+ }
96
+ (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(FormSectionIndicatorCoordinator, [{
97
+ type: Injectable
98
+ }], null, null); })();
99
+ //# sourceMappingURL=form-section-indicator-coordinator.service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"form-section-indicator-coordinator.service.js","sourceRoot":"","sources":["../../../src/lib/section-indicators/form-section-indicator-coordinator.service.ts"],"names":[],"mappings":"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","sourcesContent":["import { Injectable } from '@angular/core';\nimport { Subject } from 'rxjs';\nimport { ValidationErrorInfo } from '@memberjunction/global';\nimport {\n EMPTY_SECTION_INDICATORS,\n ParseValidationSource,\n SumSectionIndicators,\n type FormSectionIndicators,\n type ParsedValidationSource,\n} from './form-section-indicators';\n\n/**\n * Anything that can answer \"what is the state of section X right now\".\n *\n * `mj-collapsible-panel` implements this for every section it renders, deriving the\n * counts from its `mj-form-field` children. A custom section that is NOT a collapsible\n * panel — a hero, a designer, a grid editor — can implement it too and register with\n * the container's {@link FormSectionIndicatorCoordinator}, so the rail treats it like\n * any other section.\n */\nexport interface FormSectionIndicatorSource {\n /** The chrome section key this source reports for (matches `mj-collapsible-panel` `SectionKey`). */\n readonly SectionKey: string;\n /** Current state. Called during change detection — must be cheap and side-effect free. */\n GetSectionIndicators(): FormSectionIndicators;\n /**\n * Whether a form-level `ValidationErrorInfo` belongs to this section. Used only to\n * decide which errors are UNROUTED (claimed by no section), so a failure nothing\n * renders still reaches the form-wide total instead of vanishing.\n */\n OwnsValidationSource(source: ParsedValidationSource): boolean;\n}\n\n/**\n * Per-container registry of section indicator sources — the join between \"a rail\n * group\" and \"the sections it fronts\", read live rather than pushed.\n *\n * **Pull, not push.** Every query asks the registered sources for their current\n * counts. Nothing is cached here and nothing is written from inside a change-detection\n * pass, which is what keeps the rail free of `ExpressionChangedAfterItHasBeenChecked`\n * errors: within one pass a field's dirty / error state is constant (it mutates in\n * event handlers), so every reader in that pass sees the same answer.\n *\n * Provided by `<mj-record-form-container>` via `providers` (not `viewProviders`), so\n * projected panels and slot-mounted `BaseFormPanel`s reach the same instance.\n */\n@Injectable()\nexport class FormSectionIndicatorCoordinator {\n /** Insertion-ordered so totals are deterministic. */\n private readonly sources = new Map<string, FormSectionIndicatorSource>();\n\n /**\n * Fires when a source registers / leaves, or when a source reports that its state\n * changed (a field edit). The container marks itself for check on it so an `OnPush`\n * rail re-reads the counts on the next pass instead of waiting for its poll.\n */\n public readonly Changes = new Subject<void>();\n\n /** Declare (or re-declare) a section. A second source for the same key replaces the first. */\n public Register(source: FormSectionIndicatorSource): void {\n const key = source.SectionKey;\n if (!key) return;\n if (this.sources.get(key) === source) return;\n this.sources.set(key, source);\n this.Changes.next();\n }\n\n /** Remove a source. Keyed on identity, so a replaced source cannot evict its replacement. */\n public Unregister(source: FormSectionIndicatorSource, previousKey?: string): void {\n const key = previousKey ?? source.SectionKey;\n if (this.sources.get(key) === source) {\n this.sources.delete(key);\n this.Changes.next();\n }\n }\n\n /** A source's state changed (field edited, error cleared). Nudges OnPush readers. */\n public NotifyChanged(): void {\n this.Changes.next();\n }\n\n /** Section keys currently registered, in registration order. */\n public get RegisteredSectionKeys(): string[] {\n return [...this.sources.keys()];\n }\n\n /** Whether a section is registered. */\n public Has(sectionKey: string): boolean {\n return this.sources.has(sectionKey);\n }\n\n /** Current indicators for one section; empty when nothing is registered under that key. */\n public IndicatorsFor(sectionKey: string): FormSectionIndicators {\n const source = this.sources.get(sectionKey);\n return source ? source.GetSectionIndicators() : { ...EMPTY_SECTION_INDICATORS };\n }\n\n /**\n * Indicators summed over several section keys — what a rail group needs, since one\n * group (notably `Details`) fronts many panels.\n */\n public IndicatorsForKeys(sectionKeys: readonly string[]): FormSectionIndicators {\n return SumSectionIndicators(...sectionKeys.map((key) => this.IndicatorsFor(key)));\n }\n\n /** Indicators summed over every registered section. */\n public get TotalIndicators(): FormSectionIndicators {\n return SumSectionIndicators(...[...this.sources.values()].map((source) => source.GetSectionIndicators()));\n }\n\n /**\n * Form-level errors no registered section claims. Surfaced deliberately: an error that\n * vanished from every badge is worse than the no-badge behaviour this replaced.\n *\n * `reachableKeys`, when given, limits WHICH sources may claim: a section that is\n * registered but sits in no rail group (hidden by `hiddenSectionKeys`, dropped by the\n * chrome) has no badge anywhere, so letting it claim would make its failure vanish.\n * Its errors stay unrouted and land in the whole-form total instead.\n */\n public UnroutedValidationErrors(\n errors: readonly ValidationErrorInfo[] | null | undefined,\n reachableKeys?: ReadonlySet<string>,\n ): ValidationErrorInfo[] {\n if (!errors?.length) return [];\n const sources = [...this.sources.values()].filter((source) => !reachableKeys || reachableKeys.has(source.SectionKey));\n return errors.filter((error) => {\n const parsed = ParseValidationSource(error.Source);\n if (!parsed.Source) return false;\n return !sources.some((source) => source.OwnsValidationSource(parsed));\n });\n }\n}\n"]}
@@ -0,0 +1,97 @@
1
+ import { ValidationErrorInfo } from '@memberjunction/global';
2
+ /**
3
+ * Per-section state a form's chrome can surface — the left-nav rail, an accordion
4
+ * header, the collapsed rail spine — so a user editing a multi-section record can see
5
+ * WHICH section holds unsaved edits or invalid input without opening each one.
6
+ *
7
+ * Counts are of **fields**, not messages: a section with one required field left blank
8
+ * and one field failing two rules reports `ErrorCount: 2`.
9
+ */
10
+ export interface FormSectionIndicators {
11
+ /** Fields modified since the last save. Mirrors the amber dot on an edited field. */
12
+ DirtyCount: number;
13
+ /**
14
+ * Fields that cannot be saved as they stand — a failing validation rule, or a
15
+ * required field that is empty. Mirrors the red underline on the field itself.
16
+ */
17
+ ErrorCount: number;
18
+ /** Fields carrying a warning-level validation result and no failure. */
19
+ WarningCount: number;
20
+ }
21
+ export declare const EMPTY_SECTION_INDICATORS: Readonly<FormSectionIndicators>;
22
+ /** Add any number of indicator sets. Missing / non-finite counts read as 0. */
23
+ export declare function SumSectionIndicators(...sets: ReadonlyArray<Partial<FormSectionIndicators> | null | undefined>): FormSectionIndicators;
24
+ /** True when nothing in the set would render. */
25
+ export declare function SectionIndicatorsAreEmpty(set: Partial<FormSectionIndicators> | null | undefined): boolean;
26
+ /**
27
+ * A `ValidationErrorInfo.Source` broken into the parts a section needs in order to
28
+ * decide whether the failure belongs to it.
29
+ *
30
+ * MJ's own `BaseEntity.Validate()` emits a bare field name (`"Code"`). Callers that
31
+ * validate a whole object graph emit a path instead — `"Modifications[2].ProvisionID"`
32
+ * — and those failures belong to the section that hosts the collection, not to a
33
+ * section that happens to render a field of the same trailing name.
34
+ */
35
+ export interface ParsedValidationSource {
36
+ /** The original, trimmed `Source` string. */
37
+ Source: string;
38
+ /**
39
+ * Leading collection segment of a graph path (`Modifications[2].ProvisionID` →
40
+ * `Modifications`). `undefined` for a plain field-name source.
41
+ */
42
+ Collection?: string;
43
+ /** Positional index within {@link Collection} when the path carried one. */
44
+ Index?: number;
45
+ /**
46
+ * Trailing field name — the last dotted segment with any `[i]` subscript removed.
47
+ * Equals {@link Source} for a plain field-name source.
48
+ */
49
+ Field: string;
50
+ }
51
+ /**
52
+ * Parse a `ValidationErrorInfo.Source` into {@link ParsedValidationSource}.
53
+ *
54
+ * A source with no dot and no subscript is a plain field name and parses to
55
+ * `{ Field: source }` with no `Collection` — which is what keeps ordinary
56
+ * single-record validation routing on the field name.
57
+ */
58
+ export declare function ParseValidationSource(source: string | null | undefined): ParsedValidationSource;
59
+ /** What a form section owns, for the purpose of attributing a validation failure to it. */
60
+ export interface SectionValidationScope {
61
+ /** Field names the section renders — normally its `mj-form-field` children. */
62
+ FieldNames?: readonly string[];
63
+ /**
64
+ * Graph-path collection names the section owns (`"Modifications"`), so positional
65
+ * child failures land on the section that hosts that collection.
66
+ */
67
+ CollectionNames?: readonly string[];
68
+ }
69
+ /**
70
+ * Whether `parsed` belongs to a section with the given key + scope.
71
+ *
72
+ * Precedence, and it is deliberate:
73
+ * 1. **plain field source** matches a declared field name;
74
+ * 2. **graph source** whose leading collection matches a declared `CollectionNames` entry;
75
+ * 3. **graph source** whose leading collection matches the section key itself (the
76
+ * zero-configuration case — a panel keyed `modifications` hosting `Modifications[i]`).
77
+ *
78
+ * A graph source is deliberately NOT matched on its trailing field name. Doing so is
79
+ * what sends every child failure to whichever section renders a parent field of the
80
+ * same name — usually the header section, the one place the user cannot fix it.
81
+ */
82
+ export declare function SectionOwnsValidationSource(parsed: ParsedValidationSource, sectionKey: string, scope: SectionValidationScope): boolean;
83
+ /**
84
+ * The graph-path errors (`Lines[2].Amount`) a section owns. Plain field-name errors are
85
+ * excluded on purpose: those already surface through the field that renders them, and a
86
+ * section counts that field once, through the field, rather than once per message.
87
+ */
88
+ export declare function ClaimedCollectionErrors(errors: readonly ValidationErrorInfo[] | null | undefined, sectionKey: string, scope: SectionValidationScope): ValidationErrorInfo[];
89
+ /** Tally failures vs warnings over an already-selected set of errors. */
90
+ export declare function TallyValidationErrors(errors: readonly ValidationErrorInfo[]): Pick<FormSectionIndicators, 'ErrorCount' | 'WarningCount'>;
91
+ /** Tooltip / screen-reader text for an error badge. */
92
+ export declare function DescribeSectionErrors(count: number, where?: string): string;
93
+ /** Tooltip / screen-reader text for a warning badge. */
94
+ export declare function DescribeSectionWarnings(count: number, where?: string): string;
95
+ /** Tooltip / screen-reader text for the unsaved-changes dot. */
96
+ export declare function DescribeSectionDirty(count: number, where?: string): string;
97
+ //# sourceMappingURL=form-section-indicators.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"form-section-indicators.d.ts","sourceRoot":"","sources":["../../../src/lib/section-indicators/form-section-indicators.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAuB,MAAM,wBAAwB,CAAC;AAElF;;;;;;;GAOG;AACH,MAAM,WAAW,qBAAqB;IACpC,qFAAqF;IACrF,UAAU,EAAE,MAAM,CAAC;IACnB;;;OAGG;IACH,UAAU,EAAE,MAAM,CAAC;IACnB,wEAAwE;IACxE,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,eAAO,MAAM,wBAAwB,EAAE,QAAQ,CAAC,qBAAqB,CAInE,CAAC;AAEH,+EAA+E;AAC/E,wBAAgB,oBAAoB,CAClC,GAAG,IAAI,EAAE,aAAa,CAAC,OAAO,CAAC,qBAAqB,CAAC,GAAG,IAAI,GAAG,SAAS,CAAC,GACxE,qBAAqB,CAWvB;AAED,iDAAiD;AACjD,wBAAgB,yBAAyB,CAAC,GAAG,EAAE,OAAO,CAAC,qBAAqB,CAAC,GAAG,IAAI,GAAG,SAAS,GAAG,OAAO,CAGzG;AAMD;;;;;;;;GAQG;AACH,MAAM,WAAW,sBAAsB;IACrC,6CAA6C;IAC7C,MAAM,EAAE,MAAM,CAAC;IACf;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,4EAA4E;IAC5E,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;;OAGG;IACH,KAAK,EAAE,MAAM,CAAC;CACf;AASD;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,sBAAsB,CAkB/F;AAED,2FAA2F;AAC3F,MAAM,WAAW,sBAAsB;IACrC,+EAA+E;IAC/E,UAAU,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC/B;;;OAGG;IACH,eAAe,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CACrC;AAMD;;;;;;;;;;;;GAYG;AACH,wBAAgB,2BAA2B,CACzC,MAAM,EAAE,sBAAsB,EAC9B,UAAU,EAAE,MAAM,EAClB,KAAK,EAAE,sBAAsB,GAC5B,OAAO,CAWT;AAED;;;;GAIG;AACH,wBAAgB,uBAAuB,CACrC,MAAM,EAAE,SAAS,mBAAmB,EAAE,GAAG,IAAI,GAAG,SAAS,EACzD,UAAU,EAAE,MAAM,EAClB,KAAK,EAAE,sBAAsB,GAC5B,mBAAmB,EAAE,CAMvB;AAED,yEAAyE;AACzE,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,SAAS,mBAAmB,EAAE,GAAG,IAAI,CAAC,qBAAqB,EAAE,YAAY,GAAG,cAAc,CAAC,CAQxI;AAED,uDAAuD;AACvD,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,SAAiB,GAAG,MAAM,CAGnF;AAED,wDAAwD;AACxD,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,SAAiB,GAAG,MAAM,CAGrF;AAED,gEAAgE;AAChE,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,SAAiB,GAAG,MAAM,CAGlF"}
@@ -0,0 +1,132 @@
1
+ import { ValidationErrorType } from '@memberjunction/global';
2
+ export const EMPTY_SECTION_INDICATORS = Object.freeze({
3
+ DirtyCount: 0,
4
+ ErrorCount: 0,
5
+ WarningCount: 0,
6
+ });
7
+ /** Add any number of indicator sets. Missing / non-finite counts read as 0. */
8
+ export function SumSectionIndicators(...sets) {
9
+ let DirtyCount = 0;
10
+ let ErrorCount = 0;
11
+ let WarningCount = 0;
12
+ for (const set of sets) {
13
+ if (!set)
14
+ continue;
15
+ DirtyCount += CountOrZero(set.DirtyCount);
16
+ ErrorCount += CountOrZero(set.ErrorCount);
17
+ WarningCount += CountOrZero(set.WarningCount);
18
+ }
19
+ return { DirtyCount, ErrorCount, WarningCount };
20
+ }
21
+ /** True when nothing in the set would render. */
22
+ export function SectionIndicatorsAreEmpty(set) {
23
+ if (!set)
24
+ return true;
25
+ return CountOrZero(set.DirtyCount) === 0 && CountOrZero(set.ErrorCount) === 0 && CountOrZero(set.WarningCount) === 0;
26
+ }
27
+ function CountOrZero(value) {
28
+ return typeof value === 'number' && Number.isFinite(value) && value > 0 ? Math.floor(value) : 0;
29
+ }
30
+ /** `Foo[3]` → `{ name: 'Foo', index: 3 }`; `Foo` → `{ name: 'Foo' }`. */
31
+ function SplitSubscript(segment) {
32
+ const match = segment.match(/^(.*?)\[(\d+)\]$/);
33
+ if (!match)
34
+ return { name: segment };
35
+ return { name: match[1], index: Number(match[2]) };
36
+ }
37
+ /**
38
+ * Parse a `ValidationErrorInfo.Source` into {@link ParsedValidationSource}.
39
+ *
40
+ * A source with no dot and no subscript is a plain field name and parses to
41
+ * `{ Field: source }` with no `Collection` — which is what keeps ordinary
42
+ * single-record validation routing on the field name.
43
+ */
44
+ export function ParseValidationSource(source) {
45
+ const raw = (source ?? '').trim();
46
+ if (!raw)
47
+ return { Source: '', Field: '' };
48
+ const segments = raw.split('.');
49
+ const first = SplitSubscript(segments[0]);
50
+ const last = SplitSubscript(segments[segments.length - 1]);
51
+ if (segments.length === 1 && first.index === undefined) {
52
+ return { Source: raw, Field: first.name };
53
+ }
54
+ return {
55
+ Source: raw,
56
+ Collection: first.name || undefined,
57
+ Index: first.index,
58
+ Field: last.name || first.name,
59
+ };
60
+ }
61
+ function Normalize(value) {
62
+ return (value ?? '').trim().toLowerCase();
63
+ }
64
+ /**
65
+ * Whether `parsed` belongs to a section with the given key + scope.
66
+ *
67
+ * Precedence, and it is deliberate:
68
+ * 1. **plain field source** matches a declared field name;
69
+ * 2. **graph source** whose leading collection matches a declared `CollectionNames` entry;
70
+ * 3. **graph source** whose leading collection matches the section key itself (the
71
+ * zero-configuration case — a panel keyed `modifications` hosting `Modifications[i]`).
72
+ *
73
+ * A graph source is deliberately NOT matched on its trailing field name. Doing so is
74
+ * what sends every child failure to whichever section renders a parent field of the
75
+ * same name — usually the header section, the one place the user cannot fix it.
76
+ */
77
+ export function SectionOwnsValidationSource(parsed, sectionKey, scope) {
78
+ if (!parsed.Source)
79
+ return false;
80
+ if (parsed.Collection === undefined) {
81
+ const field = Normalize(parsed.Field);
82
+ return (scope.FieldNames ?? []).some((name) => Normalize(name) === field);
83
+ }
84
+ const collection = Normalize(parsed.Collection);
85
+ if ((scope.CollectionNames ?? []).some((name) => Normalize(name) === collection))
86
+ return true;
87
+ return Normalize(sectionKey) === collection;
88
+ }
89
+ /**
90
+ * The graph-path errors (`Lines[2].Amount`) a section owns. Plain field-name errors are
91
+ * excluded on purpose: those already surface through the field that renders them, and a
92
+ * section counts that field once, through the field, rather than once per message.
93
+ */
94
+ export function ClaimedCollectionErrors(errors, sectionKey, scope) {
95
+ if (!errors?.length)
96
+ return [];
97
+ return errors.filter((error) => {
98
+ const parsed = ParseValidationSource(error.Source);
99
+ return parsed.Collection !== undefined && SectionOwnsValidationSource(parsed, sectionKey, scope);
100
+ });
101
+ }
102
+ /** Tally failures vs warnings over an already-selected set of errors. */
103
+ export function TallyValidationErrors(errors) {
104
+ let ErrorCount = 0;
105
+ let WarningCount = 0;
106
+ for (const error of errors) {
107
+ if (error.Type === ValidationErrorType.Warning)
108
+ WarningCount++;
109
+ else
110
+ ErrorCount++;
111
+ }
112
+ return { ErrorCount, WarningCount };
113
+ }
114
+ /** Tooltip / screen-reader text for an error badge. */
115
+ export function DescribeSectionErrors(count, where = 'this section') {
116
+ if (count <= 0)
117
+ return '';
118
+ return count === 1 ? `1 field in ${where} needs attention` : `${count} fields in ${where} need attention`;
119
+ }
120
+ /** Tooltip / screen-reader text for a warning badge. */
121
+ export function DescribeSectionWarnings(count, where = 'this section') {
122
+ if (count <= 0)
123
+ return '';
124
+ return count === 1 ? `1 field in ${where} has a warning` : `${count} fields in ${where} have warnings`;
125
+ }
126
+ /** Tooltip / screen-reader text for the unsaved-changes dot. */
127
+ export function DescribeSectionDirty(count, where = 'this section') {
128
+ if (count <= 0)
129
+ return '';
130
+ return count === 1 ? `1 unsaved change in ${where}` : `${count} unsaved changes in ${where}`;
131
+ }
132
+ //# sourceMappingURL=form-section-indicators.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"form-section-indicators.js","sourceRoot":"","sources":["../../../src/lib/section-indicators/form-section-indicators.ts"],"names":[],"mappings":"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","sourcesContent":["import { ValidationErrorInfo, ValidationErrorType } from '@memberjunction/global';\n\n/**\n * Per-section state a form's chrome can surface — the left-nav rail, an accordion\n * header, the collapsed rail spine — so a user editing a multi-section record can see\n * WHICH section holds unsaved edits or invalid input without opening each one.\n *\n * Counts are of **fields**, not messages: a section with one required field left blank\n * and one field failing two rules reports `ErrorCount: 2`.\n */\nexport interface FormSectionIndicators {\n /** Fields modified since the last save. Mirrors the amber dot on an edited field. */\n DirtyCount: number;\n /**\n * Fields that cannot be saved as they stand — a failing validation rule, or a\n * required field that is empty. Mirrors the red underline on the field itself.\n */\n ErrorCount: number;\n /** Fields carrying a warning-level validation result and no failure. */\n WarningCount: number;\n}\n\nexport const EMPTY_SECTION_INDICATORS: Readonly<FormSectionIndicators> = Object.freeze({\n DirtyCount: 0,\n ErrorCount: 0,\n WarningCount: 0,\n});\n\n/** Add any number of indicator sets. Missing / non-finite counts read as 0. */\nexport function SumSectionIndicators(\n ...sets: ReadonlyArray<Partial<FormSectionIndicators> | null | undefined>\n): FormSectionIndicators {\n let DirtyCount = 0;\n let ErrorCount = 0;\n let WarningCount = 0;\n for (const set of sets) {\n if (!set) continue;\n DirtyCount += CountOrZero(set.DirtyCount);\n ErrorCount += CountOrZero(set.ErrorCount);\n WarningCount += CountOrZero(set.WarningCount);\n }\n return { DirtyCount, ErrorCount, WarningCount };\n}\n\n/** True when nothing in the set would render. */\nexport function SectionIndicatorsAreEmpty(set: Partial<FormSectionIndicators> | null | undefined): boolean {\n if (!set) return true;\n return CountOrZero(set.DirtyCount) === 0 && CountOrZero(set.ErrorCount) === 0 && CountOrZero(set.WarningCount) === 0;\n}\n\nfunction CountOrZero(value: number | undefined): number {\n return typeof value === 'number' && Number.isFinite(value) && value > 0 ? Math.floor(value) : 0;\n}\n\n/**\n * A `ValidationErrorInfo.Source` broken into the parts a section needs in order to\n * decide whether the failure belongs to it.\n *\n * MJ's own `BaseEntity.Validate()` emits a bare field name (`\"Code\"`). Callers that\n * validate a whole object graph emit a path instead — `\"Modifications[2].ProvisionID\"`\n * — and those failures belong to the section that hosts the collection, not to a\n * section that happens to render a field of the same trailing name.\n */\nexport interface ParsedValidationSource {\n /** The original, trimmed `Source` string. */\n Source: string;\n /**\n * Leading collection segment of a graph path (`Modifications[2].ProvisionID` →\n * `Modifications`). `undefined` for a plain field-name source.\n */\n Collection?: string;\n /** Positional index within {@link Collection} when the path carried one. */\n Index?: number;\n /**\n * Trailing field name — the last dotted segment with any `[i]` subscript removed.\n * Equals {@link Source} for a plain field-name source.\n */\n Field: string;\n}\n\n/** `Foo[3]` → `{ name: 'Foo', index: 3 }`; `Foo` → `{ name: 'Foo' }`. */\nfunction SplitSubscript(segment: string): { name: string; index?: number } {\n const match = segment.match(/^(.*?)\\[(\\d+)\\]$/);\n if (!match) return { name: segment };\n return { name: match[1], index: Number(match[2]) };\n}\n\n/**\n * Parse a `ValidationErrorInfo.Source` into {@link ParsedValidationSource}.\n *\n * A source with no dot and no subscript is a plain field name and parses to\n * `{ Field: source }` with no `Collection` — which is what keeps ordinary\n * single-record validation routing on the field name.\n */\nexport function ParseValidationSource(source: string | null | undefined): ParsedValidationSource {\n const raw = (source ?? '').trim();\n if (!raw) return { Source: '', Field: '' };\n\n const segments = raw.split('.');\n const first = SplitSubscript(segments[0]);\n const last = SplitSubscript(segments[segments.length - 1]);\n\n if (segments.length === 1 && first.index === undefined) {\n return { Source: raw, Field: first.name };\n }\n\n return {\n Source: raw,\n Collection: first.name || undefined,\n Index: first.index,\n Field: last.name || first.name,\n };\n}\n\n/** What a form section owns, for the purpose of attributing a validation failure to it. */\nexport interface SectionValidationScope {\n /** Field names the section renders — normally its `mj-form-field` children. */\n FieldNames?: readonly string[];\n /**\n * Graph-path collection names the section owns (`\"Modifications\"`), so positional\n * child failures land on the section that hosts that collection.\n */\n CollectionNames?: readonly string[];\n}\n\nfunction Normalize(value: string | null | undefined): string {\n return (value ?? '').trim().toLowerCase();\n}\n\n/**\n * Whether `parsed` belongs to a section with the given key + scope.\n *\n * Precedence, and it is deliberate:\n * 1. **plain field source** matches a declared field name;\n * 2. **graph source** whose leading collection matches a declared `CollectionNames` entry;\n * 3. **graph source** whose leading collection matches the section key itself (the\n * zero-configuration case — a panel keyed `modifications` hosting `Modifications[i]`).\n *\n * A graph source is deliberately NOT matched on its trailing field name. Doing so is\n * what sends every child failure to whichever section renders a parent field of the\n * same name — usually the header section, the one place the user cannot fix it.\n */\nexport function SectionOwnsValidationSource(\n parsed: ParsedValidationSource,\n sectionKey: string,\n scope: SectionValidationScope,\n): boolean {\n if (!parsed.Source) return false;\n\n if (parsed.Collection === undefined) {\n const field = Normalize(parsed.Field);\n return (scope.FieldNames ?? []).some((name) => Normalize(name) === field);\n }\n\n const collection = Normalize(parsed.Collection);\n if ((scope.CollectionNames ?? []).some((name) => Normalize(name) === collection)) return true;\n return Normalize(sectionKey) === collection;\n}\n\n/**\n * The graph-path errors (`Lines[2].Amount`) a section owns. Plain field-name errors are\n * excluded on purpose: those already surface through the field that renders them, and a\n * section counts that field once, through the field, rather than once per message.\n */\nexport function ClaimedCollectionErrors(\n errors: readonly ValidationErrorInfo[] | null | undefined,\n sectionKey: string,\n scope: SectionValidationScope,\n): ValidationErrorInfo[] {\n if (!errors?.length) return [];\n return errors.filter((error) => {\n const parsed = ParseValidationSource(error.Source);\n return parsed.Collection !== undefined && SectionOwnsValidationSource(parsed, sectionKey, scope);\n });\n}\n\n/** Tally failures vs warnings over an already-selected set of errors. */\nexport function TallyValidationErrors(errors: readonly ValidationErrorInfo[]): Pick<FormSectionIndicators, 'ErrorCount' | 'WarningCount'> {\n let ErrorCount = 0;\n let WarningCount = 0;\n for (const error of errors) {\n if (error.Type === ValidationErrorType.Warning) WarningCount++;\n else ErrorCount++;\n }\n return { ErrorCount, WarningCount };\n}\n\n/** Tooltip / screen-reader text for an error badge. */\nexport function DescribeSectionErrors(count: number, where = 'this section'): string {\n if (count <= 0) return '';\n return count === 1 ? `1 field in ${where} needs attention` : `${count} fields in ${where} need attention`;\n}\n\n/** Tooltip / screen-reader text for a warning badge. */\nexport function DescribeSectionWarnings(count: number, where = 'this section'): string {\n if (count <= 0) return '';\n return count === 1 ? `1 field in ${where} has a warning` : `${count} fields in ${where} have warnings`;\n}\n\n/** Tooltip / screen-reader text for the unsaved-changes dot. */\nexport function DescribeSectionDirty(count: number, where = 'this section'): string {\n if (count <= 0) return '';\n return count === 1 ? `1 unsaved change in ${where}` : `${count} unsaved changes in ${where}`;\n}\n"]}
@@ -155,7 +155,7 @@ function MjSectionManagerComponent_Conditional_0_Template(rf, ctx) { if (rf & 1)
155
155
  i0.ɵɵelementStart(0, "div", 1);
156
156
  i0.ɵɵlistener("click", function MjSectionManagerComponent_Conditional_0_Template_div_click_0_listener($event) { i0.ɵɵrestoreView(_r1); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.OnOverlayClick($event)); });
157
157
  i0.ɵɵelementStart(1, "div", 2);
158
- i0.ɵɵlistener("click", function MjSectionManagerComponent_Conditional_0_Template_div_click_1_listener($event) { i0.ɵɵrestoreView(_r1); return i0.ɵɵresetView($event.stopPropagation()); });
158
+ i0.ɵɵlistener("click", function MjSectionManagerComponent_Conditional_0_Template_div_click_1_listener($event) { return $event.stopPropagation(); });
159
159
  i0.ɵɵelementStart(2, "div", 3)(3, "h3", 4);
160
160
  i0.ɵɵelement(4, "i", 5);
161
161
  i0.ɵɵtext(5, " Manage Sections ");
@@ -359,7 +359,7 @@ export class MjSectionManagerComponent {
359
359
  i0.ɵɵconditionalCreate(0, MjSectionManagerComponent_Conditional_0_Template, 15, 1, "div", 0);
360
360
  } if (rf & 2) {
361
361
  i0.ɵɵconditional(ctx.Visible ? 0 : -1);
362
- } }, 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-group-label[_ngcontent-%COMP%] {\n padding: 12px 20px 6px;\n font-size: 10px;\n font-weight: var(--mj-font-semibold);\n letter-spacing: 0.4px;\n text-transform: uppercase;\n color: var(--mj-text-muted);\n}\n\n.mj-sm-empty[_ngcontent-%COMP%] {\n padding: 8px 20px 12px;\n font-size: var(--mj-text-xs);\n color: var(--mj-text-disabled);\n}\n\n.mj-sm-row.is-more[_ngcontent-%COMP%] {\n background: color-mix(in srgb, var(--mj-bg-surface-sunken) 55%, transparent);\n}\n\n.mj-sm-row-badge--more[_ngcontent-%COMP%] {\n background: var(--mj-bg-surface-sunken);\n color: var(--mj-text-muted);\n}\n\n.mj-sm-move[_ngcontent-%COMP%] {\n margin-left: 4px;\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 });
362
+ } }, styles: ["\n\n\n[_nghost-%COMP%] {\n display: block;\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.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.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.mj-sm-list[_ngcontent-%COMP%] {\n flex: 1;\n overflow-y: auto;\n padding: 8px 0;\n}\n\n.mj-sm-group-label[_ngcontent-%COMP%] {\n padding: 12px 20px 6px;\n font-size: 10px;\n font-weight: var(--mj-font-semibold);\n letter-spacing: 0.4px;\n text-transform: uppercase;\n color: var(--mj-text-muted);\n}\n\n.mj-sm-empty[_ngcontent-%COMP%] {\n padding: 8px 20px 12px;\n font-size: var(--mj-text-xs);\n color: var(--mj-text-disabled);\n}\n\n.mj-sm-row.is-more[_ngcontent-%COMP%] {\n background: color-mix(in srgb, var(--mj-bg-surface-sunken) 55%, transparent);\n}\n\n.mj-sm-row-badge--more[_ngcontent-%COMP%] {\n background: var(--mj-bg-surface-sunken);\n color: var(--mj-text-muted);\n}\n\n.mj-sm-move[_ngcontent-%COMP%] {\n margin-left: 4px;\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.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.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.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@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 });
363
363
  }
364
364
  (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(MjSectionManagerComponent, [{
365
365
  type: Component,
@@ -1 +1 @@
1
- {"version":3,"file":"section-manager.component.js","sourceRoot":"","sources":["../../../src/lib/section-manager/section-manager.component.ts","../../../src/lib/section-manager/section-manager.component.html"],"names":[],"mappings":"AAAA,OAAO,EACL,SAAS,EAAE,KAAK,EAAE,MAAM,EAAE,YAAY,EACtC,uBAAuB,EAAE,iBAAiB,EAAE,MAAM,EAEnD,MAAM,eAAe,CAAC;;;;;ICkBT,AADF,+BAAuB,cACO;IAC1B,wBAAqD;IACrD,gCAA6B;IAAA,YAAyB;IAAA,iBAAO;IAC7D,4BAAqE;IACnE,YACF;IACF,AADE,iBAAO,EACH;IAEJ,AADF,+BAA+B,iBAIL;IADhB,+OAAS,8DAAoC,KAAC;IAEpD,wBAAsC;IACxC,iBAAS;IACT,mCAG0B;IADlB,gPAAS,gEAAsC,KAAC;IAEtD,yBAAwC;IAC1C,iBAAS;IACT,mCAE6B;IADrB,gPAAS,+BAAqB,KAAC;IAErC,yBAAuC;IAG7C,AADE,AADE,iBAAS,EACL,EACF;;;;;IAzBC,eAAsB;IAAtB,8BAAsB;IACI,eAAyB;IAAzB,4CAAyB;IAChD,cAA8D;IAA9D,cAAA,0EAA8D,CAAA;IAClE,cACF;IADE,2EACF;IAIQ,eAAoB;IAApB,8CAAoB;IAMpB,eAAgD;IAAhD,iFAAgD;;;IAc5D,+BAAyB;IAAA,wCAAwB;IAAA,iBAAM;;;;IAMrD,AADF,+BAA+B,cACD;IAC1B,wBAAqD;IACrD,gCAA6B;IAAA,YAAyB;IAAA,iBAAO;IAC7D,gCAAoD;IAAA,oBAAI;IAC1D,AAD0D,iBAAO,EAC3D;IAEJ,AADF,+BAA+B,iBAIL;IADhB,+OAAS,wDAA8B,KAAC;IAE9C,wBAAsC;IACxC,iBAAS;IACT,mCAG0B;IADlB,gPAAS,0DAAgC,KAAC;IAEhD,yBAAwC;IAC1C,iBAAS;IACT,mCAG8E;IADtE,gPAAS,+BAAqB,KAAC;IAErC,yBAAiD;IAGvD,AADE,AADE,iBAAS,EACL,EACF;;;;;IAxBC,eAAsB;IAAtB,8BAAsB;IACI,eAAyB;IAAzB,4CAAyB;IAK9C,eAAoB;IAApB,8CAAoB;IAMpB,eAA0C;IAA1C,2EAA0C;IAM1C,eAAoC;IAEpC,AAFA,4DAAoC,gFAEiC;;;IAOjF,+BAAyB;IAAA,gCAAgB;IAAA,iBAAM;;;IAlEjD,+BAA+B;IAAA,2BAAW;IAAA,iBAAM;IAChD,0HA6BC;IACD,wHAAuC;IAIvC,+BAA+B;IAAA,oBAAI;IAAA,iBAAM;IACzC,0HA4BC;IACD,wHAAiC;;;IAhEjC,eA6BC;IA7BD,wCA6BC;IACD,eAEC;IAFD,iEAEC;IAGD,eA4BC;IA5BD,kCA4BC;IACD,eAEC;IAFD,2DAEC;;;;IAIG,AADF,+BAAuB,cACO;IAC1B,wBAAqD;IACrD,gCAA6B;IAAA,YAAyB;IAAA,iBAAO;IAC7D,4BAAqE;IACnE,YACF;IACF,AADE,iBAAO,EACH;IAEJ,AADF,+BAA+B,iBAIL;IADhB,iPAAS,+BAAW,KAAC;IAE3B,wBAAsC;IACxC,iBAAS;IACT,mCAG0B;IADlB,kPAAS,iCAAa,KAAC;IAE7B,yBAAwC;IAG9C,AADE,AADE,iBAAS,EACL,EACF;;;;;IApBC,eAAsB;IAAtB,+BAAsB;IACI,eAAyB;IAAzB,6CAAyB;IAChD,cAA8D;IAA9D,cAAA,2EAA8D,CAAA;IAClE,cACF;IADE,4EACF;IAIQ,eAAoB;IAApB,+CAAoB;IAMpB,eAA6C;IAA7C,+EAA6C;;;IAjB3D,2HAwBC;;;IAxBD,qCAwBC;;;;IA9GT,8BAA4D;IAAjC,gMAAS,6BAAsB,KAAC;IAEzD,8BAA6D;IAAnC,6JAAS,wBAAwB,KAAC;IAGxD,AADF,8BAA0B,YACA;IACtB,uBAA0C;IAC1C,iCACF;IAAA,iBAAK;IACL,iCAA8D;IAAlC,6LAAS,gBAAS,KAAC;IAC7C,uBAAiC;IAErC,AADE,iBAAS,EACL;IAGN,8BAAwB;IAsEpB,AArEF,gGAAoB,2EAqEX;IA2BX,iBAAM;IAIJ,AADF,+BAA0B,kBACwB;IAApB,8LAAS,gBAAS,KAAC;IAC7C,yBAA6C;IAC7C,oCACF;IAGN,AADE,AADE,AADE,iBAAS,EACL,EACF,EACF;;;IA1GA,eA+FC;IA/FD,8CA+FC;;AD3FT;;;;;;;;;;;;;;;;;GAiBG;AAQH,MAAM,OAAO,yBAAyB;IAC5B,GAAG,GAAG,MAAM,CAAC,iBAAiB,CAAC,CAAC;IAExC,yCAAyC;IAChC,QAAQ,GAAyB,EAAE,CAAC;IAE7C,oDAAoD;IAC3C,YAAY,GAAa,EAAE,CAAC;IAErC,iEAAiE;IACxD,eAAe,GAAa,EAAE,CAAC;IAExC,qDAAqD;IAC5C,cAAc,GAAa,EAAE,CAAC;IAEvC,oCAAoC;IAC3B,OAAO,GAAG,KAAK,CAAC;IAEzB,yDAAyD;IAC/C,kBAAkB,GAAG,IAAI,YAAY,EAAY,CAAC;IAE5D,6DAA6D;IACnD,gBAAgB,GAAG,IAAI,YAAY,EAA0B,CAAC;IAExE,sCAAsC;IAC5B,MAAM,GAAG,IAAI,YAAY,EAAQ,CAAC;IAE5C,kDAAkD;IACxC,cAAc,GAAG,IAAI,YAAY,EAAQ,CAAC;IAEpD,2CAA2C;IAC3C,eAAe,GAAyB,EAAE,CAAC;IAC3C,kBAAkB,GAAyB,EAAE,CAAC;IAC9C,YAAY,GAAyB,EAAE,CAAC;IAExC,WAAW,CAAC,OAAsB;QAChC,IAAI,OAAO,CAAC,UAAU,CAAC,IAAI,OAAO,CAAC,cAAc,CAAC,IAAI,OAAO,CAAC,iBAAiB,CAAC,IAAI,OAAO,CAAC,gBAAgB,CAAC,EAAE,CAAC;YAC9G,IAAI,CAAC,sBAAsB,EAAE,CAAC;QAChC,CAAC;IACH,CAAC;IAED,IAAI,YAAY;QACd,OAAO,IAAI,CAAC,YAAY,CAAC,MAAM,GAAG,CAAC,IAAI,IAAI,CAAC,eAAe,CAAC,MAAM,GAAG,CAAC,CAAC;IACzE,CAAC;IAED,cAAc,CAAC,OAA2B;QACxC,OAAO,IAAI,CAAC,cAAc,CAAC,QAAQ,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;IAC1D,CAAC;IAED,OAAO;QACL,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC;IACrB,CAAC;IAED,cAAc,CAAC,KAAiB;QAC9B,4DAA4D;QAC5D,IAAI,KAAK,CAAC,MAAM,KAAK,KAAK,CAAC,aAAa,EAAE,CAAC;YACzC,IAAI,CAAC,OAAO,EAAE,CAAC;QACjB,CAAC;IACH,CAAC;IAED,QAAQ,CAAC,KAAa;QACpB,IAAI,KAAK,IAAI,CAAC;YAAE,OAAO;QACvB,MAAM,QAAQ,GAAG,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC;QAC7D,CAAC,QAAQ,CAAC,KAAK,GAAG,CAAC,CAAC,EAAE,QAAQ,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,QAAQ,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC;QAChF,IAAI,CAAC,kBAAkB,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QACvC,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC;IAC5B,CAAC;IAED,UAAU,CAAC,KAAa;QACtB,IAAI,KAAK,IAAI,IAAI,CAAC,eAAe,CAAC,MAAM,GAAG,CAAC;YAAE,OAAO;QACrD,MAAM,QAAQ,GAAG,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC;QAC7D,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,QAAQ,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,QAAQ,CAAC,KAAK,GAAG,CAAC,CAAC,EAAE,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC;QAChF,IAAI,CAAC,kBAAkB,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QACvC,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC;IAC5B,CAAC;IAED,aAAa,CAAC,IAA0B,EAAE,KAAa;QACrD,IAAI,KAAK,IAAI,CAAC;YAAE,OAAO;QACvB,IAAI,CAAC,kBAAkB,CAAC,IAAI,EAAE,KAAK,EAAE,KAAK,GAAG,CAAC,CAAC,CAAC;IAClD,CAAC;IAED,eAAe,CAAC,IAA0B,EAAE,KAAa;QACvD,IAAI,KAAK,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC;YAAE,OAAO;QACrC,IAAI,CAAC,kBAAkB,CAAC,IAAI,EAAE,KAAK,EAAE,KAAK,GAAG,CAAC,CAAC,CAAC;IAClD,CAAC;IAED,YAAY,CAAC,OAA2B;QACtC,IAAI,IAAI,CAAC,eAAe,CAAC,QAAQ,CAAC,OAAO,CAAC,UAAU,CAAC;YAAE,OAAO;QAC9D,MAAM,IAAI,GAAG,CAAC,GAAG,IAAI,CAAC,eAAe,EAAE,OAAO,CAAC,UAAU,CAAC,CAAC;QAC3D,MAAM,UAAU,GAAG,IAAI,CAAC,kBAAkB;aACvC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC;aACxB,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,KAAK,OAAO,CAAC,UAAU,CAAC,CAAC;QAC/C,IAAI,CAAC,gBAAgB,CAAC,IAAI,CAAC,EAAE,eAAe,EAAE,IAAI,EAAE,qBAAqB,EAAE,UAAU,EAAE,CAAC,CAAC;IAC3F,CAAC;IAED,YAAY,CAAC,OAA2B;QACtC,IAAI,IAAI,CAAC,cAAc,CAAC,OAAO,CAAC;YAAE,OAAO;QACzC,MAAM,IAAI,GAAG,IAAI,CAAC,eAAe,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,KAAK,OAAO,CAAC,UAAU,CAAC,CAAC;QAC9E,MAAM,UAAU,GAAG;YACjB,GAAG,IAAI,CAAC,kBAAkB,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC;YACnD,OAAO,CAAC,UAAU;SACnB,CAAC;QACF,IAAI,CAAC,gBAAgB,CAAC,IAAI,CAAC,EAAE,eAAe,EAAE,IAAI,EAAE,qBAAqB,EAAE,UAAU,EAAE,CAAC,CAAC;IAC3F,CAAC;IAEO,kBAAkB,CAAC,IAA0B,EAAE,IAAY,EAAE,EAAU;QAC7E,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC;QAC3C,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,IAAI,CAAC,EAAE,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;QAChD,MAAM,KAAK,GAAG,IAAI,KAAK,IAAI,CAAC,kBAAkB,CAAC,CAAC,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC,CAAC,IAAI,CAAC,kBAAkB,CAAC;QAC7F,MAAM,QAAQ,GAAG,IAAI,KAAK,IAAI,CAAC,kBAAkB;YAC/C,CAAC,CAAC,CAAC,GAAG,IAAI,EAAE,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC;YAC9C,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,EAAE,GAAG,IAAI,CAAC,CAAC;QACjD,IAAI,CAAC,kBAAkB,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QACvC,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC;IAC5B,CAAC;IAED,OAAO;QACL,IAAI,CAAC,cAAc,CAAC,IAAI,EAAE,CAAC;IAC7B,CAAC;IAED,cAAc,CAAC,OAAqB;QAClC,QAAQ,OAAO,EAAE,CAAC;YAChB,KAAK,gBAAgB,CAAC,CAAC,OAAO,kBAAkB,CAAC;YACjD,KAAK,WAAW,CAAC,CAAC,OAAO,sBAAsB,CAAC;YAChD,OAAO,CAAC,CAAC,OAAO,yBAAyB,CAAC;QAC5C,CAAC;IACH,CAAC;IAED,eAAe,CAAC,OAAqB;QACnC,QAAQ,OAAO,EAAE,CAAC;YAChB,KAAK,gBAAgB,CAAC,CAAC,OAAO,SAAS,CAAC;YACxC,KAAK,WAAW,CAAC,CAAC,OAAO,WAAW,CAAC;YACrC,OAAO,CAAC,CAAC,OAAO,QAAQ,CAAC;QAC3B,CAAC;IACH,CAAC;IAEO,sBAAsB;QAC5B,IAAI,CAAC,IAAI,CAAC,QAAQ,IAAI,IAAI,CAAC,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACjD,IAAI,CAAC,eAAe,GAAG,EAAE,CAAC;YAC1B,OAAO;QACT,CAAC;QAED,MAAM,UAAU,GAAG,IAAI,GAAG,EAA8B,CAAC;QACzD,KAAK,MAAM,CAAC,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;YAC9B,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC;QAClC,CAAC;QAED,MAAM,OAAO,GAAyB,EAAE,CAAC;QAEzC,wCAAwC;QACxC,IAAI,IAAI,CAAC,YAAY,IAAI,IAAI,CAAC,YAAY,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACtD,KAAK,MAAM,GAAG,IAAI,IAAI,CAAC,YAAY,EAAE,CAAC;gBACpC,MAAM,OAAO,GAAG,UAAU,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;gBACpC,IAAI,OAAO,EAAE,CAAC;oBACZ,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;oBACtB,UAAU,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;gBACzB,CAAC;YACH,CAAC;QACH,CAAC;QAED,gEAAgE;QAChE,KAAK,MAAM,OAAO,IAAI,UAAU,CAAC,MAAM,EAAE,EAAE,CAAC;YAC1C,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACxB,CAAC;QAED,IAAI,CAAC,eAAe,GAAG,OAAO,CAAC;QAC/B,MAAM,OAAO,GAAG,IAAI,GAAG,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC;QAC9C,IAAI,CAAC,kBAAkB,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC;QAC5E,IAAI,CAAC,YAAY,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC;QACrE,IAAI,CAAC,GAAG,CAAC,YAAY,EAAE,CAAC;IAC1B,CAAC;IAEO,UAAU,CAAC,QAAkB;QACnC,MAAM,UAAU,GAAG,IAAI,GAAG,EAA8B,CAAC;QACzD,KAAK,MAAM,CAAC,IAAI,IAAI,CAAC,eAAe,EAAE,CAAC;YACrC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC;QAClC,CAAC;QACD,IAAI,CAAC,eAAe,GAAG,QAAQ;aAC5B,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,UAAU,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;aAC/B,MAAM,CAAC,CAAC,CAAC,EAA2B,EAAE,CAAC,CAAC,KAAK,SAAS,CAAC,CAAC;QAC3D,MAAM,OAAO,GAAG,IAAI,GAAG,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC;QAC9C,IAAI,CAAC,kBAAkB,GAAG,IAAI,CAAC,eAAe,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC;QACzF,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC,eAAe,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC;QAClF,IAAI,CAAC,GAAG,CAAC,YAAY,EAAE,CAAC;IAC1B,CAAC;mHAxLU,yBAAyB;6DAAzB,yBAAyB;YC9CtC,4FAAe;;YAAf,sCA4HC;;;iFD9EY,yBAAyB;cAPrC,SAAS;6BACI,KAAK,YACP,oBAAoB,mBACb,uBAAuB,CAAC,MAAM;;kBAQ9C,KAAK;;kBAGL,KAAK;;kBAGL,KAAK;;kBAGL,KAAK;;kBAGL,KAAK;;kBAGL,MAAM;;kBAGN,MAAM;;kBAGN,MAAM;;kBAGN,MAAM;;kFA5BI,yBAAyB","sourcesContent":["import {\n Component, Input, Output, EventEmitter,\n ChangeDetectionStrategy, ChangeDetectorRef, inject,\n OnChanges, SimpleChanges\n} from '@angular/core';\nimport { PanelVariant } from '../types/form-types';\n\n/**\n * Info about a section, passed from the container to the Section Manager drawer.\n */\nexport interface SectionManagerItem {\n SectionKey: string;\n SectionName: string;\n Variant: PanelVariant;\n Icon: string;\n}\n\nexport interface ChromeMembershipChange {\n moreSectionKeys: string[];\n firstClassSectionKeys: string[];\n}\n\n/**\n * Slide-in drawer for managing section order.\n *\n * Provides an accessible, keyboard-friendly alternative to drag-and-drop\n * for reordering form sections. Each section is displayed as a row with\n * up/down arrow buttons to change position.\n *\n * @example\n * ```html\n * <mj-section-manager\n * [Sections]=\"sectionList\"\n * [SectionOrder]=\"currentOrder\"\n * [Visible]=\"showDrawer\"\n * (SectionOrderChange)=\"onOrderChange($event)\"\n * (Closed)=\"showDrawer = false\">\n * </mj-section-manager>\n * ```\n */\n@Component({\n standalone: false,\n selector: 'mj-section-manager',\n changeDetection: ChangeDetectionStrategy.OnPush,\n templateUrl: './section-manager.component.html',\n styleUrls: ['./section-manager.component.css']\n})\nexport class MjSectionManagerComponent implements OnChanges {\n private cdr = inject(ChangeDetectorRef);\n\n /** All available sections (unordered) */\n @Input() Sections: SectionManagerItem[] = [];\n\n /** Current section order (array of section keys) */\n @Input() SectionOrder: string[] = [];\n\n /** Section keys currently in More. Empty keeps a single list. */\n @Input() MoreSectionKeys: string[] = [];\n\n /** Keys that cannot leave More (System Metadata). */\n @Input() LockedMoreKeys: string[] = [];\n\n /** Whether the drawer is visible */\n @Input() Visible = false;\n\n /** Emits the new section order when the user reorders */\n @Output() SectionOrderChange = new EventEmitter<string[]>();\n\n /** Emits when the user moves a section in or out of More. */\n @Output() MembershipChange = new EventEmitter<ChromeMembershipChange>();\n\n /** Emits when the drawer is closed */\n @Output() Closed = new EventEmitter<void>();\n\n /** Emits when the user resets to default order */\n @Output() ResetRequested = new EventEmitter<void>();\n\n /** Ordered list of sections for display */\n OrderedSections: SectionManagerItem[] = [];\n FirstClassSections: SectionManagerItem[] = [];\n MoreSections: SectionManagerItem[] = [];\n\n ngOnChanges(changes: SimpleChanges): void {\n if (changes['Sections'] || changes['SectionOrder'] || changes['MoreSectionKeys'] || changes['LockedMoreKeys']) {\n this.RebuildOrderedSections();\n }\n }\n\n get HasMoreGroup(): boolean {\n return this.MoreSections.length > 0 || this.MoreSectionKeys.length > 0;\n }\n\n IsLockedInMore(section: SectionManagerItem): boolean {\n return this.LockedMoreKeys.includes(section.SectionKey);\n }\n\n OnClose(): void {\n this.Closed.emit();\n }\n\n OnOverlayClick(event: MouseEvent): void {\n // Only close if clicking the overlay itself, not the drawer\n if (event.target === event.currentTarget) {\n this.OnClose();\n }\n }\n\n OnMoveUp(index: number): void {\n if (index <= 0) return;\n const newOrder = this.OrderedSections.map(s => s.SectionKey);\n [newOrder[index - 1], newOrder[index]] = [newOrder[index], newOrder[index - 1]];\n this.SectionOrderChange.emit(newOrder);\n this.ApplyOrder(newOrder);\n }\n\n OnMoveDown(index: number): void {\n if (index >= this.OrderedSections.length - 1) return;\n const newOrder = this.OrderedSections.map(s => s.SectionKey);\n [newOrder[index], newOrder[index + 1]] = [newOrder[index + 1], newOrder[index]];\n this.SectionOrderChange.emit(newOrder);\n this.ApplyOrder(newOrder);\n }\n\n OnMoveGroupUp(list: SectionManagerItem[], index: number): void {\n if (index <= 0) return;\n this.emitReorderedGroup(list, index, index - 1);\n }\n\n OnMoveGroupDown(list: SectionManagerItem[], index: number): void {\n if (index >= list.length - 1) return;\n this.emitReorderedGroup(list, index, index + 1);\n }\n\n OnMoveToMore(section: SectionManagerItem): void {\n if (this.MoreSectionKeys.includes(section.SectionKey)) return;\n const more = [...this.MoreSectionKeys, section.SectionKey];\n const firstClass = this.FirstClassSections\n .map((s) => s.SectionKey)\n .filter((key) => key !== section.SectionKey);\n this.MembershipChange.emit({ moreSectionKeys: more, firstClassSectionKeys: firstClass });\n }\n\n OnMoveToForm(section: SectionManagerItem): void {\n if (this.IsLockedInMore(section)) return;\n const more = this.MoreSectionKeys.filter((key) => key !== section.SectionKey);\n const firstClass = [\n ...this.FirstClassSections.map((s) => s.SectionKey),\n section.SectionKey,\n ];\n this.MembershipChange.emit({ moreSectionKeys: more, firstClassSectionKeys: firstClass });\n }\n\n private emitReorderedGroup(list: SectionManagerItem[], from: number, to: number): void {\n const keys = list.map((s) => s.SectionKey);\n [keys[from], keys[to]] = [keys[to], keys[from]];\n const other = list === this.FirstClassSections ? this.MoreSections : this.FirstClassSections;\n const combined = list === this.FirstClassSections\n ? [...keys, ...other.map((s) => s.SectionKey)]\n : [...other.map((s) => s.SectionKey), ...keys];\n this.SectionOrderChange.emit(combined);\n this.ApplyOrder(combined);\n }\n\n OnReset(): void {\n this.ResetRequested.emit();\n }\n\n GetVariantIcon(variant: PanelVariant): string {\n switch (variant) {\n case 'related-entity': return 'fa-solid fa-link';\n case 'inherited': return 'fa-solid fa-arrow-up';\n default: return 'fa-solid fa-table-cells';\n }\n }\n\n GetVariantLabel(variant: PanelVariant): string {\n switch (variant) {\n case 'related-entity': return 'Related';\n case 'inherited': return 'Inherited';\n default: return 'Fields';\n }\n }\n\n private RebuildOrderedSections(): void {\n if (!this.Sections || this.Sections.length === 0) {\n this.OrderedSections = [];\n return;\n }\n\n const sectionMap = new Map<string, SectionManagerItem>();\n for (const s of this.Sections) {\n sectionMap.set(s.SectionKey, s);\n }\n\n const ordered: SectionManagerItem[] = [];\n\n // Add sections in persisted order first\n if (this.SectionOrder && this.SectionOrder.length > 0) {\n for (const key of this.SectionOrder) {\n const section = sectionMap.get(key);\n if (section) {\n ordered.push(section);\n sectionMap.delete(key);\n }\n }\n }\n\n // Append any sections not in the persisted order (new sections)\n for (const section of sectionMap.values()) {\n ordered.push(section);\n }\n\n this.OrderedSections = ordered;\n const moreSet = new Set(this.MoreSectionKeys);\n this.FirstClassSections = ordered.filter((s) => !moreSet.has(s.SectionKey));\n this.MoreSections = ordered.filter((s) => moreSet.has(s.SectionKey));\n this.cdr.markForCheck();\n }\n\n private ApplyOrder(newOrder: string[]): void {\n const sectionMap = new Map<string, SectionManagerItem>();\n for (const s of this.OrderedSections) {\n sectionMap.set(s.SectionKey, s);\n }\n this.OrderedSections = newOrder\n .map(key => sectionMap.get(key))\n .filter((s): s is SectionManagerItem => s !== undefined);\n const moreSet = new Set(this.MoreSectionKeys);\n this.FirstClassSections = this.OrderedSections.filter((s) => !moreSet.has(s.SectionKey));\n this.MoreSections = this.OrderedSections.filter((s) => moreSet.has(s.SectionKey));\n this.cdr.markForCheck();\n }\n}\n","<!-- 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 @if (HasMoreGroup) {\n <div class=\"mj-sm-group-label\">On the form</div>\n @for (section of FirstClassSections; 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)=\"OnMoveGroupUp(FirstClassSections, 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 === FirstClassSections.length - 1\"\n (click)=\"OnMoveGroupDown(FirstClassSections, i)\"\n title=\"Move down\">\n <i class=\"fa-solid fa-chevron-down\"></i>\n </button>\n <button class=\"mj-sm-arrow mj-sm-move\"\n (click)=\"OnMoveToMore(section)\"\n title=\"Move to More\">\n <i class=\"fa-solid fa-folder-plus\"></i>\n </button>\n </div>\n </div>\n }\n @if (FirstClassSections.length === 0) {\n <div class=\"mj-sm-empty\">Nothing on the form yet.</div>\n }\n\n <div class=\"mj-sm-group-label\">More</div>\n @for (section of MoreSections; track section.SectionKey; let i = $index) {\n <div class=\"mj-sm-row is-more\">\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--more\">More</span>\n </div>\n <div class=\"mj-sm-row-actions\">\n <button class=\"mj-sm-arrow\"\n [disabled]=\"i === 0\"\n (click)=\"OnMoveGroupUp(MoreSections, 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 === MoreSections.length - 1\"\n (click)=\"OnMoveGroupDown(MoreSections, i)\"\n title=\"Move down\">\n <i class=\"fa-solid fa-chevron-down\"></i>\n </button>\n <button class=\"mj-sm-arrow mj-sm-move\"\n [disabled]=\"IsLockedInMore(section)\"\n (click)=\"OnMoveToForm(section)\"\n [title]=\"IsLockedInMore(section) ? 'Always in More' : 'Move to form'\">\n <i class=\"fa-solid fa-arrow-up-from-bracket\"></i>\n </button>\n </div>\n </div>\n }\n @if (MoreSections.length === 0) {\n <div class=\"mj-sm-empty\">Nothing in More.</div>\n }\n } @else {\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 }\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 defaults\n </button>\n </div>\n </div>\n </div>\n}\n"]}
1
+ {"version":3,"file":"section-manager.component.js","sourceRoot":"","sources":["../../../src/lib/section-manager/section-manager.component.ts","../../../src/lib/section-manager/section-manager.component.html"],"names":[],"mappings":"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","sourcesContent":["import {\n Component, Input, Output, EventEmitter,\n ChangeDetectionStrategy, ChangeDetectorRef, inject,\n OnChanges, SimpleChanges\n} from '@angular/core';\nimport { PanelVariant } from '../types/form-types';\n\n/**\n * Info about a section, passed from the container to the Section Manager drawer.\n */\nexport interface SectionManagerItem {\n SectionKey: string;\n SectionName: string;\n Variant: PanelVariant;\n Icon: string;\n}\n\nexport interface ChromeMembershipChange {\n moreSectionKeys: string[];\n firstClassSectionKeys: string[];\n}\n\n/**\n * Slide-in drawer for managing section order.\n *\n * Provides an accessible, keyboard-friendly alternative to drag-and-drop\n * for reordering form sections. Each section is displayed as a row with\n * up/down arrow buttons to change position.\n *\n * @example\n * ```html\n * <mj-section-manager\n * [Sections]=\"sectionList\"\n * [SectionOrder]=\"currentOrder\"\n * [Visible]=\"showDrawer\"\n * (SectionOrderChange)=\"onOrderChange($event)\"\n * (Closed)=\"showDrawer = false\">\n * </mj-section-manager>\n * ```\n */\n@Component({\n standalone: false,\n selector: 'mj-section-manager',\n changeDetection: ChangeDetectionStrategy.OnPush,\n templateUrl: './section-manager.component.html',\n styleUrls: ['./section-manager.component.css']\n})\nexport class MjSectionManagerComponent implements OnChanges {\n private cdr = inject(ChangeDetectorRef);\n\n /** All available sections (unordered) */\n @Input() Sections: SectionManagerItem[] = [];\n\n /** Current section order (array of section keys) */\n @Input() SectionOrder: string[] = [];\n\n /** Section keys currently in More. Empty keeps a single list. */\n @Input() MoreSectionKeys: string[] = [];\n\n /** Keys that cannot leave More (System Metadata). */\n @Input() LockedMoreKeys: string[] = [];\n\n /** Whether the drawer is visible */\n @Input() Visible = false;\n\n /** Emits the new section order when the user reorders */\n @Output() SectionOrderChange = new EventEmitter<string[]>();\n\n /** Emits when the user moves a section in or out of More. */\n @Output() MembershipChange = new EventEmitter<ChromeMembershipChange>();\n\n /** Emits when the drawer is closed */\n @Output() Closed = new EventEmitter<void>();\n\n /** Emits when the user resets to default order */\n @Output() ResetRequested = new EventEmitter<void>();\n\n /** Ordered list of sections for display */\n OrderedSections: SectionManagerItem[] = [];\n FirstClassSections: SectionManagerItem[] = [];\n MoreSections: SectionManagerItem[] = [];\n\n ngOnChanges(changes: SimpleChanges): void {\n if (changes['Sections'] || changes['SectionOrder'] || changes['MoreSectionKeys'] || changes['LockedMoreKeys']) {\n this.RebuildOrderedSections();\n }\n }\n\n get HasMoreGroup(): boolean {\n return this.MoreSections.length > 0 || this.MoreSectionKeys.length > 0;\n }\n\n IsLockedInMore(section: SectionManagerItem): boolean {\n return this.LockedMoreKeys.includes(section.SectionKey);\n }\n\n OnClose(): void {\n this.Closed.emit();\n }\n\n OnOverlayClick(event: MouseEvent): void {\n // Only close if clicking the overlay itself, not the drawer\n if (event.target === event.currentTarget) {\n this.OnClose();\n }\n }\n\n OnMoveUp(index: number): void {\n if (index <= 0) return;\n const newOrder = this.OrderedSections.map(s => s.SectionKey);\n [newOrder[index - 1], newOrder[index]] = [newOrder[index], newOrder[index - 1]];\n this.SectionOrderChange.emit(newOrder);\n this.ApplyOrder(newOrder);\n }\n\n OnMoveDown(index: number): void {\n if (index >= this.OrderedSections.length - 1) return;\n const newOrder = this.OrderedSections.map(s => s.SectionKey);\n [newOrder[index], newOrder[index + 1]] = [newOrder[index + 1], newOrder[index]];\n this.SectionOrderChange.emit(newOrder);\n this.ApplyOrder(newOrder);\n }\n\n OnMoveGroupUp(list: SectionManagerItem[], index: number): void {\n if (index <= 0) return;\n this.emitReorderedGroup(list, index, index - 1);\n }\n\n OnMoveGroupDown(list: SectionManagerItem[], index: number): void {\n if (index >= list.length - 1) return;\n this.emitReorderedGroup(list, index, index + 1);\n }\n\n OnMoveToMore(section: SectionManagerItem): void {\n if (this.MoreSectionKeys.includes(section.SectionKey)) return;\n const more = [...this.MoreSectionKeys, section.SectionKey];\n const firstClass = this.FirstClassSections\n .map((s) => s.SectionKey)\n .filter((key) => key !== section.SectionKey);\n this.MembershipChange.emit({ moreSectionKeys: more, firstClassSectionKeys: firstClass });\n }\n\n OnMoveToForm(section: SectionManagerItem): void {\n if (this.IsLockedInMore(section)) return;\n const more = this.MoreSectionKeys.filter((key) => key !== section.SectionKey);\n const firstClass = [\n ...this.FirstClassSections.map((s) => s.SectionKey),\n section.SectionKey,\n ];\n this.MembershipChange.emit({ moreSectionKeys: more, firstClassSectionKeys: firstClass });\n }\n\n private emitReorderedGroup(list: SectionManagerItem[], from: number, to: number): void {\n const keys = list.map((s) => s.SectionKey);\n [keys[from], keys[to]] = [keys[to], keys[from]];\n const other = list === this.FirstClassSections ? this.MoreSections : this.FirstClassSections;\n const combined = list === this.FirstClassSections\n ? [...keys, ...other.map((s) => s.SectionKey)]\n : [...other.map((s) => s.SectionKey), ...keys];\n this.SectionOrderChange.emit(combined);\n this.ApplyOrder(combined);\n }\n\n OnReset(): void {\n this.ResetRequested.emit();\n }\n\n GetVariantIcon(variant: PanelVariant): string {\n switch (variant) {\n case 'related-entity': return 'fa-solid fa-link';\n case 'inherited': return 'fa-solid fa-arrow-up';\n default: return 'fa-solid fa-table-cells';\n }\n }\n\n GetVariantLabel(variant: PanelVariant): string {\n switch (variant) {\n case 'related-entity': return 'Related';\n case 'inherited': return 'Inherited';\n default: return 'Fields';\n }\n }\n\n private RebuildOrderedSections(): void {\n if (!this.Sections || this.Sections.length === 0) {\n this.OrderedSections = [];\n return;\n }\n\n const sectionMap = new Map<string, SectionManagerItem>();\n for (const s of this.Sections) {\n sectionMap.set(s.SectionKey, s);\n }\n\n const ordered: SectionManagerItem[] = [];\n\n // Add sections in persisted order first\n if (this.SectionOrder && this.SectionOrder.length > 0) {\n for (const key of this.SectionOrder) {\n const section = sectionMap.get(key);\n if (section) {\n ordered.push(section);\n sectionMap.delete(key);\n }\n }\n }\n\n // Append any sections not in the persisted order (new sections)\n for (const section of sectionMap.values()) {\n ordered.push(section);\n }\n\n this.OrderedSections = ordered;\n const moreSet = new Set(this.MoreSectionKeys);\n this.FirstClassSections = ordered.filter((s) => !moreSet.has(s.SectionKey));\n this.MoreSections = ordered.filter((s) => moreSet.has(s.SectionKey));\n this.cdr.markForCheck();\n }\n\n private ApplyOrder(newOrder: string[]): void {\n const sectionMap = new Map<string, SectionManagerItem>();\n for (const s of this.OrderedSections) {\n sectionMap.set(s.SectionKey, s);\n }\n this.OrderedSections = newOrder\n .map(key => sectionMap.get(key))\n .filter((s): s is SectionManagerItem => s !== undefined);\n const moreSet = new Set(this.MoreSectionKeys);\n this.FirstClassSections = this.OrderedSections.filter((s) => !moreSet.has(s.SectionKey));\n this.MoreSections = this.OrderedSections.filter((s) => moreSet.has(s.SectionKey));\n this.cdr.markForCheck();\n }\n}\n","<!-- 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 @if (HasMoreGroup) {\n <div class=\"mj-sm-group-label\">On the form</div>\n @for (section of FirstClassSections; 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)=\"OnMoveGroupUp(FirstClassSections, 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 === FirstClassSections.length - 1\"\n (click)=\"OnMoveGroupDown(FirstClassSections, i)\"\n title=\"Move down\">\n <i class=\"fa-solid fa-chevron-down\"></i>\n </button>\n <button class=\"mj-sm-arrow mj-sm-move\"\n (click)=\"OnMoveToMore(section)\"\n title=\"Move to More\">\n <i class=\"fa-solid fa-folder-plus\"></i>\n </button>\n </div>\n </div>\n }\n @if (FirstClassSections.length === 0) {\n <div class=\"mj-sm-empty\">Nothing on the form yet.</div>\n }\n\n <div class=\"mj-sm-group-label\">More</div>\n @for (section of MoreSections; track section.SectionKey; let i = $index) {\n <div class=\"mj-sm-row is-more\">\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--more\">More</span>\n </div>\n <div class=\"mj-sm-row-actions\">\n <button class=\"mj-sm-arrow\"\n [disabled]=\"i === 0\"\n (click)=\"OnMoveGroupUp(MoreSections, 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 === MoreSections.length - 1\"\n (click)=\"OnMoveGroupDown(MoreSections, i)\"\n title=\"Move down\">\n <i class=\"fa-solid fa-chevron-down\"></i>\n </button>\n <button class=\"mj-sm-arrow mj-sm-move\"\n [disabled]=\"IsLockedInMore(section)\"\n (click)=\"OnMoveToForm(section)\"\n [title]=\"IsLockedInMore(section) ? 'Always in More' : 'Move to form'\">\n <i class=\"fa-solid fa-arrow-up-from-bracket\"></i>\n </button>\n </div>\n </div>\n }\n @if (MoreSections.length === 0) {\n <div class=\"mj-sm-empty\">Nothing in More.</div>\n }\n } @else {\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 }\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 defaults\n </button>\n </div>\n </div>\n </div>\n}\n"]}