@mmlogic/components 0.5.2 → 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 (117) hide show
  1. package/dist/cjs/{cell-renderer-CmaO0CZT.js → cell-renderer-Coy-aD3k.js} +146 -1
  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} +5696 -5229
  8. package/dist/cjs/mrd-merge-view.cjs.entry.js +5 -5
  9. package/dist/collection/collection-manifest.json +2 -0
  10. package/dist/collection/components/documents/mrd-document-container/mrd-document-container.css +3 -0
  11. package/dist/collection/components/documents/mrd-document-container/mrd-document-container.js +23 -3
  12. package/dist/collection/components/documents/mrd-document-list/mrd-document-list.js +76 -2
  13. package/dist/collection/components/documents/mrd-document-view/mrd-document-view.js +7 -8
  14. package/dist/collection/components/email/mrd-email-view/mrd-email-view.css +126 -0
  15. package/dist/collection/components/email/mrd-email-view/mrd-email-view.js +247 -0
  16. package/dist/collection/components/fields/file-upload-common.js +23 -7
  17. package/dist/collection/components/fields/mrd-boolean-field/mrd-boolean-field.js +1 -1
  18. package/dist/collection/components/fields/mrd-currency-field/mrd-currency-field.js +1 -1
  19. package/dist/collection/components/fields/mrd-date-field/mrd-date-field.js +1 -1
  20. package/dist/collection/components/fields/mrd-datetime-field/mrd-datetime-field.js +1 -1
  21. package/dist/collection/components/fields/mrd-email-field/mrd-email-field.js +1 -1
  22. package/dist/collection/components/fields/mrd-file-field/mrd-file-field.css +20 -1
  23. package/dist/collection/components/fields/mrd-file-field/mrd-file-field.js +108 -47
  24. package/dist/collection/components/fields/mrd-image-field/mrd-image-field.css +44 -0
  25. package/dist/collection/components/fields/mrd-image-field/mrd-image-field.js +126 -58
  26. package/dist/collection/components/fields/mrd-longtext-field/mrd-longtext-field.js +1 -1
  27. package/dist/collection/components/fields/mrd-number-field/mrd-number-field.js +1 -1
  28. package/dist/collection/components/fields/mrd-relation-field/mrd-relation-field.js +13 -3
  29. package/dist/collection/components/fields/mrd-secret-field/mrd-secret-field.js +2 -2
  30. package/dist/collection/components/fields/mrd-text-field/mrd-text-field.js +1 -1
  31. package/dist/collection/components/fields/mrd-textarea-field/mrd-textarea-field.js +1 -1
  32. package/dist/collection/components/fields/mrd-time-field/mrd-time-field.js +1 -1
  33. package/dist/collection/components/form/mrd-field/mrd-field.js +5 -5
  34. package/dist/collection/components/form/mrd-form/mrd-form.js +63 -10
  35. package/dist/collection/components/invoice/mrd-invoice-view/mrd-invoice-view.css +145 -0
  36. package/dist/collection/components/invoice/mrd-invoice-view/mrd-invoice-view.js +281 -0
  37. package/dist/collection/components/layout/mrd-layout-section/field-value.js +9 -3
  38. package/dist/collection/components/layout/mrd-layout-section/layout-helpers.js +6 -0
  39. package/dist/collection/components/layout/mrd-layout-section/mrd-layout-section.css +14 -0
  40. package/dist/collection/components/layout/mrd-layout-section/mrd-layout-section.js +45 -15
  41. package/dist/collection/components/merge/mrd-merge-view/mrd-merge-view.js +2 -2
  42. package/dist/collection/dev/api.js +20 -5
  43. package/dist/collection/dev/app.js +51 -13
  44. package/dist/collection/dev/example-data.js +132 -3
  45. package/dist/collection/types/client-layout.js +6 -0
  46. package/dist/collection/utils/document-attachments.js +27 -14
  47. package/dist/collection/utils/i18n.js +144 -0
  48. package/dist/components/cell-renderer.js +1 -1
  49. package/dist/components/client-layout.js +1 -1
  50. package/dist/components/document-attachments.js +1 -1
  51. package/dist/components/file-upload-common.js +1 -1
  52. package/dist/components/format.js +1 -1
  53. package/dist/components/i18n.js +1 -1
  54. package/dist/components/index.js +1 -1
  55. package/dist/components/mrd-boolean-field2.js +1 -1
  56. package/dist/components/mrd-currency-field2.js +1 -1
  57. package/dist/components/mrd-date-field2.js +1 -1
  58. package/dist/components/mrd-datetime-field2.js +1 -1
  59. package/dist/components/mrd-document-container2.js +1 -1
  60. package/dist/components/mrd-document-list2.js +1 -1
  61. package/dist/components/mrd-document-view2.js +1 -1
  62. package/dist/components/mrd-email-field2.js +1 -1
  63. package/dist/components/mrd-email-view.d.ts +11 -0
  64. package/dist/components/mrd-email-view.js +1 -0
  65. package/dist/components/mrd-email-view2.js +1 -0
  66. package/dist/components/mrd-field2.js +1 -1
  67. package/dist/components/mrd-file-field2.js +1 -1
  68. package/dist/components/mrd-form.js +1 -1
  69. package/dist/components/mrd-image-field2.js +1 -1
  70. package/dist/components/mrd-invoice-view.d.ts +11 -0
  71. package/dist/components/mrd-invoice-view.js +1 -0
  72. package/dist/components/mrd-invoice-view2.js +1 -0
  73. package/dist/components/mrd-layout-section.js +1 -1
  74. package/dist/components/mrd-longtext-field2.js +1 -1
  75. package/dist/components/mrd-merge-view.js +1 -1
  76. package/dist/components/mrd-number-field2.js +1 -1
  77. package/dist/components/mrd-relation-field2.js +1 -1
  78. package/dist/components/mrd-secret-field2.js +1 -1
  79. package/dist/components/mrd-table2.js +1 -1
  80. package/dist/components/mrd-text-field2.js +1 -1
  81. package/dist/components/mrd-textarea-field2.js +1 -2
  82. package/dist/components/mrd-time-field2.js +1 -1
  83. package/dist/components/purify.es.js +2 -0
  84. package/dist/esm/{cell-renderer-D63AUZxa.js → cell-renderer-C2FLUrMN.js} +146 -2
  85. package/dist/esm/{client-layout-DCOAAH92.js → client-layout-Bc42CfJB.js} +7 -1
  86. package/dist/esm/{index-DiSe6vjv.js → index-CDiBvvJU.js} +468 -6
  87. package/dist/esm/index.js +1 -1
  88. package/dist/esm/loader.js +3 -3
  89. package/dist/esm/mosterdcomponents.js +3 -3
  90. package/dist/esm/{mrd-boolean-field_23.entry.js → mrd-boolean-field_25.entry.js} +5695 -5230
  91. package/dist/esm/mrd-merge-view.entry.js +5 -5
  92. package/dist/mosterdcomponents/index.esm.js +1 -1
  93. package/dist/mosterdcomponents/mosterdcomponents.esm.js +1 -1
  94. package/dist/mosterdcomponents/{p-2e16a7d0.entry.js → p-4ea5b97c.entry.js} +1 -1
  95. package/dist/mosterdcomponents/p-8736313b.entry.js +3 -0
  96. package/dist/mosterdcomponents/{p-DCOAAH92.js → p-Bc42CfJB.js} +1 -1
  97. package/dist/mosterdcomponents/p-BgsPVesg.js +1 -0
  98. package/dist/mosterdcomponents/p-CDiBvvJU.js +2 -0
  99. package/dist/types/components/documents/mrd-document-container/mrd-document-container.d.ts +7 -0
  100. package/dist/types/components/documents/mrd-document-list/mrd-document-list.d.ts +17 -0
  101. package/dist/types/components/documents/mrd-document-view/mrd-document-view.d.ts +2 -2
  102. package/dist/types/components/email/mrd-email-view/mrd-email-view.d.ts +52 -0
  103. package/dist/types/components/fields/file-upload-common.d.ts +19 -1
  104. package/dist/types/components/fields/mrd-file-field/mrd-file-field.d.ts +24 -10
  105. package/dist/types/components/fields/mrd-image-field/mrd-image-field.d.ts +27 -11
  106. package/dist/types/components/fields/mrd-relation-field/mrd-relation-field.d.ts +7 -0
  107. package/dist/types/components/form/mrd-form/mrd-form.d.ts +19 -3
  108. package/dist/types/components/invoice/mrd-invoice-view/mrd-invoice-view.d.ts +60 -0
  109. package/dist/types/components/layout/mrd-layout-section/layout-helpers.d.ts +3 -0
  110. package/dist/types/components/layout/mrd-layout-section/mrd-layout-section.d.ts +13 -0
  111. package/dist/types/components.d.ts +303 -19
  112. package/dist/types/types/client-layout.d.ts +13 -0
  113. package/dist/types/utils/document-attachments.d.ts +12 -5
  114. package/package.json +1 -1
  115. package/dist/mosterdcomponents/p-CnqDM0Gb.js +0 -1
  116. package/dist/mosterdcomponents/p-DiSe6vjv.js +0 -2
  117. package/dist/mosterdcomponents/p-b5f38707.entry.js +0 -3
@@ -19,7 +19,21 @@ async function apiRequest(method, path, token, body) {
19
19
  const text = await resp.text();
20
20
  let json;
21
21
  try { json = JSON.parse(text); } catch (_) { json = text; }
22
- return { status: resp.status, ok: resp.ok, body: json };
22
+ return { status: resp.status, ok: resp.ok, body: json, location: resp.headers.get('Location') };
23
+ }
24
+
25
+ /**
26
+ * Write (POST/PATCH) that returns no body — only a Location header
27
+ * (create/patch/merge on /data/**, per TASK-0203). Follows the Location
28
+ * header with a GET to hand callers the current record, mirroring
29
+ * content-app's ApiClientService create/update methods.
30
+ */
31
+ async function apiWriteAndFetch(method, path, token, payload) {
32
+ const write = await apiRequest(method, path, token, payload);
33
+ if (!write.ok) return write;
34
+ const href = write.location || path;
35
+ const { body } = await apiRequest('GET', href, token);
36
+ return { ...write, body };
23
37
  }
24
38
 
25
39
  /** Resolve a file/image reference href to a signed, directly-usable URL.
@@ -95,7 +109,7 @@ async function apiFetchPage(token, baseHref, pageNumber, sort = '') {
95
109
  }
96
110
 
97
111
  async function apiSubmitForm(token, tenantCode, pluralName, values) {
98
- const { status, ok, body } = await apiRequest('POST', `/data/${tenantCode}/${pluralName}`, token, values);
112
+ const { status, ok, body } = await apiWriteAndFetch('POST', `/data/${tenantCode}/${pluralName}`, token, values);
99
113
  return { status, ok, body };
100
114
  }
101
115
 
@@ -150,11 +164,12 @@ async function apiSearchMergeCandidates(token, tenantCode, mostSignificantClass,
150
164
 
151
165
  /**
152
166
  * Merge two DataObjects of the same basic type. Mirrors DataController.merge():
153
- * POST {winnerHref}/merge with { loser: loserHref, patch }. Returns the
154
- * patched winner record on success.
167
+ * POST {winnerHref}/merge with { loser: loserHref, patch }. Returns no body
168
+ * (204 + Location, per TASK-0203) — follows Location with a GET for the
169
+ * patched winner record.
155
170
  */
156
171
  async function apiMerge(token, winnerHref, loserHref, patch) {
157
- const { ok, status, body } = await apiRequest('POST', `${winnerHref}/merge`, token, { loser: loserHref, patch });
172
+ const { ok, status, body } = await apiWriteAndFetch('POST', `${winnerHref}/merge`, token, { loser: loserHref, patch });
158
173
  if (!ok) throw new Error(`${status}: ${typeof body === 'string' ? body : JSON.stringify(body)}`);
159
174
  return body;
160
175
  }
@@ -266,6 +266,41 @@ function initFilePreviewTab() {
266
266
  section.addEventListener('mrdDownload', (e) => logEvent('mrdDownload', e.detail));
267
267
  section.addEventListener('mrdNavigate', (e) => logEvent('mrdNavigate', e.detail));
268
268
  });
269
+
270
+ // Invoice View demo (commons.invoice archetype, TASK-0199) — the `lines`
271
+ // RELATED_VIEW is rendered inside mrd-invoice-view via slot="lines", but the
272
+ // page/sort data-loading stays owned by mrd-layout-section, exactly like any
273
+ // other RELATED_VIEW: answer mrdLoadViewPage the same way the Detail View tab
274
+ // does for appointments/share classes.
275
+ const invoiceSection = document.getElementById('invoice-view');
276
+ const invoicePreviewEl = document.getElementById('invoice-view-preview');
277
+ invoiceSection.items = window.EXAMPLE_INVOICE_ITEMS;
278
+ invoiceSection.data = window.EXAMPLE_INVOICE_DATA;
279
+ invoiceSection.archetypes = [window.EXAMPLE_INVOICE_ARCHETYPE];
280
+ invoiceSection.addEventListener('mrdLoadViewPage', async (e) => {
281
+ const { name, page } = e.detail;
282
+ logEvent('mrdLoadViewPage', e.detail);
283
+ await invoiceSection.setViewPage(name, page, window.EXAMPLE_INVOICE_LINES, window.EXAMPLE_INVOICE_LINES.length);
284
+ });
285
+ invoiceSection.addEventListener('mrdAttachments', (e) => {
286
+ logEvent('mrdAttachments', e.detail);
287
+ renderAttachmentPreview(invoicePreviewEl, e.detail.attachments);
288
+ });
289
+ invoiceSection.addEventListener('mrdDownload', (e) => logEvent('mrdDownload', e.detail));
290
+ invoiceSection.addEventListener('mrdNavigate', (e) => logEvent('mrdNavigate', e.detail));
291
+
292
+ // Email View demo (commons.email archetype, TASK-0199) — fully read-only, no
293
+ // RELATED_VIEW/table involved.
294
+ const emailSection = document.getElementById('email-view');
295
+ const emailPreviewEl = document.getElementById('email-view-preview');
296
+ emailSection.data = window.EXAMPLE_EMAIL_DATA;
297
+ emailSection.archetypes = [window.EXAMPLE_EMAIL_ARCHETYPE];
298
+ emailSection.addEventListener('mrdAttachments', (e) => {
299
+ logEvent('mrdAttachments', e.detail);
300
+ renderAttachmentPreview(emailPreviewEl, e.detail.attachments);
301
+ });
302
+ emailSection.addEventListener('mrdDownload', (e) => logEvent('mrdDownload', e.detail));
303
+ emailSection.addEventListener('mrdNavigate', (e) => logEvent('mrdNavigate', e.detail));
269
304
  }
270
305
 
271
306
  /* =====================================================================
@@ -620,7 +655,7 @@ async function renderSection(index) {
620
655
  try {
621
656
  const result = await apiRequest('POST', `${_baseHref}${path}`, authGetToken(), values);
622
657
  if (result.ok) {
623
- const href = result.body?._links?.self?.href;
658
+ const href = result.location;
624
659
  if (href) await section.setViewCreatedObject(name, href);
625
660
  } else {
626
661
  console.error('[mrdCreateObject] mislukt', result.status, result.body);
@@ -873,17 +908,20 @@ async function renderForm(layout, record = null, selfHref = null, containerEl =
873
908
  if (_meUser) form.me = _meUser;
874
909
  form.values = record ? initialValues : {};
875
910
 
876
- // Upload files immediately on selection; write binary URI back via setFieldValue
877
- form.addEventListener('mrdChange', async (e) => {
878
- const { name, value } = e.detail;
879
- if (!(value instanceof File)) return;
880
- try {
881
- const uri = await apiUploadFile(authGetToken(), _selectedTenant, value);
882
- await form.setFieldValue(name, uri);
883
- } catch (err) {
884
- console.error(`[upload] mislukt voor veld "${name}":`, err);
885
- }
886
- });
911
+ // Upload each selected file immediately (fires once per file for `multiple` fields);
912
+ // pass `file` back into setFieldValue so mrd-form can resolve the right array entry.
913
+ // The resolved value is { href, fileName } same shape mrd-table/mrd-layout-section
914
+ // already expect when reading FILE/IMAGE data back — so the filename displays correctly
915
+ // instead of falling back to the upload URI's UUID.
916
+ form.addEventListener('mrdUpload', async (e) => {
917
+ const { name, file } = e.detail;
918
+ try {
919
+ const uri = await apiUploadFile(authGetToken(), _selectedTenant, file);
920
+ await form.setFieldValue(name, { href: uri, fileName: file.name }, file);
921
+ } catch (err) {
922
+ console.error(`[upload] mislukt voor veld "${name}":`, err);
923
+ }
924
+ });
887
925
 
888
926
  form.addEventListener('mrdSubmit', async (e) => {
889
927
  document.getElementById('panel-response').innerHTML =
@@ -922,7 +960,7 @@ async function renderForm(layout, record = null, selfHref = null, containerEl =
922
960
  renderResponse(200, 'Geen wijzigingen.');
923
961
  return;
924
962
  }
925
- const result = await apiRequest('PATCH', selfHref, authGetToken(), patch);
963
+ const result = await apiWriteAndFetch('PATCH', selfHref, authGetToken(), patch);
926
964
  renderResponse(result.status, result.body);
927
965
  } else {
928
966
  // POST mode: new record
@@ -69,7 +69,7 @@ window.EXAMPLE_LAYOUT = {
69
69
  label: "Assets",
70
70
  items: [
71
71
  { type: "FIELD", name: "projectLogo", label: "Project Logo", dataType: "IMAGE", required: false, accept: "image/*", maxSize: 5242880 },
72
- { type: "FIELD", name: "attachments", label: "Attachments", dataType: "FILE", required: false, accept: ".pdf,.doc,.docx,.zip", maxSize: 10485760 }
72
+ { type: "FIELD", name: "attachments", label: "Attachments", dataType: "FILE", required: false, accept: ".pdf,.doc,.docx,.zip", maxSize: 10485760, multiple: true }
73
73
  ]
74
74
  },
75
75
  { type: "RELATION", name: "projectManager", label: "Project Manager", relatedClass: "Employee", displayType: "SEARCH", required: true, multiple: false },
@@ -91,7 +91,15 @@ window.EXAMPLE_VALUES = {
91
91
  budget: { amount: 150000, currency: "EUR" },
92
92
  isPublic: true,
93
93
  startDate: "2026-01-15",
94
- contactEmail: "team@mosterd.nl"
94
+ contactEmail: "team@mosterd.nl",
95
+ // FILE/IMAGE values from the API are { href, fileName } — same shape as everywhere
96
+ // else in this library (mrd-table, mrd-document-view) — so the field shows the real
97
+ // filename instead of the href's UUID.
98
+ projectLogo: { href: "/uploads/proj-9f2c/logo.png", fileName: "mosterd-logo.png" },
99
+ attachments: [
100
+ { href: "/uploads/proj-9f2c/9f2c-contract.pdf", fileName: "contract-2026.pdf" },
101
+ { href: "/uploads/proj-9f2c/1a3b-invoice.pdf", fileName: "invoice-042.pdf" },
102
+ ],
95
103
  };
96
104
 
97
105
  // ─── Companies detail view demo ────────────────────────────────────────────────
@@ -215,7 +223,9 @@ window.EXAMPLE_COMPANY_METADATA = {
215
223
  { type: 'FIELD', label: 'legal form extended', name: 'legalFormExtended', dataType: 'TEXT', required: false },
216
224
  { type: 'FIELD', label: 'rsin', name: 'rsin', dataType: 'TEXT', required: false },
217
225
  { type: 'FIELD', label: 'start date', name: 'startDate', dataType: 'DATE', required: false },
218
- { 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 },
219
229
  { type: 'FIELD', label: 'primary activity description', name: 'primaryActivityDescription', dataType: 'TEXT', required: false },
220
230
  { type: 'FIELD', label: 'primary activity code', name: 'primaryActivityCode', dataType: 'TEXT', required: false },
221
231
  { type: 'FIELD', label: 'total employees', name: 'totalEmployees', dataType: 'INTEGER', required: false },
@@ -235,6 +245,9 @@ window.EXAMPLE_COMPANY_METADATA = {
235
245
  { type: 'FIELD', label: 'mailing postal code', name: 'mailingPostalCode', dataType: 'TEXT', required: false },
236
246
  { type: 'FIELD', label: 'mailing city', name: 'mailingCity', dataType: 'TEXT', required: false },
237
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 },
238
251
  ],
239
252
  },
240
253
  {
@@ -441,3 +454,119 @@ window.EXAMPLE_DOCUMENT_DATA = {
441
454
  dossier: { href: '/data/demo/dossiers/42', name: 'Acme Corp — Dossier 2026' },
442
455
  },
443
456
  };
457
+
458
+ // ─── Invoice View demo (commons.invoice archetype, TASK-0199) ────────────────
459
+ // Exercises mrd-invoice-view via mrd-layout-section's archetype path. `lines`
460
+ // is a real RELATED_VIEW item bound to the commons.invoiceLine archetype — it
461
+ // is rendered *inside* mrd-invoice-view via slot="lines" (mrd-layout-section
462
+ // still owns the table's own page/sort wiring, see renderInvoiceObject()).
463
+
464
+ window.EXAMPLE_INVOICE_ARCHETYPE = {
465
+ archetype: 'commons.invoice',
466
+ slots: {
467
+ invoiceNumber: 'invoiceNumber', invoiceDate: 'invoiceDate', dueDate: 'dueDate',
468
+ netAmount: 'netAmount', taxAmount: 'taxAmount', amount: 'amount', status: 'status',
469
+ customer: 'customer', supplier: 'supplier', lines: 'lines', attachments: 'attachments', container: 'dossier',
470
+ },
471
+ };
472
+
473
+ window.EXAMPLE_INVOICE_ITEMS = [
474
+ {
475
+ type: 'FIELD', name: 'status', label: 'Status', dataType: 'LIST', required: false,
476
+ listItems: [
477
+ { key: 'draft', label: 'Concept', color: '#6b7280', backgroundColor: '#f3f4f6' },
478
+ { key: 'sent', label: 'Verzonden', color: '#1d4ed8', backgroundColor: '#dbeafe' },
479
+ { key: 'paid', label: 'Betaald', color: '#15803d', backgroundColor: '#dcfce7' },
480
+ ],
481
+ },
482
+ {
483
+ type: 'RELATED_VIEW', label: 'Factuurregels', name: 'lines',
484
+ relatedClass: 'demo.invoiceLine', fromClass: 'demo.invoice', showTitle: true,
485
+ archetypes: [{ archetype: 'commons.invoiceLine', slots: { description: 'description', quantity: 'quantity', unitPrice: 'unitPrice', amount: 'amount' } }],
486
+ view: {
487
+ name: null,
488
+ defaultView: true,
489
+ values: [
490
+ { type: 'FIELD', label: 'Omschrijving', name: 'description', dataType: 'TEXT', required: true },
491
+ { type: 'FIELD', label: 'Aantal', name: 'quantity', dataType: 'DECIMAL', required: true },
492
+ { type: 'FIELD', label: 'Prijs', name: 'unitPrice', dataType: 'CURRENCY', required: true },
493
+ { type: 'FIELD', label: 'Bedrag', name: 'amount', dataType: 'CURRENCY', required: true, aggregate: 'sum' },
494
+ ],
495
+ type: 'demo.invoiceLine',
496
+ singularLabel: 'Factuurregel',
497
+ pluralLabel: 'Factuurregels',
498
+ },
499
+ },
500
+ ];
501
+
502
+ window.EXAMPLE_INVOICE_DATA = {
503
+ invoiceNumber: 'INV-2026-0142',
504
+ invoiceDate: '2026-06-01',
505
+ dueDate: '2026-06-30',
506
+ status: 'sent',
507
+ netAmount: { amount: 1000, currency: 'EUR' },
508
+ taxAmount: { amount: 210, currency: 'EUR' },
509
+ amount: { amount: 1210, currency: 'EUR' },
510
+ attachments: [
511
+ { href: '/data/demo/invoices/142/attachments/0', fileName: 'invoice-142.pdf', mimeType: 'application/pdf' },
512
+ ],
513
+ _links: {
514
+ self: { href: '/data/demo/invoices/142' },
515
+ customer: { href: '/data/demo/entities/acme', name: 'Acme Corp.' },
516
+ supplier: { href: '/data/demo/entities/mosterd', name: 'Mosterd B.V.' },
517
+ dossier: { href: '/data/demo/dossiers/42', name: 'Acme Corp — Dossier 2026' },
518
+ },
519
+ };
520
+
521
+ window.EXAMPLE_INVOICE_LINES = [
522
+ {
523
+ description: 'Consultancy — juni 2026',
524
+ quantity: 10,
525
+ unitPrice: { amount: 80, currency: 'EUR' },
526
+ amount: { amount: 800, currency: 'EUR' },
527
+ _links: { self: { href: '/data/demo/invoiceLines/1' } },
528
+ },
529
+ {
530
+ description: 'Projectmanagement',
531
+ quantity: 4,
532
+ unitPrice: { amount: 50, currency: 'EUR' },
533
+ amount: { amount: 200, currency: 'EUR' },
534
+ _links: { self: { href: '/data/demo/invoiceLines/2' } },
535
+ },
536
+ ];
537
+
538
+ // ─── Email View demo (commons.email archetype, TASK-0199) ────────────────────
539
+ // Exercises mrd-email-view via mrd-layout-section's archetype path. Fully
540
+ // read-only — no RELATED_VIEW/table involved.
541
+
542
+ window.EXAMPLE_EMAIL_ARCHETYPE = {
543
+ archetype: 'commons.email',
544
+ slots: {
545
+ subject: 'subject', date: 'date', from: 'from', to: 'to', cc: 'cc',
546
+ bodyHtml: 'bodyHtml', bodyText: 'bodyText', headers: 'headers',
547
+ attachments: 'attachments', container: 'dossier',
548
+ },
549
+ };
550
+
551
+ window.EXAMPLE_EMAIL_DATA = {
552
+ subject: 'RE: Contract 2026 — laatste versie bijgevoegd',
553
+ date: '2026-06-02T14:32:00',
554
+ bodyHtml: '<p>Beste Jan,</p><p>Bijgaand de laatste versie van het contract, inclusief de besproken wijzigingen in artikel 4.</p><p>Met vriendelijke groet,<br>Anne</p>',
555
+ headers: 'Message-ID: <abc123@mail.example.com>\nReceived: from mail.example.com by mx.mosterd.com',
556
+ attachments: [
557
+ { href: '/data/demo/emails/9/attachments/0', fileName: 'contract-2026-v3.pdf', mimeType: 'application/pdf' },
558
+ { href: '/data/demo/emails/9/attachments/1', fileName: 'wijzigingen.docx' },
559
+ ],
560
+ _links: {
561
+ from: { href: '/data/demo/entities/anne', name: 'Anne de Vries <anne@mosterd.com>' },
562
+ to: {
563
+ href: '/data/demo/entities/jan', name: 'Jan Bakker <jan@acme.com>',
564
+ values: [{ href: '/data/demo/entities/jan', name: 'Jan Bakker <jan@acme.com>' }],
565
+ },
566
+ cc: {
567
+ href: '/data/demo/entities/pietcc', name: 'Piet Claassen <piet@acme.com>',
568
+ values: [{ href: '/data/demo/entities/pietcc', name: 'Piet Claassen <piet@acme.com>' }],
569
+ },
570
+ dossier: { href: '/data/demo/dossiers/42', name: 'Acme Corp — Dossier 2026' },
571
+ },
572
+ };
@@ -72,6 +72,12 @@ export var ClientLayoutNavigateBehaviour;
72
72
  })(ClientLayoutNavigateBehaviour || (ClientLayoutNavigateBehaviour = {}));
73
73
  /** Well-known archetype ids the client renders specially. */
74
74
  export const ARCHETYPE_DOCUMENT = 'commons.document';
75
+ export const ARCHETYPE_INVOICE = 'commons.invoice';
76
+ export const ARCHETYPE_EMAIL = 'commons.email';
77
+ /** Bound by a `commons.invoice`'s `lines` relation target class. Used to find the
78
+ * RELATED_VIEW item for the line items generically, by archetype id rather than
79
+ * by name/class string. */
80
+ export const ARCHETYPE_INVOICE_LINE = 'commons.invoiceLine';
75
81
  /**
76
82
  * Find a binding for `archetypeId` in a published `archetypes` list.
77
83
  * Returns `undefined` when the archetype is absent — callers fall back to the
@@ -1,20 +1,33 @@
1
1
  /**
2
- * Extract the `commons.document` archetype's file slot from a data record as a
3
- * `DocumentAttachment[]` — empty when the slot is unmapped or has no value.
4
- * Shared by mrd-document-view (on load/data-change) and mrd-document-container
5
- * (row-click prefetch on mrdNavigate), so both report the exact same shape.
2
+ * Extract an archetype's file slot from a data record as a `DocumentAttachment[]` —
3
+ * empty when the slot is unmapped or has no value. `slot` defaults to `'file'` (the
4
+ * commons.document single-file slot); pass `'attachments'` for the multi-file slots
5
+ * on commons.email / commons.invoice. A single-object slot value yields a one-entry
6
+ * array, an array-valued slot yields one entry per item.
7
+ * Shared by mrd-document-view / mrd-invoice-view / mrd-email-view (on load/data-change)
8
+ * and mrd-document-container (row-click prefetch on mrdNavigate), so all report the
9
+ * exact same shape.
6
10
  */
7
- export function attachmentsFromRecord(archetype, record) {
8
- var _a, _b, _c, _d;
9
- const field = (_a = archetype === null || archetype === void 0 ? void 0 : archetype.slots) === null || _a === void 0 ? void 0 : _a['file'];
11
+ export function attachmentsFromRecord(archetype, record, slot = 'file') {
12
+ var _a;
13
+ const field = (_a = archetype === null || archetype === void 0 ? void 0 : archetype.slots) === null || _a === void 0 ? void 0 : _a[slot];
10
14
  if (!field)
11
15
  return [];
12
- const fileRaw = record === null || record === void 0 ? void 0 : record[field];
13
- if (!fileRaw)
16
+ const raw = record === null || record === void 0 ? void 0 : record[field];
17
+ if (!raw)
14
18
  return [];
15
- return [{
16
- href: fileRaw.href,
17
- fileName: (_b = fileRaw.fileName) !== null && _b !== void 0 ? _b : String(fileRaw),
18
- mimeType: (_d = (_c = fileRaw.mimeType) !== null && _c !== void 0 ? _c : fileRaw.contentType) !== null && _d !== void 0 ? _d : undefined,
19
- }];
19
+ const items = Array.isArray(raw) ? raw : [raw];
20
+ return items.filter(Boolean).map(item => {
21
+ var _a, _b, _c;
22
+ return ({
23
+ href: item.href,
24
+ fileName: (_a = item.fileName) !== null && _a !== void 0 ? _a : String(item),
25
+ mimeType: (_c = (_b = item.mimeType) !== null && _b !== void 0 ? _b : item.contentType) !== null && _c !== void 0 ? _c : undefined,
26
+ });
27
+ });
28
+ }
29
+ /** Dedup key for the componentDidLoad/@Watch('data') "emit mrdAttachments only on
30
+ * change" guard shared by mrd-document-view / mrd-invoice-view / mrd-email-view. */
31
+ export function attachmentsKey(attachments) {
32
+ return attachments.map(a => a.href).join('|');
20
33
  }