@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,362 @@
|
|
|
1
|
+
import * as i0 from '@angular/core';
|
|
2
|
+
import { input, computed, effect, untracked, 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, Badge } from '@momentumcms/ui';
|
|
7
|
+
import { humanizeFieldName } from '@momentumcms/core';
|
|
8
|
+
import { a as getFieldNodeState, i as isRecord, n as normalizeBlockDefaults, b as getSubNode, c as getFieldDefaultValue, F as FieldRenderer } from './momentumcms-admin-momentumcms-admin-o0FbJXZN.mjs';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Blocks field renderer.
|
|
12
|
+
*
|
|
13
|
+
* Renders a list of typed blocks. Each block has a `blockType` discriminator
|
|
14
|
+
* and type-specific fields. Users can add, remove, and reorder blocks.
|
|
15
|
+
* A dropdown allows selecting which block type to add.
|
|
16
|
+
*
|
|
17
|
+
* Data container pattern: passes block sub-field FieldTree nodes via
|
|
18
|
+
* getSubNode(getSubNode(formNode, blockIndex), subFieldName).
|
|
19
|
+
* Block mutations use nodeState.value.set(newArray).
|
|
20
|
+
*/
|
|
21
|
+
class BlocksFieldRenderer {
|
|
22
|
+
/** Field definition (must be a BlocksField) */
|
|
23
|
+
field = input.required(...(ngDevMode ? [{ debugName: "field" }] : []));
|
|
24
|
+
/** Signal forms FieldTree node for this blocks 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., "content") */
|
|
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
|
+
/** Block type definitions from the field */
|
|
41
|
+
blockDefinitions = computed(() => {
|
|
42
|
+
const f = this.field();
|
|
43
|
+
if (f.type === 'blocks') {
|
|
44
|
+
return f.blocks;
|
|
45
|
+
}
|
|
46
|
+
return [];
|
|
47
|
+
}, ...(ngDevMode ? [{ debugName: "blockDefinitions" }] : []));
|
|
48
|
+
/** Min rows constraint */
|
|
49
|
+
minRows = computed(() => {
|
|
50
|
+
const f = this.field();
|
|
51
|
+
return f.type === 'blocks' ? (f.minRows ?? 0) : 0;
|
|
52
|
+
}, ...(ngDevMode ? [{ debugName: "minRows" }] : []));
|
|
53
|
+
/** Max rows constraint */
|
|
54
|
+
maxRows = computed(() => {
|
|
55
|
+
const f = this.field();
|
|
56
|
+
return f.type === 'blocks' ? f.maxRows : undefined;
|
|
57
|
+
}, ...(ngDevMode ? [{ debugName: "maxRows" }] : []));
|
|
58
|
+
/** Current blocks as typed items (read from FieldState) */
|
|
59
|
+
blocks = computed(() => {
|
|
60
|
+
const state = this.nodeState();
|
|
61
|
+
if (!state)
|
|
62
|
+
return [];
|
|
63
|
+
const val = state.value();
|
|
64
|
+
if (Array.isArray(val)) {
|
|
65
|
+
return val.filter((item) => isRecord(item) && typeof item['blockType'] === 'string');
|
|
66
|
+
}
|
|
67
|
+
return [];
|
|
68
|
+
}, ...(ngDevMode ? [{ debugName: "blocks" }] : []));
|
|
69
|
+
/**
|
|
70
|
+
* Normalize loaded blocks: ensure every block has defaults for all definition fields.
|
|
71
|
+
* Blocks saved before new fields were added won't have those keys, and
|
|
72
|
+
* signal-forms only creates controls for keys present in the model.
|
|
73
|
+
*/
|
|
74
|
+
_normalizeBlocks = effect(() => {
|
|
75
|
+
const state = this.nodeState();
|
|
76
|
+
if (!state)
|
|
77
|
+
return;
|
|
78
|
+
const val = state.value();
|
|
79
|
+
if (!Array.isArray(val))
|
|
80
|
+
return;
|
|
81
|
+
const { normalized, changed } = normalizeBlockDefaults(val, this.blockDefMap());
|
|
82
|
+
if (changed) {
|
|
83
|
+
untracked(() => state.value.set(normalized));
|
|
84
|
+
}
|
|
85
|
+
}, ...(ngDevMode ? [{ debugName: "_normalizeBlocks" }] : []));
|
|
86
|
+
/** Whether the field is disabled (view mode) */
|
|
87
|
+
isDisabled = computed(() => this.mode() === 'view', ...(ngDevMode ? [{ debugName: "isDisabled" }] : []));
|
|
88
|
+
/** Whether a new block can be added */
|
|
89
|
+
canAddBlock = computed(() => {
|
|
90
|
+
if (this.isDisabled())
|
|
91
|
+
return false;
|
|
92
|
+
const max = this.maxRows();
|
|
93
|
+
return max === undefined || this.blocks().length < max;
|
|
94
|
+
}, ...(ngDevMode ? [{ debugName: "canAddBlock" }] : []));
|
|
95
|
+
/** Whether blocks can be removed */
|
|
96
|
+
canRemoveBlock = computed(() => {
|
|
97
|
+
if (this.isDisabled())
|
|
98
|
+
return false;
|
|
99
|
+
return this.blocks().length > this.minRows();
|
|
100
|
+
}, ...(ngDevMode ? [{ debugName: "canRemoveBlock" }] : []));
|
|
101
|
+
/** Block definition lookup cache */
|
|
102
|
+
blockDefMap = computed(() => {
|
|
103
|
+
const map = new Map();
|
|
104
|
+
for (const def of this.blockDefinitions()) {
|
|
105
|
+
map.set(def.slug, def);
|
|
106
|
+
}
|
|
107
|
+
return map;
|
|
108
|
+
}, ...(ngDevMode ? [{ debugName: "blockDefMap" }] : []));
|
|
109
|
+
/** Get display label for a block type */
|
|
110
|
+
getBlockLabel(blockType) {
|
|
111
|
+
const def = this.blockDefMap().get(blockType);
|
|
112
|
+
return def?.labels?.singular || blockType;
|
|
113
|
+
}
|
|
114
|
+
/** Get fields for a block type */
|
|
115
|
+
getBlockFields(blockType) {
|
|
116
|
+
const def = this.blockDefMap().get(blockType);
|
|
117
|
+
return def?.fields.filter((f) => !f.admin?.hidden) ?? [];
|
|
118
|
+
}
|
|
119
|
+
/** Get a FieldTree sub-node for a block's sub-field */
|
|
120
|
+
getBlockSubNode(blockIndex, subFieldName) {
|
|
121
|
+
const blockNode = getSubNode(this.formNode(), blockIndex);
|
|
122
|
+
return getSubNode(blockNode, subFieldName);
|
|
123
|
+
}
|
|
124
|
+
/** Get the full path for a block's sub-field */
|
|
125
|
+
getBlockSubFieldPath(blockIndex, subFieldName) {
|
|
126
|
+
return `${this.path()}.${blockIndex}.${subFieldName}`;
|
|
127
|
+
}
|
|
128
|
+
/** Handle drag-drop reorder */
|
|
129
|
+
onDrop(event) {
|
|
130
|
+
const state = this.nodeState();
|
|
131
|
+
if (!state)
|
|
132
|
+
return;
|
|
133
|
+
const blocks = [...this.blocks()];
|
|
134
|
+
moveItemInArray(blocks, event.previousIndex, event.currentIndex);
|
|
135
|
+
state.value.set(blocks);
|
|
136
|
+
}
|
|
137
|
+
/** Add a new block of the given type */
|
|
138
|
+
addBlock(blockType) {
|
|
139
|
+
const def = this.blockDefMap().get(blockType);
|
|
140
|
+
if (!def)
|
|
141
|
+
return;
|
|
142
|
+
const state = this.nodeState();
|
|
143
|
+
if (!state)
|
|
144
|
+
return;
|
|
145
|
+
const newBlock = { blockType };
|
|
146
|
+
for (const field of def.fields) {
|
|
147
|
+
newBlock[field.name] = getFieldDefaultValue(field);
|
|
148
|
+
}
|
|
149
|
+
const blocks = [...this.blocks(), newBlock];
|
|
150
|
+
state.value.set(blocks);
|
|
151
|
+
}
|
|
152
|
+
/** Remove a block at the given index */
|
|
153
|
+
removeBlock(index) {
|
|
154
|
+
const state = this.nodeState();
|
|
155
|
+
if (!state)
|
|
156
|
+
return;
|
|
157
|
+
const blocks = this.blocks().filter((_, i) => i !== index);
|
|
158
|
+
state.value.set(blocks);
|
|
159
|
+
}
|
|
160
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.2", ngImport: i0, type: BlocksFieldRenderer, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
161
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.2", type: BlocksFieldRenderer, isStandalone: true, selector: "mcms-blocks-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: `
|
|
162
|
+
<mcms-card>
|
|
163
|
+
<mcms-card-header>
|
|
164
|
+
<div class="flex items-center justify-between">
|
|
165
|
+
<div>
|
|
166
|
+
<mcms-card-title>{{ label() }}</mcms-card-title>
|
|
167
|
+
@if (description()) {
|
|
168
|
+
<p class="text-sm text-muted-foreground mt-1">{{ description() }}</p>
|
|
169
|
+
}
|
|
170
|
+
</div>
|
|
171
|
+
<span class="text-sm text-muted-foreground">
|
|
172
|
+
{{ blocks().length }}{{ maxRows() ? ' / ' + maxRows() : '' }} blocks
|
|
173
|
+
</span>
|
|
174
|
+
</div>
|
|
175
|
+
</mcms-card-header>
|
|
176
|
+
<mcms-card-content>
|
|
177
|
+
@if (blocks().length === 0) {
|
|
178
|
+
<p class="text-sm text-muted-foreground py-4 text-center">
|
|
179
|
+
No blocks yet. Add a block to get started.
|
|
180
|
+
</p>
|
|
181
|
+
} @else {
|
|
182
|
+
<div
|
|
183
|
+
cdkDropList
|
|
184
|
+
(cdkDropListDropped)="onDrop($event)"
|
|
185
|
+
class="space-y-3"
|
|
186
|
+
role="list"
|
|
187
|
+
aria-label="Content blocks"
|
|
188
|
+
>
|
|
189
|
+
@for (block of blocks(); track $index; let i = $index) {
|
|
190
|
+
<div cdkDrag class="border rounded-lg bg-card" [cdkDragDisabled]="isDisabled()">
|
|
191
|
+
<div class="flex items-center gap-3 px-4 py-2 border-b bg-muted/50 rounded-t-lg">
|
|
192
|
+
<div
|
|
193
|
+
cdkDragHandle
|
|
194
|
+
class="cursor-grab text-muted-foreground hover:text-foreground"
|
|
195
|
+
[class.hidden]="isDisabled()"
|
|
196
|
+
role="button"
|
|
197
|
+
tabindex="0"
|
|
198
|
+
[attr.aria-label]="'Reorder ' + getBlockLabel(block.blockType) + ' block'"
|
|
199
|
+
aria-roledescription="sortable"
|
|
200
|
+
>
|
|
201
|
+
<ng-icon name="heroBars2" size="16" aria-hidden="true" />
|
|
202
|
+
</div>
|
|
203
|
+
<mcms-badge>{{ getBlockLabel(block.blockType) }}</mcms-badge>
|
|
204
|
+
<div class="flex-1"></div>
|
|
205
|
+
@if (canRemoveBlock()) {
|
|
206
|
+
<button
|
|
207
|
+
mcms-button
|
|
208
|
+
variant="ghost"
|
|
209
|
+
size="icon"
|
|
210
|
+
class="h-7 w-7 text-destructive hover:text-destructive"
|
|
211
|
+
(click)="removeBlock(i)"
|
|
212
|
+
[attr.aria-label]="'Remove ' + getBlockLabel(block.blockType) + ' block'"
|
|
213
|
+
>
|
|
214
|
+
<ng-icon name="heroTrash" size="14" aria-hidden="true" />
|
|
215
|
+
</button>
|
|
216
|
+
}
|
|
217
|
+
</div>
|
|
218
|
+
<div class="p-4 space-y-3">
|
|
219
|
+
@for (subField of getBlockFields(block.blockType); track subField.name) {
|
|
220
|
+
<mcms-field-renderer
|
|
221
|
+
[field]="subField"
|
|
222
|
+
[formNode]="getBlockSubNode(i, subField.name)"
|
|
223
|
+
[formTree]="formTree()"
|
|
224
|
+
[formModel]="formModel()"
|
|
225
|
+
[mode]="mode()"
|
|
226
|
+
[path]="getBlockSubFieldPath(i, subField.name)"
|
|
227
|
+
/>
|
|
228
|
+
}
|
|
229
|
+
</div>
|
|
230
|
+
</div>
|
|
231
|
+
}
|
|
232
|
+
</div>
|
|
233
|
+
}
|
|
234
|
+
</mcms-card-content>
|
|
235
|
+
@if (canAddBlock()) {
|
|
236
|
+
<mcms-card-footer>
|
|
237
|
+
<div class="flex gap-2">
|
|
238
|
+
@for (blockDef of blockDefinitions(); track blockDef.slug) {
|
|
239
|
+
<button mcms-button variant="outline" (click)="addBlock(blockDef.slug)">
|
|
240
|
+
<ng-icon name="heroPlus" size="16" aria-hidden="true" />
|
|
241
|
+
{{ blockDef.labels?.singular || blockDef.slug }}
|
|
242
|
+
</button>
|
|
243
|
+
}
|
|
244
|
+
</div>
|
|
245
|
+
</mcms-card-footer>
|
|
246
|
+
}
|
|
247
|
+
</mcms-card>
|
|
248
|
+
`, 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: Badge, selector: "mcms-badge", inputs: ["variant", "class", "ariaLabel"] }, { 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 });
|
|
249
|
+
}
|
|
250
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.2", ngImport: i0, type: BlocksFieldRenderer, decorators: [{
|
|
251
|
+
type: Component,
|
|
252
|
+
args: [{
|
|
253
|
+
selector: 'mcms-blocks-field-renderer',
|
|
254
|
+
imports: [
|
|
255
|
+
Card,
|
|
256
|
+
CardHeader,
|
|
257
|
+
CardTitle,
|
|
258
|
+
CardContent,
|
|
259
|
+
CardFooter,
|
|
260
|
+
Button,
|
|
261
|
+
Badge,
|
|
262
|
+
NgIcon,
|
|
263
|
+
CdkDropList,
|
|
264
|
+
CdkDrag,
|
|
265
|
+
CdkDragHandle,
|
|
266
|
+
FieldRenderer,
|
|
267
|
+
],
|
|
268
|
+
providers: [provideIcons({ heroPlus, heroTrash, heroBars2 })],
|
|
269
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
270
|
+
template: `
|
|
271
|
+
<mcms-card>
|
|
272
|
+
<mcms-card-header>
|
|
273
|
+
<div class="flex items-center justify-between">
|
|
274
|
+
<div>
|
|
275
|
+
<mcms-card-title>{{ label() }}</mcms-card-title>
|
|
276
|
+
@if (description()) {
|
|
277
|
+
<p class="text-sm text-muted-foreground mt-1">{{ description() }}</p>
|
|
278
|
+
}
|
|
279
|
+
</div>
|
|
280
|
+
<span class="text-sm text-muted-foreground">
|
|
281
|
+
{{ blocks().length }}{{ maxRows() ? ' / ' + maxRows() : '' }} blocks
|
|
282
|
+
</span>
|
|
283
|
+
</div>
|
|
284
|
+
</mcms-card-header>
|
|
285
|
+
<mcms-card-content>
|
|
286
|
+
@if (blocks().length === 0) {
|
|
287
|
+
<p class="text-sm text-muted-foreground py-4 text-center">
|
|
288
|
+
No blocks yet. Add a block to get started.
|
|
289
|
+
</p>
|
|
290
|
+
} @else {
|
|
291
|
+
<div
|
|
292
|
+
cdkDropList
|
|
293
|
+
(cdkDropListDropped)="onDrop($event)"
|
|
294
|
+
class="space-y-3"
|
|
295
|
+
role="list"
|
|
296
|
+
aria-label="Content blocks"
|
|
297
|
+
>
|
|
298
|
+
@for (block of blocks(); track $index; let i = $index) {
|
|
299
|
+
<div cdkDrag class="border rounded-lg bg-card" [cdkDragDisabled]="isDisabled()">
|
|
300
|
+
<div class="flex items-center gap-3 px-4 py-2 border-b bg-muted/50 rounded-t-lg">
|
|
301
|
+
<div
|
|
302
|
+
cdkDragHandle
|
|
303
|
+
class="cursor-grab text-muted-foreground hover:text-foreground"
|
|
304
|
+
[class.hidden]="isDisabled()"
|
|
305
|
+
role="button"
|
|
306
|
+
tabindex="0"
|
|
307
|
+
[attr.aria-label]="'Reorder ' + getBlockLabel(block.blockType) + ' block'"
|
|
308
|
+
aria-roledescription="sortable"
|
|
309
|
+
>
|
|
310
|
+
<ng-icon name="heroBars2" size="16" aria-hidden="true" />
|
|
311
|
+
</div>
|
|
312
|
+
<mcms-badge>{{ getBlockLabel(block.blockType) }}</mcms-badge>
|
|
313
|
+
<div class="flex-1"></div>
|
|
314
|
+
@if (canRemoveBlock()) {
|
|
315
|
+
<button
|
|
316
|
+
mcms-button
|
|
317
|
+
variant="ghost"
|
|
318
|
+
size="icon"
|
|
319
|
+
class="h-7 w-7 text-destructive hover:text-destructive"
|
|
320
|
+
(click)="removeBlock(i)"
|
|
321
|
+
[attr.aria-label]="'Remove ' + getBlockLabel(block.blockType) + ' block'"
|
|
322
|
+
>
|
|
323
|
+
<ng-icon name="heroTrash" size="14" aria-hidden="true" />
|
|
324
|
+
</button>
|
|
325
|
+
}
|
|
326
|
+
</div>
|
|
327
|
+
<div class="p-4 space-y-3">
|
|
328
|
+
@for (subField of getBlockFields(block.blockType); track subField.name) {
|
|
329
|
+
<mcms-field-renderer
|
|
330
|
+
[field]="subField"
|
|
331
|
+
[formNode]="getBlockSubNode(i, subField.name)"
|
|
332
|
+
[formTree]="formTree()"
|
|
333
|
+
[formModel]="formModel()"
|
|
334
|
+
[mode]="mode()"
|
|
335
|
+
[path]="getBlockSubFieldPath(i, subField.name)"
|
|
336
|
+
/>
|
|
337
|
+
}
|
|
338
|
+
</div>
|
|
339
|
+
</div>
|
|
340
|
+
}
|
|
341
|
+
</div>
|
|
342
|
+
}
|
|
343
|
+
</mcms-card-content>
|
|
344
|
+
@if (canAddBlock()) {
|
|
345
|
+
<mcms-card-footer>
|
|
346
|
+
<div class="flex gap-2">
|
|
347
|
+
@for (blockDef of blockDefinitions(); track blockDef.slug) {
|
|
348
|
+
<button mcms-button variant="outline" (click)="addBlock(blockDef.slug)">
|
|
349
|
+
<ng-icon name="heroPlus" size="16" aria-hidden="true" />
|
|
350
|
+
{{ blockDef.labels?.singular || blockDef.slug }}
|
|
351
|
+
</button>
|
|
352
|
+
}
|
|
353
|
+
</div>
|
|
354
|
+
</mcms-card-footer>
|
|
355
|
+
}
|
|
356
|
+
</mcms-card>
|
|
357
|
+
`,
|
|
358
|
+
}]
|
|
359
|
+
}], 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 }] }] } });
|
|
360
|
+
|
|
361
|
+
export { BlocksFieldRenderer };
|
|
362
|
+
//# sourceMappingURL=momentumcms-admin-blocks-field.component-Cz7HmuBK.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"momentumcms-admin-blocks-field.component-Cz7HmuBK.mjs","sources":["../../../../libs/admin/src/lib/widgets/entity-form/field-renderers/blocks-field.component.ts"],"sourcesContent":["import {\n\tChangeDetectionStrategy,\n\tComponent,\n\tcomputed,\n\teffect,\n\tinput,\n\tuntracked,\n} 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 {\n\tCard,\n\tCardHeader,\n\tCardTitle,\n\tCardContent,\n\tCardFooter,\n\tButton,\n\tBadge,\n} from '@momentumcms/ui';\nimport { humanizeFieldName } from '@momentumcms/core';\nimport type { Field, BlockConfig } from '@momentumcms/core';\nimport type { EntityFormMode } from '../entity-form.types';\nimport {\n\tgetFieldNodeState,\n\tgetSubNode,\n\tisRecord,\n\tgetFieldDefaultValue,\n\tnormalizeBlockDefaults,\n} from '../entity-form.types';\nimport { FieldRenderer } from './field-renderer.component';\n\n/** Shape of a block item in the stored data */\ninterface BlockItem {\n\tblockType: string;\n\t[key: string]: unknown;\n}\n\n/**\n * Blocks field renderer.\n *\n * Renders a list of typed blocks. Each block has a `blockType` discriminator\n * and type-specific fields. Users can add, remove, and reorder blocks.\n * A dropdown allows selecting which block type to add.\n *\n * Data container pattern: passes block sub-field FieldTree nodes via\n * getSubNode(getSubNode(formNode, blockIndex), subFieldName).\n * Block mutations use nodeState.value.set(newArray).\n */\n@Component({\n\tselector: 'mcms-blocks-field-renderer',\n\timports: [\n\t\tCard,\n\t\tCardHeader,\n\t\tCardTitle,\n\t\tCardContent,\n\t\tCardFooter,\n\t\tButton,\n\t\tBadge,\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{{ blocks().length }}{{ maxRows() ? ' / ' + maxRows() : '' }} blocks\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 (blocks().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 blocks yet. Add a block 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=\"Content blocks\"\n\t\t\t\t\t>\n\t\t\t\t\t\t@for (block of blocks(); track $index; let i = $index) {\n\t\t\t\t\t\t\t<div cdkDrag class=\"border rounded-lg bg-card\" [cdkDragDisabled]=\"isDisabled()\">\n\t\t\t\t\t\t\t\t<div class=\"flex items-center gap-3 px-4 py-2 border-b bg-muted/50 rounded-t-lg\">\n\t\t\t\t\t\t\t\t\t<div\n\t\t\t\t\t\t\t\t\t\tcdkDragHandle\n\t\t\t\t\t\t\t\t\t\tclass=\"cursor-grab text-muted-foreground hover:text-foreground\"\n\t\t\t\t\t\t\t\t\t\t[class.hidden]=\"isDisabled()\"\n\t\t\t\t\t\t\t\t\t\trole=\"button\"\n\t\t\t\t\t\t\t\t\t\ttabindex=\"0\"\n\t\t\t\t\t\t\t\t\t\t[attr.aria-label]=\"'Reorder ' + getBlockLabel(block.blockType) + ' block'\"\n\t\t\t\t\t\t\t\t\t\taria-roledescription=\"sortable\"\n\t\t\t\t\t\t\t\t\t>\n\t\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\t</div>\n\t\t\t\t\t\t\t\t\t<mcms-badge>{{ getBlockLabel(block.blockType) }}</mcms-badge>\n\t\t\t\t\t\t\t\t\t<div class=\"flex-1\"></div>\n\t\t\t\t\t\t\t\t\t@if (canRemoveBlock()) {\n\t\t\t\t\t\t\t\t\t\t<button\n\t\t\t\t\t\t\t\t\t\t\tmcms-button\n\t\t\t\t\t\t\t\t\t\t\tvariant=\"ghost\"\n\t\t\t\t\t\t\t\t\t\t\tsize=\"icon\"\n\t\t\t\t\t\t\t\t\t\t\tclass=\"h-7 w-7 text-destructive hover:text-destructive\"\n\t\t\t\t\t\t\t\t\t\t\t(click)=\"removeBlock(i)\"\n\t\t\t\t\t\t\t\t\t\t\t[attr.aria-label]=\"'Remove ' + getBlockLabel(block.blockType) + ' block'\"\n\t\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t\t<ng-icon name=\"heroTrash\" size=\"14\" aria-hidden=\"true\" />\n\t\t\t\t\t\t\t\t\t\t</button>\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<div class=\"p-4 space-y-3\">\n\t\t\t\t\t\t\t\t\t@for (subField of getBlockFields(block.blockType); 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]=\"getBlockSubNode(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]=\"getBlockSubFieldPath(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</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 (canAddBlock()) {\n\t\t\t\t<mcms-card-footer>\n\t\t\t\t\t<div class=\"flex gap-2\">\n\t\t\t\t\t\t@for (blockDef of blockDefinitions(); track blockDef.slug) {\n\t\t\t\t\t\t\t<button mcms-button variant=\"outline\" (click)=\"addBlock(blockDef.slug)\">\n\t\t\t\t\t\t\t\t<ng-icon name=\"heroPlus\" size=\"16\" aria-hidden=\"true\" />\n\t\t\t\t\t\t\t\t{{ blockDef.labels?.singular || blockDef.slug }}\n\t\t\t\t\t\t\t</button>\n\t\t\t\t\t\t}\n\t\t\t\t\t</div>\n\t\t\t\t</mcms-card-footer>\n\t\t\t}\n\t\t</mcms-card>\n\t`,\n})\nexport class BlocksFieldRenderer {\n\t/** Field definition (must be a BlocksField) */\n\treadonly field = input.required<Field>();\n\n\t/** Signal forms FieldTree node for this blocks 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., \"content\") */\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/** Block type definitions from the field */\n\treadonly blockDefinitions = computed((): BlockConfig[] => {\n\t\tconst f = this.field();\n\t\tif (f.type === 'blocks') {\n\t\t\treturn f.blocks;\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 === 'blocks' ? (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 === 'blocks' ? f.maxRows : undefined;\n\t});\n\n\t/** Current blocks as typed items (read from FieldState) */\n\treadonly blocks = computed((): BlockItem[] => {\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.filter(\n\t\t\t\t(item): item is BlockItem => isRecord(item) && typeof item['blockType'] === 'string',\n\t\t\t);\n\t\t}\n\t\treturn [];\n\t});\n\n\t/**\n\t * Normalize loaded blocks: ensure every block has defaults for all definition fields.\n\t * Blocks saved before new fields were added won't have those keys, and\n\t * signal-forms only creates controls for keys present in the model.\n\t */\n\tprivate readonly _normalizeBlocks = effect(() => {\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\n\t\tconst { normalized, changed } = normalizeBlockDefaults(val, this.blockDefMap());\n\t\tif (changed) {\n\t\t\tuntracked(() => state.value.set(normalized));\n\t\t}\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 block can be added */\n\treadonly canAddBlock = computed((): boolean => {\n\t\tif (this.isDisabled()) return false;\n\t\tconst max = this.maxRows();\n\t\treturn max === undefined || this.blocks().length < max;\n\t});\n\n\t/** Whether blocks can be removed */\n\treadonly canRemoveBlock = computed((): boolean => {\n\t\tif (this.isDisabled()) return false;\n\t\treturn this.blocks().length > this.minRows();\n\t});\n\n\t/** Block definition lookup cache */\n\tprivate readonly blockDefMap = computed((): Map<string, BlockConfig> => {\n\t\tconst map = new Map<string, BlockConfig>();\n\t\tfor (const def of this.blockDefinitions()) {\n\t\t\tmap.set(def.slug, def);\n\t\t}\n\t\treturn map;\n\t});\n\n\t/** Get display label for a block type */\n\tgetBlockLabel(blockType: string): string {\n\t\tconst def = this.blockDefMap().get(blockType);\n\t\treturn def?.labels?.singular || blockType;\n\t}\n\n\t/** Get fields for a block type */\n\tgetBlockFields(blockType: string): Field[] {\n\t\tconst def = this.blockDefMap().get(blockType);\n\t\treturn def?.fields.filter((f) => !f.admin?.hidden) ?? [];\n\t}\n\n\t/** Get a FieldTree sub-node for a block's sub-field */\n\tgetBlockSubNode(blockIndex: number, subFieldName: string): unknown {\n\t\tconst blockNode = getSubNode(this.formNode(), blockIndex);\n\t\treturn getSubNode(blockNode, subFieldName);\n\t}\n\n\t/** Get the full path for a block's sub-field */\n\tgetBlockSubFieldPath(blockIndex: number, subFieldName: string): string {\n\t\treturn `${this.path()}.${blockIndex}.${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 blocks = [...this.blocks()];\n\t\tmoveItemInArray(blocks, event.previousIndex, event.currentIndex);\n\t\tstate.value.set(blocks);\n\t}\n\n\t/** Add a new block of the given type */\n\taddBlock(blockType: string): void {\n\t\tconst def = this.blockDefMap().get(blockType);\n\t\tif (!def) return;\n\t\tconst state = this.nodeState();\n\t\tif (!state) return;\n\n\t\tconst newBlock: BlockItem = { blockType };\n\t\tfor (const field of def.fields) {\n\t\t\tnewBlock[field.name] = getFieldDefaultValue(field);\n\t\t}\n\n\t\tconst blocks = [...this.blocks(), newBlock];\n\t\tstate.value.set(blocks);\n\t}\n\n\t/** Remove a block at the given index */\n\tremoveBlock(index: number): void {\n\t\tconst state = this.nodeState();\n\t\tif (!state) return;\n\t\tconst blocks = this.blocks().filter((_, i) => i !== index);\n\t\tstate.value.set(blocks);\n\t}\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import * as i0 from '@angular/core';
|
|
2
|
+
import { input, signal, computed, effect, ChangeDetectionStrategy, Component } from '@angular/core';
|
|
3
|
+
import { Accordion, AccordionItem, AccordionTrigger, AccordionContent } from '@momentumcms/ui';
|
|
4
|
+
import { humanizeFieldName } from '@momentumcms/core';
|
|
5
|
+
import { b as getSubNode, F as FieldRenderer } from './momentumcms-admin-momentumcms-admin-o0FbJXZN.mjs';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Collapsible layout field renderer.
|
|
9
|
+
*
|
|
10
|
+
* Wraps child fields in an expandable/collapsible accordion section.
|
|
11
|
+
* This is a layout-only field; it does not store data itself.
|
|
12
|
+
* Child field FieldTree nodes are looked up from the root formTree
|
|
13
|
+
* using flat field names.
|
|
14
|
+
*/
|
|
15
|
+
class CollapsibleFieldRenderer {
|
|
16
|
+
/** Field definition (must be a CollapsibleField) */
|
|
17
|
+
field = input.required(...(ngDevMode ? [{ debugName: "field" }] : []));
|
|
18
|
+
/** Root signal forms FieldTree (for looking up child field nodes) */
|
|
19
|
+
formTree = input(null, ...(ngDevMode ? [{ debugName: "formTree" }] : []));
|
|
20
|
+
/** Form model data (for condition evaluation and relationship filterOptions) */
|
|
21
|
+
formModel = input({}, ...(ngDevMode ? [{ debugName: "formModel" }] : []));
|
|
22
|
+
/** Form mode */
|
|
23
|
+
mode = input('create', ...(ngDevMode ? [{ debugName: "mode" }] : []));
|
|
24
|
+
/** Field path (unused for layout fields, kept for interface consistency) */
|
|
25
|
+
path = input.required(...(ngDevMode ? [{ debugName: "path" }] : []));
|
|
26
|
+
/** Whether the accordion is expanded */
|
|
27
|
+
isExpanded = signal(false, ...(ngDevMode ? [{ debugName: "isExpanded" }] : []));
|
|
28
|
+
/** Unique panel ID for accordion aria linkage */
|
|
29
|
+
panelId = computed(() => `collapsible-${this.path().replace(/\./g, '-')}`, ...(ngDevMode ? [{ debugName: "panelId" }] : []));
|
|
30
|
+
/** Computed label */
|
|
31
|
+
label = computed(() => this.field().label || humanizeFieldName(this.field().name), ...(ngDevMode ? [{ debugName: "label" }] : []));
|
|
32
|
+
/** Computed description */
|
|
33
|
+
description = computed(() => this.field().description || '', ...(ngDevMode ? [{ debugName: "description" }] : []));
|
|
34
|
+
/** Child fields */
|
|
35
|
+
subFields = computed(() => {
|
|
36
|
+
const f = this.field();
|
|
37
|
+
if (f.type === 'collapsible') {
|
|
38
|
+
return f.fields.filter((sf) => !sf.admin?.hidden);
|
|
39
|
+
}
|
|
40
|
+
return [];
|
|
41
|
+
}, ...(ngDevMode ? [{ debugName: "subFields" }] : []));
|
|
42
|
+
constructor() {
|
|
43
|
+
// Set initial expanded state from field config once input is available
|
|
44
|
+
effect(() => {
|
|
45
|
+
const f = this.field();
|
|
46
|
+
if (f.type === 'collapsible' && f.defaultOpen) {
|
|
47
|
+
this.isExpanded.set(true);
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
/** Get a FieldTree sub-node for a child field (flat path from root tree) */
|
|
52
|
+
getChildFormNode(fieldName) {
|
|
53
|
+
return getSubNode(this.formTree(), fieldName);
|
|
54
|
+
}
|
|
55
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.2", ngImport: i0, type: CollapsibleFieldRenderer, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
56
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.2", type: CollapsibleFieldRenderer, isStandalone: true, selector: "mcms-collapsible-field-renderer", inputs: { field: { classPropertyName: "field", publicName: "field", isSignal: true, isRequired: true, 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 } }, host: { classAttribute: "block" }, ngImport: i0, template: `
|
|
57
|
+
<mcms-accordion>
|
|
58
|
+
<mcms-accordion-item>
|
|
59
|
+
<mcms-accordion-trigger [panelId]="panelId()" [(expanded)]="isExpanded">
|
|
60
|
+
{{ label() }}
|
|
61
|
+
</mcms-accordion-trigger>
|
|
62
|
+
<mcms-accordion-content [panelId]="panelId()">
|
|
63
|
+
@if (description()) {
|
|
64
|
+
<p class="text-sm text-muted-foreground mb-4">{{ description() }}</p>
|
|
65
|
+
}
|
|
66
|
+
<div class="space-y-4 py-4">
|
|
67
|
+
@for (subField of subFields(); track subField.name) {
|
|
68
|
+
<mcms-field-renderer
|
|
69
|
+
[field]="subField"
|
|
70
|
+
[formNode]="getChildFormNode(subField.name)"
|
|
71
|
+
[formTree]="formTree()"
|
|
72
|
+
[formModel]="formModel()"
|
|
73
|
+
[mode]="mode()"
|
|
74
|
+
[path]="subField.name"
|
|
75
|
+
/>
|
|
76
|
+
}
|
|
77
|
+
</div>
|
|
78
|
+
</mcms-accordion-content>
|
|
79
|
+
</mcms-accordion-item>
|
|
80
|
+
</mcms-accordion>
|
|
81
|
+
`, isInline: true, dependencies: [{ kind: "component", type: Accordion, selector: "mcms-accordion", inputs: ["multiExpandable", "disabled", "wrap", "class"] }, { kind: "component", type: AccordionItem, selector: "mcms-accordion-item", inputs: ["class"] }, { kind: "component", type: AccordionTrigger, selector: "mcms-accordion-trigger", inputs: ["panelId", "disabled", "expanded", "class"], outputs: ["expandedChange"] }, { kind: "component", type: AccordionContent, selector: "mcms-accordion-content", inputs: ["panelId", "class"] }, { kind: "component", type: FieldRenderer, selector: "mcms-field-renderer", inputs: ["field", "formNode", "formTree", "formModel", "mode", "path"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
82
|
+
}
|
|
83
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.2", ngImport: i0, type: CollapsibleFieldRenderer, decorators: [{
|
|
84
|
+
type: Component,
|
|
85
|
+
args: [{
|
|
86
|
+
selector: 'mcms-collapsible-field-renderer',
|
|
87
|
+
imports: [Accordion, AccordionItem, AccordionTrigger, AccordionContent, FieldRenderer],
|
|
88
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
89
|
+
host: { class: 'block' },
|
|
90
|
+
template: `
|
|
91
|
+
<mcms-accordion>
|
|
92
|
+
<mcms-accordion-item>
|
|
93
|
+
<mcms-accordion-trigger [panelId]="panelId()" [(expanded)]="isExpanded">
|
|
94
|
+
{{ label() }}
|
|
95
|
+
</mcms-accordion-trigger>
|
|
96
|
+
<mcms-accordion-content [panelId]="panelId()">
|
|
97
|
+
@if (description()) {
|
|
98
|
+
<p class="text-sm text-muted-foreground mb-4">{{ description() }}</p>
|
|
99
|
+
}
|
|
100
|
+
<div class="space-y-4 py-4">
|
|
101
|
+
@for (subField of subFields(); track subField.name) {
|
|
102
|
+
<mcms-field-renderer
|
|
103
|
+
[field]="subField"
|
|
104
|
+
[formNode]="getChildFormNode(subField.name)"
|
|
105
|
+
[formTree]="formTree()"
|
|
106
|
+
[formModel]="formModel()"
|
|
107
|
+
[mode]="mode()"
|
|
108
|
+
[path]="subField.name"
|
|
109
|
+
/>
|
|
110
|
+
}
|
|
111
|
+
</div>
|
|
112
|
+
</mcms-accordion-content>
|
|
113
|
+
</mcms-accordion-item>
|
|
114
|
+
</mcms-accordion>
|
|
115
|
+
`,
|
|
116
|
+
}]
|
|
117
|
+
}], ctorParameters: () => [], propDecorators: { field: [{ type: i0.Input, args: [{ isSignal: true, alias: "field", required: true }] }], 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 }] }] } });
|
|
118
|
+
|
|
119
|
+
export { CollapsibleFieldRenderer };
|
|
120
|
+
//# sourceMappingURL=momentumcms-admin-collapsible-field.component-CtwrGQvg.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"momentumcms-admin-collapsible-field.component-CtwrGQvg.mjs","sources":["../../../../libs/admin/src/lib/widgets/entity-form/field-renderers/collapsible-field.component.ts"],"sourcesContent":["import { ChangeDetectionStrategy, Component, computed, effect, input, signal } from '@angular/core';\nimport { Accordion, AccordionItem, AccordionTrigger, AccordionContent } from '@momentumcms/ui';\nimport { humanizeFieldName } from '@momentumcms/core';\nimport type { Field } from '@momentumcms/core';\nimport type { EntityFormMode } from '../entity-form.types';\nimport { getSubNode } from '../entity-form.types';\nimport { FieldRenderer } from './field-renderer.component';\n\n/**\n * Collapsible layout field renderer.\n *\n * Wraps child fields in an expandable/collapsible accordion section.\n * This is a layout-only field; it does not store data itself.\n * Child field FieldTree nodes are looked up from the root formTree\n * using flat field names.\n */\n@Component({\n\tselector: 'mcms-collapsible-field-renderer',\n\timports: [Accordion, AccordionItem, AccordionTrigger, AccordionContent, FieldRenderer],\n\tchangeDetection: ChangeDetectionStrategy.OnPush,\n\thost: { class: 'block' },\n\ttemplate: `\n\t\t<mcms-accordion>\n\t\t\t<mcms-accordion-item>\n\t\t\t\t<mcms-accordion-trigger [panelId]=\"panelId()\" [(expanded)]=\"isExpanded\">\n\t\t\t\t\t{{ label() }}\n\t\t\t\t</mcms-accordion-trigger>\n\t\t\t\t<mcms-accordion-content [panelId]=\"panelId()\">\n\t\t\t\t\t@if (description()) {\n\t\t\t\t\t\t<p class=\"text-sm text-muted-foreground mb-4\">{{ description() }}</p>\n\t\t\t\t\t}\n\t\t\t\t\t<div class=\"space-y-4 py-4\">\n\t\t\t\t\t\t@for (subField of subFields(); track subField.name) {\n\t\t\t\t\t\t\t<mcms-field-renderer\n\t\t\t\t\t\t\t\t[field]=\"subField\"\n\t\t\t\t\t\t\t\t[formNode]=\"getChildFormNode(subField.name)\"\n\t\t\t\t\t\t\t\t[formTree]=\"formTree()\"\n\t\t\t\t\t\t\t\t[formModel]=\"formModel()\"\n\t\t\t\t\t\t\t\t[mode]=\"mode()\"\n\t\t\t\t\t\t\t\t[path]=\"subField.name\"\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t}\n\t\t\t\t\t</div>\n\t\t\t\t</mcms-accordion-content>\n\t\t\t</mcms-accordion-item>\n\t\t</mcms-accordion>\n\t`,\n})\nexport class CollapsibleFieldRenderer {\n\t/** Field definition (must be a CollapsibleField) */\n\treadonly field = input.required<Field>();\n\n\t/** Root signal forms FieldTree (for looking up child field 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 (unused for layout fields, kept for interface consistency) */\n\treadonly path = input.required<string>();\n\n\t/** Whether the accordion is expanded */\n\treadonly isExpanded = signal(false);\n\n\t/** Unique panel ID for accordion aria linkage */\n\treadonly panelId = computed(() => `collapsible-${this.path().replace(/\\./g, '-')}`);\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/** Child fields */\n\treadonly subFields = computed((): Field[] => {\n\t\tconst f = this.field();\n\t\tif (f.type === 'collapsible') {\n\t\t\treturn f.fields.filter((sf) => !sf.admin?.hidden);\n\t\t}\n\t\treturn [];\n\t});\n\n\tconstructor() {\n\t\t// Set initial expanded state from field config once input is available\n\t\teffect(() => {\n\t\t\tconst f = this.field();\n\t\t\tif (f.type === 'collapsible' && f.defaultOpen) {\n\t\t\t\tthis.isExpanded.set(true);\n\t\t\t}\n\t\t});\n\t}\n\n\t/** Get a FieldTree sub-node for a child field (flat path from root tree) */\n\tgetChildFormNode(fieldName: string): unknown {\n\t\treturn getSubNode(this.formTree(), fieldName);\n\t}\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -2,7 +2,7 @@ import * as i0 from '@angular/core';
|
|
|
2
2
|
import { inject, viewChild, computed, ChangeDetectionStrategy, Component } from '@angular/core';
|
|
3
3
|
import { ActivatedRoute } from '@angular/router';
|
|
4
4
|
import { humanizeFieldName } from '@momentumcms/core';
|
|
5
|
-
import { g as getGlobalsFromRouteData, E as EntityFormWidget } from './momentumcms-admin-momentumcms-admin-
|
|
5
|
+
import { g as getGlobalsFromRouteData, E as EntityFormWidget } from './momentumcms-admin-momentumcms-admin-o0FbJXZN.mjs';
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* Global Edit Page
|
|
@@ -87,4 +87,4 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.2", ngImpor
|
|
|
87
87
|
}], propDecorators: { entityFormRef: [{ type: i0.ViewChild, args: ['entityForm', { isSignal: true }] }] } });
|
|
88
88
|
|
|
89
89
|
export { GlobalEditPage };
|
|
90
|
-
//# sourceMappingURL=momentumcms-admin-global-edit.page-
|
|
90
|
+
//# sourceMappingURL=momentumcms-admin-global-edit.page-BBUtWCSl.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"momentumcms-admin-global-edit.page-
|
|
1
|
+
{"version":3,"file":"momentumcms-admin-global-edit.page-BBUtWCSl.mjs","sources":["../../../../libs/admin/src/lib/pages/global-edit/global-edit.page.ts"],"sourcesContent":["import { Component, ChangeDetectionStrategy, inject, computed, viewChild } from '@angular/core';\nimport { ActivatedRoute } from '@angular/router';\nimport type { CollectionConfig, GlobalConfig } from '@momentumcms/core';\nimport { humanizeFieldName } from '@momentumcms/core';\nimport { getGlobalsFromRouteData } from '../../utils/route-data';\nimport { EntityFormWidget } from '../../widgets/entity-form/entity-form.component';\nimport type { HasUnsavedChanges } from '../../guards/unsaved-changes.guard';\n\n/**\n * Global Edit Page\n *\n * Renders the EntityFormWidget in global (singleton) mode for editing a global document.\n * The global config is converted to a CollectionConfig shape and passed with isGlobal=true.\n */\n@Component({\n\tselector: 'mcms-global-edit',\n\timports: [EntityFormWidget],\n\tchangeDetection: ChangeDetectionStrategy.OnPush,\n\thost: { class: 'block' },\n\ttemplate: `\n\t\t@if (collectionConfig(); as col) {\n\t\t\t<mcms-entity-form\n\t\t\t\t#entityForm\n\t\t\t\t[collection]=\"col\"\n\t\t\t\t[mode]=\"'edit'\"\n\t\t\t\t[basePath]=\"'/admin/globals'\"\n\t\t\t\t[showBreadcrumbs]=\"true\"\n\t\t\t\t[isGlobal]=\"true\"\n\t\t\t\t[globalSlug]=\"globalSlug()\"\n\t\t\t/>\n\t\t} @else {\n\t\t\t<div class=\"p-12 text-center text-muted-foreground\">Global not found</div>\n\t\t}\n\t`,\n})\nexport class GlobalEditPage implements HasUnsavedChanges {\n\tprivate readonly route = inject(ActivatedRoute);\n\n\tprivate readonly entityFormRef = viewChild<EntityFormWidget>('entityForm');\n\n\treadonly globalSlug = computed((): string => {\n\t\treturn this.route.snapshot.paramMap.get('slug') ?? '';\n\t});\n\n\treadonly globalConfig = computed((): GlobalConfig | undefined => {\n\t\tconst slug = this.globalSlug();\n\t\tconst globals = getGlobalsFromRouteData(this.route.parent?.snapshot.data);\n\t\treturn globals.find((g) => g.slug === slug);\n\t});\n\n\t/** Convert GlobalConfig to CollectionConfig shape for EntityFormWidget */\n\treadonly collectionConfig = computed((): CollectionConfig | undefined => {\n\t\tconst global = this.globalConfig();\n\t\tif (!global) return undefined;\n\n\t\treturn {\n\t\t\tslug: global.slug,\n\t\t\tfields: global.fields,\n\t\t\tlabels: {\n\t\t\t\tsingular: global.label ?? humanizeFieldName(global.slug),\n\t\t\t\tplural: global.label ?? humanizeFieldName(global.slug),\n\t\t\t},\n\t\t\tadmin: global.admin,\n\t\t\taccess: global.access\n\t\t\t\t? { read: global.access.read, update: global.access.update }\n\t\t\t\t: undefined,\n\t\t\thooks: global.hooks,\n\t\t\tversions: global.versions,\n\t\t};\n\t});\n\n\thasUnsavedChanges(): boolean {\n\t\treturn this.entityFormRef()?.isDirty() ?? false;\n\t}\n}\n"],"names":[],"mappings":"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"}
|