@mmlogic/components 0.5.16 → 0.5.17
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.
- package/dist/cjs/loader.cjs.js +1 -1
- package/dist/cjs/mosterdcomponents.cjs.js +1 -1
- package/dist/cjs/mrd-boolean-field_28.cjs.entry.js +158 -20
- package/dist/collection/collection-manifest.json +2 -2
- package/dist/collection/components/documents/mrd-document-container/mrd-document-container.js +31 -4
- package/dist/collection/components/layout/mrd-layout-section/mrd-layout-section.js +131 -12
- package/dist/collection/components/memo/mrd-memo-container/mrd-memo-container.js +28 -4
- package/dist/collection/components/table/mrd-table/mrd-table.js +105 -2
- package/dist/collection/dev/api.js +17 -0
- package/dist/collection/dev/app.js +36 -0
- package/dist/components/mrd-document-container2.js +1 -1
- package/dist/components/mrd-layout-section.js +1 -1
- package/dist/components/mrd-memo-container2.js +1 -1
- package/dist/components/mrd-table2.js +1 -1
- package/dist/esm/loader.js +1 -1
- package/dist/esm/mosterdcomponents.js +1 -1
- package/dist/esm/mrd-boolean-field_28.entry.js +158 -20
- package/dist/mosterdcomponents/mosterdcomponents.esm.js +1 -1
- package/dist/mosterdcomponents/p-f72668e9.entry.js +3 -0
- package/dist/types/components/documents/mrd-document-container/mrd-document-container.d.ts +7 -0
- package/dist/types/components/layout/mrd-layout-section/mrd-layout-section.d.ts +47 -0
- package/dist/types/components/memo/mrd-memo-container/mrd-memo-container.d.ts +4 -0
- package/dist/types/components/table/mrd-table/mrd-table.d.ts +45 -0
- package/dist/types/components.d.ts +49 -0
- package/package.json +1 -1
- package/dist/mosterdcomponents/cell-renderer-CMUxFREW.js.map +0 -1
- package/dist/mosterdcomponents/client-layout-BJkVeSq6.js.map +0 -1
- package/dist/mosterdcomponents/document-attachments-BufPmMRv.js.map +0 -1
- package/dist/mosterdcomponents/field-helpers-BWAp5fD8.js.map +0 -1
- package/dist/mosterdcomponents/file-upload-common-DDWuC9-3.js.map +0 -1
- package/dist/mosterdcomponents/format-gcecItcD.js.map +0 -1
- package/dist/mosterdcomponents/i18n-OzbszktW.js.map +0 -1
- package/dist/mosterdcomponents/index-1ayBojyN.js.map +0 -1
- package/dist/mosterdcomponents/index-CkcFwdPc.js.map +0 -1
- package/dist/mosterdcomponents/index.esm.js.map +0 -1
- package/dist/mosterdcomponents/mosterdcomponents.esm.js.map +0 -1
- package/dist/mosterdcomponents/p-8a7fa81d.entry.js +0 -3
- package/dist/mosterdcomponents/purify.es-_duHfPgC.js.map +0 -1
- package/dist/mosterdcomponents/query-params-3TXd5CUO.js.map +0 -1
- package/dist/mosterdcomponents/quill-C9pgw_k-.js.map +0 -1
- package/dist/mosterdcomponents/table-query-DJq1uKqJ.js.map +0 -1
- package/dist/mosterdcomponents/validation-B_0Lx9DE.js.map +0 -1
package/dist/collection/components/documents/mrd-document-container/mrd-document-container.js
CHANGED
|
@@ -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: '
|
|
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
|
}
|
|
@@ -31,7 +31,16 @@ export class MrdLayoutSection {
|
|
|
31
31
|
this.imagePreviewUrl = null;
|
|
32
32
|
this.imagePreviews = {};
|
|
33
33
|
this.openHistoryField = null;
|
|
34
|
+
/** Answers to mrdViewCheckCapabilities, keyed by view key — see emitCapabilitiesChecks().
|
|
35
|
+
* Passed straight down as a `canCreate`/`mayCreate` prop to whichever body (mrd-table,
|
|
36
|
+
* mrd-memo-container, mrd-document-container) is mounted for that key; a missing entry
|
|
37
|
+
* means "not answered yet" and is treated as false (hidden), same default as mrd-table's
|
|
38
|
+
* own standalone behaviour (TASK-0299). */
|
|
39
|
+
this.canCreateMap = {};
|
|
34
40
|
this.historyClickOutside = null;
|
|
41
|
+
/** Signature last asked per view key (see emitCapabilitiesChecks()) — avoids re-emitting
|
|
42
|
+
* mrdViewCheckCapabilities on every data/items update when the binding hasn't changed. */
|
|
43
|
+
this.capabilitiesAsked = new Map();
|
|
35
44
|
this.searchTimers = {};
|
|
36
45
|
this.handleViewLoadPage = (e, name) => {
|
|
37
46
|
e.stopPropagation();
|
|
@@ -51,6 +60,10 @@ export class MrdLayoutSection {
|
|
|
51
60
|
};
|
|
52
61
|
}
|
|
53
62
|
componentDidLoad() {
|
|
63
|
+
// No DOM/child-hydration dependency, unlike initEmbeddedTables()/emitLoadImages() below —
|
|
64
|
+
// run it immediately rather than deferring, so the host's answer (setViewCanCreate) can
|
|
65
|
+
// land before the child bodies even mount.
|
|
66
|
+
this.emitCapabilitiesChecks();
|
|
54
67
|
setTimeout(() => {
|
|
55
68
|
this.initEmbeddedTables();
|
|
56
69
|
this.emitLoadImages();
|
|
@@ -69,9 +82,15 @@ export class MrdLayoutSection {
|
|
|
69
82
|
}
|
|
70
83
|
dataChanged(newVal) {
|
|
71
84
|
if (newVal && Object.keys(newVal).length > 0) {
|
|
85
|
+
this.emitCapabilitiesChecks();
|
|
72
86
|
setTimeout(() => this.initEmbeddedTables(), 0);
|
|
73
87
|
}
|
|
74
88
|
}
|
|
89
|
+
/** A host can swap `items` (e.g. a different dashboard layout) without `data` changing —
|
|
90
|
+
* dataChanged() alone would miss that binding change. */
|
|
91
|
+
itemsChanged() {
|
|
92
|
+
this.emitCapabilitiesChecks();
|
|
93
|
+
}
|
|
75
94
|
async initEmbeddedTables() {
|
|
76
95
|
const tables = this.el.querySelectorAll('mrd-table[data-view]');
|
|
77
96
|
for (const table of Array.from(tables)) {
|
|
@@ -99,6 +118,53 @@ export class MrdLayoutSection {
|
|
|
99
118
|
}
|
|
100
119
|
}
|
|
101
120
|
}
|
|
121
|
+
/** Parent record id extracted from `data._links.self.href` — the same value every
|
|
122
|
+
* VIEW/RELATED_VIEW item on this record shares, used for RELATED_VIEW path construction
|
|
123
|
+
* (renderRelatedView) and capability checks (emitCapabilitiesChecks). */
|
|
124
|
+
computeParentId() {
|
|
125
|
+
var _a, _b, _c, _d, _e;
|
|
126
|
+
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 : '';
|
|
127
|
+
return (_e = selfHref.split('/').filter(Boolean).pop()) !== null && _e !== void 0 ? _e : '';
|
|
128
|
+
}
|
|
129
|
+
/** Asks, once per VIEW/RELATED_VIEW binding, whether the current user may create a record
|
|
130
|
+
* there — the single generic place for this check (TASK-0299/FINDING-0155), so mrd-table,
|
|
131
|
+
* mrd-memo-container and mrd-document-container all get the answer the same way: as a plain
|
|
132
|
+
* `canCreate`/`mayCreate` prop from this component, computed here and handed down in
|
|
133
|
+
* renderTable()/renderMemoContainer()/renderDocumentContainer() — none of the three leaf
|
|
134
|
+
* components need to ask on their own when rendered through mrd-layout-section (mrd-table's
|
|
135
|
+
* own self-ask only kicks in for standalone use, i.e. its `canCreate` prop left undefined).
|
|
136
|
+
*
|
|
137
|
+
* The route/path variables mirror what each renderer already uses for its own rows —
|
|
138
|
+
* `type` = `dataClass`, `refType`/`refId` = `fromClass`/parentId for a RELATED_VIEW, and
|
|
139
|
+
* `filterClass` when the view is scoped to one subtype (the same value sent as `?type=` on
|
|
140
|
+
* the row fetch). A signature (not just the key) is tracked per binding so a genuine route
|
|
141
|
+
* change re-asks and hides again, without re-asking on every unrelated data/items update. */
|
|
142
|
+
emitCapabilitiesChecks() {
|
|
143
|
+
var _a, _b, _c, _d;
|
|
144
|
+
const parentId = this.computeParentId();
|
|
145
|
+
for (const item of flattenItems(this.items)) {
|
|
146
|
+
if (item.type !== ClientLayoutItemType.VIEW && item.type !== ClientLayoutItemType.RELATED_VIEW)
|
|
147
|
+
continue;
|
|
148
|
+
const key = viewKeyFor(item);
|
|
149
|
+
if (!key || !item.view)
|
|
150
|
+
continue;
|
|
151
|
+
const isRelatedView = item.type === ClientLayoutItemType.RELATED_VIEW;
|
|
152
|
+
const type = (_b = (_a = item.dataClass) !== null && _a !== void 0 ? _a : item.relatedClass) !== null && _b !== void 0 ? _b : key;
|
|
153
|
+
const refType = isRelatedView ? ((_c = item.fromClass) !== null && _c !== void 0 ? _c : '') : undefined;
|
|
154
|
+
const refId = isRelatedView ? parentId : undefined;
|
|
155
|
+
const filterClass = (_d = item.filterClass) !== null && _d !== void 0 ? _d : undefined;
|
|
156
|
+
const signature = JSON.stringify([type, refType, refId, filterClass]);
|
|
157
|
+
if (this.capabilitiesAsked.get(key) === signature)
|
|
158
|
+
continue;
|
|
159
|
+
this.capabilitiesAsked.set(key, signature);
|
|
160
|
+
if (key in this.canCreateMap) {
|
|
161
|
+
const next = Object.assign({}, this.canCreateMap);
|
|
162
|
+
delete next[key];
|
|
163
|
+
this.canCreateMap = next;
|
|
164
|
+
}
|
|
165
|
+
this.mrdViewCheckCapabilities.emit(Object.assign(Object.assign({ name: key, type }, (refType !== undefined ? { refType, refId } : {})), (filterClass ? { filterClass } : {})));
|
|
166
|
+
}
|
|
167
|
+
}
|
|
102
168
|
/** Inject search results. Pass dataClass to target a specific SEARCH item; omit when there is only one. */
|
|
103
169
|
async setSearchResults(results, dataClass) {
|
|
104
170
|
const key = dataClass !== null && dataClass !== void 0 ? dataClass : this.resolveSearchKey();
|
|
@@ -140,6 +206,14 @@ export class MrdLayoutSection {
|
|
|
140
206
|
return;
|
|
141
207
|
await table.setAggregations(data);
|
|
142
208
|
}
|
|
209
|
+
/** Host answer to mrdViewCheckCapabilities — whether the current user may create a record
|
|
210
|
+
* for the given view's bound route. `name` is the view key from the event detail. Stored in
|
|
211
|
+
* canCreateMap and passed down as a plain prop to whichever body is mounted for that key
|
|
212
|
+
* (mrd-table, mrd-memo-container or mrd-document-container) — no DOM lookup needed, unlike
|
|
213
|
+
* setViewPage/setViewAggregations, since the prop flows through the next render. */
|
|
214
|
+
async setViewCanCreate(name, value) {
|
|
215
|
+
this.canCreateMap = Object.assign(Object.assign({}, this.canCreateMap), { [name]: value });
|
|
216
|
+
}
|
|
143
217
|
/** Inject distinct values into an embedded mrd-document-list for a document view. */
|
|
144
218
|
async setViewDistinct(name, nodeId, values) {
|
|
145
219
|
const list = this.el.querySelector(`mrd-document-list[data-doclist="${name}"]`);
|
|
@@ -227,20 +301,19 @@ export class MrdLayoutSection {
|
|
|
227
301
|
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
302
|
}
|
|
229
303
|
renderRelatedView(item) {
|
|
230
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k
|
|
304
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
|
|
231
305
|
const key = viewKeyFor(item);
|
|
232
306
|
if (!key)
|
|
233
307
|
return null;
|
|
234
308
|
if (!item.view)
|
|
235
309
|
return null;
|
|
236
310
|
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
311
|
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 =
|
|
312
|
+
const parentId = this.computeParentId();
|
|
240
313
|
// The API publishes `archetypes` on the VIEW/RELATED_VIEW item itself
|
|
241
314
|
// (sibling of `view`), not inside `view`.
|
|
242
|
-
const docArchetype = resolveArchetype((
|
|
243
|
-
const memoArchetype = !docArchetype ? resolveArchetype((
|
|
315
|
+
const docArchetype = resolveArchetype((_f = item.archetypes) !== null && _f !== void 0 ? _f : (_g = item.view) === null || _g === void 0 ? void 0 : _g.archetypes, ARCHETYPE_DOCUMENT);
|
|
316
|
+
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
317
|
// Canonical parent href for single-container seeding: use the RELATED_VIEW's
|
|
245
318
|
// fromClass (base class) rather than the route class, so polymorphic subtypes
|
|
246
319
|
// resolve to the same container the documents actually reference.
|
|
@@ -251,7 +324,7 @@ export class MrdLayoutSection {
|
|
|
251
324
|
// back to this parent; only when it matches the container slot does the
|
|
252
325
|
// parent's own href double as the memo's container.
|
|
253
326
|
const memoContainerRelated = item.type !== ClientLayoutItemType.RELATED_VIEW
|
|
254
|
-
|| item.inverseRelation === ((
|
|
327
|
+
|| item.inverseRelation === ((_k = memoArchetype === null || memoArchetype === void 0 ? void 0 : memoArchetype.slots) === null || _k === void 0 ? void 0 : _k.container);
|
|
255
328
|
const memoContainerHref = memoContainerRelated ? containerHref : '';
|
|
256
329
|
// Distinct from memoContainerRelated: only a genuine container-scoped
|
|
257
330
|
// RELATED_VIEW (a company's own memo tab) makes every card's container
|
|
@@ -264,7 +337,8 @@ export class MrdLayoutSection {
|
|
|
264
337
|
* swaps between the folder tree and the flat table. Events are re-emitted to
|
|
265
338
|
* the host with the view key, exactly as the raw list/table events were. */
|
|
266
339
|
renderDocumentContainer(item, key, parentId, archetype, containerHref) {
|
|
267
|
-
|
|
340
|
+
var _a;
|
|
341
|
+
return (h("mrd-document-container", { item: item, archetype: archetype, parentId: parentId, containerHref: containerHref, viewKey: key, locale: this.locale, readOnly: this.readOnly, mayCreate: (_a = this.canCreateMap[key]) !== null && _a !== void 0 ? _a : false, onMrdLoadDistinct: (e) => {
|
|
268
342
|
e.stopPropagation();
|
|
269
343
|
this.mrdLoadViewDistinct.emit(Object.assign({ name: key }, e.detail));
|
|
270
344
|
}, onMrdLoadPage: (e) => {
|
|
@@ -299,9 +373,9 @@ export class MrdLayoutSection {
|
|
|
299
373
|
/** Memo collection view: owns the view-switch + "New memo" and swaps between
|
|
300
374
|
* the card list and the flat table, mirroring renderDocumentContainer(). */
|
|
301
375
|
renderMemoContainer(item, key, parentId, archetype, containerHref, containerRelated, showContainerLink) {
|
|
302
|
-
var _a, _b, _c, _d;
|
|
376
|
+
var _a, _b, _c, _d, _e;
|
|
303
377
|
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) => {
|
|
378
|
+
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: (_e = this.canCreateMap[key]) !== null && _e !== void 0 ? _e : false, me: this.me, labelsListItems: labelsListItems, onMrdNavigate: (e) => {
|
|
305
379
|
var _a;
|
|
306
380
|
e.stopPropagation();
|
|
307
381
|
this.mrdNavigate.emit(Object.assign(Object.assign({}, e.detail), { navigateBehaviour: (_a = item.navigateBehaviour) !== null && _a !== void 0 ? _a : ClientLayoutNavigateBehaviour.PAGE }));
|
|
@@ -328,7 +402,8 @@ export class MrdLayoutSection {
|
|
|
328
402
|
} }));
|
|
329
403
|
}
|
|
330
404
|
renderTable(item, key, parentId) {
|
|
331
|
-
|
|
405
|
+
var _a;
|
|
406
|
+
return (h("mrd-table", { "data-view": key, item: item, parentId: parentId, locale: this.locale, readOnly: this.readOnly, canCreate: (_a = this.canCreateMap[key]) !== null && _a !== void 0 ? _a : false, onMrdLoadPage: (e) => this.handleViewLoadPage(e, key), onMrdLoadAggregations: (e) => {
|
|
332
407
|
var _a;
|
|
333
408
|
e.stopPropagation();
|
|
334
409
|
this.mrdLoadViewAggregations.emit(Object.assign({ name: key, dataClass: (_a = item.dataClass) !== null && _a !== void 0 ? _a : key }, e.detail));
|
|
@@ -466,7 +541,7 @@ export class MrdLayoutSection {
|
|
|
466
541
|
const emailArchetype = !docArchetype && !invoiceArchetype ? resolveArchetype(this.archetypes, ARCHETYPE_EMAIL) : undefined;
|
|
467
542
|
const memoArchetype = !docArchetype && !invoiceArchetype && !emailArchetype ? resolveArchetype(this.archetypes, ARCHETYPE_MEMO) : undefined;
|
|
468
543
|
const anyArchetype = docArchetype || invoiceArchetype || emailArchetype || memoArchetype;
|
|
469
|
-
return (h(Host, { key: '
|
|
544
|
+
return (h(Host, { key: '508ca481c777d65d1739f7b5f30f5f3ad8722655' }, h("div", { key: 'c02630235a2a21511b391ff7b5bebeca1bcc6903', 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
545
|
}
|
|
471
546
|
static get is() { return "mrd-layout-section"; }
|
|
472
547
|
static get encapsulation() { return "scoped"; }
|
|
@@ -680,7 +755,8 @@ export class MrdLayoutSection {
|
|
|
680
755
|
"searchResultsMap": {},
|
|
681
756
|
"imagePreviewUrl": {},
|
|
682
757
|
"imagePreviews": {},
|
|
683
|
-
"openHistoryField": {}
|
|
758
|
+
"openHistoryField": {},
|
|
759
|
+
"canCreateMap": {}
|
|
684
760
|
};
|
|
685
761
|
}
|
|
686
762
|
static get events() {
|
|
@@ -851,6 +927,21 @@ export class MrdLayoutSection {
|
|
|
851
927
|
"resolved": "{ name: string; nodeId: string; distinct: string; path: string; qs: string; }",
|
|
852
928
|
"references": {}
|
|
853
929
|
}
|
|
930
|
+
}, {
|
|
931
|
+
"method": "mrdViewCheckCapabilities",
|
|
932
|
+
"name": "mrdViewCheckCapabilities",
|
|
933
|
+
"bubbles": true,
|
|
934
|
+
"cancelable": true,
|
|
935
|
+
"composed": true,
|
|
936
|
+
"docs": {
|
|
937
|
+
"tags": [],
|
|
938
|
+
"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."
|
|
939
|
+
},
|
|
940
|
+
"complexType": {
|
|
941
|
+
"original": "{ name: string; type: string; refType?: string; refId?: string; filterClass?: string }",
|
|
942
|
+
"resolved": "{ name: string; type: string; refType?: string | undefined; refId?: string | undefined; filterClass?: string | undefined; }",
|
|
943
|
+
"references": {}
|
|
944
|
+
}
|
|
854
945
|
}, {
|
|
855
946
|
"method": "mrdUpdateObject",
|
|
856
947
|
"name": "mrdUpdateObject",
|
|
@@ -1043,6 +1134,31 @@ export class MrdLayoutSection {
|
|
|
1043
1134
|
"tags": []
|
|
1044
1135
|
}
|
|
1045
1136
|
},
|
|
1137
|
+
"setViewCanCreate": {
|
|
1138
|
+
"complexType": {
|
|
1139
|
+
"signature": "(name: string, value: boolean) => Promise<void>",
|
|
1140
|
+
"parameters": [{
|
|
1141
|
+
"name": "name",
|
|
1142
|
+
"type": "string",
|
|
1143
|
+
"docs": ""
|
|
1144
|
+
}, {
|
|
1145
|
+
"name": "value",
|
|
1146
|
+
"type": "boolean",
|
|
1147
|
+
"docs": ""
|
|
1148
|
+
}],
|
|
1149
|
+
"references": {
|
|
1150
|
+
"Promise": {
|
|
1151
|
+
"location": "global",
|
|
1152
|
+
"id": "global::Promise"
|
|
1153
|
+
}
|
|
1154
|
+
},
|
|
1155
|
+
"return": "Promise<void>"
|
|
1156
|
+
},
|
|
1157
|
+
"docs": {
|
|
1158
|
+
"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.",
|
|
1159
|
+
"tags": []
|
|
1160
|
+
}
|
|
1161
|
+
},
|
|
1046
1162
|
"setViewDistinct": {
|
|
1047
1163
|
"complexType": {
|
|
1048
1164
|
"signature": "(name: string, nodeId: string, values: DistinctValue[]) => Promise<void>",
|
|
@@ -1197,6 +1313,9 @@ export class MrdLayoutSection {
|
|
|
1197
1313
|
return [{
|
|
1198
1314
|
"propName": "data",
|
|
1199
1315
|
"methodName": "dataChanged"
|
|
1316
|
+
}, {
|
|
1317
|
+
"propName": "items",
|
|
1318
|
+
"methodName": "itemsChanged"
|
|
1200
1319
|
}];
|
|
1201
1320
|
}
|
|
1202
1321
|
}
|
|
@@ -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: '
|
|
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,
|
|
@@ -120,6 +120,10 @@ export class MrdTable {
|
|
|
120
120
|
this.aggregationsPending = false;
|
|
121
121
|
/** Lower bound on total derived from setPage() hasNext info; grows as pages load. */
|
|
122
122
|
this.minKnownTotal = 0;
|
|
123
|
+
/** Self-asked answer for standalone use (the `canCreate` prop is undefined) — defaults to
|
|
124
|
+
* hidden (TASK-0299) until the host answers mrdCheckCapabilities via setCanCreate(). Ignored
|
|
125
|
+
* when the host controls `canCreate` directly; see effectiveCanCreate(). */
|
|
126
|
+
this.selfCanCreate = false;
|
|
123
127
|
this.handleScroll = (e) => {
|
|
124
128
|
const scroller = e.currentTarget;
|
|
125
129
|
const { renderStart, renderEnd } = computeWindow(scroller.scrollTop, this.rowHeight, this.tableHeight, this.baseTotal);
|
|
@@ -154,6 +158,14 @@ export class MrdTable {
|
|
|
154
158
|
this.searchOpen = false;
|
|
155
159
|
this.searchQuery = '';
|
|
156
160
|
this.stashedFilters = null;
|
|
161
|
+
this.emitCapabilitiesCheck();
|
|
162
|
+
}
|
|
163
|
+
/** A parent-record navigation can reuse the same `item` reference while only `parentId`
|
|
164
|
+
* changes (e.g. clicking a different dossier for the same RELATED_VIEW layout) — itemChanged()
|
|
165
|
+
* would not fire for that. Re-ask so a stale canCreate answer from the previous parent never
|
|
166
|
+
* leaks into the new one. */
|
|
167
|
+
parentIdChanged() {
|
|
168
|
+
this.emitCapabilitiesCheck();
|
|
157
169
|
}
|
|
158
170
|
// ── Lifecycle ──────────────────────────────────────────────────────────────
|
|
159
171
|
componentWillLoad() {
|
|
@@ -161,6 +173,7 @@ export class MrdTable {
|
|
|
161
173
|
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 : '');
|
|
162
174
|
}
|
|
163
175
|
componentDidLoad() {
|
|
176
|
+
this.emitCapabilitiesCheck();
|
|
164
177
|
this.staleCheckTimer = setInterval(() => this.checkStalePages(), STALE_CHECK_INTERVAL_MS);
|
|
165
178
|
// A table inside a hidden tab pane renders and loads page 0 without ever having a box, so
|
|
166
179
|
// the width freeze cannot measure anything. ResizeObserver fires the moment the pane is
|
|
@@ -279,6 +292,14 @@ export class MrdTable {
|
|
|
279
292
|
// Fresh totals can be wider than the ones the columns were checked against.
|
|
280
293
|
this.fixedContentMeasured = false;
|
|
281
294
|
}
|
|
295
|
+
/** Host answer to mrdCheckCapabilities — whether the current user may create a record for
|
|
296
|
+
* the table's currently bound route (standalone use only; ignored while the `canCreate`
|
|
297
|
+
* prop is set, since the host controls it directly in that mode). Call it any time after
|
|
298
|
+
* the event fires; a stale answer for an old binding is discarded automatically
|
|
299
|
+
* (selfCanCreate is reset to false before every new mrdCheckCapabilities emission). */
|
|
300
|
+
async setCanCreate(value) {
|
|
301
|
+
this.selfCanCreate = value;
|
|
302
|
+
}
|
|
282
303
|
// ── Lifecycle ──────────────────────────────────────────────────────────────
|
|
283
304
|
disconnectedCallback() {
|
|
284
305
|
if (this.resizeObserver) {
|
|
@@ -638,6 +659,22 @@ export class MrdTable {
|
|
|
638
659
|
dataClass: v.dataClass,
|
|
639
660
|
});
|
|
640
661
|
}
|
|
662
|
+
/** Asks the host whether creating is allowed for the table's current binding (item, parent
|
|
663
|
+
* and active view) — only in standalone use (`canCreate` prop undefined). A host that
|
|
664
|
+
* controls `canCreate` directly already knows the answer, so the table never asks on its
|
|
665
|
+
* own in that mode. Resets selfCanCreate to false first so the add button stays hidden for
|
|
666
|
+
* the new binding until the host answers — it never inherits the previous binding's answer. */
|
|
667
|
+
emitCapabilitiesCheck() {
|
|
668
|
+
var _a, _b;
|
|
669
|
+
if (this.canCreate !== undefined)
|
|
670
|
+
return;
|
|
671
|
+
this.selfCanCreate = false;
|
|
672
|
+
const v = this.allViews[this.activeViewIdx];
|
|
673
|
+
if (!(v === null || v === void 0 ? void 0 : v.dataClass))
|
|
674
|
+
return;
|
|
675
|
+
const isRelatedView = ((_a = this.item) === null || _a === void 0 ? void 0 : _a.type) === 'RELATED_VIEW';
|
|
676
|
+
this.mrdCheckCapabilities.emit(Object.assign(Object.assign({ type: v.dataClass }, (isRelatedView ? { refType: (_b = v.fromClass) !== null && _b !== void 0 ? _b : '', refId: this.parentId } : {})), (v.filterClass ? { filterClass: v.filterClass } : {})));
|
|
677
|
+
}
|
|
641
678
|
/** Build query params for a page request from current sort, view filters, filterClass and active column filters.
|
|
642
679
|
*
|
|
643
680
|
* `q` and column filters are mutually exclusive on the backend — combining them breaks
|
|
@@ -672,11 +709,17 @@ export class MrdTable {
|
|
|
672
709
|
var _a, _b, _c;
|
|
673
710
|
return ((_c = (_b = (_a = this.allViews[this.activeViewIdx]) === null || _a === void 0 ? void 0 : _a.view) === null || _b === void 0 ? void 0 : _b.values) !== null && _c !== void 0 ? _c : []);
|
|
674
711
|
}
|
|
712
|
+
/** `canCreate` when the host controls it directly, else the table's own self-asked answer. */
|
|
713
|
+
get effectiveCanCreate() {
|
|
714
|
+
return this.canCreate !== undefined ? this.canCreate : this.selfCanCreate;
|
|
715
|
+
}
|
|
675
716
|
get tableActions() {
|
|
676
717
|
var _a, _b;
|
|
677
718
|
const raw = (_b = (_a = this.item) === null || _a === void 0 ? void 0 : _a.actions) !== null && _b !== void 0 ? _b : [];
|
|
678
719
|
return (raw !== null && raw !== void 0 ? raw : []).reduce((acc, a) => {
|
|
679
|
-
|
|
720
|
+
// Hidden (not just disabled) until creating is confirmed allowed for this binding —
|
|
721
|
+
// see effectiveCanCreate() / emitCapabilitiesCheck() (TASK-0299).
|
|
722
|
+
if (a === 'NEW' && this.effectiveCanCreate)
|
|
680
723
|
acc.push({ action: 'create', label: t('table_new_record', this.locale), icon: 'assets/sprites.svg#icon-plus', variant: 'primary', disabled: this.readOnly });
|
|
681
724
|
if (a === 'EXPORT')
|
|
682
725
|
acc.push({ action: 'export', label: t('table_export_excel', this.locale), icon: 'assets/sprites.svg#icon-file-excel' });
|
|
@@ -1084,6 +1127,7 @@ export class MrdTable {
|
|
|
1084
1127
|
this.searchOpen = false;
|
|
1085
1128
|
this.searchQuery = '';
|
|
1086
1129
|
this.stashedFilters = null;
|
|
1130
|
+
this.emitCapabilitiesCheck();
|
|
1087
1131
|
this.init();
|
|
1088
1132
|
}
|
|
1089
1133
|
toggleViewPopover(e) {
|
|
@@ -1475,6 +1519,25 @@ export class MrdTable {
|
|
|
1475
1519
|
"reflect": false,
|
|
1476
1520
|
"attribute": "read-only",
|
|
1477
1521
|
"defaultValue": "false"
|
|
1522
|
+
},
|
|
1523
|
+
"canCreate": {
|
|
1524
|
+
"type": "boolean",
|
|
1525
|
+
"mutable": false,
|
|
1526
|
+
"complexType": {
|
|
1527
|
+
"original": "boolean",
|
|
1528
|
+
"resolved": "boolean | undefined",
|
|
1529
|
+
"references": {}
|
|
1530
|
+
},
|
|
1531
|
+
"required": false,
|
|
1532
|
+
"optional": true,
|
|
1533
|
+
"docs": {
|
|
1534
|
+
"tags": [],
|
|
1535
|
+
"text": "Whether the current user may create a record for the table's bound route. Leave\n`undefined` for standalone use \u2014 the table then asks on its own via mrdCheckCapabilities\nand hides the create button until setCanCreate() answers. Set it explicitly when a host\nalready knows the answer (e.g. mrd-layout-section, which asks once per binding and hands\nthe answer to whichever body it mounts) \u2014 the table then trusts it directly and never\nasks on its own (TASK-0299)."
|
|
1536
|
+
},
|
|
1537
|
+
"getter": false,
|
|
1538
|
+
"setter": false,
|
|
1539
|
+
"reflect": false,
|
|
1540
|
+
"attribute": "can-create"
|
|
1478
1541
|
}
|
|
1479
1542
|
};
|
|
1480
1543
|
}
|
|
@@ -1503,7 +1566,8 @@ export class MrdTable {
|
|
|
1503
1566
|
"jsonModal": {},
|
|
1504
1567
|
"aggregations": {},
|
|
1505
1568
|
"aggregationsTotal": {},
|
|
1506
|
-
"minKnownTotal": {}
|
|
1569
|
+
"minKnownTotal": {},
|
|
1570
|
+
"selfCanCreate": {}
|
|
1507
1571
|
};
|
|
1508
1572
|
}
|
|
1509
1573
|
static get events() {
|
|
@@ -1608,6 +1672,21 @@ export class MrdTable {
|
|
|
1608
1672
|
"resolved": "{ path: string; qs: string; aggQs: string; }",
|
|
1609
1673
|
"references": {}
|
|
1610
1674
|
}
|
|
1675
|
+
}, {
|
|
1676
|
+
"method": "mrdCheckCapabilities",
|
|
1677
|
+
"name": "mrdCheckCapabilities",
|
|
1678
|
+
"bubbles": true,
|
|
1679
|
+
"cancelable": true,
|
|
1680
|
+
"composed": true,
|
|
1681
|
+
"docs": {
|
|
1682
|
+
"tags": [],
|
|
1683
|
+
"text": "Standalone use only (never fires while the `canCreate` prop is set \u2014 see there). Fired\nonce per table binding (initial load, and whenever the bound item, parent record or active\nview changes) to ask the host whether the current user may create a record here. Carries\nthe same route the table already uses for its own rows, unconstructed \u2014 VIEW:\nGET /data/{tenant}/{type}/capabilities; RELATED_VIEW: GET /data/{tenant}/{refType}/{refId}/{type}/capabilities.\n`filterClass`, when the view declares one, is the same subtype the row fetch sends as\n`?type=` \u2014 e.g. a \"contents\" RELATED_VIEW filtered to `document` asks specifically whether\na document may be created here, not any content type.\nThe add button stays hidden until the host calls setCanCreate() (FINDING-0155 / TASK-0299)."
|
|
1684
|
+
},
|
|
1685
|
+
"complexType": {
|
|
1686
|
+
"original": "{ type: string; refType?: string; refId?: string; filterClass?: string }",
|
|
1687
|
+
"resolved": "{ type: string; refType?: string | undefined; refId?: string | undefined; filterClass?: string | undefined; }",
|
|
1688
|
+
"references": {}
|
|
1689
|
+
}
|
|
1611
1690
|
}];
|
|
1612
1691
|
}
|
|
1613
1692
|
static get methods() {
|
|
@@ -1692,6 +1771,27 @@ export class MrdTable {
|
|
|
1692
1771
|
"text": "Inject aggregation totals returned by the /aggregations endpoint.",
|
|
1693
1772
|
"tags": []
|
|
1694
1773
|
}
|
|
1774
|
+
},
|
|
1775
|
+
"setCanCreate": {
|
|
1776
|
+
"complexType": {
|
|
1777
|
+
"signature": "(value: boolean) => Promise<void>",
|
|
1778
|
+
"parameters": [{
|
|
1779
|
+
"name": "value",
|
|
1780
|
+
"type": "boolean",
|
|
1781
|
+
"docs": ""
|
|
1782
|
+
}],
|
|
1783
|
+
"references": {
|
|
1784
|
+
"Promise": {
|
|
1785
|
+
"location": "global",
|
|
1786
|
+
"id": "global::Promise"
|
|
1787
|
+
}
|
|
1788
|
+
},
|
|
1789
|
+
"return": "Promise<void>"
|
|
1790
|
+
},
|
|
1791
|
+
"docs": {
|
|
1792
|
+
"text": "Host answer to mrdCheckCapabilities \u2014 whether the current user may create a record for\nthe table's currently bound route (standalone use only; ignored while the `canCreate`\nprop is set, since the host controls it directly in that mode). Call it any time after\nthe event fires; a stale answer for an old binding is discarded automatically\n(selfCanCreate is reset to false before every new mrdCheckCapabilities emission).",
|
|
1793
|
+
"tags": []
|
|
1794
|
+
}
|
|
1695
1795
|
}
|
|
1696
1796
|
};
|
|
1697
1797
|
}
|
|
@@ -1703,6 +1803,9 @@ export class MrdTable {
|
|
|
1703
1803
|
}, {
|
|
1704
1804
|
"propName": "item",
|
|
1705
1805
|
"methodName": "itemChanged"
|
|
1806
|
+
}, {
|
|
1807
|
+
"propName": "parentId",
|
|
1808
|
+
"methodName": "parentIdChanged"
|
|
1706
1809
|
}];
|
|
1707
1810
|
}
|
|
1708
1811
|
}
|