@mintplayer/ng-spark 22.23.0 → 22.25.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 (71) hide show
  1. package/README.md +196 -0
  2. package/fesm2022/mintplayer-ng-spark-attribute-description.mjs +100 -47
  3. package/fesm2022/mintplayer-ng-spark-attribute-description.mjs.map +1 -1
  4. package/fesm2022/mintplayer-ng-spark-client-operations.mjs +310 -329
  5. package/fesm2022/mintplayer-ng-spark-client-operations.mjs.map +1 -1
  6. package/fesm2022/mintplayer-ng-spark-column-filter.mjs +175 -135
  7. package/fesm2022/mintplayer-ng-spark-column-filter.mjs.map +1 -1
  8. package/fesm2022/mintplayer-ng-spark-contributions.mjs +721 -0
  9. package/fesm2022/mintplayer-ng-spark-contributions.mjs.map +1 -0
  10. package/fesm2022/mintplayer-ng-spark-grid.mjs +2385 -1118
  11. package/fesm2022/mintplayer-ng-spark-grid.mjs.map +1 -1
  12. package/fesm2022/mintplayer-ng-spark-history.mjs +598 -0
  13. package/fesm2022/mintplayer-ng-spark-history.mjs.map +1 -0
  14. package/fesm2022/mintplayer-ng-spark-icon.mjs +64 -27
  15. package/fesm2022/mintplayer-ng-spark-icon.mjs.map +1 -1
  16. package/fesm2022/mintplayer-ng-spark-models.mjs +405 -762
  17. package/fesm2022/mintplayer-ng-spark-models.mjs.map +1 -1
  18. package/fesm2022/mintplayer-ng-spark-moderation.mjs +1212 -0
  19. package/fesm2022/mintplayer-ng-spark-moderation.mjs.map +1 -0
  20. package/fesm2022/mintplayer-ng-spark-panels.mjs +60 -0
  21. package/fesm2022/mintplayer-ng-spark-panels.mjs.map +1 -0
  22. package/fesm2022/mintplayer-ng-spark-pipes.mjs +1158 -765
  23. package/fesm2022/mintplayer-ng-spark-pipes.mjs.map +1 -1
  24. package/fesm2022/mintplayer-ng-spark-po-create.mjs +273 -169
  25. package/fesm2022/mintplayer-ng-spark-po-create.mjs.map +1 -1
  26. package/fesm2022/mintplayer-ng-spark-po-detail.mjs +795 -410
  27. package/fesm2022/mintplayer-ng-spark-po-detail.mjs.map +1 -1
  28. package/fesm2022/mintplayer-ng-spark-po-edit.mjs +1196 -280
  29. package/fesm2022/mintplayer-ng-spark-po-edit.mjs.map +1 -1
  30. package/fesm2022/mintplayer-ng-spark-po-form.mjs +2244 -1224
  31. package/fesm2022/mintplayer-ng-spark-po-form.mjs.map +1 -1
  32. package/fesm2022/mintplayer-ng-spark-query-list.mjs +571 -303
  33. package/fesm2022/mintplayer-ng-spark-query-list.mjs.map +1 -1
  34. package/fesm2022/mintplayer-ng-spark-renderers.mjs +44 -55
  35. package/fesm2022/mintplayer-ng-spark-renderers.mjs.map +1 -1
  36. package/fesm2022/mintplayer-ng-spark-retry-action-modal.mjs +216 -177
  37. package/fesm2022/mintplayer-ng-spark-retry-action-modal.mjs.map +1 -1
  38. package/fesm2022/mintplayer-ng-spark-routes.mjs +24 -28
  39. package/fesm2022/mintplayer-ng-spark-routes.mjs.map +1 -1
  40. package/fesm2022/mintplayer-ng-spark-services.mjs +625 -640
  41. package/fesm2022/mintplayer-ng-spark-services.mjs.map +1 -1
  42. package/fesm2022/mintplayer-ng-spark-shell.mjs +1031 -397
  43. package/fesm2022/mintplayer-ng-spark-shell.mjs.map +1 -1
  44. package/fesm2022/mintplayer-ng-spark-soft-delete.mjs +294 -0
  45. package/fesm2022/mintplayer-ng-spark-soft-delete.mjs.map +1 -0
  46. package/fesm2022/mintplayer-ng-spark.mjs +14 -48
  47. package/fesm2022/mintplayer-ng-spark.mjs.map +1 -1
  48. package/package.json +23 -2
  49. package/types/mintplayer-ng-spark-attribute-description.d.ts +25 -18
  50. package/types/mintplayer-ng-spark-client-operations.d.ts +139 -160
  51. package/types/mintplayer-ng-spark-column-filter.d.ts +73 -55
  52. package/types/mintplayer-ng-spark-contributions.d.ts +231 -0
  53. package/types/mintplayer-ng-spark-grid.d.ts +917 -448
  54. package/types/mintplayer-ng-spark-history.d.ts +216 -0
  55. package/types/mintplayer-ng-spark-icon.d.ts +15 -13
  56. package/types/mintplayer-ng-spark-models.d.ts +681 -573
  57. package/types/mintplayer-ng-spark-moderation.d.ts +402 -0
  58. package/types/mintplayer-ng-spark-panels.d.ts +143 -0
  59. package/types/mintplayer-ng-spark-pipes.d.ts +131 -149
  60. package/types/mintplayer-ng-spark-po-create.d.ts +46 -37
  61. package/types/mintplayer-ng-spark-po-detail.d.ts +180 -117
  62. package/types/mintplayer-ng-spark-po-edit.d.ts +367 -64
  63. package/types/mintplayer-ng-spark-po-form.d.ts +558 -373
  64. package/types/mintplayer-ng-spark-query-list.d.ts +143 -82
  65. package/types/mintplayer-ng-spark-renderers.d.ts +121 -85
  66. package/types/mintplayer-ng-spark-retry-action-modal.d.ts +31 -34
  67. package/types/mintplayer-ng-spark-routes.d.ts +7 -11
  68. package/types/mintplayer-ng-spark-services.d.ts +377 -261
  69. package/types/mintplayer-ng-spark-shell.d.ts +228 -132
  70. package/types/mintplayer-ng-spark-soft-delete.d.ts +90 -0
  71. package/types/mintplayer-ng-spark.d.ts +19 -12
@@ -1,23 +1,22 @@
1
- import * as _angular_core from '@angular/core';
2
- import { Type } from '@angular/core';
3
- import { CdkDragDrop } from '@angular/cdk/drag-drop';
4
- import { Color } from '@mintplayer/ng-bootstrap';
5
- import { InMemoryTreeSelectProvider, TreeNode } from '@mintplayer/ng-bootstrap/tree-select';
6
- import * as _mintplayer_ng_spark_models from '@mintplayer/ng-spark/models';
7
- import { PersistentObject, EntityType, ValidationError, RefreshOverlay, QueryResultItem, LookupReference, EntityAttributeDefinition, EntityPermissions, ELookupDisplayType, EReferenceDisplayType, AttributeTab, AttributeGroup, LookupReferenceValue, RuleFailure } from '@mintplayer/ng-spark/models';
8
- import { DatatableSettings } from '@mintplayer/ng-bootstrap/datatable';
9
- import { PaginationResponse } from '@mintplayer/pagination';
10
-
1
+ import * as i0 from "@angular/core";
2
+ import { Type } from "@angular/core";
3
+ import { CdkDragDrop } from "@angular/cdk/drag-drop";
4
+ import { Color } from "@mintplayer/ng-bootstrap";
5
+ import { InMemoryTreeSelectProvider, TreeNode } from "@mintplayer/ng-bootstrap/tree-select";
6
+ import { AttributeGroup, AttributeTab, ELookupDisplayType, EReferenceDisplayType, EntityAttributeDefinition, EntityPermissions, EntityType, LookupReference, LookupReferenceValue, PersistentObject, QueryResultItem, RefreshOverlay, RefreshTrigger, RuleFailure, ValidationError } from "@mintplayer/ng-spark/models";
7
+ import { SparkResolvedRowRenderer } from "@mintplayer/ng-spark/renderers";
8
+ import { DatatableSettings } from "@mintplayer/ng-bootstrap/datatable";
9
+ import { PaginationResponse } from "@mintplayer/pagination";
11
10
  /** What the coordinator needs from its host, so it can be tested without mounting a form. */
12
11
  interface RefreshCoordinatorHost {
13
- /** POSTs the object and resolves with the reshaped one. */
14
- send(triggeredBy: string): Promise<PersistentObject>;
15
- /** Values as they are right now — read at dispatch time to snapshot what is being sent. */
16
- currentValues(): Record<string, any>;
17
- /** Applies a settled response. Not called for a superseded one. */
18
- apply(response: PersistentObject, sent: Record<string, any>): void;
19
- /** Surfaced so the host can show a busy affordance. Never used to disable fields. */
20
- setBusy(busy: boolean): void;
12
+ /** POSTs the object and resolves with the reshaped one. */
13
+ send(triggeredBy: string): Promise<PersistentObject>;
14
+ /** Values as they are right now — read at dispatch time to snapshot what is being sent. */
15
+ currentValues(): Record<string, any>;
16
+ /** Applies a settled response. Not called for a superseded one. */
17
+ apply(response: PersistentObject, sent: Record<string, any>): void;
18
+ /** Surfaced so the host can show a busy affordance. Never used to disable fields. */
19
+ setBusy(busy: boolean): void;
21
20
  }
22
21
  /**
23
22
  * Serializes refreshes for **one** form instance and drops superseded ones.
@@ -32,308 +31,437 @@ interface RefreshCoordinatorHost {
32
31
  * response *will* arrive. It is discarded by sequence number rather than prevented.
33
32
  */
34
33
  declare class RefreshCoordinator {
35
- private readonly host;
36
- private queue;
37
- private sequence;
38
- private settled;
39
- private pending;
40
- constructor(host: RefreshCoordinatorHost);
41
- /** Whether a refresh is in flight. */
42
- get isRefreshing(): boolean;
43
- /**
44
- * Marks `attributeName` as needing a refresh without sending one — for free-text editors, which
45
- * would otherwise issue a request per keystroke. Flushed by {@link blur} or {@link flush}.
46
- */
47
- markPending(attributeName: string): void;
48
- /** Sends a pending refresh for `attributeName`, if one was marked. */
49
- blur(attributeName: string): Promise<void>;
50
- /**
51
- * Sends every refresh still marked pending. Called before save, so a value typed and never blurred
52
- * — the user tabbing straight to the save button — is still reflected before the object goes.
53
- */
54
- flush(): Promise<void>;
55
- /** Sends a refresh immediately — discrete editors, where every change is a committed one. */
56
- trigger(attributeName: string): Promise<void>;
34
+ private readonly host;
35
+ private queue;
36
+ private sequence;
37
+ private settled;
38
+ private pending;
39
+ /** Debounce timers for `ValueChanged` free text, keyed by path. A path here is also in `pending`. */
40
+ private timers;
41
+ constructor(host: RefreshCoordinatorHost);
42
+ /** Whether a refresh is in flight. */
43
+ get isRefreshing(): boolean;
44
+ /**
45
+ * Marks `attributeName` as needing a refresh without sending one — for free-text editors, which
46
+ * would otherwise issue a request per keystroke. Flushed by {@link blur} or {@link flush}.
47
+ */
48
+ markPending(attributeName: string): void;
49
+ /**
50
+ * Routes a change by its {@link RefreshDispatch}: send now, send once typing pauses, or wait for
51
+ * blur. The one entry point a form needs, so the three editors' policies live here.
52
+ */
53
+ dispatch(attributeName: string, mode: RefreshDispatch): Promise<void>;
54
+ /** Sends a pending refresh for `attributeName`, if one was marked (or is waiting on its debounce). */
55
+ blur(attributeName: string): Promise<void>;
56
+ /**
57
+ * Sends every refresh still marked pending, including any waiting on a debounce. Called before
58
+ * save, so a value typed and never blurred — the user tabbing straight to the save button — is
59
+ * still reflected before the object goes.
60
+ */
61
+ flush(): Promise<void>;
62
+ /** Drops every pending mark and debounce timer without sending — the form is going away. */
63
+ dispose(): void;
64
+ /**
65
+ * Sends a refresh — immediately for discrete editors, where every change is a committed one.
66
+ *
67
+ * With `debounceMs`, the send waits until `attributeName` has been quiet for that long; each call
68
+ * restarts the wait, so a burst of keystrokes costs one request. Until it fires, the path counts as
69
+ * pending, so {@link blur} and {@link flush} send it straight away instead of waiting out the timer.
70
+ */
71
+ trigger(attributeName: string, options?: {
72
+ debounceMs?: number;
73
+ }): Promise<void>;
74
+ private clearTimer;
57
75
  }
58
-
76
+ /** How long `ValueChanged` free text waits for typing to pause before refreshing — like the search box. */
77
+ declare const SPARK_REFRESH_DEBOUNCE_MS = 300;
78
+ /** The subset of an attribute definition that decides how its editor behaves. */
79
+ interface RefreshTriggerShape {
80
+ dataType?: string;
81
+ lookupReferenceType?: string;
82
+ isArray?: boolean;
83
+ triggersRefresh?: RefreshTrigger;
84
+ }
85
+ /** When a change sends its refresh: on the change itself, on blur, or never. */
86
+ type EffectiveRefreshTrigger = 'change' | 'blur' | 'none';
87
+ /** How {@link RefreshCoordinator.dispatch} handles a change. */
88
+ type RefreshDispatch = 'immediate' | 'debounced' | 'blur';
89
+ /**
90
+ * Resolves the declared `triggersRefresh` against the editor the attribute renders as.
91
+ *
92
+ * - `None` / absent → `'none'`.
93
+ * - `Auto` → `'change'` for a discrete editor, `'blur'` for free text (the original behaviour).
94
+ * - `ValueChanged` → `'change'` (debounced for free text — see {@link refreshDispatch}).
95
+ * - `Blur` → `'blur'`, except on a discrete editor, which has no meaningful blur and so acts as
96
+ * `ValueChanged`. The server's `--spark-verify-model` warns about that declaration.
97
+ */
98
+ declare function effectiveTrigger(attr: RefreshTriggerShape | undefined): EffectiveRefreshTrigger;
99
+ /**
100
+ * How a change to `attr` is sent, or `null` when it sends nothing. A `'change'` trigger on free
101
+ * text is debounced; on a discrete editor it is immediate.
102
+ */
103
+ declare function refreshDispatch(attr: RefreshTriggerShape | undefined): RefreshDispatch | null;
104
+ /** Whether the change to `attr` is sent immediately (a discrete editor with a change trigger). */
105
+ declare function triggersImmediately(attr: RefreshTriggerShape | undefined): boolean;
106
+ /**
107
+ * Editors where every change is a deliberate, committed one. Everything else is free text, where
108
+ * firing per keystroke would be unacceptable.
109
+ *
110
+ * ⚠️ This asks how the attribute is **rendered**, not what its `dataType` says, and the difference
111
+ * is not academic. A lookup attribute carries the data type of its *key* — Fleet's `Car.Status` is
112
+ * `dataType: "string"` with `lookupReferenceType: "CarStatus"` — so keying on `dataType` alone
113
+ * classifies a `<bs-select>` as free text. It then waits for a blur that a select never emits, and
114
+ * the refresh simply never fires: no request, no error, nothing to see.
115
+ */
116
+ declare function isDiscreteEditor(attr: RefreshTriggerShape | undefined): boolean;
117
+ /** Empty overlay, so callers do not have to spell the type. */
118
+ declare const NO_OVERLAY: RefreshOverlay;
59
119
  /**
60
120
  * A refresh a form embedded in another form is asking its host to issue on its behalf.
61
121
  *
62
- * `immediate` mirrors the distinction the host's own editors make: a discrete editor fires on
63
- * change, free text only marks itself pending and fires on blur.
122
+ * `dispatch` carries the decision the child's own editor made from its declared `triggersRefresh`
123
+ * — send now, send once typing pauses, or wait for blur — so the host applies exactly the semantics
124
+ * it would have applied to an editor of its own.
64
125
  */
65
- interface NestedTriggerRequest {
66
- /** The full path the server addresses, e.g. `Gate.ProjectMode`. */
67
- path: string;
68
- immediate: boolean;
126
+ export interface NestedTriggerRequest {
127
+ /** The full path the server addresses, e.g. `Gate.ProjectMode`. */
128
+ path: string;
129
+ dispatch: RefreshDispatch;
69
130
  }
70
- declare class SparkPoFormComponent {
71
- private readonly sparkService;
72
- private readonly translations;
73
- private readonly rendererRegistry;
74
- entityType: _angular_core.InputSignal<EntityType | null>;
75
- formData: _angular_core.ModelSignal<Record<string, any>>;
76
- validationErrors: _angular_core.InputSignal<ValidationError[]>;
77
- showButtons: _angular_core.InputSignal<boolean>;
78
- isSaving: _angular_core.InputSignal<boolean>;
79
- parentId: _angular_core.InputSignal<string | undefined>;
80
- parentType: _angular_core.InputSignal<string | undefined>;
81
- save: _angular_core.OutputEmitterRef<void>;
82
- cancel: _angular_core.OutputEmitterRef<void>;
83
- /**
84
- * The type id to refresh against. Absent means refresh is unavailable — the form still renders and
85
- * edits normally, so a host that has not opted in loses nothing.
86
- */
87
- objectTypeId: _angular_core.InputSignal<string | undefined>;
88
- /** The id of the object being edited; absent for a create. */
89
- objectId: _angular_core.InputSignal<string | undefined>;
90
- /**
91
- * Set when this form renders a single embedded AsDetail object inside a host form: the name of
92
- * the attribute holding it, e.g. `"Gate"`. Presence of a prefix switches the form from issuing
93
- * its own refreshes to reporting them upward, because an embedded object is not something the
94
- * server can be asked about on its own — the request must describe, and be authorized against,
95
- * the object that owns it.
96
- */
97
- triggerPathPrefix: _angular_core.InputSignal<string | null>;
98
- /** Raised instead of self-issuing when {@link triggerPathPrefix} is set. */
99
- nestedTriggerRequested: _angular_core.OutputEmitterRef<NestedTriggerRequest>;
100
- /** Blur counterpart, flushing a trigger the host only marked pending. */
101
- nestedTriggerBlurred: _angular_core.OutputEmitterRef<string>;
102
- /**
103
- * What the last refresh changed about each attribute's presentation, keyed by attribute name.
104
- *
105
- * Deliberately NOT folded back into `entityType`. All option loading hangs off one effect keyed on
106
- * `entityType` identity and `SparkService` caches nothing, so re-setting it would re-issue every
107
- * reference query and lookup fetch on every refresh; mutating it in place would not re-render at
108
- * all.
109
- *
110
- * ⚠️ A `model`, not a plain signal, because the page that owns the save has to see it. This form
111
- * renders from the overlaid attribute list; `spark-po-edit` and `spark-po-create` build the
112
- * payload from their own copy of the same filter. While the overlay stopped here, those two
113
- * filtered on the attribute's state *as loaded* — so an attribute a refresh hook revealed was
114
- * rendered, filled in by the user, and then silently left out of the save. When the same hook also
115
- * made it required (the ordinary case — a field appears *because* it is now needed) the server
116
- * refused the save for the very field the user had just filled, and the form became a dead end
117
- * with no way forward from inside it.
118
- */
119
- refreshOverlay: _angular_core.ModelSignal<RefreshOverlay>;
120
- isRefreshing: _angular_core.WritableSignal<boolean>;
121
- colors: typeof Color;
122
- referenceOptions: _angular_core.WritableSignal<Record<string, QueryResultItem[]>>;
123
- referenceProviders: _angular_core.WritableSignal<Record<string, InMemoryTreeSelectProvider>>;
124
- referenceNodes: _angular_core.WritableSignal<Record<string, Record<string, TreeNode>>>;
125
- asDetailTypes: _angular_core.WritableSignal<Record<string, EntityType>>;
126
- lookupReferenceOptions: _angular_core.WritableSignal<Record<string, LookupReference>>;
127
- editingAsDetailAttr: _angular_core.WritableSignal<EntityAttributeDefinition | null>;
128
- asDetailFormData: _angular_core.WritableSignal<Record<string, any>>;
129
- showAsDetailModal: _angular_core.WritableSignal<boolean>;
130
- editingArrayIndex: _angular_core.WritableSignal<number | null>;
131
- asDetailPermissions: _angular_core.WritableSignal<Record<string, EntityPermissions>>;
132
- /**
133
- * What the server said when it refused to construct or release a row.
134
- *
135
- * A local signal rather than the `validationErrors` input, because this is the one class of error
136
- * the form raises on its own behalf: `validationErrors` is owned by the host (po-edit / po-create)
137
- * and arrives from a save. Cleared at the start of each lifecycle call, so a stale refusal never
138
- * outlives the click that caused it.
139
- *
140
- * Keyed by attribute name so a message renders against the grid it came from. A single global
141
- * alert would have been less code and worse: a form with two detail grids would show one grid's
142
- * refusal above the other, and the user's next click is on the grid, not the top of the page.
143
- */
144
- rowLifecycleErrors: _angular_core.WritableSignal<Record<string, ValidationError[]>>;
145
- /** The refusal to render under `attrName`'s grid, if any. */
146
- rowLifecycleErrorsFor(attrName: string): ValidationError[];
147
- asDetailReferenceOptions: _angular_core.WritableSignal<Record<string, Record<string, QueryResultItem[]>>>;
148
- ELookupDisplayType: typeof ELookupDisplayType;
149
- EReferenceDisplayType: typeof EReferenceDisplayType;
150
- /**
151
- * Every attribute this form could ever need option data for — including ones the model hides,
152
- * because a refresh may reveal them.
153
- *
154
- * Read by the option-loading effect, and deliberately independent of `refreshOverlay`: the loaders
155
- * read this synchronously, so an overlay dependency here would make every refresh re-issue every
156
- * reference query and lookup fetch. That is the whole reason the overlay is a separate signal.
157
- */
158
- optionSourceAttributes: _angular_core.Signal<EntityAttributeDefinition[]>;
159
- editableAttributes: _angular_core.Signal<EntityAttributeDefinition[]>;
160
- private static readonly DEFAULT_TAB;
161
- ungroupedAttributes: _angular_core.Signal<EntityAttributeDefinition[]>;
162
- resolvedTabs: _angular_core.Signal<AttributeTab[]>;
163
- groupsForTab(tab: AttributeTab): AttributeGroup[];
164
- attrsForGroup(group: AttributeGroup): EntityAttributeDefinition[];
165
- constructor();
166
- private toRecord;
167
- loadReferenceOptions(): Promise<void>;
168
- /**
169
- * Stable TreeNode[] value per multi-reference attribute, derived from the id array
170
- * in formData and the resolved node map. Recomputes only when formData or the node
171
- * map changes, so binding it into <bs-tree-select> doesn't churn on every CD pass.
172
- * Ids without a resolved node fall back to a label of the id itself.
173
- */
174
- referenceTreeValues: _angular_core.Signal<Record<string, TreeNode[]>>;
175
- getReferenceProvider(attr: EntityAttributeDefinition): InMemoryTreeSelectProvider | undefined;
176
- onReferenceTreeChange(attr: EntityAttributeDefinition, value: TreeNode | TreeNode[] | null): void;
177
- loadAsDetailTypes(): Promise<void>;
178
- loadLookupReferenceOptions(): Promise<void>;
179
- getLookupOptions(attr: EntityAttributeDefinition): LookupReferenceValue[];
180
- onReferenceValueChange(attr: EntityAttributeDefinition, id: string | null): void;
181
- onLookupValueChange(attr: EntityAttributeDefinition, key: string | null): void;
182
- getEditRendererComponent(attr: EntityAttributeDefinition): Type<any> | null;
183
- getEditRendererInputs(component: Type<any>, attr: EntityAttributeDefinition): Record<string, any>;
184
- /** Column renderer for a cell of an AsDetail sub-table (so embedded rows honor `col.renderer` too). */
185
- getAsDetailCellRendererComponent(col: EntityAttributeDefinition): Type<any> | null;
186
- getAsDetailCellRendererInputs(component: Type<any>, row: Record<string, any>, col: EntityAttributeDefinition): Record<string, any>;
187
- /** Edit-renderer for an inline AsDetail cell (so inline editing honors `col.renderer`, not just display). */
188
- getAsDetailCellEditRenderer(col: EntityAttributeDefinition): Type<any> | null;
189
- getAsDetailCellEditRendererInputs(component: Type<any>, row: Record<string, any>, col: EntityAttributeDefinition): Record<string, any>;
190
- /**
191
- * Rules evaluated in the browser, against the *effective* metadata — so a rule a refresh hook
192
- * imposed is visible before the round-trip rather than only after the server rejects the save.
193
- */
194
- clientRuleFailures: _angular_core.Signal<RuleFailure[]>;
195
- hasError(attrName: string): boolean;
196
- private inlineErrorPath;
197
- hasInlineError(attr: EntityAttributeDefinition, rowIndex: number, col: EntityAttributeDefinition): boolean;
198
- inlineErrorMessage(attr: EntityAttributeDefinition, rowIndex: number, col: EntityAttributeDefinition): string | null;
199
- /**
200
- * The single funnel every scalar / boolean / inline-cell edit passes through.
201
- *
202
- * `attr` is optional because some editors have nothing useful to say about which attribute moved.
203
- * A caller that knows should always say so — without it no refresh can fire.
204
- */
205
- onFieldChange(attr?: EntityAttributeDefinition): void;
206
- /**
207
- * A trigger inside an AsDetail row. Addressed by the same `{attr}[{index}].{col}` path the inline
208
- * validation errors already use, so the server can tell which row asked without a second
209
- * addressing scheme being invented for it.
210
- */
211
- onInlineCellChange(attr: EntityAttributeDefinition, rowIndex: number, col: EntityAttributeDefinition): void;
212
- /**
213
- * Which embedded target the in-flight refresh belongs to, if any.
214
- *
215
- * ⚠️ Tagged, not a nullable `rowIndex`. The two arms write to different places — a row inside
216
- * `formData`, versus the modal's working copy in `asDetailFormData` — and an index that happens
217
- * to be absent is not the same statement as "this attribute holds one object". With a nullable
218
- * index, a row refresh carrying a bad index would fall silently into the object arm.
219
- */
220
- private pendingNestedTrigger;
221
- /**
222
- * A trigger inside a single embedded AsDetail object, raised by the modal's recursive form.
223
- *
224
- * The child form does not issue the request itself, for two reasons that both bite. Its
225
- * `entityType` is the embedded type, so a payload built there describes `GateSettings` while the
226
- * request must describe the Repository that owns it. And nested AsDetail types carry no rights of
227
- * their own — the server authorizes a nested refresh on the OWNING type — so a child-issued
228
- * request would be asking the wrong question of security.json.
229
- */
230
- onEmbeddedTrigger(attributeName: string, event: NestedTriggerRequest): void;
231
- /** Blur counterpart of {@link onEmbeddedTrigger}, for the child's free-text editors. */
232
- onEmbeddedTriggerBlur(path: string): void;
233
- /**
234
- * Applies a refresh that ran against a detail row: the row's own values, and the column metadata
235
- * for the grid it lives in.
236
- *
237
- * The column metadata comes from `asDetailTypes` — a different signal from `entityType` — which is
238
- * why a nested response cannot go through the top-level overlay.
239
- *
240
- * ⚠️ The row array is mutated in place rather than replaced. Rows are tracked by index, so handing
241
- * the template a new array destroys and rebuilds every row's DOM and takes focus with it, mid-edit.
242
- */
243
- private applyNestedResponse;
244
- onInlineCellBlur(attr: EntityAttributeDefinition, rowIndex: number, col: EntityAttributeDefinition): void;
245
- /** Blur handler for free-text editors — sends the refresh their keystrokes only marked pending. */
246
- onFieldBlur(attr: EntityAttributeDefinition): void;
247
- private noteChange;
248
- /** `Gate.ProjectMode` — the index-free counterpart of the inline grid's `Jobs[1].Kind`. */
249
- private nestedTriggerPath;
250
- private canRefresh;
251
- /**
252
- * Per-instance, never a service: the retry-action modal renders its own `spark-po-form`, and a
253
- * refresh may carry a retry operation — so a refresh can open a modal containing a form that
254
- * refreshes. A shared coordinator would let the nested form supersede this one's request.
255
- */
256
- protected readonly refreshCoordinator: RefreshCoordinator;
257
- private buildRefreshPayload;
258
- /**
259
- * The values to post, with the open modal's working copy substituted for the attribute it edits.
260
- *
261
- * ⚠️ Without this the server receives the embedded object as it was when the modal opened — `{}`
262
- * for a gate that has never been set — and the hook decides against values the user cannot see it
263
- * ignoring. `formData` deliberately does not hold the in-progress edit (cancelling the modal has
264
- * to discard it), so the refresh has to reach into `asDetailFormData` instead. Only the attribute
265
- * under edit is substituted; everything else is the form's own state.
266
- */
267
- private embeddedValues;
268
- /**
269
- * Folds replaced option lists into the signals the editors already read, so a refreshed dropdown
270
- * renders through the same path as a loaded one.
271
- *
272
- * `undefined` means the hook did not touch this attribute's options and the loaded set stands; an
273
- * empty array means it deliberately left none. Collapsing the two would blank every dropdown the
274
- * hook never mentioned.
275
- */
276
- private applyRefreshedOptions;
277
- /** Sends anything still pending, so a typed-but-never-blurred trigger is reflected before save. */
278
- flushPendingRefresh(): Promise<void>;
279
- onSave(): Promise<void>;
280
- onCancel(): void;
281
- openAsDetailEditor(attr: EntityAttributeDefinition): void;
282
- saveAsDetailObject(): void;
283
- closeAsDetailModal(): void;
284
- /**
285
- * Whether `attr`'s row type asks the server before a row is added or removed.
286
- *
287
- * Read off the ROW type, never the parent. `asDetailTypes` already resolves it from the Query-gated
288
- * catalogue first and the parent's `detailTypes` second, and the flag survives the server-side
289
- * pruning of the embedded copy, so both sources give the same answer.
290
- */
291
- private roundTripsRowLifecycle;
292
- /** The type id to address the row type by on the wire. */
293
- private rowTypeId;
294
- /**
295
- * How to name the PARENT's type on the wire.
296
- *
297
- * ⚠️ The fallback must be the type **id**, never `clrType`. The server resolves this through
298
- * `ModelLoader.ResolveEntityType`, which accepts a GUID or a declared alias and nothing else — so
299
- * a CLR name resolves to null and the request is refused, indistinguishably from an unknown type.
300
- * The user would see "the server refused this change" on a perfectly ordinary Add.
301
- *
302
- * It is a live path, not a theoretical one: `spark-po-create` binds no `parentType` (there is no
303
- * parent route segment on a create page), so every host that creates an object with an opted-in
304
- * detail grid lands here.
305
- */
306
- private parentTypeForWire;
307
- /**
308
- * Builds the dict a new row starts from: `{}` as before, or the object the server constructed.
309
- *
310
- * Returns null when the server refused — the caller must then add nothing, which is the whole
311
- * difference between a veto and a message.
312
- */
313
- private buildNewRow;
314
- /**
315
- * Asks whether a stored row may be removed. True when it may — including every case where there
316
- * is nothing to ask about: a type that has not opted in, a parent that has never been saved, or a
317
- * row with no key yet because it was added in this same editing session and never persisted.
318
- */
319
- private mayRemoveRow;
320
- /**
321
- * Turns a rejected lifecycle call into something the user can read.
322
- *
323
- * The envelope puts validation errors under `result`, the same place the save path reads them
324
- * from — reading `error.error.errors` matched nothing and made every veto silent.
325
- */
326
- private clearRowLifecycleError;
327
- private captureRowLifecycleError;
328
- addInlineRow(attr: EntityAttributeDefinition): Promise<void>;
329
- addArrayItem(attr: EntityAttributeDefinition): Promise<void>;
330
- editArrayItem(attr: EntityAttributeDefinition, index: number): void;
331
- removeArrayItem(attr: EntityAttributeDefinition, index: number): Promise<void>;
332
- onAsDetailReorder(attr: EntityAttributeDefinition, event: CdkDragDrop<Record<string, any>[]>): void;
333
- static ɵfac: _angular_core.ɵɵFactoryDeclaration<SparkPoFormComponent, never>;
334
- static ɵcmp: _angular_core.ɵɵComponentDeclaration<SparkPoFormComponent, "spark-po-form", never, { "entityType": { "alias": "entityType"; "required": false; "isSignal": true; }; "formData": { "alias": "formData"; "required": false; "isSignal": true; }; "validationErrors": { "alias": "validationErrors"; "required": false; "isSignal": true; }; "showButtons": { "alias": "showButtons"; "required": false; "isSignal": true; }; "isSaving": { "alias": "isSaving"; "required": false; "isSignal": true; }; "parentId": { "alias": "parentId"; "required": false; "isSignal": true; }; "parentType": { "alias": "parentType"; "required": false; "isSignal": true; }; "objectTypeId": { "alias": "objectTypeId"; "required": false; "isSignal": true; }; "objectId": { "alias": "objectId"; "required": false; "isSignal": true; }; "triggerPathPrefix": { "alias": "triggerPathPrefix"; "required": false; "isSignal": true; }; "refreshOverlay": { "alias": "refreshOverlay"; "required": false; "isSignal": true; }; }, { "formData": "formDataChange"; "save": "save"; "cancel": "cancel"; "nestedTriggerRequested": "nestedTriggerRequested"; "nestedTriggerBlurred": "nestedTriggerBlurred"; "refreshOverlay": "refreshOverlayChange"; }, never, never, true, never>;
131
+ export declare class SparkPoFormComponent {
132
+ private readonly sparkService;
133
+ private readonly translations;
134
+ protected readonly rendererRegistry: import("@mintplayer/ng-spark/renderers").SparkAttributeRendererRegistration[];
135
+ entityType: import("@angular/core").InputSignal<EntityType | null>;
136
+ formData: import("@angular/core").ModelSignal<Record<string, any>>;
137
+ validationErrors: import("@angular/core").InputSignal<ValidationError[]>;
138
+ showButtons: import("@angular/core").InputSignal<boolean>;
139
+ isSaving: import("@angular/core").InputSignal<boolean>;
140
+ parentId: import("@angular/core").InputSignal<string | undefined>;
141
+ parentType: import("@angular/core").InputSignal<string | undefined>;
142
+ save: import("@angular/core").OutputEmitterRef<void>;
143
+ cancel: import("@angular/core").OutputEmitterRef<void>;
144
+ /**
145
+ * The type id to refresh against. Absent means refresh is unavailable — the form still renders and
146
+ * edits normally, so a host that has not opted in loses nothing.
147
+ */
148
+ objectTypeId: import("@angular/core").InputSignal<string | undefined>;
149
+ /** The id of the object being edited; absent for a create. */
150
+ objectId: import("@angular/core").InputSignal<string | undefined>;
151
+ /**
152
+ * Set when this form renders a single embedded AsDetail object inside a host form: the name of
153
+ * the attribute holding it, e.g. `"Gate"`. Presence of a prefix switches the form from issuing
154
+ * its own refreshes to reporting them upward, because an embedded object is not something the
155
+ * server can be asked about on its own — the request must describe, and be authorized against,
156
+ * the object that owns it.
157
+ */
158
+ triggerPathPrefix: import("@angular/core").InputSignal<string | null>;
159
+ /** Raised instead of self-issuing when {@link triggerPathPrefix} is set. */
160
+ nestedTriggerRequested: import("@angular/core").OutputEmitterRef<NestedTriggerRequest>;
161
+ /** Blur counterpart, flushing a trigger the host only marked pending. */
162
+ nestedTriggerBlurred: import("@angular/core").OutputEmitterRef<string>;
163
+ /**
164
+ * What the last refresh changed about each attribute's presentation, keyed by attribute name.
165
+ *
166
+ * Deliberately NOT folded back into `entityType`. All option loading hangs off one effect keyed on
167
+ * `entityType` identity and `SparkService` caches nothing, so re-setting it would re-issue every
168
+ * reference query and lookup fetch on every refresh; mutating it in place would not re-render at
169
+ * all.
170
+ *
171
+ * ⚠️ A `model`, not a plain signal, because the page that owns the save has to see it. This form
172
+ * renders from the overlaid attribute list; `spark-po-edit` and `spark-po-create` build the
173
+ * payload from their own copy of the same filter. While the overlay stopped here, those two
174
+ * filtered on the attribute's state *as loaded* — so an attribute a refresh hook revealed was
175
+ * rendered, filled in by the user, and then silently left out of the save. When the same hook also
176
+ * made it required (the ordinary case — a field appears *because* it is now needed) the server
177
+ * refused the save for the very field the user had just filled, and the form became a dead end
178
+ * with no way forward from inside it.
179
+ */
180
+ refreshOverlay: import("@angular/core").ModelSignal<RefreshOverlay>;
181
+ isRefreshing: import("@angular/core").WritableSignal<boolean>;
182
+ colors: typeof Color;
183
+ referenceOptions: import("@angular/core").WritableSignal<Record<string, QueryResultItem[]>>;
184
+ referenceProviders: import("@angular/core").WritableSignal<Record<string, InMemoryTreeSelectProvider>>;
185
+ referenceNodes: import("@angular/core").WritableSignal<Record<string, Record<string, TreeNode>>>;
186
+ asDetailTypes: import("@angular/core").WritableSignal<Record<string, EntityType>>;
187
+ lookupReferenceOptions: import("@angular/core").WritableSignal<Record<string, LookupReference>>;
188
+ editingAsDetailAttr: import("@angular/core").WritableSignal<EntityAttributeDefinition | null>;
189
+ asDetailFormData: import("@angular/core").WritableSignal<Record<string, any>>;
190
+ showAsDetailModal: import("@angular/core").WritableSignal<boolean>;
191
+ editingArrayIndex: import("@angular/core").WritableSignal<number | null>;
192
+ asDetailPermissions: import("@angular/core").WritableSignal<Record<string, EntityPermissions>>;
193
+ /**
194
+ * What the server said when it refused to construct or release a row.
195
+ *
196
+ * A local signal rather than the `validationErrors` input, because this is the one class of error
197
+ * the form raises on its own behalf: `validationErrors` is owned by the host (po-edit / po-create)
198
+ * and arrives from a save. Cleared at the start of each lifecycle call, so a stale refusal never
199
+ * outlives the click that caused it.
200
+ *
201
+ * Keyed by attribute name so a message renders against the grid it came from. A single global
202
+ * alert would have been less code and worse: a form with two detail grids would show one grid's
203
+ * refusal above the other, and the user's next click is on the grid, not the top of the page.
204
+ */
205
+ rowLifecycleErrors: import("@angular/core").WritableSignal<Record<string, ValidationError[]>>;
206
+ /** The refusal to render under `attrName`'s grid, if any. */
207
+ rowLifecycleErrorsFor(attrName: string): ValidationError[];
208
+ asDetailReferenceOptions: import("@angular/core").WritableSignal<Record<string, Record<string, QueryResultItem[]>>>;
209
+ ELookupDisplayType: typeof ELookupDisplayType;
210
+ EReferenceDisplayType: typeof EReferenceDisplayType;
211
+ /**
212
+ * Every attribute this form could ever need option data for — including ones the model hides,
213
+ * because a refresh may reveal them.
214
+ *
215
+ * Read by the option-loading effect, and deliberately independent of `refreshOverlay`: the loaders
216
+ * read this synchronously, so an overlay dependency here would make every refresh re-issue every
217
+ * reference query and lookup fetch. That is the whole reason the overlay is a separate signal.
218
+ */
219
+ optionSourceAttributes: import("@angular/core").Signal<EntityAttributeDefinition[]>;
220
+ editableAttributes: import("@angular/core").Signal<EntityAttributeDefinition[]>;
221
+ private static readonly DEFAULT_TAB;
222
+ ungroupedAttributes: import("@angular/core").Signal<EntityAttributeDefinition[]>;
223
+ resolvedTabs: import("@angular/core").Signal<AttributeTab[]>;
224
+ groupsForTab(tab: AttributeTab): AttributeGroup[];
225
+ attrsForGroup(group: AttributeGroup): EntityAttributeDefinition[];
226
+ constructor();
227
+ private toRecord;
228
+ loadReferenceOptions(): Promise<void>;
229
+ /**
230
+ * Stable TreeNode[] value per multi-reference attribute, derived from the id array
231
+ * in formData and the resolved node map. Recomputes only when formData or the node
232
+ * map changes, so binding it into <bs-tree-select> doesn't churn on every CD pass.
233
+ * Ids without a resolved node fall back to a label of the id itself.
234
+ */
235
+ referenceTreeValues: import("@angular/core").Signal<Record<string, TreeNode[]>>;
236
+ getReferenceProvider(attr: EntityAttributeDefinition): InMemoryTreeSelectProvider | undefined;
237
+ onReferenceTreeChange(attr: EntityAttributeDefinition, value: TreeNode | TreeNode[] | null): void;
238
+ loadAsDetailTypes(): Promise<void>;
239
+ loadLookupReferenceOptions(): Promise<void>;
240
+ getLookupOptions(attr: EntityAttributeDefinition): LookupReferenceValue[];
241
+ onReferenceValueChange(attr: EntityAttributeDefinition, id: string | null): void;
242
+ onLookupValueChange(attr: EntityAttributeDefinition, key: string | null): void;
243
+ getEditRendererComponent(attr: EntityAttributeDefinition): Type<any> | null;
244
+ getEditRendererInputs(component: Type<any>, attr: EntityAttributeDefinition): Record<string, any>;
245
+ /** Column renderer for a cell of an AsDetail sub-table (so embedded rows honor `col.renderer` too). */
246
+ getAsDetailCellRendererComponent(col: EntityAttributeDefinition): Type<any> | null;
247
+ /** Inputs of an AsDetail row renderer (`rowComponent`): the row and the object being edited. */
248
+ getRowRendererInputs(renderer: SparkResolvedRowRenderer, row: Record<string, any>): Record<string, any>;
249
+ getAsDetailCellRendererInputs(component: Type<any>, row: Record<string, any>, col: EntityAttributeDefinition): Record<string, any>;
250
+ /** Edit-renderer for an inline AsDetail cell (so inline editing honors `col.renderer`, not just display). */
251
+ getAsDetailCellEditRenderer(col: EntityAttributeDefinition): Type<any> | null;
252
+ getAsDetailCellEditRendererInputs(component: Type<any>, row: Record<string, any>, col: EntityAttributeDefinition): Record<string, any>;
253
+ /**
254
+ * Rules evaluated in the browser, against the *effective* metadata — so a rule a refresh hook
255
+ * imposed is visible before the round-trip rather than only after the server rejects the save.
256
+ */
257
+ clientRuleFailures: import("@angular/core").Signal<RuleFailure[]>;
258
+ hasError(attrName: string): boolean;
259
+ private inlineErrorPath;
260
+ hasInlineError(attr: EntityAttributeDefinition, rowIndex: number, col: EntityAttributeDefinition): boolean;
261
+ inlineErrorMessage(attr: EntityAttributeDefinition, rowIndex: number, col: EntityAttributeDefinition): string | null;
262
+ /**
263
+ * The single funnel every scalar / boolean / inline-cell edit passes through.
264
+ *
265
+ * `attr` is optional because some editors have nothing useful to say about which attribute moved.
266
+ * A caller that knows should always say so — without it no refresh can fire.
267
+ */
268
+ onFieldChange(attr?: EntityAttributeDefinition): void;
269
+ /**
270
+ * A trigger inside an AsDetail row. Addressed by the same `{attr}[{index}].{col}` path the inline
271
+ * validation errors already use, so the server can tell which row asked without a second
272
+ * addressing scheme being invented for it.
273
+ */
274
+ onInlineCellChange(attr: EntityAttributeDefinition, rowIndex: number, col: EntityAttributeDefinition): void;
275
+ /**
276
+ * Which embedded target the in-flight refresh belongs to, if any.
277
+ *
278
+ * ⚠️ Tagged, not a nullable `rowIndex`. The two arms write to different places — a row inside
279
+ * `formData`, versus the modal's working copy in `asDetailFormData` — and an index that happens
280
+ * to be absent is not the same statement as "this attribute holds one object". With a nullable
281
+ * index, a row refresh carrying a bad index would fall silently into the object arm.
282
+ */
283
+ private pendingNestedTrigger;
284
+ /**
285
+ * A trigger inside a single embedded AsDetail object, raised by the modal's recursive form.
286
+ *
287
+ * The child form does not issue the request itself, for two reasons that both bite. Its
288
+ * `entityType` is the embedded type, so a payload built there describes `GateSettings` while the
289
+ * request must describe the Repository that owns it. And nested AsDetail types carry no rights of
290
+ * their own — the server authorizes a nested refresh on the OWNING type — so a child-issued
291
+ * request would be asking the wrong question of security.json.
292
+ */
293
+ onEmbeddedTrigger(attributeName: string, event: NestedTriggerRequest): void;
294
+ /** Blur counterpart of {@link onEmbeddedTrigger}, for the child's free-text editors. */
295
+ onEmbeddedTriggerBlur(path: string): void;
296
+ /**
297
+ * Applies a refresh that ran against a detail row: the row's own values, and the column metadata
298
+ * for the grid it lives in.
299
+ *
300
+ * The column metadata comes from `asDetailTypes` — a different signal from `entityType` — which is
301
+ * why a nested response cannot go through the top-level overlay.
302
+ *
303
+ * ⚠️ The row array is mutated in place rather than replaced. Rows are tracked by index, so handing
304
+ * the template a new array destroys and rebuilds every row's DOM and takes focus with it, mid-edit.
305
+ */
306
+ private applyNestedResponse;
307
+ onInlineCellBlur(attr: EntityAttributeDefinition, rowIndex: number, col: EntityAttributeDefinition): void;
308
+ /**
309
+ * Blur handler for free-text editors — sends the refresh their keystrokes only marked pending, or
310
+ * cuts short a `ValueChanged` debounce that is still waiting.
311
+ */
312
+ onFieldBlur(attr: EntityAttributeDefinition): void;
313
+ private noteChange;
314
+ /** `Gate.ProjectMode` — the index-free counterpart of the inline grid's `Jobs[1].Kind`. */
315
+ private nestedTriggerPath;
316
+ private canRefresh;
317
+ /**
318
+ * Per-instance, never a service: the retry-action modal renders its own `spark-po-form`, and a
319
+ * refresh may carry a retry operation — so a refresh can open a modal containing a form that
320
+ * refreshes. A shared coordinator would let the nested form supersede this one's request.
321
+ */
322
+ protected readonly refreshCoordinator: RefreshCoordinator;
323
+ private buildRefreshPayload;
324
+ /**
325
+ * The values to post, with the open modal's working copy substituted for the attribute it edits.
326
+ *
327
+ * ⚠️ Without this the server receives the embedded object as it was when the modal opened — `{}`
328
+ * for a gate that has never been set — and the hook decides against values the user cannot see it
329
+ * ignoring. `formData` deliberately does not hold the in-progress edit (cancelling the modal has
330
+ * to discard it), so the refresh has to reach into `asDetailFormData` instead. Only the attribute
331
+ * under edit is substituted; everything else is the form's own state.
332
+ */
333
+ private embeddedValues;
334
+ /**
335
+ * Folds replaced option lists into the signals the editors already read, so a refreshed dropdown
336
+ * renders through the same path as a loaded one.
337
+ *
338
+ * `undefined` means the hook did not touch this attribute's options and the loaded set stands; an
339
+ * empty array means it deliberately left none. Collapsing the two would blank every dropdown the
340
+ * hook never mentioned.
341
+ */
342
+ private applyRefreshedOptions;
343
+ /** Sends anything still pending, so a typed-but-never-blurred trigger is reflected before save. */
344
+ flushPendingRefresh(): Promise<void>;
345
+ onSave(): Promise<void>;
346
+ onCancel(): void;
347
+ openAsDetailEditor(attr: EntityAttributeDefinition): void;
348
+ saveAsDetailObject(): void;
349
+ closeAsDetailModal(): void;
350
+ /**
351
+ * Whether `attr`'s row type asks the server before a row is added or removed.
352
+ *
353
+ * Read off the ROW type, never the parent. `asDetailTypes` already resolves it from the Query-gated
354
+ * catalogue first and the parent's `detailTypes` second, and the flag survives the server-side
355
+ * pruning of the embedded copy, so both sources give the same answer.
356
+ */
357
+ private roundTripsRowLifecycle;
358
+ /** The type id to address the row type by on the wire. */
359
+ private rowTypeId;
360
+ /**
361
+ * How to name the PARENT's type on the wire.
362
+ *
363
+ * ⚠️ The fallback must be the type **id**, never `clrType`. The server resolves this through
364
+ * `ModelLoader.ResolveEntityType`, which accepts a GUID or a declared alias and nothing else — so
365
+ * a CLR name resolves to null and the request is refused, indistinguishably from an unknown type.
366
+ * The user would see "the server refused this change" on a perfectly ordinary Add.
367
+ *
368
+ * It is a live path, not a theoretical one: `spark-po-create` binds no `parentType` (there is no
369
+ * parent route segment on a create page), so every host that creates an object with an opted-in
370
+ * detail grid lands here.
371
+ */
372
+ private parentTypeForWire;
373
+ /**
374
+ * Builds the dict a new row starts from: `{}` as before, or the object the server constructed.
375
+ *
376
+ * Returns null when the server refused — the caller must then add nothing, which is the whole
377
+ * difference between a veto and a message.
378
+ */
379
+ private buildNewRow;
380
+ /**
381
+ * Asks whether a stored row may be removed. True when it may — including every case where there
382
+ * is nothing to ask about: a type that has not opted in, a parent that has never been saved, or a
383
+ * row with no key yet because it was added in this same editing session and never persisted.
384
+ */
385
+ private mayRemoveRow;
386
+ /**
387
+ * Turns a rejected lifecycle call into something the user can read.
388
+ *
389
+ * The envelope puts validation errors under `result`, the same place the save path reads them
390
+ * from — reading `error.error.errors` matched nothing and made every veto silent.
391
+ */
392
+ private clearRowLifecycleError;
393
+ private captureRowLifecycleError;
394
+ addInlineRow(attr: EntityAttributeDefinition): Promise<void>;
395
+ addArrayItem(attr: EntityAttributeDefinition): Promise<void>;
396
+ editArrayItem(attr: EntityAttributeDefinition, index: number): void;
397
+ removeArrayItem(attr: EntityAttributeDefinition, index: number): Promise<void>;
398
+ onAsDetailReorder(attr: EntityAttributeDefinition, event: CdkDragDrop<Record<string, any>[]>): void;
399
+ static ɵfac: i0.ɵɵFactoryDeclaration<SparkPoFormComponent, never>;
400
+ static ɵcmp: i0.ɵɵComponentDeclaration<SparkPoFormComponent, "spark-po-form", never, {
401
+ "entityType": {
402
+ "alias": "entityType";
403
+ "required": false;
404
+ "isSignal": true;
405
+ };
406
+ "formData": {
407
+ "alias": "formData";
408
+ "required": false;
409
+ "isSignal": true;
410
+ };
411
+ "validationErrors": {
412
+ "alias": "validationErrors";
413
+ "required": false;
414
+ "isSignal": true;
415
+ };
416
+ "showButtons": {
417
+ "alias": "showButtons";
418
+ "required": false;
419
+ "isSignal": true;
420
+ };
421
+ "isSaving": {
422
+ "alias": "isSaving";
423
+ "required": false;
424
+ "isSignal": true;
425
+ };
426
+ "parentId": {
427
+ "alias": "parentId";
428
+ "required": false;
429
+ "isSignal": true;
430
+ };
431
+ "parentType": {
432
+ "alias": "parentType";
433
+ "required": false;
434
+ "isSignal": true;
435
+ };
436
+ "objectTypeId": {
437
+ "alias": "objectTypeId";
438
+ "required": false;
439
+ "isSignal": true;
440
+ };
441
+ "objectId": {
442
+ "alias": "objectId";
443
+ "required": false;
444
+ "isSignal": true;
445
+ };
446
+ "triggerPathPrefix": {
447
+ "alias": "triggerPathPrefix";
448
+ "required": false;
449
+ "isSignal": true;
450
+ };
451
+ "refreshOverlay": {
452
+ "alias": "refreshOverlay";
453
+ "required": false;
454
+ "isSignal": true;
455
+ };
456
+ }, {
457
+ "formData": "formDataChange";
458
+ "save": "save";
459
+ "cancel": "cancel";
460
+ "nestedTriggerRequested": "nestedTriggerRequested";
461
+ "nestedTriggerBlurred": "nestedTriggerBlurred";
462
+ "refreshOverlay": "refreshOverlayChange";
463
+ }, never, never, true, never>;
335
464
  }
336
-
337
465
  /**
338
466
  * Reusable Reference value picker: a readonly textbox showing the current selection's
339
467
  * breadcrumb/name plus a "…" button that opens a searchable modal grid over the candidate
@@ -344,70 +472,127 @@ declare class SparkPoFormComponent {
344
472
  * CLR type); the component lazily loads that EntityType's metadata on first open to render
345
473
  * the grid's column headers. Emits the picked id via `valueChange`.
346
474
  */
347
- declare class SparkReferencePickerComponent {
348
- private readonly sparkService;
349
- private readonly lang;
350
- /** Currently selected referenced id (or null when unset). */
351
- value: _angular_core.InputSignal<string | null>;
352
- /** Candidate items to pick from (the full query result, pre-loaded by the parent). */
353
- options: _angular_core.InputSignal<QueryResultItem[]>;
354
- /** Target entity type's CLR type name; used to load the grid's column headers. */
355
- referenceType: _angular_core.InputSignal<string | undefined>;
356
- /** Renders the field with the Bootstrap is-invalid state. */
357
- isInvalid: _angular_core.InputSignal<boolean>;
358
- /** Optional id forwarded to the readonly input (for label association). */
359
- inputId: _angular_core.InputSignal<string | undefined>;
360
- /** Optional resolved label appended to the modal header ("Select {title}"). */
361
- title: _angular_core.InputSignal<string>;
362
- valueChange: _angular_core.OutputEmitterRef<string | null>;
363
- colors: typeof Color;
364
- showModal: _angular_core.WritableSignal<boolean>;
365
- entityType: _angular_core.WritableSignal<EntityType | null>;
366
- pagination: _angular_core.WritableSignal<PaginationResponse<QueryResultItem> | undefined>;
367
- settings: _angular_core.WritableSignal<DatatableSettings>;
368
- searchTerm: string;
369
- visibleAttributes: _angular_core.Signal<_mintplayer_ng_spark_models.EntityAttributeDefinition[]>;
370
- rows: _angular_core.Signal<QueryResultItem[]>;
371
- displayValue: _angular_core.Signal<string>;
372
- open(): Promise<void>;
373
- onSearchChange(): void;
374
- applyFilter(): void;
375
- clearSearch(): void;
376
- select(item: QueryResultItem): void;
377
- close(): void;
378
- static ɵfac: _angular_core.ɵɵFactoryDeclaration<SparkReferencePickerComponent, never>;
379
- static ɵcmp: _angular_core.ɵɵComponentDeclaration<SparkReferencePickerComponent, "spark-reference-picker", never, { "value": { "alias": "value"; "required": false; "isSignal": true; }; "options": { "alias": "options"; "required": false; "isSignal": true; }; "referenceType": { "alias": "referenceType"; "required": false; "isSignal": true; }; "isInvalid": { "alias": "isInvalid"; "required": false; "isSignal": true; }; "inputId": { "alias": "inputId"; "required": false; "isSignal": true; }; "title": { "alias": "title"; "required": false; "isSignal": true; }; }, { "valueChange": "valueChange"; }, never, never, true, never>;
475
+ export declare class SparkReferencePickerComponent {
476
+ private readonly sparkService;
477
+ private readonly lang;
478
+ /** Currently selected referenced id (or null when unset). */
479
+ value: import("@angular/core").InputSignal<string | null>;
480
+ /** Candidate items to pick from (the full query result, pre-loaded by the parent). */
481
+ options: import("@angular/core").InputSignal<QueryResultItem[]>;
482
+ /** Target entity type's CLR type name; used to load the grid's column headers. */
483
+ referenceType: import("@angular/core").InputSignal<string | undefined>;
484
+ /** Renders the field with the Bootstrap is-invalid state. */
485
+ isInvalid: import("@angular/core").InputSignal<boolean>;
486
+ /** Optional id forwarded to the readonly input (for label association). */
487
+ inputId: import("@angular/core").InputSignal<string | undefined>;
488
+ /** Optional resolved label appended to the modal header ("Select {title}"). */
489
+ title: import("@angular/core").InputSignal<string>;
490
+ valueChange: import("@angular/core").OutputEmitterRef<string | null>;
491
+ colors: typeof Color;
492
+ showModal: import("@angular/core").WritableSignal<boolean>;
493
+ entityType: import("@angular/core").WritableSignal<EntityType | null>;
494
+ pagination: import("@angular/core").WritableSignal<PaginationResponse<QueryResultItem> | undefined>;
495
+ settings: import("@angular/core").WritableSignal<DatatableSettings>;
496
+ searchTerm: string;
497
+ visibleAttributes: import("@angular/core").Signal<import("@mintplayer/ng-spark/models").EntityAttributeDefinition[]>;
498
+ rows: import("@angular/core").Signal<QueryResultItem[]>;
499
+ displayValue: import("@angular/core").Signal<string>;
500
+ open(): Promise<void>;
501
+ onSearchChange(): void;
502
+ applyFilter(): void;
503
+ clearSearch(): void;
504
+ select(item: QueryResultItem): void;
505
+ close(): void;
506
+ static ɵfac: i0.ɵɵFactoryDeclaration<SparkReferencePickerComponent, never>;
507
+ static ɵcmp: i0.ɵɵComponentDeclaration<SparkReferencePickerComponent, "spark-reference-picker", never, {
508
+ "value": {
509
+ "alias": "value";
510
+ "required": false;
511
+ "isSignal": true;
512
+ };
513
+ "options": {
514
+ "alias": "options";
515
+ "required": false;
516
+ "isSignal": true;
517
+ };
518
+ "referenceType": {
519
+ "alias": "referenceType";
520
+ "required": false;
521
+ "isSignal": true;
522
+ };
523
+ "isInvalid": {
524
+ "alias": "isInvalid";
525
+ "required": false;
526
+ "isSignal": true;
527
+ };
528
+ "inputId": {
529
+ "alias": "inputId";
530
+ "required": false;
531
+ "isSignal": true;
532
+ };
533
+ "title": {
534
+ "alias": "title";
535
+ "required": false;
536
+ "isSignal": true;
537
+ };
538
+ }, {
539
+ "valueChange": "valueChange";
540
+ }, never, never, true, never>;
380
541
  }
381
-
382
542
  /**
383
543
  * Reusable LookupReference value picker: a readonly textbox showing the current selection's
384
544
  * translated label plus a "…" button that opens a searchable modal list over the active
385
545
  * `options`. Per-instance state, so it serves both top-level lookup attributes (display type
386
546
  * Modal) and individual inline AsDetail lookup cells. Emits the picked key via `valueChange`.
387
547
  */
388
- declare class SparkLookupPickerComponent {
389
- /** Currently selected lookup key (or null when unset). */
390
- value: _angular_core.InputSignal<string | null>;
391
- /** Active lookup values to pick from. */
392
- options: _angular_core.InputSignal<LookupReferenceValue[]>;
393
- /** Renders the field with the Bootstrap is-invalid state. */
394
- isInvalid: _angular_core.InputSignal<boolean>;
395
- /** Optional id forwarded to the readonly input (for label association). */
396
- inputId: _angular_core.InputSignal<string | undefined>;
397
- /** Optional resolved label appended to the modal header ("Select {title}"). */
398
- title: _angular_core.InputSignal<string>;
399
- valueChange: _angular_core.OutputEmitterRef<string | null>;
400
- colors: typeof Color;
401
- showModal: _angular_core.WritableSignal<boolean>;
402
- searchTerm: _angular_core.WritableSignal<string>;
403
- displayValue: _angular_core.Signal<string>;
404
- filteredItems: _angular_core.Signal<LookupReferenceValue[]>;
405
- open(): void;
406
- select(item: LookupReferenceValue): void;
407
- close(): void;
408
- static ɵfac: _angular_core.ɵɵFactoryDeclaration<SparkLookupPickerComponent, never>;
409
- static ɵcmp: _angular_core.ɵɵComponentDeclaration<SparkLookupPickerComponent, "spark-lookup-picker", never, { "value": { "alias": "value"; "required": false; "isSignal": true; }; "options": { "alias": "options"; "required": false; "isSignal": true; }; "isInvalid": { "alias": "isInvalid"; "required": false; "isSignal": true; }; "inputId": { "alias": "inputId"; "required": false; "isSignal": true; }; "title": { "alias": "title"; "required": false; "isSignal": true; }; }, { "valueChange": "valueChange"; }, never, never, true, never>;
410
- }
411
-
412
- export { SparkLookupPickerComponent, SparkPoFormComponent, SparkReferencePickerComponent };
413
- export type { NestedTriggerRequest };
548
+ export declare class SparkLookupPickerComponent {
549
+ /** Currently selected lookup key (or null when unset). */
550
+ value: import("@angular/core").InputSignal<string | null>;
551
+ /** Active lookup values to pick from. */
552
+ options: import("@angular/core").InputSignal<LookupReferenceValue[]>;
553
+ /** Renders the field with the Bootstrap is-invalid state. */
554
+ isInvalid: import("@angular/core").InputSignal<boolean>;
555
+ /** Optional id forwarded to the readonly input (for label association). */
556
+ inputId: import("@angular/core").InputSignal<string | undefined>;
557
+ /** Optional resolved label appended to the modal header ("Select {title}"). */
558
+ title: import("@angular/core").InputSignal<string>;
559
+ valueChange: import("@angular/core").OutputEmitterRef<string | null>;
560
+ colors: typeof Color;
561
+ showModal: import("@angular/core").WritableSignal<boolean>;
562
+ searchTerm: import("@angular/core").WritableSignal<string>;
563
+ displayValue: import("@angular/core").Signal<string>;
564
+ filteredItems: import("@angular/core").Signal<LookupReferenceValue[]>;
565
+ open(): void;
566
+ select(item: LookupReferenceValue): void;
567
+ close(): void;
568
+ static ɵfac: i0.ɵɵFactoryDeclaration<SparkLookupPickerComponent, never>;
569
+ static ɵcmp: i0.ɵɵComponentDeclaration<SparkLookupPickerComponent, "spark-lookup-picker", never, {
570
+ "value": {
571
+ "alias": "value";
572
+ "required": false;
573
+ "isSignal": true;
574
+ };
575
+ "options": {
576
+ "alias": "options";
577
+ "required": false;
578
+ "isSignal": true;
579
+ };
580
+ "isInvalid": {
581
+ "alias": "isInvalid";
582
+ "required": false;
583
+ "isSignal": true;
584
+ };
585
+ "inputId": {
586
+ "alias": "inputId";
587
+ "required": false;
588
+ "isSignal": true;
589
+ };
590
+ "title": {
591
+ "alias": "title";
592
+ "required": false;
593
+ "isSignal": true;
594
+ };
595
+ }, {
596
+ "valueChange": "valueChange";
597
+ }, never, never, true, never>;
598
+ }