@mmlogic/components 0.1.29 → 0.2.0
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/mrd-boolean-field_19.cjs.entry.js +150 -167
- package/dist/collection/components/mrd-field/mrd-field.js +18 -20
- package/dist/collection/components/mrd-form/mrd-form.js +38 -46
- package/dist/collection/components/mrd-layout-section/mrd-layout-section.js +52 -61
- package/dist/collection/components/mrd-layout-section/mrd-layout-section.scss +16 -1
- package/dist/collection/components/mrd-table/mrd-table.js +34 -34
- package/dist/collection/dev/api.js +9 -97
- package/dist/collection/dev/app.js +34 -17
- package/dist/collection/dev/example-data.js +111 -298
- package/dist/collection/utils/cell-renderer.js +7 -5
- package/dist/components/mrd-field2.js +1 -1
- package/dist/components/mrd-form.js +1 -1
- package/dist/components/mrd-layout-section.js +1 -1
- package/dist/components/mrd-table2.js +1 -1
- package/dist/esm/mrd-boolean-field_19.entry.js +150 -167
- package/dist/mosterdcomponents/mosterdcomponents.esm.js +1 -1
- package/dist/mosterdcomponents/p-61ef0232.entry.js +1 -0
- package/dist/types/types/client-layout.d.ts +25 -0
- package/package.json +1 -1
- package/dist/mosterdcomponents/p-3aacd101.entry.js +0 -1
|
@@ -13,7 +13,7 @@ const NUMERIC_TYPES = new Set(['INTEGER', 'DECIMAL', 'PERCENTAGE', 'CURRENCY']);
|
|
|
13
13
|
const DATE_TYPES = new Set(['DATE', 'DATETIME', 'TIME']);
|
|
14
14
|
const NO_FILTER_TYPES = new Set(['FILE', 'IMAGE']);
|
|
15
15
|
/** Column types that cannot be sorted or filtered (not stored in PostgreSQL). */
|
|
16
|
-
const NON_INTERACTIVE_TYPES = new Set(['LONGTEXT', 'JSON']);
|
|
16
|
+
const NON_INTERACTIVE_TYPES = new Set(['LONGTEXT', 'JSON', 'FILE', 'IMAGE']);
|
|
17
17
|
export class MrdTable {
|
|
18
18
|
constructor() {
|
|
19
19
|
// ── Non-state internals ────────────────────────────────────────────────────
|
|
@@ -185,25 +185,25 @@ export class MrdTable {
|
|
|
185
185
|
return this.sortDir === 'desc' ? `${this.sortField},desc` : this.sortField;
|
|
186
186
|
}
|
|
187
187
|
colName(col) {
|
|
188
|
-
var _a
|
|
189
|
-
return (
|
|
188
|
+
var _a;
|
|
189
|
+
return (_a = col.name) !== null && _a !== void 0 ? _a : '';
|
|
190
190
|
}
|
|
191
191
|
colDataType(col) {
|
|
192
|
-
var _a
|
|
192
|
+
var _a;
|
|
193
193
|
if (col.type === 'RELATION')
|
|
194
194
|
return 'RELATION';
|
|
195
|
-
return (
|
|
195
|
+
return (_a = col.dataType) !== null && _a !== void 0 ? _a : 'TEXT';
|
|
196
196
|
}
|
|
197
197
|
// ── Aggregation helpers ────────────────────────────────────────────────────
|
|
198
198
|
buildAggregationParams() {
|
|
199
199
|
var _a;
|
|
200
200
|
const groups = { sum: [], avg: [], count: [] };
|
|
201
201
|
for (const col of this.columns) {
|
|
202
|
-
if (col.type !== 'FIELD' || !
|
|
202
|
+
if (col.type !== 'FIELD' || !col.aggregate)
|
|
203
203
|
continue;
|
|
204
|
-
const fn = col.
|
|
204
|
+
const fn = col.aggregate.toLowerCase();
|
|
205
205
|
if (fn in groups)
|
|
206
|
-
groups[fn].push(col.
|
|
206
|
+
groups[fn].push((_a = col.name) !== null && _a !== void 0 ? _a : '');
|
|
207
207
|
}
|
|
208
208
|
const params = {};
|
|
209
209
|
if (groups.sum.length)
|
|
@@ -221,19 +221,19 @@ export class MrdTable {
|
|
|
221
221
|
}
|
|
222
222
|
renderAggregationValue(col) {
|
|
223
223
|
var _a, _b;
|
|
224
|
-
if (col.type !== 'FIELD' || !
|
|
224
|
+
if (col.type !== 'FIELD' || !col.aggregate || !this.aggregations)
|
|
225
225
|
return '';
|
|
226
|
-
const fn = col.
|
|
227
|
-
const val = (
|
|
226
|
+
const fn = col.aggregate.toLowerCase();
|
|
227
|
+
const val = (_a = this.aggregations[fn]) === null || _a === void 0 ? void 0 : _a[(_b = col.name) !== null && _b !== void 0 ? _b : ''];
|
|
228
228
|
if (val == null)
|
|
229
229
|
return '';
|
|
230
|
-
const dt = col.
|
|
230
|
+
const dt = col.dataType;
|
|
231
231
|
if (dt === 'INTEGER')
|
|
232
232
|
return formatNumber(val, this.locale, { maximumFractionDigits: 0 });
|
|
233
233
|
if (dt === 'PERCENTAGE')
|
|
234
234
|
return formatPercentage(val, this.locale);
|
|
235
|
-
if (dt === 'CURRENCY' && col.
|
|
236
|
-
return formatCurrency(val, col.
|
|
235
|
+
if (dt === 'CURRENCY' && col.currencyCode)
|
|
236
|
+
return formatCurrency(val, col.currencyCode, this.locale);
|
|
237
237
|
return formatNumber(val, this.locale);
|
|
238
238
|
}
|
|
239
239
|
// ── Reset pagination ───────────────────────────────────────────────────────
|
|
@@ -578,7 +578,7 @@ export class MrdTable {
|
|
|
578
578
|
}
|
|
579
579
|
// ── Render: filter popup ───────────────────────────────────────────────────
|
|
580
580
|
renderFilterEditor(col) {
|
|
581
|
-
var _a, _b, _c, _d, _e, _f, _g
|
|
581
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
582
582
|
const pf = (_a = this.pendingFilter) !== null && _a !== void 0 ? _a : {};
|
|
583
583
|
const dataType = this.colDataType(col);
|
|
584
584
|
if (NO_FILTER_TYPES.has(dataType)) {
|
|
@@ -594,19 +594,19 @@ export class MrdTable {
|
|
|
594
594
|
].map(opt => (h("label", { class: "mrd-table__filter-radio-label" }, h("input", { type: "radio", name: `bf-${this.openFilterCol}`, checked: !noValueOp && pf.value === opt.value, onChange: () => { this.pendingFilter = Object.assign(Object.assign({}, pf), { operator: undefined, value: opt.value }); } }), t(opt.labelKey, this.locale)))), h("label", { class: "mrd-table__filter-radio-label" }, h("input", { type: "radio", name: `bf-${this.openFilterCol}`, checked: boolOp === 'isEmpty', onChange: () => { this.pendingFilter = Object.assign(Object.assign({}, pf), { operator: 'isEmpty', value: undefined }); } }), t('filter_is_empty', this.locale)), h("label", { class: "mrd-table__filter-radio-label" }, h("input", { type: "radio", name: `bf-${this.openFilterCol}`, checked: boolOp === 'isNotEmpty', onChange: () => { this.pendingFilter = Object.assign(Object.assign({}, pf), { operator: 'isNotEmpty', value: undefined }); } }), t('filter_is_not_empty', this.locale))));
|
|
595
595
|
}
|
|
596
596
|
if (dataType === 'LIST') {
|
|
597
|
-
const items = (
|
|
598
|
-
const selected = (
|
|
597
|
+
const items = (_b = col.listItems) !== null && _b !== void 0 ? _b : [];
|
|
598
|
+
const selected = (_c = pf.values) !== null && _c !== void 0 ? _c : [];
|
|
599
599
|
return (h("div", { class: "mrd-table__filter-list" }, h("div", { class: "mrd-table__filter-list-controls" }, h("button", { class: "mrd-table__filter-list-btn", onClick: () => { this.pendingFilter = Object.assign(Object.assign({}, pf), { values: items.map(i => i.key) }); } }, t('filter_select_all', this.locale)), h("button", { class: "mrd-table__filter-list-btn", onClick: () => { this.pendingFilter = Object.assign(Object.assign({}, pf), { values: [] }); } }, t('filter_select_none', this.locale))), items.map(item => (h("label", { class: "mrd-table__filter-checkbox-label" }, h("input", { type: "checkbox", checked: selected.includes(item.key), onChange: (e) => this.togglePendingValue(item.key, e.target.checked) }), item.label)))));
|
|
600
600
|
}
|
|
601
601
|
if (TEXT_TYPES.has(dataType) || dataType === 'RELATION') {
|
|
602
|
-
const op = (
|
|
602
|
+
const op = (_d = pf.operator) !== null && _d !== void 0 ? _d : 'startsWith';
|
|
603
603
|
const noInput = op === 'isEmpty' || op === 'isNotEmpty';
|
|
604
604
|
return (h("div", { class: "mrd-table__filter-editor" }, h("select", { class: "mrd-table__filter-select", onChange: (e) => this.setPending('operator', e.target.value) }, [
|
|
605
605
|
{ val: 'startsWith', labelKey: 'filter_starts_with' },
|
|
606
606
|
{ val: 'equals', labelKey: 'filter_equals' },
|
|
607
607
|
{ val: 'isEmpty', labelKey: 'filter_is_empty' },
|
|
608
608
|
{ val: 'isNotEmpty', labelKey: 'filter_is_not_empty' },
|
|
609
|
-
].map(o => h("option", { value: o.val, selected: op === o.val }, t(o.labelKey, this.locale)))), !noInput && (h("input", { type: "text", class: "mrd-table__filter-input", value: String((
|
|
609
|
+
].map(o => h("option", { value: o.val, selected: op === o.val }, t(o.labelKey, this.locale)))), !noInput && (h("input", { type: "text", class: "mrd-table__filter-input", value: String((_e = pf.value) !== null && _e !== void 0 ? _e : ''), placeholder: t('filter_search_value', this.locale), onInput: (e) => this.setPending('value', e.target.value) }))));
|
|
610
610
|
}
|
|
611
611
|
if (NUMERIC_TYPES.has(dataType)) {
|
|
612
612
|
const numOp = pf.operator;
|
|
@@ -634,7 +634,7 @@ export class MrdTable {
|
|
|
634
634
|
else {
|
|
635
635
|
this.pendingFilter = Object.assign(Object.assign({}, pf), { operator: undefined });
|
|
636
636
|
}
|
|
637
|
-
} }, h("option", { value: "", selected: !noInput }, t('filter_has_value', this.locale)), h("option", { value: "isEmpty", selected: dtOp === 'isEmpty' }, t('filter_is_empty', this.locale)), h("option", { value: "isNotEmpty", selected: dtOp === 'isNotEmpty' }, t('filter_is_not_empty', this.locale))), !noInput && (h("div", { class: "mrd-table__filter-editor" }, h("div", { class: "mrd-table__filter-radio-group mrd-table__filter-radio-group--inline" }, h("label", { class: "mrd-table__filter-radio-label" }, h("input", { type: "radio", name: `dt-${this.openFilterCol}`, checked: !rangeMode, onChange: () => { this.pendingFilter = Object.assign(Object.assign({}, pf), { from: undefined, to: undefined }); } }), t('filter_exact', this.locale)), h("label", { class: "mrd-table__filter-radio-label" }, h("input", { type: "radio", name: `dt-${this.openFilterCol}`, checked: rangeMode, onChange: () => { this.pendingFilter = Object.assign(Object.assign({}, pf), { value: undefined, from: null, to: null }); } }), t('filter_range', this.locale))), !rangeMode ? (h("input", { type: "date", class: "mrd-table__filter-input", value: String((
|
|
637
|
+
} }, h("option", { value: "", selected: !noInput }, t('filter_has_value', this.locale)), h("option", { value: "isEmpty", selected: dtOp === 'isEmpty' }, t('filter_is_empty', this.locale)), h("option", { value: "isNotEmpty", selected: dtOp === 'isNotEmpty' }, t('filter_is_not_empty', this.locale))), !noInput && (h("div", { class: "mrd-table__filter-editor" }, h("div", { class: "mrd-table__filter-radio-group mrd-table__filter-radio-group--inline" }, h("label", { class: "mrd-table__filter-radio-label" }, h("input", { type: "radio", name: `dt-${this.openFilterCol}`, checked: !rangeMode, onChange: () => { this.pendingFilter = Object.assign(Object.assign({}, pf), { from: undefined, to: undefined }); } }), t('filter_exact', this.locale)), h("label", { class: "mrd-table__filter-radio-label" }, h("input", { type: "radio", name: `dt-${this.openFilterCol}`, checked: rangeMode, onChange: () => { this.pendingFilter = Object.assign(Object.assign({}, pf), { value: undefined, from: null, to: null }); } }), t('filter_range', this.locale))), !rangeMode ? (h("input", { type: "date", class: "mrd-table__filter-input", value: String((_f = pf.value) !== null && _f !== void 0 ? _f : ''), onInput: (e) => this.setPending('value', e.target.value) })) : (h("div", { class: "mrd-table__filter-range mrd-table__filter-range--stacked" }, h("label", { class: "mrd-table__filter-range-label" }, t('filter_from', this.locale)), h("input", { type: "date", class: "mrd-table__filter-input", value: pf.from != null ? String(pf.from) : '', onInput: (e) => this.setPending('from', e.target.value) }), h("label", { class: "mrd-table__filter-range-label" }, t('filter_to', this.locale)), h("input", { type: "date", class: "mrd-table__filter-input", value: pf.to != null ? String(pf.to) : '', onInput: (e) => this.setPending('to', e.target.value) })))))));
|
|
638
638
|
}
|
|
639
639
|
if (DATE_TYPES.has(dataType)) {
|
|
640
640
|
const inputType = dataType === 'DATE' ? 'date' : 'time';
|
|
@@ -649,18 +649,18 @@ export class MrdTable {
|
|
|
649
649
|
else {
|
|
650
650
|
this.pendingFilter = Object.assign(Object.assign({}, pf), { operator: undefined });
|
|
651
651
|
}
|
|
652
|
-
} }, h("option", { value: "", selected: !noInput }, t('filter_has_value', this.locale)), h("option", { value: "isEmpty", selected: dtdOp === 'isEmpty' }, t('filter_is_empty', this.locale)), h("option", { value: "isNotEmpty", selected: dtdOp === 'isNotEmpty' }, t('filter_is_not_empty', this.locale))), !noInput && (h("div", { class: "mrd-table__filter-editor" }, h("div", { class: "mrd-table__filter-radio-group mrd-table__filter-radio-group--inline" }, h("label", { class: "mrd-table__filter-radio-label" }, h("input", { type: "radio", name: `dt-${this.openFilterCol}`, checked: !rangeMode, onChange: () => { this.pendingFilter = Object.assign(Object.assign({}, pf), { from: undefined, to: undefined }); } }), t('filter_exact', this.locale)), h("label", { class: "mrd-table__filter-radio-label" }, h("input", { type: "radio", name: `dt-${this.openFilterCol}`, checked: rangeMode, onChange: () => { this.pendingFilter = Object.assign(Object.assign({}, pf), { value: undefined, from: null, to: null }); } }), t('filter_range', this.locale))), !rangeMode ? (h("input", { type: inputType, class: "mrd-table__filter-input", value: String((
|
|
652
|
+
} }, h("option", { value: "", selected: !noInput }, t('filter_has_value', this.locale)), h("option", { value: "isEmpty", selected: dtdOp === 'isEmpty' }, t('filter_is_empty', this.locale)), h("option", { value: "isNotEmpty", selected: dtdOp === 'isNotEmpty' }, t('filter_is_not_empty', this.locale))), !noInput && (h("div", { class: "mrd-table__filter-editor" }, h("div", { class: "mrd-table__filter-radio-group mrd-table__filter-radio-group--inline" }, h("label", { class: "mrd-table__filter-radio-label" }, h("input", { type: "radio", name: `dt-${this.openFilterCol}`, checked: !rangeMode, onChange: () => { this.pendingFilter = Object.assign(Object.assign({}, pf), { from: undefined, to: undefined }); } }), t('filter_exact', this.locale)), h("label", { class: "mrd-table__filter-radio-label" }, h("input", { type: "radio", name: `dt-${this.openFilterCol}`, checked: rangeMode, onChange: () => { this.pendingFilter = Object.assign(Object.assign({}, pf), { value: undefined, from: null, to: null }); } }), t('filter_range', this.locale))), !rangeMode ? (h("input", { type: inputType, class: "mrd-table__filter-input", value: String((_g = pf.value) !== null && _g !== void 0 ? _g : ''), onInput: (e) => this.setPending('value', e.target.value) })) : (h("div", { class: "mrd-table__filter-range" }, h("input", { type: inputType, class: "mrd-table__filter-input", placeholder: t('filter_from', this.locale), value: pf.from != null ? String(pf.from) : '', onInput: (e) => this.setPending('from', e.target.value) }), h("input", { type: inputType, class: "mrd-table__filter-input", placeholder: t('filter_to', this.locale), value: pf.to != null ? String(pf.to) : '', onInput: (e) => this.setPending('to', e.target.value) })))))));
|
|
653
653
|
}
|
|
654
654
|
return null;
|
|
655
655
|
}
|
|
656
656
|
renderFilterPopup() {
|
|
657
|
-
var _a
|
|
657
|
+
var _a;
|
|
658
658
|
if (!this.openFilterCol || !this.pendingFilter)
|
|
659
659
|
return null;
|
|
660
660
|
const col = this.columns.find(c => this.colName(c) === this.openFilterCol);
|
|
661
661
|
if (!col)
|
|
662
662
|
return null;
|
|
663
|
-
const label = (
|
|
663
|
+
const label = (_a = col.label) !== null && _a !== void 0 ? _a : this.openFilterCol;
|
|
664
664
|
const sortActive = this.sortField === this.openFilterCol;
|
|
665
665
|
return (h("div", { class: "mrd-table__filter-popup", style: { top: `${this.popupPos.top}px`, left: `${this.popupPos.left}px` }, onClick: (e) => e.stopPropagation() }, h("div", { class: "mrd-table__filter-popup-header" }, h("span", { class: "mrd-table__filter-popup-title" }, label), h("button", { class: "mrd-table__filter-close", onClick: () => this.closeFilterPopup() }, "\u2715")), h("div", { class: "mrd-table__filter-section" }, h("div", { class: "mrd-table__filter-section-label" }, t('filter_sorting', this.locale)), h("div", { class: "mrd-table__filter-sort-buttons" }, h("button", { class: `mrd-table__filter-sort-btn${sortActive && this.sortDir === 'asc' ? ' mrd-table__filter-sort-btn--active' : ''}`, onClick: () => this.applySort(col, 'asc') }, "\u25B2 ", t('filter_ascending', this.locale)), h("button", { class: `mrd-table__filter-sort-btn${sortActive && this.sortDir === 'desc' ? ' mrd-table__filter-sort-btn--active' : ''}`, onClick: () => this.applySort(col, 'desc') }, "\u25BC ", t('filter_descending', this.locale)))), h("div", { class: "mrd-table__filter-divider" }), h("div", { class: "mrd-table__filter-section" }, h("div", { class: "mrd-table__filter-section-label" }, t('filter_section', this.locale)), this.renderFilterEditor(col)), h("div", { class: "mrd-table__filter-popup-footer" }, h("button", { class: "mrd-table__filter-btn mrd-table__filter-btn--clear", onClick: () => this.clearFilter() }, t('filter_clear', this.locale)), h("button", { class: "mrd-table__filter-btn mrd-table__filter-btn--apply", onClick: () => this.applyFilter() }, t('filter_apply', this.locale)))));
|
|
666
666
|
}
|
|
@@ -687,13 +687,13 @@ export class MrdTable {
|
|
|
687
687
|
}
|
|
688
688
|
// ── Render: cell ──────────────────────────────────────────────────────────
|
|
689
689
|
renderCell(col, row) {
|
|
690
|
-
var _a, _b, _c
|
|
690
|
+
var _a, _b, _c;
|
|
691
691
|
const numericTypes = new Set(['INTEGER', 'DECIMAL', 'PERCENTAGE', 'CURRENCY']);
|
|
692
|
-
const dataType = (
|
|
692
|
+
const dataType = (_a = col.dataType) !== null && _a !== void 0 ? _a : '';
|
|
693
693
|
const isNumeric = col.type === 'FIELD' && numericTypes.has(dataType);
|
|
694
694
|
const isFile = col.type === 'FIELD' && (dataType === 'FILE' || dataType === 'IMAGE');
|
|
695
695
|
if (isFile) {
|
|
696
|
-
const name = (
|
|
696
|
+
const name = (_b = col.name) !== null && _b !== void 0 ? _b : '';
|
|
697
697
|
const fileVal = row === null || row === void 0 ? void 0 : row[name];
|
|
698
698
|
const href = fileVal === null || fileVal === void 0 ? void 0 : fileVal.href;
|
|
699
699
|
const fileName = fileVal === null || fileVal === void 0 ? void 0 : fileVal.fileName;
|
|
@@ -703,7 +703,7 @@ export class MrdTable {
|
|
|
703
703
|
} }, h("svg", { class: "mrd-table__file-icon", viewBox: "0 0 24 24", "aria-hidden": "true" }, h("path", { fill: "currentColor", d: "M14 2H6c-1.1 0-2 .9-2 2v16c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V8l-6-6zm-1 7V3.5L18.5 9H13zm-3 8l-3-3 1.41-1.41L10 14.17l4.59-4.58L16 11l-6 6z" })), t('download', this.locale))) : ''));
|
|
704
704
|
}
|
|
705
705
|
if (dataType === 'JSON') {
|
|
706
|
-
const name = (
|
|
706
|
+
const name = (_c = col.name) !== null && _c !== void 0 ? _c : '';
|
|
707
707
|
const rawValue = name ? row === null || row === void 0 ? void 0 : row[name] : undefined;
|
|
708
708
|
if (rawValue == null || rawValue === '')
|
|
709
709
|
return h("td", { class: "mrd-table__cell" });
|
|
@@ -727,12 +727,12 @@ export class MrdTable {
|
|
|
727
727
|
renderTotalsRow() {
|
|
728
728
|
if (!this.aggregations)
|
|
729
729
|
return null;
|
|
730
|
-
if (!this.columns.some(c =>
|
|
730
|
+
if (!this.columns.some(c => c.type === 'FIELD' && c.aggregate))
|
|
731
731
|
return null;
|
|
732
732
|
return (h("tfoot", null, h("tr", { class: "mrd-table__totals-row" }, this.columns.map(col => {
|
|
733
|
-
var _a
|
|
733
|
+
var _a;
|
|
734
734
|
const val = this.renderAggregationValue(col);
|
|
735
|
-
const isNumeric = col.type === 'FIELD' && NUMERIC_TYPES.has((
|
|
735
|
+
const isNumeric = col.type === 'FIELD' && NUMERIC_TYPES.has((_a = col.dataType) !== null && _a !== void 0 ? _a : '');
|
|
736
736
|
return (h("td", { class: `mrd-table__totals-cell${isNumeric ? ' mrd-table__totals-cell--numeric' : ''}` }, val));
|
|
737
737
|
}))));
|
|
738
738
|
}
|
|
@@ -744,7 +744,7 @@ export class MrdTable {
|
|
|
744
744
|
// ── Non-paginated mode ──────────────────────────────────────────────────
|
|
745
745
|
if (this.totalElements === 0) {
|
|
746
746
|
return (h(Host, null, this.renderToolbar(), h("div", { class: "mrd-table" }, h("table", { class: "mrd-table__table" }, h("thead", null, h("tr", null, this.columns.map(col => {
|
|
747
|
-
var _a
|
|
747
|
+
var _a;
|
|
748
748
|
const name = this.colName(col);
|
|
749
749
|
const isFiltered = this.activeFilters.has(name);
|
|
750
750
|
const cls = [
|
|
@@ -752,7 +752,7 @@ export class MrdTable {
|
|
|
752
752
|
isFiltered ? 'mrd-table__header--filtered' : '',
|
|
753
753
|
this.filterMode ? 'mrd-table__header--sortable' : '',
|
|
754
754
|
].filter(Boolean).join(' ');
|
|
755
|
-
return (h("th", { class: cls, onClick: this.filterMode ? (e) => this.handleFilterOpen(col, e) : undefined }, h("span", { class: "mrd-table__header-label" }, (
|
|
755
|
+
return (h("th", { class: cls, onClick: this.filterMode ? (e) => this.handleFilterOpen(col, e) : undefined }, h("span", { class: "mrd-table__header-label" }, (_a = col.label) !== null && _a !== void 0 ? _a : ''), isFiltered && this.renderFilterIcon()));
|
|
756
756
|
}))), h("tbody", null, (_b = this.rows) === null || _b === void 0 ? void 0 : _b.map((row, i) => (h("tr", { class: "mrd-table__row mrd-table__row--clickable", style: { background: i % 2 === 0 ? '' : 'var(--mrd-color-neutral-100)' }, onClick: () => this.mrdRowClick.emit(row) }, this.columns.map(col => this.renderCell(col, row)))))), this.renderTotalsRow()), (!this.rows || this.rows.length === 0) && (h("p", { class: "mrd-table__empty" }, t('no_results', this.locale)))), this.renderFooter((_c = this.rows) === null || _c === void 0 ? void 0 : _c.length), this.renderFilterPopup(), this.renderTextblockModal(), this.renderJsonModal()));
|
|
757
757
|
}
|
|
758
758
|
// ── Paginated / virtual-scroll mode ────────────────────────────────────
|
|
@@ -785,7 +785,7 @@ export class MrdTable {
|
|
|
785
785
|
}
|
|
786
786
|
}
|
|
787
787
|
return (h(Host, null, this.renderToolbar(), h("div", { class: "mrd-table__scroll", style: { height: `${this.tableHeight}px` }, onScroll: this.handleScroll }, h("table", { class: "mrd-table__table", style: tableStyle }, h("thead", null, h("tr", null, this.columns.map((col, idx) => {
|
|
788
|
-
var _a
|
|
788
|
+
var _a;
|
|
789
789
|
const name = this.colName(col);
|
|
790
790
|
const isActive = this.sortField === name;
|
|
791
791
|
const isFiltered = this.activeFilters.has(name);
|
|
@@ -796,7 +796,7 @@ export class MrdTable {
|
|
|
796
796
|
isActive ? `mrd-table__header--sorted-${this.sortDir}` : '',
|
|
797
797
|
isFiltered ? 'mrd-table__header--filtered' : '',
|
|
798
798
|
].filter(Boolean).join(' ');
|
|
799
|
-
return (h("th", { class: cls, style: this.colWidths[idx] ? { width: `${this.colWidths[idx]}px` } : undefined, onClick: isInteractive ? ((e) => this.filterMode ? this.handleFilterOpen(col, e) : this.handleSortClick(col)) : undefined }, h("span", { class: "mrd-table__header-label" }, (
|
|
799
|
+
return (h("th", { class: cls, style: this.colWidths[idx] ? { width: `${this.colWidths[idx]}px` } : undefined, onClick: isInteractive ? ((e) => this.filterMode ? this.handleFilterOpen(col, e) : this.handleSortClick(col)) : undefined }, h("span", { class: "mrd-table__header-label" }, (_a = col.label) !== null && _a !== void 0 ? _a : ''), isInteractive && isActive && (h("span", { class: "mrd-table__sort-icon", "aria-hidden": "true" }, this.sortDir === 'asc' ? '▲' : '▼')), isInteractive && !isActive && !this.filterMode && (h("span", { class: "mrd-table__sort-icon", "aria-hidden": "true" }, "\u21C5")), isInteractive && isFiltered && this.renderFilterIcon()));
|
|
800
800
|
}))), h("tbody", null, topSpacerHeight > 0 && (h("tr", { class: "mrd-table__spacer", style: { height: `${topSpacerHeight}px` } }, h("td", { colSpan: colCount }))), renderedRows, bottomSpacerHeight > 0 && (h("tr", { class: "mrd-table__spacer", style: { height: `${bottomSpacerHeight}px` } }, h("td", { colSpan: colCount })))), this.renderTotalsRow())), effectiveTotal === 0 && this.loadedPages.has(0) && (h("p", { class: "mrd-table__empty" }, t('no_results', this.locale))), effectiveTotal > 0 && this.renderFooter(undefined, effectiveTotal), this.renderFilterPopup(), this.renderTextblockModal()));
|
|
801
801
|
}
|
|
802
802
|
renderFilterIcon() {
|
|
@@ -102,106 +102,18 @@ async function apiSearchRelation(token, tenantCode, mostSignificantClass, query)
|
|
|
102
102
|
|
|
103
103
|
/* =====================================================================
|
|
104
104
|
LAYOUT MAPPERS
|
|
105
|
-
|
|
106
|
-
|
|
105
|
+
mrd-form and mrd-field now accept the flat API format directly.
|
|
106
|
+
No transformation needed — pass the raw layout through as-is.
|
|
107
107
|
===================================================================== */
|
|
108
108
|
|
|
109
109
|
/**
|
|
110
|
-
*
|
|
111
|
-
*
|
|
112
|
-
|
|
113
|
-
function mapApiItem(item) {
|
|
114
|
-
if (item.type === 'FIELD') {
|
|
115
|
-
if (item.field) return item; // already nested — pass through
|
|
116
|
-
return {
|
|
117
|
-
type: 'FIELD',
|
|
118
|
-
field: {
|
|
119
|
-
name: item.name,
|
|
120
|
-
label: item.label,
|
|
121
|
-
dataType: item.dataType,
|
|
122
|
-
required: !!item.required,
|
|
123
|
-
multiple: !!item.multiple,
|
|
124
|
-
header: !!item.header,
|
|
125
|
-
defaultValue: item.defaultValue ?? null,
|
|
126
|
-
listItems: item.listItems ?? null,
|
|
127
|
-
aggregate: item.aggregate ?? null,
|
|
128
|
-
},
|
|
129
|
-
};
|
|
130
|
-
}
|
|
131
|
-
if (item.type === 'RELATION') {
|
|
132
|
-
if (item.relation) return item; // already nested — pass through
|
|
133
|
-
return {
|
|
134
|
-
type: 'RELATION',
|
|
135
|
-
relation: {
|
|
136
|
-
name: item.name,
|
|
137
|
-
label: item.label,
|
|
138
|
-
relatedClass: item.relatedClass,
|
|
139
|
-
mostSignificantClass: item.mostSignificantClass ?? null,
|
|
140
|
-
displayType: item.editBehavior ?? 'SEARCH',
|
|
141
|
-
editBehavior: item.editBehavior ?? null,
|
|
142
|
-
commonRelation: item.commonRelation ?? null,
|
|
143
|
-
required: !!item.required,
|
|
144
|
-
multiple: !!item.multiple,
|
|
145
|
-
defaultValue: item.defaultValue ?? null,
|
|
146
|
-
},
|
|
147
|
-
};
|
|
148
|
-
}
|
|
149
|
-
if (Array.isArray(item.items)) {
|
|
150
|
-
return { ...item, items: item.items.map(mapApiItem) };
|
|
151
|
-
}
|
|
152
|
-
return item;
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
/**
|
|
156
|
-
* Map API layout (OBJECT_FORM_DASHBOARD shape) → ClientLayout for mrd-form.
|
|
157
|
-
*
|
|
158
|
-
* IMPORTANT: always map editBehavior and commonRelation from the API response —
|
|
159
|
-
* omitting them causes relation fields to fall back to SEARCH mode.
|
|
160
|
-
* _relationMeta must be keyed by BOTH relatedClass AND mostSignificantClass
|
|
161
|
-
* because mrdSearch sends mostSignificantClass as the lookup key.
|
|
110
|
+
* Extract a ClientLayout from the raw API form response.
|
|
111
|
+
* The API sends flat items (name, dataType, relatedClass at root level)
|
|
112
|
+
* which mrd-form/mrd-field read directly.
|
|
162
113
|
*/
|
|
163
114
|
function mapApiLayoutToMrdForm(raw) {
|
|
164
|
-
if (!raw
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
return {
|
|
169
|
-
type: 'FIELD',
|
|
170
|
-
field: {
|
|
171
|
-
name: item.name,
|
|
172
|
-
label: item.label,
|
|
173
|
-
dataType: item.dataType,
|
|
174
|
-
required: !!item.required,
|
|
175
|
-
multiple: !!item.multiple,
|
|
176
|
-
header: !!item.header,
|
|
177
|
-
defaultValue: item.defaultValue ?? null,
|
|
178
|
-
listItems: item.listItems ?? null,
|
|
179
|
-
},
|
|
180
|
-
};
|
|
181
|
-
}
|
|
182
|
-
if (item.type === 'RELATION') {
|
|
183
|
-
return {
|
|
184
|
-
type: 'RELATION',
|
|
185
|
-
relation: {
|
|
186
|
-
name: item.name,
|
|
187
|
-
label: item.label,
|
|
188
|
-
relatedClass: item.relatedClass,
|
|
189
|
-
mostSignificantClass: item.mostSignificantClass ?? null,
|
|
190
|
-
displayType: item.editBehavior ?? 'SEARCH',
|
|
191
|
-
editBehavior: item.editBehavior ?? null,
|
|
192
|
-
commonRelation: item.commonRelation ?? null,
|
|
193
|
-
required: !!item.required,
|
|
194
|
-
multiple: !!item.multiple,
|
|
195
|
-
defaultValue: item.defaultValue ?? null,
|
|
196
|
-
},
|
|
197
|
-
};
|
|
198
|
-
}
|
|
199
|
-
// SECTION / GROUP — recurse into children
|
|
200
|
-
if (Array.isArray(item.items)) {
|
|
201
|
-
return { ...item, items: item.items.map(mapItem) };
|
|
202
|
-
}
|
|
203
|
-
return item;
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
return { items: raw.items.map(mapItem) };
|
|
115
|
+
if (!raw) return raw;
|
|
116
|
+
// Unwrap OBJECT_FORM_DASHBOARD: keep items at top level, drop type wrapper
|
|
117
|
+
if (Array.isArray(raw.items)) return raw;
|
|
118
|
+
return raw;
|
|
207
119
|
}
|
|
@@ -260,7 +260,7 @@ async function loadTable() {
|
|
|
260
260
|
// Strip any ?page= from href so we control pagination ourselves
|
|
261
261
|
_tableDataHref = dataHref.replace(/([?&])page=\d+/, '').replace(/\?$/, '');
|
|
262
262
|
|
|
263
|
-
const columns =
|
|
263
|
+
const columns = view.values ?? [];
|
|
264
264
|
const defaultSort = view.defaultSort ?? '';
|
|
265
265
|
const viewLabel = layoutItem?.label ?? viewKey ?? '';
|
|
266
266
|
const alternativeViews = layoutItem?.alternativeViews ?? [];
|
|
@@ -407,7 +407,7 @@ function renderTable(columns, totalElements, pageSize, page0Rows, dataHref, defa
|
|
|
407
407
|
const meta = page0.page ?? {};
|
|
408
408
|
const rows0 = Object.values(page0._embedded ?? {})[0] ?? [];
|
|
409
409
|
|
|
410
|
-
table.columns =
|
|
410
|
+
table.columns = newView.values ?? [];
|
|
411
411
|
table.defaultSort = newDefaultSort;
|
|
412
412
|
table.totalElements = meta.totalElements ?? 0;
|
|
413
413
|
table.pageSize = meta.size ?? 20;
|
|
@@ -455,7 +455,7 @@ async function loadRecord(row) {
|
|
|
455
455
|
|
|
456
456
|
const formHref = recordResp.body?._links?.form?.href ?? null;
|
|
457
457
|
const layout = await apiFetchForm(authGetToken(), _selectedTenant, _selectedType.pluralName, formHref);
|
|
458
|
-
renderForm(layout, recordResp.body, selfHref);
|
|
458
|
+
await renderForm(layout, recordResp.body, selfHref);
|
|
459
459
|
} catch (err) {
|
|
460
460
|
formPanel.innerHTML = `<span style="color:var(--mrd-color-danger)">❌ ${escHtml(err.message)}</span>`;
|
|
461
461
|
}
|
|
@@ -476,7 +476,7 @@ async function loadForm() {
|
|
|
476
476
|
|
|
477
477
|
try {
|
|
478
478
|
const layout = await apiFetchForm(authGetToken(), _selectedTenant, pluralName);
|
|
479
|
-
renderForm(layout);
|
|
479
|
+
await renderForm(layout);
|
|
480
480
|
} catch (err) {
|
|
481
481
|
formPanel.innerHTML = `<span style="color:var(--mrd-color-danger)">❌ ${escHtml(err.message)}</span>`;
|
|
482
482
|
}
|
|
@@ -489,21 +489,21 @@ async function loadForm() {
|
|
|
489
489
|
* @param record - existing record for edit mode (null = new record)
|
|
490
490
|
* @param selfHref - absolute URL for PATCH; null = POST (new record)
|
|
491
491
|
*/
|
|
492
|
-
function renderForm(layout, record = null, selfHref = null) {
|
|
492
|
+
async function renderForm(layout, record = null, selfHref = null) {
|
|
493
493
|
// Build relation metadata map keyed by both relatedClass and mostSignificantClass
|
|
494
494
|
// so lookups work regardless of which value arrives in events (mrdSearch uses mostSignificantClass).
|
|
495
495
|
_relationMeta = {};
|
|
496
496
|
function collectRelations(items) {
|
|
497
497
|
if (!Array.isArray(items)) return;
|
|
498
498
|
items.forEach(item => {
|
|
499
|
-
if (item.type === 'RELATION' && item.
|
|
499
|
+
if (item.type === 'RELATION' && item.relatedClass) {
|
|
500
500
|
const meta = {
|
|
501
|
-
name: item.
|
|
502
|
-
mostSignificantClass: item.
|
|
501
|
+
name: item.name,
|
|
502
|
+
mostSignificantClass: item.mostSignificantClass,
|
|
503
503
|
};
|
|
504
|
-
_relationMeta[item.
|
|
505
|
-
if (item.
|
|
506
|
-
_relationMeta[item.
|
|
504
|
+
_relationMeta[item.relatedClass] = meta;
|
|
505
|
+
if (item.mostSignificantClass) {
|
|
506
|
+
_relationMeta[item.mostSignificantClass] = meta;
|
|
507
507
|
}
|
|
508
508
|
}
|
|
509
509
|
if (Array.isArray(item.items)) collectRelations(item.items);
|
|
@@ -534,12 +534,19 @@ function renderForm(layout, record = null, selfHref = null) {
|
|
|
534
534
|
const formPanel = document.getElementById('panel-form');
|
|
535
535
|
formPanel.innerHTML = `<mrd-form id="live-form" locale="${_locale}"></mrd-form>`;
|
|
536
536
|
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
537
|
+
const form = document.getElementById('live-form');
|
|
538
|
+
// Wait for Stencil to fully initialize this specific instance before setting props.
|
|
539
|
+
// componentOnReady() resolves after componentDidLoad — more reliable than customElements.whenDefined.
|
|
540
|
+
if (typeof form.componentOnReady === 'function') {
|
|
541
|
+
await form.componentOnReady();
|
|
542
|
+
} else {
|
|
543
|
+
await customElements.whenDefined('mrd-form');
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
form.layout = layout;
|
|
547
|
+
if (record) form.values = initialValues;
|
|
541
548
|
|
|
542
|
-
|
|
549
|
+
// Upload files immediately on selection; write binary URI back via setFieldValue
|
|
543
550
|
form.addEventListener('mrdChange', async (e) => {
|
|
544
551
|
const { name, value } = e.detail;
|
|
545
552
|
if (!(value instanceof File)) return;
|
|
@@ -645,7 +652,6 @@ function renderForm(layout, record = null, selfHref = null) {
|
|
|
645
652
|
console.error('[mrdFetchAll] failed:', err);
|
|
646
653
|
}
|
|
647
654
|
});
|
|
648
|
-
});
|
|
649
655
|
}
|
|
650
656
|
|
|
651
657
|
/* =====================================================================
|
|
@@ -745,6 +751,17 @@ document.addEventListener('DOMContentLoaded', async () => {
|
|
|
745
751
|
logEvent('mrdDownload', e.detail);
|
|
746
752
|
});
|
|
747
753
|
|
|
754
|
+
ftSection.addEventListener('mrdSearch', (e) => {
|
|
755
|
+
logEvent('mrdSearch', e.detail);
|
|
756
|
+
const { query, dataClass } = e.detail;
|
|
757
|
+
const mock = [
|
|
758
|
+
{ id: '/data/demo/1', label: 'Alice Johnson', description: 'Senior Engineer' },
|
|
759
|
+
{ id: '/data/demo/2', label: 'Bob van der Berg', description: 'Product Manager' },
|
|
760
|
+
{ id: '/data/demo/3', label: 'Carol Martínez', description: 'UX Designer' },
|
|
761
|
+
].filter(r => r.label.toLowerCase().includes(query.toLowerCase()));
|
|
762
|
+
ftSection.setSearchResults(mock, dataClass);
|
|
763
|
+
});
|
|
764
|
+
|
|
748
765
|
authRestoreSession();
|
|
749
766
|
await authHandleCallback();
|
|
750
767
|
|