@mmlogic/components 0.5.16 → 0.5.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (26) hide show
  1. package/dist/cjs/loader.cjs.js +1 -1
  2. package/dist/cjs/mosterdcomponents.cjs.js +1 -1
  3. package/dist/cjs/mrd-boolean-field_28.cjs.entry.js +244 -21
  4. package/dist/collection/collection-manifest.json +2 -2
  5. package/dist/collection/components/documents/mrd-document-container/mrd-document-container.js +31 -4
  6. package/dist/collection/components/layout/mrd-layout-section/mrd-layout-section.js +196 -11
  7. package/dist/collection/components/memo/mrd-memo-container/mrd-memo-container.js +28 -4
  8. package/dist/collection/components/table/mrd-table/mrd-table.js +193 -4
  9. package/dist/collection/dev/api.js +19 -0
  10. package/dist/collection/dev/app.js +75 -0
  11. package/dist/components/mrd-document-container2.js +1 -1
  12. package/dist/components/mrd-layout-section.js +1 -1
  13. package/dist/components/mrd-memo-container2.js +1 -1
  14. package/dist/components/mrd-table2.js +1 -1
  15. package/dist/esm/loader.js +1 -1
  16. package/dist/esm/mosterdcomponents.js +1 -1
  17. package/dist/esm/mrd-boolean-field_28.entry.js +244 -21
  18. package/dist/mosterdcomponents/mosterdcomponents.esm.js +1 -1
  19. package/dist/mosterdcomponents/p-29d6dcbc.entry.js +3 -0
  20. package/dist/types/components/documents/mrd-document-container/mrd-document-container.d.ts +7 -0
  21. package/dist/types/components/layout/mrd-layout-section/mrd-layout-section.d.ts +69 -0
  22. package/dist/types/components/memo/mrd-memo-container/mrd-memo-container.d.ts +4 -0
  23. package/dist/types/components/table/mrd-table/mrd-table.d.ts +75 -0
  24. package/dist/types/components.d.ts +79 -0
  25. package/package.json +1 -1
  26. package/dist/mosterdcomponents/p-8a7fa81d.entry.js +0 -3
@@ -32,6 +32,13 @@ export class MrdDocumentContainer {
32
32
  * (with a tooltip explaining why) and the embedded table/list get it passed
33
33
  * through so their own create/upload/drag-drop paths are disabled too. */
34
34
  this.readOnly = false;
35
+ /** Whether the current user may create a document here — set by mrd-layout-section from
36
+ * its generic capability check (TASK-0299/FINDING-0155), the same answer the embedded
37
+ * table gets in list mode. Named `mayCreate` (not `canCreate`) to avoid colliding with
38
+ * the existing `canCreate` state below, which tracks something unrelated: whether the
39
+ * folder tree currently has a valid drop target selected. Defaults to false (hidden)
40
+ * until confirmed, same as mrd-table. */
41
+ this.mayCreate = false;
35
42
  /** Active body: folder tree or flat table. */
36
43
  this.mode = 'tree';
37
44
  /** Whether the folder tree currently has a valid target for "New folder". */
@@ -46,7 +53,7 @@ export class MrdDocumentContainer {
46
53
  };
47
54
  this.pickFile = () => {
48
55
  var _a;
49
- if (this.readOnly)
56
+ if (this.readOnly || !this.mayCreate)
50
57
  return;
51
58
  (_a = this.fileInputEl) === null || _a === void 0 ? void 0 : _a.click();
52
59
  };
@@ -84,7 +91,7 @@ export class MrdDocumentContainer {
84
91
  }
85
92
  // ── Toolbar ────────────────────────────────────────────────────────────────
86
93
  renderToolbar() {
87
- return (h("div", { class: "mrd-document-container__toolbar" }, this.mode === 'tree' && (h("button", { type: "button", class: "mrd-document-container__action", disabled: this.readOnly || !this.canCreate, title: this.readOnly ? t('readonly_access', this.locale) : undefined, 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.readOnly || !this.canCreate, title: this.readOnly ? t('readonly_access', this.locale) : (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" }))))));
94
+ return (h("div", { class: "mrd-document-container__toolbar" }, this.mode === 'tree' && this.mayCreate && (h("button", { type: "button", class: "mrd-document-container__action", disabled: this.readOnly || !this.canCreate, title: this.readOnly ? t('readonly_access', this.locale) : undefined, 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' && this.mayCreate && (h("button", { type: "button", class: "mrd-document-container__action", disabled: this.readOnly || !this.canCreate, title: this.readOnly ? t('readonly_access', this.locale) : (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" }))))));
88
95
  }
89
96
  // ── Bodies ───────────────────────────────────────────────────────────────
90
97
  renderTree() {
@@ -115,7 +122,7 @@ export class MrdDocumentContainer {
115
122
  } }));
116
123
  }
117
124
  renderList() {
118
- return (h("mrd-table", { "data-view": this.viewKey, ref: el => (this.tableEl = el), item: this.item, parentId: this.parentId, locale: this.locale, readOnly: this.readOnly, onMrdLoadPage: (e) => {
125
+ return (h("mrd-table", { "data-view": this.viewKey, ref: el => (this.tableEl = el), item: this.item, parentId: this.parentId, locale: this.locale, readOnly: this.readOnly, canCreate: this.mayCreate, onMrdLoadPage: (e) => {
119
126
  e.stopPropagation();
120
127
  this.mrdLoadPage.emit(e.detail);
121
128
  }, onMrdLoadAggregations: (e) => {
@@ -139,7 +146,7 @@ export class MrdDocumentContainer {
139
146
  }
140
147
  render() {
141
148
  const treeMode = this.mode === 'tree';
142
- return (h(Host, { key: '6792d49f08dd19f6c51e4b656df06bbd8bc14f3e' }, this.renderToolbar(), h("div", { key: '702f4c288f8c40bd2c5999beb392b9dfd022b0bc', class: {
149
+ return (h(Host, { key: '4b674a508ac7ddf6af80a40836b72dc65f41c741' }, this.renderToolbar(), h("div", { key: '19968916f685a2e3cd075dabf170f67fb104251c', class: {
143
150
  'mrd-document-container__body': true,
144
151
  'mrd-document-container__body--tree': treeMode,
145
152
  }, style: treeMode ? { height: `${this.height}px`, overflowY: 'auto' } : {} }, treeMode ? this.renderTree() : this.renderList())));
@@ -326,6 +333,26 @@ export class MrdDocumentContainer {
326
333
  "reflect": false,
327
334
  "attribute": "read-only",
328
335
  "defaultValue": "false"
336
+ },
337
+ "mayCreate": {
338
+ "type": "boolean",
339
+ "mutable": false,
340
+ "complexType": {
341
+ "original": "boolean",
342
+ "resolved": "boolean",
343
+ "references": {}
344
+ },
345
+ "required": false,
346
+ "optional": false,
347
+ "docs": {
348
+ "tags": [],
349
+ "text": "Whether the current user may create a document here \u2014 set by mrd-layout-section from\nits generic capability check (TASK-0299/FINDING-0155), the same answer the embedded\ntable gets in list mode. Named `mayCreate` (not `canCreate`) to avoid colliding with\nthe existing `canCreate` state below, which tracks something unrelated: whether the\nfolder tree currently has a valid drop target selected. Defaults to false (hidden)\nuntil confirmed, same as mrd-table."
350
+ },
351
+ "getter": false,
352
+ "setter": false,
353
+ "reflect": false,
354
+ "attribute": "may-create",
355
+ "defaultValue": "false"
329
356
  }
330
357
  };
331
358
  }
@@ -21,6 +21,12 @@ export class MrdLayoutSection {
21
21
  * create ("+") action and the documents view's New folder / Upload are disabled
22
22
  * with a tooltip explaining why, so it's clear at a glance the user can't mutate data. */
23
23
  this.readOnly = false;
24
+ /** When false, skips the mrdViewCheckCapabilities round trip for every VIEW/RELATED_VIEW
25
+ * binding and hands mrd-table/mrd-memo-container/mrd-document-container their create
26
+ * affordance as allowed immediately — for a tenant with no access control configured,
27
+ * the answer would always be mayCreate: true anyway (FINDING-0171 / TASK-0322). Default
28
+ * `true` keeps today's ask-and-hide behaviour unchanged. */
29
+ this.accessControlEnabled = true;
24
30
  /** The active account's linked person record (userProfile.links.person), if any.
25
31
  * Threaded to mrd-memo-container so a top-level memo VIEW (no parent record to
26
32
  * attach a new memo to) can still offer "New memo" by falling back to the
@@ -31,7 +37,22 @@ export class MrdLayoutSection {
31
37
  this.imagePreviewUrl = null;
32
38
  this.imagePreviews = {};
33
39
  this.openHistoryField = null;
40
+ /** Answers to mrdViewCheckCapabilities, keyed by view key — see emitCapabilitiesChecks().
41
+ * Passed straight down as a `canCreate`/`mayCreate` prop to whichever body (mrd-table,
42
+ * mrd-memo-container, mrd-document-container) is mounted for that key; a missing entry
43
+ * means "not answered yet" and is treated as false (hidden), same default as mrd-table's
44
+ * own standalone behaviour (TASK-0299). */
45
+ this.canCreateMap = {};
46
+ /** Companion to canCreateMap — concrete types the mrdViewCheckCapabilities answer allows,
47
+ * when the API reports them; passed to mrd-table's `allowedTypes` prop to narrow its
48
+ * create-type picker down to types a create POST would actually accept (FINDING-0176 /
49
+ * TASK-0314). A missing entry means "not answered yet / not reported" — mrd-table then
50
+ * leaves `item.createTypes` unfiltered, same as before this existed. */
51
+ this.allowedTypesMap = {};
34
52
  this.historyClickOutside = null;
53
+ /** Signature last asked per view key (see emitCapabilitiesChecks()) — avoids re-emitting
54
+ * mrdViewCheckCapabilities on every data/items update when the binding hasn't changed. */
55
+ this.capabilitiesAsked = new Map();
35
56
  this.searchTimers = {};
36
57
  this.handleViewLoadPage = (e, name) => {
37
58
  e.stopPropagation();
@@ -51,6 +72,10 @@ export class MrdLayoutSection {
51
72
  };
52
73
  }
53
74
  componentDidLoad() {
75
+ // No DOM/child-hydration dependency, unlike initEmbeddedTables()/emitLoadImages() below —
76
+ // run it immediately rather than deferring, so the host's answer (setViewCanCreate) can
77
+ // land before the child bodies even mount.
78
+ this.emitCapabilitiesChecks();
54
79
  setTimeout(() => {
55
80
  this.initEmbeddedTables();
56
81
  this.emitLoadImages();
@@ -69,9 +94,15 @@ export class MrdLayoutSection {
69
94
  }
70
95
  dataChanged(newVal) {
71
96
  if (newVal && Object.keys(newVal).length > 0) {
97
+ this.emitCapabilitiesChecks();
72
98
  setTimeout(() => this.initEmbeddedTables(), 0);
73
99
  }
74
100
  }
101
+ /** A host can swap `items` (e.g. a different dashboard layout) without `data` changing —
102
+ * dataChanged() alone would miss that binding change. */
103
+ itemsChanged() {
104
+ this.emitCapabilitiesChecks();
105
+ }
75
106
  async initEmbeddedTables() {
76
107
  const tables = this.el.querySelectorAll('mrd-table[data-view]');
77
108
  for (const table of Array.from(tables)) {
@@ -99,6 +130,70 @@ export class MrdLayoutSection {
99
130
  }
100
131
  }
101
132
  }
133
+ /** Parent record id extracted from `data._links.self.href` — the same value every
134
+ * VIEW/RELATED_VIEW item on this record shares, used for RELATED_VIEW path construction
135
+ * (renderRelatedView) and capability checks (emitCapabilitiesChecks). */
136
+ computeParentId() {
137
+ var _a, _b, _c, _d, _e;
138
+ const selfHref = (_d = (_c = (_b = (_a = this.data) === null || _a === void 0 ? void 0 : _a._links) === null || _b === void 0 ? void 0 : _b.self) === null || _c === void 0 ? void 0 : _c.href) !== null && _d !== void 0 ? _d : '';
139
+ return (_e = selfHref.split('/').filter(Boolean).pop()) !== null && _e !== void 0 ? _e : '';
140
+ }
141
+ /** Asks, once per VIEW/RELATED_VIEW binding, whether the current user may create a record
142
+ * there — the single generic place for this check (TASK-0299/FINDING-0155), so mrd-table,
143
+ * mrd-memo-container and mrd-document-container all get the answer the same way: as a plain
144
+ * `canCreate`/`mayCreate` prop from this component, computed here and handed down in
145
+ * renderTable()/renderMemoContainer()/renderDocumentContainer() — none of the three leaf
146
+ * components need to ask on their own when rendered through mrd-layout-section (mrd-table's
147
+ * own self-ask only kicks in for standalone use, i.e. its `canCreate` prop left undefined).
148
+ *
149
+ * The route/path variables mirror what each renderer already uses for its own rows —
150
+ * `type` = `dataClass`, `refType`/`refId` = `fromClass`/parentId for a RELATED_VIEW, and
151
+ * `filterClass` when the view is scoped to one subtype (the same value sent as `?type=` on
152
+ * the row fetch). A signature (not just the key) is tracked per binding so a genuine route
153
+ * change re-asks and hides again, without re-asking on every unrelated data/items update. */
154
+ emitCapabilitiesChecks() {
155
+ var _a, _b, _c, _d;
156
+ if (!this.accessControlEnabled)
157
+ return;
158
+ const parentId = this.computeParentId();
159
+ for (const item of flattenItems(this.items)) {
160
+ if (item.type !== ClientLayoutItemType.VIEW && item.type !== ClientLayoutItemType.RELATED_VIEW)
161
+ continue;
162
+ const key = viewKeyFor(item);
163
+ if (!key || !item.view)
164
+ continue;
165
+ const isRelatedView = item.type === ClientLayoutItemType.RELATED_VIEW;
166
+ const type = (_b = (_a = item.dataClass) !== null && _a !== void 0 ? _a : item.relatedClass) !== null && _b !== void 0 ? _b : key;
167
+ const refType = isRelatedView ? ((_c = item.fromClass) !== null && _c !== void 0 ? _c : '') : undefined;
168
+ const refId = isRelatedView ? parentId : undefined;
169
+ const filterClass = (_d = item.filterClass) !== null && _d !== void 0 ? _d : undefined;
170
+ // A host binding `items` and `data` as separate properties (e.g. Angular's
171
+ // `[items]="…" [data]="…"` on <mrd-layout-section>) can apply them in either
172
+ // order — itemsChanged() can fire while `data` still holds its default `{}`,
173
+ // before the real record (and its self href) has landed. Asking here would
174
+ // emit refId: '' for a RELATED_VIEW, which a host's `refType && refId` check
175
+ // reads as "no parent" and answers via the plain, unscoped capabilities route
176
+ // instead of the parent-scoped one (FINDING-0175 / TASK-0316). Skip without
177
+ // marking capabilitiesAsked, so the real ask fires once dataChanged() lands.
178
+ if (isRelatedView && !refId)
179
+ continue;
180
+ const signature = JSON.stringify([type, refType, refId, filterClass]);
181
+ if (this.capabilitiesAsked.get(key) === signature)
182
+ continue;
183
+ this.capabilitiesAsked.set(key, signature);
184
+ if (key in this.canCreateMap) {
185
+ const next = Object.assign({}, this.canCreateMap);
186
+ delete next[key];
187
+ this.canCreateMap = next;
188
+ }
189
+ if (key in this.allowedTypesMap) {
190
+ const next = Object.assign({}, this.allowedTypesMap);
191
+ delete next[key];
192
+ this.allowedTypesMap = next;
193
+ }
194
+ this.mrdViewCheckCapabilities.emit(Object.assign(Object.assign({ name: key, type }, (refType !== undefined ? { refType, refId } : {})), (filterClass ? { filterClass } : {})));
195
+ }
196
+ }
102
197
  /** Inject search results. Pass dataClass to target a specific SEARCH item; omit when there is only one. */
103
198
  async setSearchResults(results, dataClass) {
104
199
  const key = dataClass !== null && dataClass !== void 0 ? dataClass : this.resolveSearchKey();
@@ -140,6 +235,28 @@ export class MrdLayoutSection {
140
235
  return;
141
236
  await table.setAggregations(data);
142
237
  }
238
+ /** Host answer to mrdViewCheckCapabilities — whether the current user may create a record
239
+ * for the given view's bound route. `name` is the view key from the event detail. Stored in
240
+ * canCreateMap and passed down as a plain prop to whichever body is mounted for that key
241
+ * (mrd-table, mrd-memo-container or mrd-document-container) — no DOM lookup needed, unlike
242
+ * setViewPage/setViewAggregations, since the prop flows through the next render. */
243
+ async setViewCanCreate(name, value, allowedTypes) {
244
+ this.canCreateMap = Object.assign(Object.assign({}, this.canCreateMap), { [name]: value });
245
+ this.allowedTypesMap = Object.assign(Object.assign({}, this.allowedTypesMap), { [name]: allowedTypes });
246
+ }
247
+ /** `canCreateMap[key]` when access control is configured (default); always allowed
248
+ * when `accessControlEnabled` is false, since emitCapabilitiesChecks() never asked
249
+ * and canCreateMap[key] would otherwise stay permanently unanswered. */
250
+ effectiveCanCreate(key) {
251
+ var _a;
252
+ return this.accessControlEnabled ? ((_a = this.canCreateMap[key]) !== null && _a !== void 0 ? _a : false) : true;
253
+ }
254
+ /** `allowedTypesMap[key]` when access control is configured; `undefined` (don't filter)
255
+ * when disabled, since emitCapabilitiesChecks() never asked and the map entry would
256
+ * otherwise stay permanently unanswered — see effectiveCanCreate(). */
257
+ effectiveAllowedTypes(key) {
258
+ return this.accessControlEnabled ? this.allowedTypesMap[key] : undefined;
259
+ }
143
260
  /** Inject distinct values into an embedded mrd-document-list for a document view. */
144
261
  async setViewDistinct(name, nodeId, values) {
145
262
  const list = this.el.querySelector(`mrd-document-list[data-doclist="${name}"]`);
@@ -227,20 +344,19 @@ export class MrdLayoutSection {
227
344
  return (h("div", { class: "mrd-layout-section__search", key: `search-${dataClass}` }, h("div", { class: "mrd-layout-section__search-wrap" }, h("svg", { class: "mrd-layout-section__search-icon", xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 20 20", fill: "currentColor", "aria-hidden": "true" }, h("path", { "fill-rule": "evenodd", d: "M9 3.5a5.5 5.5 0 100 11 5.5 5.5 0 000-11zM2 9a7 7 0 1112.452 4.391l3.328 3.329a.75.75 0 11-1.06 1.06l-3.329-3.328A7 7 0 012 9z", "clip-rule": "evenodd" })), h("input", { class: "mrd-layout-section__search-input", type: "text", value: query, placeholder: (_c = item.label) !== null && _c !== void 0 ? _c : '', onInput: e => this.handleSearchInput(dataClass, e.target.value) })), results.length > 0 && (h("ul", { class: "mrd-layout-section__search-results" }, results.map(r => (h("li", { key: r.id, class: "mrd-layout-section__search-result" }, h("button", { class: "mrd-layout-section__search-result-btn", onClick: () => this.mrdNavigate.emit({ href: r.id, label: r.label }) }, h("span", { class: "mrd-layout-section__search-result-label" }, r.label), r.description && h("span", { class: "mrd-layout-section__search-result-desc" }, r.description)))))))));
228
345
  }
229
346
  renderRelatedView(item) {
230
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
347
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
231
348
  const key = viewKeyFor(item);
232
349
  if (!key)
233
350
  return null;
234
351
  if (!item.view)
235
352
  return null;
236
353
  const showTitle = (_a = item.showTitle) !== null && _a !== void 0 ? _a : false;
237
- // Extract parentId from data._links.self.href for RELATED_VIEW path construction
238
354
  const selfHref = (_e = (_d = (_c = (_b = this.data) === null || _b === void 0 ? void 0 : _b._links) === null || _c === void 0 ? void 0 : _c.self) === null || _d === void 0 ? void 0 : _d.href) !== null && _e !== void 0 ? _e : '';
239
- const parentId = (_f = selfHref.split('/').filter(Boolean).pop()) !== null && _f !== void 0 ? _f : '';
355
+ const parentId = this.computeParentId();
240
356
  // The API publishes `archetypes` on the VIEW/RELATED_VIEW item itself
241
357
  // (sibling of `view`), not inside `view`.
242
- const docArchetype = resolveArchetype((_g = item.archetypes) !== null && _g !== void 0 ? _g : (_h = item.view) === null || _h === void 0 ? void 0 : _h.archetypes, ARCHETYPE_DOCUMENT);
243
- const memoArchetype = !docArchetype ? resolveArchetype((_j = item.archetypes) !== null && _j !== void 0 ? _j : (_k = item.view) === null || _k === void 0 ? void 0 : _k.archetypes, ARCHETYPE_MEMO) : undefined;
358
+ const docArchetype = resolveArchetype((_f = item.archetypes) !== null && _f !== void 0 ? _f : (_g = item.view) === null || _g === void 0 ? void 0 : _g.archetypes, ARCHETYPE_DOCUMENT);
359
+ const memoArchetype = !docArchetype ? resolveArchetype((_h = item.archetypes) !== null && _h !== void 0 ? _h : (_j = item.view) === null || _j === void 0 ? void 0 : _j.archetypes, ARCHETYPE_MEMO) : undefined;
244
360
  // Canonical parent href for single-container seeding: use the RELATED_VIEW's
245
361
  // fromClass (base class) rather than the route class, so polymorphic subtypes
246
362
  // resolve to the same container the documents actually reference.
@@ -251,7 +367,7 @@ export class MrdLayoutSection {
251
367
  // back to this parent; only when it matches the container slot does the
252
368
  // parent's own href double as the memo's container.
253
369
  const memoContainerRelated = item.type !== ClientLayoutItemType.RELATED_VIEW
254
- || item.inverseRelation === ((_l = memoArchetype === null || memoArchetype === void 0 ? void 0 : memoArchetype.slots) === null || _l === void 0 ? void 0 : _l.container);
370
+ || item.inverseRelation === ((_k = memoArchetype === null || memoArchetype === void 0 ? void 0 : memoArchetype.slots) === null || _k === void 0 ? void 0 : _k.container);
255
371
  const memoContainerHref = memoContainerRelated ? containerHref : '';
256
372
  // Distinct from memoContainerRelated: only a genuine container-scoped
257
373
  // RELATED_VIEW (a company's own memo tab) makes every card's container
@@ -264,7 +380,7 @@ export class MrdLayoutSection {
264
380
  * swaps between the folder tree and the flat table. Events are re-emitted to
265
381
  * the host with the view key, exactly as the raw list/table events were. */
266
382
  renderDocumentContainer(item, key, parentId, archetype, containerHref) {
267
- return (h("mrd-document-container", { item: item, archetype: archetype, parentId: parentId, containerHref: containerHref, viewKey: key, locale: this.locale, readOnly: this.readOnly, onMrdLoadDistinct: (e) => {
383
+ return (h("mrd-document-container", { item: item, archetype: archetype, parentId: parentId, containerHref: containerHref, viewKey: key, locale: this.locale, readOnly: this.readOnly, mayCreate: this.effectiveCanCreate(key), onMrdLoadDistinct: (e) => {
268
384
  e.stopPropagation();
269
385
  this.mrdLoadViewDistinct.emit(Object.assign({ name: key }, e.detail));
270
386
  }, onMrdLoadPage: (e) => {
@@ -301,7 +417,7 @@ export class MrdLayoutSection {
301
417
  renderMemoContainer(item, key, parentId, archetype, containerHref, containerRelated, showContainerLink) {
302
418
  var _a, _b, _c, _d;
303
419
  const labelsListItems = (_d = (_c = (_b = (_a = item.view) === null || _a === void 0 ? void 0 : _a.values) === null || _b === void 0 ? void 0 : _b.find(v => { var _a; return v.name === ((_a = archetype.slots) === null || _a === void 0 ? void 0 : _a.labels); })) === null || _c === void 0 ? void 0 : _c.listItems) !== null && _d !== void 0 ? _d : null;
304
- return (h("mrd-memo-container", { "data-view": key, item: item, archetype: archetype, parentId: parentId, containerHref: containerHref, containerRelated: containerRelated, showContainerLink: showContainerLink, viewKey: key, locale: this.locale, readOnly: this.readOnly, me: this.me, labelsListItems: labelsListItems, onMrdNavigate: (e) => {
420
+ return (h("mrd-memo-container", { "data-view": key, item: item, archetype: archetype, parentId: parentId, containerHref: containerHref, containerRelated: containerRelated, showContainerLink: showContainerLink, viewKey: key, locale: this.locale, readOnly: this.readOnly, canCreate: this.effectiveCanCreate(key), me: this.me, labelsListItems: labelsListItems, onMrdNavigate: (e) => {
305
421
  var _a;
306
422
  e.stopPropagation();
307
423
  this.mrdNavigate.emit(Object.assign(Object.assign({}, e.detail), { navigateBehaviour: (_a = item.navigateBehaviour) !== null && _a !== void 0 ? _a : ClientLayoutNavigateBehaviour.PAGE }));
@@ -328,7 +444,7 @@ export class MrdLayoutSection {
328
444
  } }));
329
445
  }
330
446
  renderTable(item, key, parentId) {
331
- return (h("mrd-table", { "data-view": key, item: item, parentId: parentId, locale: this.locale, readOnly: this.readOnly, onMrdLoadPage: (e) => this.handleViewLoadPage(e, key), onMrdLoadAggregations: (e) => {
447
+ return (h("mrd-table", { "data-view": key, item: item, parentId: parentId, locale: this.locale, readOnly: this.readOnly, canCreate: this.effectiveCanCreate(key), allowedTypes: this.effectiveAllowedTypes(key), accessControlEnabled: this.accessControlEnabled, onMrdLoadPage: (e) => this.handleViewLoadPage(e, key), onMrdLoadAggregations: (e) => {
332
448
  var _a;
333
449
  e.stopPropagation();
334
450
  this.mrdLoadViewAggregations.emit(Object.assign({ name: key, dataClass: (_a = item.dataClass) !== null && _a !== void 0 ? _a : key }, e.detail));
@@ -466,7 +582,7 @@ export class MrdLayoutSection {
466
582
  const emailArchetype = !docArchetype && !invoiceArchetype ? resolveArchetype(this.archetypes, ARCHETYPE_EMAIL) : undefined;
467
583
  const memoArchetype = !docArchetype && !invoiceArchetype && !emailArchetype ? resolveArchetype(this.archetypes, ARCHETYPE_MEMO) : undefined;
468
584
  const anyArchetype = docArchetype || invoiceArchetype || emailArchetype || memoArchetype;
469
- return (h(Host, { key: '057d145da172e68f84b6ff34f71ae649d6d6bf92' }, h("div", { key: 'e03a1761dc6bcd9ff54b264fee56130270bddc2f', class: "mrd-layout-section" }, docArchetype && this.renderDocumentObject(docArchetype), invoiceArchetype && this.renderInvoiceObject(invoiceArchetype), emailArchetype && this.renderEmailObject(emailArchetype), memoArchetype && this.renderMemoObject(memoArchetype), !anyArchetype && this.items.map((item, i) => this.renderItem(item, this.items, i))), this.renderImageModal()));
585
+ return (h(Host, { key: '97351c57865614662e888b3c0278a61d866a74e1' }, h("div", { key: '43e8f0fda8fcd3300b879d05ac692ce331c896d5', class: "mrd-layout-section" }, docArchetype && this.renderDocumentObject(docArchetype), invoiceArchetype && this.renderInvoiceObject(invoiceArchetype), emailArchetype && this.renderEmailObject(emailArchetype), memoArchetype && this.renderMemoObject(memoArchetype), !anyArchetype && this.items.map((item, i) => this.renderItem(item, this.items, i))), this.renderImageModal()));
470
586
  }
471
587
  static get is() { return "mrd-layout-section"; }
472
588
  static get encapsulation() { return "scoped"; }
@@ -647,6 +763,26 @@ export class MrdLayoutSection {
647
763
  "attribute": "read-only",
648
764
  "defaultValue": "false"
649
765
  },
766
+ "accessControlEnabled": {
767
+ "type": "boolean",
768
+ "mutable": false,
769
+ "complexType": {
770
+ "original": "boolean",
771
+ "resolved": "boolean",
772
+ "references": {}
773
+ },
774
+ "required": false,
775
+ "optional": false,
776
+ "docs": {
777
+ "tags": [],
778
+ "text": "When false, skips the mrdViewCheckCapabilities round trip for every VIEW/RELATED_VIEW\nbinding and hands mrd-table/mrd-memo-container/mrd-document-container their create\naffordance as allowed immediately \u2014 for a tenant with no access control configured,\nthe answer would always be mayCreate: true anyway (FINDING-0171 / TASK-0322). Default\n`true` keeps today's ask-and-hide behaviour unchanged."
779
+ },
780
+ "getter": false,
781
+ "setter": false,
782
+ "reflect": false,
783
+ "attribute": "access-control-enabled",
784
+ "defaultValue": "true"
785
+ },
650
786
  "me": {
651
787
  "type": "unknown",
652
788
  "mutable": false,
@@ -680,7 +816,9 @@ export class MrdLayoutSection {
680
816
  "searchResultsMap": {},
681
817
  "imagePreviewUrl": {},
682
818
  "imagePreviews": {},
683
- "openHistoryField": {}
819
+ "openHistoryField": {},
820
+ "canCreateMap": {},
821
+ "allowedTypesMap": {}
684
822
  };
685
823
  }
686
824
  static get events() {
@@ -851,6 +989,21 @@ export class MrdLayoutSection {
851
989
  "resolved": "{ name: string; nodeId: string; distinct: string; path: string; qs: string; }",
852
990
  "references": {}
853
991
  }
992
+ }, {
993
+ "method": "mrdViewCheckCapabilities",
994
+ "name": "mrdViewCheckCapabilities",
995
+ "bubbles": true,
996
+ "cancelable": true,
997
+ "composed": true,
998
+ "docs": {
999
+ "tags": [],
1000
+ "text": "Emitted once per VIEW/RELATED_VIEW binding \u2014 computed here directly, not relayed from a\nchild (see emitCapabilitiesChecks()), so it covers mrd-table, mrd-memo-container and\nmrd-document-container uniformly. `name` is the view key (viewKeyFor(item)) \u2014 pass it\nback to setViewCanCreate() to answer it. setViewCanCreate()'s optional third argument\nnarrows a table's `item.createTypes` picker to the concrete types the answer allows,\nwhen the API's capabilities response reports them (FINDING-0176 / TASK-0314)."
1001
+ },
1002
+ "complexType": {
1003
+ "original": "{ name: string; type: string; refType?: string; refId?: string; filterClass?: string }",
1004
+ "resolved": "{ name: string; type: string; refType?: string | undefined; refId?: string | undefined; filterClass?: string | undefined; }",
1005
+ "references": {}
1006
+ }
854
1007
  }, {
855
1008
  "method": "mrdUpdateObject",
856
1009
  "name": "mrdUpdateObject",
@@ -1043,6 +1196,35 @@ export class MrdLayoutSection {
1043
1196
  "tags": []
1044
1197
  }
1045
1198
  },
1199
+ "setViewCanCreate": {
1200
+ "complexType": {
1201
+ "signature": "(name: string, value: boolean, allowedTypes?: string[]) => Promise<void>",
1202
+ "parameters": [{
1203
+ "name": "name",
1204
+ "type": "string",
1205
+ "docs": ""
1206
+ }, {
1207
+ "name": "value",
1208
+ "type": "boolean",
1209
+ "docs": ""
1210
+ }, {
1211
+ "name": "allowedTypes",
1212
+ "type": "string[] | undefined",
1213
+ "docs": ""
1214
+ }],
1215
+ "references": {
1216
+ "Promise": {
1217
+ "location": "global",
1218
+ "id": "global::Promise"
1219
+ }
1220
+ },
1221
+ "return": "Promise<void>"
1222
+ },
1223
+ "docs": {
1224
+ "text": "Host answer to mrdViewCheckCapabilities \u2014 whether the current user may create a record\nfor the given view's bound route. `name` is the view key from the event detail. Stored in\ncanCreateMap and passed down as a plain prop to whichever body is mounted for that key\n(mrd-table, mrd-memo-container or mrd-document-container) \u2014 no DOM lookup needed, unlike\nsetViewPage/setViewAggregations, since the prop flows through the next render.",
1225
+ "tags": []
1226
+ }
1227
+ },
1046
1228
  "setViewDistinct": {
1047
1229
  "complexType": {
1048
1230
  "signature": "(name: string, nodeId: string, values: DistinctValue[]) => Promise<void>",
@@ -1197,6 +1379,9 @@ export class MrdLayoutSection {
1197
1379
  return [{
1198
1380
  "propName": "data",
1199
1381
  "methodName": "dataChanged"
1382
+ }, {
1383
+ "propName": "items",
1384
+ "methodName": "itemsChanged"
1200
1385
  }];
1201
1386
  }
1202
1387
  }
@@ -53,6 +53,10 @@ export class MrdMemoContainer {
53
53
  * tooltip, cards/rows are not clickable, and the embedded table's own
54
54
  * create action is disabled too. */
55
55
  this.readOnly = false;
56
+ /** Whether the current user may create a memo here — set by mrd-layout-section from its
57
+ * generic capability check (TASK-0299/FINDING-0155), the same answer the embedded table
58
+ * gets in table mode. Defaults to false (hidden) until confirmed, same as mrd-table. */
59
+ this.canCreate = false;
56
60
  /** The active account's linked person record (userProfile.links.person), if any.
57
61
  * Used both to gate "New memo" (see effectiveContainerHref) and, on submit, to
58
62
  * fill the author slot. */
@@ -285,7 +289,7 @@ export class MrdMemoContainer {
285
289
  * the dialog), so without it there is nothing valid to create — regardless
286
290
  * of whether a parent record already supplies a container. */
287
291
  get canAdd() {
288
- return !this.readOnly && !!this.meHref() && !!this.effectiveContainerHref;
292
+ return this.canCreate && !this.readOnly && !!this.meHref() && !!this.effectiveContainerHref;
289
293
  }
290
294
  /** Path for creating a new memo: `/{dataClass}` for a top-level VIEW, or
291
295
  * `/{fromClass}/{parentId}/{dataClass}` when nested under a parent record. */
@@ -299,7 +303,7 @@ export class MrdMemoContainer {
299
303
  });
300
304
  }
301
305
  renderToolbar() {
302
- return (h("div", { class: "mrd-memo-container__toolbar" }, this.mode === 'cards' && (h("button", { type: "button", class: "mrd-memo-container__action", disabled: !this.canAdd, title: this.readOnly ? t('readonly_access', this.locale) : undefined, onClick: this.openCreate }, 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: "M12 5v14M5 12h14" })), t('memo_new', this.locale))), h("div", { class: "mrd-memo-container__seg", role: "group", "aria-label": t('doc_view_switch', this.locale) }, h("button", { class: "mrd-memo-container__seg-btn", "aria-pressed": String(this.mode === 'cards'), title: t('memo_view_cards', this.locale), "aria-label": t('memo_view_cards', this.locale), onClick: () => this.setMode('cards') }, h("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round", "aria-hidden": "true" }, h("rect", { x: "3", y: "4", width: "7", height: "7", rx: "1" }), h("rect", { x: "14", y: "4", width: "7", height: "7", rx: "1" }), h("rect", { x: "3", y: "15", width: "7", height: "5", rx: "1" }), h("rect", { x: "14", y: "15", width: "7", height: "5", rx: "1" }))), h("button", { class: "mrd-memo-container__seg-btn", "aria-pressed": String(this.mode === 'table'), title: t('memo_view_table', this.locale), "aria-label": t('memo_view_table', this.locale), onClick: () => this.setMode('table') }, 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" }))))));
306
+ return (h("div", { class: "mrd-memo-container__toolbar" }, this.mode === 'cards' && this.canCreate && (h("button", { type: "button", class: "mrd-memo-container__action", disabled: !this.canAdd, title: this.readOnly ? t('readonly_access', this.locale) : undefined, onClick: this.openCreate }, 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: "M12 5v14M5 12h14" })), t('memo_new', this.locale))), h("div", { class: "mrd-memo-container__seg", role: "group", "aria-label": t('doc_view_switch', this.locale) }, h("button", { class: "mrd-memo-container__seg-btn", "aria-pressed": String(this.mode === 'cards'), title: t('memo_view_cards', this.locale), "aria-label": t('memo_view_cards', this.locale), onClick: () => this.setMode('cards') }, h("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2", "stroke-linecap": "round", "stroke-linejoin": "round", "aria-hidden": "true" }, h("rect", { x: "3", y: "4", width: "7", height: "7", rx: "1" }), h("rect", { x: "14", y: "4", width: "7", height: "7", rx: "1" }), h("rect", { x: "3", y: "15", width: "7", height: "5", rx: "1" }), h("rect", { x: "14", y: "15", width: "7", height: "5", rx: "1" }))), h("button", { class: "mrd-memo-container__seg-btn", "aria-pressed": String(this.mode === 'table'), title: t('memo_view_table', this.locale), "aria-label": t('memo_view_table', this.locale), onClick: () => this.setMode('table') }, 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" }))))));
303
307
  }
304
308
  renderDeleteConfirm() {
305
309
  return (h("div", null, h("h2", { class: "mrd-memo-container__modal-title" }, t('memo_delete_confirm_title', this.locale)), h("p", { class: "mrd-memo-container__modal-warning" }, t('memo_delete_confirm_body', this.locale)), h("div", { class: "mrd-memo-container__modal-actions" }, h("button", { type: "button", class: "mrd-memo-container__modal-cancel", onClick: this.cancelDeleteConfirm }, t('cancel', this.locale)), h("button", { type: "button", class: "mrd-memo-container__modal-delete-confirm", onClick: this.confirmDelete }, t('delete', this.locale)))));
@@ -336,7 +340,7 @@ export class MrdMemoContainer {
336
340
  } }));
337
341
  }
338
342
  renderTable() {
339
- return (h("mrd-table", { "data-view": this.viewKey, ref: el => (this.tableEl = el), item: this.item, parentId: this.parentId, locale: this.locale, readOnly: this.readOnly, onMrdLoadPage: (e) => {
343
+ return (h("mrd-table", { "data-view": this.viewKey, ref: el => (this.tableEl = el), item: this.item, parentId: this.parentId, locale: this.locale, readOnly: this.readOnly, canCreate: this.canCreate, onMrdLoadPage: (e) => {
340
344
  e.stopPropagation();
341
345
  this.mrdLoadPage.emit(e.detail);
342
346
  }, onMrdLoadAggregations: (e) => {
@@ -351,7 +355,7 @@ export class MrdMemoContainer {
351
355
  } }));
352
356
  }
353
357
  render() {
354
- return (h(Host, { key: 'a000221db6b1ba12351f38619f319b277b791f68' }, this.renderToolbar(), h("div", { key: '712951c8e391ed693bf6e4263c4f1076172b0451', class: "mrd-memo-container__body" }, this.mode === 'cards' ? this.renderCards() : this.renderTable()), this.renderDialog()));
358
+ return (h(Host, { key: 'b0479aad6dc49935442183fcbdf30c07c2c490ff' }, this.renderToolbar(), h("div", { key: '40d0fb882186001ad4984c1ba64c5a5c529f4d76', class: "mrd-memo-container__body" }, this.mode === 'cards' ? this.renderCards() : this.renderTable()), this.renderDialog()));
355
359
  }
356
360
  static get is() { return "mrd-memo-container"; }
357
361
  static get encapsulation() { return "scoped"; }
@@ -581,6 +585,26 @@ export class MrdMemoContainer {
581
585
  "attribute": "read-only",
582
586
  "defaultValue": "false"
583
587
  },
588
+ "canCreate": {
589
+ "type": "boolean",
590
+ "mutable": false,
591
+ "complexType": {
592
+ "original": "boolean",
593
+ "resolved": "boolean",
594
+ "references": {}
595
+ },
596
+ "required": false,
597
+ "optional": false,
598
+ "docs": {
599
+ "tags": [],
600
+ "text": "Whether the current user may create a memo here \u2014 set by mrd-layout-section from its\ngeneric capability check (TASK-0299/FINDING-0155), the same answer the embedded table\ngets in table mode. Defaults to false (hidden) until confirmed, same as mrd-table."
601
+ },
602
+ "getter": false,
603
+ "setter": false,
604
+ "reflect": false,
605
+ "attribute": "can-create",
606
+ "defaultValue": "false"
607
+ },
584
608
  "me": {
585
609
  "type": "unknown",
586
610
  "mutable": false,