@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,76 @@
1
+ import { BaseSingleton } from '@memberjunction/global';
2
+ /**
3
+ * Per (host-entity, FK-column) user overrides for the foreign-key search dropdown
4
+ * rendered by {@link MjFormFieldComponent}. Every field is optional — an entry
5
+ * exists only for the specific dropdowns a user has actively customized. Most users
6
+ * never customize anything, so the stored blob stays tiny (often empty).
7
+ */
8
+ export interface LinkedFieldOption {
9
+ /**
10
+ * Related-entity field code name to search against. When unset, the dropdown
11
+ * searches the related entity's Name field (the default).
12
+ */
13
+ searchField?: string;
14
+ /**
15
+ * Related-entity field code name to sort the dropdown by. `null` (or unset) means
16
+ * natural order (whatever order the cache / DB returned).
17
+ */
18
+ sortField?: string | null;
19
+ /** Sort direction; only meaningful when {@link sortField} is set. */
20
+ sortDir?: 'asc' | 'desc';
21
+ /**
22
+ * User-resized column widths in pixels, keyed by related-entity field code name
23
+ * (the Name column uses its own field code name as the key). Columns the user
24
+ * never resized are absent and fall back to content-based sizing.
25
+ */
26
+ colWidths?: Record<string, number>;
27
+ /**
28
+ * Ordered list of extra-column field code names to display (besides the always-shown
29
+ * Name column). When set, fully overrides the default `DefaultInView`-derived columns —
30
+ * letting the user add normally-hidden fields (e.g. ID) or remove default ones.
31
+ * `undefined` = use the default column set. An empty array = only the Name column.
32
+ */
33
+ visibleFields?: string[];
34
+ }
35
+ /**
36
+ * The whole preference blob: a map of `"<hostEntity>|<fkField>"` (lowercased) →
37
+ * {@link LinkedFieldOption}. Persisted as a single `MJ: User Settings` row so we
38
+ * never spam the settings table with one key per field.
39
+ */
40
+ export type LinkedFieldUserOptions = Record<string, LinkedFieldOption>;
41
+ /**
42
+ * Reads/writes the user's per-FK-dropdown preferences (search scope, sort, column
43
+ * widths) through {@link UserInfoEngine} — server-side, cross-device, never
44
+ * `localStorage`. All writes are debounced (UI handlers can fire freely).
45
+ *
46
+ * Everything funnels through ONE settings key ({@link STORAGE_KEY}); reads are
47
+ * synchronous in-memory cache hits via `UserInfoEngine.GetSetting`, so calling
48
+ * `Get(...)` on every focus is cheap.
49
+ *
50
+ * Implemented as a {@link BaseSingleton} so the in-process parse cache is shared.
51
+ */
52
+ export declare class LinkedFieldOptionsStore extends BaseSingleton<LinkedFieldOptionsStore> {
53
+ /** The single User Settings key holding the entire preference blob (v1 shape). */
54
+ private static readonly STORAGE_KEY;
55
+ protected constructor();
56
+ static get Instance(): LinkedFieldOptionsStore;
57
+ /** Compose the per-field map key from the host entity + FK column names. */
58
+ private buildKey;
59
+ /** Parse the full blob from the user setting (empty object when unset / malformed). */
60
+ private readAll;
61
+ /** Persist the full blob (debounced fire-and-forget). */
62
+ private writeAll;
63
+ /** Read the saved options for a (host entity, FK field) pair, or undefined. */
64
+ Get(entityName: string, fieldName: string): LinkedFieldOption | undefined;
65
+ /** Merge a shallow patch into a field's options and persist. */
66
+ private patch;
67
+ /** Persist the user's chosen search scope field for this dropdown. */
68
+ SetSearchField(entityName: string, fieldName: string, searchField: string): void;
69
+ /** Persist the user's chosen sort column + direction (or clear with sortField=null). */
70
+ SetSort(entityName: string, fieldName: string, sortField: string | null, sortDir: 'asc' | 'desc'): void;
71
+ /** Persist one column's resized width (px), merging into any existing widths. */
72
+ SetColWidth(entityName: string, fieldName: string, colField: string, width: number): void;
73
+ /** Persist the user's chosen set/order of visible extra columns. */
74
+ SetVisibleFields(entityName: string, fieldName: string, fields: string[]): void;
75
+ }
76
+ //# sourceMappingURL=linked-field-options.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"linked-field-options.d.ts","sourceRoot":"","sources":["../../../src/lib/field/linked-field-options.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAGvD;;;;;GAKG;AACH,MAAM,WAAW,iBAAiB;IAChC;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,qEAAqE;IACrE,OAAO,CAAC,EAAE,KAAK,GAAG,MAAM,CAAC;IACzB;;;;OAIG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACnC;;;;;OAKG;IACH,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;CAC1B;AAED;;;;GAIG;AACH,MAAM,MAAM,sBAAsB,GAAG,MAAM,CAAC,MAAM,EAAE,iBAAiB,CAAC,CAAC;AAEvE;;;;;;;;;;GAUG;AACH,qBAAa,uBAAwB,SAAQ,aAAa,CAAC,uBAAuB,CAAC;IACjF,kFAAkF;IAClF,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,WAAW,CAAkC;IAErE,SAAS;IAIT,WAAkB,QAAQ,IAAI,uBAAuB,CAEpD;IAED,4EAA4E;IAC5E,OAAO,CAAC,QAAQ;IAIhB,uFAAuF;IACvF,OAAO,CAAC,OAAO;IAWf,yDAAyD;IACzD,OAAO,CAAC,QAAQ;IAOhB,+EAA+E;IACxE,GAAG,CAAC,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,iBAAiB,GAAG,SAAS;IAKhF,gEAAgE;IAChE,OAAO,CAAC,KAAK;IAUb,sEAAsE;IAC/D,cAAc,CAAC,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,IAAI;IAIvF,wFAAwF;IACjF,OAAO,CAAC,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,GAAG,MAAM,GAAG,IAAI;IAI9G,iFAAiF;IAC1E,WAAW,CAAC,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI;IAMhG,oEAAoE;IAC7D,gBAAgB,CAAC,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,IAAI;CAGvF"}
@@ -0,0 +1,80 @@
1
+ import { BaseSingleton } from '@memberjunction/global';
2
+ import { UserInfoEngine } from '@memberjunction/core-entities';
3
+ /**
4
+ * Reads/writes the user's per-FK-dropdown preferences (search scope, sort, column
5
+ * widths) through {@link UserInfoEngine} — server-side, cross-device, never
6
+ * `localStorage`. All writes are debounced (UI handlers can fire freely).
7
+ *
8
+ * Everything funnels through ONE settings key ({@link STORAGE_KEY}); reads are
9
+ * synchronous in-memory cache hits via `UserInfoEngine.GetSetting`, so calling
10
+ * `Get(...)` on every focus is cheap.
11
+ *
12
+ * Implemented as a {@link BaseSingleton} so the in-process parse cache is shared.
13
+ */
14
+ export class LinkedFieldOptionsStore extends BaseSingleton {
15
+ /** The single User Settings key holding the entire preference blob (v1 shape). */
16
+ static STORAGE_KEY = 'mj.linkedFieldUserOptions.v1';
17
+ constructor() {
18
+ super();
19
+ }
20
+ static get Instance() {
21
+ return super.getInstance();
22
+ }
23
+ /** Compose the per-field map key from the host entity + FK column names. */
24
+ buildKey(entityName, fieldName) {
25
+ return `${(entityName ?? '').trim().toLowerCase()}|${(fieldName ?? '').trim().toLowerCase()}`;
26
+ }
27
+ /** Parse the full blob from the user setting (empty object when unset / malformed). */
28
+ readAll() {
29
+ const raw = UserInfoEngine.Instance.GetSetting(LinkedFieldOptionsStore.STORAGE_KEY);
30
+ if (!raw)
31
+ return {};
32
+ try {
33
+ const parsed = JSON.parse(raw);
34
+ return parsed && typeof parsed === 'object' ? parsed : {};
35
+ }
36
+ catch {
37
+ return {};
38
+ }
39
+ }
40
+ /** Persist the full blob (debounced fire-and-forget). */
41
+ writeAll(all) {
42
+ UserInfoEngine.Instance.SetSettingDebounced(LinkedFieldOptionsStore.STORAGE_KEY, JSON.stringify(all));
43
+ }
44
+ /** Read the saved options for a (host entity, FK field) pair, or undefined. */
45
+ Get(entityName, fieldName) {
46
+ if (!entityName || !fieldName)
47
+ return undefined;
48
+ return this.readAll()[this.buildKey(entityName, fieldName)];
49
+ }
50
+ /** Merge a shallow patch into a field's options and persist. */
51
+ patch(entityName, fieldName, mutate) {
52
+ if (!entityName || !fieldName)
53
+ return; // no host context → don't persist
54
+ const all = this.readAll();
55
+ const key = this.buildKey(entityName, fieldName);
56
+ const opt = { ...(all[key] ?? {}) };
57
+ mutate(opt);
58
+ all[key] = opt;
59
+ this.writeAll(all);
60
+ }
61
+ /** Persist the user's chosen search scope field for this dropdown. */
62
+ SetSearchField(entityName, fieldName, searchField) {
63
+ this.patch(entityName, fieldName, o => { o.searchField = searchField; });
64
+ }
65
+ /** Persist the user's chosen sort column + direction (or clear with sortField=null). */
66
+ SetSort(entityName, fieldName, sortField, sortDir) {
67
+ this.patch(entityName, fieldName, o => { o.sortField = sortField; o.sortDir = sortDir; });
68
+ }
69
+ /** Persist one column's resized width (px), merging into any existing widths. */
70
+ SetColWidth(entityName, fieldName, colField, width) {
71
+ this.patch(entityName, fieldName, o => {
72
+ o.colWidths = { ...(o.colWidths ?? {}), [colField]: Math.round(width) };
73
+ });
74
+ }
75
+ /** Persist the user's chosen set/order of visible extra columns. */
76
+ SetVisibleFields(entityName, fieldName, fields) {
77
+ this.patch(entityName, fieldName, o => { o.visibleFields = [...fields]; });
78
+ }
79
+ }
80
+ //# sourceMappingURL=linked-field-options.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"linked-field-options.js","sourceRoot":"","sources":["../../../src/lib/field/linked-field-options.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AACvD,OAAO,EAAE,cAAc,EAAE,MAAM,+BAA+B,CAAC;AA2C/D;;;;;;;;;;GAUG;AACH,MAAM,OAAO,uBAAwB,SAAQ,aAAsC;IACjF,kFAAkF;IAC1E,MAAM,CAAU,WAAW,GAAG,8BAA8B,CAAC;IAErE;QACE,KAAK,EAAE,CAAC;IACV,CAAC;IAEM,MAAM,KAAK,QAAQ;QACxB,OAAO,KAAK,CAAC,WAAW,EAA2B,CAAC;IACtD,CAAC;IAED,4EAA4E;IACpE,QAAQ,CAAC,UAAkB,EAAE,SAAiB;QACpD,OAAO,GAAG,CAAC,UAAU,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,IAAI,CAAC,SAAS,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,EAAE,CAAC;IAChG,CAAC;IAED,uFAAuF;IAC/E,OAAO;QACb,MAAM,GAAG,GAAG,cAAc,CAAC,QAAQ,CAAC,UAAU,CAAC,uBAAuB,CAAC,WAAW,CAAC,CAAC;QACpF,IAAI,CAAC,GAAG;YAAE,OAAO,EAAE,CAAC;QACpB,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;YAC/B,OAAO,MAAM,IAAI,OAAO,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAE,MAAiC,CAAC,CAAC,CAAC,EAAE,CAAC;QACxF,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,EAAE,CAAC;QACZ,CAAC;IACH,CAAC;IAED,yDAAyD;IACjD,QAAQ,CAAC,GAA2B;QAC1C,cAAc,CAAC,QAAQ,CAAC,mBAAmB,CACzC,uBAAuB,CAAC,WAAW,EACnC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,CACpB,CAAC;IACJ,CAAC;IAED,+EAA+E;IACxE,GAAG,CAAC,UAAkB,EAAE,SAAiB;QAC9C,IAAI,CAAC,UAAU,IAAI,CAAC,SAAS;YAAE,OAAO,SAAS,CAAC;QAChD,OAAO,IAAI,CAAC,OAAO,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,UAAU,EAAE,SAAS,CAAC,CAAC,CAAC;IAC9D,CAAC;IAED,gEAAgE;IACxD,KAAK,CAAC,UAAkB,EAAE,SAAiB,EAAE,MAAsC;QACzF,IAAI,CAAC,UAAU,IAAI,CAAC,SAAS;YAAE,OAAO,CAAC,kCAAkC;QACzE,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,EAAE,CAAC;QAC3B,MAAM,GAAG,GAAG,IAAI,CAAC,QAAQ,CAAC,UAAU,EAAE,SAAS,CAAC,CAAC;QACjD,MAAM,GAAG,GAAsB,EAAE,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,EAAE,CAAC;QACvD,MAAM,CAAC,GAAG,CAAC,CAAC;QACZ,GAAG,CAAC,GAAG,CAAC,GAAG,GAAG,CAAC;QACf,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;IACrB,CAAC;IAED,sEAAsE;IAC/D,cAAc,CAAC,UAAkB,EAAE,SAAiB,EAAE,WAAmB;QAC9E,IAAI,CAAC,KAAK,CAAC,UAAU,EAAE,SAAS,EAAE,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,WAAW,GAAG,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC;IAC3E,CAAC;IAED,wFAAwF;IACjF,OAAO,CAAC,UAAkB,EAAE,SAAiB,EAAE,SAAwB,EAAE,OAAuB;QACrG,IAAI,CAAC,KAAK,CAAC,UAAU,EAAE,SAAS,EAAE,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,SAAS,GAAG,SAAS,CAAC,CAAC,CAAC,CAAC,OAAO,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC;IAC5F,CAAC;IAED,iFAAiF;IAC1E,WAAW,CAAC,UAAkB,EAAE,SAAiB,EAAE,QAAgB,EAAE,KAAa;QACvF,IAAI,CAAC,KAAK,CAAC,UAAU,EAAE,SAAS,EAAE,CAAC,CAAC,EAAE;YACpC,CAAC,CAAC,SAAS,GAAG,EAAE,GAAG,CAAC,CAAC,CAAC,SAAS,IAAI,EAAE,CAAC,EAAE,CAAC,QAAQ,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE,CAAC;QAC1E,CAAC,CAAC,CAAC;IACL,CAAC;IAED,oEAAoE;IAC7D,gBAAgB,CAAC,UAAkB,EAAE,SAAiB,EAAE,MAAgB;QAC7E,IAAI,CAAC,KAAK,CAAC,UAAU,EAAE,SAAS,EAAE,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,aAAa,GAAG,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAC7E,CAAC","sourcesContent":["import { BaseSingleton } from '@memberjunction/global';\nimport { UserInfoEngine } from '@memberjunction/core-entities';\n\n/**\n * Per (host-entity, FK-column) user overrides for the foreign-key search dropdown\n * rendered by {@link MjFormFieldComponent}. Every field is optional — an entry\n * exists only for the specific dropdowns a user has actively customized. Most users\n * never customize anything, so the stored blob stays tiny (often empty).\n */\nexport interface LinkedFieldOption {\n /**\n * Related-entity field code name to search against. When unset, the dropdown\n * searches the related entity's Name field (the default).\n */\n searchField?: string;\n /**\n * Related-entity field code name to sort the dropdown by. `null` (or unset) means\n * natural order (whatever order the cache / DB returned).\n */\n sortField?: string | null;\n /** Sort direction; only meaningful when {@link sortField} is set. */\n sortDir?: 'asc' | 'desc';\n /**\n * User-resized column widths in pixels, keyed by related-entity field code name\n * (the Name column uses its own field code name as the key). Columns the user\n * never resized are absent and fall back to content-based sizing.\n */\n colWidths?: Record<string, number>;\n /**\n * Ordered list of extra-column field code names to display (besides the always-shown\n * Name column). When set, fully overrides the default `DefaultInView`-derived columns —\n * letting the user add normally-hidden fields (e.g. ID) or remove default ones.\n * `undefined` = use the default column set. An empty array = only the Name column.\n */\n visibleFields?: string[];\n}\n\n/**\n * The whole preference blob: a map of `\"<hostEntity>|<fkField>\"` (lowercased) →\n * {@link LinkedFieldOption}. Persisted as a single `MJ: User Settings` row so we\n * never spam the settings table with one key per field.\n */\nexport type LinkedFieldUserOptions = Record<string, LinkedFieldOption>;\n\n/**\n * Reads/writes the user's per-FK-dropdown preferences (search scope, sort, column\n * widths) through {@link UserInfoEngine} — server-side, cross-device, never\n * `localStorage`. All writes are debounced (UI handlers can fire freely).\n *\n * Everything funnels through ONE settings key ({@link STORAGE_KEY}); reads are\n * synchronous in-memory cache hits via `UserInfoEngine.GetSetting`, so calling\n * `Get(...)` on every focus is cheap.\n *\n * Implemented as a {@link BaseSingleton} so the in-process parse cache is shared.\n */\nexport class LinkedFieldOptionsStore extends BaseSingleton<LinkedFieldOptionsStore> {\n /** The single User Settings key holding the entire preference blob (v1 shape). */\n private static readonly STORAGE_KEY = 'mj.linkedFieldUserOptions.v1';\n\n protected constructor() {\n super();\n }\n\n public static get Instance(): LinkedFieldOptionsStore {\n return super.getInstance<LinkedFieldOptionsStore>();\n }\n\n /** Compose the per-field map key from the host entity + FK column names. */\n private buildKey(entityName: string, fieldName: string): string {\n return `${(entityName ?? '').trim().toLowerCase()}|${(fieldName ?? '').trim().toLowerCase()}`;\n }\n\n /** Parse the full blob from the user setting (empty object when unset / malformed). */\n private readAll(): LinkedFieldUserOptions {\n const raw = UserInfoEngine.Instance.GetSetting(LinkedFieldOptionsStore.STORAGE_KEY);\n if (!raw) return {};\n try {\n const parsed = JSON.parse(raw);\n return parsed && typeof parsed === 'object' ? (parsed as LinkedFieldUserOptions) : {};\n } catch {\n return {};\n }\n }\n\n /** Persist the full blob (debounced fire-and-forget). */\n private writeAll(all: LinkedFieldUserOptions): void {\n UserInfoEngine.Instance.SetSettingDebounced(\n LinkedFieldOptionsStore.STORAGE_KEY,\n JSON.stringify(all)\n );\n }\n\n /** Read the saved options for a (host entity, FK field) pair, or undefined. */\n public Get(entityName: string, fieldName: string): LinkedFieldOption | undefined {\n if (!entityName || !fieldName) return undefined;\n return this.readAll()[this.buildKey(entityName, fieldName)];\n }\n\n /** Merge a shallow patch into a field's options and persist. */\n private patch(entityName: string, fieldName: string, mutate: (o: LinkedFieldOption) => void): void {\n if (!entityName || !fieldName) return; // no host context → don't persist\n const all = this.readAll();\n const key = this.buildKey(entityName, fieldName);\n const opt: LinkedFieldOption = { ...(all[key] ?? {}) };\n mutate(opt);\n all[key] = opt;\n this.writeAll(all);\n }\n\n /** Persist the user's chosen search scope field for this dropdown. */\n public SetSearchField(entityName: string, fieldName: string, searchField: string): void {\n this.patch(entityName, fieldName, o => { o.searchField = searchField; });\n }\n\n /** Persist the user's chosen sort column + direction (or clear with sortField=null). */\n public SetSort(entityName: string, fieldName: string, sortField: string | null, sortDir: 'asc' | 'desc'): void {\n this.patch(entityName, fieldName, o => { o.sortField = sortField; o.sortDir = sortDir; });\n }\n\n /** Persist one column's resized width (px), merging into any existing widths. */\n public SetColWidth(entityName: string, fieldName: string, colField: string, width: number): void {\n this.patch(entityName, fieldName, o => {\n o.colWidths = { ...(o.colWidths ?? {}), [colField]: Math.round(width) };\n });\n }\n\n /** Persist the user's chosen set/order of visible extra columns. */\n public SetVisibleFields(entityName: string, fieldName: string, fields: string[]): void {\n this.patch(entityName, fieldName, o => { o.visibleFields = [...fields]; });\n }\n}\n"]}
@@ -0,0 +1,159 @@
1
+ import { EventEmitter, AfterViewInit, OnDestroy } from '@angular/core';
2
+ import { CompositeKey, BaseEntity } from '@memberjunction/core';
3
+ import { BaseAngularComponent } from '@memberjunction/ng-base-types';
4
+ import { BaseFormComponent } from '../base-form-component';
5
+ import { EntityFormConfig } from '../types/entity-form-config';
6
+ import { FormNavigationEvent } from '../types/navigation-events';
7
+ import { FormNotificationEvent, RecordSavedEvent, RecordDeletedEvent, RecordSaveFailedEvent, ValidationFailedEvent } from '../types/form-types';
8
+ import * as i0 from "@angular/core";
9
+ /**
10
+ * Presentation-agnostic host that renders ANY MemberJunction entity form —
11
+ * generated (`@RegisterClass` / CodeGen), custom (`*Extended`), or interactive
12
+ * (`EntityFormOverride`) — for a given record.
13
+ *
14
+ * This is the shared core extracted from Explorer's `SingleRecordComponent`. It
15
+ * encapsulates the full lifecycle: **resolve** which form to use (via
16
+ * {@link FormResolverService}, honoring variant overrides) → **load** the
17
+ * record (or accept a pre-loaded one) → **create** the form component
18
+ * dynamically → **bind** record / EditMode / Config / variants → **re-emit**
19
+ * the form's events → **tear down** cleanly.
20
+ *
21
+ * It has ZERO routing and ZERO Explorer dependencies — it only emits events.
22
+ * The surrounding surface (a full-page tab in Explorer, a dialog, or a
23
+ * slide-in) decides what to do with `Navigate`, `Notification`, etc.
24
+ *
25
+ * @example Explorer tab (thin wrapper subscribes + maps to NavigationService)
26
+ * ```html
27
+ * <mj-entity-form-host [EntityName]="entityName" [PrimaryKey]="pk"
28
+ * (Navigate)="handleNavigation($event)" (Saved)="onSaved($event)">
29
+ * </mj-entity-form-host>
30
+ * ```
31
+ *
32
+ * @example Bind a pre-loaded record (dialog/slide-in)
33
+ * ```html
34
+ * <mj-entity-form-host [Record]="myEntity" [Config]="DIALOG_FORM_CONFIG"></mj-entity-form-host>
35
+ * ```
36
+ */
37
+ export declare class MjEntityFormHostComponent extends BaseAngularComponent implements AfterViewInit, OnDestroy {
38
+ private cdr;
39
+ private formResolver;
40
+ /** Anchor for the dynamically-created form component. Always present in the DOM. */
41
+ private anchor;
42
+ private _entityName;
43
+ /** Entity name to load a record for. Ignored when {@link Record} is supplied. */
44
+ set EntityName(value: string | null);
45
+ get EntityName(): string | null;
46
+ private _primaryKey;
47
+ /** Primary key of the record to load. Empty key → new record. */
48
+ set PrimaryKey(value: CompositeKey);
49
+ get PrimaryKey(): CompositeKey;
50
+ private _record;
51
+ /**
52
+ * Pre-loaded record to bind directly. When supplied, the host skips the
53
+ * load step entirely and mounts the resolved form against this instance —
54
+ * `EntityName`/`PrimaryKey` are not required.
55
+ */
56
+ set Record(value: BaseEntity | null);
57
+ get Record(): BaseEntity | null;
58
+ /** New-record default values: URL-segment string or a plain object. */
59
+ NewRecordValues: string | Record<string, unknown> | null;
60
+ /**
61
+ * Render a single registered form **section** (a `BaseFormSectionComponent`
62
+ * registered as `'<EntityName>.<SectionName>'`) instead of the full form.
63
+ * When set, the full-form resolver / variant picker / toolbar container are
64
+ * bypassed — the section component renders its own fields and the host saves
65
+ * the record directly. Used for compact, focused editors (e.g. a quick-edit
66
+ * grid row). Leave null for the normal full-form behavior.
67
+ */
68
+ SectionName: string | null;
69
+ private _editMode;
70
+ /** Force edit mode. When omitted, new records start in edit, existing in read. */
71
+ set EditMode(value: boolean | null);
72
+ get EditMode(): boolean | null;
73
+ /** Per-instance form presentation config (toolbar, sections, width, links). */
74
+ Config: EntityFormConfig | null;
75
+ /** Form navigation request (record link, new record, hierarchy, email, external, dismiss). */
76
+ Navigate: EventEmitter<FormNavigationEvent>;
77
+ /** User-facing notification request (success/error/warning/info). */
78
+ Notification: EventEmitter<FormNotificationEvent>;
79
+ /** Emitted after the record is successfully saved (carries the live entity). */
80
+ Saved: EventEmitter<BaseEntity<unknown>>;
81
+ /** Re-emitted form `RecordSaved` (richer payload). */
82
+ RecordSaved: EventEmitter<RecordSavedEvent>;
83
+ /** Re-emitted form `RecordDeleted`. */
84
+ RecordDeleted: EventEmitter<RecordDeletedEvent>;
85
+ /** Re-emitted form `RecordSaveFailed`. */
86
+ RecordSaveFailed: EventEmitter<RecordSaveFailedEvent>;
87
+ /** Re-emitted form `ValidationFailed`. */
88
+ ValidationFailed: EventEmitter<ValidationFailedEvent>;
89
+ /** Re-emitted form `RecordReady` (record fully initialized). */
90
+ RecordReady: EventEmitter<BaseEntity<unknown>>;
91
+ /** Form asked to be dismissed (e.g. Discard on a brand-new record). */
92
+ Dismissed: EventEmitter<void>;
93
+ /** Initial load + mount finished (success path). */
94
+ LoadComplete: EventEmitter<void>;
95
+ /** Load failed; carries a user-facing title + detail. */
96
+ LoadError: EventEmitter<{
97
+ title: string;
98
+ detail: string;
99
+ }>;
100
+ /** The live form instance, emitted right after it's created (for power-user wiring). */
101
+ FormCreated: EventEmitter<BaseFormComponent>;
102
+ loading: boolean;
103
+ errorTitle: string | null;
104
+ errorDetail: string | null;
105
+ private _formComponentRef;
106
+ private _sectionRef;
107
+ private _isSection;
108
+ private _currentRecord;
109
+ private _saveHandlerSub;
110
+ private _formEventSubs;
111
+ private _viewInitialized;
112
+ /** The live form component instance, or null before mount / after teardown. */
113
+ get Form(): BaseFormComponent | null;
114
+ /** Whether the bound record has unsaved changes. */
115
+ get Dirty(): boolean;
116
+ ngAfterViewInit(): void;
117
+ ngOnDestroy(): void;
118
+ /**
119
+ * Save the bound record. In full-form mode this runs the form's save
120
+ * pipeline (validation, pending records, notifications); in section mode the
121
+ * section component has no pipeline, so we save the record directly (the
122
+ * record's own save event still drives the `Saved` output).
123
+ */
124
+ Save(): Promise<boolean>;
125
+ /** Cancel edits (revert). Full-form mode uses the form's cancel pipeline. */
126
+ Cancel(): void;
127
+ private reload;
128
+ private loadAndMount;
129
+ /**
130
+ * The config to bind for a given record. For brand-new (unsaved) records we hide the
131
+ * `systemMetadata` section by default — its fields (audit timestamps, IDs, etc.) are
132
+ * empty and irrelevant until the record exists. Doesn't mutate the consumer's config,
133
+ * and defers entirely to a consumer that uses an explicit `VisibleSectionKeys` allowlist.
134
+ */
135
+ private effectiveConfigFor;
136
+ /**
137
+ * Section mode: resolve a `BaseFormSectionComponent` registered as
138
+ * `'<entity>.<section>'`, mount it, and bind the record. No resolver,
139
+ * variants, container, or form-event surface — section components are bare
140
+ * field editors; the host saves the record directly (see {@link Save}).
141
+ */
142
+ private mountSection;
143
+ /** Resolve the BaseEntity to bind: the supplied instance, or a freshly loaded/new one. */
144
+ private obtainRecord;
145
+ /** Push variant list + active selection and wire the variant-switch handler. */
146
+ private applyVariants;
147
+ /** Subscribe to the form's outputs and re-emit them through the host. */
148
+ private subscribeToFormEvents;
149
+ private applyNewRecordValues;
150
+ /** Set a field with TSType-aware coercion (shared by string + object paths). */
151
+ private setTypedField;
152
+ private fail;
153
+ private compositeKeysEqual;
154
+ private cleanupFormSubs;
155
+ private teardown;
156
+ static ɵfac: i0.ɵɵFactoryDeclaration<MjEntityFormHostComponent, never>;
157
+ static ɵcmp: i0.ɵɵComponentDeclaration<MjEntityFormHostComponent, "mj-entity-form-host", never, { "EntityName": { "alias": "EntityName"; "required": false; }; "PrimaryKey": { "alias": "PrimaryKey"; "required": false; }; "Record": { "alias": "Record"; "required": false; }; "NewRecordValues": { "alias": "NewRecordValues"; "required": false; }; "SectionName": { "alias": "SectionName"; "required": false; }; "EditMode": { "alias": "EditMode"; "required": false; }; "Config": { "alias": "Config"; "required": false; }; }, { "Navigate": "Navigate"; "Notification": "Notification"; "Saved": "Saved"; "RecordSaved": "RecordSaved"; "RecordDeleted": "RecordDeleted"; "RecordSaveFailed": "RecordSaveFailed"; "ValidationFailed": "ValidationFailed"; "RecordReady": "RecordReady"; "Dismissed": "Dismissed"; "LoadComplete": "LoadComplete"; "LoadError": "LoadError"; "FormCreated": "FormCreated"; }, never, never, false, never>;
158
+ }
159
+ //# sourceMappingURL=entity-form-host.component.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"entity-form-host.component.d.ts","sourceRoot":"","sources":["../../../src/lib/host/entity-form-host.component.ts"],"names":[],"mappings":"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"}