@momentumcms/admin 0.1.9 → 0.2.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.
- package/fesm2022/momentumcms-admin-array-field.component-CT5NlIEv.mjs +316 -0
- package/fesm2022/momentumcms-admin-array-field.component-CT5NlIEv.mjs.map +1 -0
- package/fesm2022/momentumcms-admin-blocks-field.component-Cz7HmuBK.mjs +362 -0
- package/fesm2022/momentumcms-admin-blocks-field.component-Cz7HmuBK.mjs.map +1 -0
- package/fesm2022/momentumcms-admin-collapsible-field.component-CtwrGQvg.mjs +120 -0
- package/fesm2022/momentumcms-admin-collapsible-field.component-CtwrGQvg.mjs.map +1 -0
- package/fesm2022/{momentumcms-admin-global-edit.page-DFDV-uh3.mjs → momentumcms-admin-global-edit.page-BBUtWCSl.mjs} +2 -2
- package/fesm2022/{momentumcms-admin-global-edit.page-DFDV-uh3.mjs.map → momentumcms-admin-global-edit.page-BBUtWCSl.mjs.map} +1 -1
- package/fesm2022/momentumcms-admin-group-field.component-BZeG8Oqy.mjs +184 -0
- package/fesm2022/momentumcms-admin-group-field.component-BZeG8Oqy.mjs.map +1 -0
- package/fesm2022/{momentumcms-admin-momentumcms-admin-z82jXEsP.mjs → momentumcms-admin-momentumcms-admin-o0FbJXZN.mjs} +9688 -12241
- package/fesm2022/momentumcms-admin-momentumcms-admin-o0FbJXZN.mjs.map +1 -0
- package/fesm2022/momentumcms-admin-relationship-field.component-BuxtRs2_.mjs +473 -0
- package/fesm2022/momentumcms-admin-relationship-field.component-BuxtRs2_.mjs.map +1 -0
- package/fesm2022/momentumcms-admin-rich-text-field.component-DKQ6pwp7.mjs +813 -0
- package/fesm2022/momentumcms-admin-rich-text-field.component-DKQ6pwp7.mjs.map +1 -0
- package/fesm2022/momentumcms-admin-row-field.component-ks3FXd4B.mjs +98 -0
- package/fesm2022/momentumcms-admin-row-field.component-ks3FXd4B.mjs.map +1 -0
- package/fesm2022/momentumcms-admin-tabs-field.component-mZ4dpZoD.mjs +189 -0
- package/fesm2022/momentumcms-admin-tabs-field.component-mZ4dpZoD.mjs.map +1 -0
- package/fesm2022/momentumcms-admin.mjs +1 -1
- package/package.json +1 -1
- package/types/momentumcms-admin.d.ts +79 -13
- package/fesm2022/momentumcms-admin-momentumcms-admin-z82jXEsP.mjs.map +0 -1
|
@@ -0,0 +1,473 @@
|
|
|
1
|
+
import * as i0 from '@angular/core';
|
|
2
|
+
import { inject, input, computed, signal, effect, ChangeDetectionStrategy, Component } from '@angular/core';
|
|
3
|
+
import { HttpClient } from '@angular/common/http';
|
|
4
|
+
import { McmsFormField, Badge } from '@momentumcms/ui';
|
|
5
|
+
import { NgIcon, provideIcons } from '@ng-icons/core';
|
|
6
|
+
import { heroEye, heroPlus, heroXMark } from '@ng-icons/heroicons/outline';
|
|
7
|
+
import { d as EntitySheetService, a as getFieldNodeState, i as isRecord, e as getTitleField } from './momentumcms-admin-momentumcms-admin-o0FbJXZN.mjs';
|
|
8
|
+
import { humanizeFieldName } from '@momentumcms/core';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Relationship field renderer.
|
|
12
|
+
*
|
|
13
|
+
* Fetches related collection documents and renders a dropdown selector.
|
|
14
|
+
* Supports single select (default) and multi-select (hasMany: true).
|
|
15
|
+
* Multi-select shows selected items as badges with remove buttons.
|
|
16
|
+
*
|
|
17
|
+
* Uses Angular Signal Forms bridge pattern: reads/writes value via
|
|
18
|
+
* a FieldTree node's FieldState rather than event-based I/O.
|
|
19
|
+
*/
|
|
20
|
+
class RelationshipFieldRenderer {
|
|
21
|
+
http = inject(HttpClient);
|
|
22
|
+
entitySheetService = inject(EntitySheetService, { optional: true });
|
|
23
|
+
/** Field definition (must be a RelationshipField) */
|
|
24
|
+
field = input.required(...(ngDevMode ? [{ debugName: "field" }] : []));
|
|
25
|
+
/** Signal forms FieldTree node for this field */
|
|
26
|
+
formNode = input(null, ...(ngDevMode ? [{ debugName: "formNode" }] : []));
|
|
27
|
+
/** Form mode */
|
|
28
|
+
mode = input('create', ...(ngDevMode ? [{ debugName: "mode" }] : []));
|
|
29
|
+
/** Field path */
|
|
30
|
+
path = input.required(...(ngDevMode ? [{ debugName: "path" }] : []));
|
|
31
|
+
/** Full form model data (used for filterOptions) */
|
|
32
|
+
formModel = input({}, ...(ngDevMode ? [{ debugName: "formModel" }] : []));
|
|
33
|
+
/** Bridge: extract FieldState from formNode */
|
|
34
|
+
nodeState = computed(() => getFieldNodeState(this.formNode()), ...(ngDevMode ? [{ debugName: "nodeState" }] : []));
|
|
35
|
+
/** Loaded options from related collection */
|
|
36
|
+
allOptions = signal([], ...(ngDevMode ? [{ debugName: "allOptions" }] : []));
|
|
37
|
+
/** Loading state */
|
|
38
|
+
isLoading = signal(false, ...(ngDevMode ? [{ debugName: "isLoading" }] : []));
|
|
39
|
+
/** Unique field ID */
|
|
40
|
+
fieldId = computed(() => `field-${this.path().replace(/\./g, '-')}`, ...(ngDevMode ? [{ debugName: "fieldId" }] : []));
|
|
41
|
+
/** Computed label */
|
|
42
|
+
label = computed(() => this.field().label || humanizeFieldName(this.field().name), ...(ngDevMode ? [{ debugName: "label" }] : []));
|
|
43
|
+
/** Whether the field is required */
|
|
44
|
+
required = computed(() => this.field().required ?? false, ...(ngDevMode ? [{ debugName: "required" }] : []));
|
|
45
|
+
/** Whether the field is disabled */
|
|
46
|
+
isDisabled = computed(() => this.mode() === 'view' || (this.field().admin?.readOnly ?? false), ...(ngDevMode ? [{ debugName: "isDisabled" }] : []));
|
|
47
|
+
/** Whether this is a hasMany relationship */
|
|
48
|
+
isMulti = computed(() => {
|
|
49
|
+
const f = this.field();
|
|
50
|
+
return f.type === 'relationship' ? (f.hasMany ?? false) : false;
|
|
51
|
+
}, ...(ngDevMode ? [{ debugName: "isMulti" }] : []));
|
|
52
|
+
/** Related collection slug extracted from the lazy collection reference */
|
|
53
|
+
relatedSlug = computed(() => {
|
|
54
|
+
const f = this.field();
|
|
55
|
+
if (f.type === 'relationship') {
|
|
56
|
+
const config = f.collection();
|
|
57
|
+
if (isRecord(config) && typeof config['slug'] === 'string') {
|
|
58
|
+
return config['slug'];
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return '';
|
|
62
|
+
}, ...(ngDevMode ? [{ debugName: "relatedSlug" }] : []));
|
|
63
|
+
/** The field name used to display document titles */
|
|
64
|
+
titleField = computed(() => {
|
|
65
|
+
const f = this.field();
|
|
66
|
+
if (f.type !== 'relationship')
|
|
67
|
+
return 'id';
|
|
68
|
+
const config = f.collection();
|
|
69
|
+
if (!isRecord(config))
|
|
70
|
+
return 'id';
|
|
71
|
+
return getTitleField(config);
|
|
72
|
+
}, ...(ngDevMode ? [{ debugName: "titleField" }] : []));
|
|
73
|
+
/** Label for the related collection (singular) */
|
|
74
|
+
relatedLabel = computed(() => {
|
|
75
|
+
const f = this.field();
|
|
76
|
+
if (f.type !== 'relationship')
|
|
77
|
+
return 'item';
|
|
78
|
+
const config = f.collection();
|
|
79
|
+
if (!isRecord(config))
|
|
80
|
+
return 'item';
|
|
81
|
+
const labels = config['labels'];
|
|
82
|
+
if (isRecord(labels) && typeof labels['singular'] === 'string') {
|
|
83
|
+
return labels['singular'];
|
|
84
|
+
}
|
|
85
|
+
if (typeof config['slug'] === 'string') {
|
|
86
|
+
return config['slug'];
|
|
87
|
+
}
|
|
88
|
+
return 'item';
|
|
89
|
+
}, ...(ngDevMode ? [{ debugName: "relatedLabel" }] : []));
|
|
90
|
+
/** Current value for single-select mode */
|
|
91
|
+
singleValue = computed(() => {
|
|
92
|
+
const state = this.nodeState();
|
|
93
|
+
if (!state)
|
|
94
|
+
return '';
|
|
95
|
+
const val = state.value();
|
|
96
|
+
if (typeof val === 'string')
|
|
97
|
+
return val;
|
|
98
|
+
// Handle populated objects with an id
|
|
99
|
+
if (isRecord(val) && typeof val['id'] === 'string')
|
|
100
|
+
return val['id'];
|
|
101
|
+
return '';
|
|
102
|
+
}, ...(ngDevMode ? [{ debugName: "singleValue" }] : []));
|
|
103
|
+
/** Current values for multi-select mode */
|
|
104
|
+
multiValues = computed(() => {
|
|
105
|
+
const state = this.nodeState();
|
|
106
|
+
if (!state)
|
|
107
|
+
return [];
|
|
108
|
+
const val = state.value();
|
|
109
|
+
if (!Array.isArray(val))
|
|
110
|
+
return [];
|
|
111
|
+
return val
|
|
112
|
+
.map((item) => {
|
|
113
|
+
if (typeof item === 'string')
|
|
114
|
+
return item;
|
|
115
|
+
if (isRecord(item) && typeof item['id'] === 'string')
|
|
116
|
+
return item['id'];
|
|
117
|
+
return '';
|
|
118
|
+
})
|
|
119
|
+
.filter((v) => v !== '');
|
|
120
|
+
}, ...(ngDevMode ? [{ debugName: "multiValues" }] : []));
|
|
121
|
+
/** Selected options for multi-select display (resolved to labels) */
|
|
122
|
+
selectedOptions = computed(() => {
|
|
123
|
+
const values = this.multiValues();
|
|
124
|
+
const options = this.allOptions();
|
|
125
|
+
return values
|
|
126
|
+
.map((v) => options.find((opt) => opt.value === v))
|
|
127
|
+
.filter((opt) => opt !== undefined);
|
|
128
|
+
}, ...(ngDevMode ? [{ debugName: "selectedOptions" }] : []));
|
|
129
|
+
/** Available options for multi-select (excludes already selected) */
|
|
130
|
+
availableOptions = computed(() => {
|
|
131
|
+
const selected = new Set(this.multiValues());
|
|
132
|
+
return this.allOptions().filter((opt) => !selected.has(opt.value));
|
|
133
|
+
}, ...(ngDevMode ? [{ debugName: "availableOptions" }] : []));
|
|
134
|
+
/** Validation errors shown only when field is touched */
|
|
135
|
+
touchedErrors = computed(() => {
|
|
136
|
+
const state = this.nodeState();
|
|
137
|
+
if (!state || !state.touched())
|
|
138
|
+
return [];
|
|
139
|
+
return state.errors().map((e) => ({ kind: e.kind, message: e.message }));
|
|
140
|
+
}, ...(ngDevMode ? [{ debugName: "touchedErrors" }] : []));
|
|
141
|
+
constructor() {
|
|
142
|
+
// Fetch related collection docs when the slug is resolved.
|
|
143
|
+
// Uses onCleanup to cancel in-flight requests if the slug changes.
|
|
144
|
+
effect((onCleanup) => {
|
|
145
|
+
const slug = this.relatedSlug();
|
|
146
|
+
if (slug) {
|
|
147
|
+
const sub = this.fetchOptions(slug);
|
|
148
|
+
onCleanup(() => sub.unsubscribe());
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
/** Handle single-select change */
|
|
153
|
+
onSingleSelect(event) {
|
|
154
|
+
const target = event.target;
|
|
155
|
+
if (target instanceof HTMLSelectElement) {
|
|
156
|
+
const state = this.nodeState();
|
|
157
|
+
if (state) {
|
|
158
|
+
state.value.set(target.value || null);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
/** Handle multi-select add */
|
|
163
|
+
onMultiSelect(event) {
|
|
164
|
+
const target = event.target;
|
|
165
|
+
if (!(target instanceof HTMLSelectElement))
|
|
166
|
+
return;
|
|
167
|
+
const selectedValue = target.value;
|
|
168
|
+
if (!selectedValue)
|
|
169
|
+
return;
|
|
170
|
+
const state = this.nodeState();
|
|
171
|
+
if (!state)
|
|
172
|
+
return;
|
|
173
|
+
const currentValues = this.multiValues();
|
|
174
|
+
if (!currentValues.includes(selectedValue)) {
|
|
175
|
+
state.value.set([...currentValues, selectedValue]);
|
|
176
|
+
}
|
|
177
|
+
// Reset dropdown to placeholder
|
|
178
|
+
target.value = '';
|
|
179
|
+
}
|
|
180
|
+
/** Remove a value from multi-select */
|
|
181
|
+
removeSelection(valueToRemove) {
|
|
182
|
+
const state = this.nodeState();
|
|
183
|
+
if (!state)
|
|
184
|
+
return;
|
|
185
|
+
const currentValues = this.multiValues();
|
|
186
|
+
state.value.set(currentValues.filter((v) => v !== valueToRemove));
|
|
187
|
+
}
|
|
188
|
+
/** Whether there is a current selection (for showing "View" button) */
|
|
189
|
+
hasSelection = computed(() => !!this.singleValue() || this.multiValues().length > 0, ...(ngDevMode ? [{ debugName: "hasSelection" }] : []));
|
|
190
|
+
/**
|
|
191
|
+
* Handle blur from select elements.
|
|
192
|
+
*/
|
|
193
|
+
onBlur() {
|
|
194
|
+
const state = this.nodeState();
|
|
195
|
+
if (state)
|
|
196
|
+
state.markAsTouched();
|
|
197
|
+
}
|
|
198
|
+
/** Open the entity sheet to create a new related entity */
|
|
199
|
+
onCreateRelated() {
|
|
200
|
+
const slug = this.relatedSlug();
|
|
201
|
+
if (!slug || !this.entitySheetService)
|
|
202
|
+
return;
|
|
203
|
+
this.entitySheetService.openCreate(slug).subscribe((result) => {
|
|
204
|
+
if (result.action === 'created' && result.entity) {
|
|
205
|
+
const state = this.nodeState();
|
|
206
|
+
if (!state)
|
|
207
|
+
return;
|
|
208
|
+
const entityId = String(result.entity.id);
|
|
209
|
+
if (this.isMulti()) {
|
|
210
|
+
const current = this.multiValues();
|
|
211
|
+
state.value.set([...current, entityId]);
|
|
212
|
+
}
|
|
213
|
+
else {
|
|
214
|
+
state.value.set(entityId);
|
|
215
|
+
}
|
|
216
|
+
// Refresh options to include the newly created entity
|
|
217
|
+
this.fetchOptions(slug);
|
|
218
|
+
}
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
/** Open the entity sheet to view the selected related entity */
|
|
222
|
+
onViewRelated() {
|
|
223
|
+
const slug = this.relatedSlug();
|
|
224
|
+
if (!slug || !this.entitySheetService)
|
|
225
|
+
return;
|
|
226
|
+
const id = this.isMulti() ? this.multiValues()[0] : this.singleValue();
|
|
227
|
+
if (!id)
|
|
228
|
+
return;
|
|
229
|
+
this.entitySheetService.openView(slug, id).subscribe((result) => {
|
|
230
|
+
if (result.action === 'deleted') {
|
|
231
|
+
const state = this.nodeState();
|
|
232
|
+
if (!state)
|
|
233
|
+
return;
|
|
234
|
+
// Clear the deleted entity from the selection
|
|
235
|
+
if (this.isMulti()) {
|
|
236
|
+
const current = this.multiValues();
|
|
237
|
+
state.value.set(current.filter((v) => v !== id));
|
|
238
|
+
}
|
|
239
|
+
else {
|
|
240
|
+
state.value.set(null);
|
|
241
|
+
}
|
|
242
|
+
// Refresh options to remove the deleted entity
|
|
243
|
+
this.fetchOptions(slug);
|
|
244
|
+
}
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
/** Fetch options from the related collection API, returns subscription for cleanup */
|
|
248
|
+
fetchOptions(slug) {
|
|
249
|
+
this.isLoading.set(true);
|
|
250
|
+
const titleField = this.titleField();
|
|
251
|
+
// Build query params, including filterOptions if defined
|
|
252
|
+
const params = { limit: '100' };
|
|
253
|
+
const f = this.field();
|
|
254
|
+
if (f.type === 'relationship' && f.filterOptions) {
|
|
255
|
+
const whereClause = f.filterOptions({ data: this.formModel() });
|
|
256
|
+
for (const [key, val] of Object.entries(whereClause)) {
|
|
257
|
+
if (val !== undefined && val !== null) {
|
|
258
|
+
params[`where[${key}]`] = String(val);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
return this.http
|
|
263
|
+
.get(`/api/${slug}`, {
|
|
264
|
+
params,
|
|
265
|
+
})
|
|
266
|
+
.subscribe({
|
|
267
|
+
next: (response) => {
|
|
268
|
+
const docs = response.docs ?? [];
|
|
269
|
+
const options = docs.map((doc) => {
|
|
270
|
+
const id = typeof doc['id'] === 'string' ? doc['id'] : String(doc['id'] ?? '');
|
|
271
|
+
const titleValue = doc[titleField];
|
|
272
|
+
const label = titleField !== 'id' && typeof titleValue === 'string' ? titleValue : id;
|
|
273
|
+
return { value: id, label };
|
|
274
|
+
});
|
|
275
|
+
this.allOptions.set(options);
|
|
276
|
+
this.isLoading.set(false);
|
|
277
|
+
},
|
|
278
|
+
error: (err) => {
|
|
279
|
+
console.error(`Failed to load relationship options for "${slug}":`, err);
|
|
280
|
+
this.allOptions.set([]);
|
|
281
|
+
this.isLoading.set(false);
|
|
282
|
+
},
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.2", ngImport: i0, type: RelationshipFieldRenderer, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
286
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.2", type: RelationshipFieldRenderer, isStandalone: true, selector: "mcms-relationship-field-renderer", inputs: { field: { classPropertyName: "field", publicName: "field", isSignal: true, isRequired: true, transformFunction: null }, formNode: { classPropertyName: "formNode", publicName: "formNode", isSignal: true, isRequired: false, transformFunction: null }, mode: { classPropertyName: "mode", publicName: "mode", isSignal: true, isRequired: false, transformFunction: null }, path: { classPropertyName: "path", publicName: "path", isSignal: true, isRequired: true, transformFunction: null }, formModel: { classPropertyName: "formModel", publicName: "formModel", isSignal: true, isRequired: false, transformFunction: null } }, host: { classAttribute: "block" }, providers: [provideIcons({ heroXMark, heroPlus, heroEye })], ngImport: i0, template: `
|
|
287
|
+
<mcms-form-field
|
|
288
|
+
[id]="fieldId()"
|
|
289
|
+
[required]="required()"
|
|
290
|
+
[disabled]="isDisabled()"
|
|
291
|
+
[errors]="touchedErrors()"
|
|
292
|
+
>
|
|
293
|
+
<span mcmsLabel>{{ label() }}</span>
|
|
294
|
+
|
|
295
|
+
@if (isLoading()) {
|
|
296
|
+
<p class="text-sm text-muted-foreground py-2">Loading options...</p>
|
|
297
|
+
} @else if (isMulti()) {
|
|
298
|
+
@if (selectedOptions().length > 0) {
|
|
299
|
+
<div class="flex flex-wrap gap-1 mb-2">
|
|
300
|
+
@for (opt of selectedOptions(); track opt.value) {
|
|
301
|
+
<mcms-badge variant="secondary">
|
|
302
|
+
{{ opt.label }}
|
|
303
|
+
@if (!isDisabled()) {
|
|
304
|
+
<button
|
|
305
|
+
type="button"
|
|
306
|
+
class="ml-1 hover:text-destructive"
|
|
307
|
+
(click)="removeSelection(opt.value)"
|
|
308
|
+
[attr.aria-label]="'Remove ' + opt.label"
|
|
309
|
+
>
|
|
310
|
+
<ng-icon name="heroXMark" size="12" aria-hidden="true" />
|
|
311
|
+
</button>
|
|
312
|
+
}
|
|
313
|
+
</mcms-badge>
|
|
314
|
+
}
|
|
315
|
+
</div>
|
|
316
|
+
}
|
|
317
|
+
@if (!isDisabled()) {
|
|
318
|
+
<select
|
|
319
|
+
[id]="fieldId()"
|
|
320
|
+
class="flex h-10 w-full items-center justify-between rounded-md border border-input bg-background px-3 py-2 text-sm text-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2"
|
|
321
|
+
(change)="onMultiSelect($event)"
|
|
322
|
+
(blur)="onBlur()"
|
|
323
|
+
>
|
|
324
|
+
<option value="">Add {{ relatedLabel() }}...</option>
|
|
325
|
+
@for (opt of availableOptions(); track opt.value) {
|
|
326
|
+
<option [value]="opt.value">{{ opt.label }}</option>
|
|
327
|
+
}
|
|
328
|
+
</select>
|
|
329
|
+
}
|
|
330
|
+
} @else {
|
|
331
|
+
<select
|
|
332
|
+
[id]="fieldId()"
|
|
333
|
+
[disabled]="isDisabled()"
|
|
334
|
+
class="flex h-10 w-full items-center justify-between rounded-md border border-input bg-background px-3 py-2 text-sm text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50"
|
|
335
|
+
(change)="onSingleSelect($event)"
|
|
336
|
+
(blur)="onBlur()"
|
|
337
|
+
>
|
|
338
|
+
<option value="">Select {{ relatedLabel() }}...</option>
|
|
339
|
+
@for (opt of allOptions(); track opt.value) {
|
|
340
|
+
<option [value]="opt.value" [selected]="opt.value === singleValue()">
|
|
341
|
+
{{ opt.label }}
|
|
342
|
+
</option>
|
|
343
|
+
}
|
|
344
|
+
</select>
|
|
345
|
+
}
|
|
346
|
+
</mcms-form-field>
|
|
347
|
+
|
|
348
|
+
@if (entitySheetService && !isDisabled()) {
|
|
349
|
+
<div class="flex gap-2 mt-1.5">
|
|
350
|
+
<button
|
|
351
|
+
type="button"
|
|
352
|
+
class="inline-flex items-center gap-1 text-xs text-muted-foreground hover:text-foreground transition-colors"
|
|
353
|
+
(click)="onCreateRelated()"
|
|
354
|
+
[attr.aria-label]="'Create new ' + relatedLabel()"
|
|
355
|
+
>
|
|
356
|
+
<ng-icon name="heroPlus" size="14" aria-hidden="true" />
|
|
357
|
+
New
|
|
358
|
+
</button>
|
|
359
|
+
@if (hasSelection()) {
|
|
360
|
+
<button
|
|
361
|
+
type="button"
|
|
362
|
+
class="inline-flex items-center gap-1 text-xs text-muted-foreground hover:text-foreground transition-colors"
|
|
363
|
+
(click)="onViewRelated()"
|
|
364
|
+
[attr.aria-label]="'View ' + relatedLabel()"
|
|
365
|
+
>
|
|
366
|
+
<ng-icon name="heroEye" size="14" aria-hidden="true" />
|
|
367
|
+
View
|
|
368
|
+
</button>
|
|
369
|
+
}
|
|
370
|
+
</div>
|
|
371
|
+
}
|
|
372
|
+
`, isInline: true, dependencies: [{ kind: "component", type: McmsFormField, selector: "mcms-form-field", inputs: ["id", "required", "disabled", "errors", "hint", "hasLabel"] }, { kind: "component", type: Badge, selector: "mcms-badge", inputs: ["variant", "class", "ariaLabel"] }, { kind: "component", type: NgIcon, selector: "ng-icon", inputs: ["name", "svg", "size", "strokeWidth", "color"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
373
|
+
}
|
|
374
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.2", ngImport: i0, type: RelationshipFieldRenderer, decorators: [{
|
|
375
|
+
type: Component,
|
|
376
|
+
args: [{
|
|
377
|
+
selector: 'mcms-relationship-field-renderer',
|
|
378
|
+
imports: [McmsFormField, Badge, NgIcon],
|
|
379
|
+
providers: [provideIcons({ heroXMark, heroPlus, heroEye })],
|
|
380
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
381
|
+
host: { class: 'block' },
|
|
382
|
+
template: `
|
|
383
|
+
<mcms-form-field
|
|
384
|
+
[id]="fieldId()"
|
|
385
|
+
[required]="required()"
|
|
386
|
+
[disabled]="isDisabled()"
|
|
387
|
+
[errors]="touchedErrors()"
|
|
388
|
+
>
|
|
389
|
+
<span mcmsLabel>{{ label() }}</span>
|
|
390
|
+
|
|
391
|
+
@if (isLoading()) {
|
|
392
|
+
<p class="text-sm text-muted-foreground py-2">Loading options...</p>
|
|
393
|
+
} @else if (isMulti()) {
|
|
394
|
+
@if (selectedOptions().length > 0) {
|
|
395
|
+
<div class="flex flex-wrap gap-1 mb-2">
|
|
396
|
+
@for (opt of selectedOptions(); track opt.value) {
|
|
397
|
+
<mcms-badge variant="secondary">
|
|
398
|
+
{{ opt.label }}
|
|
399
|
+
@if (!isDisabled()) {
|
|
400
|
+
<button
|
|
401
|
+
type="button"
|
|
402
|
+
class="ml-1 hover:text-destructive"
|
|
403
|
+
(click)="removeSelection(opt.value)"
|
|
404
|
+
[attr.aria-label]="'Remove ' + opt.label"
|
|
405
|
+
>
|
|
406
|
+
<ng-icon name="heroXMark" size="12" aria-hidden="true" />
|
|
407
|
+
</button>
|
|
408
|
+
}
|
|
409
|
+
</mcms-badge>
|
|
410
|
+
}
|
|
411
|
+
</div>
|
|
412
|
+
}
|
|
413
|
+
@if (!isDisabled()) {
|
|
414
|
+
<select
|
|
415
|
+
[id]="fieldId()"
|
|
416
|
+
class="flex h-10 w-full items-center justify-between rounded-md border border-input bg-background px-3 py-2 text-sm text-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2"
|
|
417
|
+
(change)="onMultiSelect($event)"
|
|
418
|
+
(blur)="onBlur()"
|
|
419
|
+
>
|
|
420
|
+
<option value="">Add {{ relatedLabel() }}...</option>
|
|
421
|
+
@for (opt of availableOptions(); track opt.value) {
|
|
422
|
+
<option [value]="opt.value">{{ opt.label }}</option>
|
|
423
|
+
}
|
|
424
|
+
</select>
|
|
425
|
+
}
|
|
426
|
+
} @else {
|
|
427
|
+
<select
|
|
428
|
+
[id]="fieldId()"
|
|
429
|
+
[disabled]="isDisabled()"
|
|
430
|
+
class="flex h-10 w-full items-center justify-between rounded-md border border-input bg-background px-3 py-2 text-sm text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50"
|
|
431
|
+
(change)="onSingleSelect($event)"
|
|
432
|
+
(blur)="onBlur()"
|
|
433
|
+
>
|
|
434
|
+
<option value="">Select {{ relatedLabel() }}...</option>
|
|
435
|
+
@for (opt of allOptions(); track opt.value) {
|
|
436
|
+
<option [value]="opt.value" [selected]="opt.value === singleValue()">
|
|
437
|
+
{{ opt.label }}
|
|
438
|
+
</option>
|
|
439
|
+
}
|
|
440
|
+
</select>
|
|
441
|
+
}
|
|
442
|
+
</mcms-form-field>
|
|
443
|
+
|
|
444
|
+
@if (entitySheetService && !isDisabled()) {
|
|
445
|
+
<div class="flex gap-2 mt-1.5">
|
|
446
|
+
<button
|
|
447
|
+
type="button"
|
|
448
|
+
class="inline-flex items-center gap-1 text-xs text-muted-foreground hover:text-foreground transition-colors"
|
|
449
|
+
(click)="onCreateRelated()"
|
|
450
|
+
[attr.aria-label]="'Create new ' + relatedLabel()"
|
|
451
|
+
>
|
|
452
|
+
<ng-icon name="heroPlus" size="14" aria-hidden="true" />
|
|
453
|
+
New
|
|
454
|
+
</button>
|
|
455
|
+
@if (hasSelection()) {
|
|
456
|
+
<button
|
|
457
|
+
type="button"
|
|
458
|
+
class="inline-flex items-center gap-1 text-xs text-muted-foreground hover:text-foreground transition-colors"
|
|
459
|
+
(click)="onViewRelated()"
|
|
460
|
+
[attr.aria-label]="'View ' + relatedLabel()"
|
|
461
|
+
>
|
|
462
|
+
<ng-icon name="heroEye" size="14" aria-hidden="true" />
|
|
463
|
+
View
|
|
464
|
+
</button>
|
|
465
|
+
}
|
|
466
|
+
</div>
|
|
467
|
+
}
|
|
468
|
+
`,
|
|
469
|
+
}]
|
|
470
|
+
}], ctorParameters: () => [], propDecorators: { field: [{ type: i0.Input, args: [{ isSignal: true, alias: "field", required: true }] }], formNode: [{ type: i0.Input, args: [{ isSignal: true, alias: "formNode", required: false }] }], mode: [{ type: i0.Input, args: [{ isSignal: true, alias: "mode", required: false }] }], path: [{ type: i0.Input, args: [{ isSignal: true, alias: "path", required: true }] }], formModel: [{ type: i0.Input, args: [{ isSignal: true, alias: "formModel", required: false }] }] } });
|
|
471
|
+
|
|
472
|
+
export { RelationshipFieldRenderer };
|
|
473
|
+
//# sourceMappingURL=momentumcms-admin-relationship-field.component-BuxtRs2_.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"momentumcms-admin-relationship-field.component-BuxtRs2_.mjs","sources":["../../../../libs/admin/src/lib/widgets/entity-form/field-renderers/relationship-field.component.ts"],"sourcesContent":["import {\n\tChangeDetectionStrategy,\n\tComponent,\n\tcomputed,\n\teffect,\n\tinject,\n\tinput,\n\tsignal,\n} from '@angular/core';\nimport { HttpClient } from '@angular/common/http';\nimport type { Subscription } from 'rxjs';\nimport { McmsFormField, Badge } from '@momentumcms/ui';\nimport type { ValidationError } from '@momentumcms/ui';\nimport { NgIcon, provideIcons } from '@ng-icons/core';\nimport { heroXMark, heroPlus, heroEye } from '@ng-icons/heroicons/outline';\nimport { EntitySheetService } from '../../../services/entity-sheet.service';\nimport { humanizeFieldName } from '@momentumcms/core';\nimport type { Field } from '@momentumcms/core';\nimport type { EntityFormMode } from '../entity-form.types';\nimport { getFieldNodeState, isRecord, getTitleField } from '../entity-form.types';\n\n/** Option for the relationship dropdown */\ninterface RelationshipOption {\n\tvalue: string;\n\tlabel: string;\n}\n\n/**\n * Relationship field renderer.\n *\n * Fetches related collection documents and renders a dropdown selector.\n * Supports single select (default) and multi-select (hasMany: true).\n * Multi-select shows selected items as badges with remove buttons.\n *\n * Uses Angular Signal Forms bridge pattern: reads/writes value via\n * a FieldTree node's FieldState rather than event-based I/O.\n */\n@Component({\n\tselector: 'mcms-relationship-field-renderer',\n\timports: [McmsFormField, Badge, NgIcon],\n\tproviders: [provideIcons({ heroXMark, heroPlus, heroEye })],\n\tchangeDetection: ChangeDetectionStrategy.OnPush,\n\thost: { class: 'block' },\n\ttemplate: `\n\t\t<mcms-form-field\n\t\t\t[id]=\"fieldId()\"\n\t\t\t[required]=\"required()\"\n\t\t\t[disabled]=\"isDisabled()\"\n\t\t\t[errors]=\"touchedErrors()\"\n\t\t>\n\t\t\t<span mcmsLabel>{{ label() }}</span>\n\n\t\t\t@if (isLoading()) {\n\t\t\t\t<p class=\"text-sm text-muted-foreground py-2\">Loading options...</p>\n\t\t\t} @else if (isMulti()) {\n\t\t\t\t@if (selectedOptions().length > 0) {\n\t\t\t\t\t<div class=\"flex flex-wrap gap-1 mb-2\">\n\t\t\t\t\t\t@for (opt of selectedOptions(); track opt.value) {\n\t\t\t\t\t\t\t<mcms-badge variant=\"secondary\">\n\t\t\t\t\t\t\t\t{{ opt.label }}\n\t\t\t\t\t\t\t\t@if (!isDisabled()) {\n\t\t\t\t\t\t\t\t\t<button\n\t\t\t\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\t\t\t\tclass=\"ml-1 hover:text-destructive\"\n\t\t\t\t\t\t\t\t\t\t(click)=\"removeSelection(opt.value)\"\n\t\t\t\t\t\t\t\t\t\t[attr.aria-label]=\"'Remove ' + opt.label\"\n\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t<ng-icon name=\"heroXMark\" size=\"12\" aria-hidden=\"true\" />\n\t\t\t\t\t\t\t\t\t</button>\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t</mcms-badge>\n\t\t\t\t\t\t}\n\t\t\t\t\t</div>\n\t\t\t\t}\n\t\t\t\t@if (!isDisabled()) {\n\t\t\t\t\t<select\n\t\t\t\t\t\t[id]=\"fieldId()\"\n\t\t\t\t\t\tclass=\"flex h-10 w-full items-center justify-between rounded-md border border-input bg-background px-3 py-2 text-sm text-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2\"\n\t\t\t\t\t\t(change)=\"onMultiSelect($event)\"\n\t\t\t\t\t\t(blur)=\"onBlur()\"\n\t\t\t\t\t>\n\t\t\t\t\t\t<option value=\"\">Add {{ relatedLabel() }}...</option>\n\t\t\t\t\t\t@for (opt of availableOptions(); track opt.value) {\n\t\t\t\t\t\t\t<option [value]=\"opt.value\">{{ opt.label }}</option>\n\t\t\t\t\t\t}\n\t\t\t\t\t</select>\n\t\t\t\t}\n\t\t\t} @else {\n\t\t\t\t<select\n\t\t\t\t\t[id]=\"fieldId()\"\n\t\t\t\t\t[disabled]=\"isDisabled()\"\n\t\t\t\t\tclass=\"flex h-10 w-full items-center justify-between rounded-md border border-input bg-background px-3 py-2 text-sm text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50\"\n\t\t\t\t\t(change)=\"onSingleSelect($event)\"\n\t\t\t\t\t(blur)=\"onBlur()\"\n\t\t\t\t>\n\t\t\t\t\t<option value=\"\">Select {{ relatedLabel() }}...</option>\n\t\t\t\t\t@for (opt of allOptions(); track opt.value) {\n\t\t\t\t\t\t<option [value]=\"opt.value\" [selected]=\"opt.value === singleValue()\">\n\t\t\t\t\t\t\t{{ opt.label }}\n\t\t\t\t\t\t</option>\n\t\t\t\t\t}\n\t\t\t\t</select>\n\t\t\t}\n\t\t</mcms-form-field>\n\n\t\t@if (entitySheetService && !isDisabled()) {\n\t\t\t<div class=\"flex gap-2 mt-1.5\">\n\t\t\t\t<button\n\t\t\t\t\ttype=\"button\"\n\t\t\t\t\tclass=\"inline-flex items-center gap-1 text-xs text-muted-foreground hover:text-foreground transition-colors\"\n\t\t\t\t\t(click)=\"onCreateRelated()\"\n\t\t\t\t\t[attr.aria-label]=\"'Create new ' + relatedLabel()\"\n\t\t\t\t>\n\t\t\t\t\t<ng-icon name=\"heroPlus\" size=\"14\" aria-hidden=\"true\" />\n\t\t\t\t\tNew\n\t\t\t\t</button>\n\t\t\t\t@if (hasSelection()) {\n\t\t\t\t\t<button\n\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\tclass=\"inline-flex items-center gap-1 text-xs text-muted-foreground hover:text-foreground transition-colors\"\n\t\t\t\t\t\t(click)=\"onViewRelated()\"\n\t\t\t\t\t\t[attr.aria-label]=\"'View ' + relatedLabel()\"\n\t\t\t\t\t>\n\t\t\t\t\t\t<ng-icon name=\"heroEye\" size=\"14\" aria-hidden=\"true\" />\n\t\t\t\t\t\tView\n\t\t\t\t\t</button>\n\t\t\t\t}\n\t\t\t</div>\n\t\t}\n\t`,\n})\nexport class RelationshipFieldRenderer {\n\tprivate readonly http = inject(HttpClient);\n\treadonly entitySheetService = inject(EntitySheetService, { optional: true });\n\n\t/** Field definition (must be a RelationshipField) */\n\treadonly field = input.required<Field>();\n\n\t/** Signal forms FieldTree node for this field */\n\treadonly formNode = input<unknown>(null);\n\n\t/** Form mode */\n\treadonly mode = input<EntityFormMode>('create');\n\n\t/** Field path */\n\treadonly path = input.required<string>();\n\n\t/** Full form model data (used for filterOptions) */\n\treadonly formModel = input<Record<string, unknown>>({});\n\n\t/** Bridge: extract FieldState from formNode */\n\tprivate readonly nodeState = computed(() => getFieldNodeState(this.formNode()));\n\n\t/** Loaded options from related collection */\n\treadonly allOptions = signal<RelationshipOption[]>([]);\n\n\t/** Loading state */\n\treadonly isLoading = signal(false);\n\n\t/** Unique field ID */\n\treadonly fieldId = computed(() => `field-${this.path().replace(/\\./g, '-')}`);\n\n\t/** Computed label */\n\treadonly label = computed(() => this.field().label || humanizeFieldName(this.field().name));\n\n\t/** Whether the field is required */\n\treadonly required = computed(() => this.field().required ?? false);\n\n\t/** Whether the field is disabled */\n\treadonly isDisabled = computed(\n\t\t() => this.mode() === 'view' || (this.field().admin?.readOnly ?? false),\n\t);\n\n\t/** Whether this is a hasMany relationship */\n\treadonly isMulti = computed((): boolean => {\n\t\tconst f = this.field();\n\t\treturn f.type === 'relationship' ? (f.hasMany ?? false) : false;\n\t});\n\n\t/** Related collection slug extracted from the lazy collection reference */\n\treadonly relatedSlug = computed((): string => {\n\t\tconst f = this.field();\n\t\tif (f.type === 'relationship') {\n\t\t\tconst config = f.collection();\n\t\t\tif (isRecord(config) && typeof config['slug'] === 'string') {\n\t\t\t\treturn config['slug'];\n\t\t\t}\n\t\t}\n\t\treturn '';\n\t});\n\n\t/** The field name used to display document titles */\n\treadonly titleField = computed((): string => {\n\t\tconst f = this.field();\n\t\tif (f.type !== 'relationship') return 'id';\n\n\t\tconst config = f.collection();\n\t\tif (!isRecord(config)) return 'id';\n\n\t\treturn getTitleField(config);\n\t});\n\n\t/** Label for the related collection (singular) */\n\treadonly relatedLabel = computed((): string => {\n\t\tconst f = this.field();\n\t\tif (f.type !== 'relationship') return 'item';\n\n\t\tconst config = f.collection();\n\t\tif (!isRecord(config)) return 'item';\n\n\t\tconst labels = config['labels'];\n\t\tif (isRecord(labels) && typeof labels['singular'] === 'string') {\n\t\t\treturn labels['singular'];\n\t\t}\n\t\tif (typeof config['slug'] === 'string') {\n\t\t\treturn config['slug'];\n\t\t}\n\t\treturn 'item';\n\t});\n\n\t/** Current value for single-select mode */\n\treadonly singleValue = computed((): string => {\n\t\tconst state = this.nodeState();\n\t\tif (!state) return '';\n\t\tconst val = state.value();\n\t\tif (typeof val === 'string') return val;\n\t\t// Handle populated objects with an id\n\t\tif (isRecord(val) && typeof val['id'] === 'string') return val['id'];\n\t\treturn '';\n\t});\n\n\t/** Current values for multi-select mode */\n\treadonly multiValues = computed((): string[] => {\n\t\tconst state = this.nodeState();\n\t\tif (!state) return [];\n\t\tconst val = state.value();\n\t\tif (!Array.isArray(val)) return [];\n\t\treturn val\n\t\t\t.map((item: unknown) => {\n\t\t\t\tif (typeof item === 'string') return item;\n\t\t\t\tif (isRecord(item) && typeof item['id'] === 'string') return item['id'];\n\t\t\t\treturn '';\n\t\t\t})\n\t\t\t.filter((v: string) => v !== '');\n\t});\n\n\t/** Selected options for multi-select display (resolved to labels) */\n\treadonly selectedOptions = computed((): RelationshipOption[] => {\n\t\tconst values = this.multiValues();\n\t\tconst options = this.allOptions();\n\t\treturn values\n\t\t\t.map((v) => options.find((opt) => opt.value === v))\n\t\t\t.filter((opt): opt is RelationshipOption => opt !== undefined);\n\t});\n\n\t/** Available options for multi-select (excludes already selected) */\n\treadonly availableOptions = computed((): RelationshipOption[] => {\n\t\tconst selected = new Set(this.multiValues());\n\t\treturn this.allOptions().filter((opt) => !selected.has(opt.value));\n\t});\n\n\t/** Validation errors shown only when field is touched */\n\treadonly touchedErrors = computed((): readonly ValidationError[] => {\n\t\tconst state = this.nodeState();\n\t\tif (!state || !state.touched()) return [];\n\t\treturn state.errors().map((e) => ({ kind: e.kind, message: e.message }));\n\t});\n\n\tconstructor() {\n\t\t// Fetch related collection docs when the slug is resolved.\n\t\t// Uses onCleanup to cancel in-flight requests if the slug changes.\n\t\teffect((onCleanup) => {\n\t\t\tconst slug = this.relatedSlug();\n\t\t\tif (slug) {\n\t\t\t\tconst sub = this.fetchOptions(slug);\n\t\t\t\tonCleanup(() => sub.unsubscribe());\n\t\t\t}\n\t\t});\n\t}\n\n\t/** Handle single-select change */\n\tonSingleSelect(event: Event): void {\n\t\tconst target = event.target;\n\t\tif (target instanceof HTMLSelectElement) {\n\t\t\tconst state = this.nodeState();\n\t\t\tif (state) {\n\t\t\t\tstate.value.set(target.value || null);\n\t\t\t}\n\t\t}\n\t}\n\n\t/** Handle multi-select add */\n\tonMultiSelect(event: Event): void {\n\t\tconst target = event.target;\n\t\tif (!(target instanceof HTMLSelectElement)) return;\n\n\t\tconst selectedValue = target.value;\n\t\tif (!selectedValue) return;\n\n\t\tconst state = this.nodeState();\n\t\tif (!state) return;\n\n\t\tconst currentValues = this.multiValues();\n\t\tif (!currentValues.includes(selectedValue)) {\n\t\t\tstate.value.set([...currentValues, selectedValue]);\n\t\t}\n\t\t// Reset dropdown to placeholder\n\t\ttarget.value = '';\n\t}\n\n\t/** Remove a value from multi-select */\n\tremoveSelection(valueToRemove: string): void {\n\t\tconst state = this.nodeState();\n\t\tif (!state) return;\n\t\tconst currentValues = this.multiValues();\n\t\tstate.value.set(currentValues.filter((v) => v !== valueToRemove));\n\t}\n\n\t/** Whether there is a current selection (for showing \"View\" button) */\n\treadonly hasSelection = computed(\n\t\t(): boolean => !!this.singleValue() || this.multiValues().length > 0,\n\t);\n\n\t/**\n\t * Handle blur from select elements.\n\t */\n\tonBlur(): void {\n\t\tconst state = this.nodeState();\n\t\tif (state) state.markAsTouched();\n\t}\n\n\t/** Open the entity sheet to create a new related entity */\n\tonCreateRelated(): void {\n\t\tconst slug = this.relatedSlug();\n\t\tif (!slug || !this.entitySheetService) return;\n\n\t\tthis.entitySheetService.openCreate(slug).subscribe((result) => {\n\t\t\tif (result.action === 'created' && result.entity) {\n\t\t\t\tconst state = this.nodeState();\n\t\t\t\tif (!state) return;\n\n\t\t\t\tconst entityId = String(result.entity.id);\n\t\t\t\tif (this.isMulti()) {\n\t\t\t\t\tconst current = this.multiValues();\n\t\t\t\t\tstate.value.set([...current, entityId]);\n\t\t\t\t} else {\n\t\t\t\t\tstate.value.set(entityId);\n\t\t\t\t}\n\n\t\t\t\t// Refresh options to include the newly created entity\n\t\t\t\tthis.fetchOptions(slug);\n\t\t\t}\n\t\t});\n\t}\n\n\t/** Open the entity sheet to view the selected related entity */\n\tonViewRelated(): void {\n\t\tconst slug = this.relatedSlug();\n\t\tif (!slug || !this.entitySheetService) return;\n\n\t\tconst id = this.isMulti() ? this.multiValues()[0] : this.singleValue();\n\t\tif (!id) return;\n\n\t\tthis.entitySheetService.openView(slug, id).subscribe((result) => {\n\t\t\tif (result.action === 'deleted') {\n\t\t\t\tconst state = this.nodeState();\n\t\t\t\tif (!state) return;\n\n\t\t\t\t// Clear the deleted entity from the selection\n\t\t\t\tif (this.isMulti()) {\n\t\t\t\t\tconst current = this.multiValues();\n\t\t\t\t\tstate.value.set(current.filter((v) => v !== id));\n\t\t\t\t} else {\n\t\t\t\t\tstate.value.set(null);\n\t\t\t\t}\n\n\t\t\t\t// Refresh options to remove the deleted entity\n\t\t\t\tthis.fetchOptions(slug);\n\t\t\t}\n\t\t});\n\t}\n\n\t/** Fetch options from the related collection API, returns subscription for cleanup */\n\tfetchOptions(slug: string): Subscription {\n\t\tthis.isLoading.set(true);\n\t\tconst titleField = this.titleField();\n\n\t\t// Build query params, including filterOptions if defined\n\t\tconst params: Record<string, string> = { limit: '100' };\n\t\tconst f = this.field();\n\t\tif (f.type === 'relationship' && f.filterOptions) {\n\t\t\tconst whereClause = f.filterOptions({ data: this.formModel() });\n\t\t\tfor (const [key, val] of Object.entries(whereClause)) {\n\t\t\t\tif (val !== undefined && val !== null) {\n\t\t\t\t\tparams[`where[${key}]`] = String(val);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn this.http\n\t\t\t.get<{ docs?: Array<Record<string, unknown>> }>(`/api/${slug}`, {\n\t\t\t\tparams,\n\t\t\t})\n\t\t\t.subscribe({\n\t\t\t\tnext: (response) => {\n\t\t\t\t\tconst docs = response.docs ?? [];\n\t\t\t\t\tconst options: RelationshipOption[] = docs.map((doc) => {\n\t\t\t\t\t\tconst id = typeof doc['id'] === 'string' ? doc['id'] : String(doc['id'] ?? '');\n\t\t\t\t\t\tconst titleValue = doc[titleField];\n\t\t\t\t\t\tconst label = titleField !== 'id' && typeof titleValue === 'string' ? titleValue : id;\n\t\t\t\t\t\treturn { value: id, label };\n\t\t\t\t\t});\n\t\t\t\t\tthis.allOptions.set(options);\n\t\t\t\t\tthis.isLoading.set(false);\n\t\t\t\t},\n\t\t\t\terror: (err: unknown) => {\n\t\t\t\t\tconsole.error(`Failed to load relationship options for \"${slug}\":`, err);\n\t\t\t\t\tthis.allOptions.set([]);\n\t\t\t\t\tthis.isLoading.set(false);\n\t\t\t\t},\n\t\t\t});\n\t}\n}\n"],"names":[],"mappings":"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"}
|