@mmlogic/components 0.1.8 → 0.1.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (26) hide show
  1. package/dist/cjs/loader.cjs.js +1 -1
  2. package/dist/cjs/mosterdcomponents.cjs.js +1 -1
  3. package/dist/cjs/mrd-boolean-field_16.cjs.entry.js +167 -17
  4. package/dist/collection/components/mrd-field/mrd-field.js +26 -2
  5. package/dist/collection/components/mrd-file-field/mrd-file-field.js +70 -2
  6. package/dist/collection/components/mrd-file-field/mrd-file-field.scss +13 -0
  7. package/dist/collection/components/mrd-form/mrd-form.js +187 -8
  8. package/dist/collection/components/mrd-form/mrd-form.scss +32 -0
  9. package/dist/collection/components/mrd-image-field/mrd-image-field.js +71 -2
  10. package/dist/collection/components/mrd-image-field/mrd-image-field.scss +26 -2
  11. package/dist/components/mrd-field2.js +1 -1
  12. package/dist/components/mrd-file-field2.js +1 -1
  13. package/dist/components/mrd-form.js +1 -1
  14. package/dist/components/mrd-image-field2.js +1 -1
  15. package/dist/esm/loader.js +1 -1
  16. package/dist/esm/mosterdcomponents.js +1 -1
  17. package/dist/esm/mrd-boolean-field_16.entry.js +167 -17
  18. package/dist/mosterdcomponents/mosterdcomponents.esm.js +1 -1
  19. package/dist/mosterdcomponents/p-bb567c32.entry.js +1 -0
  20. package/dist/types/components/mrd-field/mrd-field.d.ts +5 -0
  21. package/dist/types/components/mrd-file-field/mrd-file-field.d.ts +10 -0
  22. package/dist/types/components/mrd-form/mrd-form.d.ts +40 -0
  23. package/dist/types/components/mrd-image-field/mrd-image-field.d.ts +10 -0
  24. package/dist/types/components.d.ts +65 -0
  25. package/package.json +1 -1
  26. package/dist/mosterdcomponents/p-5a453e03.entry.js +0 -1
@@ -4,6 +4,7 @@ export class MrdFileField {
4
4
  constructor() {
5
5
  this.name = '';
6
6
  this.label = '';
7
+ this.value = null;
7
8
  this.required = false;
8
9
  this.disabled = false;
9
10
  this.locale = navigator.language;
@@ -11,6 +12,7 @@ export class MrdFileField {
11
12
  this.maxSize = 0; // bytes, 0 = no limit
12
13
  this.fileName = '';
13
14
  this.isDragging = false;
15
+ this.uploading = false;
14
16
  this.error = '';
15
17
  this.handleInputChange = (e) => {
16
18
  var _a;
@@ -33,7 +35,7 @@ export class MrdFileField {
33
35
  };
34
36
  this.handleZoneClick = () => {
35
37
  var _a;
36
- if (!this.disabled) {
38
+ if (!this.disabled && !this.uploading) {
37
39
  (_a = this.fileInputRef) === null || _a === void 0 ? void 0 : _a.click();
38
40
  }
39
41
  };
@@ -41,14 +43,26 @@ export class MrdFileField {
41
43
  e.stopPropagation();
42
44
  this.fileName = '';
43
45
  this.error = '';
46
+ this.uploading = false;
44
47
  if (this.fileInputRef)
45
48
  this.fileInputRef.value = '';
46
49
  this.mrdChange.emit({ name: this.name, value: null });
47
50
  };
48
51
  }
52
+ /** When the host provides a URI back via setFieldValue, the upload is done. */
53
+ valueChanged(newVal) {
54
+ if (typeof newVal === 'string' && newVal) {
55
+ this.uploading = false;
56
+ }
57
+ else if (!newVal) {
58
+ this.uploading = false;
59
+ this.fileName = '';
60
+ }
61
+ }
49
62
  handleFile(file) {
50
63
  if (!file) {
51
64
  this.fileName = '';
65
+ this.uploading = false;
52
66
  this.mrdChange.emit({ name: this.name, value: null });
53
67
  return;
54
68
  }
@@ -58,11 +72,20 @@ export class MrdFileField {
58
72
  }
59
73
  this.error = '';
60
74
  this.fileName = file.name;
75
+ this.uploading = true;
61
76
  this.mrdChange.emit({ name: this.name, value: file });
77
+ this.mrdUpload.emit({ name: this.name, file });
62
78
  }
63
79
  render() {
80
+ const hasFile = this.uploading || (typeof this.value === 'string' && this.value) || this.fileName;
64
81
  const hasError = !!this.error;
65
- return (h(Host, { key: '6617c70edaf193c204a951ff8116d54742592f84' }, h("div", { key: '69a45446da411c1cfdadd9ee7051e3ceea95a2d2', class: "mrd-file-field" }, this.label && (h("label", { key: 'ca415565514c5a3829b1e4a446493916bd1b5b17', class: `mrd-file-field__label${this.required ? ' mrd-file-field__label--required' : ''}` }, this.label)), h("div", { key: 'abee0fc6a53c9f1266e6ed2d0ec8ab85a20a81ca', class: `mrd-file-field__zone${this.isDragging ? ' mrd-file-field__zone--dragging' : ''}${hasError ? ' mrd-file-field__zone--error' : ''}${this.disabled ? ' mrd-file-field__zone--disabled' : ''}`, onClick: this.handleZoneClick, onDragOver: this.handleDragOver, onDragLeave: this.handleDragLeave, onDrop: this.handleDrop }, h("input", { key: 'c1dbca750d4115b7d8ea3dcd9ed3405ed11c93a2', ref: el => (this.fileInputRef = el), class: "mrd-file-field__input", type: "file", name: this.name, accept: this.accept, disabled: this.disabled, required: this.required && !this.fileName, onChange: this.handleInputChange }), this.fileName ? (h("div", { class: "mrd-file-field__selected" }, h("svg", { class: "mrd-file-field__icon", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2" }, h("path", { d: "M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" }), h("polyline", { points: "14 2 14 8 20 8" })), h("span", { class: "mrd-file-field__filename" }, this.fileName), h("button", { class: "mrd-file-field__clear", type: "button", onClick: this.handleClear, "aria-label": t('clear', this.locale) }, "\u2715"))) : (h("div", { class: "mrd-file-field__prompt" }, h("svg", { class: "mrd-file-field__upload-icon", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2" }, h("polyline", { points: "16 16 12 12 8 16" }), h("line", { x1: "12", y1: "12", x2: "12", y2: "21" }), h("path", { d: "M20.39 18.39A5 5 0 0 0 18 9h-1.26A8 8 0 1 0 3 16.3" })), h("span", null, t('drop_file_here', this.locale), ' ', h("span", { class: "mrd-file-field__browse" }, t('browse', this.locale)))))), hasError && h("span", { key: '91ef6a6240f139e161288cb812d17e13a619ec89', class: "mrd-file-field__error" }, this.error))));
82
+ const zoneClass = [
83
+ 'mrd-file-field__zone',
84
+ this.isDragging ? 'mrd-file-field__zone--dragging' : '',
85
+ hasError ? 'mrd-file-field__zone--error' : '',
86
+ this.disabled || this.uploading ? 'mrd-file-field__zone--disabled' : '',
87
+ ].filter(Boolean).join(' ');
88
+ return (h(Host, { key: '2090102df8169226c1e66f87cbc837296e4b55d4' }, h("div", { key: '7a37a09df1bfa2eaf28ed04c806ad5f7c0337bb0', class: "mrd-file-field" }, this.label && (h("label", { key: '8f1fcb8adbb66b792be902c7c548e50db99b6a53', class: `mrd-file-field__label${this.required ? ' mrd-file-field__label--required' : ''}` }, this.label)), h("div", { key: '66d992298f537b24d69ce3ead3dd229f6d124f3d', class: zoneClass, onClick: this.handleZoneClick, onDragOver: this.handleDragOver, onDragLeave: this.handleDragLeave, onDrop: this.handleDrop }, h("input", { key: '95ba6560e71ee08c6b2526802727b29a18cdc454', ref: el => (this.fileInputRef = el), class: "mrd-file-field__input", type: "file", name: this.name, accept: this.accept, disabled: this.disabled || this.uploading, required: this.required && !hasFile, onChange: this.handleInputChange }), hasFile ? (h("div", { class: "mrd-file-field__selected" }, this.uploading ? (h("span", { class: "mrd-file-field__spinner", "aria-label": t('loading', this.locale) })) : (h("svg", { class: "mrd-file-field__icon", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2" }, h("path", { d: "M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" }), h("polyline", { points: "14 2 14 8 20 8" }))), h("span", { class: "mrd-file-field__filename" }, this.fileName), !this.uploading && (h("button", { class: "mrd-file-field__clear", type: "button", onClick: this.handleClear, "aria-label": t('clear', this.locale) }, "\u2715")))) : (h("div", { class: "mrd-file-field__prompt" }, h("svg", { class: "mrd-file-field__upload-icon", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2" }, h("polyline", { points: "16 16 12 12 8 16" }), h("line", { x1: "12", y1: "12", x2: "12", y2: "21" }), h("path", { d: "M20.39 18.39A5 5 0 0 0 18 9h-1.26A8 8 0 1 0 3 16.3" })), h("span", null, t('drop_file_here', this.locale), ' ', h("span", { class: "mrd-file-field__browse" }, t('browse', this.locale)))))), hasError && h("span", { key: 'a6020e63bebb01e31a980a903f81badd486585aa', class: "mrd-file-field__error" }, this.error))));
66
89
  }
67
90
  static get is() { return "mrd-file-field"; }
68
91
  static get encapsulation() { return "scoped"; }
@@ -118,6 +141,24 @@ export class MrdFileField {
118
141
  "attribute": "label",
119
142
  "defaultValue": "''"
120
143
  },
144
+ "value": {
145
+ "type": "unknown",
146
+ "mutable": false,
147
+ "complexType": {
148
+ "original": "unknown",
149
+ "resolved": "unknown",
150
+ "references": {}
151
+ },
152
+ "required": false,
153
+ "optional": false,
154
+ "docs": {
155
+ "tags": [],
156
+ "text": ""
157
+ },
158
+ "getter": false,
159
+ "setter": false,
160
+ "defaultValue": "null"
161
+ },
121
162
  "required": {
122
163
  "type": "boolean",
123
164
  "mutable": false,
@@ -224,6 +265,7 @@ export class MrdFileField {
224
265
  return {
225
266
  "fileName": {},
226
267
  "isDragging": {},
268
+ "uploading": {},
227
269
  "error": {}
228
270
  };
229
271
  }
@@ -268,6 +310,32 @@ export class MrdFileField {
268
310
  }
269
311
  }
270
312
  }
313
+ }, {
314
+ "method": "mrdUpload",
315
+ "name": "mrdUpload",
316
+ "bubbles": true,
317
+ "cancelable": true,
318
+ "composed": true,
319
+ "docs": {
320
+ "tags": [],
321
+ "text": "Emitted when a file is selected and needs to be uploaded.\nHost should upload the file and call form.setFieldValue(name, uri) with the result."
322
+ },
323
+ "complexType": {
324
+ "original": "{ name: string; file: File }",
325
+ "resolved": "{ name: string; file: File; }",
326
+ "references": {
327
+ "File": {
328
+ "location": "global",
329
+ "id": "global::File"
330
+ }
331
+ }
332
+ }
333
+ }];
334
+ }
335
+ static get watchers() {
336
+ return [{
337
+ "propName": "value",
338
+ "methodName": "valueChanged"
271
339
  }];
272
340
  }
273
341
  }
@@ -133,6 +133,19 @@
133
133
  background-color: var(--mrd-color-danger-light);
134
134
  }
135
135
 
136
+ .mrd-file-field__spinner {
137
+ display: inline-block;
138
+ flex-shrink: 0;
139
+ width: 18px;
140
+ height: 18px;
141
+ border: 2px solid var(--mrd-color-neutral-300);
142
+ border-top-color: var(--mrd-color-primary);
143
+ border-radius: 50%;
144
+ animation: mrd-file-spin 0.6s linear infinite;
145
+ }
146
+
147
+ @keyframes mrd-file-spin { to { transform: rotate(360deg); } }
148
+
136
149
  .mrd-file-field__error {
137
150
  font-family: var(--mrd-font-family);
138
151
  font-size: var(--mrd-error-font-size);
@@ -6,30 +6,56 @@ export class MrdForm {
6
6
  constructor() {
7
7
  this.locale = navigator.language;
8
8
  this.values = {};
9
+ /**
10
+ * Absolute href of the parent/reference object (e.g. the clientAgreement href
11
+ * when creating an invoice from within a client agreement).
12
+ * Combined with `referenceClass`, mrd-form will automatically pre-fill the
13
+ * matching relation field so dependent DROPDOWN fields can be fetched on load
14
+ * — without the host app needing to know anything about the form layout.
15
+ */
16
+ /**
17
+ * Absolute href of the parent/reference object (e.g. the clientAgreement href
18
+ * when creating an invoice from within a client agreement).
19
+ * Combined with `referenceClass`, mrd-form will automatically pre-fill the
20
+ * matching relation field so dependent DROPDOWN fields can be fetched on load.
21
+ */
22
+ this.referenceHref = '';
23
+ /**
24
+ * The `mostSignificantClass` of the parent/reference object
25
+ * (e.g. `'clientAgreements'`). Used to locate the matching RELATION field.
26
+ */
27
+ this.referenceClass = '';
28
+ /** When true, a cancel button is shown next to the submit button. */
29
+ this.showCancel = false;
9
30
  this.formValues = {};
10
31
  this.errors = {};
11
32
  this.submitted = false;
12
33
  this.handleFieldChange = (e) => {
13
34
  const { name, value } = e.detail;
35
+ const prevHref = this.getHref(this.formValues[name]);
14
36
  this.formValues = Object.assign(Object.assign({}, this.formValues), { [name]: value });
15
37
  if (this.errors[name]) {
16
38
  this.errors = Object.assign(Object.assign({}, this.errors), { [name]: '' });
17
39
  }
18
40
  // When a field changes, check if it is the commonRelation dependency for any
19
41
  // DROPDOWN relation. If so, reset the dependent field and re-fetch its options.
42
+ // Skip when the effective href did not change (e.g. mrdBlur fires after mrdChange
43
+ // with the same value, which would otherwise trigger a duplicate fetch).
44
+ const newHref = this.getHref(value);
45
+ if (newHref === prevHref)
46
+ return;
20
47
  for (const rel of this.collectDependentDropdowns()) {
21
48
  if (rel.commonRelation !== name)
22
49
  continue;
23
50
  // Clear the dependent field's current selection (options have changed)
24
51
  this.formValues = Object.assign(Object.assign({}, this.formValues), { [rel.name]: null });
25
- const filterValue = this.getHref(value);
26
52
  this.mrdFetchAll.emit({
27
53
  name: rel.name,
28
54
  relatedClass: rel.relatedClass,
29
55
  mostSignificantClass: rel.mostSignificantClass,
30
56
  commonRelation: rel.commonRelation,
31
57
  filter: rel.commonRelation,
32
- filterValue, // empty string when dependency was cleared → host should clear the list
58
+ filterValue: newHref, // empty string when dependency was cleared → host should clear the list
33
59
  });
34
60
  }
35
61
  };
@@ -41,6 +67,10 @@ export class MrdForm {
41
67
  e.stopPropagation();
42
68
  this.mrdFetchAll.emit(e.detail);
43
69
  };
70
+ this.handleUpload = (e) => {
71
+ e.stopPropagation();
72
+ this.mrdUpload.emit(e.detail);
73
+ };
44
74
  this.handleSubmit = (e) => {
45
75
  e.preventDefault();
46
76
  this.submitted = true;
@@ -54,20 +84,71 @@ export class MrdForm {
54
84
  this.formValues = Object.assign({}, ((_a = this.values) !== null && _a !== void 0 ? _a : {}));
55
85
  }
56
86
  componentDidLoad() {
57
- // Emit mrdFetchAll for DROPDOWN fields with commonRelation whose dependency
58
- // already has a value this handles the edit-mode case where values are set
59
- // as a prop before mount.
60
- setTimeout(() => this.emitDependentFetchAll(), 0);
87
+ // Apply reference pre-fill and emit mrdFetchAll for dependent DROPDOWN fields.
88
+ // Deferred so Angular/host prop bindings are settled before we read them.
89
+ setTimeout(() => {
90
+ this.applyReferenceValue();
91
+ this.emitDependentFetchAll();
92
+ }, 0);
61
93
  }
62
94
  /** Sync formValues when the values prop is set from outside after mount
63
95
  * (e.g. when pre-filling an existing record in edit mode). */
64
96
  valuesChanged(newValues) {
65
97
  this.formValues = Object.assign({}, (newValues !== null && newValues !== void 0 ? newValues : {}));
98
+ this.applyReferenceValue();
66
99
  this.errors = {};
67
100
  this.submitted = false;
68
101
  // Re-check DROPDOWN dependencies now that formValues are updated
69
102
  setTimeout(() => this.emitDependentFetchAll(), 0);
70
103
  }
104
+ /**
105
+ * When referenceHref + referenceClass are set, find the matching layout field
106
+ * and inject its value into formValues. This allows dependent DROPDOWN fields
107
+ * (those with commonRelation pointing to that field) to be fetched on load
108
+ * without the host app doing any form-domain logic.
109
+ *
110
+ * Two lookup strategies:
111
+ * 1. Find a RELATION whose mostSignificantClass matches referenceClass.
112
+ * 2. Fallback: find a DROPDOWN whose commonRelation field is absent from the
113
+ * layout (API omitted it because it is implied by the reference context).
114
+ */
115
+ applyReferenceValue() {
116
+ if (!this.referenceHref || !this.referenceClass)
117
+ return;
118
+ const fieldName = this.resolveReferenceFieldName();
119
+ if (!fieldName)
120
+ return;
121
+ // Only set when not already present (don't overwrite an explicit value)
122
+ if (!this.formValues[fieldName]) {
123
+ this.formValues = Object.assign(Object.assign({}, this.formValues), { [fieldName]: this.referenceHref });
124
+ }
125
+ }
126
+ resolveReferenceFieldName() {
127
+ var _a, _b;
128
+ const allItems = this.collectFields((_b = (_a = this.layout) === null || _a === void 0 ? void 0 : _a.items) !== null && _b !== void 0 ? _b : []);
129
+ // Strategy 1: direct match on mostSignificantClass
130
+ const direct = allItems.find(item => {
131
+ var _a;
132
+ return item.type === ClientLayoutItemType.RELATION &&
133
+ ((_a = item.relation) === null || _a === void 0 ? void 0 : _a.mostSignificantClass) === this.referenceClass;
134
+ });
135
+ if (direct === null || direct === void 0 ? void 0 : direct.relation)
136
+ return direct.relation.name;
137
+ // Strategy 2: a DROPDOWN whose commonRelation field was omitted from the layout
138
+ const layoutRelationNames = new Set(allItems
139
+ .filter(item => item.type === ClientLayoutItemType.RELATION)
140
+ .map(item => item.relation.name));
141
+ for (const item of allItems) {
142
+ const rel = item.relation;
143
+ if (item.type === ClientLayoutItemType.RELATION &&
144
+ (rel === null || rel === void 0 ? void 0 : rel.editBehavior) === ClientLayoutItemRelationEditBehavior.DROPDOWN &&
145
+ rel.commonRelation &&
146
+ !layoutRelationNames.has(rel.commonRelation)) {
147
+ return rel.commonRelation;
148
+ }
149
+ }
150
+ return null;
151
+ }
71
152
  async setFieldValue(name, value) {
72
153
  this.formValues = Object.assign(Object.assign({}, this.formValues), { [name]: value });
73
154
  if (this.errors[name]) {
@@ -153,6 +234,9 @@ export class MrdForm {
153
234
  if (item.type === ClientLayoutItemType.FIELD && item.field) {
154
235
  const name = item.field.name;
155
236
  const value = this.formValues[name];
237
+ // Skip file/image fields that are still uploading (value is a File object)
238
+ if (value instanceof File)
239
+ continue;
156
240
  // Omit empty strings for optional fields
157
241
  if (value !== '')
158
242
  payload[name] = value !== null && value !== void 0 ? value : null;
@@ -182,7 +266,7 @@ export class MrdForm {
182
266
  }
183
267
  const fieldName = (_d = (_b = (_a = item.field) === null || _a === void 0 ? void 0 : _a.name) !== null && _b !== void 0 ? _b : (_c = item.relation) === null || _c === void 0 ? void 0 : _c.name) !== null && _d !== void 0 ? _d : '';
184
268
  const fieldValue = this.formValues[fieldName];
185
- return (h("div", { class: "mrd-form__field" }, h("mrd-field", { item: item, locale: this.locale, value: fieldValue, onMrdChange: this.handleFieldChange, onMrdBlur: this.handleFieldChange, onMrdSearch: this.handleSearch, onMrdFetchAll: this.handleFetchAll }), this.errors[fieldName] && (h("span", { class: "mrd-form__field-error" }, this.errors[fieldName]))));
269
+ return (h("div", { class: "mrd-form__field" }, h("mrd-field", { item: item, locale: this.locale, value: fieldValue, onMrdChange: this.handleFieldChange, onMrdBlur: this.handleFieldChange, onMrdSearch: this.handleSearch, onMrdFetchAll: this.handleFetchAll, onMrdUpload: this.handleUpload }), this.errors[fieldName] && (h("span", { class: "mrd-form__field-error" }, this.errors[fieldName]))));
186
270
  });
187
271
  }
188
272
  render() {
@@ -190,7 +274,7 @@ export class MrdForm {
190
274
  return h(Host, null);
191
275
  }
192
276
  const dir = this.locale.startsWith('ar') ? 'rtl' : 'ltr';
193
- return (h(Host, null, h("form", { class: "mrd-form", dir: dir, onSubmit: this.handleSubmit, noValidate: true }, this.layout.title && h("h2", { class: "mrd-form__title" }, this.layout.title), h("div", { class: "mrd-form__body" }, this.renderItems(this.layout.items)), h("div", { class: "mrd-form__footer" }, h("button", { type: "submit", class: "mrd-form__submit" }, t('submit', this.locale))))));
277
+ return (h(Host, null, h("form", { class: "mrd-form", dir: dir, onSubmit: this.handleSubmit, noValidate: true }, this.layout.title && h("h2", { class: "mrd-form__title" }, this.layout.title), h("div", { class: "mrd-form__body" }, this.renderItems(this.layout.items)), h("div", { class: "mrd-form__footer" }, h("button", { type: "submit", class: "mrd-form__submit" }, t('submit', this.locale)), this.showCancel && (h("button", { type: "button", class: "mrd-form__cancel", onClick: () => this.mrdCancel.emit() }, t('cancel', this.locale)))))));
194
278
  }
195
279
  static get is() { return "mrd-form"; }
196
280
  static get encapsulation() { return "scoped"; }
@@ -272,6 +356,66 @@ export class MrdForm {
272
356
  "getter": false,
273
357
  "setter": false,
274
358
  "defaultValue": "{}"
359
+ },
360
+ "referenceHref": {
361
+ "type": "string",
362
+ "mutable": false,
363
+ "complexType": {
364
+ "original": "string",
365
+ "resolved": "string",
366
+ "references": {}
367
+ },
368
+ "required": false,
369
+ "optional": false,
370
+ "docs": {
371
+ "tags": [],
372
+ "text": "Absolute href of the parent/reference object (e.g. the clientAgreement href\nwhen creating an invoice from within a client agreement).\nCombined with `referenceClass`, mrd-form will automatically pre-fill the\nmatching relation field so dependent DROPDOWN fields can be fetched on load."
373
+ },
374
+ "getter": false,
375
+ "setter": false,
376
+ "reflect": false,
377
+ "attribute": "reference-href",
378
+ "defaultValue": "''"
379
+ },
380
+ "referenceClass": {
381
+ "type": "string",
382
+ "mutable": false,
383
+ "complexType": {
384
+ "original": "string",
385
+ "resolved": "string",
386
+ "references": {}
387
+ },
388
+ "required": false,
389
+ "optional": false,
390
+ "docs": {
391
+ "tags": [],
392
+ "text": "The `mostSignificantClass` of the parent/reference object\n(e.g. `'clientAgreements'`). Used to locate the matching RELATION field."
393
+ },
394
+ "getter": false,
395
+ "setter": false,
396
+ "reflect": false,
397
+ "attribute": "reference-class",
398
+ "defaultValue": "''"
399
+ },
400
+ "showCancel": {
401
+ "type": "boolean",
402
+ "mutable": false,
403
+ "complexType": {
404
+ "original": "boolean",
405
+ "resolved": "boolean",
406
+ "references": {}
407
+ },
408
+ "required": false,
409
+ "optional": false,
410
+ "docs": {
411
+ "tags": [],
412
+ "text": "When true, a cancel button is shown next to the submit button."
413
+ },
414
+ "getter": false,
415
+ "setter": false,
416
+ "reflect": false,
417
+ "attribute": "show-cancel",
418
+ "defaultValue": "false"
275
419
  }
276
420
  };
277
421
  }
@@ -303,6 +447,21 @@ export class MrdForm {
303
447
  }
304
448
  }
305
449
  }
450
+ }, {
451
+ "method": "mrdCancel",
452
+ "name": "mrdCancel",
453
+ "bubbles": true,
454
+ "cancelable": true,
455
+ "composed": true,
456
+ "docs": {
457
+ "tags": [],
458
+ "text": ""
459
+ },
460
+ "complexType": {
461
+ "original": "void",
462
+ "resolved": "void",
463
+ "references": {}
464
+ }
306
465
  }, {
307
466
  "method": "mrdSearch",
308
467
  "name": "mrdSearch",
@@ -333,6 +492,26 @@ export class MrdForm {
333
492
  "resolved": "{ name: string; relatedClass: string; mostSignificantClass?: string | undefined; commonRelation?: string | undefined; filter?: string | undefined; filterValue?: string | undefined; }",
334
493
  "references": {}
335
494
  }
495
+ }, {
496
+ "method": "mrdUpload",
497
+ "name": "mrdUpload",
498
+ "bubbles": true,
499
+ "cancelable": true,
500
+ "composed": true,
501
+ "docs": {
502
+ "tags": [],
503
+ "text": ""
504
+ },
505
+ "complexType": {
506
+ "original": "{ name: string; file: File }",
507
+ "resolved": "{ name: string; file: File; }",
508
+ "references": {
509
+ "File": {
510
+ "location": "global",
511
+ "id": "global::File"
512
+ }
513
+ }
514
+ }
336
515
  }];
337
516
  }
338
517
  static get methods() {
@@ -83,6 +83,7 @@
83
83
  border-top: var(--mrd-border-width) solid var(--mrd-border-color);
84
84
  display: flex;
85
85
  justify-content: flex-end;
86
+ gap: var(--mrd-space-3);
86
87
  }
87
88
 
88
89
  .mrd-form__submit {
@@ -114,3 +115,34 @@
114
115
  .mrd-form__submit:active {
115
116
  background-color: var(--mrd-color-primary-dark);
116
117
  }
118
+
119
+ .mrd-form__cancel {
120
+ display: inline-flex;
121
+ align-items: center;
122
+ justify-content: center;
123
+ height: var(--mrd-input-height);
124
+ padding: 0 var(--mrd-space-6);
125
+ background-color: transparent;
126
+ color: var(--mrd-color-neutral-600);
127
+ font-family: var(--mrd-font-family);
128
+ font-size: var(--mrd-font-size-base);
129
+ font-weight: var(--mrd-font-weight-medium);
130
+ border: var(--mrd-border-width) solid var(--mrd-border-color);
131
+ border-radius: var(--mrd-border-radius);
132
+ cursor: pointer;
133
+ transition: background-color var(--mrd-transition), color var(--mrd-transition);
134
+ }
135
+
136
+ .mrd-form__cancel:hover {
137
+ background-color: var(--mrd-color-neutral-100);
138
+ color: var(--mrd-color-neutral-800);
139
+ }
140
+
141
+ .mrd-form__cancel:focus {
142
+ outline: none;
143
+ box-shadow: var(--mrd-shadow-focus);
144
+ }
145
+
146
+ .mrd-form__cancel:active {
147
+ background-color: var(--mrd-color-neutral-200);
148
+ }
@@ -4,6 +4,7 @@ export class MrdImageField {
4
4
  constructor() {
5
5
  this.name = '';
6
6
  this.label = '';
7
+ this.value = null;
7
8
  this.required = false;
8
9
  this.disabled = false;
9
10
  this.locale = navigator.language;
@@ -12,6 +13,7 @@ export class MrdImageField {
12
13
  this.previewUrl = '';
13
14
  this.fileName = '';
14
15
  this.isDragging = false;
16
+ this.uploading = false;
15
17
  this.error = '';
16
18
  this.handleInputChange = (e) => {
17
19
  var _a;
@@ -34,7 +36,7 @@ export class MrdImageField {
34
36
  };
35
37
  this.handleZoneClick = () => {
36
38
  var _a;
37
- if (!this.disabled) {
39
+ if (!this.disabled && !this.uploading) {
38
40
  (_a = this.fileInputRef) === null || _a === void 0 ? void 0 : _a.click();
39
41
  }
40
42
  };
@@ -43,15 +45,28 @@ export class MrdImageField {
43
45
  this.previewUrl = '';
44
46
  this.fileName = '';
45
47
  this.error = '';
48
+ this.uploading = false;
46
49
  if (this.fileInputRef)
47
50
  this.fileInputRef.value = '';
48
51
  this.mrdChange.emit({ name: this.name, value: null });
49
52
  };
50
53
  }
54
+ /** When the host provides a URI back via setFieldValue, the upload is done. */
55
+ valueChanged(newVal) {
56
+ if (typeof newVal === 'string' && newVal) {
57
+ this.uploading = false;
58
+ }
59
+ else if (!newVal) {
60
+ this.uploading = false;
61
+ this.previewUrl = '';
62
+ this.fileName = '';
63
+ }
64
+ }
51
65
  handleFile(file) {
52
66
  if (!file) {
53
67
  this.previewUrl = '';
54
68
  this.fileName = '';
69
+ this.uploading = false;
55
70
  this.mrdChange.emit({ name: this.name, value: null });
56
71
  return;
57
72
  }
@@ -65,6 +80,8 @@ export class MrdImageField {
65
80
  }
66
81
  this.error = '';
67
82
  this.fileName = file.name;
83
+ this.uploading = true;
84
+ // Show local preview immediately while upload is in progress
68
85
  const reader = new FileReader();
69
86
  reader.onload = (ev) => {
70
87
  var _a;
@@ -72,10 +89,17 @@ export class MrdImageField {
72
89
  };
73
90
  reader.readAsDataURL(file);
74
91
  this.mrdChange.emit({ name: this.name, value: file });
92
+ this.mrdUpload.emit({ name: this.name, file });
75
93
  }
76
94
  render() {
77
95
  const hasError = !!this.error;
78
- return (h(Host, { key: 'c188d49744b7044d46b6c6f42126032f1ecc8a8f' }, h("div", { key: '53990650a5109172addca5ae274b180569b08860', class: "mrd-image-field" }, this.label && (h("label", { key: '3dc78faca422fcafed31833a11307cace9f2ff13', class: `mrd-image-field__label${this.required ? ' mrd-image-field__label--required' : ''}` }, this.label)), h("div", { key: '4ca9eb1362c999721742ac3d727326078fe844c7', class: `mrd-image-field__zone${this.isDragging ? ' mrd-image-field__zone--dragging' : ''}${hasError ? ' mrd-image-field__zone--error' : ''}${this.disabled ? ' mrd-image-field__zone--disabled' : ''}`, onClick: this.handleZoneClick, onDragOver: this.handleDragOver, onDragLeave: this.handleDragLeave, onDrop: this.handleDrop }, h("input", { key: '9e3a237ffb69ce9f6fb20e6b3e603e4db453e7e9', ref: el => (this.fileInputRef = el), class: "mrd-image-field__input", type: "file", name: this.name, accept: this.accept, disabled: this.disabled, 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 })), h("div", { class: "mrd-image-field__preview-info" }, h("span", { class: "mrd-image-field__preview-name" }, this.fileName)), 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)))))), hasError && h("span", { key: '85cf998e1ab26cce7fbf87836df25672971a4eb1', class: "mrd-image-field__error" }, this.error))));
96
+ const zoneClass = [
97
+ 'mrd-image-field__zone',
98
+ this.isDragging ? 'mrd-image-field__zone--dragging' : '',
99
+ hasError ? 'mrd-image-field__zone--error' : '',
100
+ this.disabled || this.uploading ? 'mrd-image-field__zone--disabled' : '',
101
+ ].filter(Boolean).join(' ');
102
+ return (h(Host, { key: '76b5a36a7f5a420ded3400c8a1481843363d1cbe' }, h("div", { key: '3544df84aaf427a25518bafe13d60cf89ed28537', class: "mrd-image-field" }, this.label && (h("label", { key: 'dca64c1600cb98526eb4b91a908106087f099ad1', class: `mrd-image-field__label${this.required ? ' mrd-image-field__label--required' : ''}` }, this.label)), h("div", { key: 'ce07f32126f0956e47ff6fc41ff231590e0503d3', class: zoneClass, onClick: this.handleZoneClick, onDragOver: this.handleDragOver, onDragLeave: this.handleDragLeave, onDrop: this.handleDrop }, h("input", { key: '660fbae5f730834c465522a11dd674fc8e50a54e', 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)))))), hasError && h("span", { key: '9b8f9563fea63bd12c38f5c480e9cbd24106c3af', class: "mrd-image-field__error" }, this.error))));
79
103
  }
80
104
  static get is() { return "mrd-image-field"; }
81
105
  static get encapsulation() { return "scoped"; }
@@ -131,6 +155,24 @@ export class MrdImageField {
131
155
  "attribute": "label",
132
156
  "defaultValue": "''"
133
157
  },
158
+ "value": {
159
+ "type": "unknown",
160
+ "mutable": false,
161
+ "complexType": {
162
+ "original": "unknown",
163
+ "resolved": "unknown",
164
+ "references": {}
165
+ },
166
+ "required": false,
167
+ "optional": false,
168
+ "docs": {
169
+ "tags": [],
170
+ "text": ""
171
+ },
172
+ "getter": false,
173
+ "setter": false,
174
+ "defaultValue": "null"
175
+ },
134
176
  "required": {
135
177
  "type": "boolean",
136
178
  "mutable": false,
@@ -238,6 +280,7 @@ export class MrdImageField {
238
280
  "previewUrl": {},
239
281
  "fileName": {},
240
282
  "isDragging": {},
283
+ "uploading": {},
241
284
  "error": {}
242
285
  };
243
286
  }
@@ -282,6 +325,32 @@ export class MrdImageField {
282
325
  }
283
326
  }
284
327
  }
328
+ }, {
329
+ "method": "mrdUpload",
330
+ "name": "mrdUpload",
331
+ "bubbles": true,
332
+ "cancelable": true,
333
+ "composed": true,
334
+ "docs": {
335
+ "tags": [],
336
+ "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."
337
+ },
338
+ "complexType": {
339
+ "original": "{ name: string; file: File }",
340
+ "resolved": "{ name: string; file: File; }",
341
+ "references": {
342
+ "File": {
343
+ "location": "global",
344
+ "id": "global::File"
345
+ }
346
+ }
347
+ }
348
+ }];
349
+ }
350
+ static get watchers() {
351
+ return [{
352
+ "propName": "value",
353
+ "methodName": "valueChanged"
285
354
  }];
286
355
  }
287
356
  }