@momentumcms/admin 0.5.1 → 0.5.3

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 (25) hide show
  1. package/fesm2022/{momentumcms-admin-array-field.component-pqA3_nC8.mjs → momentumcms-admin-array-field.component-ChjP5zK9.mjs} +2 -2
  2. package/fesm2022/{momentumcms-admin-array-field.component-pqA3_nC8.mjs.map → momentumcms-admin-array-field.component-ChjP5zK9.mjs.map} +1 -1
  3. package/fesm2022/{momentumcms-admin-blocks-field.component-88TEhVm4.mjs → momentumcms-admin-blocks-field.component-Dl0FxGnf.mjs} +119 -30
  4. package/fesm2022/momentumcms-admin-blocks-field.component-Dl0FxGnf.mjs.map +1 -0
  5. package/fesm2022/{momentumcms-admin-collapsible-field.component-D5Jc8h2Q.mjs → momentumcms-admin-collapsible-field.component-CSLOT0Dp.mjs} +2 -2
  6. package/fesm2022/{momentumcms-admin-collapsible-field.component-D5Jc8h2Q.mjs.map → momentumcms-admin-collapsible-field.component-CSLOT0Dp.mjs.map} +1 -1
  7. package/fesm2022/{momentumcms-admin-global-edit.page-D4QNE5AM.mjs → momentumcms-admin-global-edit.page-DHr7Icl6.mjs} +2 -2
  8. package/fesm2022/{momentumcms-admin-global-edit.page-D4QNE5AM.mjs.map → momentumcms-admin-global-edit.page-DHr7Icl6.mjs.map} +1 -1
  9. package/fesm2022/{momentumcms-admin-group-field.component-Cenc5zMW.mjs → momentumcms-admin-group-field.component-Bofhumd5.mjs} +2 -2
  10. package/fesm2022/{momentumcms-admin-group-field.component-Cenc5zMW.mjs.map → momentumcms-admin-group-field.component-Bofhumd5.mjs.map} +1 -1
  11. package/fesm2022/{momentumcms-admin-momentumcms-admin-5WigESOC.mjs → momentumcms-admin-momentumcms-admin-D8WvqCxe.mjs} +793 -25
  12. package/fesm2022/momentumcms-admin-momentumcms-admin-D8WvqCxe.mjs.map +1 -0
  13. package/fesm2022/{momentumcms-admin-relationship-field.component-DlCdpcRy.mjs → momentumcms-admin-relationship-field.component-DSZhc5MP.mjs} +8 -3
  14. package/fesm2022/{momentumcms-admin-relationship-field.component-DlCdpcRy.mjs.map → momentumcms-admin-relationship-field.component-DSZhc5MP.mjs.map} +1 -1
  15. package/fesm2022/{momentumcms-admin-rich-text-field.component-BUziCgyn.mjs → momentumcms-admin-rich-text-field.component-BIUu6NXa.mjs} +2 -2
  16. package/fesm2022/{momentumcms-admin-rich-text-field.component-BUziCgyn.mjs.map → momentumcms-admin-rich-text-field.component-BIUu6NXa.mjs.map} +1 -1
  17. package/fesm2022/{momentumcms-admin-row-field.component-fFTcYU-P.mjs → momentumcms-admin-row-field.component-DBzqzooT.mjs} +2 -2
  18. package/fesm2022/{momentumcms-admin-row-field.component-fFTcYU-P.mjs.map → momentumcms-admin-row-field.component-DBzqzooT.mjs.map} +1 -1
  19. package/fesm2022/{momentumcms-admin-tabs-field.component-D_T_JZej.mjs → momentumcms-admin-tabs-field.component-BsnCWC5J.mjs} +2 -2
  20. package/fesm2022/{momentumcms-admin-tabs-field.component-D_T_JZej.mjs.map → momentumcms-admin-tabs-field.component-BsnCWC5J.mjs.map} +1 -1
  21. package/fesm2022/momentumcms-admin.mjs +1 -1
  22. package/package.json +1 -1
  23. package/types/momentumcms-admin.d.ts +127 -62
  24. package/fesm2022/momentumcms-admin-blocks-field.component-88TEhVm4.mjs.map +0 -1
  25. package/fesm2022/momentumcms-admin-momentumcms-admin-5WigESOC.mjs.map +0 -1
@@ -1134,7 +1134,7 @@ function momentumAdminRoutes(configOrOptions) {
1134
1134
  // Global edit
1135
1135
  {
1136
1136
  path: 'globals/:slug',
1137
- loadComponent: () => import('./momentumcms-admin-global-edit.page-D4QNE5AM.mjs').then((m) => m.GlobalEditPage),
1137
+ loadComponent: () => import('./momentumcms-admin-global-edit.page-DHr7Icl6.mjs').then((m) => m.GlobalEditPage),
1138
1138
  canDeactivate: [unsavedChangesGuard],
1139
1139
  },
1140
1140
  // Plugin-registered routes
@@ -4837,6 +4837,427 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImpor
4837
4837
  }]
4838
4838
  }], ctorParameters: () => [], propDecorators: { fileInputRef: [{ type: i0.ViewChild, args: ['fileInput', { isSignal: true }] }], uploadConfig: [{ type: i0.Input, args: [{ isSignal: true, alias: "uploadConfig", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], pendingFile: [{ type: i0.Input, args: [{ isSignal: true, alias: "pendingFile", required: false }] }], isUploading: [{ type: i0.Input, args: [{ isSignal: true, alias: "isUploading", required: false }] }], uploadProgress: [{ type: i0.Input, args: [{ isSignal: true, alias: "uploadProgress", required: false }] }], error: [{ type: i0.Input, args: [{ isSignal: true, alias: "error", required: false }] }], existingMedia: [{ type: i0.Input, args: [{ isSignal: true, alias: "existingMedia", required: false }] }], fileSelected: [{ type: i0.Output, args: ["fileSelected"] }], fileRemoved: [{ type: i0.Output, args: ["fileRemoved"] }] } });
4839
4839
 
4840
+ /**
4841
+ * Normalize a click position within a container to a 0-1 focal point.
4842
+ */
4843
+ function normalizeFocalPoint(click, container) {
4844
+ if (container.width <= 0 || container.height <= 0) {
4845
+ return { x: 0.5, y: 0.5 };
4846
+ }
4847
+ return clampFocalPoint({
4848
+ x: click.x / container.width,
4849
+ y: click.y / container.height,
4850
+ });
4851
+ }
4852
+ /**
4853
+ * Clamp a focal point to the valid 0-1 range on both axes.
4854
+ */
4855
+ function clampFocalPoint(point) {
4856
+ return {
4857
+ x: Math.max(0, Math.min(1, point.x)),
4858
+ y: Math.max(0, Math.min(1, point.y)),
4859
+ };
4860
+ }
4861
+ /**
4862
+ * Convert a normalized focal point to a CSS `object-position` string.
4863
+ *
4864
+ * @example focalPointToCssPosition({ x: 0.25, y: 0.75 }) → '25% 75%'
4865
+ */
4866
+ function focalPointToCssPosition(point) {
4867
+ const fp = point ?? { x: 0.5, y: 0.5 };
4868
+ return `${Math.round(fp.x * 100)}% ${Math.round(fp.y * 100)}%`;
4869
+ }
4870
+
4871
+ /**
4872
+ * Calculate the crop region for a cover-fit preview.
4873
+ * Same math as the server-side crop-calculator, but for browser-side preview.
4874
+ */
4875
+ function calculatePreviewCrop(source, target, focalPoint) {
4876
+ const fp = focalPoint ?? { x: 0.5, y: 0.5 };
4877
+ const scale = Math.max(target.width / source.width, target.height / source.height);
4878
+ const cropW = Math.round(target.width / scale);
4879
+ const cropH = Math.round(target.height / scale);
4880
+ const focalX = fp.x * source.width;
4881
+ const focalY = fp.y * source.height;
4882
+ let x = Math.round(focalX - cropW / 2);
4883
+ let y = Math.round(focalY - cropH / 2);
4884
+ x = Math.max(0, Math.min(x, source.width - cropW));
4885
+ y = Math.max(0, Math.min(y, source.height - cropH));
4886
+ return { x, y, width: cropW, height: cropH };
4887
+ }
4888
+
4889
+ /**
4890
+ * Focal Point Picker Component
4891
+ *
4892
+ * Displays an image with an interactive crosshair overlay.
4893
+ * Click anywhere on the image to set the focal point.
4894
+ * Optionally shows crop preview outlines for configured image sizes.
4895
+ *
4896
+ * @example
4897
+ * ```html
4898
+ * <mcms-focal-point-picker
4899
+ * [imageUrl]="mediaUrl"
4900
+ * [focalPoint]="{ x: 0.5, y: 0.5 }"
4901
+ * [imageSizes]="configuredSizes"
4902
+ * [naturalWidth]="800"
4903
+ * [naturalHeight]="600"
4904
+ * (focalPointChange)="onFocalPointChange($event)"
4905
+ * />
4906
+ * ```
4907
+ */
4908
+ class FocalPointPickerComponent {
4909
+ imageElRef = viewChild('imageEl', ...(ngDevMode ? [{ debugName: "imageElRef" }] : []));
4910
+ /** URL of the image to display */
4911
+ imageUrl = input.required(...(ngDevMode ? [{ debugName: "imageUrl" }] : []));
4912
+ /** Current focal point (0-1 normalized) */
4913
+ focalPoint = input({ x: 0.5, y: 0.5 }, ...(ngDevMode ? [{ debugName: "focalPoint" }] : []));
4914
+ /** Alt text for the image */
4915
+ alt = input('', ...(ngDevMode ? [{ debugName: "alt" }] : []));
4916
+ /** Natural width of the source image (for crop preview calculations) */
4917
+ naturalWidth = input(0, ...(ngDevMode ? [{ debugName: "naturalWidth" }] : []));
4918
+ /** Natural height of the source image (for crop preview calculations) */
4919
+ naturalHeight = input(0, ...(ngDevMode ? [{ debugName: "naturalHeight" }] : []));
4920
+ /** Configured image sizes for crop preview outlines */
4921
+ imageSizes = input([], ...(ngDevMode ? [{ debugName: "imageSizes" }] : []));
4922
+ /** Emitted when the focal point changes */
4923
+ focalPointChange = output();
4924
+ /** Focal point X as percentage */
4925
+ focalPointX = computed(() => Math.round(this.focalPoint().x * 100), ...(ngDevMode ? [{ debugName: "focalPointX" }] : []));
4926
+ /** Focal point Y as percentage */
4927
+ focalPointY = computed(() => Math.round(this.focalPoint().y * 100), ...(ngDevMode ? [{ debugName: "focalPointY" }] : []));
4928
+ /** CSS object-position string */
4929
+ cssPosition = computed(() => focalPointToCssPosition(this.focalPoint()), ...(ngDevMode ? [{ debugName: "cssPosition" }] : []));
4930
+ /** Human-readable position label */
4931
+ positionLabel = computed(() => {
4932
+ const fp = this.focalPoint();
4933
+ return `${Math.round(fp.x * 100)}% x ${Math.round(fp.y * 100)}%`;
4934
+ }, ...(ngDevMode ? [{ debugName: "positionLabel" }] : []));
4935
+ /** Crop preview data for each configured size */
4936
+ cropPreviews = computed(() => {
4937
+ const w = this.naturalWidth();
4938
+ const h = this.naturalHeight();
4939
+ const fp = this.focalPoint();
4940
+ const sizes = this.imageSizes();
4941
+ if (!w || !h || sizes.length === 0)
4942
+ return [];
4943
+ return sizes
4944
+ .filter((s) => s.width && s.height && s.fit === 'cover')
4945
+ .map((s) => {
4946
+ const crop = calculatePreviewCrop({ width: w, height: h }, { width: s.width ?? 0, height: s.height ?? 0 }, fp);
4947
+ return {
4948
+ name: s.name,
4949
+ leftPct: (crop.x / w) * 100,
4950
+ topPct: (crop.y / h) * 100,
4951
+ widthPct: (crop.width / w) * 100,
4952
+ heightPct: (crop.height / h) * 100,
4953
+ };
4954
+ });
4955
+ }, ...(ngDevMode ? [{ debugName: "cropPreviews" }] : []));
4956
+ /**
4957
+ * Handle click on the image overlay.
4958
+ */
4959
+ onClick(event) {
4960
+ const el = this.imageElRef();
4961
+ if (!el)
4962
+ return;
4963
+ const rect = el.nativeElement.getBoundingClientRect();
4964
+ if (rect.width <= 0 || rect.height <= 0)
4965
+ return;
4966
+ const x = event.clientX - rect.left;
4967
+ const y = event.clientY - rect.top;
4968
+ const fp = normalizeFocalPoint({ x, y }, { width: rect.width, height: rect.height });
4969
+ this.focalPointChange.emit(fp);
4970
+ }
4971
+ /**
4972
+ * Reset focal point to center.
4973
+ */
4974
+ onResetCenter() {
4975
+ this.focalPointChange.emit({ x: 0.5, y: 0.5 });
4976
+ }
4977
+ /**
4978
+ * Nudge focal point by a small amount (keyboard navigation).
4979
+ */
4980
+ onNudge(dx, dy, event) {
4981
+ event.preventDefault();
4982
+ const fp = this.focalPoint();
4983
+ const newFp = {
4984
+ x: Math.max(0, Math.min(1, fp.x + dx)),
4985
+ y: Math.max(0, Math.min(1, fp.y + dy)),
4986
+ };
4987
+ this.focalPointChange.emit(newFp);
4988
+ }
4989
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: FocalPointPickerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
4990
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.0", type: FocalPointPickerComponent, isStandalone: true, selector: "mcms-focal-point-picker", inputs: { imageUrl: { classPropertyName: "imageUrl", publicName: "imageUrl", isSignal: true, isRequired: true, transformFunction: null }, focalPoint: { classPropertyName: "focalPoint", publicName: "focalPoint", isSignal: true, isRequired: false, transformFunction: null }, alt: { classPropertyName: "alt", publicName: "alt", isSignal: true, isRequired: false, transformFunction: null }, naturalWidth: { classPropertyName: "naturalWidth", publicName: "naturalWidth", isSignal: true, isRequired: false, transformFunction: null }, naturalHeight: { classPropertyName: "naturalHeight", publicName: "naturalHeight", isSignal: true, isRequired: false, transformFunction: null }, imageSizes: { classPropertyName: "imageSizes", publicName: "imageSizes", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { focalPointChange: "focalPointChange" }, host: { classAttribute: "block" }, viewQueries: [{ propertyName: "imageElRef", first: true, predicate: ["imageEl"], descendants: true, isSignal: true }], ngImport: i0, template: `
4991
+ <div class="space-y-3">
4992
+ <div class="relative inline-block overflow-hidden rounded-lg border border-mcms-border">
4993
+ <!-- Image -->
4994
+ <img
4995
+ #imageEl
4996
+ [src]="imageUrl()"
4997
+ [alt]="alt() || 'Image with focal point selector'"
4998
+ class="block max-h-96 max-w-full"
4999
+ [style.object-position]="cssPosition()"
5000
+ />
5001
+
5002
+ <!-- Clickable overlay -->
5003
+ <div
5004
+ class="absolute inset-0 cursor-crosshair"
5005
+ role="button"
5006
+ tabindex="0"
5007
+ [attr.aria-label]="'Set focal point. Current position: ' + positionLabel()"
5008
+ (click)="onClick($event)"
5009
+ (keydown.enter)="onResetCenter()"
5010
+ (keydown.space)="onResetCenter()"
5011
+ (keydown.ArrowLeft)="onNudge(-0.05, 0, $event)"
5012
+ (keydown.ArrowRight)="onNudge(0.05, 0, $event)"
5013
+ (keydown.ArrowUp)="onNudge(0, -0.05, $event)"
5014
+ (keydown.ArrowDown)="onNudge(0, 0.05, $event)"
5015
+ >
5016
+ <!-- Crosshair lines -->
5017
+ <div
5018
+ class="pointer-events-none absolute h-px w-full bg-white/70"
5019
+ [style.top.%]="focalPointY()"
5020
+ ></div>
5021
+ <div
5022
+ class="pointer-events-none absolute w-px h-full bg-white/70"
5023
+ [style.left.%]="focalPointX()"
5024
+ ></div>
5025
+
5026
+ <!-- Focal point dot -->
5027
+ <div
5028
+ class="pointer-events-none absolute h-4 w-4 -translate-x-1/2 -translate-y-1/2 rounded-full border-2 border-white bg-mcms-primary shadow-md"
5029
+ [style.left.%]="focalPointX()"
5030
+ [style.top.%]="focalPointY()"
5031
+ aria-hidden="true"
5032
+ ></div>
5033
+
5034
+ <!-- Crop preview outlines -->
5035
+ @for (preview of cropPreviews(); track preview.name) {
5036
+ <div
5037
+ class="pointer-events-none absolute border border-dashed border-yellow-400/60"
5038
+ [style.left.%]="preview.leftPct"
5039
+ [style.top.%]="preview.topPct"
5040
+ [style.width.%]="preview.widthPct"
5041
+ [style.height.%]="preview.heightPct"
5042
+ [attr.aria-label]="'Crop preview for ' + preview.name"
5043
+ >
5044
+ <span class="absolute -top-5 left-0 text-xs text-yellow-400 drop-shadow-sm">
5045
+ {{ preview.name }}
5046
+ </span>
5047
+ </div>
5048
+ }
5049
+ </div>
5050
+ </div>
5051
+
5052
+ <!-- Coordinates display -->
5053
+ <p class="text-xs text-mcms-muted-foreground" aria-live="polite">
5054
+ Focal point: {{ positionLabel() }}
5055
+ <button
5056
+ class="ml-2 underline hover:text-mcms-foreground"
5057
+ type="button"
5058
+ (click)="onResetCenter()"
5059
+ aria-label="Reset focal point to center"
5060
+ >
5061
+ Reset to center
5062
+ </button>
5063
+ </p>
5064
+ </div>
5065
+ `, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
5066
+ }
5067
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: FocalPointPickerComponent, decorators: [{
5068
+ type: Component,
5069
+ args: [{
5070
+ selector: 'mcms-focal-point-picker',
5071
+ changeDetection: ChangeDetectionStrategy.OnPush,
5072
+ host: { class: 'block' },
5073
+ template: `
5074
+ <div class="space-y-3">
5075
+ <div class="relative inline-block overflow-hidden rounded-lg border border-mcms-border">
5076
+ <!-- Image -->
5077
+ <img
5078
+ #imageEl
5079
+ [src]="imageUrl()"
5080
+ [alt]="alt() || 'Image with focal point selector'"
5081
+ class="block max-h-96 max-w-full"
5082
+ [style.object-position]="cssPosition()"
5083
+ />
5084
+
5085
+ <!-- Clickable overlay -->
5086
+ <div
5087
+ class="absolute inset-0 cursor-crosshair"
5088
+ role="button"
5089
+ tabindex="0"
5090
+ [attr.aria-label]="'Set focal point. Current position: ' + positionLabel()"
5091
+ (click)="onClick($event)"
5092
+ (keydown.enter)="onResetCenter()"
5093
+ (keydown.space)="onResetCenter()"
5094
+ (keydown.ArrowLeft)="onNudge(-0.05, 0, $event)"
5095
+ (keydown.ArrowRight)="onNudge(0.05, 0, $event)"
5096
+ (keydown.ArrowUp)="onNudge(0, -0.05, $event)"
5097
+ (keydown.ArrowDown)="onNudge(0, 0.05, $event)"
5098
+ >
5099
+ <!-- Crosshair lines -->
5100
+ <div
5101
+ class="pointer-events-none absolute h-px w-full bg-white/70"
5102
+ [style.top.%]="focalPointY()"
5103
+ ></div>
5104
+ <div
5105
+ class="pointer-events-none absolute w-px h-full bg-white/70"
5106
+ [style.left.%]="focalPointX()"
5107
+ ></div>
5108
+
5109
+ <!-- Focal point dot -->
5110
+ <div
5111
+ class="pointer-events-none absolute h-4 w-4 -translate-x-1/2 -translate-y-1/2 rounded-full border-2 border-white bg-mcms-primary shadow-md"
5112
+ [style.left.%]="focalPointX()"
5113
+ [style.top.%]="focalPointY()"
5114
+ aria-hidden="true"
5115
+ ></div>
5116
+
5117
+ <!-- Crop preview outlines -->
5118
+ @for (preview of cropPreviews(); track preview.name) {
5119
+ <div
5120
+ class="pointer-events-none absolute border border-dashed border-yellow-400/60"
5121
+ [style.left.%]="preview.leftPct"
5122
+ [style.top.%]="preview.topPct"
5123
+ [style.width.%]="preview.widthPct"
5124
+ [style.height.%]="preview.heightPct"
5125
+ [attr.aria-label]="'Crop preview for ' + preview.name"
5126
+ >
5127
+ <span class="absolute -top-5 left-0 text-xs text-yellow-400 drop-shadow-sm">
5128
+ {{ preview.name }}
5129
+ </span>
5130
+ </div>
5131
+ }
5132
+ </div>
5133
+ </div>
5134
+
5135
+ <!-- Coordinates display -->
5136
+ <p class="text-xs text-mcms-muted-foreground" aria-live="polite">
5137
+ Focal point: {{ positionLabel() }}
5138
+ <button
5139
+ class="ml-2 underline hover:text-mcms-foreground"
5140
+ type="button"
5141
+ (click)="onResetCenter()"
5142
+ aria-label="Reset focal point to center"
5143
+ >
5144
+ Reset to center
5145
+ </button>
5146
+ </p>
5147
+ </div>
5148
+ `,
5149
+ }]
5150
+ }], propDecorators: { imageElRef: [{ type: i0.ViewChild, args: ['imageEl', { isSignal: true }] }], imageUrl: [{ type: i0.Input, args: [{ isSignal: true, alias: "imageUrl", required: true }] }], focalPoint: [{ type: i0.Input, args: [{ isSignal: true, alias: "focalPoint", required: false }] }], alt: [{ type: i0.Input, args: [{ isSignal: true, alias: "alt", required: false }] }], naturalWidth: [{ type: i0.Input, args: [{ isSignal: true, alias: "naturalWidth", required: false }] }], naturalHeight: [{ type: i0.Input, args: [{ isSignal: true, alias: "naturalHeight", required: false }] }], imageSizes: [{ type: i0.Input, args: [{ isSignal: true, alias: "imageSizes", required: false }] }], focalPointChange: [{ type: i0.Output, args: ["focalPointChange"] }] } });
5151
+
5152
+ /**
5153
+ * Displays generated image size variants as a grid of thumbnail cards.
5154
+ *
5155
+ * @example
5156
+ * ```html
5157
+ * <mcms-image-variants-display [sizes]="entity.sizes" />
5158
+ * ```
5159
+ */
5160
+ class ImageVariantsDisplay {
5161
+ /** The sizes record from the media document (e.g., entity['sizes']) */
5162
+ sizes = input(null, ...(ngDevMode ? [{ debugName: "sizes" }] : []));
5163
+ /** Parsed variant entries */
5164
+ variants = computed(() => {
5165
+ const sizes = this.sizes();
5166
+ if (!sizes || typeof sizes !== 'object')
5167
+ return [];
5168
+ return Object.entries(sizes)
5169
+ .filter((entry) => {
5170
+ const v = entry[1];
5171
+ return v != null && typeof v === 'object' && 'width' in v && 'path' in v;
5172
+ })
5173
+ .map(([name, data]) => ({
5174
+ name,
5175
+ ...data,
5176
+ }));
5177
+ }, ...(ngDevMode ? [{ debugName: "variants" }] : []));
5178
+ /** Build URL for a variant image */
5179
+ variantUrl(variant) {
5180
+ if (variant.url)
5181
+ return variant.url;
5182
+ if (variant.path)
5183
+ return `/api/media/file/${variant.path}`;
5184
+ return '';
5185
+ }
5186
+ /** Format file size for display */
5187
+ formatFileSize(bytes) {
5188
+ if (!bytes)
5189
+ return '';
5190
+ if (bytes >= 1024 * 1024)
5191
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
5192
+ if (bytes >= 1024)
5193
+ return `${(bytes / 1024).toFixed(1)} KB`;
5194
+ return `${bytes} bytes`;
5195
+ }
5196
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: ImageVariantsDisplay, deps: [], target: i0.ɵɵFactoryTarget.Component });
5197
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.0", type: ImageVariantsDisplay, isStandalone: true, selector: "mcms-image-variants-display", inputs: { sizes: { classPropertyName: "sizes", publicName: "sizes", isSignal: true, isRequired: false, transformFunction: null } }, host: { classAttribute: "block" }, ngImport: i0, template: `
5198
+ @if (variants().length > 0) {
5199
+ <div class="space-y-3">
5200
+ <p class="text-sm font-medium">Generated Sizes</p>
5201
+ <div class="grid gap-3 grid-cols-2 sm:grid-cols-3">
5202
+ @for (variant of variants(); track variant.name) {
5203
+ <div class="rounded-lg border border-mcms-border bg-mcms-card overflow-hidden">
5204
+ <img
5205
+ [src]="variantUrl(variant)"
5206
+ [alt]="variant.name + ' variant'"
5207
+ class="h-24 w-full object-cover bg-mcms-muted"
5208
+ />
5209
+ <div class="p-2">
5210
+ <p class="text-xs font-medium">{{ variant.name }}</p>
5211
+ <p class="text-xs text-mcms-muted-foreground">
5212
+ {{ variant.width }} &times; {{ variant.height }}
5213
+ </p>
5214
+ <p class="text-xs text-mcms-muted-foreground">
5215
+ {{ formatFileSize(variant.filesize) }}
5216
+ </p>
5217
+ </div>
5218
+ </div>
5219
+ }
5220
+ </div>
5221
+ </div>
5222
+ }
5223
+ `, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
5224
+ }
5225
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: ImageVariantsDisplay, decorators: [{
5226
+ type: Component,
5227
+ args: [{
5228
+ selector: 'mcms-image-variants-display',
5229
+ changeDetection: ChangeDetectionStrategy.OnPush,
5230
+ host: { class: 'block' },
5231
+ template: `
5232
+ @if (variants().length > 0) {
5233
+ <div class="space-y-3">
5234
+ <p class="text-sm font-medium">Generated Sizes</p>
5235
+ <div class="grid gap-3 grid-cols-2 sm:grid-cols-3">
5236
+ @for (variant of variants(); track variant.name) {
5237
+ <div class="rounded-lg border border-mcms-border bg-mcms-card overflow-hidden">
5238
+ <img
5239
+ [src]="variantUrl(variant)"
5240
+ [alt]="variant.name + ' variant'"
5241
+ class="h-24 w-full object-cover bg-mcms-muted"
5242
+ />
5243
+ <div class="p-2">
5244
+ <p class="text-xs font-medium">{{ variant.name }}</p>
5245
+ <p class="text-xs text-mcms-muted-foreground">
5246
+ {{ variant.width }} &times; {{ variant.height }}
5247
+ </p>
5248
+ <p class="text-xs text-mcms-muted-foreground">
5249
+ {{ formatFileSize(variant.filesize) }}
5250
+ </p>
5251
+ </div>
5252
+ </div>
5253
+ }
5254
+ </div>
5255
+ </div>
5256
+ }
5257
+ `,
5258
+ }]
5259
+ }], propDecorators: { sizes: [{ type: i0.Input, args: [{ isSignal: true, alias: "sizes", required: false }] }] } });
5260
+
4840
5261
  /**
4841
5262
  * Entity Form Widget
4842
5263
  *
@@ -4886,8 +5307,17 @@ class EntityFormWidget {
4886
5307
  draftSaved = output();
4887
5308
  /** Model signal — the single source of truth for form data */
4888
5309
  formModel = signal({}, ...(ngDevMode ? [{ debugName: "formModel" }] : []));
4889
- /** Alias for backward compatibility (CollectionEditPage reads formData) */
4890
- formData = this.formModel;
5310
+ /** Reactive form data that tracks Signal Forms changes for downstream consumers (e.g. live preview).
5311
+ * Reads the root FieldState value directly so it re-triggers when any field value changes,
5312
+ * unlike formModel which may keep the same object reference after in-place mutations. */
5313
+ formData = computed(() => {
5314
+ const ef = this.entityForm();
5315
+ if (!ef)
5316
+ return this.formModel();
5317
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- FieldState value is the full model
5318
+ const val = ef().value();
5319
+ return val ?? this.formModel();
5320
+ }, ...(ngDevMode ? [{ debugName: "formData" }] : []));
4891
5321
  /** Signal forms tree — created once when collection is available */
4892
5322
  entityForm = signal(null, ...(ngDevMode ? [{ debugName: "entityForm" }] : []));
4893
5323
  /** Original data for edit mode */
@@ -4902,8 +5332,87 @@ class EntityFormWidget {
4902
5332
  isUploadingFile = signal(false, ...(ngDevMode ? [{ debugName: "isUploadingFile" }] : []));
4903
5333
  uploadFileProgress = signal(0, ...(ngDevMode ? [{ debugName: "uploadFileProgress" }] : []));
4904
5334
  uploadFileError = signal(null, ...(ngDevMode ? [{ debugName: "uploadFileError" }] : []));
5335
+ /** Preview URL for the pending image file (focal point picker) */
5336
+ pendingFileUrl = signal(null, ...(ngDevMode ? [{ debugName: "pendingFileUrl" }] : []));
5337
+ /** Detected dimensions of the pending image */
5338
+ pendingImageDimensions = signal({
5339
+ width: 0,
5340
+ height: 0,
5341
+ }, ...(ngDevMode ? [{ debugName: "pendingImageDimensions" }] : []));
4905
5342
  /** Whether the collection is an upload collection */
4906
5343
  isUploadCol = computed(() => isUploadCollection(this.collection()), ...(ngDevMode ? [{ debugName: "isUploadCol" }] : []));
5344
+ /** Whether the current file (pending or existing) is an image */
5345
+ isImageFile = computed(() => {
5346
+ const file = this.pendingFile();
5347
+ if (file)
5348
+ return file.type.startsWith('image/');
5349
+ if (this.isUploadCol() && this.mode() !== 'create') {
5350
+ const mimeType = this.formModel()['mimeType'];
5351
+ return typeof mimeType === 'string' && mimeType.startsWith('image/');
5352
+ }
5353
+ return false;
5354
+ }, ...(ngDevMode ? [{ debugName: "isImageFile" }] : []));
5355
+ /** Image sizes from collection upload config */
5356
+ uploadImageSizes = computed(() => {
5357
+ return this.collection().upload?.imageSizes ?? [];
5358
+ }, ...(ngDevMode ? [{ debugName: "uploadImageSizes" }] : []));
5359
+ /** Image URL for the focal point picker */
5360
+ focalPointImageUrl = computed(() => {
5361
+ const fileUrl = this.pendingFileUrl();
5362
+ if (fileUrl)
5363
+ return fileUrl;
5364
+ const model = this.formModel();
5365
+ if (typeof model['url'] === 'string' && model['url'])
5366
+ return model['url'];
5367
+ if (typeof model['path'] === 'string' && model['path'])
5368
+ return `/api/media/file/${model['path']}`;
5369
+ return '';
5370
+ }, ...(ngDevMode ? [{ debugName: "focalPointImageUrl" }] : []));
5371
+ /** Current focal point value */
5372
+ currentFocalPoint = computed(() => {
5373
+ const fp = this.formModel()['focalPoint'];
5374
+ if (fp != null && typeof fp === 'object' && !Array.isArray(fp)) {
5375
+ const obj = fp; // eslint-disable-line @typescript-eslint/consistent-type-assertions
5376
+ const x = obj['x'];
5377
+ const y = obj['y'];
5378
+ if (typeof x === 'number' && typeof y === 'number') {
5379
+ return { x, y };
5380
+ }
5381
+ }
5382
+ return { x: 0.5, y: 0.5 };
5383
+ }, ...(ngDevMode ? [{ debugName: "currentFocalPoint" }] : []));
5384
+ /** Natural image dimensions (pending file detection or existing media) */
5385
+ imageNaturalDimensions = computed(() => {
5386
+ if (this.pendingFile())
5387
+ return this.pendingImageDimensions();
5388
+ const model = this.formModel();
5389
+ return {
5390
+ width: typeof model['width'] === 'number' ? model['width'] : 0,
5391
+ height: typeof model['height'] === 'number' ? model['height'] : 0,
5392
+ };
5393
+ }, ...(ngDevMode ? [{ debugName: "imageNaturalDimensions" }] : []));
5394
+ /** Alt text for the focal point picker */
5395
+ focalPointAlt = computed(() => {
5396
+ const model = this.formModel();
5397
+ const alt = model['alt'];
5398
+ if (typeof alt === 'string' && alt)
5399
+ return alt;
5400
+ const fn = model['filename'];
5401
+ if (typeof fn === 'string')
5402
+ return fn;
5403
+ return '';
5404
+ }, ...(ngDevMode ? [{ debugName: "focalPointAlt" }] : []));
5405
+ /** Generated image sizes from the form model */
5406
+ formModelSizes = computed(() => {
5407
+ const sizes = this.formModel()['sizes'];
5408
+ if (sizes != null &&
5409
+ typeof sizes === 'object' &&
5410
+ !Array.isArray(sizes) &&
5411
+ Object.keys(sizes).length > 0) {
5412
+ return sizes; // eslint-disable-line @typescript-eslint/consistent-type-assertions
5413
+ }
5414
+ return null;
5415
+ }, ...(ngDevMode ? [{ debugName: "formModelSizes" }] : []));
4907
5416
  /** Whether the form has been set up */
4908
5417
  formCreated = false;
4909
5418
  /** Whether the form has unsaved changes (from signal forms dirty tracking) */
@@ -5002,16 +5511,46 @@ class EntityFormWidget {
5002
5511
  this.loadGlobal(gSlug);
5003
5512
  }
5004
5513
  else if (currentMode === 'create' || !id) {
5005
- this.formModel.set(createInitialFormData(col));
5006
- const ef = this.entityForm();
5007
- if (ef)
5008
- ef().reset();
5514
+ // Guard: don't reset formModel if the user has already selected a file.
5515
+ // On Analog (SSR), reactive route signals may re-emit after hydration,
5516
+ // causing this effect to re-run and wipe user input.
5517
+ const hasPendingFile = untracked(() => this.pendingFile());
5518
+ if (!hasPendingFile) {
5519
+ this.formModel.set(createInitialFormData(col));
5520
+ const ef = this.entityForm();
5521
+ if (ef)
5522
+ ef().reset();
5523
+ }
5009
5524
  }
5010
5525
  else {
5011
5526
  this.loadEntity(col.slug, id);
5012
5527
  }
5013
5528
  }
5014
5529
  });
5530
+ // Manage preview URL for focal point picker and detect image dimensions
5531
+ effect((onCleanup) => {
5532
+ const file = this.pendingFile();
5533
+ if (file && file.type.startsWith('image/')) {
5534
+ const url = URL.createObjectURL(file);
5535
+ this.pendingFileUrl.set(url);
5536
+ let cancelled = false;
5537
+ onCleanup(() => {
5538
+ cancelled = true;
5539
+ URL.revokeObjectURL(url);
5540
+ });
5541
+ const img = new Image();
5542
+ img.onload = () => {
5543
+ if (!cancelled) {
5544
+ this.pendingImageDimensions.set({ width: img.naturalWidth, height: img.naturalHeight });
5545
+ }
5546
+ };
5547
+ img.src = url;
5548
+ }
5549
+ else {
5550
+ this.pendingFileUrl.set(null);
5551
+ this.pendingImageDimensions.set({ width: 0, height: 0 });
5552
+ }
5553
+ });
5015
5554
  }
5016
5555
  /**
5017
5556
  * Get a FieldTree node for a top-level field by name.
@@ -5120,6 +5659,14 @@ class EntityFormWidget {
5120
5659
  data['filesize'] = file.size;
5121
5660
  this.formModel.set(data);
5122
5661
  }
5662
+ /**
5663
+ * Handle focal point change from the picker.
5664
+ */
5665
+ onFocalPointChange(fp) {
5666
+ const data = { ...this.formModel() };
5667
+ data['focalPoint'] = fp;
5668
+ this.formModel.set(data);
5669
+ }
5123
5670
  /**
5124
5671
  * Handle file removed from the upload zone.
5125
5672
  */
@@ -5395,7 +5942,7 @@ class EntityFormWidget {
5395
5942
  <mcms-collection-upload-zone
5396
5943
  [uploadConfig]="collection().upload"
5397
5944
  [pendingFile]="pendingFile()"
5398
- [existingMedia]="mode() === 'edit' && !pendingFile() ? formModel() : null"
5945
+ [existingMedia]="mode() !== 'create' && !pendingFile() ? formModel() : null"
5399
5946
  [disabled]="mode() === 'view'"
5400
5947
  [isUploading]="isUploadingFile()"
5401
5948
  [uploadProgress]="uploadFileProgress()"
@@ -5405,6 +5952,27 @@ class EntityFormWidget {
5405
5952
  />
5406
5953
  }
5407
5954
 
5955
+ @if (isUploadCol() && isImageFile() && focalPointImageUrl()) {
5956
+ <div class="mb-6" [class.pointer-events-none]="mode() === 'view'">
5957
+ <p class="mb-2 text-sm font-medium">Focal Point</p>
5958
+ <mcms-focal-point-picker
5959
+ [imageUrl]="focalPointImageUrl()"
5960
+ [focalPoint]="currentFocalPoint()"
5961
+ [naturalWidth]="imageNaturalDimensions().width"
5962
+ [naturalHeight]="imageNaturalDimensions().height"
5963
+ [alt]="focalPointAlt()"
5964
+ [imageSizes]="uploadImageSizes()"
5965
+ (focalPointChange)="onFocalPointChange($event)"
5966
+ />
5967
+ </div>
5968
+ }
5969
+
5970
+ @if (isUploadCol() && formModelSizes()) {
5971
+ <div class="mb-6">
5972
+ <mcms-image-variants-display [sizes]="formModelSizes()" />
5973
+ </div>
5974
+ }
5975
+
5408
5976
  <div class="space-y-6">
5409
5977
  @for (field of visibleFields(); track field.name) {
5410
5978
  <mcms-field-renderer
@@ -5473,7 +6041,7 @@ class EntityFormWidget {
5473
6041
  </div>
5474
6042
  }
5475
6043
  </div>
5476
- `, isInline: true, dependencies: [{ kind: "component", type: Card, selector: "mcms-card" }, { 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: Spinner, selector: "mcms-spinner", inputs: ["size", "label", "class"] }, { kind: "component", type: Alert, selector: "mcms-alert", inputs: ["variant", "class"] }, { kind: "component", type: FieldRenderer, selector: "mcms-field-renderer", inputs: ["field", "formNode", "formTree", "formModel", "mode", "path"] }, { kind: "component", type: Breadcrumbs, selector: "mcms-breadcrumbs", inputs: ["class"] }, { kind: "component", type: BreadcrumbItem, selector: "mcms-breadcrumb-item", inputs: ["href", "current", "class"] }, { kind: "component", type: BreadcrumbSeparator, selector: "mcms-breadcrumb-separator", inputs: ["class"] }, { kind: "component", type: VersionHistoryWidget, selector: "mcms-version-history", inputs: ["collection", "documentId", "documentLabel"], outputs: ["restored"] }, { kind: "component", type: CollectionUploadZoneComponent, selector: "mcms-collection-upload-zone", inputs: ["uploadConfig", "disabled", "pendingFile", "isUploading", "uploadProgress", "error", "existingMedia"], outputs: ["fileSelected", "fileRemoved"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
6044
+ `, isInline: true, dependencies: [{ kind: "component", type: Card, selector: "mcms-card" }, { 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: Spinner, selector: "mcms-spinner", inputs: ["size", "label", "class"] }, { kind: "component", type: Alert, selector: "mcms-alert", inputs: ["variant", "class"] }, { kind: "component", type: FieldRenderer, selector: "mcms-field-renderer", inputs: ["field", "formNode", "formTree", "formModel", "mode", "path"] }, { kind: "component", type: Breadcrumbs, selector: "mcms-breadcrumbs", inputs: ["class"] }, { kind: "component", type: BreadcrumbItem, selector: "mcms-breadcrumb-item", inputs: ["href", "current", "class"] }, { kind: "component", type: BreadcrumbSeparator, selector: "mcms-breadcrumb-separator", inputs: ["class"] }, { kind: "component", type: VersionHistoryWidget, selector: "mcms-version-history", inputs: ["collection", "documentId", "documentLabel"], outputs: ["restored"] }, { kind: "component", type: CollectionUploadZoneComponent, selector: "mcms-collection-upload-zone", inputs: ["uploadConfig", "disabled", "pendingFile", "isUploading", "uploadProgress", "error", "existingMedia"], outputs: ["fileSelected", "fileRemoved"] }, { kind: "component", type: FocalPointPickerComponent, selector: "mcms-focal-point-picker", inputs: ["imageUrl", "focalPoint", "alt", "naturalWidth", "naturalHeight", "imageSizes"], outputs: ["focalPointChange"] }, { kind: "component", type: ImageVariantsDisplay, selector: "mcms-image-variants-display", inputs: ["sizes"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
5477
6045
  }
5478
6046
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: EntityFormWidget, decorators: [{
5479
6047
  type: Component,
@@ -5492,6 +6060,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImpor
5492
6060
  BreadcrumbSeparator,
5493
6061
  VersionHistoryWidget,
5494
6062
  CollectionUploadZoneComponent,
6063
+ FocalPointPickerComponent,
6064
+ ImageVariantsDisplay,
5495
6065
  ],
5496
6066
  changeDetection: ChangeDetectionStrategy.OnPush,
5497
6067
  host: { class: 'block' },
@@ -5557,7 +6127,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImpor
5557
6127
  <mcms-collection-upload-zone
5558
6128
  [uploadConfig]="collection().upload"
5559
6129
  [pendingFile]="pendingFile()"
5560
- [existingMedia]="mode() === 'edit' && !pendingFile() ? formModel() : null"
6130
+ [existingMedia]="mode() !== 'create' && !pendingFile() ? formModel() : null"
5561
6131
  [disabled]="mode() === 'view'"
5562
6132
  [isUploading]="isUploadingFile()"
5563
6133
  [uploadProgress]="uploadFileProgress()"
@@ -5567,6 +6137,27 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImpor
5567
6137
  />
5568
6138
  }
5569
6139
 
6140
+ @if (isUploadCol() && isImageFile() && focalPointImageUrl()) {
6141
+ <div class="mb-6" [class.pointer-events-none]="mode() === 'view'">
6142
+ <p class="mb-2 text-sm font-medium">Focal Point</p>
6143
+ <mcms-focal-point-picker
6144
+ [imageUrl]="focalPointImageUrl()"
6145
+ [focalPoint]="currentFocalPoint()"
6146
+ [naturalWidth]="imageNaturalDimensions().width"
6147
+ [naturalHeight]="imageNaturalDimensions().height"
6148
+ [alt]="focalPointAlt()"
6149
+ [imageSizes]="uploadImageSizes()"
6150
+ (focalPointChange)="onFocalPointChange($event)"
6151
+ />
6152
+ </div>
6153
+ }
6154
+
6155
+ @if (isUploadCol() && formModelSizes()) {
6156
+ <div class="mb-6">
6157
+ <mcms-image-variants-display [sizes]="formModelSizes()" />
6158
+ </div>
6159
+ }
6160
+
5570
6161
  <div class="space-y-6">
5571
6162
  @for (field of visibleFields(); track field.name) {
5572
6163
  <mcms-field-renderer
@@ -5945,6 +6536,81 @@ class EntityViewWidget {
5945
6536
  canDelete = computed(() => {
5946
6537
  return this.collectionAccess.canDelete(this.collection().slug);
5947
6538
  }, ...(ngDevMode ? [{ debugName: "canDelete" }] : []));
6539
+ /** Whether this collection is an upload collection */
6540
+ isUploadCol = computed(() => isUploadCollection(this.collection()), ...(ngDevMode ? [{ debugName: "isUploadCol" }] : []));
6541
+ /** Whether the entity is an image */
6542
+ isEntityImage = computed(() => {
6543
+ const e = this.entity();
6544
+ if (!e)
6545
+ return false;
6546
+ const mimeType = e['mimeType'];
6547
+ return typeof mimeType === 'string' && mimeType.startsWith('image/');
6548
+ }, ...(ngDevMode ? [{ debugName: "isEntityImage" }] : []));
6549
+ /** Media URL for preview */
6550
+ entityMediaUrl = computed(() => {
6551
+ const e = this.entity();
6552
+ if (!e)
6553
+ return '';
6554
+ if (typeof e['url'] === 'string' && e['url'])
6555
+ return e['url'];
6556
+ if (typeof e['path'] === 'string' && e['path'])
6557
+ return `/api/media/file/${e['path']}`;
6558
+ return '';
6559
+ }, ...(ngDevMode ? [{ debugName: "entityMediaUrl" }] : []));
6560
+ /** Focal point from entity data */
6561
+ entityFocalPoint = computed(() => {
6562
+ const e = this.entity();
6563
+ if (!e)
6564
+ return { x: 0.5, y: 0.5 };
6565
+ const fp = e['focalPoint'];
6566
+ if (fp != null && typeof fp === 'object' && !Array.isArray(fp)) {
6567
+ const obj = fp; // eslint-disable-line @typescript-eslint/consistent-type-assertions
6568
+ const x = obj['x'];
6569
+ const y = obj['y'];
6570
+ if (typeof x === 'number' && typeof y === 'number')
6571
+ return { x, y };
6572
+ }
6573
+ return { x: 0.5, y: 0.5 };
6574
+ }, ...(ngDevMode ? [{ debugName: "entityFocalPoint" }] : []));
6575
+ /** Image dimensions from entity data */
6576
+ entityDimensions = computed(() => {
6577
+ const e = this.entity();
6578
+ return {
6579
+ width: typeof e?.['width'] === 'number' ? e['width'] : 0,
6580
+ height: typeof e?.['height'] === 'number' ? e['height'] : 0,
6581
+ };
6582
+ }, ...(ngDevMode ? [{ debugName: "entityDimensions" }] : []));
6583
+ /** Image sizes from collection upload config */
6584
+ viewImageSizes = computed(() => {
6585
+ return this.collection().upload?.imageSizes ?? [];
6586
+ }, ...(ngDevMode ? [{ debugName: "viewImageSizes" }] : []));
6587
+ /** Generated image sizes from entity data */
6588
+ entitySizes = computed(() => {
6589
+ const e = this.entity();
6590
+ if (!e)
6591
+ return null;
6592
+ const sizes = e['sizes'];
6593
+ if (sizes != null &&
6594
+ typeof sizes === 'object' &&
6595
+ !Array.isArray(sizes) &&
6596
+ Object.keys(sizes).length > 0) {
6597
+ return sizes; // eslint-disable-line @typescript-eslint/consistent-type-assertions
6598
+ }
6599
+ return null;
6600
+ }, ...(ngDevMode ? [{ debugName: "entitySizes" }] : []));
6601
+ /** Media preview data for non-image files */
6602
+ entityMediaPreview = computed(() => {
6603
+ const e = this.entity();
6604
+ if (!e)
6605
+ return null;
6606
+ return {
6607
+ url: typeof e['url'] === 'string' ? e['url'] : undefined,
6608
+ path: typeof e['path'] === 'string' ? e['path'] : undefined,
6609
+ mimeType: typeof e['mimeType'] === 'string' ? e['mimeType'] : undefined,
6610
+ filename: typeof e['filename'] === 'string' ? e['filename'] : undefined,
6611
+ alt: typeof e['alt'] === 'string' ? e['alt'] : undefined,
6612
+ };
6613
+ }, ...(ngDevMode ? [{ debugName: "entityMediaPreview" }] : []));
5948
6614
  /** Whether collection has soft delete enabled */
5949
6615
  hasSoftDelete = computed(() => !!this.collection().softDelete, ...(ngDevMode ? [{ debugName: "hasSoftDelete" }] : []));
5950
6616
  /** Whether the current entity is soft-deleted */
@@ -6400,6 +7066,28 @@ class EntityViewWidget {
6400
7066
  {{ loadError() }}
6401
7067
  </mcms-alert>
6402
7068
  } @else if (entity()) {
7069
+ @if (isUploadCol() && entityMediaUrl()) {
7070
+ <div class="mb-6">
7071
+ @if (isEntityImage()) {
7072
+ <div class="pointer-events-none">
7073
+ <mcms-focal-point-picker
7074
+ [imageUrl]="entityMediaUrl()"
7075
+ [focalPoint]="entityFocalPoint()"
7076
+ [naturalWidth]="entityDimensions().width"
7077
+ [naturalHeight]="entityDimensions().height"
7078
+ [imageSizes]="viewImageSizes()"
7079
+ />
7080
+ </div>
7081
+ } @else {
7082
+ <mcms-media-preview [media]="entityMediaPreview()" size="xl" />
7083
+ }
7084
+ </div>
7085
+ }
7086
+ @if (isUploadCol() && entitySizes()) {
7087
+ <div class="mb-6">
7088
+ <mcms-image-variants-display [sizes]="entitySizes()" />
7089
+ </div>
7090
+ }
6403
7091
  <div class="grid gap-6 md:grid-cols-2">
6404
7092
  @for (field of visibleFields(); track field.name) {
6405
7093
  <mcms-field-display
@@ -6448,7 +7136,7 @@ class EntityViewWidget {
6448
7136
  </div>
6449
7137
  }
6450
7138
  </div>
6451
- `, isInline: true, dependencies: [{ kind: "component", type: Card, selector: "mcms-card" }, { 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: Alert, selector: "mcms-alert", inputs: ["variant", "class"] }, { kind: "component", type: Skeleton, selector: "mcms-skeleton", inputs: ["class"] }, { kind: "component", type: FieldDisplay, selector: "mcms-field-display", inputs: ["value", "type", "label", "format", "emptyText", "badgeConfig", "openInNewTab", "maxItems", "class", "fieldMeta", "numberFormat", "dateFormat"] }, { kind: "component", type: Breadcrumbs, selector: "mcms-breadcrumbs", inputs: ["class"] }, { kind: "component", type: BreadcrumbItem, selector: "mcms-breadcrumb-item", inputs: ["href", "current", "class"] }, { kind: "component", type: BreadcrumbSeparator, selector: "mcms-breadcrumb-separator", inputs: ["class"] }, { kind: "component", type: VersionHistoryWidget, selector: "mcms-version-history", inputs: ["collection", "documentId", "documentLabel"], outputs: ["restored"] }, { kind: "component", type: PublishControlsWidget, selector: "mcms-publish-controls", inputs: ["collection", "documentId", "documentLabel", "initialStatus"], outputs: ["statusChanged"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
7139
+ `, isInline: true, dependencies: [{ kind: "component", type: Card, selector: "mcms-card" }, { 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: Alert, selector: "mcms-alert", inputs: ["variant", "class"] }, { kind: "component", type: Skeleton, selector: "mcms-skeleton", inputs: ["class"] }, { kind: "component", type: FieldDisplay, selector: "mcms-field-display", inputs: ["value", "type", "label", "format", "emptyText", "badgeConfig", "openInNewTab", "maxItems", "class", "fieldMeta", "numberFormat", "dateFormat"] }, { kind: "component", type: Breadcrumbs, selector: "mcms-breadcrumbs", inputs: ["class"] }, { kind: "component", type: BreadcrumbItem, selector: "mcms-breadcrumb-item", inputs: ["href", "current", "class"] }, { kind: "component", type: BreadcrumbSeparator, selector: "mcms-breadcrumb-separator", inputs: ["class"] }, { kind: "component", type: VersionHistoryWidget, selector: "mcms-version-history", inputs: ["collection", "documentId", "documentLabel"], outputs: ["restored"] }, { kind: "component", type: PublishControlsWidget, selector: "mcms-publish-controls", inputs: ["collection", "documentId", "documentLabel", "initialStatus"], outputs: ["statusChanged"] }, { kind: "component", type: MediaPreviewComponent, selector: "mcms-media-preview", inputs: ["media", "size", "class", "rounded"] }, { kind: "component", type: FocalPointPickerComponent, selector: "mcms-focal-point-picker", inputs: ["imageUrl", "focalPoint", "alt", "naturalWidth", "naturalHeight", "imageSizes"], outputs: ["focalPointChange"] }, { kind: "component", type: ImageVariantsDisplay, selector: "mcms-image-variants-display", inputs: ["sizes"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
6452
7140
  }
6453
7141
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: EntityViewWidget, decorators: [{
6454
7142
  type: Component,
@@ -6467,6 +7155,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImpor
6467
7155
  BreadcrumbSeparator,
6468
7156
  VersionHistoryWidget,
6469
7157
  PublishControlsWidget,
7158
+ MediaPreviewComponent,
7159
+ FocalPointPickerComponent,
7160
+ ImageVariantsDisplay,
6470
7161
  ],
6471
7162
  changeDetection: ChangeDetectionStrategy.OnPush,
6472
7163
  host: { class: 'block' },
@@ -6570,6 +7261,28 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImpor
6570
7261
  {{ loadError() }}
6571
7262
  </mcms-alert>
6572
7263
  } @else if (entity()) {
7264
+ @if (isUploadCol() && entityMediaUrl()) {
7265
+ <div class="mb-6">
7266
+ @if (isEntityImage()) {
7267
+ <div class="pointer-events-none">
7268
+ <mcms-focal-point-picker
7269
+ [imageUrl]="entityMediaUrl()"
7270
+ [focalPoint]="entityFocalPoint()"
7271
+ [naturalWidth]="entityDimensions().width"
7272
+ [naturalHeight]="entityDimensions().height"
7273
+ [imageSizes]="viewImageSizes()"
7274
+ />
7275
+ </div>
7276
+ } @else {
7277
+ <mcms-media-preview [media]="entityMediaPreview()" size="xl" />
7278
+ }
7279
+ </div>
7280
+ }
7281
+ @if (isUploadCol() && entitySizes()) {
7282
+ <div class="mb-6">
7283
+ <mcms-image-variants-display [sizes]="entitySizes()" />
7284
+ </div>
7285
+ }
6573
7286
  <div class="grid gap-6 md:grid-cols-2">
6574
7287
  @for (field of visibleFields(); track field.name) {
6575
7288
  <mcms-field-display
@@ -11452,14 +12165,27 @@ class MediaEditDialog {
11452
12165
  data = inject(DIALOG_DATA);
11453
12166
  api = injectMomentumAPI();
11454
12167
  media = this.data.media;
12168
+ imageSizes = this.data.imageSizes ?? [];
11455
12169
  filename = signal(this.data.media.filename, ...(ngDevMode ? [{ debugName: "filename" }] : []));
11456
12170
  altText = signal(this.data.media.alt ?? '', ...(ngDevMode ? [{ debugName: "altText" }] : []));
12171
+ focalPointValue = signal(this.data.media.focalPoint ?? { x: 0.5, y: 0.5 }, ...(ngDevMode ? [{ debugName: "focalPointValue" }] : []));
11457
12172
  isSaving = signal(false, ...(ngDevMode ? [{ debugName: "isSaving" }] : []));
11458
12173
  saveError = signal(null, ...(ngDevMode ? [{ debugName: "saveError" }] : []));
11459
12174
  formattedSize = formatFileSize(this.data.media.filesize);
12175
+ isImage = computed(() => this.media.mimeType.startsWith('image/'), ...(ngDevMode ? [{ debugName: "isImage" }] : []));
12176
+ imageUrl = computed(() => {
12177
+ return this.media.url ?? `/api/media/file/${this.media.path}`;
12178
+ }, ...(ngDevMode ? [{ debugName: "imageUrl" }] : []));
11460
12179
  hasChanges = computed(() => {
11461
- return this.filename() !== this.media.filename || this.altText() !== (this.media.alt ?? '');
12180
+ const fpChanged = this.focalPointValue().x !== (this.media.focalPoint?.x ?? 0.5) ||
12181
+ this.focalPointValue().y !== (this.media.focalPoint?.y ?? 0.5);
12182
+ return (this.filename() !== this.media.filename ||
12183
+ this.altText() !== (this.media.alt ?? '') ||
12184
+ fpChanged);
11462
12185
  }, ...(ngDevMode ? [{ debugName: "hasChanges" }] : []));
12186
+ onFocalPointChange(fp) {
12187
+ this.focalPointValue.set(fp);
12188
+ }
11463
12189
  /**
11464
12190
  * Save media metadata changes via API.
11465
12191
  */
@@ -11467,10 +12193,14 @@ class MediaEditDialog {
11467
12193
  this.isSaving.set(true);
11468
12194
  this.saveError.set(null);
11469
12195
  try {
11470
- const result = await this.api.collection('media').update(this.media.id, {
12196
+ const updateData = {
11471
12197
  filename: this.filename(),
11472
12198
  alt: this.altText(),
11473
- });
12199
+ };
12200
+ if (this.isImage()) {
12201
+ updateData['focalPoint'] = this.focalPointValue();
12202
+ }
12203
+ const result = await this.api.collection('media').update(this.media.id, updateData);
11474
12204
  if (isMediaEditItem(result)) {
11475
12205
  this.dialogRef.close({ updated: true, media: result });
11476
12206
  }
@@ -11527,6 +12257,21 @@ class MediaEditDialog {
11527
12257
  </div>
11528
12258
  </div>
11529
12259
 
12260
+ @if (isImage()) {
12261
+ <div class="mt-4">
12262
+ <p class="mb-2 text-sm font-medium">Focal Point</p>
12263
+ <mcms-focal-point-picker
12264
+ [imageUrl]="imageUrl()"
12265
+ [focalPoint]="focalPointValue()"
12266
+ [naturalWidth]="media.width ?? 0"
12267
+ [naturalHeight]="media.height ?? 0"
12268
+ [alt]="media.alt ?? media.filename"
12269
+ [imageSizes]="imageSizes"
12270
+ (focalPointChange)="onFocalPointChange($event)"
12271
+ />
12272
+ </div>
12273
+ }
12274
+
11530
12275
  @if (saveError()) {
11531
12276
  <mcms-alert variant="destructive" class="mt-4">
11532
12277
  {{ saveError() }}
@@ -11544,7 +12289,7 @@ class MediaEditDialog {
11544
12289
  </button>
11545
12290
  </mcms-dialog-footer>
11546
12291
  </mcms-dialog>
11547
- `, isInline: true, dependencies: [{ kind: "component", type: Button, selector: "button[mcms-button], a[mcms-button]", inputs: ["variant", "size", "disabled", "loading", "ariaLabel", "class"] }, { kind: "component", type: Dialog, selector: "mcms-dialog", inputs: ["class"] }, { kind: "component", type: DialogHeader, selector: "mcms-dialog-header" }, { kind: "component", type: DialogTitle, selector: "mcms-dialog-title", inputs: ["id"] }, { kind: "component", type: DialogContent, selector: "mcms-dialog-content" }, { kind: "component", type: DialogFooter, selector: "mcms-dialog-footer" }, { kind: "directive", type: DialogClose, selector: "[mcmsDialogClose]", inputs: ["mcmsDialogClose"] }, { kind: "component", type: Input, selector: "mcms-input", inputs: ["value", "disabled", "errors", "touched", "invalid", "readonly", "required", "type", "id", "name", "placeholder", "autocomplete", "ariaLabel", "describedBy", "min", "max", "step"], outputs: ["valueChange", "blurred"] }, { kind: "component", type: Textarea, selector: "mcms-textarea", inputs: ["value", "disabled", "errors", "touched", "invalid", "readonly", "required", "id", "name", "placeholder", "rows", "ariaLabel", "describedBy"], outputs: ["valueChange", "blurred"] }, { kind: "component", type: Label, selector: "mcms-label", inputs: ["for", "required", "disabled", "class"] }, { kind: "component", type: Spinner, selector: "mcms-spinner", inputs: ["size", "label", "class"] }, { kind: "component", type: Alert, selector: "mcms-alert", inputs: ["variant", "class"] }, { kind: "component", type: MediaPreviewComponent, selector: "mcms-media-preview", inputs: ["media", "size", "class", "rounded"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
12292
+ `, isInline: true, dependencies: [{ kind: "component", type: Button, selector: "button[mcms-button], a[mcms-button]", inputs: ["variant", "size", "disabled", "loading", "ariaLabel", "class"] }, { kind: "component", type: Dialog, selector: "mcms-dialog", inputs: ["class"] }, { kind: "component", type: DialogHeader, selector: "mcms-dialog-header" }, { kind: "component", type: DialogTitle, selector: "mcms-dialog-title", inputs: ["id"] }, { kind: "component", type: DialogContent, selector: "mcms-dialog-content" }, { kind: "component", type: DialogFooter, selector: "mcms-dialog-footer" }, { kind: "directive", type: DialogClose, selector: "[mcmsDialogClose]", inputs: ["mcmsDialogClose"] }, { kind: "component", type: Input, selector: "mcms-input", inputs: ["value", "disabled", "errors", "touched", "invalid", "readonly", "required", "type", "id", "name", "placeholder", "autocomplete", "ariaLabel", "describedBy", "min", "max", "step"], outputs: ["valueChange", "blurred"] }, { kind: "component", type: Textarea, selector: "mcms-textarea", inputs: ["value", "disabled", "errors", "touched", "invalid", "readonly", "required", "id", "name", "placeholder", "rows", "ariaLabel", "describedBy"], outputs: ["valueChange", "blurred"] }, { kind: "component", type: Label, selector: "mcms-label", inputs: ["for", "required", "disabled", "class"] }, { kind: "component", type: Spinner, selector: "mcms-spinner", inputs: ["size", "label", "class"] }, { kind: "component", type: Alert, selector: "mcms-alert", inputs: ["variant", "class"] }, { kind: "component", type: MediaPreviewComponent, selector: "mcms-media-preview", inputs: ["media", "size", "class", "rounded"] }, { kind: "component", type: FocalPointPickerComponent, selector: "mcms-focal-point-picker", inputs: ["imageUrl", "focalPoint", "alt", "naturalWidth", "naturalHeight", "imageSizes"], outputs: ["focalPointChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
11548
12293
  }
11549
12294
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: MediaEditDialog, decorators: [{
11550
12295
  type: Component,
@@ -11564,6 +12309,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImpor
11564
12309
  Spinner,
11565
12310
  Alert,
11566
12311
  MediaPreviewComponent,
12312
+ FocalPointPickerComponent,
11567
12313
  ],
11568
12314
  changeDetection: ChangeDetectionStrategy.OnPush,
11569
12315
  host: { style: 'display: block; width: 100%' },
@@ -11608,6 +12354,21 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImpor
11608
12354
  </div>
11609
12355
  </div>
11610
12356
 
12357
+ @if (isImage()) {
12358
+ <div class="mt-4">
12359
+ <p class="mb-2 text-sm font-medium">Focal Point</p>
12360
+ <mcms-focal-point-picker
12361
+ [imageUrl]="imageUrl()"
12362
+ [focalPoint]="focalPointValue()"
12363
+ [naturalWidth]="media.width ?? 0"
12364
+ [naturalHeight]="media.height ?? 0"
12365
+ [alt]="media.alt ?? media.filename"
12366
+ [imageSizes]="imageSizes"
12367
+ (focalPointChange)="onFocalPointChange($event)"
12368
+ />
12369
+ </div>
12370
+ }
12371
+
11611
12372
  @if (saveError()) {
11612
12373
  <mcms-alert variant="destructive" class="mt-4">
11613
12374
  {{ saveError() }}
@@ -11668,6 +12429,7 @@ function getInputElement(event) {
11668
12429
  */
11669
12430
  class MediaLibraryPage {
11670
12431
  document = inject(DOCUMENT);
12432
+ route = inject(ActivatedRoute);
11671
12433
  api = injectMomentumAPI();
11672
12434
  uploadService = inject(UploadService);
11673
12435
  feedback = inject(FeedbackService);
@@ -11675,6 +12437,12 @@ class MediaLibraryPage {
11675
12437
  dialog = inject(DialogService);
11676
12438
  destroyRef = inject(DestroyRef);
11677
12439
  uploadSubscriptions = [];
12440
+ /** Image sizes config from the media collection (for crop previews in edit dialog) */
12441
+ mediaImageSizes = (() => {
12442
+ const collections = getCollectionsFromRouteData(this.route.parent?.snapshot.data);
12443
+ const mediaColl = collections.find((c) => c.slug === 'media');
12444
+ return mediaColl?.upload?.imageSizes ?? [];
12445
+ })();
11678
12446
  /** Internal state */
11679
12447
  isLoading = signal(true, ...(ngDevMode ? [{ debugName: "isLoading" }] : []));
11680
12448
  mediaItems = signal([], ...(ngDevMode ? [{ debugName: "mediaItems" }] : []));
@@ -11826,7 +12594,7 @@ class MediaLibraryPage {
11826
12594
  */
11827
12595
  editMedia(media) {
11828
12596
  const dialogRef = this.dialog.open(MediaEditDialog, {
11829
- data: { media },
12597
+ data: { media, imageSizes: this.mediaImageSizes },
11830
12598
  width: '36rem',
11831
12599
  });
11832
12600
  dialogRef.afterClosed.subscribe((result) => {
@@ -13454,18 +14222,18 @@ function provideMomentumFieldRenderers() {
13454
14222
  registry.register('checkbox', () => Promise.resolve().then(function () { return checkboxField_component; }).then((m) => m.CheckboxFieldRenderer));
13455
14223
  registry.register('date', () => Promise.resolve().then(function () { return dateField_component; }).then((m) => m.DateFieldRenderer));
13456
14224
  registry.register('upload', () => Promise.resolve().then(function () { return uploadField_component; }).then((m) => m.UploadFieldRenderer));
13457
- registry.register('richText', () => import('./momentumcms-admin-rich-text-field.component-BUziCgyn.mjs').then((m) => m.RichTextFieldRenderer));
14225
+ registry.register('richText', () => import('./momentumcms-admin-rich-text-field.component-BIUu6NXa.mjs').then((m) => m.RichTextFieldRenderer));
13458
14226
  // Layout field renderers (support nested field rendering)
13459
- registry.register('group', () => import('./momentumcms-admin-group-field.component-Cenc5zMW.mjs').then((m) => m.GroupFieldRenderer));
13460
- registry.register('array', () => import('./momentumcms-admin-array-field.component-pqA3_nC8.mjs').then((m) => m.ArrayFieldRenderer));
13461
- registry.register('blocks', () => import('./momentumcms-admin-blocks-field.component-88TEhVm4.mjs').then((m) => m.BlocksFieldRenderer));
14227
+ registry.register('group', () => import('./momentumcms-admin-group-field.component-Bofhumd5.mjs').then((m) => m.GroupFieldRenderer));
14228
+ registry.register('array', () => import('./momentumcms-admin-array-field.component-ChjP5zK9.mjs').then((m) => m.ArrayFieldRenderer));
14229
+ registry.register('blocks', () => import('./momentumcms-admin-blocks-field.component-Dl0FxGnf.mjs').then((m) => m.BlocksFieldRenderer));
13462
14230
  // Visual block editor variant (blocks field with admin.editor === 'visual')
13463
14231
  registry.register('blocks-visual', () => Promise.resolve().then(function () { return visualBlockEditor_component; }).then((m) => m.VisualBlockEditorComponent));
13464
- registry.register('relationship', () => import('./momentumcms-admin-relationship-field.component-DlCdpcRy.mjs').then((m) => m.RelationshipFieldRenderer));
14232
+ registry.register('relationship', () => import('./momentumcms-admin-relationship-field.component-DSZhc5MP.mjs').then((m) => m.RelationshipFieldRenderer));
13465
14233
  // Layout-only renderers (tabs, collapsible, row)
13466
- registry.register('tabs', () => import('./momentumcms-admin-tabs-field.component-D_T_JZej.mjs').then((m) => m.TabsFieldRenderer));
13467
- registry.register('collapsible', () => import('./momentumcms-admin-collapsible-field.component-D5Jc8h2Q.mjs').then((m) => m.CollapsibleFieldRenderer));
13468
- registry.register('row', () => import('./momentumcms-admin-row-field.component-fFTcYU-P.mjs').then((m) => m.RowFieldRenderer));
14234
+ registry.register('tabs', () => import('./momentumcms-admin-tabs-field.component-BsnCWC5J.mjs').then((m) => m.TabsFieldRenderer));
14235
+ registry.register('collapsible', () => import('./momentumcms-admin-collapsible-field.component-CSLOT0Dp.mjs').then((m) => m.CollapsibleFieldRenderer));
14236
+ registry.register('row', () => import('./momentumcms-admin-row-field.component-DBzqzooT.mjs').then((m) => m.RowFieldRenderer));
13469
14237
  };
13470
14238
  },
13471
14239
  },
@@ -15245,4 +16013,4 @@ var uploadField_component = /*#__PURE__*/Object.freeze({
15245
16013
  */
15246
16014
 
15247
16015
  export { adminGuard as $, AdminShellComponent as A, BlockEditDialog as B, CheckboxFieldRenderer as C, DashboardPage as D, EntityFormWidget as E, FieldRenderer as F, MediaLibraryPage as G, MediaPickerDialog as H, MediaPreviewComponent as I, MomentumApiService as J, MomentumAuthService as K, LivePreviewComponent as L, MOMENTUM_API as M, NumberFieldRenderer as N, ResetPasswordPage as O, PublishControlsWidget as P, SKIP_AUTO_TOAST as Q, ResetPasswordFormComponent as R, SHEET_QUERY_PARAMS as S, SelectFieldRenderer as T, SetupPage as U, TextFieldRenderer as V, UploadFieldRenderer as W, UploadService as X, VersionHistoryWidget as Y, VersionService as Z, VisualBlockEditorComponent as _, getFieldNodeState as a, authGuard as a0, collectionAccessGuard as a1, crudToastInterceptor as a2, guestGuard as a3, injectHasAnyRole as a4, injectHasRole as a5, injectIsAdmin as a6, injectIsAuthenticated as a7, injectMomentumAPI as a8, injectTypedMomentumAPI as a9, injectUser as aa, injectUserRole as ab, injectVersionService as ac, momentumAdminRoutes as ad, provideFieldRenderer as ae, provideMomentumAPI as af, provideMomentumFieldRenderers as ag, setupGuard as ah, unsavedChangesGuard as ai, getSubNode as b, getFieldDefaultValue as c, EntitySheetService as d, getTitleField as e, AdminSidebarWidget as f, getGlobalsFromRouteData as g, BlockInserterComponent as h, isRecord as i, BlockWrapperComponent as j, CollectionAccessService as k, CollectionCardWidget as l, CollectionEditPage as m, normalizeBlockDefaults as n, CollectionListPage as o, CollectionViewPage as p, DateFieldRenderer as q, EntityListWidget as r, EntityViewWidget as s, FeedbackService as t, FieldRendererRegistry as u, ForgotPasswordFormComponent as v, ForgotPasswordPage as w, LoginPage as x, MOMENTUM_API_CONTEXT as y, McmsThemeService as z };
15248
- //# sourceMappingURL=momentumcms-admin-momentumcms-admin-5WigESOC.mjs.map
16016
+ //# sourceMappingURL=momentumcms-admin-momentumcms-admin-D8WvqCxe.mjs.map