@mmlogic/components 0.4.0 → 0.4.2

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 (92) hide show
  1. package/dist/cjs/index.cjs.js +8 -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_23.cjs.entry.js → mrd-boolean-field_24.cjs.entry.js} +348 -39
  5. package/dist/collection/collection-manifest.json +1 -0
  6. package/dist/collection/components/documents/mrd-document-container/mrd-document-container.css +14 -1
  7. package/dist/collection/components/documents/mrd-document-container/mrd-document-container.js +22 -7
  8. package/dist/collection/components/documents/mrd-document-list/mrd-document-list.css +8 -5
  9. package/dist/collection/components/documents/mrd-document-list/tree-view.js +13 -1
  10. package/dist/collection/components/documents/mrd-document-view/mrd-document-view.css +39 -29
  11. package/dist/collection/components/documents/mrd-document-view/mrd-document-view.js +109 -8
  12. package/dist/collection/components/documents/mrd-file-preview/file-preview-kind.js +30 -0
  13. package/dist/collection/components/documents/mrd-file-preview/mrd-file-preview.css +59 -0
  14. package/dist/collection/components/documents/mrd-file-preview/mrd-file-preview.js +212 -0
  15. package/dist/collection/components/fields/mrd-boolean-field/mrd-boolean-field.js +1 -1
  16. package/dist/collection/components/fields/mrd-currency-field/mrd-currency-field.js +1 -1
  17. package/dist/collection/components/fields/mrd-date-field/mrd-date-field.js +1 -1
  18. package/dist/collection/components/fields/mrd-datetime-field/mrd-datetime-field.js +1 -1
  19. package/dist/collection/components/fields/mrd-email-field/mrd-email-field.js +1 -1
  20. package/dist/collection/components/fields/mrd-file-field/mrd-file-field.js +1 -1
  21. package/dist/collection/components/fields/mrd-image-field/mrd-image-field.js +1 -1
  22. package/dist/collection/components/fields/mrd-longtext-field/mrd-longtext-field.js +1 -1
  23. package/dist/collection/components/fields/mrd-number-field/mrd-number-field.js +1 -1
  24. package/dist/collection/components/fields/mrd-secret-field/mrd-secret-field.js +2 -2
  25. package/dist/collection/components/fields/mrd-text-field/mrd-text-field.js +1 -1
  26. package/dist/collection/components/fields/mrd-textarea-field/mrd-textarea-field.js +1 -1
  27. package/dist/collection/components/fields/mrd-time-field/mrd-time-field.js +1 -1
  28. package/dist/collection/components/layout/mrd-layout-section/mrd-layout-section.css +5 -0
  29. package/dist/collection/components/layout/mrd-layout-section/mrd-layout-section.js +91 -8
  30. package/dist/collection/components/table/mrd-table/mrd-table.css +20 -0
  31. package/dist/collection/components/table/mrd-table/mrd-table.js +124 -5
  32. package/dist/collection/components/table/mrd-table/virtual-scroll.js +14 -0
  33. package/dist/collection/dev/api.js +9 -0
  34. package/dist/collection/dev/app.js +101 -3
  35. package/dist/collection/dev/example-data.js +24 -0
  36. package/dist/collection/types/client-layout.js +8 -0
  37. package/dist/collection/utils/i18n.js +40 -0
  38. package/dist/components/client-layout.js +1 -1
  39. package/dist/components/i18n.js +1 -1
  40. package/dist/components/index.js +1 -1
  41. package/dist/components/mrd-boolean-field2.js +1 -1
  42. package/dist/components/mrd-currency-field2.js +1 -1
  43. package/dist/components/mrd-date-field2.js +1 -1
  44. package/dist/components/mrd-datetime-field2.js +1 -1
  45. package/dist/components/mrd-document-container2.js +1 -1
  46. package/dist/components/mrd-document-list2.js +1 -1
  47. package/dist/components/mrd-document-view2.js +1 -1
  48. package/dist/components/mrd-email-field2.js +1 -1
  49. package/dist/components/mrd-file-field2.js +1 -1
  50. package/dist/components/mrd-file-preview.d.ts +11 -0
  51. package/dist/components/mrd-file-preview.js +1 -0
  52. package/dist/components/mrd-file-preview2.js +1 -0
  53. package/dist/components/mrd-image-field2.js +1 -1
  54. package/dist/components/mrd-layout-section.js +1 -1
  55. package/dist/components/mrd-longtext-field2.js +1 -1
  56. package/dist/components/mrd-number-field2.js +1 -1
  57. package/dist/components/mrd-secret-field2.js +1 -1
  58. package/dist/components/mrd-table2.js +1 -1
  59. package/dist/components/mrd-text-field2.js +1 -1
  60. package/dist/components/mrd-textarea-field2.js +1 -1
  61. package/dist/components/mrd-time-field2.js +1 -1
  62. package/dist/esm/index.js +9 -1
  63. package/dist/esm/loader.js +1 -1
  64. package/dist/esm/mosterdcomponents.js +1 -1
  65. package/dist/esm/{mrd-boolean-field_23.entry.js → mrd-boolean-field_24.entry.js} +349 -41
  66. package/dist/mosterdcomponents/cell-renderer-PbcEU4ph.js.map +1 -0
  67. package/dist/mosterdcomponents/client-layout-DCOAAH92.js.map +1 -0
  68. package/dist/mosterdcomponents/field-helpers-CASS3akr.js.map +1 -0
  69. package/dist/mosterdcomponents/file-upload-common-D1ndi6cN.js.map +1 -0
  70. package/dist/mosterdcomponents/format-BAfsQfy1.js.map +1 -0
  71. package/dist/mosterdcomponents/i18n-B1IIkRF9.js.map +1 -0
  72. package/dist/mosterdcomponents/index-AokUFVX0.js.map +1 -0
  73. package/dist/mosterdcomponents/index-CprJSlzK.js.map +1 -0
  74. package/dist/mosterdcomponents/index.esm.js +1 -1
  75. package/dist/mosterdcomponents/index.esm.js.map +1 -0
  76. package/dist/mosterdcomponents/mosterdcomponents.esm.js +1 -1
  77. package/dist/mosterdcomponents/mosterdcomponents.esm.js.map +1 -0
  78. package/dist/mosterdcomponents/p-aae8a9dc.entry.js +1 -0
  79. package/dist/mosterdcomponents/query-params-B1HfnMOl.js.map +1 -0
  80. package/dist/mosterdcomponents/quill-C9pgw_k-.js.map +1 -0
  81. package/dist/mosterdcomponents/validation-DbEViuiw.js.map +1 -0
  82. package/dist/types/components/documents/mrd-document-container/mrd-document-container.d.ts +6 -2
  83. package/dist/types/components/documents/mrd-document-view/mrd-document-view.d.ts +29 -1
  84. package/dist/types/components/documents/mrd-file-preview/file-preview-kind.d.ts +9 -0
  85. package/dist/types/components/documents/mrd-file-preview/mrd-file-preview.d.ts +41 -0
  86. package/dist/types/components/layout/mrd-layout-section/mrd-layout-section.d.ts +17 -1
  87. package/dist/types/components/table/mrd-table/mrd-table.d.ts +23 -0
  88. package/dist/types/components/table/mrd-table/virtual-scroll.d.ts +7 -0
  89. package/dist/types/components.d.ts +194 -7
  90. package/dist/types/types/client-layout.d.ts +10 -0
  91. package/package.json +1 -1
  92. package/dist/mosterdcomponents/p-9b3c0917.entry.js +0 -1
@@ -4,7 +4,9 @@ import { buildListPath } from "../../../utils/query-params";
4
4
  import { TEXT_TYPES, NON_INTERACTIVE_TYPES, parseDefaultSort, buildSortParam, buildTableQueryParams, buildAggregationQs, filterHasValue, normalizeDatetimeFilter, datetimeFilterToDisplay, } from "./table-query";
5
5
  import { FilterIcon, TableCell, TotalsRow, TextblockModal, JsonModal, Toolbar } from "./table-parts";
6
6
  import { FilterPopup } from "./table-filter-popup";
7
- import { REQUEST_DEBOUNCE_MS, visibleCount, computeWindow, pageRange, getRowFromPages, computeEffectiveTotal, isTotalKnownFromPages, } from "./virtual-scroll";
7
+ import { REQUEST_DEBOUNCE_MS, REQUEST_TIMEOUT_MS, MAX_RETRY_ATTEMPTS, visibleCount, computeWindow, pageRange, getRowFromPages, computeEffectiveTotal, isTotalKnownFromPages, getStalePages, } from "./virtual-scroll";
8
+ /** How often (ms) to scan outstanding page requests for staleness. */
9
+ const STALE_CHECK_INTERVAL_MS = 2000;
8
10
  /** Breedte van de filterpopup in px — voor overflow-correctie. */
9
11
  const POPUP_WIDTH = 280;
10
12
  export class MrdTable {
@@ -12,6 +14,11 @@ export class MrdTable {
12
14
  // ── Non-state internals ────────────────────────────────────────────────────
13
15
  this.pendingPages = new Set();
14
16
  this.debounceTimer = null;
17
+ /** Timestamp of the last mrdLoadPage emission per outstanding page — drives stale-request detection. */
18
+ this.requestedAt = new Map();
19
+ /** Retry count per page since it first became stale. */
20
+ this.retryAttempts = new Map();
21
+ this.staleCheckTimer = null;
15
22
  this.outsideClickHandler = null;
16
23
  this.keydownHandler = null;
17
24
  this.createPickerClickHandler = null;
@@ -34,11 +41,16 @@ export class MrdTable {
34
41
  this.rowHeight = 36;
35
42
  /** Height of the scroll container in px. */
36
43
  this.tableHeight = 500;
44
+ /** Time (ms) to wait for setPage() before retrying a page request; after
45
+ * MAX_RETRY_ATTEMPTS retries the page is marked failed (see failedPages). */
46
+ this.requestTimeoutMs = REQUEST_TIMEOUT_MS;
37
47
  // ── Internal state ─────────────────────────────────────────────────────────
38
48
  /** Index into allViews[] for the currently displayed view. 0 = primary, 1+ = alternatives. */
39
49
  this.activeViewIdx = 0;
40
50
  this.loadedPages = new Map();
41
51
  this.requestedPages = new Set();
52
+ /** Pages that timed out MAX_RETRY_ATTEMPTS times — rendered as a retry row instead of shimmer. */
53
+ this.failedPages = new Set();
42
54
  this.renderStart = 0;
43
55
  this.renderEnd = 0;
44
56
  this.colWidths = [];
@@ -97,6 +109,9 @@ export class MrdTable {
97
109
  var _a, _b, _c;
98
110
  this.applyDefaultSort((_c = (_b = (_a = this.item) === null || _a === void 0 ? void 0 : _a.view) === null || _b === void 0 ? void 0 : _b.defaultSort) !== null && _c !== void 0 ? _c : '');
99
111
  }
112
+ componentDidLoad() {
113
+ this.staleCheckTimer = setInterval(() => this.checkStalePages(), STALE_CHECK_INTERVAL_MS);
114
+ }
100
115
  // ── Helpers ────────────────────────────────────────────────────────────────
101
116
  applyDefaultSort(defaultSort) {
102
117
  const { field, dir } = parseDefaultSort(defaultSort);
@@ -117,6 +132,9 @@ export class MrdTable {
117
132
  this.pendingPages.clear();
118
133
  this.loadedPages = new Map();
119
134
  this.requestedPages = new Set();
135
+ this.failedPages = new Set();
136
+ this.requestedAt.clear();
137
+ this.retryAttempts.clear();
120
138
  this.colWidths = [];
121
139
  this.colWidthMeasureScheduled = false;
122
140
  this.scrollTop = 0;
@@ -132,7 +150,7 @@ export class MrdTable {
132
150
  this.aggregationsPending = true;
133
151
  this.minKnownTotal = 0;
134
152
  // Always request page 0 — totalElements may be unknown (0) on first load.
135
- this.mrdLoadPage.emit({ page: 0, sort: this.sortParam(), path: this.buildDataPath(), qs: this.buildQueryParams(0) });
153
+ this.emitLoadPage(0);
136
154
  this.requestedPages = new Set([0]);
137
155
  }
138
156
  /**
@@ -163,6 +181,13 @@ export class MrdTable {
163
181
  const next = new Map(this.loadedPages);
164
182
  next.set(pageNumber, rows);
165
183
  this.loadedPages = next;
184
+ this.requestedAt.delete(pageNumber);
185
+ this.retryAttempts.delete(pageNumber);
186
+ if (this.failedPages.has(pageNumber)) {
187
+ const nextFailed = new Set(this.failedPages);
188
+ nextFailed.delete(pageNumber);
189
+ this.failedPages = nextFailed;
190
+ }
166
191
  if (pageNumber === 0 && this.aggregationsPending) {
167
192
  this.aggregationsPending = false;
168
193
  const hasAggColumns = this.columns.some(c => c.type === 'FIELD' && c.aggregate);
@@ -181,6 +206,10 @@ export class MrdTable {
181
206
  }
182
207
  // ── Lifecycle ──────────────────────────────────────────────────────────────
183
208
  disconnectedCallback() {
209
+ if (this.staleCheckTimer !== null) {
210
+ clearInterval(this.staleCheckTimer);
211
+ this.staleCheckTimer = null;
212
+ }
184
213
  if (this.outsideClickHandler) {
185
214
  document.removeEventListener('click', this.outsideClickHandler);
186
215
  this.outsideClickHandler = null;
@@ -382,6 +411,9 @@ export class MrdTable {
382
411
  this.pendingPages.clear();
383
412
  this.loadedPages = new Map();
384
413
  this.requestedPages = new Set();
414
+ this.failedPages = new Set();
415
+ this.requestedAt.clear();
416
+ this.retryAttempts.clear();
385
417
  this.colWidths = [];
386
418
  this.colWidthMeasureScheduled = false;
387
419
  this.scrollTop = 0;
@@ -412,6 +444,11 @@ export class MrdTable {
412
444
  this.resetPages();
413
445
  this.emitPagesForWindow(this.renderStart, this.renderEnd);
414
446
  }
447
+ /** Emits mrdLoadPage for a page and records the request timestamp for stale-request detection. */
448
+ emitLoadPage(page) {
449
+ this.mrdLoadPage.emit({ page, sort: this.sortParam(), path: this.buildDataPath(), qs: this.buildQueryParams(page) });
450
+ this.requestedAt.set(page, Date.now());
451
+ }
415
452
  emitPagesForWindow(start, end) {
416
453
  const { firstPage, lastPage } = pageRange(start, end, this.pageSize);
417
454
  const next = new Set(this.requestedPages);
@@ -419,7 +456,7 @@ export class MrdTable {
419
456
  for (let p = firstPage; p <= lastPage; p++) {
420
457
  if (!this.loadedPages.has(p) && !next.has(p)) {
421
458
  next.add(p);
422
- this.mrdLoadPage.emit({ page: p, sort: this.sortParam(), path: this.buildDataPath(), qs: this.buildQueryParams(p) });
459
+ this.emitLoadPage(p);
423
460
  changed = true;
424
461
  }
425
462
  }
@@ -458,13 +495,53 @@ export class MrdTable {
458
495
  if (pageEnd < this.renderStart || pageStart > this.renderEnd)
459
496
  continue;
460
497
  next.add(page);
461
- this.mrdLoadPage.emit({ page, sort: this.sortParam(), path: this.buildDataPath(), qs: this.buildQueryParams(page) });
498
+ this.emitLoadPage(page);
462
499
  changed = true;
463
500
  }
464
501
  this.pendingPages.clear();
465
502
  if (changed)
466
503
  this.requestedPages = next;
467
504
  }
505
+ /** Scans outstanding requests for staleness: retries up to MAX_RETRY_ATTEMPTS times,
506
+ * then gives up and marks the page as failed (rendered as a retry row). */
507
+ checkStalePages() {
508
+ var _a;
509
+ if (this.requestedAt.size === 0)
510
+ return;
511
+ const stale = getStalePages(this.requestedAt, Date.now(), this.requestTimeoutMs)
512
+ .filter(p => !this.loadedPages.has(p));
513
+ if (stale.length === 0)
514
+ return;
515
+ let nextFailed = null;
516
+ for (const page of stale) {
517
+ const attempts = (_a = this.retryAttempts.get(page)) !== null && _a !== void 0 ? _a : 0;
518
+ if (attempts < MAX_RETRY_ATTEMPTS) {
519
+ this.retryAttempts.set(page, attempts + 1);
520
+ this.emitLoadPage(page);
521
+ }
522
+ else {
523
+ this.requestedAt.delete(page);
524
+ this.retryAttempts.delete(page);
525
+ if (!nextFailed)
526
+ nextFailed = new Set(this.failedPages);
527
+ nextFailed.add(page);
528
+ this.mrdPageError.emit({ page });
529
+ }
530
+ }
531
+ if (nextFailed)
532
+ this.failedPages = nextFailed;
533
+ }
534
+ /** Manually retry a page the user gave up on (clicked the retry row). */
535
+ retryPage(page) {
536
+ const next = new Set(this.failedPages);
537
+ next.delete(page);
538
+ this.failedPages = next;
539
+ this.retryAttempts.delete(page);
540
+ this.emitLoadPage(page);
541
+ const requested = new Set(this.requestedPages);
542
+ requested.add(page);
543
+ this.requestedPages = requested;
544
+ }
468
545
  // ── Filter helpers ─────────────────────────────────────────────────────────
469
546
  handleFilterToggle() {
470
547
  this.filterMode = !this.filterMode;
@@ -737,7 +814,13 @@ export class MrdTable {
737
814
  for (let i = this.renderStart; i <= clampedEnd; i++) {
738
815
  const row = this.getRow(i);
739
816
  if (row === null) {
740
- renderedRows.push(h("tr", { class: "mrd-table__row mrd-table__row--loading" }, h("td", { class: "mrd-table__cell--placeholder", colSpan: colCount }, h("span", { class: "mrd-table__placeholder-bar" }))));
817
+ const page = Math.floor(i / this.pageSize);
818
+ if (this.failedPages.has(page)) {
819
+ renderedRows.push(h("tr", { class: "mrd-table__row mrd-table__row--failed" }, h("td", { class: "mrd-table__cell--failed", colSpan: colCount }, h("button", { type: "button", class: "mrd-table__retry-button", onClick: () => this.retryPage(page) }, t('load_failed_retry', this.locale)))));
820
+ }
821
+ else {
822
+ renderedRows.push(h("tr", { class: "mrd-table__row mrd-table__row--loading" }, h("td", { class: "mrd-table__cell--placeholder", colSpan: colCount }, h("span", { class: "mrd-table__placeholder-bar" }))));
823
+ }
741
824
  }
742
825
  else {
743
826
  renderedRows.push(h("tr", { class: "mrd-table__row mrd-table__row--clickable", style: { background: i % 2 === 0 ? '' : 'var(--mrd-color-neutral-100)' }, onClick: () => this.mrdRowClick.emit(row) }, this.columns.map(col => this.renderCell(col, row))));
@@ -945,6 +1028,26 @@ export class MrdTable {
945
1028
  "reflect": false,
946
1029
  "attribute": "table-height",
947
1030
  "defaultValue": "500"
1031
+ },
1032
+ "requestTimeoutMs": {
1033
+ "type": "number",
1034
+ "mutable": false,
1035
+ "complexType": {
1036
+ "original": "number",
1037
+ "resolved": "number",
1038
+ "references": {}
1039
+ },
1040
+ "required": false,
1041
+ "optional": false,
1042
+ "docs": {
1043
+ "tags": [],
1044
+ "text": "Time (ms) to wait for setPage() before retrying a page request; after\nMAX_RETRY_ATTEMPTS retries the page is marked failed (see failedPages)."
1045
+ },
1046
+ "getter": false,
1047
+ "setter": false,
1048
+ "reflect": false,
1049
+ "attribute": "request-timeout-ms",
1050
+ "defaultValue": "REQUEST_TIMEOUT_MS"
948
1051
  }
949
1052
  };
950
1053
  }
@@ -953,6 +1056,7 @@ export class MrdTable {
953
1056
  "activeViewIdx": {},
954
1057
  "loadedPages": {},
955
1058
  "requestedPages": {},
1059
+ "failedPages": {},
956
1060
  "renderStart": {},
957
1061
  "renderEnd": {},
958
1062
  "colWidths": {},
@@ -989,6 +1093,21 @@ export class MrdTable {
989
1093
  "resolved": "{ page: number; sort: string; path: string; qs: string; }",
990
1094
  "references": {}
991
1095
  }
1096
+ }, {
1097
+ "method": "mrdPageError",
1098
+ "name": "mrdPageError",
1099
+ "bubbles": true,
1100
+ "cancelable": true,
1101
+ "composed": true,
1102
+ "docs": {
1103
+ "tags": [],
1104
+ "text": "Fired when a page request timed out MAX_RETRY_ATTEMPTS times without setPage() being called.\nPurely informational \u2014 the table shows its own retry row regardless of whether a host listens."
1105
+ },
1106
+ "complexType": {
1107
+ "original": "{ page: number }",
1108
+ "resolved": "{ page: number; }",
1109
+ "references": {}
1110
+ }
992
1111
  }, {
993
1112
  "method": "mrdRowClick",
994
1113
  "name": "mrdRowClick",
@@ -7,6 +7,10 @@ export const BUFFER = 10;
7
7
  /** Wait this long (ms) after the last scroll event before requesting pages,
8
8
  * so pages the user quickly scrolls past are skipped. */
9
9
  export const REQUEST_DEBOUNCE_MS = 150;
10
+ /** Default time (ms) to wait for setPage() before treating a request as stale and retrying. */
11
+ export const REQUEST_TIMEOUT_MS = 15000;
12
+ /** Max retries for a stale page request before giving up and showing a retry row. */
13
+ export const MAX_RETRY_ATTEMPTS = 3;
10
14
  export function visibleCount(tableHeight, rowHeight) {
11
15
  return Math.ceil(tableHeight / rowHeight);
12
16
  }
@@ -47,3 +51,13 @@ export function isTotalKnownFromPages(loadedPages, pageSize) {
47
51
  }
48
52
  return false;
49
53
  }
54
+ /** Pages whose request timestamp is older than timeoutMs — candidates for retry or failure.
55
+ * Caller is expected to already exclude pages present in loadedPages. */
56
+ export function getStalePages(requestedAt, now, timeoutMs) {
57
+ const stale = [];
58
+ for (const [page, ts] of requestedAt) {
59
+ if (now - ts >= timeoutMs)
60
+ stale.push(page);
61
+ }
62
+ return stale;
63
+ }
@@ -22,6 +22,15 @@ async function apiRequest(method, path, token, body) {
22
22
  return { status: resp.status, ok: resp.ok, body: json };
23
23
  }
24
24
 
25
+ /** Resolve a file/image reference href to a signed, directly-usable URL.
26
+ * Mirrors content-app's ApiClientService.getDownloadLink(): a plain GET on
27
+ * the href, response body is the signed URL string. */
28
+ async function apiGetDownloadLink(token, href) {
29
+ const { ok, status, body } = await apiRequest('GET', href, token);
30
+ if (!ok) throw new Error(`${status}: ${typeof body === 'string' ? body : JSON.stringify(body)}`);
31
+ return body;
32
+ }
33
+
25
34
  async function apiFetchTenants(token) {
26
35
  const { ok, status, body } = await apiRequest('GET', '/tenants', token);
27
36
  if (!ok) throw new Error(`${status}: ${typeof body === 'string' ? body : JSON.stringify(body)}`);
@@ -103,7 +103,7 @@ function clearEventLog() {
103
103
  ===================================================================== */
104
104
 
105
105
  function showTab(name) {
106
- const names = ['embedded', 'field-types', 'detail-view', 'live-api'];
106
+ const names = ['embedded', 'field-types', 'detail-view', 'file-preview', 'live-api'];
107
107
  document.querySelectorAll('.tab-btn').forEach((btn, i) => {
108
108
  btn.classList.toggle('active', names[i] === name);
109
109
  });
@@ -111,6 +111,7 @@ function showTab(name) {
111
111
  document.getElementById('tab-' + n).classList.toggle('active', n === name);
112
112
  });
113
113
  if (name === 'detail-view') initDetailViewTab();
114
+ if (name === 'file-preview') initFilePreviewTab();
114
115
  }
115
116
 
116
117
  /* =====================================================================
@@ -197,6 +198,63 @@ function showCompanyList() {
197
198
  document.getElementById('detail-table-panel').style.display = 'block';
198
199
  }
199
200
 
201
+ /* =====================================================================
202
+ FILE PREVIEW TAB
203
+ ===================================================================== */
204
+
205
+ let _filePreviewInitialized = false;
206
+
207
+ /**
208
+ * Deliberately lazy: a PDF src loads Chrome's PDF-viewer-extension iframe
209
+ * immediately (display:none on an inactive tab-pane doesn't stop an iframe
210
+ * from loading), which detaches DevTools for the whole tab — regardless of
211
+ * which tab is actually visible. Only wiring this up on first visit keeps
212
+ * DevTools usable while working on other tabs.
213
+ */
214
+ function initFilePreviewTab() {
215
+ if (_filePreviewInitialized) return;
216
+ _filePreviewInitialized = true;
217
+
218
+ const pdfDemo = document.getElementById('preview-pdf-demo');
219
+ pdfDemo.src = '/dev/sample.pdf';
220
+ pdfDemo.mimeType = 'application/pdf';
221
+ pdfDemo.fileName = 'sample.pdf';
222
+
223
+ const imageDemo = document.getElementById('preview-image-demo');
224
+ imageDemo.src = 'https://picsum.photos/seed/mrd/600/400';
225
+ imageDemo.mimeType = 'image/jpeg';
226
+ imageDemo.fileName = 'photo.jpg';
227
+ imageDemo.addEventListener('mrdPreviewReload', () => {
228
+ logEvent('mrdPreviewReload', { source: 'preview-image-demo' });
229
+ });
230
+
231
+ // Unsupported type's fallback download button — same mrdDownload contract
232
+ // as mrd-document-view / FILE fields, so the host handler is identical.
233
+ const unsupportedDemo = document.getElementById('preview-unsupported-demo');
234
+ unsupportedDemo.src = 'https://example.com/report.docx';
235
+ unsupportedDemo.fileName = 'report.docx';
236
+ unsupportedDemo.href = 'https://example.com/data/tenant/documents/1';
237
+ unsupportedDemo.addEventListener('mrdDownload', (e) => logEvent('mrdDownload', e.detail));
238
+
239
+ // Document View demo (commons.document archetype, TASK-0157) — same
240
+ // mrdLoadDocumentPreview / setDocumentPreviewUrl round trip a real host
241
+ // (content-app) will implement; here we just hand back the local sample PDF.
242
+ ['document-view-fullpage', 'document-view-overlay'].forEach((id) => {
243
+ const section = document.getElementById(id);
244
+ section.archetypes = [window.EXAMPLE_DOCUMENT_ARCHETYPE];
245
+ section.data = window.EXAMPLE_DOCUMENT_DATA;
246
+
247
+ section.addEventListener('mrdLoadDocumentPreview', (e) => {
248
+ logEvent('mrdLoadDocumentPreview', e.detail);
249
+ // Simulate a real host round trip (resolve href -> signed URL via the API)
250
+ // instead of resolving synchronously, so the loading state is visible.
251
+ setTimeout(() => section.setDocumentPreviewUrl('/dev/sample.pdf'), 600);
252
+ });
253
+ section.addEventListener('mrdDownload', (e) => logEvent('mrdDownload', e.detail));
254
+ section.addEventListener('mrdNavigate', (e) => logEvent('mrdNavigate', e.detail));
255
+ });
256
+ }
257
+
200
258
  /* =====================================================================
201
259
  TENANT SELECTION
202
260
  ===================================================================== */
@@ -438,8 +496,48 @@ async function renderSection(index) {
438
496
  if (generation !== _sectionGeneration) return;
439
497
  await handleNavigate(e.detail);
440
498
  });
441
- section.addEventListener('mrdDownload', (e) => logEvent('mrdDownload', e.detail));
442
- section.addEventListener('mrdSearch', (e) => logEvent('mrdSearch', e.detail));
499
+ section.addEventListener('mrdSearch', (e) => logEvent('mrdSearch', e.detail));
500
+
501
+ // Resolve a FILE/IMAGE field's href to a signed URL, then open it — mirrors
502
+ // content-app's DashboardComponent.onMrdDownload (getDownloadLink + window.open).
503
+ section.addEventListener('mrdDownload', async (e) => {
504
+ if (generation !== _sectionGeneration) return;
505
+ logEvent('mrdDownload', e.detail);
506
+ try {
507
+ const url = await apiGetDownloadLink(authGetToken(), e.detail.href);
508
+ window.open(url, '_blank');
509
+ } catch (err) {
510
+ console.error('[mrdDownload] mislukt', e.detail.href, err);
511
+ }
512
+ });
513
+
514
+ // IMAGE field thumbnail — same signed-URL round trip as mrdLoadDocumentPreview below.
515
+ section.addEventListener('mrdLoadImage', async (e) => {
516
+ if (generation !== _sectionGeneration) return;
517
+ const { fieldName, href } = e.detail;
518
+ logEvent('mrdLoadImage', e.detail);
519
+ try {
520
+ const url = await apiGetDownloadLink(authGetToken(), href);
521
+ await section.setImagePreview(fieldName, url);
522
+ } catch (err) {
523
+ console.error('[mrdLoadImage] mislukt', fieldName, err);
524
+ }
525
+ });
526
+
527
+ // commons.document archetype's inline file preview (TASK-0157). Without this
528
+ // listener the component's mrdLoadDocumentPreview request goes unanswered and
529
+ // previewUrl never arrives — the preview area stays on its loading state forever.
530
+ section.addEventListener('mrdLoadDocumentPreview', async (e) => {
531
+ if (generation !== _sectionGeneration) return;
532
+ const { href } = e.detail;
533
+ logEvent('mrdLoadDocumentPreview', e.detail);
534
+ try {
535
+ const url = await apiGetDownloadLink(authGetToken(), href);
536
+ await section.setDocumentPreviewUrl(url);
537
+ } catch (err) {
538
+ console.error('[mrdLoadDocumentPreview] mislukt', href, err);
539
+ }
540
+ });
443
541
 
444
542
  // Generic in-place update (e.g. a document dragged into another folder).
445
543
  // Fire-and-forget PATCH; the component already updated its tree optimistically.
@@ -243,8 +243,13 @@ window.EXAMPLE_COMPANY_METADATA = {
243
243
  tabPage: true,
244
244
  items: [
245
245
  {
246
+ // navigateBehaviour demo (TASK-0155): OVERLAY_PAGE here vs. the default
247
+ // PAGE on the Share classes view below — mrd-layout-section always
248
+ // includes navigateBehaviour on mrdNavigate, regardless of which one
249
+ // is configured. Click a row in each tab and compare the Event log.
246
250
  type: 'RELATED_VIEW', label: 'Appointments', name: 'view0',
247
251
  relatedClass: 'appointments', fromClass: 'companies', showTitle: false,
252
+ navigateBehaviour: 'OVERLAY_PAGE',
248
253
  view: {
249
254
  name: null,
250
255
  defaultView: true,
@@ -416,3 +421,22 @@ window.EXAMPLE_FIELD_TYPES_DATA = {
416
421
  fRelation: { href: '/data/demo/entities/person-1', name: 'A. Jansen' },
417
422
  },
418
423
  };
424
+
425
+ // ─── Document View demo (commons.document archetype, TASK-0157) ──────────────
426
+ // Exercises mrd-document-view via mrd-layout-section's archetype path — the
427
+ // same rendering used inside content-app's future overlay ("quick look").
428
+
429
+ window.EXAMPLE_DOCUMENT_ARCHETYPE = {
430
+ archetype: 'commons.document',
431
+ slots: { title: 'name', date: 'createdDate', summary: 'description', file: 'file', container: 'dossier' },
432
+ };
433
+
434
+ window.EXAMPLE_DOCUMENT_DATA = {
435
+ name: 'Contract 2026 - Acme Corp.pdf',
436
+ createdDate: '2026-03-14',
437
+ description: '<p>Signed engagement contract for the FY2026 period. Reviewed and countersigned by both parties.</p>',
438
+ file: { href: '/data/demo/documents/1/file', fileName: 'contract-2026-acme.pdf' },
439
+ _links: {
440
+ dossier: { href: '/data/demo/dossiers/42', name: 'Acme Corp — Dossier 2026' },
441
+ },
442
+ };
@@ -62,6 +62,14 @@ export var ClientLayoutItemRelationEditBehavior;
62
62
  ClientLayoutItemRelationEditBehavior["DROPDOWN"] = "DROPDOWN";
63
63
  ClientLayoutItemRelationEditBehavior["CHECKBOX"] = "CHECKBOX";
64
64
  })(ClientLayoutItemRelationEditBehavior || (ClientLayoutItemRelationEditBehavior = {}));
65
+ // ─── VIEW / RELATED_VIEW navigate behaviour ──────────────────────────────────
66
+ /** How navigating from a row/document in this VIEW or RELATED_VIEW should open
67
+ * its target: a full page navigation, or an overlay on top of the current page. */
68
+ export var ClientLayoutNavigateBehaviour;
69
+ (function (ClientLayoutNavigateBehaviour) {
70
+ ClientLayoutNavigateBehaviour["PAGE"] = "PAGE";
71
+ ClientLayoutNavigateBehaviour["OVERLAY_PAGE"] = "OVERLAY_PAGE";
72
+ })(ClientLayoutNavigateBehaviour || (ClientLayoutNavigateBehaviour = {}));
65
73
  /** Well-known archetype ids the client renders specially. */
66
74
  export const ARCHETYPE_DOCUMENT = 'commons.document';
67
75
  /**
@@ -16,6 +16,7 @@ const translations = {
16
16
  invalid_image: 'Selecteer een afbeeldingsbestand',
17
17
  search_results: 'Zoekresultaten',
18
18
  no_results: 'Geen resultaten gevonden',
19
+ load_failed_retry: 'Laden mislukt — opnieuw proberen',
19
20
  loading: 'Laden...',
20
21
  submit: 'Opslaan',
21
22
  cancel: 'Annuleren',
@@ -71,6 +72,10 @@ const translations = {
71
72
  folder_no_slash: "Een foldernaam mag geen '/' bevatten",
72
73
  folder_duplicate: 'Er bestaat al een folder met deze naam',
73
74
  doc_empty_drop: 'Nog geen documenten — sleep bestanden hierheen',
75
+ // mrd-file-preview
76
+ preview_unsupported: 'Geen voorbeeld beschikbaar voor dit bestandstype',
77
+ preview_error: 'Voorbeeld laden mislukt',
78
+ retry: 'Opnieuw proberen',
74
79
  },
75
80
  en: {
76
81
  required: 'This field is required',
@@ -89,6 +94,7 @@ const translations = {
89
94
  invalid_image: 'Please select an image file',
90
95
  search_results: 'Search results',
91
96
  no_results: 'No results found',
97
+ load_failed_retry: 'Failed to load — retry',
92
98
  loading: 'Loading...',
93
99
  submit: 'Save',
94
100
  cancel: 'Cancel',
@@ -144,6 +150,10 @@ const translations = {
144
150
  folder_no_slash: "A folder name cannot contain '/'",
145
151
  folder_duplicate: 'A folder with this name already exists',
146
152
  doc_empty_drop: 'No documents yet — drop files here',
153
+ // mrd-file-preview
154
+ preview_unsupported: 'No preview available for this file type',
155
+ preview_error: 'Failed to load preview',
156
+ retry: 'Retry',
147
157
  },
148
158
  ar: {
149
159
  required: 'هذا الحقل مطلوب',
@@ -162,6 +172,7 @@ const translations = {
162
172
  invalid_image: 'يرجى اختيار ملف صورة',
163
173
  search_results: 'نتائج البحث',
164
174
  no_results: 'لم يتم العثور على نتائج',
175
+ load_failed_retry: 'فشل التحميل — إعادة المحاولة',
165
176
  loading: 'جار التحميل...',
166
177
  submit: 'حفظ',
167
178
  cancel: 'إلغاء',
@@ -211,6 +222,10 @@ const translations = {
211
222
  history_until: 'حتى',
212
223
  history_badge_tooltip: 'القيم السابقة',
213
224
  hyperlink_name: 'نص الرابط (اختياري)',
225
+ // mrd-file-preview
226
+ preview_unsupported: 'لا تتوفر معاينة لهذا النوع من الملفات',
227
+ preview_error: 'فشل تحميل المعاينة',
228
+ retry: 'إعادة المحاولة',
214
229
  },
215
230
  fr: {
216
231
  required: 'Ce champ est obligatoire',
@@ -229,6 +244,7 @@ const translations = {
229
244
  invalid_image: 'Veuillez sélectionner un fichier image',
230
245
  search_results: 'Résultats de recherche',
231
246
  no_results: 'Aucun résultat trouvé',
247
+ load_failed_retry: 'Échec du chargement — réessayer',
232
248
  loading: 'Chargement...',
233
249
  submit: 'Enregistrer',
234
250
  cancel: 'Annuler',
@@ -278,6 +294,10 @@ const translations = {
278
294
  history_until: "jusqu'au",
279
295
  history_badge_tooltip: 'Valeurs précédentes',
280
296
  hyperlink_name: 'Texte du lien (optionnel)',
297
+ // mrd-file-preview
298
+ preview_unsupported: "Aucun aperçu disponible pour ce type de fichier",
299
+ preview_error: "Échec du chargement de l'aperçu",
300
+ retry: 'Réessayer',
281
301
  },
282
302
  de: {
283
303
  required: 'Dieses Feld ist erforderlich',
@@ -296,6 +316,7 @@ const translations = {
296
316
  invalid_image: 'Bitte eine Bilddatei auswählen',
297
317
  search_results: 'Suchergebnisse',
298
318
  no_results: 'Keine Ergebnisse gefunden',
319
+ load_failed_retry: 'Laden fehlgeschlagen — erneut versuchen',
299
320
  loading: 'Laden...',
300
321
  submit: 'Speichern',
301
322
  cancel: 'Abbrechen',
@@ -345,6 +366,10 @@ const translations = {
345
366
  history_until: 'bis',
346
367
  history_badge_tooltip: 'Vorherige Werte',
347
368
  hyperlink_name: 'Linktext (optional)',
369
+ // mrd-file-preview
370
+ preview_unsupported: 'Keine Vorschau für diesen Dateityp verfügbar',
371
+ preview_error: 'Vorschau konnte nicht geladen werden',
372
+ retry: 'Erneut versuchen',
348
373
  },
349
374
  es: {
350
375
  required: 'Este campo es obligatorio',
@@ -363,6 +388,7 @@ const translations = {
363
388
  invalid_image: 'Seleccione un archivo de imagen',
364
389
  search_results: 'Resultados de búsqueda',
365
390
  no_results: 'No se encontraron resultados',
391
+ load_failed_retry: 'Error al cargar — reintentar',
366
392
  loading: 'Cargando...',
367
393
  submit: 'Guardar',
368
394
  cancel: 'Cancelar',
@@ -412,6 +438,10 @@ const translations = {
412
438
  history_until: 'hasta',
413
439
  history_badge_tooltip: 'Valores anteriores',
414
440
  hyperlink_name: 'Texto del enlace (opcional)',
441
+ // mrd-file-preview
442
+ preview_unsupported: 'Vista previa no disponible para este tipo de archivo',
443
+ preview_error: 'Error al cargar la vista previa',
444
+ retry: 'Reintentar',
415
445
  },
416
446
  it: {
417
447
  required: 'Questo campo è obbligatorio',
@@ -430,6 +460,7 @@ const translations = {
430
460
  invalid_image: 'Seleziona un file immagine',
431
461
  search_results: 'Risultati della ricerca',
432
462
  no_results: 'Nessun risultato trovato',
463
+ load_failed_retry: 'Caricamento non riuscito — riprova',
433
464
  loading: 'Caricamento...',
434
465
  submit: 'Salva',
435
466
  cancel: 'Annulla',
@@ -479,6 +510,10 @@ const translations = {
479
510
  history_until: 'fino a',
480
511
  history_badge_tooltip: 'Valori precedenti',
481
512
  hyperlink_name: 'Testo del collegamento (opzionale)',
513
+ // mrd-file-preview
514
+ preview_unsupported: 'Nessuna anteprima disponibile per questo tipo di file',
515
+ preview_error: "Impossibile caricare l'anteprima",
516
+ retry: 'Riprova',
482
517
  },
483
518
  uk: {
484
519
  required: "Це поле обов'язкове",
@@ -497,6 +532,7 @@ const translations = {
497
532
  invalid_image: 'Будь ласка, виберіть файл зображення',
498
533
  search_results: 'Результати пошуку',
499
534
  no_results: 'Результатів не знайдено',
535
+ load_failed_retry: 'Не вдалося завантажити — повторити',
500
536
  loading: 'Завантаження...',
501
537
  submit: 'Зберегти',
502
538
  cancel: 'Скасувати',
@@ -546,6 +582,10 @@ const translations = {
546
582
  history_until: 'до',
547
583
  history_badge_tooltip: 'Попередні значення',
548
584
  hyperlink_name: "Текст посилання (необов'язково)",
585
+ // mrd-file-preview
586
+ preview_unsupported: 'Попередній перегляд недоступний для цього типу файлу',
587
+ preview_error: 'Не вдалося завантажити попередній перегляд',
588
+ retry: 'Повторити',
549
589
  },
550
590
  };
551
591
  export function t(key, locale) {
@@ -1 +1 @@
1
- var E,A,T,O,R;!function(E){E.OBJECT_FORM_DASHBOARD="OBJECT_FORM_DASHBOARD",E.CLASS_DASHBOARD="CLASS_DASHBOARD",E.GENERAL_DASHBOARD="GENERAL_DASHBOARD",E.NAVIGATION_PANE="NAVIGATION_PANE"}(E||(E={})),function(E){E.FIELD="FIELD",E.RELATION="RELATION",E.SECTION="SECTION",E.GROUP="GROUP",E.HEADER="HEADER",E.TEXT="TEXT",E.NAVIGATE="NAVIGATE",E.ACTION="ACTION",E.SEARCH="SEARCH",E.VIEW="VIEW",E.RELATED_VIEW="RELATED_VIEW",E.RELATED_OBJECT="RELATED_OBJECT"}(A||(A={})),function(E){E.TEXT="TEXT",E.TEXTBLOCK="TEXTBLOCK",E.INTEGER="INTEGER",E.DECIMAL="DECIMAL",E.PERCENTAGE="PERCENTAGE",E.CURRENCY="CURRENCY",E.BOOLEAN="BOOLEAN",E.DATE="DATE",E.DATETIME="DATETIME",E.TIME="TIME",E.EMAIL="EMAIL",E.HYPERLINK="HYPERLINK",E.LIST="LIST",E.FILE="FILE",E.IMAGE="IMAGE",E.LONGTEXT="LONGTEXT",E.JSON="JSON",E.SECRET="SECRET"}(T||(T={})),function(E){E.SEARCH="SEARCH",E.DROPDOWN="DROPDOWN",E.CHECKBOX="CHECKBOX"}(O||(O={})),function(E){E.SEARCH="SEARCH",E.DROPDOWN="DROPDOWN",E.CHECKBOX="CHECKBOX"}(R||(R={}));const C="commons.document";function I(E,A){return(null!=E?E:[]).find((E=>E.archetype===A))}export{C as A,E as C,A as a,T as b,O as c,R as d,I as r}
1
+ var E,A,T,O,R,n;!function(E){E.OBJECT_FORM_DASHBOARD="OBJECT_FORM_DASHBOARD",E.CLASS_DASHBOARD="CLASS_DASHBOARD",E.GENERAL_DASHBOARD="GENERAL_DASHBOARD",E.NAVIGATION_PANE="NAVIGATION_PANE"}(E||(E={})),function(E){E.FIELD="FIELD",E.RELATION="RELATION",E.SECTION="SECTION",E.GROUP="GROUP",E.HEADER="HEADER",E.TEXT="TEXT",E.NAVIGATE="NAVIGATE",E.ACTION="ACTION",E.SEARCH="SEARCH",E.VIEW="VIEW",E.RELATED_VIEW="RELATED_VIEW",E.RELATED_OBJECT="RELATED_OBJECT"}(A||(A={})),function(E){E.TEXT="TEXT",E.TEXTBLOCK="TEXTBLOCK",E.INTEGER="INTEGER",E.DECIMAL="DECIMAL",E.PERCENTAGE="PERCENTAGE",E.CURRENCY="CURRENCY",E.BOOLEAN="BOOLEAN",E.DATE="DATE",E.DATETIME="DATETIME",E.TIME="TIME",E.EMAIL="EMAIL",E.HYPERLINK="HYPERLINK",E.LIST="LIST",E.FILE="FILE",E.IMAGE="IMAGE",E.LONGTEXT="LONGTEXT",E.JSON="JSON",E.SECRET="SECRET"}(T||(T={})),function(E){E.SEARCH="SEARCH",E.DROPDOWN="DROPDOWN",E.CHECKBOX="CHECKBOX"}(O||(O={})),function(E){E.SEARCH="SEARCH",E.DROPDOWN="DROPDOWN",E.CHECKBOX="CHECKBOX"}(R||(R={})),function(E){E.PAGE="PAGE",E.OVERLAY_PAGE="OVERLAY_PAGE"}(n||(n={}));const C="commons.document";function I(E,A){return(null!=E?E:[]).find((E=>E.archetype===A))}export{C as A,E as C,A as a,T as b,O as c,R as d,n as e,I as r}