@mmlogic/components 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 (113) hide show
  1. package/dist/cjs/cell-renderer-7KX9Qbml.js +983 -0
  2. package/dist/cjs/{client-layout-RZ3Ix-4a.js → client-layout-CqVuJumR.js} +9 -0
  3. package/dist/cjs/{index-DzRfoAPz.js → index-DVbdkYO3.js} +468 -6
  4. package/dist/cjs/index.cjs.js +4 -1
  5. package/dist/cjs/loader.cjs.js +2 -2
  6. package/dist/cjs/mosterdcomponents.cjs.js +2 -2
  7. package/dist/cjs/{mrd-boolean-field_23.cjs.entry.js → mrd-boolean-field_25.cjs.entry.js} +3017 -951
  8. package/dist/cjs/mrd-merge-view.cjs.entry.js +273 -0
  9. package/dist/collection/collection-manifest.json +3 -0
  10. package/dist/collection/components/documents/mrd-document-view/mrd-document-view.js +7 -8
  11. package/dist/collection/components/email/mrd-email-view/mrd-email-view.css +126 -0
  12. package/dist/collection/components/email/mrd-email-view/mrd-email-view.js +247 -0
  13. package/dist/collection/components/fields/file-upload-common.js +23 -7
  14. package/dist/collection/components/fields/mrd-boolean-field/mrd-boolean-field.js +1 -1
  15. package/dist/collection/components/fields/mrd-currency-field/mrd-currency-field.js +1 -1
  16. package/dist/collection/components/fields/mrd-date-field/mrd-date-field.js +1 -1
  17. package/dist/collection/components/fields/mrd-datetime-field/mrd-datetime-field.js +1 -1
  18. package/dist/collection/components/fields/mrd-email-field/mrd-email-field.js +1 -1
  19. package/dist/collection/components/fields/mrd-file-field/mrd-file-field.css +20 -1
  20. package/dist/collection/components/fields/mrd-file-field/mrd-file-field.js +108 -47
  21. package/dist/collection/components/fields/mrd-image-field/mrd-image-field.css +44 -0
  22. package/dist/collection/components/fields/mrd-image-field/mrd-image-field.js +126 -58
  23. package/dist/collection/components/fields/mrd-longtext-field/mrd-longtext-field.js +1 -1
  24. package/dist/collection/components/fields/mrd-number-field/mrd-number-field.js +1 -1
  25. package/dist/collection/components/fields/mrd-secret-field/mrd-secret-field.js +2 -2
  26. package/dist/collection/components/fields/mrd-text-field/mrd-text-field.js +1 -1
  27. package/dist/collection/components/fields/mrd-textarea-field/mrd-textarea-field.js +9 -5
  28. package/dist/collection/components/fields/mrd-time-field/mrd-time-field.js +1 -1
  29. package/dist/collection/components/form/mrd-field/mrd-field.js +5 -5
  30. package/dist/collection/components/form/mrd-form/mrd-form.js +55 -6
  31. package/dist/collection/components/invoice/mrd-invoice-view/mrd-invoice-view.css +145 -0
  32. package/dist/collection/components/invoice/mrd-invoice-view/mrd-invoice-view.js +281 -0
  33. package/dist/collection/components/layout/mrd-layout-section/field-value.js +5 -1
  34. package/dist/collection/components/layout/mrd-layout-section/mrd-layout-section.css +10 -0
  35. package/dist/collection/components/layout/mrd-layout-section/mrd-layout-section.js +38 -8
  36. package/dist/collection/components/merge/mrd-merge-view/merge-diff.js +99 -0
  37. package/dist/collection/components/merge/mrd-merge-view/merge-parts.js +44 -0
  38. package/dist/collection/components/merge/mrd-merge-view/mrd-merge-view.css +244 -0
  39. package/dist/collection/components/merge/mrd-merge-view/mrd-merge-view.js +397 -0
  40. package/dist/collection/dev/api.js +41 -2
  41. package/dist/collection/dev/app.js +135 -17
  42. package/dist/collection/dev/example-data.js +126 -2
  43. package/dist/collection/types/client-layout.js +6 -0
  44. package/dist/collection/utils/cell-renderer.js +6 -1
  45. package/dist/collection/utils/document-attachments.js +27 -14
  46. package/dist/collection/utils/i18n.js +224 -0
  47. package/dist/components/cell-renderer.js +1 -0
  48. package/dist/components/client-layout.js +1 -1
  49. package/dist/components/document-attachments.js +1 -1
  50. package/dist/components/file-upload-common.js +1 -1
  51. package/dist/components/format.js +1 -1
  52. package/dist/components/i18n.js +1 -1
  53. package/dist/components/index.js +1 -1
  54. package/dist/components/mrd-boolean-field2.js +1 -1
  55. package/dist/components/mrd-currency-field2.js +1 -1
  56. package/dist/components/mrd-date-field2.js +1 -1
  57. package/dist/components/mrd-datetime-field2.js +1 -1
  58. package/dist/components/mrd-document-view2.js +1 -1
  59. package/dist/components/mrd-email-field2.js +1 -1
  60. package/dist/components/mrd-email-view.d.ts +11 -0
  61. package/dist/components/mrd-email-view.js +1 -0
  62. package/dist/components/mrd-email-view2.js +1 -0
  63. package/dist/components/mrd-field2.js +1 -1
  64. package/dist/components/mrd-file-field2.js +1 -1
  65. package/dist/components/mrd-form.js +1 -1
  66. package/dist/components/mrd-image-field2.js +1 -1
  67. package/dist/components/mrd-invoice-view.d.ts +11 -0
  68. package/dist/components/mrd-invoice-view.js +1 -0
  69. package/dist/components/mrd-invoice-view2.js +1 -0
  70. package/dist/components/mrd-layout-section.js +1 -1
  71. package/dist/components/mrd-longtext-field2.js +1 -1
  72. package/dist/components/mrd-merge-view.d.ts +11 -0
  73. package/dist/components/mrd-merge-view.js +1 -0
  74. package/dist/components/mrd-number-field2.js +1 -1
  75. package/dist/components/mrd-secret-field2.js +1 -1
  76. package/dist/components/mrd-table2.js +1 -1
  77. package/dist/components/mrd-text-field2.js +1 -1
  78. package/dist/components/mrd-textarea-field2.js +1 -1
  79. package/dist/components/mrd-time-field2.js +1 -1
  80. package/dist/components/purify.es.js +2 -0
  81. package/dist/esm/cell-renderer-DIsNX9D7.js +974 -0
  82. package/dist/esm/{client-layout-DCOAAH92.js → client-layout-Bc42CfJB.js} +7 -1
  83. package/dist/esm/{index-DiSe6vjv.js → index-CDiBvvJU.js} +468 -6
  84. package/dist/esm/index.js +1 -1
  85. package/dist/esm/loader.js +3 -3
  86. package/dist/esm/mosterdcomponents.js +3 -3
  87. package/dist/esm/{mrd-boolean-field_23.entry.js → mrd-boolean-field_25.entry.js} +2949 -885
  88. package/dist/esm/mrd-merge-view.entry.js +271 -0
  89. package/dist/mosterdcomponents/index.esm.js +1 -1
  90. package/dist/mosterdcomponents/mosterdcomponents.esm.js +1 -1
  91. package/dist/mosterdcomponents/p-4e061416.entry.js +1 -0
  92. package/dist/mosterdcomponents/p-910ebfee.entry.js +3 -0
  93. package/dist/mosterdcomponents/{p-DCOAAH92.js → p-Bc42CfJB.js} +1 -1
  94. package/dist/mosterdcomponents/p-CDiBvvJU.js +2 -0
  95. package/dist/mosterdcomponents/p-Ce2QDq0t.js +1 -0
  96. package/dist/types/components/documents/mrd-document-view/mrd-document-view.d.ts +2 -2
  97. package/dist/types/components/email/mrd-email-view/mrd-email-view.d.ts +52 -0
  98. package/dist/types/components/fields/file-upload-common.d.ts +19 -1
  99. package/dist/types/components/fields/mrd-file-field/mrd-file-field.d.ts +24 -10
  100. package/dist/types/components/fields/mrd-image-field/mrd-image-field.d.ts +27 -11
  101. package/dist/types/components/fields/mrd-textarea-field/mrd-textarea-field.d.ts +1 -0
  102. package/dist/types/components/form/mrd-form/mrd-form.d.ts +16 -1
  103. package/dist/types/components/invoice/mrd-invoice-view/mrd-invoice-view.d.ts +60 -0
  104. package/dist/types/components/layout/mrd-layout-section/mrd-layout-section.d.ts +13 -0
  105. package/dist/types/components/merge/mrd-merge-view/merge-diff.d.ts +43 -0
  106. package/dist/types/components/merge/mrd-merge-view/merge-parts.d.ts +37 -0
  107. package/dist/types/components/merge/mrd-merge-view/mrd-merge-view.d.ts +68 -0
  108. package/dist/types/components.d.ts +445 -19
  109. package/dist/types/types/client-layout.d.ts +6 -0
  110. package/dist/types/utils/document-attachments.d.ts +12 -5
  111. package/package.json +2 -1
  112. package/dist/mosterdcomponents/p-DiSe6vjv.js +0 -2
  113. package/dist/mosterdcomponents/p-cdecc698.entry.js +0 -1
@@ -89,6 +89,7 @@
89
89
  width: 100%;
90
90
  }
91
91
  .mrd-image-field__preview-thumb {
92
+ position: relative;
92
93
  flex-shrink: 0;
93
94
  width: 80px;
94
95
  height: 80px;
@@ -159,6 +160,49 @@
159
160
  background-color: var(--mrd-color-danger);
160
161
  color: var(--mrd-color-white);
161
162
  }
163
+ .mrd-image-field__grid {
164
+ list-style: none;
165
+ display: flex;
166
+ flex-wrap: wrap;
167
+ gap: var(--mrd-space-3);
168
+ margin: var(--mrd-space-2) 0 0;
169
+ padding: 0;
170
+ }
171
+ .mrd-image-field__grid-item {
172
+ position: relative;
173
+ display: flex;
174
+ flex-direction: column;
175
+ align-items: center;
176
+ gap: var(--mrd-space-1);
177
+ width: 88px;
178
+ }
179
+ .mrd-image-field__grid-name {
180
+ font-family: var(--mrd-font-family);
181
+ font-size: var(--mrd-font-size-xs);
182
+ color: var(--mrd-color-neutral-600);
183
+ max-width: 100%;
184
+ overflow: hidden;
185
+ text-overflow: ellipsis;
186
+ white-space: nowrap;
187
+ }
188
+ .mrd-image-field__grid-remove {
189
+ position: absolute;
190
+ top: -6px;
191
+ right: -6px;
192
+ width: 20px;
193
+ height: 20px;
194
+ line-height: 1;
195
+ background-color: var(--mrd-color-white);
196
+ color: var(--mrd-color-neutral-500);
197
+ border: var(--mrd-border-width) solid var(--mrd-border-color);
198
+ border-radius: 50%;
199
+ cursor: pointer;
200
+ font-size: var(--mrd-font-size-xs);
201
+ }
202
+ .mrd-image-field__grid-remove:hover {
203
+ color: var(--mrd-color-danger);
204
+ border-color: var(--mrd-color-danger);
205
+ }
162
206
 
163
207
  @keyframes mrd-spin {
164
208
  to {
@@ -1,7 +1,7 @@
1
1
  import { Host, h } from "@stencil/core";
2
2
  import { t } from "../../../utils/i18n";
3
3
  import { FieldLabel, FieldError } from "../field-helpers";
4
- import { validateUploadFile, uploadZoneClass, makeDragHandlers } from "../file-upload-common";
4
+ import { isPending, entryLabel, validateUploadFile, uploadZoneClass, makeDragHandlers } from "../file-upload-common";
5
5
  export class MrdImageField {
6
6
  constructor() {
7
7
  this.name = '';
@@ -12,79 +12,125 @@ export class MrdImageField {
12
12
  this.locale = navigator.language;
13
13
  this.accept = 'image/*';
14
14
  this.maxSize = 0;
15
- this.previewUrl = '';
16
- this.fileName = '';
15
+ this.multiple = false;
16
+ /** Working copy of the field's files, kept in sync with `value`. Entries still
17
+ * a `File` are pending upload; resolved entries are `{ href, fileName }` (or a
18
+ * bare href string for older hosts). */
19
+ this.entries = [];
17
20
  this.isDragging = false;
18
- this.uploading = false;
19
21
  this.error = '';
22
+ /** Local data-URL previews for pending File entries, keyed by File reference. */
23
+ this.previewCache = new Map();
20
24
  this.handleInputChange = (e) => {
21
25
  var _a;
22
- const files = e.target.files;
23
- this.handleFile((_a = files === null || files === void 0 ? void 0 : files[0]) !== null && _a !== void 0 ? _a : null);
26
+ const input = e.target;
27
+ this.addFiles(Array.from((_a = input.files) !== null && _a !== void 0 ? _a : []));
28
+ input.value = '';
24
29
  };
25
- this.drag = makeDragHandlers(dragging => (this.isDragging = dragging), file => this.handleFile(file));
30
+ this.drag = makeDragHandlers(dragging => (this.isDragging = dragging), files => this.addFiles(files));
26
31
  this.handleZoneClick = () => {
27
32
  var _a;
28
- if (!this.disabled && !this.uploading) {
33
+ if (!this.disabled && !this.zoneBusy) {
29
34
  (_a = this.fileInputRef) === null || _a === void 0 ? void 0 : _a.click();
30
35
  }
31
36
  };
32
- this.handleClear = (e) => {
37
+ this.handleRemove = (index) => (e) => {
33
38
  e.stopPropagation();
34
- this.previewUrl = '';
35
- this.fileName = '';
39
+ const removed = this.entries[index];
40
+ if (removed instanceof File)
41
+ this.previewCache.delete(removed);
42
+ const next = this.entries.filter((_, i) => i !== index);
43
+ this.entries = next;
36
44
  this.error = '';
37
- this.uploading = false;
38
45
  if (this.fileInputRef)
39
46
  this.fileInputRef.value = '';
40
- this.mrdChange.emit({ name: this.name, value: null });
47
+ this.emitValue(next);
41
48
  };
42
49
  }
43
- /** When the host provides a URI back via setFieldValue, the upload is done. */
44
- valueChanged(newVal) {
45
- if (typeof newVal === 'string' && newVal) {
46
- this.uploading = false;
47
- }
48
- else if (!newVal) {
49
- this.uploading = false;
50
- this.previewUrl = '';
51
- this.fileName = '';
52
- }
50
+ componentWillLoad() {
51
+ this.syncEntriesFromValue();
53
52
  }
54
- handleFile(file) {
55
- if (!file) {
56
- this.previewUrl = '';
57
- this.fileName = '';
58
- this.uploading = false;
59
- this.mrdChange.emit({ name: this.name, value: null });
60
- return;
53
+ /** The host (via mrd-form.setFieldValue) is the source of truth once a value round-trips back. */
54
+ valueChanged() {
55
+ this.syncEntriesFromValue();
56
+ }
57
+ syncEntriesFromValue() {
58
+ const v = this.value;
59
+ if (this.multiple) {
60
+ this.entries = Array.isArray(v) ? [...v] : v ? [v] : [];
61
61
  }
62
- const uploadError = validateUploadFile(file, this.maxSize, this.locale, true);
63
- if (uploadError) {
64
- this.error = uploadError;
65
- return;
62
+ else {
63
+ this.entries = v ? [v] : [];
66
64
  }
67
- this.error = '';
68
- this.fileName = file.name;
69
- this.uploading = true;
70
- // Show local preview immediately while upload is in progress
65
+ }
66
+ emitValue(entries) {
67
+ var _a;
68
+ const value = this.multiple ? entries : (_a = entries[0]) !== null && _a !== void 0 ? _a : null;
69
+ this.mrdChange.emit({ name: this.name, value: value });
70
+ }
71
+ readPreview(file) {
71
72
  const reader = new FileReader();
72
- reader.onload = (ev) => {
73
+ reader.onload = ev => {
73
74
  var _a;
74
- this.previewUrl = (_a = ev.target) === null || _a === void 0 ? void 0 : _a.result;
75
+ this.previewCache.set(file, (_a = ev.target) === null || _a === void 0 ? void 0 : _a.result);
76
+ this.entries = [...this.entries]; // trigger re-render now that the preview is ready
75
77
  };
76
78
  reader.readAsDataURL(file);
77
- this.mrdChange.emit({ name: this.name, value: file });
78
- this.mrdUpload.emit({ name: this.name, file });
79
+ }
80
+ addFiles(files) {
81
+ if (this.disabled || this.zoneBusy)
82
+ return;
83
+ const candidates = this.multiple ? files : files.slice(0, 1);
84
+ if (!candidates.length)
85
+ return;
86
+ const accepted = [];
87
+ let uploadError = '';
88
+ for (const file of candidates) {
89
+ const err = validateUploadFile(file, this.maxSize, this.locale, true);
90
+ if (err) {
91
+ uploadError = err;
92
+ continue;
93
+ }
94
+ accepted.push(file);
95
+ }
96
+ this.error = uploadError;
97
+ if (!accepted.length)
98
+ return;
99
+ const next = this.multiple ? [...this.entries, ...accepted] : [accepted[0]];
100
+ this.entries = next;
101
+ this.emitValue(next);
102
+ for (const file of accepted) {
103
+ this.readPreview(file);
104
+ this.mrdUpload.emit({ name: this.name, file });
105
+ }
106
+ }
107
+ /** In single mode, block re-selection while the one image is still uploading;
108
+ * in multiple mode, more images can always be added alongside pending uploads. */
109
+ get zoneBusy() {
110
+ return !this.multiple && this.entries.some(isPending);
111
+ }
112
+ renderThumb(entry) {
113
+ const uploading = isPending(entry);
114
+ const previewUrl = uploading ? this.previewCache.get(entry) : null;
115
+ return (h("div", { class: "mrd-image-field__preview-thumb" }, previewUrl && h("img", { class: "mrd-image-field__preview", src: previewUrl, alt: entryLabel(entry) }), uploading && (h("div", { class: "mrd-image-field__preview-overlay" }, h("span", { class: "mrd-image-field__spinner", "aria-label": t('loading', this.locale) })))));
116
+ }
117
+ renderSingle(entry) {
118
+ const uploading = isPending(entry);
119
+ return (h("div", { class: "mrd-image-field__preview-container" }, this.renderThumb(entry), h("div", { class: "mrd-image-field__preview-info" }, h("span", { class: "mrd-image-field__preview-name" }, entryLabel(entry)), uploading && h("span", { class: "mrd-image-field__upload-status" }, t('loading', this.locale))), !uploading && (h("button", { class: "mrd-image-field__clear", type: "button", onClick: this.handleRemove(0), "aria-label": t('remove', this.locale) }, t('remove', this.locale)))));
120
+ }
121
+ renderGridItem(entry, index) {
122
+ const uploading = isPending(entry);
123
+ return (h("li", { class: "mrd-image-field__grid-item", key: `${index}-${entryLabel(entry)}` }, this.renderThumb(entry), h("span", { class: "mrd-image-field__grid-name" }, entryLabel(entry)), !uploading && (h("button", { class: "mrd-image-field__grid-remove", type: "button", onClick: this.handleRemove(index), "aria-label": t('remove', this.locale) }, "\u2715"))));
79
124
  }
80
125
  render() {
81
126
  const hasError = !!this.error;
127
+ const hasEntries = this.entries.length > 0;
82
128
  const zoneClass = uploadZoneClass('mrd-image-field', {
83
129
  dragging: this.isDragging,
84
130
  error: hasError,
85
- disabled: this.disabled || this.uploading,
131
+ disabled: this.disabled || this.zoneBusy,
86
132
  });
87
- return (h(Host, { key: '700310af716dcc167f98bc7ae7024bb44e1285b6' }, h("div", { key: '95d29a82751249e560404488fb279138eee0c1be', class: "mrd-image-field" }, h(FieldLabel, { key: 'f53f040257a4b65d8435c836846adab137b91a5e', base: "mrd-image-field", label: this.label, required: this.required }), h("div", { key: 'a9cc7a0861b1457d8e4abb11b380b6eeb519a863', class: zoneClass, onClick: this.handleZoneClick, onDragOver: this.drag.onDragOver, onDragLeave: this.drag.onDragLeave, onDrop: this.drag.onDrop }, h("input", { key: '7112030bc2b80b4c3b31a4421656da4c926a2fe1', ref: el => (this.fileInputRef = el), class: "mrd-image-field__input", type: "file", name: this.name, accept: this.accept, disabled: this.disabled || this.uploading, required: this.required && !this.previewUrl, onChange: this.handleInputChange }), this.previewUrl ? (h("div", { class: "mrd-image-field__preview-container" }, h("div", { class: "mrd-image-field__preview-thumb" }, h("img", { class: "mrd-image-field__preview", src: this.previewUrl, alt: this.fileName }), this.uploading && h("div", { class: "mrd-image-field__preview-overlay" }, h("span", { class: "mrd-image-field__spinner" }))), h("div", { class: "mrd-image-field__preview-info" }, h("span", { class: "mrd-image-field__preview-name" }, this.fileName), this.uploading && (h("span", { class: "mrd-image-field__upload-status" }, t('loading', this.locale)))), !this.uploading && (h("button", { class: "mrd-image-field__clear", type: "button", onClick: this.handleClear, "aria-label": t('clear', this.locale) }, t('remove', this.locale))))) : (h("div", { class: "mrd-image-field__prompt" }, h("svg", { class: "mrd-image-field__upload-icon", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2" }, h("rect", { x: "3", y: "3", width: "18", height: "18", rx: "2", ry: "2" }), h("circle", { cx: "8.5", cy: "8.5", r: "1.5" }), h("polyline", { points: "21 15 16 10 5 21" })), h("span", null, t('drop_file_here', this.locale), ' ', h("span", { class: "mrd-image-field__browse" }, t('browse', this.locale)))))), h(FieldError, { key: '0cf5346ecb1a5d110822b087081b4b5f05255381', base: "mrd-image-field", error: this.error }))));
133
+ return (h(Host, { key: '51d39ea80195565a5dc780ff6a7923a976cfb0a7' }, h("div", { key: 'd76edb7ac9f1efea8c7c3d5b04e906ad4ab4c761', class: "mrd-image-field" }, h(FieldLabel, { key: 'b46a67a76188047677c40ce3f406daed7d8de141', base: "mrd-image-field", label: this.label, required: this.required }), h("div", { key: '0156a9722ea48d84e3ea1a829e4266425a594a6a', class: zoneClass, onClick: this.handleZoneClick, onDragOver: this.drag.onDragOver, onDragLeave: this.drag.onDragLeave, onDrop: this.drag.onDrop }, h("input", { key: '1b8e41da71bb57254bfd2e5527b05e1aeb32eb2f', ref: el => (this.fileInputRef = el), class: "mrd-image-field__input", type: "file", name: this.name, accept: this.accept, multiple: this.multiple, disabled: this.disabled || this.zoneBusy, required: this.required && !hasEntries, onChange: this.handleInputChange }), hasEntries && !this.multiple ? (this.renderSingle(this.entries[0])) : (h("div", { class: "mrd-image-field__prompt" }, h("svg", { class: "mrd-image-field__upload-icon", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2" }, h("rect", { x: "3", y: "3", width: "18", height: "18", rx: "2", ry: "2" }), h("circle", { cx: "8.5", cy: "8.5", r: "1.5" }), h("polyline", { points: "21 15 16 10 5 21" })), h("span", null, t('drop_file_here', this.locale), ' ', h("span", { class: "mrd-image-field__browse" }, t('browse', this.locale)))))), this.multiple && hasEntries && (h("ul", { key: '9211b5212eae0629f7c9b2b49d80e2b658a9ddd3', class: "mrd-image-field__grid" }, this.entries.map((entry, index) => this.renderGridItem(entry, index)))), h(FieldError, { key: '5f49c03156ed18e7c8307adff679b149658ca4a6', base: "mrd-image-field", error: this.error }))));
88
134
  }
89
135
  static get is() { return "mrd-image-field"; }
90
136
  static get encapsulation() { return "scoped"; }
@@ -257,15 +303,33 @@ export class MrdImageField {
257
303
  "reflect": false,
258
304
  "attribute": "max-size",
259
305
  "defaultValue": "0"
306
+ },
307
+ "multiple": {
308
+ "type": "boolean",
309
+ "mutable": false,
310
+ "complexType": {
311
+ "original": "boolean",
312
+ "resolved": "boolean",
313
+ "references": {}
314
+ },
315
+ "required": false,
316
+ "optional": false,
317
+ "docs": {
318
+ "tags": [],
319
+ "text": ""
320
+ },
321
+ "getter": false,
322
+ "setter": false,
323
+ "reflect": false,
324
+ "attribute": "multiple",
325
+ "defaultValue": "false"
260
326
  }
261
327
  };
262
328
  }
263
329
  static get states() {
264
330
  return {
265
- "previewUrl": {},
266
- "fileName": {},
331
+ "entries": {},
267
332
  "isDragging": {},
268
- "uploading": {},
269
333
  "error": {}
270
334
  };
271
335
  }
@@ -281,12 +345,14 @@ export class MrdImageField {
281
345
  "text": ""
282
346
  },
283
347
  "complexType": {
284
- "original": "{ name: string; value: File | null }",
285
- "resolved": "{ name: string; value: File | null; }",
348
+ "original": "{ name: string; value: FileEntry | FileEntry[] | null }",
349
+ "resolved": "{ name: string; value: FileEntry | FileEntry[] | null; }",
286
350
  "references": {
287
- "File": {
288
- "location": "global",
289
- "id": "global::File"
351
+ "FileEntry": {
352
+ "location": "import",
353
+ "path": "../file-upload-common",
354
+ "id": "src/components/fields/file-upload-common.ts::FileEntry",
355
+ "referenceLocation": "FileEntry"
290
356
  }
291
357
  }
292
358
  }
@@ -301,12 +367,14 @@ export class MrdImageField {
301
367
  "text": ""
302
368
  },
303
369
  "complexType": {
304
- "original": "{ name: string; value: File | null }",
305
- "resolved": "{ name: string; value: File | null; }",
370
+ "original": "{ name: string; value: FileEntry | FileEntry[] | null }",
371
+ "resolved": "{ name: string; value: FileEntry | FileEntry[] | null; }",
306
372
  "references": {
307
- "File": {
308
- "location": "global",
309
- "id": "global::File"
373
+ "FileEntry": {
374
+ "location": "import",
375
+ "path": "../file-upload-common",
376
+ "id": "src/components/fields/file-upload-common.ts::FileEntry",
377
+ "referenceLocation": "FileEntry"
310
378
  }
311
379
  }
312
380
  }
@@ -318,7 +386,7 @@ export class MrdImageField {
318
386
  "composed": true,
319
387
  "docs": {
320
388
  "tags": [],
321
- "text": "Emitted when an image is selected and needs to be uploaded.\nHost should upload the file and call form.setFieldValue(name, uri) with the result."
389
+ "text": "Emitted once per selected/dropped image that needs to be uploaded.\nHost should upload the file and call form.setFieldValue(name, { href, fileName: file.name }, file)\nwith the result \u2014 passing `file` back lets mrd-form resolve the right entry in a `multiple` field."
322
390
  },
323
391
  "complexType": {
324
392
  "original": "{ name: string; file: File }",
@@ -21,7 +21,7 @@ export class MrdLongtextField {
21
21
  };
22
22
  }
23
23
  render() {
24
- return (h(Host, { key: 'b27d14772f5184d41c178606629a9945120296ef' }, h("div", { key: '206b1b9ee1d37e05680e31bf8b324455867fd93b', class: "mrd-longtext-field" }, h(FieldLabel, { key: '0b6f4959f32a097907965753ed8708d7580aee53', base: "mrd-longtext-field", label: this.label, required: this.required }), this.disabled ? (h("pre", { class: "mrd-longtext-field__content" }, this.value)) : (h("textarea", { class: controlClass('mrd-longtext-field', !!this.error), name: this.name, placeholder: this.placeholder, required: this.required, rows: 10, onInput: this.handleInput, onBlur: this.handleBlur }, this.value)), h(FieldError, { key: '0803cf0c1b6409e6c8a65121efef97d28d870119', base: "mrd-longtext-field", error: this.error }))));
24
+ return (h(Host, { key: 'e53fec4d4eafbd68d9cffb1f064d8e0c4c37839c' }, h("div", { key: '8843a23e42b0545c269fc6c924e7ae295770064e', class: "mrd-longtext-field" }, h(FieldLabel, { key: '8767676dd35f25f9bf5ebdb26887edc0882582e0', base: "mrd-longtext-field", label: this.label, required: this.required }), this.disabled ? (h("pre", { class: "mrd-longtext-field__content" }, this.value)) : (h("textarea", { class: controlClass('mrd-longtext-field', !!this.error), name: this.name, placeholder: this.placeholder, required: this.required, rows: 10, onInput: this.handleInput, onBlur: this.handleBlur }, this.value)), h(FieldError, { key: '54dc790173575e8a02764e5b14040b900c2eb609', base: "mrd-longtext-field", error: this.error }))));
25
25
  }
26
26
  static get is() { return "mrd-longtext-field"; }
27
27
  static get encapsulation() { return "scoped"; }
@@ -70,7 +70,7 @@ export class MrdNumberField {
70
70
  const hasError = !!this.error;
71
71
  const suffix = this.dataType === ClientLayoutItemFieldDataType.PERCENTAGE ? '%' :
72
72
  this.dataType === ClientLayoutItemFieldDataType.DECIMAL ? '' : '';
73
- return (h(Host, { key: '90cae99b332f9554bc80d4044fce892ae37a48cf' }, h("div", { key: '26a9cfcc662eaeb571d17785f10f74269cf78537', class: "mrd-number-field" }, h(FieldLabel, { key: '0d9e70c1591f309ad2875d9e9fe6dbb8aa910323', base: "mrd-number-field", label: this.label, required: this.required }), h("div", { key: '95e9b53fa73c452491f65355915bf04912900ded', class: "mrd-number-field__input-wrapper" }, h("input", { key: '1d341c270a21b1a10c51757e90257e48e741abd7', class: controlClass('mrd-number-field', hasError), type: "text", inputMode: "decimal", name: this.name, value: this.displayValue, placeholder: this.placeholder || (suffix ? `0${suffix}` : '0'), required: this.required, disabled: this.disabled, onInput: this.handleInput, onBlur: this.handleBlur, onFocus: this.handleFocus })), h(FieldError, { key: '0dba75e0f7355b71bfa71dab16cd1a6fcc36922f', base: "mrd-number-field", error: this.error }))));
73
+ return (h(Host, { key: '5df5c2e16c85cd54944d95b7a8c340ae1a4ddb34' }, h("div", { key: 'e332bd0190d537c002b14090fa96ec6502e34746', class: "mrd-number-field" }, h(FieldLabel, { key: 'e26ae85ef6c6d7935b2d34041a7ee2294da392a4', base: "mrd-number-field", label: this.label, required: this.required }), h("div", { key: 'fb1a44a363a36b69124a0092e80b67d41b538c03', class: "mrd-number-field__input-wrapper" }, h("input", { key: '175c448177bbd46015fc7f47b98b76e16ee3b743', class: controlClass('mrd-number-field', hasError), type: "text", inputMode: "decimal", name: this.name, value: this.displayValue, placeholder: this.placeholder || (suffix ? `0${suffix}` : '0'), required: this.required, disabled: this.disabled, onInput: this.handleInput, onBlur: this.handleBlur, onFocus: this.handleFocus })), h(FieldError, { key: 'ecb16ddf655e563f0569e774ab2f2e419f60b0f1', base: "mrd-number-field", error: this.error }))));
74
74
  }
75
75
  static get is() { return "mrd-number-field"; }
76
76
  static get encapsulation() { return "scoped"; }
@@ -21,9 +21,9 @@ export class MrdSecretField {
21
21
  };
22
22
  }
23
23
  render() {
24
- return (h(Host, { key: '9883a87b7fcc840aa4bbe0a3cde163cc58bda65d' }, h("div", { key: '3c7ec45d59a1a17863daa39b7c115db5c58815ee', class: "mrd-secret-field" }, h(FieldLabel, { key: '2dda66c7e6d4946661bff5a5a46589a7e17f569a', base: "mrd-secret-field", label: this.label, required: this.required }), this.disabled
24
+ return (h(Host, { key: 'e06d04fdae34b42441976e21605fe94ad935382e' }, h("div", { key: '2889e1eb1b6d0887ae531523a0b9b43c49ef6f99', class: "mrd-secret-field" }, h(FieldLabel, { key: 'c40907905292cdef8bb0b8b9b0c03e05650bef37', base: "mrd-secret-field", label: this.label, required: this.required }), this.disabled
25
25
  ? (h("span", { class: "mrd-secret-field__masked", "aria-label": this.label }, this.value ? '••••••••' : ''))
26
- : (h("input", { class: controlClass('mrd-secret-field', !!this.error), type: "password", name: this.name, value: this.value, placeholder: this.placeholder, required: this.required, autocomplete: "new-password", onInput: this.handleInput, onBlur: this.handleBlur })), h(FieldError, { key: 'ad8f54a7c76c59591fee77e11a12bbd62c392d91', base: "mrd-secret-field", error: this.error }))));
26
+ : (h("input", { class: controlClass('mrd-secret-field', !!this.error), type: "password", name: this.name, value: this.value, placeholder: this.placeholder, required: this.required, autocomplete: "new-password", onInput: this.handleInput, onBlur: this.handleBlur })), h(FieldError, { key: '6353e4b40566852dbc1994c7c9c409d0d0aa1b37', base: "mrd-secret-field", error: this.error }))));
27
27
  }
28
28
  static get is() { return "mrd-secret-field"; }
29
29
  static get encapsulation() { return "scoped"; }
@@ -21,7 +21,7 @@ export class MrdTextField {
21
21
  };
22
22
  }
23
23
  render() {
24
- return (h(Host, { key: 'd2a6fb5735a3a01887cc467274a0ff9d71312124' }, h("div", { key: '7864bf852d43f93ebd0120a3ec1b72e54837d1ba', class: "mrd-text-field" }, h(FieldLabel, { key: 'bdad112197b44a643a58dd79c4648e284311199d', base: "mrd-text-field", label: this.label, required: this.required }), h("input", { key: '1d3e1cf3374ea2e5a83794b5110205964fff86e1', class: controlClass('mrd-text-field', !!this.error), type: "text", name: this.name, value: this.value, placeholder: this.placeholder, required: this.required, disabled: this.disabled, onInput: this.handleInput, onBlur: this.handleBlur }), h(FieldError, { key: 'bb4ec51de7b6d360fdb3d46dd53e85a5d497bcce', base: "mrd-text-field", error: this.error }))));
24
+ return (h(Host, { key: '3153f0c6f8ab4d9dae93306f7507399aeda75eaf' }, h("div", { key: '5d9c28023fdfb5472fc115c66bc3df509506e10f', class: "mrd-text-field" }, h(FieldLabel, { key: '1e21139aae0cc1ae95aa3297271b618a40043e77', base: "mrd-text-field", label: this.label, required: this.required }), h("input", { key: '46f98d5e39bb834075449fac7096b077a1ae7bb3', class: controlClass('mrd-text-field', !!this.error), type: "text", name: this.name, value: this.value, placeholder: this.placeholder, required: this.required, disabled: this.disabled, onInput: this.handleInput, onBlur: this.handleBlur }), h(FieldError, { key: '10b6ddae9cd9f36dc9d7176167d79df8e4102c03', base: "mrd-text-field", error: this.error }))));
25
25
  }
26
26
  static get is() { return "mrd-text-field"; }
27
27
  static get encapsulation() { return "scoped"; }
@@ -1,4 +1,5 @@
1
1
  import { Host, h } from "@stencil/core";
2
+ import DOMPurify from "dompurify";
2
3
  import { FieldLabel, FieldError, controlClass, requiredError } from "../field-helpers";
3
4
  export class MrdTextareaField {
4
5
  constructor() {
@@ -40,14 +41,12 @@ export class MrdTextareaField {
40
41
  quill.clipboard.dangerouslyPasteHTML(this.value);
41
42
  }
42
43
  quill.on('text-change', () => {
43
- var _a, _b;
44
- const html = (_b = (_a = this.editorEl.querySelector('.ql-editor')) === null || _a === void 0 ? void 0 : _a.innerHTML) !== null && _b !== void 0 ? _b : '';
44
+ const html = this.getSanitizedHtml();
45
45
  this.mrdChange.emit({ name: this.name, value: html });
46
46
  });
47
47
  quill.on('selection-change', (range) => {
48
- var _a, _b;
49
48
  if (!range) {
50
- const html = (_b = (_a = this.editorEl.querySelector('.ql-editor')) === null || _a === void 0 ? void 0 : _a.innerHTML) !== null && _b !== void 0 ? _b : '';
49
+ const html = this.getSanitizedHtml();
51
50
  this.error = requiredError(html, this.required, this.locale);
52
51
  this.mrdBlur.emit({ name: this.name, value: html });
53
52
  }
@@ -59,8 +58,13 @@ export class MrdTextareaField {
59
58
  console.error('Failed to load Quill editor:', err);
60
59
  }
61
60
  }
61
+ getSanitizedHtml() {
62
+ var _a, _b;
63
+ const html = (_b = (_a = this.editorEl.querySelector('.ql-editor')) === null || _a === void 0 ? void 0 : _a.innerHTML) !== null && _b !== void 0 ? _b : '';
64
+ return DOMPurify.sanitize(html);
65
+ }
62
66
  render() {
63
- return (h(Host, { key: 'de39890fc23146b880e69be7a8f04c91786ae9c9' }, h("div", { key: '0ccc855f3ee44553559a9ea8a70690466789f76d', class: "mrd-textarea-field" }, h(FieldLabel, { key: '2a16dcbeedbba8b5d8d1e8e8c0f021f14813f609', base: "mrd-textarea-field", label: this.label, required: this.required }), h("div", { key: '979ecb0ed163e3f753d75635452eea49d76edf29', class: controlClass('mrd-textarea-field', !!this.error, 'container') }, h("div", { key: '8ea96a080086b68df55017a1464f6bce502f9a58', class: "mrd-textarea-field__editor" })), h(FieldError, { key: 'd6ac1a12f7be3594ed8cdb6d94b304c46dc8285b', base: "mrd-textarea-field", error: this.error }))));
67
+ return (h(Host, { key: 'e132531881f68f373e700617aac8033c8ebccb47' }, h("div", { key: 'fec0cf56cd7868d3fcd5a4e8a382b2dcdb6e5c50', class: "mrd-textarea-field" }, h(FieldLabel, { key: '333aa2ff57cce9e3178f0c4be4a1df1077884248', base: "mrd-textarea-field", label: this.label, required: this.required }), h("div", { key: '40025207580eb5768981b85aaa0a8ff95fce7234', class: controlClass('mrd-textarea-field', !!this.error, 'container') }, h("div", { key: '621987b3b2b1d8b4836bff065075ba527a82db81', class: "mrd-textarea-field__editor" })), h(FieldError, { key: 'ac2d8de22f008cd1c91842137ccd0cd36eb9ce19', base: "mrd-textarea-field", error: this.error }))));
64
68
  }
65
69
  static get is() { return "mrd-textarea-field"; }
66
70
  static get encapsulation() { return "scoped"; }
@@ -20,7 +20,7 @@ export class MrdTimeField {
20
20
  };
21
21
  }
22
22
  render() {
23
- return (h(Host, { key: '588e298c4f52aa4e65d87bfdc6918282ed97b3ae' }, h("div", { key: 'eed7332ca78747d4ff8b6c8d06ad4674e1c37e69', class: "mrd-time-field" }, h(FieldLabel, { key: '6383beb79fd05dabdc408256e3326f9637e4c993', base: "mrd-time-field", label: this.label, required: this.required }), h("input", { key: 'b372d8b5dd523ee6182fe302450ce904ac17cc3c', class: controlClass('mrd-time-field', !!this.error), type: "time", name: this.name, value: this.value, required: this.required, disabled: this.disabled, onChange: this.handleChange, onBlur: this.handleBlur }), h(FieldError, { key: '7d8ce1e557bf583c0409e8f02893a15fadeab634', base: "mrd-time-field", error: this.error }))));
23
+ return (h(Host, { key: 'd521c654bc7beda71a31542a25198edaaf53b838' }, h("div", { key: '084ab16ac8aed952bd0ec50e0430591d4db5bd4b', class: "mrd-time-field" }, h(FieldLabel, { key: '43938ad7c5a9765353f6523d399d4b74cbb9ec6f', base: "mrd-time-field", label: this.label, required: this.required }), h("input", { key: 'e46eda620e7a5f1bb3b433a4c051e913675c33f9', class: controlClass('mrd-time-field', !!this.error), type: "time", name: this.name, value: this.value, required: this.required, disabled: this.disabled, onChange: this.handleChange, onBlur: this.handleBlur }), h(FieldError, { key: '34bc1eaa1b969951dffaaacad73af532b6d290fb', base: "mrd-time-field", error: this.error }))));
24
24
  }
25
25
  static get is() { return "mrd-time-field"; }
26
26
  static get encapsulation() { return "scoped"; }
@@ -111,7 +111,7 @@ export class MrdField {
111
111
  return (h("div", { class: "mrd-field__history-editor" }, this.historyEntries.length > 0 && (h("span", { class: "mrd-field__history-editor-label" }, t('history_badge_tooltip', locale))), this.historyEntries.map((entry, i) => (h("div", { key: String(i), class: "mrd-field__history-editor-row" }, h("input", { class: "mrd-field__history-editor-value", type: valueType, value: entry.value, onInput: (e) => updateEntry(i, 'value', e.target.value) }), h("span", { class: "mrd-field__history-editor-sep" }, t('history_until', locale)), h("input", { class: "mrd-field__history-editor-until", type: "date", value: entry.until, onInput: (e) => updateEntry(i, 'until', e.target.value) }), h("button", { type: "button", class: "mrd-field__history-editor-remove", onClick: () => removeEntry(i), "aria-label": t('remove', locale) }, h("svg", { viewBox: "0 0 20 20", fill: "currentColor", "aria-hidden": "true" }, h("path", { "fill-rule": "evenodd", d: "M8.75 1A2.75 2.75 0 006 3.75v.443c-.795.077-1.584.176-2.365.298a.75.75 0 10.23 1.482l.149-.022.841 10.518A2.75 2.75 0 007.596 19h4.807a2.75 2.75 0 002.742-2.53l.841-10.52.149.023a.75.75 0 00.23-1.482A41.03 41.03 0 0014 4.193V3.75A2.75 2.75 0 0011.25 1h-2.5zM10 4c.84 0 1.673.025 2.5.075V3.75c0-.69-.56-1.25-1.25-1.25h-2.5c-.69 0-1.25.56-1.25 1.25v.325C8.327 4.025 9.16 4 10 4zM8.58 7.72a.75.75 0 00-1.5.06l.3 7.5a.75.75 0 101.5-.06l-.3-7.5zm4.34.06a.75.75 0 10-1.5-.06l-.3 7.5a.75.75 0 101.5.06l.3-7.5z", "clip-rule": "evenodd" })))))), h("button", { type: "button", class: "mrd-field__history-editor-add", onClick: addEntry }, "+ ", t('add', locale))));
112
112
  }
113
113
  renderLeafField(displayValue) {
114
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z, _0, _1, _2, _3, _4;
114
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z, _0, _1, _2, _3, _4, _5, _6;
115
115
  const { item, locale } = this;
116
116
  const commonProps = {
117
117
  name: item.name,
@@ -148,13 +148,13 @@ export class MrdField {
148
148
  case ClientLayoutItemFieldDataType.LIST:
149
149
  return (h("mrd-list-field", Object.assign({}, commonProps, { value: (_u = displayValue) !== null && _u !== void 0 ? _u : '', multiple: (_v = item.multiple) !== null && _v !== void 0 ? _v : false, listItems: (_w = item.listItems) !== null && _w !== void 0 ? _w : [] })));
150
150
  case ClientLayoutItemFieldDataType.FILE:
151
- return (h("mrd-file-field", Object.assign({}, commonProps, { value: displayValue, accept: (_x = item.accept) !== null && _x !== void 0 ? _x : '', maxSize: (_y = item.maxSize) !== null && _y !== void 0 ? _y : 0, onMrdUpload: this.handleUpload })));
151
+ return (h("mrd-file-field", Object.assign({}, commonProps, { value: displayValue, accept: (_x = item.accept) !== null && _x !== void 0 ? _x : '', maxSize: (_y = item.maxSize) !== null && _y !== void 0 ? _y : 0, multiple: (_z = item.multiple) !== null && _z !== void 0 ? _z : false, onMrdUpload: this.handleUpload })));
152
152
  case ClientLayoutItemFieldDataType.IMAGE:
153
- return (h("mrd-image-field", Object.assign({}, commonProps, { value: displayValue, accept: (_z = item.accept) !== null && _z !== void 0 ? _z : 'image/*', maxSize: (_0 = item.maxSize) !== null && _0 !== void 0 ? _0 : 0, onMrdUpload: this.handleUpload })));
153
+ return (h("mrd-image-field", Object.assign({}, commonProps, { value: displayValue, accept: (_0 = item.accept) !== null && _0 !== void 0 ? _0 : 'image/*', maxSize: (_1 = item.maxSize) !== null && _1 !== void 0 ? _1 : 0, multiple: (_2 = item.multiple) !== null && _2 !== void 0 ? _2 : false, onMrdUpload: this.handleUpload })));
154
154
  case ClientLayoutItemFieldDataType.LONGTEXT:
155
- return (h("mrd-longtext-field", Object.assign({}, commonProps, { value: (_1 = displayValue) !== null && _1 !== void 0 ? _1 : '', placeholder: (_2 = item.placeholder) !== null && _2 !== void 0 ? _2 : '' })));
155
+ return (h("mrd-longtext-field", Object.assign({}, commonProps, { value: (_3 = displayValue) !== null && _3 !== void 0 ? _3 : '', placeholder: (_4 = item.placeholder) !== null && _4 !== void 0 ? _4 : '' })));
156
156
  case ClientLayoutItemFieldDataType.SECRET:
157
- return (h("mrd-secret-field", Object.assign({}, commonProps, { value: (_3 = displayValue) !== null && _3 !== void 0 ? _3 : '', placeholder: (_4 = item.placeholder) !== null && _4 !== void 0 ? _4 : '' })));
157
+ return (h("mrd-secret-field", Object.assign({}, commonProps, { value: (_5 = displayValue) !== null && _5 !== void 0 ? _5 : '', placeholder: (_6 = item.placeholder) !== null && _6 !== void 0 ? _6 : '' })));
158
158
  default:
159
159
  return null;
160
160
  }
@@ -1,5 +1,5 @@
1
1
  import { Host, h } from "@stencil/core";
2
- import { ClientLayoutItemType, ClientLayoutItemRelationEditBehavior } from "../../../types";
2
+ import { ClientLayoutItemFieldDataType, ClientLayoutItemType, ClientLayoutItemRelationEditBehavior } from "../../../types";
3
3
  import { t } from "../../../utils/i18n";
4
4
  import { validateRequired } from "../../../utils/validation";
5
5
  export class MrdForm {
@@ -199,7 +199,31 @@ export class MrdForm {
199
199
  }
200
200
  return null;
201
201
  }
202
- async setFieldValue(name, value) {
202
+ /**
203
+ * Inject a value from outside (e.g. after a file upload completes).
204
+ * `file` is optional and only meaningful for `multiple` FILE/IMAGE fields: when
205
+ * the field's current value is an array, mrd-form replaces the array entry that
206
+ * `=== file` with `value`, leaving other pending/resolved entries untouched —
207
+ * this is how mrd-file-field/mrd-image-field resolve concurrent per-file uploads.
208
+ * Pass the `file` from the originating mrdUpload event back unchanged.
209
+ */
210
+ async setFieldValue(name, value, file) {
211
+ if (file) {
212
+ const current = this.formValues[name];
213
+ if (Array.isArray(current)) {
214
+ const index = current.indexOf(file);
215
+ if (index === -1)
216
+ return; // the file was removed from the field before its upload resolved
217
+ const next = [...current];
218
+ next[index] = value;
219
+ this.formValues = Object.assign(Object.assign({}, this.formValues), { [name]: next });
220
+ if (this.errors[name])
221
+ this.errors = Object.assign(Object.assign({}, this.errors), { [name]: '' });
222
+ return;
223
+ }
224
+ if (current !== file)
225
+ return; // stale resolution for a value that was since cleared/replaced
226
+ }
203
227
  this.formValues = Object.assign(Object.assign({}, this.formValues), { [name]: value });
204
228
  if (this.errors[name]) {
205
229
  this.errors = Object.assign(Object.assign({}, this.errors), { [name]: '' });
@@ -269,6 +293,20 @@ export class MrdForm {
269
293
  normalizeFieldValue(value) {
270
294
  return (value === '' || value == null) ? null : value;
271
295
  }
296
+ /** FILE/IMAGE entries render as `{ href, fileName, contentType?, size? }` — the same
297
+ * shape the API returns for an already-persisted file — or a bare href string for
298
+ * freshly-resolved uploads. Only `href` is meaningful to the API, so unwrap before
299
+ * submit (mirrors normalizeRelationValue). Do NOT fall back to any other key (e.g.
300
+ * `id`) as a stand-in for `href` — a bare id is not a valid/complete reference on its
301
+ * own; if `href` is ever missing that's an API bug to fix upstream, not to paper over here. */
302
+ normalizeFileFieldValue(value) {
303
+ const toHref = (v) => v && typeof v === 'object' && 'href' in v ? v.href : v;
304
+ if (Array.isArray(value)) {
305
+ const hrefs = value.map(toHref).filter(v => v != null && v !== '');
306
+ return hrefs.length ? hrefs : null;
307
+ }
308
+ return this.normalizeFieldValue(toHref(value));
309
+ }
272
310
  normalizeRelationValue(value) {
273
311
  if (value == null || value === '')
274
312
  return null;
@@ -315,8 +353,11 @@ export class MrdForm {
315
353
  const rawValue = this.formValues[name];
316
354
  if (rawValue instanceof File)
317
355
  continue;
318
- const current = this.normalizeFieldValue(rawValue);
319
- const initial = this.normalizeFieldValue(this.initialValues[name]);
356
+ if (Array.isArray(rawValue) && rawValue.some(v => v instanceof File))
357
+ continue;
358
+ const isFileField = item.dataType === ClientLayoutItemFieldDataType.FILE || item.dataType === ClientLayoutItemFieldDataType.IMAGE;
359
+ const current = isFileField ? this.normalizeFileFieldValue(rawValue) : this.normalizeFieldValue(rawValue);
360
+ const initial = isFileField ? this.normalizeFileFieldValue(this.initialValues[name]) : this.normalizeFieldValue(this.initialValues[name]);
320
361
  if (this.deepEqual(current, initial))
321
362
  continue;
322
363
  payload[name] = current;
@@ -619,7 +660,7 @@ export class MrdForm {
619
660
  return {
620
661
  "setFieldValue": {
621
662
  "complexType": {
622
- "signature": "(name: string, value: unknown) => Promise<void>",
663
+ "signature": "(name: string, value: unknown, file?: File) => Promise<void>",
623
664
  "parameters": [{
624
665
  "name": "name",
625
666
  "type": "string",
@@ -628,17 +669,25 @@ export class MrdForm {
628
669
  "name": "value",
629
670
  "type": "unknown",
630
671
  "docs": ""
672
+ }, {
673
+ "name": "file",
674
+ "type": "File | undefined",
675
+ "docs": ""
631
676
  }],
632
677
  "references": {
633
678
  "Promise": {
634
679
  "location": "global",
635
680
  "id": "global::Promise"
681
+ },
682
+ "File": {
683
+ "location": "global",
684
+ "id": "global::File"
636
685
  }
637
686
  },
638
687
  "return": "Promise<void>"
639
688
  },
640
689
  "docs": {
641
- "text": "",
690
+ "text": "Inject a value from outside (e.g. after a file upload completes).\n`file` is optional and only meaningful for `multiple` FILE/IMAGE fields: when\nthe field's current value is an array, mrd-form replaces the array entry that\n`=== file` with `value`, leaving other pending/resolved entries untouched \u2014\nthis is how mrd-file-field/mrd-image-field resolve concurrent per-file uploads.\nPass the `file` from the originating mrdUpload event back unchanged.",
642
691
  "tags": []
643
692
  }
644
693
  }