@mmlogic/components 0.5.3 → 0.5.4

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 (41) hide show
  1. package/dist/cjs/{cell-renderer-7KX9Qbml.js → cell-renderer-Coy-aD3k.js} +24 -0
  2. package/dist/cjs/loader.cjs.js +1 -1
  3. package/dist/cjs/mosterdcomponents.cjs.js +1 -1
  4. package/dist/cjs/mrd-boolean-field_25.cjs.entry.js +97 -23
  5. package/dist/cjs/mrd-merge-view.cjs.entry.js +1 -1
  6. package/dist/collection/components/documents/mrd-document-container/mrd-document-container.css +3 -0
  7. package/dist/collection/components/documents/mrd-document-container/mrd-document-container.js +23 -3
  8. package/dist/collection/components/documents/mrd-document-list/mrd-document-list.js +76 -2
  9. package/dist/collection/components/fields/mrd-relation-field/mrd-relation-field.js +13 -3
  10. package/dist/collection/components/form/mrd-form/mrd-form.js +8 -4
  11. package/dist/collection/components/layout/mrd-layout-section/field-value.js +4 -2
  12. package/dist/collection/components/layout/mrd-layout-section/layout-helpers.js +6 -0
  13. package/dist/collection/components/layout/mrd-layout-section/mrd-layout-section.css +4 -0
  14. package/dist/collection/components/layout/mrd-layout-section/mrd-layout-section.js +8 -8
  15. package/dist/collection/dev/example-data.js +6 -1
  16. package/dist/collection/utils/i18n.js +24 -0
  17. package/dist/components/i18n.js +1 -1
  18. package/dist/components/mrd-document-container2.js +1 -1
  19. package/dist/components/mrd-document-list2.js +1 -1
  20. package/dist/components/mrd-form.js +1 -1
  21. package/dist/components/mrd-layout-section.js +1 -1
  22. package/dist/components/mrd-relation-field2.js +1 -1
  23. package/dist/esm/{cell-renderer-DIsNX9D7.js → cell-renderer-C2FLUrMN.js} +24 -0
  24. package/dist/esm/loader.js +1 -1
  25. package/dist/esm/mosterdcomponents.js +1 -1
  26. package/dist/esm/mrd-boolean-field_25.entry.js +97 -23
  27. package/dist/esm/mrd-merge-view.entry.js +1 -1
  28. package/dist/mosterdcomponents/mosterdcomponents.esm.js +1 -1
  29. package/dist/mosterdcomponents/{p-4e061416.entry.js → p-4ea5b97c.entry.js} +1 -1
  30. package/dist/mosterdcomponents/p-8736313b.entry.js +3 -0
  31. package/dist/mosterdcomponents/p-BgsPVesg.js +1 -0
  32. package/dist/types/components/documents/mrd-document-container/mrd-document-container.d.ts +7 -0
  33. package/dist/types/components/documents/mrd-document-list/mrd-document-list.d.ts +17 -0
  34. package/dist/types/components/fields/mrd-relation-field/mrd-relation-field.d.ts +7 -0
  35. package/dist/types/components/form/mrd-form/mrd-form.d.ts +3 -2
  36. package/dist/types/components/layout/mrd-layout-section/layout-helpers.d.ts +3 -0
  37. package/dist/types/components.d.ts +9 -0
  38. package/dist/types/types/client-layout.d.ts +7 -0
  39. package/package.json +1 -1
  40. package/dist/mosterdcomponents/p-910ebfee.entry.js +0 -3
  41. package/dist/mosterdcomponents/p-Ce2QDq0t.js +0 -1
@@ -32,12 +32,28 @@ export class MrdDocumentContainer {
32
32
  this.mode = 'tree';
33
33
  /** Whether the folder tree currently has a valid target for "New folder". */
34
34
  this.canCreate = false;
35
+ /** Name of that target (folder path, or the container's label), for the upload tooltip. */
36
+ this.targetLabel = '';
35
37
  /** Whether the currently mounted table has been init()'d. Reset on every mode switch. */
36
38
  this.tableInited = false;
37
39
  this.createFolder = () => {
38
40
  var _a;
39
41
  (_a = this.listEl) === null || _a === void 0 ? void 0 : _a.createFolder();
40
42
  };
43
+ this.pickFile = () => {
44
+ var _a;
45
+ (_a = this.fileInputEl) === null || _a === void 0 ? void 0 : _a.click();
46
+ };
47
+ /** One file per upload, matching the drop handler. Reset the input afterwards
48
+ * so picking the same file twice in a row still fires a change event. */
49
+ this.onFilePicked = (e) => {
50
+ var _a, _b;
51
+ const input = e.target;
52
+ const file = (_a = input.files) === null || _a === void 0 ? void 0 : _a[0];
53
+ if (file)
54
+ (_b = this.listEl) === null || _b === void 0 ? void 0 : _b.uploadFile(file);
55
+ input.value = '';
56
+ };
41
57
  }
42
58
  /** The embedded table does not self-load: like every mrd-table it waits for the
43
59
  * host to call init() (which emits mrdLoadPage, and — once page 0 lands —
@@ -62,7 +78,7 @@ export class MrdDocumentContainer {
62
78
  }
63
79
  // ── Toolbar ────────────────────────────────────────────────────────────────
64
80
  renderToolbar() {
65
- return (h("div", { class: "mrd-document-container__toolbar" }, this.mode === 'tree' && (h("button", { type: "button", class: "mrd-document-container__action", disabled: !this.canCreate, onClick: this.createFolder }, h("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round", "aria-hidden": "true" }, h("path", { d: "M3 7a2 2 0 0 1 2-2h4l2 2h8a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z" }), h("path", { d: "M12 11v4M10 13h4" })), t('new_folder', this.locale))), h("div", { class: "mrd-document-container__seg", role: "group", "aria-label": t('doc_view_switch', this.locale) }, h("button", { class: "mrd-document-container__seg-btn", "aria-pressed": String(this.mode === 'tree'), title: t('doc_view_tree', this.locale), "aria-label": t('doc_view_tree', this.locale), onClick: () => this.setMode('tree') }, h("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round", "aria-hidden": "true" }, h("path", { d: "M3 5h6M3 12h9M3 19h6" }), h("circle", { cx: "18", cy: "5", r: "1.6" }), h("circle", { cx: "18", cy: "19", r: "1.6" }), h("path", { d: "M16 12h4" }))), h("button", { class: "mrd-document-container__seg-btn", "aria-pressed": String(this.mode === 'list'), title: t('doc_view_list', this.locale), "aria-label": t('doc_view_list', this.locale), onClick: () => this.setMode('list') }, h("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round", "aria-hidden": "true" }, h("path", { d: "M8 6h13M8 12h13M8 18h13M3 6h.01M3 12h.01M3 18h.01" }))))));
81
+ return (h("div", { class: "mrd-document-container__toolbar" }, this.mode === 'tree' && (h("button", { type: "button", class: "mrd-document-container__action", disabled: !this.canCreate, onClick: this.createFolder }, h("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round", "aria-hidden": "true" }, h("path", { d: "M3 7a2 2 0 0 1 2-2h4l2 2h8a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z" }), h("path", { d: "M12 11v4M10 13h4" })), t('new_folder', this.locale))), this.mode === 'tree' && (h("button", { type: "button", class: "mrd-document-container__action", disabled: !this.canCreate, title: this.targetLabel ? `${t('doc_upload_to', this.locale)} ${this.targetLabel}` : undefined, onClick: this.pickFile }, h("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round", "aria-hidden": "true" }, h("path", { d: "M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" }), h("path", { d: "M12 3v13M7 8l5-5 5 5" })), t('doc_upload', this.locale))), h("input", { type: "file", class: "mrd-document-container__file-input", ref: el => (this.fileInputEl = el), onChange: this.onFilePicked }), h("div", { class: "mrd-document-container__seg", role: "group", "aria-label": t('doc_view_switch', this.locale) }, h("button", { class: "mrd-document-container__seg-btn", "aria-pressed": String(this.mode === 'tree'), title: t('doc_view_tree', this.locale), "aria-label": t('doc_view_tree', this.locale), onClick: () => this.setMode('tree') }, h("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round", "aria-hidden": "true" }, h("path", { d: "M3 5h6M3 12h9M3 19h6" }), h("circle", { cx: "18", cy: "5", r: "1.6" }), h("circle", { cx: "18", cy: "19", r: "1.6" }), h("path", { d: "M16 12h4" }))), h("button", { class: "mrd-document-container__seg-btn", "aria-pressed": String(this.mode === 'list'), title: t('doc_view_list', this.locale), "aria-label": t('doc_view_list', this.locale), onClick: () => this.setMode('list') }, h("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round", "aria-hidden": "true" }, h("path", { d: "M8 6h13M8 12h13M8 18h13M3 6h.01M3 12h.01M3 18h.01" }))))));
66
82
  }
67
83
  // ── Bodies ───────────────────────────────────────────────────────────────
68
84
  renderTree() {
@@ -78,6 +94,9 @@ export class MrdDocumentContainer {
78
94
  }, onMrdCanCreate: (e) => {
79
95
  e.stopPropagation();
80
96
  this.canCreate = e.detail;
97
+ }, onMrdTargetLabel: (e) => {
98
+ e.stopPropagation();
99
+ this.targetLabel = e.detail;
81
100
  }, onMrdUpdateObject: (e) => {
82
101
  e.stopPropagation();
83
102
  this.mrdUpdateObject.emit(e.detail);
@@ -114,7 +133,7 @@ export class MrdDocumentContainer {
114
133
  }
115
134
  render() {
116
135
  const treeMode = this.mode === 'tree';
117
- return (h(Host, { key: '4d21f26c956773c63cbf5118394c6afa29d6014d' }, this.renderToolbar(), h("div", { key: 'bc070236bf9a7d0eba57aa2cd70fa3ae36cbf5f1', class: {
136
+ return (h(Host, { key: 'ae948e2458d9087179e850b527802c489067d93f' }, this.renderToolbar(), h("div", { key: 'a04e1a78413aa794d0bda551aeb9c6245f1aafa2', class: {
118
137
  'mrd-document-container__body': true,
119
138
  'mrd-document-container__body--tree': treeMode,
120
139
  }, style: treeMode ? { height: `${this.height}px`, overflowY: 'auto' } : {} }, treeMode ? this.renderTree() : this.renderList())));
@@ -287,7 +306,8 @@ export class MrdDocumentContainer {
287
306
  static get states() {
288
307
  return {
289
308
  "mode": {},
290
- "canCreate": {}
309
+ "canCreate": {},
310
+ "targetLabel": {}
291
311
  };
292
312
  }
293
313
  static get events() {
@@ -64,6 +64,8 @@ export class MrdDocumentList {
64
64
  this.newFolderSeq = 0;
65
65
  /** Last emitted canCreateFolder value, to only emit mrdCanCreate on change. */
66
66
  this.lastCanCreate = false;
67
+ /** Last emitted upload-target label, to only emit mrdTargetLabel on change. */
68
+ this.lastTargetLabel = '';
67
69
  // ── Drag & drop (move a document into another folder) ──────────────────────
68
70
  /** The document row currently being dragged. */
69
71
  this.dragDoc = null;
@@ -237,6 +239,22 @@ export class MrdDocumentList {
237
239
  this.lastCanCreate = can;
238
240
  this.mrdCanCreate.emit(can);
239
241
  }
242
+ const label = this.targetLabel;
243
+ if (label !== this.lastTargetLabel) {
244
+ this.lastTargetLabel = label;
245
+ this.mrdTargetLabel.emit(label);
246
+ }
247
+ }
248
+ /** Human-readable name of the node a new folder or upload lands in — the
249
+ * folder path when one is selected, otherwise the container's own label.
250
+ * Empty when there is no target at all. */
251
+ get targetLabel() {
252
+ var _a;
253
+ const resolved = this.resolveTarget();
254
+ if (!resolved)
255
+ return '';
256
+ const { target, container } = resolved;
257
+ return (_a = folderStringOf(target, container)) !== null && _a !== void 0 ? _a : target.label;
240
258
  }
241
259
  // ── Slot field names ─────────────────────────────────────────────────────
242
260
  get containerField() {
@@ -510,12 +528,28 @@ export class MrdDocumentList {
510
528
  this.dropTargetId = null;
511
529
  if (!file)
512
530
  return;
531
+ this.beginUpload(file, node, container);
532
+ }
533
+ /** Shared by the file drop and the host toolbar's upload button. */
534
+ beginUpload(file, node, container) {
513
535
  this.pendingUpload = { file, node, container };
514
536
  node.open = true;
515
537
  this.uploadingNodeId = node.id;
516
538
  this.bump();
517
539
  this.mrdUpload.emit({ file });
518
540
  }
541
+ /** Upload a file into the current target — the selected folder/container, or
542
+ * the only container when there is just one. Called by the host toolbar
543
+ * (mrd-document-container). No-op without a target, or while another upload
544
+ * is still awaiting its setFileReference. */
545
+ async uploadFile(file) {
546
+ if (!file || this.pendingUpload)
547
+ return;
548
+ const resolved = this.resolveTarget();
549
+ if (!resolved)
550
+ return;
551
+ this.beginUpload(file, resolved.target, resolved.container);
552
+ }
519
553
  /** Host callback with the uploaded file reference. Builds the create payload
520
554
  * from the archetype slots and requests the new document. */
521
555
  async setFileReference(uri) {
@@ -650,7 +684,7 @@ export class MrdDocumentList {
650
684
  body = renderLoadingRow(ctx, 0);
651
685
  }
652
686
  else if (this.containers.length === 0) {
653
- body = h("div", { key: '308725d741deb384082bd514edd34593f7d662a2', class: "mrd-document-list__empty" }, t('no_results', this.locale));
687
+ body = h("div", { key: '63604a99fc62d9108f61b8efc126c0cd3e8fc5ed', class: "mrd-document-list__empty" }, t('no_results', this.locale));
654
688
  }
655
689
  else if (this.singleContainer) {
656
690
  // Dossier context: render the single container's folder tree at the root.
@@ -668,7 +702,7 @@ export class MrdDocumentList {
668
702
  else {
669
703
  body = this.containers.map(c => renderContainerNode(ctx, c, 0));
670
704
  }
671
- return (h(Host, { key: '9d5fde912999b57a0f0b737980d59e94f385f11d' }, h("div", { key: '17191411c2e626995f08b66bad755342fe3b13ae', class: 'mrd-document-list' + (this.rootDropActive ? ' mrd-document-list--drop-active' : ''), onDragOver: this.onRootDragOver, onDragLeave: this.onRootDragLeave, onDrop: this.onRootDrop }, body)));
705
+ return (h(Host, { key: '2a092e3a7483deb1fb30753b0c0428aaf75c303a' }, h("div", { key: '2b716c912a272b925a95133de5b1ff2abedd9dbc', class: 'mrd-document-list' + (this.rootDropActive ? ' mrd-document-list--drop-active' : ''), onDragOver: this.onRootDragOver, onDragLeave: this.onRootDragLeave, onDrop: this.onRootDrop }, body)));
672
706
  }
673
707
  static get is() { return "mrd-document-list"; }
674
708
  static get encapsulation() { return "scoped"; }
@@ -880,6 +914,21 @@ export class MrdDocumentList {
880
914
  "resolved": "boolean",
881
915
  "references": {}
882
916
  }
917
+ }, {
918
+ "method": "mrdTargetLabel",
919
+ "name": "mrdTargetLabel",
920
+ "bubbles": true,
921
+ "cancelable": true,
922
+ "composed": true,
923
+ "docs": {
924
+ "tags": [],
925
+ "text": "Emitted whenever the current target changes: the folder path a new folder or\nupload lands in, or the container's label when no folder is selected. Empty\nwhen there is no target. Lets a host toolbar name the destination."
926
+ },
927
+ "complexType": {
928
+ "original": "string",
929
+ "resolved": "string",
930
+ "references": {}
931
+ }
883
932
  }, {
884
933
  "method": "mrdUpdateObject",
885
934
  "name": "mrdUpdateObject",
@@ -1023,6 +1072,31 @@ export class MrdDocumentList {
1023
1072
  "tags": []
1024
1073
  }
1025
1074
  },
1075
+ "uploadFile": {
1076
+ "complexType": {
1077
+ "signature": "(file: File) => Promise<void>",
1078
+ "parameters": [{
1079
+ "name": "file",
1080
+ "type": "File",
1081
+ "docs": ""
1082
+ }],
1083
+ "references": {
1084
+ "Promise": {
1085
+ "location": "global",
1086
+ "id": "global::Promise"
1087
+ },
1088
+ "File": {
1089
+ "location": "global",
1090
+ "id": "global::File"
1091
+ }
1092
+ },
1093
+ "return": "Promise<void>"
1094
+ },
1095
+ "docs": {
1096
+ "text": "Upload a file into the current target \u2014 the selected folder/container, or\nthe only container when there is just one. Called by the host toolbar\n(mrd-document-container). No-op without a target, or while another upload\nis still awaiting its setFileReference.",
1097
+ "tags": []
1098
+ }
1099
+ },
1026
1100
  "setFileReference": {
1027
1101
  "complexType": {
1028
1102
  "signature": "(uri: string) => Promise<void>",
@@ -121,16 +121,22 @@ export class MrdRelationField {
121
121
  async setAllRecords(records) {
122
122
  this.allRecords = records;
123
123
  }
124
+ /** Href of the current single value. Accepts both a RelationSearchResult
125
+ * (`id`) and the raw API link shape (`{ href, name }`) a host puts in the
126
+ * form values for edit mode — same tolerance as `toSearchResult()`. */
124
127
  getValueHref() {
125
- var _a, _b, _c;
128
+ var _a, _b, _c, _d;
126
129
  const v = Array.isArray(this.value) ? ((_a = this.value[0]) !== null && _a !== void 0 ? _a : '') : ((_b = this.value) !== null && _b !== void 0 ? _b : '');
127
130
  if (!v)
128
131
  return '';
129
132
  if (typeof v === 'object')
130
- return (_c = v.id) !== null && _c !== void 0 ? _c : '';
133
+ return (_d = (_c = v.id) !== null && _c !== void 0 ? _c : v.href) !== null && _d !== void 0 ? _d : '';
131
134
  return v;
132
135
  }
133
- allRecordsChanged() {
136
+ /** Push the current value onto the rendered <select>. Options and value arrive
137
+ * independently in edit mode (fetched records vs. the async `values` prop), so
138
+ * this runs on both — whichever lands last completes the preselection. */
139
+ syncDropdownSelection() {
134
140
  if (this.editBehavior !== ClientLayoutItemRelationEditBehavior.DROPDOWN)
135
141
  return;
136
142
  const current = this.getValueHref();
@@ -146,6 +152,9 @@ export class MrdRelationField {
146
152
  select.value = current;
147
153
  }, 0);
148
154
  }
155
+ allRecordsChanged() {
156
+ this.syncDropdownSelection();
157
+ }
149
158
  async setSearchResults(results) {
150
159
  this.searchResults = results;
151
160
  this.isLoading = false;
@@ -197,6 +206,7 @@ export class MrdRelationField {
197
206
  // (host pre-fill). Plain href strings from user interaction are ignored.
198
207
  valueChanged(newValue) {
199
208
  this.applyValuePrefill(newValue);
209
+ this.syncDropdownSelection();
200
210
  }
201
211
  componentDidLoad() {
202
212
  // Only emit when there is no commonRelation dependency — the form orchestrates those.
@@ -253,15 +253,19 @@ export class MrdForm {
253
253
  }
254
254
  }
255
255
  }
256
- /** Extract a plain href string from a form value, which can be a string or
257
- * a RelationSearchResult-like object with an `id` field. */
256
+ /** Extract a plain href string from a form value: a string, a
257
+ * RelationSearchResult-like object (`id`), or the raw API link shape
258
+ * (`{ href, name }`) hosts spread into `values` for edit mode. */
258
259
  getHref(value) {
260
+ var _a, _b;
259
261
  if (!value)
260
262
  return '';
261
263
  if (typeof value === 'string')
262
264
  return value;
263
- if (typeof value === 'object' && 'id' in value)
264
- return value.id;
265
+ if (typeof value === 'object') {
266
+ const v = value;
267
+ return (_b = (_a = v.id) !== null && _a !== void 0 ? _a : v.href) !== null && _b !== void 0 ? _b : '';
268
+ }
265
269
  return '';
266
270
  }
267
271
  collectFields(items) {
@@ -3,6 +3,7 @@ import { ClientLayoutItemFieldDataType } from "../../../types/client-layout";
3
3
  import { CellRenderer } from "../../../utils/cell-renderer";
4
4
  import { t } from "../../../utils/i18n";
5
5
  import { formatDate } from "../../../utils/format";
6
+ import { showsEmpty } from "./layout-helpers";
6
7
  /** FILE and IMAGE cells share the same download button. */
7
8
  function downloadLink(ctx, href, fileName, label) {
8
9
  return (h("button", { class: "mrd-layout-section__download-link", onClick: () => href && ctx.on.download({ href, fileName }) }, h("svg", { class: "mrd-layout-section__file-icon", viewBox: "0 0 24 24", "aria-hidden": "true" }, h("path", { fill: "currentColor", d: "M14 2H6c-1.1 0-2 .9-2 2v16c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V8l-6-6zm-1 7V3.5L18.5 9H13zm-3 8l-3-3 1.41-1.41L10 14.17l4.59-4.58L16 11l-6 6z" })), label));
@@ -106,10 +107,11 @@ export function renderField(ctx, item) {
106
107
  if (item.header) {
107
108
  return (h("h1", { class: "mrd-layout-section__field-header", key: item.name }, typeof renderedValue === 'string' ? renderedValue : rawValue != null ? String(rawValue) : item.label));
108
109
  }
109
- if (renderedValue == null)
110
+ const isEmpty = renderedValue == null;
111
+ if (isEmpty && !showsEmpty(item))
110
112
  return null;
111
113
  const isBlock = item.dataType === ClientLayoutItemFieldDataType.TEXTBLOCK
112
114
  || item.dataType === ClientLayoutItemFieldDataType.LONGTEXT
113
115
  || item.dataType === ClientLayoutItemFieldDataType.JSON;
114
- return (h("div", { class: `mrd-layout-section__field${isBlock ? ' mrd-layout-section__field--block' : ''}`, key: item.name }, h("span", { class: "mrd-layout-section__field-label" }, item.label), h("span", { class: "mrd-layout-section__field-value" }, renderedValue, renderHistoryBadge(ctx, item, raw))));
116
+ return (h("div", { class: `mrd-layout-section__field${isBlock ? ' mrd-layout-section__field--block' : ''}${isEmpty ? ' mrd-layout-section__field--empty' : ''}`, key: item.name }, h("span", { class: "mrd-layout-section__field-label" }, item.label), h("span", { class: "mrd-layout-section__field-value" }, isEmpty ? t('value_empty', ctx.locale) : renderedValue, renderHistoryBadge(ctx, item, raw))));
115
117
  }
@@ -6,6 +6,12 @@ export function viewKeyFor(item) {
6
6
  return (_b = (_a = item.relatedClass) !== null && _a !== void 0 ? _a : item.name) !== null && _b !== void 0 ? _b : '';
7
7
  return (_d = (_c = item.dataClass) !== null && _c !== void 0 ? _c : item.name) !== null && _d !== void 0 ? _d : '';
8
8
  }
9
+ /** Whether a FIELD/RELATION item stays visible on an OBJECT_DASHBOARD when it
10
+ * has no value. Reads the flat API flag, falling back to the nested wrappers. */
11
+ export function showsEmpty(item) {
12
+ var _a, _b, _c, _d, _e;
13
+ return (_e = (_c = (_a = item.showEmpty) !== null && _a !== void 0 ? _a : (_b = item.field) === null || _b === void 0 ? void 0 : _b.showEmpty) !== null && _c !== void 0 ? _c : (_d = item.relation) === null || _d === void 0 ? void 0 : _d.showEmpty) !== null && _e !== void 0 ? _e : false;
14
+ }
9
15
  /** Depth-first flatten of the SECTION/GROUP tree. */
10
16
  export function flattenItems(items) {
11
17
  const result = [];
@@ -33,6 +33,10 @@
33
33
  align-items: center;
34
34
  gap: var(--mrd-space-1);
35
35
  }
36
+ .mrd-layout-section__field--empty .mrd-layout-section__field-value {
37
+ color: var(--mrd-color-neutral-400);
38
+ font-weight: var(--mrd-font-weight-normal);
39
+ }
36
40
  .mrd-layout-section__history-wrap {
37
41
  position: relative;
38
42
  display: inline-flex;
@@ -1,6 +1,7 @@
1
1
  import { Host, h } from "@stencil/core";
2
2
  import { ClientLayoutItemType, ClientLayoutItemFieldDataType, ClientLayoutNavigateBehaviour, resolveArchetype, ARCHETYPE_DOCUMENT, ARCHETYPE_INVOICE, ARCHETYPE_EMAIL, ARCHETYPE_INVOICE_LINE, } from "../../../types/client-layout";
3
- import { viewKeyFor, flattenItems, buildContainerHref } from "./layout-helpers";
3
+ import { t } from "../../../utils/i18n";
4
+ import { viewKeyFor, flattenItems, buildContainerHref, showsEmpty } from "./layout-helpers";
4
5
  import { renderField } from "./field-value";
5
6
  export class MrdLayoutSection {
6
7
  constructor() {
@@ -173,19 +174,18 @@ export class MrdLayoutSection {
173
174
  return null;
174
175
  const links = ((_b = (_a = this.data) === null || _a === void 0 ? void 0 : _a._links) !== null && _b !== void 0 ? _b : {});
175
176
  const link = links[item.name];
176
- if (!link)
177
- return null;
178
177
  const makeBtn = (href, name) => (h("button", { key: href, class: "mrd-layout-section__relation-link", onClick: () => this.mrdNavigate.emit({ href, label: name }) }, name));
179
178
  let valueContent;
180
- if ((_c = link.values) === null || _c === void 0 ? void 0 : _c.length) {
179
+ if ((_c = link === null || link === void 0 ? void 0 : link.values) === null || _c === void 0 ? void 0 : _c.length) {
181
180
  valueContent = link.values.map(v => makeBtn(v.href, v.name));
182
181
  }
183
- else if (link.name) {
182
+ else if (link === null || link === void 0 ? void 0 : link.name) {
184
183
  valueContent = makeBtn(link.href, link.name);
185
184
  }
186
- if (!valueContent)
185
+ const isEmpty = !valueContent;
186
+ if (isEmpty && !showsEmpty(item))
187
187
  return null;
188
- return (h("div", { class: "mrd-layout-section__field", key: item.name }, h("span", { class: "mrd-layout-section__field-label" }, item.label), h("span", { class: "mrd-layout-section__field-value" }, valueContent)));
188
+ return (h("div", { class: `mrd-layout-section__field${isEmpty ? ' mrd-layout-section__field--empty' : ''}`, key: item.name }, h("span", { class: "mrd-layout-section__field-label" }, item.label), h("span", { class: "mrd-layout-section__field-value" }, isEmpty ? t('value_empty', this.locale) : valueContent)));
189
189
  }
190
190
  renderSearch(item) {
191
191
  var _a, _b, _c;
@@ -359,7 +359,7 @@ export class MrdLayoutSection {
359
359
  const docArchetype = resolveArchetype(this.archetypes, ARCHETYPE_DOCUMENT);
360
360
  const invoiceArchetype = !docArchetype ? resolveArchetype(this.archetypes, ARCHETYPE_INVOICE) : undefined;
361
361
  const emailArchetype = !docArchetype && !invoiceArchetype ? resolveArchetype(this.archetypes, ARCHETYPE_EMAIL) : undefined;
362
- return (h(Host, { key: '0078764a136e7ca9561632718c1b527ec3bc01c5' }, h("div", { key: '0c1428ce20bd7d2bbce467a36b6c25ffa0e6367e', class: "mrd-layout-section" }, docArchetype && this.renderDocumentObject(docArchetype), invoiceArchetype && this.renderInvoiceObject(invoiceArchetype), emailArchetype && this.renderEmailObject(emailArchetype), !docArchetype && !invoiceArchetype && !emailArchetype && this.items.map(item => this.renderItem(item))), this.renderImageModal()));
362
+ return (h(Host, { key: 'ccae8e82b4dd549ab24239fe685a6830a508061d' }, h("div", { key: '4072cc4ecdd2b087c54a5515e653f7f713b2150f', class: "mrd-layout-section" }, docArchetype && this.renderDocumentObject(docArchetype), invoiceArchetype && this.renderInvoiceObject(invoiceArchetype), emailArchetype && this.renderEmailObject(emailArchetype), !docArchetype && !invoiceArchetype && !emailArchetype && this.items.map(item => this.renderItem(item))), this.renderImageModal()));
363
363
  }
364
364
  static get is() { return "mrd-layout-section"; }
365
365
  static get encapsulation() { return "scoped"; }
@@ -223,7 +223,9 @@ window.EXAMPLE_COMPANY_METADATA = {
223
223
  { type: 'FIELD', label: 'legal form extended', name: 'legalFormExtended', dataType: 'TEXT', required: false },
224
224
  { type: 'FIELD', label: 'rsin', name: 'rsin', dataType: 'TEXT', required: false },
225
225
  { type: 'FIELD', label: 'start date', name: 'startDate', dataType: 'DATE', required: false },
226
- { type: 'FIELD', label: 'end date', name: 'endDate', dataType: 'DATE', required: false },
226
+ // showEmpty demo (TASK-0231): endDate is null on EXAMPLE_COMPANY, so it
227
+ // stays visible with a placeholder instead of being hidden.
228
+ { type: 'FIELD', label: 'end date', name: 'endDate', dataType: 'DATE', required: false, showEmpty: true },
227
229
  { type: 'FIELD', label: 'primary activity description', name: 'primaryActivityDescription', dataType: 'TEXT', required: false },
228
230
  { type: 'FIELD', label: 'primary activity code', name: 'primaryActivityCode', dataType: 'TEXT', required: false },
229
231
  { type: 'FIELD', label: 'total employees', name: 'totalEmployees', dataType: 'INTEGER', required: false },
@@ -243,6 +245,9 @@ window.EXAMPLE_COMPANY_METADATA = {
243
245
  { type: 'FIELD', label: 'mailing postal code', name: 'mailingPostalCode', dataType: 'TEXT', required: false },
244
246
  { type: 'FIELD', label: 'mailing city', name: 'mailingCity', dataType: 'TEXT', required: false },
245
247
  { type: 'RELATION', label: 'country', name: 'country', relatedClass: 'commons.country', mostSignificantClass: 'countries', required: false },
248
+ // showEmpty demo (TASK-0231): no `parentCompany` link on EXAMPLE_COMPANY,
249
+ // so the relation still renders with a placeholder value.
250
+ { type: 'RELATION', label: 'parent company', name: 'parentCompany', relatedClass: 'commons.company', mostSignificantClass: 'companies', required: false, showEmpty: true },
246
251
  ],
247
252
  },
248
253
  {
@@ -27,6 +27,7 @@ const translations = {
27
27
  // mrd-table footer
28
28
  table_of: 'van',
29
29
  download: 'Downloaden',
30
+ value_empty: '—',
30
31
  // mrd-table toolbar
31
32
  table_filter: 'Filteren',
32
33
  table_filter_hide: 'Filter verbergen',
@@ -36,6 +37,8 @@ const translations = {
36
37
  table_export_excel: 'Exporteer naar Excel',
37
38
  doc_view_switch: 'Weergave wisselen',
38
39
  doc_view_tree: 'Boomweergave',
40
+ doc_upload: 'Uploaden',
41
+ doc_upload_to: 'Uploaden naar',
39
42
  doc_view_list: 'Lijstweergave',
40
43
  // mrd-table filter popup
41
44
  filter_sorting: 'Sortering',
@@ -129,6 +132,7 @@ const translations = {
129
132
  // mrd-table footer
130
133
  table_of: 'of',
131
134
  download: 'Download',
135
+ value_empty: '—',
132
136
  // mrd-table toolbar
133
137
  table_filter: 'Filter',
134
138
  table_filter_hide: 'Hide filter',
@@ -138,6 +142,8 @@ const translations = {
138
142
  table_export_excel: 'Export to Excel',
139
143
  doc_view_switch: 'Switch view',
140
144
  doc_view_tree: 'Tree view',
145
+ doc_upload: 'Upload',
146
+ doc_upload_to: 'Upload to',
141
147
  doc_view_list: 'List view',
142
148
  // mrd-table filter popup
143
149
  filter_sorting: 'Sorting',
@@ -231,6 +237,7 @@ const translations = {
231
237
  // mrd-table footer
232
238
  table_of: 'من أصل',
233
239
  download: 'تنزيل',
240
+ value_empty: '—',
234
241
  // mrd-table toolbar
235
242
  table_filter: 'تصفية',
236
243
  table_filter_hide: 'إخفاء التصفية',
@@ -240,6 +247,8 @@ const translations = {
240
247
  table_export_excel: 'تصدير إلى Excel',
241
248
  doc_view_switch: 'تبديل العرض',
242
249
  doc_view_tree: 'عرض شجري',
250
+ doc_upload: 'رفع',
251
+ doc_upload_to: 'رفع إلى',
243
252
  doc_view_list: 'عرض القائمة',
244
253
  // mrd-table filter popup
245
254
  filter_sorting: 'الترتيب',
@@ -327,6 +336,7 @@ const translations = {
327
336
  // mrd-table footer
328
337
  table_of: 'sur',
329
338
  download: 'Télécharger',
339
+ value_empty: '—',
330
340
  // mrd-table toolbar
331
341
  table_filter: 'Filtrer',
332
342
  table_filter_hide: 'Masquer le filtre',
@@ -336,6 +346,8 @@ const translations = {
336
346
  table_export_excel: 'Exporter vers Excel',
337
347
  doc_view_switch: 'Changer de vue',
338
348
  doc_view_tree: 'Vue arborescente',
349
+ doc_upload: 'Téléverser',
350
+ doc_upload_to: 'Téléverser vers',
339
351
  doc_view_list: 'Vue liste',
340
352
  // mrd-table filter popup
341
353
  filter_sorting: 'Tri',
@@ -423,6 +435,7 @@ const translations = {
423
435
  // mrd-table footer
424
436
  table_of: 'von',
425
437
  download: 'Herunterladen',
438
+ value_empty: '—',
426
439
  // mrd-table toolbar
427
440
  table_filter: 'Filtern',
428
441
  table_filter_hide: 'Filter ausblenden',
@@ -432,6 +445,8 @@ const translations = {
432
445
  table_export_excel: 'Als Excel exportieren',
433
446
  doc_view_switch: 'Ansicht wechseln',
434
447
  doc_view_tree: 'Baumansicht',
448
+ doc_upload: 'Hochladen',
449
+ doc_upload_to: 'Hochladen nach',
435
450
  doc_view_list: 'Listenansicht',
436
451
  // mrd-table filter popup
437
452
  filter_sorting: 'Sortierung',
@@ -519,6 +534,7 @@ const translations = {
519
534
  // mrd-table footer
520
535
  table_of: 'de',
521
536
  download: 'Descargar',
537
+ value_empty: '—',
522
538
  // mrd-table toolbar
523
539
  table_filter: 'Filtrar',
524
540
  table_filter_hide: 'Ocultar filtro',
@@ -528,6 +544,8 @@ const translations = {
528
544
  table_export_excel: 'Exportar a Excel',
529
545
  doc_view_switch: 'Cambiar vista',
530
546
  doc_view_tree: 'Vista de árbol',
547
+ doc_upload: 'Subir',
548
+ doc_upload_to: 'Subir a',
531
549
  doc_view_list: 'Vista de lista',
532
550
  // mrd-table filter popup
533
551
  filter_sorting: 'Ordenación',
@@ -615,6 +633,7 @@ const translations = {
615
633
  // mrd-table footer
616
634
  table_of: 'di',
617
635
  download: 'Scarica',
636
+ value_empty: '—',
618
637
  // mrd-table toolbar
619
638
  table_filter: 'Filtra',
620
639
  table_filter_hide: 'Nascondi filtro',
@@ -624,6 +643,8 @@ const translations = {
624
643
  table_export_excel: 'Esporta in Excel',
625
644
  doc_view_switch: 'Cambia vista',
626
645
  doc_view_tree: 'Vista ad albero',
646
+ doc_upload: 'Carica',
647
+ doc_upload_to: 'Carica in',
627
648
  doc_view_list: 'Vista elenco',
628
649
  // mrd-table filter popup
629
650
  filter_sorting: 'Ordinamento',
@@ -711,6 +732,7 @@ const translations = {
711
732
  // mrd-table footer
712
733
  table_of: 'з',
713
734
  download: 'Завантажити',
735
+ value_empty: '—',
714
736
  // mrd-table toolbar
715
737
  table_filter: 'Фільтрувати',
716
738
  table_filter_hide: 'Сховати фільтр',
@@ -720,6 +742,8 @@ const translations = {
720
742
  table_export_excel: 'Експортувати до Excel',
721
743
  doc_view_switch: 'Змінити вигляд',
722
744
  doc_view_tree: 'Деревоподібний вигляд',
745
+ doc_upload: 'Завантажити',
746
+ doc_upload_to: 'Завантажити до',
723
747
  doc_view_list: 'Список',
724
748
  // mrd-table filter popup
725
749
  filter_sorting: 'Сортування',