@momentumcms/admin 0.1.8 → 0.1.10

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 (24) hide show
  1. package/fesm2022/momentumcms-admin-array-field.component-BZva87Sh.mjs +316 -0
  2. package/fesm2022/momentumcms-admin-array-field.component-BZva87Sh.mjs.map +1 -0
  3. package/fesm2022/momentumcms-admin-blocks-field.component-CIxpyKAV.mjs +362 -0
  4. package/fesm2022/momentumcms-admin-blocks-field.component-CIxpyKAV.mjs.map +1 -0
  5. package/fesm2022/momentumcms-admin-collapsible-field.component-BpmaUKom.mjs +126 -0
  6. package/fesm2022/momentumcms-admin-collapsible-field.component-BpmaUKom.mjs.map +1 -0
  7. package/fesm2022/{momentumcms-admin-global-edit.page-DFDV-uh3.mjs → momentumcms-admin-global-edit.page-976NlNjw.mjs} +2 -2
  8. package/fesm2022/{momentumcms-admin-global-edit.page-DFDV-uh3.mjs.map → momentumcms-admin-global-edit.page-976NlNjw.mjs.map} +1 -1
  9. package/fesm2022/momentumcms-admin-group-field.component-Bgy_tQOG.mjs +184 -0
  10. package/fesm2022/momentumcms-admin-group-field.component-Bgy_tQOG.mjs.map +1 -0
  11. package/fesm2022/{momentumcms-admin-momentumcms-admin-z82jXEsP.mjs → momentumcms-admin-momentumcms-admin-TvEIOeYg.mjs} +9680 -12243
  12. package/fesm2022/momentumcms-admin-momentumcms-admin-TvEIOeYg.mjs.map +1 -0
  13. package/fesm2022/momentumcms-admin-relationship-field.component-s46Lu33u.mjs +473 -0
  14. package/fesm2022/momentumcms-admin-relationship-field.component-s46Lu33u.mjs.map +1 -0
  15. package/fesm2022/momentumcms-admin-rich-text-field.component-Djv7tDS2.mjs +813 -0
  16. package/fesm2022/momentumcms-admin-rich-text-field.component-Djv7tDS2.mjs.map +1 -0
  17. package/fesm2022/momentumcms-admin-row-field.component-Dc5vqRQ8.mjs +98 -0
  18. package/fesm2022/momentumcms-admin-row-field.component-Dc5vqRQ8.mjs.map +1 -0
  19. package/fesm2022/momentumcms-admin-tabs-field.component-CdZoCrvw.mjs +127 -0
  20. package/fesm2022/momentumcms-admin-tabs-field.component-CdZoCrvw.mjs.map +1 -0
  21. package/fesm2022/momentumcms-admin.mjs +1 -1
  22. package/package.json +1 -1
  23. package/types/momentumcms-admin.d.ts +79 -13
  24. package/fesm2022/momentumcms-admin-momentumcms-admin-z82jXEsP.mjs.map +0 -1
@@ -0,0 +1,316 @@
1
+ import * as i0 from '@angular/core';
2
+ import { input, computed, ChangeDetectionStrategy, Component } from '@angular/core';
3
+ import { moveItemInArray, CdkDropList, CdkDrag, CdkDragHandle } from '@angular/cdk/drag-drop';
4
+ import { NgIcon, provideIcons } from '@ng-icons/core';
5
+ import { heroBars2, heroTrash, heroPlus } from '@ng-icons/heroicons/outline';
6
+ import { Card, CardHeader, CardTitle, CardContent, CardFooter, Button } from '@momentumcms/ui';
7
+ import { humanizeFieldName } from '@momentumcms/core';
8
+ import { a as getFieldNodeState, i as isRecord, b as getSubNode, c as getFieldDefaultValue, F as FieldRenderer } from './momentumcms-admin-momentumcms-admin-TvEIOeYg.mjs';
9
+
10
+ /**
11
+ * Array field renderer.
12
+ *
13
+ * Renders an array of rows, each row displayed as a card with sub-fields.
14
+ * Supports add/remove rows and drag-drop reordering via CDK DragDrop.
15
+ * Respects minRows/maxRows constraints.
16
+ *
17
+ * Data container pattern: passes row sub-field FieldTree nodes via
18
+ * getSubNode(getSubNode(formNode, rowIndex), subFieldName).
19
+ * Array mutations use nodeState.value.set(newArray).
20
+ */
21
+ class ArrayFieldRenderer {
22
+ /** Field definition (must be an ArrayField) */
23
+ field = input.required(...(ngDevMode ? [{ debugName: "field" }] : []));
24
+ /** Signal forms FieldTree node for this array */
25
+ formNode = input(null, ...(ngDevMode ? [{ debugName: "formNode" }] : []));
26
+ /** Root signal forms FieldTree (for layout fields that look up child nodes) */
27
+ formTree = input(null, ...(ngDevMode ? [{ debugName: "formTree" }] : []));
28
+ /** Form model data (for condition evaluation and relationship filterOptions) */
29
+ formModel = input({}, ...(ngDevMode ? [{ debugName: "formModel" }] : []));
30
+ /** Form mode */
31
+ mode = input('create', ...(ngDevMode ? [{ debugName: "mode" }] : []));
32
+ /** Field path (e.g., "features") */
33
+ path = input.required(...(ngDevMode ? [{ debugName: "path" }] : []));
34
+ /** Bridge: extract FieldState from formNode */
35
+ nodeState = computed(() => getFieldNodeState(this.formNode()), ...(ngDevMode ? [{ debugName: "nodeState" }] : []));
36
+ /** Computed label */
37
+ label = computed(() => this.field().label || humanizeFieldName(this.field().name), ...(ngDevMode ? [{ debugName: "label" }] : []));
38
+ /** Computed description */
39
+ description = computed(() => this.field().description || '', ...(ngDevMode ? [{ debugName: "description" }] : []));
40
+ /** Sub-fields from the array definition */
41
+ subFields = computed(() => {
42
+ const f = this.field();
43
+ if (f.type === 'array') {
44
+ return f.fields.filter((sf) => !sf.admin?.hidden);
45
+ }
46
+ return [];
47
+ }, ...(ngDevMode ? [{ debugName: "subFields" }] : []));
48
+ /** Min rows constraint */
49
+ minRows = computed(() => {
50
+ const f = this.field();
51
+ return f.type === 'array' ? (f.minRows ?? 0) : 0;
52
+ }, ...(ngDevMode ? [{ debugName: "minRows" }] : []));
53
+ /** Max rows constraint */
54
+ maxRows = computed(() => {
55
+ const f = this.field();
56
+ return f.type === 'array' ? f.maxRows : undefined;
57
+ }, ...(ngDevMode ? [{ debugName: "maxRows" }] : []));
58
+ /** Current rows as array of objects (read from FieldState) */
59
+ rows = computed(() => {
60
+ const state = this.nodeState();
61
+ if (!state)
62
+ return [];
63
+ const val = state.value();
64
+ if (Array.isArray(val)) {
65
+ return val.map((item) => (isRecord(item) ? item : {}));
66
+ }
67
+ return [];
68
+ }, ...(ngDevMode ? [{ debugName: "rows" }] : []));
69
+ /** Whether the field is disabled (view mode) */
70
+ isDisabled = computed(() => this.mode() === 'view', ...(ngDevMode ? [{ debugName: "isDisabled" }] : []));
71
+ /** Whether a new row can be added */
72
+ canAddRow = computed(() => {
73
+ if (this.isDisabled())
74
+ return false;
75
+ const max = this.maxRows();
76
+ return max === undefined || this.rows().length < max;
77
+ }, ...(ngDevMode ? [{ debugName: "canAddRow" }] : []));
78
+ /** Whether rows can be removed */
79
+ canRemoveRow = computed(() => {
80
+ if (this.isDisabled())
81
+ return false;
82
+ return this.rows().length > this.minRows();
83
+ }, ...(ngDevMode ? [{ debugName: "canRemoveRow" }] : []));
84
+ /** Get a FieldTree sub-node for a row's sub-field */
85
+ getRowSubNode(rowIndex, subFieldName) {
86
+ const rowNode = getSubNode(this.formNode(), rowIndex);
87
+ return getSubNode(rowNode, subFieldName);
88
+ }
89
+ /** Get the full path for a row's sub-field */
90
+ getRowSubFieldPath(rowIndex, subFieldName) {
91
+ return `${this.path()}.${rowIndex}.${subFieldName}`;
92
+ }
93
+ /** Handle drag-drop reorder */
94
+ onDrop(event) {
95
+ const state = this.nodeState();
96
+ if (!state)
97
+ return;
98
+ const rows = [...this.rows()];
99
+ moveItemInArray(rows, event.previousIndex, event.currentIndex);
100
+ state.value.set(rows);
101
+ }
102
+ /** Add a new empty row */
103
+ addRow() {
104
+ const state = this.nodeState();
105
+ if (!state)
106
+ return;
107
+ const rows = [...this.rows()];
108
+ const newRow = {};
109
+ for (const field of this.subFields()) {
110
+ newRow[field.name] = getFieldDefaultValue(field);
111
+ }
112
+ rows.push(newRow);
113
+ state.value.set(rows);
114
+ }
115
+ /** Remove a row at the given index */
116
+ removeRow(index) {
117
+ const state = this.nodeState();
118
+ if (!state)
119
+ return;
120
+ const rows = this.rows().filter((_, i) => i !== index);
121
+ state.value.set(rows);
122
+ }
123
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.2", ngImport: i0, type: ArrayFieldRenderer, deps: [], target: i0.ɵɵFactoryTarget.Component });
124
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.2", type: ArrayFieldRenderer, isStandalone: true, selector: "mcms-array-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 }, formTree: { classPropertyName: "formTree", publicName: "formTree", isSignal: true, isRequired: false, transformFunction: null }, formModel: { classPropertyName: "formModel", publicName: "formModel", 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 } }, providers: [provideIcons({ heroPlus, heroTrash, heroBars2 })], ngImport: i0, template: `
125
+ <mcms-card>
126
+ <mcms-card-header>
127
+ <div class="flex items-center justify-between">
128
+ <div>
129
+ <mcms-card-title>{{ label() }}</mcms-card-title>
130
+ @if (description()) {
131
+ <p class="text-sm text-muted-foreground mt-1">{{ description() }}</p>
132
+ }
133
+ </div>
134
+ <span class="text-sm text-muted-foreground">
135
+ {{ rows().length }}{{ maxRows() ? ' / ' + maxRows() : '' }} rows
136
+ </span>
137
+ </div>
138
+ </mcms-card-header>
139
+ <mcms-card-content>
140
+ @if (rows().length === 0) {
141
+ <p class="text-sm text-muted-foreground py-4 text-center">
142
+ No items yet. Click "Add Row" to get started.
143
+ </p>
144
+ } @else {
145
+ <div
146
+ cdkDropList
147
+ (cdkDropListDropped)="onDrop($event)"
148
+ class="space-y-3"
149
+ role="list"
150
+ aria-label="Array rows"
151
+ >
152
+ @for (row of rows(); track $index; let i = $index) {
153
+ <div
154
+ cdkDrag
155
+ class="border rounded-lg p-4 bg-card flex gap-3 items-start"
156
+ [cdkDragDisabled]="isDisabled()"
157
+ >
158
+ <div
159
+ cdkDragHandle
160
+ class="cursor-grab pt-1 text-muted-foreground hover:text-foreground"
161
+ [class.hidden]="isDisabled()"
162
+ role="button"
163
+ tabindex="0"
164
+ [attr.aria-label]="'Reorder row ' + (i + 1)"
165
+ aria-roledescription="sortable"
166
+ >
167
+ <ng-icon name="heroBars2" size="16" aria-hidden="true" />
168
+ </div>
169
+ <div class="flex-1 space-y-3">
170
+ @for (subField of subFields(); track subField.name) {
171
+ <mcms-field-renderer
172
+ [field]="subField"
173
+ [formNode]="getRowSubNode(i, subField.name)"
174
+ [formTree]="formTree()"
175
+ [formModel]="formModel()"
176
+ [mode]="mode()"
177
+ [path]="getRowSubFieldPath(i, subField.name)"
178
+ />
179
+ }
180
+ </div>
181
+ @if (canRemoveRow()) {
182
+ <button
183
+ mcms-button
184
+ variant="ghost"
185
+ size="icon"
186
+ class="shrink-0 text-destructive hover:text-destructive"
187
+ (click)="removeRow(i)"
188
+ [attr.aria-label]="'Remove row ' + (i + 1)"
189
+ >
190
+ <ng-icon name="heroTrash" size="16" aria-hidden="true" />
191
+ </button>
192
+ }
193
+ </div>
194
+ }
195
+ </div>
196
+ }
197
+ </mcms-card-content>
198
+ @if (canAddRow()) {
199
+ <mcms-card-footer>
200
+ <button mcms-button variant="outline" (click)="addRow()">
201
+ <ng-icon name="heroPlus" size="16" aria-hidden="true" />
202
+ Add Row
203
+ </button>
204
+ </mcms-card-footer>
205
+ }
206
+ </mcms-card>
207
+ `, isInline: true, dependencies: [{ kind: "component", type: Card, selector: "mcms-card" }, { kind: "component", type: CardHeader, selector: "mcms-card-header" }, { kind: "component", type: CardTitle, selector: "mcms-card-title" }, { kind: "component", type: CardContent, selector: "mcms-card-content" }, { kind: "component", type: CardFooter, selector: "mcms-card-footer" }, { kind: "component", type: Button, selector: "button[mcms-button], a[mcms-button]", inputs: ["variant", "size", "disabled", "loading", "ariaLabel", "class"] }, { kind: "component", type: NgIcon, selector: "ng-icon", inputs: ["name", "svg", "size", "strokeWidth", "color"] }, { kind: "directive", type: CdkDropList, selector: "[cdkDropList], cdk-drop-list", inputs: ["cdkDropListConnectedTo", "cdkDropListData", "cdkDropListOrientation", "id", "cdkDropListLockAxis", "cdkDropListDisabled", "cdkDropListSortingDisabled", "cdkDropListEnterPredicate", "cdkDropListSortPredicate", "cdkDropListAutoScrollDisabled", "cdkDropListAutoScrollStep", "cdkDropListElementContainer", "cdkDropListHasAnchor"], outputs: ["cdkDropListDropped", "cdkDropListEntered", "cdkDropListExited", "cdkDropListSorted"], exportAs: ["cdkDropList"] }, { kind: "directive", type: CdkDrag, selector: "[cdkDrag]", inputs: ["cdkDragData", "cdkDragLockAxis", "cdkDragRootElement", "cdkDragBoundary", "cdkDragStartDelay", "cdkDragFreeDragPosition", "cdkDragDisabled", "cdkDragConstrainPosition", "cdkDragPreviewClass", "cdkDragPreviewContainer", "cdkDragScale"], outputs: ["cdkDragStarted", "cdkDragReleased", "cdkDragEnded", "cdkDragEntered", "cdkDragExited", "cdkDragDropped", "cdkDragMoved"], exportAs: ["cdkDrag"] }, { kind: "directive", type: CdkDragHandle, selector: "[cdkDragHandle]", inputs: ["cdkDragHandleDisabled"] }, { kind: "component", type: FieldRenderer, selector: "mcms-field-renderer", inputs: ["field", "formNode", "formTree", "formModel", "mode", "path"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
208
+ }
209
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.2", ngImport: i0, type: ArrayFieldRenderer, decorators: [{
210
+ type: Component,
211
+ args: [{
212
+ selector: 'mcms-array-field-renderer',
213
+ imports: [
214
+ Card,
215
+ CardHeader,
216
+ CardTitle,
217
+ CardContent,
218
+ CardFooter,
219
+ Button,
220
+ NgIcon,
221
+ CdkDropList,
222
+ CdkDrag,
223
+ CdkDragHandle,
224
+ FieldRenderer,
225
+ ],
226
+ providers: [provideIcons({ heroPlus, heroTrash, heroBars2 })],
227
+ changeDetection: ChangeDetectionStrategy.OnPush,
228
+ template: `
229
+ <mcms-card>
230
+ <mcms-card-header>
231
+ <div class="flex items-center justify-between">
232
+ <div>
233
+ <mcms-card-title>{{ label() }}</mcms-card-title>
234
+ @if (description()) {
235
+ <p class="text-sm text-muted-foreground mt-1">{{ description() }}</p>
236
+ }
237
+ </div>
238
+ <span class="text-sm text-muted-foreground">
239
+ {{ rows().length }}{{ maxRows() ? ' / ' + maxRows() : '' }} rows
240
+ </span>
241
+ </div>
242
+ </mcms-card-header>
243
+ <mcms-card-content>
244
+ @if (rows().length === 0) {
245
+ <p class="text-sm text-muted-foreground py-4 text-center">
246
+ No items yet. Click "Add Row" to get started.
247
+ </p>
248
+ } @else {
249
+ <div
250
+ cdkDropList
251
+ (cdkDropListDropped)="onDrop($event)"
252
+ class="space-y-3"
253
+ role="list"
254
+ aria-label="Array rows"
255
+ >
256
+ @for (row of rows(); track $index; let i = $index) {
257
+ <div
258
+ cdkDrag
259
+ class="border rounded-lg p-4 bg-card flex gap-3 items-start"
260
+ [cdkDragDisabled]="isDisabled()"
261
+ >
262
+ <div
263
+ cdkDragHandle
264
+ class="cursor-grab pt-1 text-muted-foreground hover:text-foreground"
265
+ [class.hidden]="isDisabled()"
266
+ role="button"
267
+ tabindex="0"
268
+ [attr.aria-label]="'Reorder row ' + (i + 1)"
269
+ aria-roledescription="sortable"
270
+ >
271
+ <ng-icon name="heroBars2" size="16" aria-hidden="true" />
272
+ </div>
273
+ <div class="flex-1 space-y-3">
274
+ @for (subField of subFields(); track subField.name) {
275
+ <mcms-field-renderer
276
+ [field]="subField"
277
+ [formNode]="getRowSubNode(i, subField.name)"
278
+ [formTree]="formTree()"
279
+ [formModel]="formModel()"
280
+ [mode]="mode()"
281
+ [path]="getRowSubFieldPath(i, subField.name)"
282
+ />
283
+ }
284
+ </div>
285
+ @if (canRemoveRow()) {
286
+ <button
287
+ mcms-button
288
+ variant="ghost"
289
+ size="icon"
290
+ class="shrink-0 text-destructive hover:text-destructive"
291
+ (click)="removeRow(i)"
292
+ [attr.aria-label]="'Remove row ' + (i + 1)"
293
+ >
294
+ <ng-icon name="heroTrash" size="16" aria-hidden="true" />
295
+ </button>
296
+ }
297
+ </div>
298
+ }
299
+ </div>
300
+ }
301
+ </mcms-card-content>
302
+ @if (canAddRow()) {
303
+ <mcms-card-footer>
304
+ <button mcms-button variant="outline" (click)="addRow()">
305
+ <ng-icon name="heroPlus" size="16" aria-hidden="true" />
306
+ Add Row
307
+ </button>
308
+ </mcms-card-footer>
309
+ }
310
+ </mcms-card>
311
+ `,
312
+ }]
313
+ }], propDecorators: { field: [{ type: i0.Input, args: [{ isSignal: true, alias: "field", required: true }] }], formNode: [{ type: i0.Input, args: [{ isSignal: true, alias: "formNode", required: false }] }], formTree: [{ type: i0.Input, args: [{ isSignal: true, alias: "formTree", required: false }] }], formModel: [{ type: i0.Input, args: [{ isSignal: true, alias: "formModel", required: false }] }], mode: [{ type: i0.Input, args: [{ isSignal: true, alias: "mode", required: false }] }], path: [{ type: i0.Input, args: [{ isSignal: true, alias: "path", required: true }] }] } });
314
+
315
+ export { ArrayFieldRenderer };
316
+ //# sourceMappingURL=momentumcms-admin-array-field.component-BZva87Sh.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"momentumcms-admin-array-field.component-BZva87Sh.mjs","sources":["../../../../libs/admin/src/lib/widgets/entity-form/field-renderers/array-field.component.ts"],"sourcesContent":["import { ChangeDetectionStrategy, Component, computed, input } from '@angular/core';\nimport {\n\tCdkDropList,\n\tCdkDrag,\n\tCdkDragHandle,\n\ttype CdkDragDrop,\n\tmoveItemInArray,\n} from '@angular/cdk/drag-drop';\nimport { NgIcon, provideIcons } from '@ng-icons/core';\nimport { heroPlus, heroTrash, heroBars2 } from '@ng-icons/heroicons/outline';\nimport { Card, CardHeader, CardTitle, CardContent, CardFooter, Button } from '@momentumcms/ui';\nimport { humanizeFieldName } from '@momentumcms/core';\nimport type { Field } from '@momentumcms/core';\nimport type { EntityFormMode } from '../entity-form.types';\nimport {\n\tgetFieldNodeState,\n\tgetSubNode,\n\tisRecord,\n\tgetFieldDefaultValue,\n} from '../entity-form.types';\nimport { FieldRenderer } from './field-renderer.component';\n\n/**\n * Array field renderer.\n *\n * Renders an array of rows, each row displayed as a card with sub-fields.\n * Supports add/remove rows and drag-drop reordering via CDK DragDrop.\n * Respects minRows/maxRows constraints.\n *\n * Data container pattern: passes row sub-field FieldTree nodes via\n * getSubNode(getSubNode(formNode, rowIndex), subFieldName).\n * Array mutations use nodeState.value.set(newArray).\n */\n@Component({\n\tselector: 'mcms-array-field-renderer',\n\timports: [\n\t\tCard,\n\t\tCardHeader,\n\t\tCardTitle,\n\t\tCardContent,\n\t\tCardFooter,\n\t\tButton,\n\t\tNgIcon,\n\t\tCdkDropList,\n\t\tCdkDrag,\n\t\tCdkDragHandle,\n\t\tFieldRenderer,\n\t],\n\tproviders: [provideIcons({ heroPlus, heroTrash, heroBars2 })],\n\tchangeDetection: ChangeDetectionStrategy.OnPush,\n\ttemplate: `\n\t\t<mcms-card>\n\t\t\t<mcms-card-header>\n\t\t\t\t<div class=\"flex items-center justify-between\">\n\t\t\t\t\t<div>\n\t\t\t\t\t\t<mcms-card-title>{{ label() }}</mcms-card-title>\n\t\t\t\t\t\t@if (description()) {\n\t\t\t\t\t\t\t<p class=\"text-sm text-muted-foreground mt-1\">{{ description() }}</p>\n\t\t\t\t\t\t}\n\t\t\t\t\t</div>\n\t\t\t\t\t<span class=\"text-sm text-muted-foreground\">\n\t\t\t\t\t\t{{ rows().length }}{{ maxRows() ? ' / ' + maxRows() : '' }} rows\n\t\t\t\t\t</span>\n\t\t\t\t</div>\n\t\t\t</mcms-card-header>\n\t\t\t<mcms-card-content>\n\t\t\t\t@if (rows().length === 0) {\n\t\t\t\t\t<p class=\"text-sm text-muted-foreground py-4 text-center\">\n\t\t\t\t\t\tNo items yet. Click \"Add Row\" to get started.\n\t\t\t\t\t</p>\n\t\t\t\t} @else {\n\t\t\t\t\t<div\n\t\t\t\t\t\tcdkDropList\n\t\t\t\t\t\t(cdkDropListDropped)=\"onDrop($event)\"\n\t\t\t\t\t\tclass=\"space-y-3\"\n\t\t\t\t\t\trole=\"list\"\n\t\t\t\t\t\taria-label=\"Array rows\"\n\t\t\t\t\t>\n\t\t\t\t\t\t@for (row of rows(); track $index; let i = $index) {\n\t\t\t\t\t\t\t<div\n\t\t\t\t\t\t\t\tcdkDrag\n\t\t\t\t\t\t\t\tclass=\"border rounded-lg p-4 bg-card flex gap-3 items-start\"\n\t\t\t\t\t\t\t\t[cdkDragDisabled]=\"isDisabled()\"\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t<div\n\t\t\t\t\t\t\t\t\tcdkDragHandle\n\t\t\t\t\t\t\t\t\tclass=\"cursor-grab pt-1 text-muted-foreground hover:text-foreground\"\n\t\t\t\t\t\t\t\t\t[class.hidden]=\"isDisabled()\"\n\t\t\t\t\t\t\t\t\trole=\"button\"\n\t\t\t\t\t\t\t\t\ttabindex=\"0\"\n\t\t\t\t\t\t\t\t\t[attr.aria-label]=\"'Reorder row ' + (i + 1)\"\n\t\t\t\t\t\t\t\t\taria-roledescription=\"sortable\"\n\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t<ng-icon name=\"heroBars2\" size=\"16\" aria-hidden=\"true\" />\n\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t<div class=\"flex-1 space-y-3\">\n\t\t\t\t\t\t\t\t\t@for (subField of subFields(); track subField.name) {\n\t\t\t\t\t\t\t\t\t\t<mcms-field-renderer\n\t\t\t\t\t\t\t\t\t\t\t[field]=\"subField\"\n\t\t\t\t\t\t\t\t\t\t\t[formNode]=\"getRowSubNode(i, subField.name)\"\n\t\t\t\t\t\t\t\t\t\t\t[formTree]=\"formTree()\"\n\t\t\t\t\t\t\t\t\t\t\t[formModel]=\"formModel()\"\n\t\t\t\t\t\t\t\t\t\t\t[mode]=\"mode()\"\n\t\t\t\t\t\t\t\t\t\t\t[path]=\"getRowSubFieldPath(i, subField.name)\"\n\t\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t@if (canRemoveRow()) {\n\t\t\t\t\t\t\t\t\t<button\n\t\t\t\t\t\t\t\t\t\tmcms-button\n\t\t\t\t\t\t\t\t\t\tvariant=\"ghost\"\n\t\t\t\t\t\t\t\t\t\tsize=\"icon\"\n\t\t\t\t\t\t\t\t\t\tclass=\"shrink-0 text-destructive hover:text-destructive\"\n\t\t\t\t\t\t\t\t\t\t(click)=\"removeRow(i)\"\n\t\t\t\t\t\t\t\t\t\t[attr.aria-label]=\"'Remove row ' + (i + 1)\"\n\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t<ng-icon name=\"heroTrash\" size=\"16\" 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</div>\n\t\t\t\t\t\t}\n\t\t\t\t\t</div>\n\t\t\t\t}\n\t\t\t</mcms-card-content>\n\t\t\t@if (canAddRow()) {\n\t\t\t\t<mcms-card-footer>\n\t\t\t\t\t<button mcms-button variant=\"outline\" (click)=\"addRow()\">\n\t\t\t\t\t\t<ng-icon name=\"heroPlus\" size=\"16\" aria-hidden=\"true\" />\n\t\t\t\t\t\tAdd Row\n\t\t\t\t\t</button>\n\t\t\t\t</mcms-card-footer>\n\t\t\t}\n\t\t</mcms-card>\n\t`,\n})\nexport class ArrayFieldRenderer {\n\t/** Field definition (must be an ArrayField) */\n\treadonly field = input.required<Field>();\n\n\t/** Signal forms FieldTree node for this array */\n\treadonly formNode = input<unknown>(null);\n\n\t/** Root signal forms FieldTree (for layout fields that look up child nodes) */\n\treadonly formTree = input<unknown>(null);\n\n\t/** Form model data (for condition evaluation and relationship filterOptions) */\n\treadonly formModel = input<Record<string, unknown>>({});\n\n\t/** Form mode */\n\treadonly mode = input<EntityFormMode>('create');\n\n\t/** Field path (e.g., \"features\") */\n\treadonly path = input.required<string>();\n\n\t/** Bridge: extract FieldState from formNode */\n\tprivate readonly nodeState = computed(() => getFieldNodeState(this.formNode()));\n\n\t/** Computed label */\n\treadonly label = computed(() => this.field().label || humanizeFieldName(this.field().name));\n\n\t/** Computed description */\n\treadonly description = computed(() => this.field().description || '');\n\n\t/** Sub-fields from the array definition */\n\treadonly subFields = computed((): Field[] => {\n\t\tconst f = this.field();\n\t\tif (f.type === 'array') {\n\t\t\treturn f.fields.filter((sf) => !sf.admin?.hidden);\n\t\t}\n\t\treturn [];\n\t});\n\n\t/** Min rows constraint */\n\treadonly minRows = computed((): number => {\n\t\tconst f = this.field();\n\t\treturn f.type === 'array' ? (f.minRows ?? 0) : 0;\n\t});\n\n\t/** Max rows constraint */\n\treadonly maxRows = computed((): number | undefined => {\n\t\tconst f = this.field();\n\t\treturn f.type === 'array' ? f.maxRows : undefined;\n\t});\n\n\t/** Current rows as array of objects (read from FieldState) */\n\treadonly rows = computed((): Record<string, unknown>[] => {\n\t\tconst state = this.nodeState();\n\t\tif (!state) return [];\n\t\tconst val = state.value();\n\t\tif (Array.isArray(val)) {\n\t\t\treturn val.map((item) => (isRecord(item) ? item : {}));\n\t\t}\n\t\treturn [];\n\t});\n\n\t/** Whether the field is disabled (view mode) */\n\treadonly isDisabled = computed(() => this.mode() === 'view');\n\n\t/** Whether a new row can be added */\n\treadonly canAddRow = computed((): boolean => {\n\t\tif (this.isDisabled()) return false;\n\t\tconst max = this.maxRows();\n\t\treturn max === undefined || this.rows().length < max;\n\t});\n\n\t/** Whether rows can be removed */\n\treadonly canRemoveRow = computed((): boolean => {\n\t\tif (this.isDisabled()) return false;\n\t\treturn this.rows().length > this.minRows();\n\t});\n\n\t/** Get a FieldTree sub-node for a row's sub-field */\n\tgetRowSubNode(rowIndex: number, subFieldName: string): unknown {\n\t\tconst rowNode = getSubNode(this.formNode(), rowIndex);\n\t\treturn getSubNode(rowNode, subFieldName);\n\t}\n\n\t/** Get the full path for a row's sub-field */\n\tgetRowSubFieldPath(rowIndex: number, subFieldName: string): string {\n\t\treturn `${this.path()}.${rowIndex}.${subFieldName}`;\n\t}\n\n\t/** Handle drag-drop reorder */\n\tonDrop(event: CdkDragDrop<unknown>): void {\n\t\tconst state = this.nodeState();\n\t\tif (!state) return;\n\t\tconst rows = [...this.rows()];\n\t\tmoveItemInArray(rows, event.previousIndex, event.currentIndex);\n\t\tstate.value.set(rows);\n\t}\n\n\t/** Add a new empty row */\n\taddRow(): void {\n\t\tconst state = this.nodeState();\n\t\tif (!state) return;\n\t\tconst rows = [...this.rows()];\n\t\tconst newRow: Record<string, unknown> = {};\n\t\tfor (const field of this.subFields()) {\n\t\t\tnewRow[field.name] = getFieldDefaultValue(field);\n\t\t}\n\t\trows.push(newRow);\n\t\tstate.value.set(rows);\n\t}\n\n\t/** Remove a row at the given index */\n\tremoveRow(index: number): void {\n\t\tconst state = this.nodeState();\n\t\tif (!state) return;\n\t\tconst rows = this.rows().filter((_, i) => i !== index);\n\t\tstate.value.set(rows);\n\t}\n}\n"],"names":[],"mappings":"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"}