@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.
- 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 +244 -21
- 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 +196 -11
- package/dist/collection/components/memo/mrd-memo-container/mrd-memo-container.js +28 -4
- package/dist/collection/components/table/mrd-table/mrd-table.js +193 -4
- package/dist/collection/dev/api.js +19 -0
- package/dist/collection/dev/app.js +75 -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 +244 -21
- package/dist/mosterdcomponents/mosterdcomponents.esm.js +1 -1
- package/dist/mosterdcomponents/p-29d6dcbc.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 +69 -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 +75 -0
- package/dist/types/components.d.ts +79 -0
- package/package.json +1 -1
- package/dist/mosterdcomponents/p-8a7fa81d.entry.js +0 -3
|
@@ -76,6 +76,12 @@ export class MrdTable {
|
|
|
76
76
|
* explaining why) so a viewer-role user can see at a glance that this dashboard
|
|
77
77
|
* is read-only. Sorting, filtering and export stay available — those don't mutate data. */
|
|
78
78
|
this.readOnly = false;
|
|
79
|
+
/** When false, skips the standalone mrdCheckCapabilities round trip entirely and renders
|
|
80
|
+
* the create action immediately as allowed — for a tenant that has no access control
|
|
81
|
+
* configured, the answer would always be true anyway (FINDING-0171 / TASK-0322). Ignored
|
|
82
|
+
* while `canCreate` is set explicitly (the host already controls the answer directly in
|
|
83
|
+
* that mode). Default `true` keeps today's ask-and-hide behaviour unchanged. */
|
|
84
|
+
this.accessControlEnabled = true;
|
|
79
85
|
// ── Internal state ─────────────────────────────────────────────────────────
|
|
80
86
|
/** Index into allViews[] for the currently displayed view. 0 = primary, 1+ = alternatives. */
|
|
81
87
|
this.activeViewIdx = 0;
|
|
@@ -120,6 +126,13 @@ export class MrdTable {
|
|
|
120
126
|
this.aggregationsPending = false;
|
|
121
127
|
/** Lower bound on total derived from setPage() hasNext info; grows as pages load. */
|
|
122
128
|
this.minKnownTotal = 0;
|
|
129
|
+
/** Self-asked answer for standalone use (the `canCreate` prop is undefined) — defaults to
|
|
130
|
+
* hidden (TASK-0299) until the host answers mrdCheckCapabilities via setCanCreate(). Ignored
|
|
131
|
+
* when the host controls `canCreate` directly; see effectiveCanCreate(). */
|
|
132
|
+
this.selfCanCreate = false;
|
|
133
|
+
/** Self-asked companion to selfCanCreate — the concrete types setCanCreate() reported allowed,
|
|
134
|
+
* or `undefined` while unanswered / when the host's mrdCheckCapabilities answer omitted it. */
|
|
135
|
+
this.selfAllowedTypes = undefined;
|
|
123
136
|
this.handleScroll = (e) => {
|
|
124
137
|
const scroller = e.currentTarget;
|
|
125
138
|
const { renderStart, renderEnd } = computeWindow(scroller.scrollTop, this.rowHeight, this.tableHeight, this.baseTotal);
|
|
@@ -154,6 +167,14 @@ export class MrdTable {
|
|
|
154
167
|
this.searchOpen = false;
|
|
155
168
|
this.searchQuery = '';
|
|
156
169
|
this.stashedFilters = null;
|
|
170
|
+
this.emitCapabilitiesCheck();
|
|
171
|
+
}
|
|
172
|
+
/** A parent-record navigation can reuse the same `item` reference while only `parentId`
|
|
173
|
+
* changes (e.g. clicking a different dossier for the same RELATED_VIEW layout) — itemChanged()
|
|
174
|
+
* would not fire for that. Re-ask so a stale canCreate answer from the previous parent never
|
|
175
|
+
* leaks into the new one. */
|
|
176
|
+
parentIdChanged() {
|
|
177
|
+
this.emitCapabilitiesCheck();
|
|
157
178
|
}
|
|
158
179
|
// ── Lifecycle ──────────────────────────────────────────────────────────────
|
|
159
180
|
componentWillLoad() {
|
|
@@ -161,6 +182,7 @@ export class MrdTable {
|
|
|
161
182
|
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
183
|
}
|
|
163
184
|
componentDidLoad() {
|
|
185
|
+
this.emitCapabilitiesCheck();
|
|
164
186
|
this.staleCheckTimer = setInterval(() => this.checkStalePages(), STALE_CHECK_INTERVAL_MS);
|
|
165
187
|
// A table inside a hidden tab pane renders and loads page 0 without ever having a box, so
|
|
166
188
|
// the width freeze cannot measure anything. ResizeObserver fires the moment the pane is
|
|
@@ -279,6 +301,15 @@ export class MrdTable {
|
|
|
279
301
|
// Fresh totals can be wider than the ones the columns were checked against.
|
|
280
302
|
this.fixedContentMeasured = false;
|
|
281
303
|
}
|
|
304
|
+
/** Host answer to mrdCheckCapabilities — whether the current user may create a record for
|
|
305
|
+
* the table's currently bound route (standalone use only; ignored while the `canCreate`
|
|
306
|
+
* prop is set, since the host controls it directly in that mode). Call it any time after
|
|
307
|
+
* the event fires; a stale answer for an old binding is discarded automatically
|
|
308
|
+
* (selfCanCreate is reset to false before every new mrdCheckCapabilities emission). */
|
|
309
|
+
async setCanCreate(value, allowedTypes) {
|
|
310
|
+
this.selfCanCreate = value;
|
|
311
|
+
this.selfAllowedTypes = allowedTypes;
|
|
312
|
+
}
|
|
282
313
|
// ── Lifecycle ──────────────────────────────────────────────────────────────
|
|
283
314
|
disconnectedCallback() {
|
|
284
315
|
if (this.resizeObserver) {
|
|
@@ -638,6 +669,33 @@ export class MrdTable {
|
|
|
638
669
|
dataClass: v.dataClass,
|
|
639
670
|
});
|
|
640
671
|
}
|
|
672
|
+
/** Asks the host whether creating is allowed for the table's current binding (item, parent
|
|
673
|
+
* and active view) — only in standalone use (`canCreate` prop undefined). A host that
|
|
674
|
+
* controls `canCreate` directly already knows the answer, so the table never asks on its
|
|
675
|
+
* own in that mode. Resets selfCanCreate to false first so the add button stays hidden for
|
|
676
|
+
* the new binding until the host answers — it never inherits the previous binding's answer. */
|
|
677
|
+
emitCapabilitiesCheck() {
|
|
678
|
+
var _a, _b;
|
|
679
|
+
if (this.canCreate !== undefined)
|
|
680
|
+
return;
|
|
681
|
+
if (!this.accessControlEnabled)
|
|
682
|
+
return;
|
|
683
|
+
this.selfCanCreate = false;
|
|
684
|
+
this.selfAllowedTypes = undefined;
|
|
685
|
+
const v = this.allViews[this.activeViewIdx];
|
|
686
|
+
if (!(v === null || v === void 0 ? void 0 : v.dataClass))
|
|
687
|
+
return;
|
|
688
|
+
const isRelatedView = ((_a = this.item) === null || _a === void 0 ? void 0 : _a.type) === 'RELATED_VIEW';
|
|
689
|
+
// A host binding `item` and `parentId` as separate properties (e.g. Angular) can apply
|
|
690
|
+
// them in either order — this can run while `parentId` still holds its default `''`,
|
|
691
|
+
// before the real parent record has landed. Asking here would emit refId: '' for a
|
|
692
|
+
// RELATED_VIEW, which a host's `refType && refId` check reads as "no parent" and answers
|
|
693
|
+
// via the plain, unscoped capabilities route instead of the parent-scoped one
|
|
694
|
+
// (FINDING-0175 / TASK-0316). Skip; parentIdChanged() re-asks once it lands.
|
|
695
|
+
if (isRelatedView && !this.parentId)
|
|
696
|
+
return;
|
|
697
|
+
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 } : {})));
|
|
698
|
+
}
|
|
641
699
|
/** Build query params for a page request from current sort, view filters, filterClass and active column filters.
|
|
642
700
|
*
|
|
643
701
|
* `q` and column filters are mutually exclusive on the backend — combining them breaks
|
|
@@ -672,11 +730,41 @@ export class MrdTable {
|
|
|
672
730
|
var _a, _b, _c;
|
|
673
731
|
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
732
|
}
|
|
733
|
+
/** `canCreate` when the host controls it directly; else, when access control is disabled,
|
|
734
|
+
* always allowed; else the table's own self-asked answer. */
|
|
735
|
+
get effectiveCanCreate() {
|
|
736
|
+
if (this.canCreate !== undefined)
|
|
737
|
+
return this.canCreate;
|
|
738
|
+
if (!this.accessControlEnabled)
|
|
739
|
+
return true;
|
|
740
|
+
return this.selfCanCreate;
|
|
741
|
+
}
|
|
742
|
+
/** `allowedTypes` when the host controls it directly, else the table's own self-asked
|
|
743
|
+
* answer. `undefined` means "unknown — don't filter", not "nothing allowed". */
|
|
744
|
+
get effectiveAllowedTypes() {
|
|
745
|
+
if (this.allowedTypes !== undefined)
|
|
746
|
+
return this.allowedTypes;
|
|
747
|
+
return this.selfAllowedTypes;
|
|
748
|
+
}
|
|
749
|
+
/** `item.createTypes` intersected with effectiveAllowedTypes when that is known, so the
|
|
750
|
+
* create-type picker only offers concrete types a create POST would actually accept
|
|
751
|
+
* (FINDING-0176 / TASK-0314). Unfiltered when the answer isn't known (undefined) — an
|
|
752
|
+
* older host/API that never reports allowedTypes keeps today's unfiltered behaviour. */
|
|
753
|
+
get effectiveCreateTypes() {
|
|
754
|
+
var _a;
|
|
755
|
+
const all = (_a = this.item) === null || _a === void 0 ? void 0 : _a.createTypes;
|
|
756
|
+
const allowed = this.effectiveAllowedTypes;
|
|
757
|
+
if (!all || allowed == null)
|
|
758
|
+
return all;
|
|
759
|
+
return all.filter(ct => allowed.includes(ct.type));
|
|
760
|
+
}
|
|
675
761
|
get tableActions() {
|
|
676
762
|
var _a, _b;
|
|
677
763
|
const raw = (_b = (_a = this.item) === null || _a === void 0 ? void 0 : _a.actions) !== null && _b !== void 0 ? _b : [];
|
|
678
764
|
return (raw !== null && raw !== void 0 ? raw : []).reduce((acc, a) => {
|
|
679
|
-
|
|
765
|
+
// Hidden (not just disabled) until creating is confirmed allowed for this binding —
|
|
766
|
+
// see effectiveCanCreate() / emitCapabilitiesCheck() (TASK-0299).
|
|
767
|
+
if (a === 'NEW' && this.effectiveCanCreate)
|
|
680
768
|
acc.push({ action: 'create', label: t('table_new_record', this.locale), icon: 'assets/sprites.svg#icon-plus', variant: 'primary', disabled: this.readOnly });
|
|
681
769
|
if (a === 'EXPORT')
|
|
682
770
|
acc.push({ action: 'export', label: t('table_export_excel', this.locale), icon: 'assets/sprites.svg#icon-file-excel' });
|
|
@@ -1084,6 +1172,7 @@ export class MrdTable {
|
|
|
1084
1172
|
this.searchOpen = false;
|
|
1085
1173
|
this.searchQuery = '';
|
|
1086
1174
|
this.stashedFilters = null;
|
|
1175
|
+
this.emitCapabilitiesCheck();
|
|
1087
1176
|
this.init();
|
|
1088
1177
|
}
|
|
1089
1178
|
toggleViewPopover(e) {
|
|
@@ -1105,8 +1194,7 @@ export class MrdTable {
|
|
|
1105
1194
|
}
|
|
1106
1195
|
// ── Render: toolbar ────────────────────────────────────────────────────────
|
|
1107
1196
|
renderToolbar() {
|
|
1108
|
-
|
|
1109
|
-
return (h(Toolbar, { locale: this.locale, filterCount: this.activeFilters.size, actions: this.tableActions, views: this.allViews, activeViewIdx: this.activeViewIdx, viewPopoverOpen: this.viewPopoverOpen, createPickerOpen: this.createPickerOpen, createTypes: (_a = this.item) === null || _a === void 0 ? void 0 : _a.createTypes, readOnly: this.readOnly, searchEnabled: this.searchEnabled, searchOpen: this.searchOpen, searchQuery: this.searchQuery, cb: {
|
|
1197
|
+
return (h(Toolbar, { locale: this.locale, filterCount: this.activeFilters.size, actions: this.tableActions, views: this.allViews, activeViewIdx: this.activeViewIdx, viewPopoverOpen: this.viewPopoverOpen, createPickerOpen: this.createPickerOpen, createTypes: this.effectiveCreateTypes, readOnly: this.readOnly, searchEnabled: this.searchEnabled, searchOpen: this.searchOpen, searchQuery: this.searchQuery, cb: {
|
|
1110
1198
|
onClearAllFilters: () => this.clearAllFilters(),
|
|
1111
1199
|
onViewSwitch: idx => this.handleViewSwitch(idx),
|
|
1112
1200
|
onToggleViewPopover: e => this.toggleViewPopover(e),
|
|
@@ -1475,6 +1563,62 @@ export class MrdTable {
|
|
|
1475
1563
|
"reflect": false,
|
|
1476
1564
|
"attribute": "read-only",
|
|
1477
1565
|
"defaultValue": "false"
|
|
1566
|
+
},
|
|
1567
|
+
"canCreate": {
|
|
1568
|
+
"type": "boolean",
|
|
1569
|
+
"mutable": false,
|
|
1570
|
+
"complexType": {
|
|
1571
|
+
"original": "boolean",
|
|
1572
|
+
"resolved": "boolean | undefined",
|
|
1573
|
+
"references": {}
|
|
1574
|
+
},
|
|
1575
|
+
"required": false,
|
|
1576
|
+
"optional": true,
|
|
1577
|
+
"docs": {
|
|
1578
|
+
"tags": [],
|
|
1579
|
+
"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)."
|
|
1580
|
+
},
|
|
1581
|
+
"getter": false,
|
|
1582
|
+
"setter": false,
|
|
1583
|
+
"reflect": false,
|
|
1584
|
+
"attribute": "can-create"
|
|
1585
|
+
},
|
|
1586
|
+
"allowedTypes": {
|
|
1587
|
+
"type": "unknown",
|
|
1588
|
+
"mutable": false,
|
|
1589
|
+
"complexType": {
|
|
1590
|
+
"original": "string[] | null",
|
|
1591
|
+
"resolved": "null | string[] | undefined",
|
|
1592
|
+
"references": {}
|
|
1593
|
+
},
|
|
1594
|
+
"required": false,
|
|
1595
|
+
"optional": true,
|
|
1596
|
+
"docs": {
|
|
1597
|
+
"tags": [],
|
|
1598
|
+
"text": "Concrete types (`item.createTypes[].type`) the current user may actually create, when\n`item.createTypes` offers a picker for a family type \u2014 narrows it down to only the types\na create POST would accept (FINDING-0176 / TASK-0314). Leave `undefined` for standalone\nuse \u2014 the table takes it from the second argument of setCanCreate() alongside `canCreate`.\nSet it explicitly (e.g. from mrd-layout-section, alongside `canCreate`) when the host\nalready has the answer. `undefined` (not yet answered, or an older host/API that doesn't\nreport it) means \"don't filter\" \u2014 `item.createTypes` is shown unfiltered, same as before\nthis existed."
|
|
1599
|
+
},
|
|
1600
|
+
"getter": false,
|
|
1601
|
+
"setter": false
|
|
1602
|
+
},
|
|
1603
|
+
"accessControlEnabled": {
|
|
1604
|
+
"type": "boolean",
|
|
1605
|
+
"mutable": false,
|
|
1606
|
+
"complexType": {
|
|
1607
|
+
"original": "boolean",
|
|
1608
|
+
"resolved": "boolean",
|
|
1609
|
+
"references": {}
|
|
1610
|
+
},
|
|
1611
|
+
"required": false,
|
|
1612
|
+
"optional": false,
|
|
1613
|
+
"docs": {
|
|
1614
|
+
"tags": [],
|
|
1615
|
+
"text": "When false, skips the standalone mrdCheckCapabilities round trip entirely and renders\nthe create action immediately as allowed \u2014 for a tenant that has no access control\nconfigured, the answer would always be true anyway (FINDING-0171 / TASK-0322). Ignored\nwhile `canCreate` is set explicitly (the host already controls the answer directly in\nthat mode). Default `true` keeps today's ask-and-hide behaviour unchanged."
|
|
1616
|
+
},
|
|
1617
|
+
"getter": false,
|
|
1618
|
+
"setter": false,
|
|
1619
|
+
"reflect": false,
|
|
1620
|
+
"attribute": "access-control-enabled",
|
|
1621
|
+
"defaultValue": "true"
|
|
1478
1622
|
}
|
|
1479
1623
|
};
|
|
1480
1624
|
}
|
|
@@ -1503,7 +1647,9 @@ export class MrdTable {
|
|
|
1503
1647
|
"jsonModal": {},
|
|
1504
1648
|
"aggregations": {},
|
|
1505
1649
|
"aggregationsTotal": {},
|
|
1506
|
-
"minKnownTotal": {}
|
|
1650
|
+
"minKnownTotal": {},
|
|
1651
|
+
"selfCanCreate": {},
|
|
1652
|
+
"selfAllowedTypes": {}
|
|
1507
1653
|
};
|
|
1508
1654
|
}
|
|
1509
1655
|
static get events() {
|
|
@@ -1608,6 +1754,21 @@ export class MrdTable {
|
|
|
1608
1754
|
"resolved": "{ path: string; qs: string; aggQs: string; }",
|
|
1609
1755
|
"references": {}
|
|
1610
1756
|
}
|
|
1757
|
+
}, {
|
|
1758
|
+
"method": "mrdCheckCapabilities",
|
|
1759
|
+
"name": "mrdCheckCapabilities",
|
|
1760
|
+
"bubbles": true,
|
|
1761
|
+
"cancelable": true,
|
|
1762
|
+
"composed": true,
|
|
1763
|
+
"docs": {
|
|
1764
|
+
"tags": [],
|
|
1765
|
+
"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).\nsetCanCreate()'s optional second argument narrows `item.createTypes`'s picker down to the\nconcrete types the answer allows, when the API's capabilities response reports them\n(FINDING-0176 / TASK-0314)."
|
|
1766
|
+
},
|
|
1767
|
+
"complexType": {
|
|
1768
|
+
"original": "{ type: string; refType?: string; refId?: string; filterClass?: string }",
|
|
1769
|
+
"resolved": "{ type: string; refType?: string | undefined; refId?: string | undefined; filterClass?: string | undefined; }",
|
|
1770
|
+
"references": {}
|
|
1771
|
+
}
|
|
1611
1772
|
}];
|
|
1612
1773
|
}
|
|
1613
1774
|
static get methods() {
|
|
@@ -1692,6 +1853,31 @@ export class MrdTable {
|
|
|
1692
1853
|
"text": "Inject aggregation totals returned by the /aggregations endpoint.",
|
|
1693
1854
|
"tags": []
|
|
1694
1855
|
}
|
|
1856
|
+
},
|
|
1857
|
+
"setCanCreate": {
|
|
1858
|
+
"complexType": {
|
|
1859
|
+
"signature": "(value: boolean, allowedTypes?: string[]) => Promise<void>",
|
|
1860
|
+
"parameters": [{
|
|
1861
|
+
"name": "value",
|
|
1862
|
+
"type": "boolean",
|
|
1863
|
+
"docs": ""
|
|
1864
|
+
}, {
|
|
1865
|
+
"name": "allowedTypes",
|
|
1866
|
+
"type": "string[] | undefined",
|
|
1867
|
+
"docs": ""
|
|
1868
|
+
}],
|
|
1869
|
+
"references": {
|
|
1870
|
+
"Promise": {
|
|
1871
|
+
"location": "global",
|
|
1872
|
+
"id": "global::Promise"
|
|
1873
|
+
}
|
|
1874
|
+
},
|
|
1875
|
+
"return": "Promise<void>"
|
|
1876
|
+
},
|
|
1877
|
+
"docs": {
|
|
1878
|
+
"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).",
|
|
1879
|
+
"tags": []
|
|
1880
|
+
}
|
|
1695
1881
|
}
|
|
1696
1882
|
};
|
|
1697
1883
|
}
|
|
@@ -1703,6 +1889,9 @@ export class MrdTable {
|
|
|
1703
1889
|
}, {
|
|
1704
1890
|
"propName": "item",
|
|
1705
1891
|
"methodName": "itemChanged"
|
|
1892
|
+
}, {
|
|
1893
|
+
"propName": "parentId",
|
|
1894
|
+
"methodName": "parentIdChanged"
|
|
1706
1895
|
}];
|
|
1707
1896
|
}
|
|
1708
1897
|
}
|
|
@@ -108,6 +108,25 @@ async function apiFetchPage(token, baseHref, pageNumber, sort = '') {
|
|
|
108
108
|
return body; // { _embedded, _links, page }
|
|
109
109
|
}
|
|
110
110
|
|
|
111
|
+
/** Answers mrdCheckCapabilities/mrdViewCheckCapabilities (TASK-0299): same route/path
|
|
112
|
+
* variables the table already fetches its rows from, `/capabilities` appended.
|
|
113
|
+
* VIEW: GET /data/{tenant}/{type}/capabilities
|
|
114
|
+
* RELATED_VIEW: GET /data/{tenant}/{refType}/{refId}/{type}/capabilities
|
|
115
|
+
* `filterClass`, when the view declares one, is appended the same way the row fetch
|
|
116
|
+
* sends it — `?type={filterClass}` — so a "contents" view filtered to `document` asks
|
|
117
|
+
* specifically whether a document may be created here. */
|
|
118
|
+
async function apiFetchCapabilities(token, baseHref, refType, refId, type, filterClass) {
|
|
119
|
+
const path = refType
|
|
120
|
+
? `${baseHref}/${refType}/${refId}/${type}/capabilities`
|
|
121
|
+
: `${baseHref}/${type}/capabilities`;
|
|
122
|
+
const url = filterClass ? `${path}?type=${encodeURIComponent(filterClass)}` : path;
|
|
123
|
+
const { ok, status, body } = await apiRequest('GET', url, token);
|
|
124
|
+
if (!ok) throw new Error(`${status}: ${typeof body === 'string' ? body : JSON.stringify(body)}`);
|
|
125
|
+
return body; // { mayCreate, allowedTypes? } — allowedTypes (TASK-0313/TASK-0314) lists the
|
|
126
|
+
// concrete types (createTypes[].type) the caller may create when `type` was a
|
|
127
|
+
// family; empty exactly when mayCreate is false.
|
|
128
|
+
}
|
|
129
|
+
|
|
111
130
|
async function apiSubmitForm(token, tenantCode, pluralName, values) {
|
|
112
131
|
const { status, ok, body } = await apiWriteAndFetch('POST', `/data/${tenantCode}/${pluralName}`, token, values);
|
|
113
132
|
return { status, ok, body };
|
|
@@ -16,6 +16,10 @@ let _navHistory = []; // stack van { dashboardData, dashboardRecord,
|
|
|
16
16
|
let _meUser = null; // { id: href, label: name } — resolved once after login via /accounts/me
|
|
17
17
|
let _detailReadOnly = false; // Detail View tab: readOnly toggle (TASK-0248 demo)
|
|
18
18
|
let _liveApiReadOnly = false; // Live API tab: readOnly toggle (TASK-0248 demo)
|
|
19
|
+
let _detailCanCreate = true; // Detail View tab: mock answer to mrdViewCheckCapabilities (TASK-0299 demo)
|
|
20
|
+
let _detailAllowedTypesFiltered = false; // Detail View tab: allowedTypes demo (TASK-0314)
|
|
21
|
+
let _detailAccessControlEnabled = true; // Detail View tab: accessControlEnabled toggle (TASK-0322 demo)
|
|
22
|
+
let _liveApiAccessControlEnabled = true; // Live API tab: accessControlEnabled toggle (TASK-0322 demo)
|
|
19
23
|
|
|
20
24
|
/* =====================================================================
|
|
21
25
|
UTILITIES
|
|
@@ -179,6 +183,7 @@ function showCompanyDetail(row) {
|
|
|
179
183
|
section.data = company;
|
|
180
184
|
section.locale = _locale;
|
|
181
185
|
section.readOnly = _detailReadOnly;
|
|
186
|
+
section.accessControlEnabled = _detailAccessControlEnabled;
|
|
182
187
|
|
|
183
188
|
section.addEventListener('mrdNavigate', (e) => {
|
|
184
189
|
logEvent('mrdNavigate', e.detail);
|
|
@@ -193,6 +198,21 @@ function showCompanyDetail(row) {
|
|
|
193
198
|
await section.setViewPage(name, page, mockRows, mockRows.length);
|
|
194
199
|
});
|
|
195
200
|
|
|
201
|
+
// TASK-0299: mock answer so the "Share classes" tab's add button shows up
|
|
202
|
+
// (its layout item declares actions: ['NEW', 'EXPORT']) — a real host would
|
|
203
|
+
// call apiFetchCapabilities() instead, as the Live API tab does below.
|
|
204
|
+
// _detailCanCreate is toggleable (see onDetailViewCanCreateToggle) so both the
|
|
205
|
+
// hidden and visible states can be demonstrated without a live backend.
|
|
206
|
+
// TASK-0314: when _detailAllowedTypesFiltered is on, the mock answer also reports
|
|
207
|
+
// allowedTypes: ['ordinaryShares'] — the "Share classes" createTypes picker offers
|
|
208
|
+
// ordinaryShares + preferenceShares, so this demonstrates preferenceShares dropping
|
|
209
|
+
// out of the menu even though the layout item still declares both.
|
|
210
|
+
section.addEventListener('mrdViewCheckCapabilities', async (e) => {
|
|
211
|
+
logEvent('mrdViewCheckCapabilities', e.detail);
|
|
212
|
+
const allowedTypes = _detailAllowedTypesFiltered ? ['ordinaryShares'] : undefined;
|
|
213
|
+
await section.setViewCanCreate(e.detail.name, _detailCanCreate, allowedTypes);
|
|
214
|
+
});
|
|
215
|
+
|
|
196
216
|
wrapper.appendChild(section);
|
|
197
217
|
container.appendChild(wrapper);
|
|
198
218
|
});
|
|
@@ -214,6 +234,36 @@ function onDetailViewReadOnlyToggle(checked) {
|
|
|
214
234
|
});
|
|
215
235
|
}
|
|
216
236
|
|
|
237
|
+
/** canCreate demo (TASK-0299): flips the mock answer to mrdViewCheckCapabilities and
|
|
238
|
+
* re-applies it to any already-rendered "Share classes" table via setViewCanCreate() —
|
|
239
|
+
* no re-fetch, so the add button appears/disappears immediately. */
|
|
240
|
+
function onDetailViewCanCreateToggle(checked) {
|
|
241
|
+
_detailCanCreate = checked;
|
|
242
|
+
document.querySelectorAll('#detail-sections mrd-layout-section').forEach((section) => {
|
|
243
|
+
section.setViewCanCreate('shareClasses', checked, _detailAllowedTypesFiltered ? ['ordinaryShares'] : undefined);
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/** allowedTypes demo (TASK-0314): re-applies the mock capabilities answer with/without
|
|
248
|
+
* allowedTypes so the "Share classes" +-button's picker narrows to "Ordinary shares"
|
|
249
|
+
* only, without re-fetching. */
|
|
250
|
+
function onDetailViewAllowedTypesToggle(checked) {
|
|
251
|
+
_detailAllowedTypesFiltered = checked;
|
|
252
|
+
document.querySelectorAll('#detail-sections mrd-layout-section').forEach((section) => {
|
|
253
|
+
section.setViewCanCreate('shareClasses', _detailCanCreate, checked ? ['ordinaryShares'] : undefined);
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/** accessControlEnabled demo (TASK-0322): when disabled, mrd-layout-section skips
|
|
258
|
+
* mrdViewCheckCapabilities entirely and the "Share classes" add button shows up
|
|
259
|
+
* immediately, regardless of the mock canCreate answer above. */
|
|
260
|
+
function onDetailViewAccessControlToggle(disabled) {
|
|
261
|
+
_detailAccessControlEnabled = !disabled;
|
|
262
|
+
document.querySelectorAll('#detail-sections mrd-layout-section').forEach((section) => {
|
|
263
|
+
section.accessControlEnabled = _detailAccessControlEnabled;
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
|
|
217
267
|
/* =====================================================================
|
|
218
268
|
DOCUMENT PREVIEW TAB
|
|
219
269
|
===================================================================== */
|
|
@@ -510,6 +560,7 @@ async function renderSection(index) {
|
|
|
510
560
|
section.data = _dashboardRecord ?? { _links: _dashboardData._links ?? {} };
|
|
511
561
|
section.locale = _locale;
|
|
512
562
|
section.readOnly = _liveApiReadOnly;
|
|
563
|
+
section.accessControlEnabled = _liveApiAccessControlEnabled;
|
|
513
564
|
// Dashboard-level archetypes describe the object itself → single-document view.
|
|
514
565
|
// Only apply to the main object layout, not tab-page (related) layouts.
|
|
515
566
|
section.archetypes = (!layout.tabPage && _dashboardData.archetypes) ? _dashboardData.archetypes : [];
|
|
@@ -567,6 +618,21 @@ async function renderSection(index) {
|
|
|
567
618
|
}
|
|
568
619
|
});
|
|
569
620
|
|
|
621
|
+
// TASK-0299: the table's add button stays hidden until this answers — real call
|
|
622
|
+
// against GET /data/{tenant}/{refType}/{refId}/{type}/capabilities (or the top-level
|
|
623
|
+
// /{type}/capabilities form for a VIEW without a parent).
|
|
624
|
+
section.addEventListener('mrdViewCheckCapabilities', async (e) => {
|
|
625
|
+
if (generation !== _sectionGeneration) return;
|
|
626
|
+
const { name, type, refType, refId, filterClass } = e.detail;
|
|
627
|
+
logEvent('mrdViewCheckCapabilities', e.detail);
|
|
628
|
+
try {
|
|
629
|
+
const result = await apiFetchCapabilities(authGetToken(), _baseHref, refType, refId, type, filterClass);
|
|
630
|
+
await section.setViewCanCreate(name, !!result?.mayCreate, result?.allowedTypes);
|
|
631
|
+
} catch (err) {
|
|
632
|
+
console.error('[mrdViewCheckCapabilities] mislukt', name, err);
|
|
633
|
+
}
|
|
634
|
+
});
|
|
635
|
+
|
|
570
636
|
section.addEventListener('mrdNavigate', async (e) => {
|
|
571
637
|
if (generation !== _sectionGeneration) return;
|
|
572
638
|
await handleNavigate(e.detail);
|
|
@@ -711,6 +777,15 @@ function onLiveApiReadOnlyToggle(checked) {
|
|
|
711
777
|
if (section) section.readOnly = checked;
|
|
712
778
|
}
|
|
713
779
|
|
|
780
|
+
/** accessControlEnabled demo (TASK-0322): when disabled, mrd-layout-section skips
|
|
781
|
+
* mrdViewCheckCapabilities entirely (no apiFetchCapabilities call) and every
|
|
782
|
+
* VIEW/RELATED_VIEW's create affordance shows up immediately. */
|
|
783
|
+
function onLiveApiAccessControlToggle(disabled) {
|
|
784
|
+
_liveApiAccessControlEnabled = !disabled;
|
|
785
|
+
const section = document.querySelector('#sections-container mrd-layout-section');
|
|
786
|
+
if (section) section.accessControlEnabled = _liveApiAccessControlEnabled;
|
|
787
|
+
}
|
|
788
|
+
|
|
714
789
|
function toggleJsonViewer() {
|
|
715
790
|
const viewer = document.getElementById('json-viewer');
|
|
716
791
|
const btn = document.getElementById('btn-json-toggle');
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{proxyCustomElement as t,HTMLElement as r,createEvent as e,h as o,Host as i,transformTag as n}from"@stencil/core/internal/client";import{e as d}from"./client-layout.js";import{t as
|
|
1
|
+
import{proxyCustomElement as t,HTMLElement as r,createEvent as e,h as o,Host as i,transformTag as n}from"@stencil/core/internal/client";import{e as d}from"./client-layout.js";import{t as a}from"./i18n.js";import{a as s}from"./document-attachments.js";import{d as c}from"./mrd-document-list2.js";import{d as l}from"./mrd-table2.js";const m=t(class extends r{constructor(t){super(),!1!==t&&this.__registerHost(),this.mrdLoadPage=e(this,"mrdLoadPage",7),this.mrdLoadDistinct=e(this,"mrdLoadDistinct",7),this.mrdLoadAggregations=e(this,"mrdLoadAggregations",7),this.mrdNavigate=e(this,"mrdNavigate",7),this.mrdAction=e(this,"mrdAction",7),this.mrdUpdateObject=e(this,"mrdUpdateObject",7),this.mrdUpload=e(this,"mrdUpload",7),this.mrdCreateObject=e(this,"mrdCreateObject",7),this.item=null,this.parentId="",this.containerHref="",this.viewKey="",this.height=460,this.locale=navigator.language,this.readOnly=!1,this.mayCreate=!1,this.mode="tree",this.canCreate=!1,this.targetLabel="",this.tableInited=!1,this.createFolder=()=>{var t;null===(t=this.listEl)||void 0===t||t.createFolder()},this.pickFile=()=>{var t;!this.readOnly&&this.mayCreate&&(null===(t=this.fileInputEl)||void 0===t||t.click())},this.onFilePicked=t=>{var r,e;const o=t.target,i=null===(r=o.files)||void 0===r?void 0:r[0];i&&(null===(e=this.listEl)||void 0===e||e.uploadFile(i)),o.value=""}}componentDidRender(){var t;if("list"===this.mode&&this.tableEl&&!this.tableInited){this.tableInited=!0;const r=this.tableEl;Promise.resolve(null===(t=r.componentOnReady)||void 0===t?void 0:t.call(r)).then((()=>r.init()))}}setMode(t){t!==this.mode&&(this.canCreate=!1,this.tableInited=!1,this.mode=t)}renderToolbar(){return o("div",{class:"mrd-document-container__toolbar"},"tree"===this.mode&&this.mayCreate&&o("button",{type:"button",class:"mrd-document-container__action",disabled:this.readOnly||!this.canCreate,title:this.readOnly?a("readonly_access",this.locale):void 0,onClick:this.createFolder},o("svg",{viewBox:"0 0 24 24",fill:"none",stroke:"currentColor","stroke-width":"2","stroke-linecap":"round","stroke-linejoin":"round","aria-hidden":"true"},o("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"}),o("path",{d:"M12 11v4M10 13h4"})),a("new_folder",this.locale)),"tree"===this.mode&&this.mayCreate&&o("button",{type:"button",class:"mrd-document-container__action",disabled:this.readOnly||!this.canCreate,title:this.readOnly?a("readonly_access",this.locale):this.targetLabel?`${a("doc_upload_to",this.locale)} ${this.targetLabel}`:void 0,onClick:this.pickFile},o("svg",{viewBox:"0 0 24 24",fill:"none",stroke:"currentColor","stroke-width":"2","stroke-linecap":"round","stroke-linejoin":"round","aria-hidden":"true"},o("path",{d:"M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"}),o("path",{d:"M12 3v13M7 8l5-5 5 5"})),a("doc_upload",this.locale)),o("input",{type:"file",class:"mrd-document-container__file-input",ref:t=>this.fileInputEl=t,onChange:this.onFilePicked}),o("div",{class:"mrd-document-container__seg",role:"group","aria-label":a("doc_view_switch",this.locale)},o("button",{class:"mrd-document-container__seg-btn","aria-pressed":String("tree"===this.mode),title:a("doc_view_tree",this.locale),"aria-label":a("doc_view_tree",this.locale),onClick:()=>this.setMode("tree")},o("svg",{viewBox:"0 0 24 24",fill:"none",stroke:"currentColor","stroke-width":"2","stroke-linecap":"round","stroke-linejoin":"round","aria-hidden":"true"},o("path",{d:"M3 5h6M3 12h9M3 19h6"}),o("circle",{cx:"18",cy:"5",r:"1.6"}),o("circle",{cx:"18",cy:"19",r:"1.6"}),o("path",{d:"M16 12h4"}))),o("button",{class:"mrd-document-container__seg-btn","aria-pressed":String("list"===this.mode),title:a("doc_view_list",this.locale),"aria-label":a("doc_view_list",this.locale),onClick:()=>this.setMode("list")},o("svg",{viewBox:"0 0 24 24",fill:"none",stroke:"currentColor","stroke-width":"2","stroke-linecap":"round","stroke-linejoin":"round","aria-hidden":"true"},o("path",{d:"M8 6h13M8 12h13M8 18h13M3 6h.01M3 12h.01M3 18h.01"})))))}renderTree(){return o("mrd-document-list",{"data-doclist":this.viewKey,ref:t=>this.listEl=t,item:this.item,archetype:this.archetype,parentId:this.parentId,containerHref:this.containerHref,locale:this.locale,readOnly:this.readOnly,onMrdLoadDistinct:t=>{t.stopPropagation(),this.mrdLoadDistinct.emit(t.detail)},onMrdLoadPage:t=>{t.stopPropagation(),this.mrdLoadPage.emit(t.detail)},onMrdNavigate:t=>{t.stopPropagation(),this.mrdNavigate.emit(Object.assign(Object.assign({},t.detail),{navigateBehaviour:d.OVERLAY_PAGE}))},onMrdCanCreate:t=>{t.stopPropagation(),this.canCreate=t.detail},onMrdTargetLabel:t=>{t.stopPropagation(),this.targetLabel=t.detail},onMrdUpdateObject:t=>{t.stopPropagation(),this.mrdUpdateObject.emit(t.detail)},onMrdUpload:t=>{t.stopPropagation(),this.mrdUpload.emit(t.detail)},onMrdCreateObject:t=>{t.stopPropagation(),this.mrdCreateObject.emit(t.detail)}})}renderList(){return o("mrd-table",{"data-view":this.viewKey,ref:t=>this.tableEl=t,item:this.item,parentId:this.parentId,locale:this.locale,readOnly:this.readOnly,canCreate:this.mayCreate,onMrdLoadPage:t=>{t.stopPropagation(),this.mrdLoadPage.emit(t.detail)},onMrdLoadAggregations:t=>{t.stopPropagation(),this.mrdLoadAggregations.emit(t.detail)},onMrdRowClick:t=>{var r,e,o;t.stopPropagation();const{row:i,listContext:n}=t.detail;this.mrdNavigate.emit({href:null===(e=null===(r=null==i?void 0:i._links)||void 0===r?void 0:r.self)||void 0===e?void 0:e.href,label:null!==(o=null==i?void 0:i.name)&&void 0!==o?o:"",navigateBehaviour:d.OVERLAY_PAGE,listContext:n,attachments:s(this.archetype,i)})},onMrdAction:t=>{t.stopPropagation(),this.mrdAction.emit(t.detail)}})}render(){const t="tree"===this.mode;return o(i,{key:"4b674a508ac7ddf6af80a40836b72dc65f41c741"},this.renderToolbar(),o("div",{key:"19968916f685a2e3cd075dabf170f67fb104251c",class:{"mrd-document-container__body":!0,"mrd-document-container__body--tree":t},style:t?{height:`${this.height}px`,overflowY:"auto"}:{}},t?this.renderTree():this.renderList()))}get el(){return this}static get style(){return".sc-mrd-document-container-h{display:block}.mrd-document-container__toolbar.sc-mrd-document-container{display:flex;align-items:center;justify-content:flex-end;gap:var(--mrd-space-2);padding:var(--mrd-space-2) 0;border-bottom:1px solid var(--mrd-color-neutral-100)}.mrd-document-container__body--tree.sc-mrd-document-container{border:1px solid var(--mrd-border-color);border-radius:var(--mrd-border-radius);overflow:hidden}.mrd-document-container__action.sc-mrd-document-container{display:inline-flex;align-items:center;gap:var(--mrd-space-2);height:1.625rem;padding:0 var(--mrd-space-3);font-size:var(--mrd-font-size-sm);color:var(--mrd-color-neutral-700);background:var(--mrd-color-white);border:1px solid var(--mrd-color-neutral-200);border-radius:var(--mrd-border-radius);cursor:pointer;transition:background var(--mrd-transition-fast), border-color var(--mrd-transition-fast)}.mrd-document-container__action.sc-mrd-document-container svg.sc-mrd-document-container{width:1rem;height:1rem;color:var(--mrd-color-folder)}.mrd-document-container__action.sc-mrd-document-container:hover:not(:disabled){background:var(--mrd-color-neutral-50);border-color:var(--mrd-color-neutral-300)}.mrd-document-container__action.sc-mrd-document-container:disabled{opacity:0.5;cursor:not-allowed}.mrd-document-container__file-input.sc-mrd-document-container{display:none}.mrd-document-container__seg.sc-mrd-document-container{display:inline-flex;gap:2px;padding:2px;background:var(--mrd-color-neutral-100);border-radius:var(--mrd-border-radius)}.mrd-document-container__seg-btn.sc-mrd-document-container{width:1.875rem;height:1.625rem;display:grid;place-items:center;border:none;background:none;border-radius:var(--mrd-border-radius-sm);color:var(--mrd-color-neutral-500);cursor:pointer;transition:color var(--mrd-transition-fast), background var(--mrd-transition-fast)}.mrd-document-container__seg-btn.sc-mrd-document-container:hover{color:var(--mrd-color-neutral-800)}.mrd-document-container__seg-btn[aria-pressed=true].sc-mrd-document-container{background:var(--mrd-color-white);color:var(--mrd-color-primary-dark);box-shadow:var(--mrd-shadow-sm)}.mrd-document-container__seg-btn.sc-mrd-document-container svg.sc-mrd-document-container{width:1rem;height:1rem}"}},[2,"mrd-document-container",{item:[16],archetype:[16],parentId:[1,"parent-id"],containerHref:[1,"container-href"],viewKey:[1,"view-key"],height:[2],locale:[1],readOnly:[4,"read-only"],mayCreate:[4,"may-create"],mode:[32],canCreate:[32],targetLabel:[32]}]);function h(){"undefined"!=typeof customElements&&["mrd-document-container","mrd-document-list","mrd-table"].forEach((t=>{switch(t){case"mrd-document-container":customElements.get(n(t))||customElements.define(n(t),m);break;case"mrd-document-list":customElements.get(n(t))||c();break;case"mrd-table":customElements.get(n(t))||l()}}))}export{m as M,h as d}
|