@mmlogic/components 0.5.0 → 0.5.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/cell-renderer-CmaO0CZT.js +862 -0
- package/dist/cjs/loader.cjs.js +1 -1
- package/dist/cjs/mosterdcomponents.cjs.js +1 -1
- package/dist/cjs/mrd-boolean-field_23.cjs.entry.js +2506 -832
- package/dist/cjs/mrd-merge-view.cjs.entry.js +273 -0
- package/dist/collection/collection-manifest.json +1 -0
- package/dist/collection/components/documents/mrd-document-view/mrd-document-view.css +27 -24
- package/dist/collection/components/documents/mrd-document-view/mrd-document-view.js +16 -9
- package/dist/collection/components/fields/mrd-textarea-field/mrd-textarea-field.js +9 -5
- package/dist/collection/components/merge/mrd-merge-view/merge-diff.js +99 -0
- package/dist/collection/components/merge/mrd-merge-view/merge-parts.js +44 -0
- package/dist/collection/components/merge/mrd-merge-view/mrd-merge-view.css +244 -0
- package/dist/collection/components/merge/mrd-merge-view/mrd-merge-view.js +397 -0
- package/dist/collection/components/table/mrd-table/mrd-table.js +2 -4
- package/dist/collection/components/table/mrd-table/table-parts.js +2 -5
- package/dist/collection/dev/api.js +24 -0
- package/dist/collection/dev/app.js +84 -4
- package/dist/collection/dev/example-data.js +2 -1
- package/dist/collection/utils/cell-renderer.js +6 -1
- package/dist/collection/utils/i18n.js +104 -0
- package/dist/components/cell-renderer.js +1 -0
- package/dist/components/format.js +1 -1
- package/dist/components/i18n.js +1 -1
- package/dist/components/mrd-document-view2.js +1 -1
- package/dist/components/mrd-layout-section.js +1 -1
- package/dist/components/mrd-merge-view.d.ts +11 -0
- package/dist/components/mrd-merge-view.js +1 -0
- package/dist/components/mrd-table2.js +1 -1
- package/dist/components/mrd-textarea-field2.js +2 -1
- package/dist/esm/cell-renderer-D63AUZxa.js +854 -0
- package/dist/esm/loader.js +1 -1
- package/dist/esm/mosterdcomponents.js +1 -1
- package/dist/esm/mrd-boolean-field_23.entry.js +2437 -763
- package/dist/esm/mrd-merge-view.entry.js +271 -0
- package/dist/mosterdcomponents/mosterdcomponents.esm.js +1 -1
- package/dist/mosterdcomponents/p-2e16a7d0.entry.js +1 -0
- package/dist/mosterdcomponents/p-CnqDM0Gb.js +1 -0
- package/dist/mosterdcomponents/p-b5f38707.entry.js +3 -0
- package/dist/types/components/documents/mrd-document-view/mrd-document-view.d.ts +9 -5
- package/dist/types/components/fields/mrd-textarea-field/mrd-textarea-field.d.ts +1 -0
- package/dist/types/components/merge/mrd-merge-view/merge-diff.d.ts +43 -0
- package/dist/types/components/merge/mrd-merge-view/merge-parts.d.ts +37 -0
- package/dist/types/components/merge/mrd-merge-view/mrd-merge-view.d.ts +68 -0
- package/dist/types/components.d.ts +167 -12
- package/dist/types/types/client-layout.d.ts +2 -3
- package/package.json +2 -1
- package/dist/mosterdcomponents/p-b56f9802.entry.js +0 -1
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var index = require('./index-DzRfoAPz.js');
|
|
4
|
+
var clientLayout = require('./client-layout-RZ3Ix-4a.js');
|
|
5
|
+
var cellRenderer = require('./cell-renderer-CmaO0CZT.js');
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Whether a layout item can appear in the merge diff at all. m-n relations
|
|
9
|
+
* (`multiple: true`) are unioned automatically server-side (DataService.merge)
|
|
10
|
+
* and must never be surfaced as a user choice — only FIELD and single-value
|
|
11
|
+
* (n-1) RELATION items are diffable.
|
|
12
|
+
*/
|
|
13
|
+
function isDiffable(item) {
|
|
14
|
+
if (item.type === clientLayout.ClientLayoutItemType.FIELD)
|
|
15
|
+
return true;
|
|
16
|
+
if (item.type === clientLayout.ClientLayoutItemType.RELATION)
|
|
17
|
+
return !item.multiple;
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Raw value for a field/relation off a record in the API's row shape (flat
|
|
22
|
+
* fields + `_links`). History-enabled fields come back as `{ current,
|
|
23
|
+
* history: [...] }` — this returns that whole object as-is, since a winning
|
|
24
|
+
* B value must carry its entire history into the patch (FINDING-0114: "the
|
|
25
|
+
* entire history list follows whichever value wins ... never interleaved").
|
|
26
|
+
*/
|
|
27
|
+
function getRawValue(item, record) {
|
|
28
|
+
var _a, _b, _c, _d, _e;
|
|
29
|
+
const name = (_a = item.name) !== null && _a !== void 0 ? _a : '';
|
|
30
|
+
if (item.type === clientLayout.ClientLayoutItemType.RELATION) {
|
|
31
|
+
return (_d = (_c = (_b = record === null || record === void 0 ? void 0 : record._links) === null || _b === void 0 ? void 0 : _b[name]) === null || _c === void 0 ? void 0 : _c.href) !== null && _d !== void 0 ? _d : null;
|
|
32
|
+
}
|
|
33
|
+
return (_e = record === null || record === void 0 ? void 0 : record[name]) !== null && _e !== void 0 ? _e : null;
|
|
34
|
+
}
|
|
35
|
+
/** Unwraps a history-enabled field's `{ current, history }` shape to its current value, for comparison only. */
|
|
36
|
+
function comparableValue(item, value) {
|
|
37
|
+
if (item.historyEnabled && value != null && typeof value === 'object' && !Array.isArray(value) && 'current' in value) {
|
|
38
|
+
return value.current;
|
|
39
|
+
}
|
|
40
|
+
return value;
|
|
41
|
+
}
|
|
42
|
+
function normalize(value) {
|
|
43
|
+
return value === undefined || value === '' ? null : value;
|
|
44
|
+
}
|
|
45
|
+
function valuesEqual(a, b) {
|
|
46
|
+
const na = normalize(a);
|
|
47
|
+
const nb = normalize(b);
|
|
48
|
+
if (na === nb)
|
|
49
|
+
return true;
|
|
50
|
+
if (na === null || nb === null)
|
|
51
|
+
return false;
|
|
52
|
+
return JSON.stringify(na) === JSON.stringify(nb);
|
|
53
|
+
}
|
|
54
|
+
function isEmpty(value) {
|
|
55
|
+
return normalize(value) === null;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Splits diffable fields into `conflicts` (both records have a — different —
|
|
59
|
+
* value, user must pick) and `autoFilled` (A has no value, B does; silently
|
|
60
|
+
* taken from B without a UI choice).
|
|
61
|
+
*/
|
|
62
|
+
function diffFields(fields, recordA, recordB) {
|
|
63
|
+
const conflicts = [];
|
|
64
|
+
const autoFilled = [];
|
|
65
|
+
for (const item of fields.filter(isDiffable)) {
|
|
66
|
+
const aValue = getRawValue(item, recordA);
|
|
67
|
+
const bValue = getRawValue(item, recordB);
|
|
68
|
+
const aComparable = comparableValue(item, aValue);
|
|
69
|
+
const bComparable = comparableValue(item, bValue);
|
|
70
|
+
if (valuesEqual(aComparable, bComparable))
|
|
71
|
+
continue;
|
|
72
|
+
if (isEmpty(aComparable) && !isEmpty(bComparable)) {
|
|
73
|
+
autoFilled.push({ item, aValue, bValue });
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
conflicts.push({ item, aValue, bValue });
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return { conflicts, autoFilled };
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Builds the patch payload per the merge endpoint contract: every `autoFilled`
|
|
83
|
+
* entry is always included (A had nothing to lose); a `conflicts` entry is
|
|
84
|
+
* only included when the user explicitly resolved it to B — anything left
|
|
85
|
+
* unresolved keeps A's value by omission.
|
|
86
|
+
*/
|
|
87
|
+
function buildPatch(diff, resolutions) {
|
|
88
|
+
var _a, _b, _c;
|
|
89
|
+
const patch = {};
|
|
90
|
+
for (const { item, bValue } of diff.autoFilled) {
|
|
91
|
+
patch[(_a = item.name) !== null && _a !== void 0 ? _a : ''] = bValue;
|
|
92
|
+
}
|
|
93
|
+
for (const { item, bValue } of diff.conflicts) {
|
|
94
|
+
if (resolutions[(_b = item.name) !== null && _b !== void 0 ? _b : ''] === 'B') {
|
|
95
|
+
patch[(_c = item.name) !== null && _c !== void 0 ? _c : ''] = bValue;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return patch;
|
|
99
|
+
}
|
|
100
|
+
/** Display label for a candidate record in the search results list. */
|
|
101
|
+
function candidateLabel(record) {
|
|
102
|
+
var _a, _b, _c, _d, _e;
|
|
103
|
+
return (_e = (_b = (_a = record === null || record === void 0 ? void 0 : record.name) !== null && _a !== void 0 ? _a : record === null || record === void 0 ? void 0 : record.label) !== null && _b !== void 0 ? _b : (_d = (_c = record === null || record === void 0 ? void 0 : record._links) === null || _c === void 0 ? void 0 : _c.self) === null || _d === void 0 ? void 0 : _d.href) !== null && _e !== void 0 ? _e : '';
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** History-enabled fields carry `{ current, history: [...] }` — this is the legal record itself (e.g. past company names), not an audit log. */
|
|
107
|
+
function getHistory(item, record) {
|
|
108
|
+
var _a, _b;
|
|
109
|
+
if (!item.historyEnabled || !item.name)
|
|
110
|
+
return [];
|
|
111
|
+
return (_b = (_a = record === null || record === void 0 ? void 0 : record[item.name]) === null || _a === void 0 ? void 0 : _a.history) !== null && _b !== void 0 ? _b : [];
|
|
112
|
+
}
|
|
113
|
+
/** Small clock badge + popover listing past values for a history-enabled field — same UX as mrd-layout-section's field view. */
|
|
114
|
+
const HistoryBadge = ({ locale, history, isOpen, onToggle }) => {
|
|
115
|
+
if (history.length === 0)
|
|
116
|
+
return null;
|
|
117
|
+
const sorted = [...history].sort((a, b) => b.until.localeCompare(a.until));
|
|
118
|
+
return (index.h("span", { class: "mrd-merge-view__history-wrap" }, index.h("button", { type: "button", class: "mrd-merge-view__history-btn", title: cellRenderer.t('history_badge_tooltip', locale), "aria-label": cellRenderer.t('history_badge_tooltip', locale), onClick: (e) => { e.stopPropagation(); onToggle(); } }, index.h("svg", { class: "mrd-merge-view__history-icon", viewBox: "0 0 20 20", fill: "currentColor", "aria-hidden": "true" }, index.h("path", { "fill-rule": "evenodd", d: "M10 18a8 8 0 100-16 8 8 0 000 16zm.75-13a.75.75 0 00-1.5 0v5c0 .207.085.394.22.53l2.5 2.5a.75.75 0 101.06-1.06L10.75 9.69V5z", "clip-rule": "evenodd" }))), isOpen && (index.h("div", { class: "mrd-merge-view__history-popover", role: "listbox" }, sorted.map((entry, i) => (index.h("div", { key: String(i), class: "mrd-merge-view__history-entry" }, entry.value, " (", cellRenderer.t('history_until', locale), " ", cellRenderer.formatDate(entry.until, locale), ")")))))));
|
|
119
|
+
};
|
|
120
|
+
const MergeSearchStep = ({ locale, searchQuery, searchResults, isLoading, onSearchInput, onSelect }) => (index.h("div", { class: "mrd-merge-view__search-step" }, index.h("p", { class: "mrd-merge-view__hint" }, cellRenderer.t('merge_search_hint', locale)), index.h("div", { class: "mrd-merge-view__search-wrapper" }, index.h("input", { class: "mrd-merge-view__search", type: "text", value: searchQuery, placeholder: cellRenderer.t('search_placeholder', locale), onInput: (e) => onSearchInput(e.target.value) }), isLoading && index.h("span", { class: "mrd-merge-view__spinner", "aria-label": cellRenderer.t('loading', locale) })), searchQuery.trim().length >= 2 && !isLoading && (index.h("div", { class: "mrd-merge-view__results" }, searchResults.length === 0 ? (index.h("div", { class: "mrd-merge-view__no-results" }, cellRenderer.t('no_results', locale))) : (searchResults.map(record => {
|
|
121
|
+
var _a, _b, _c;
|
|
122
|
+
return (index.h("div", { key: (_c = (_b = (_a = record === null || record === void 0 ? void 0 : record._links) === null || _a === void 0 ? void 0 : _a.self) === null || _b === void 0 ? void 0 : _b.href) !== null && _c !== void 0 ? _c : candidateLabel(record), class: "mrd-merge-view__result-item" }, index.h("span", { class: "mrd-merge-view__result-label" }, candidateLabel(record)), index.h("button", { type: "button", class: "mrd-merge-view__btn mrd-merge-view__btn--secondary", onClick: () => onSelect(record) }, cellRenderer.t('merge_select', locale))));
|
|
123
|
+
}))))));
|
|
124
|
+
const MergeCompareStep = ({ locale, currentLabel, candidateLabel: bLabel, conflicts, autoFilled, recordA, recordB, resolutions, openHistoryKey, onResolve, onToggleHistory, onChangeCandidate, onContinue }) => (index.h("div", { class: "mrd-merge-view__compare-step" }, index.h("h3", { class: "mrd-merge-view__heading" }, cellRenderer.t('merge_compare_heading', locale)), conflicts.length === 0 ? (index.h("p", { class: "mrd-merge-view__no-conflicts" }, cellRenderer.t('merge_no_conflicts', locale))) : (index.h("table", { class: "mrd-merge-view__table" }, index.h("thead", null, index.h("tr", null, index.h("th", { class: "mrd-merge-view__header" }), index.h("th", { class: "mrd-merge-view__header" }, cellRenderer.t('merge_column_a', locale), index.h("span", { class: "mrd-merge-view__header-name" }, currentLabel)), index.h("th", { class: "mrd-merge-view__header" }, cellRenderer.t('merge_column_b', locale), index.h("span", { class: "mrd-merge-view__header-name" }, bLabel)))), index.h("tbody", null, conflicts.map(({ item }) => {
|
|
125
|
+
var _a, _b, _c;
|
|
126
|
+
const name = (_a = item.name) !== null && _a !== void 0 ? _a : '';
|
|
127
|
+
const resolution = (_b = resolutions[name]) !== null && _b !== void 0 ? _b : 'A';
|
|
128
|
+
const historyA = getHistory(item, recordA);
|
|
129
|
+
const historyB = getHistory(item, recordB);
|
|
130
|
+
const keyA = `${name}:A`;
|
|
131
|
+
const keyB = `${name}:B`;
|
|
132
|
+
return (index.h("tr", { key: name, class: "mrd-merge-view__row" }, index.h("td", { class: "mrd-merge-view__cell mrd-merge-view__cell--label" }, (_c = item.label) !== null && _c !== void 0 ? _c : name), index.h("td", { class: "mrd-merge-view__cell" }, index.h("label", { class: "mrd-merge-view__option" }, index.h("input", { type: "radio", name: `merge-${name}`, checked: resolution === 'A', onChange: () => onResolve(name, 'A') }), index.h("span", null, cellRenderer.CellRenderer.render(item, recordA, locale) || '—')), index.h(HistoryBadge, { locale: locale, history: historyA, isOpen: openHistoryKey === keyA, onToggle: () => onToggleHistory(openHistoryKey === keyA ? null : keyA) })), index.h("td", { class: "mrd-merge-view__cell" }, index.h("label", { class: "mrd-merge-view__option" }, index.h("input", { type: "radio", name: `merge-${name}`, checked: resolution === 'B', onChange: () => onResolve(name, 'B') }), index.h("span", null, cellRenderer.CellRenderer.render(item, recordB, locale) || '—')), index.h(HistoryBadge, { locale: locale, history: historyB, isOpen: openHistoryKey === keyB, onToggle: () => onToggleHistory(openHistoryKey === keyB ? null : keyB) }))));
|
|
133
|
+
})))), autoFilled.length > 0 && (index.h("div", { class: "mrd-merge-view__auto-note" }, index.h("p", null, cellRenderer.t('merge_auto_heading', locale)), index.h("ul", null, autoFilled.map(({ item }) => { var _a; return index.h("li", { key: item.name }, (_a = item.label) !== null && _a !== void 0 ? _a : item.name); })))), index.h("div", { class: "mrd-merge-view__actions" }, index.h("button", { type: "button", class: "mrd-merge-view__btn mrd-merge-view__btn--secondary", onClick: onChangeCandidate }, cellRenderer.t('merge_back', locale)), index.h("button", { type: "button", class: "mrd-merge-view__btn mrd-merge-view__btn--primary", onClick: onContinue }, cellRenderer.t('merge_continue', locale)))));
|
|
134
|
+
const MergeConfirmStep = ({ locale, candidateLabel: bLabel, conflicts, autoFilled, resolutions, recordB, openHistoryKey, onToggleHistory, onBack, onConfirm }) => {
|
|
135
|
+
const changed = [
|
|
136
|
+
...autoFilled,
|
|
137
|
+
...conflicts.filter(({ item }) => { var _a; return resolutions[(_a = item.name) !== null && _a !== void 0 ? _a : ''] === 'B'; }),
|
|
138
|
+
];
|
|
139
|
+
return (index.h("div", { class: "mrd-merge-view__confirm-step" }, index.h("h3", { class: "mrd-merge-view__heading" }, cellRenderer.t('merge_confirm_heading', locale)), changed.length === 0 ? (index.h("p", { class: "mrd-merge-view__no-conflicts" }, cellRenderer.t('merge_no_conflicts', locale))) : (index.h("ul", { class: "mrd-merge-view__summary-list" }, changed.map(({ item }) => {
|
|
140
|
+
var _a;
|
|
141
|
+
const key = `${item.name}:confirm`;
|
|
142
|
+
return (index.h("li", { key: item.name }, (_a = item.label) !== null && _a !== void 0 ? _a : item.name, ": ", cellRenderer.CellRenderer.render(item, recordB, locale) || '—', index.h(HistoryBadge, { locale: locale, history: getHistory(item, recordB), isOpen: openHistoryKey === key, onToggle: () => onToggleHistory(openHistoryKey === key ? null : key) })));
|
|
143
|
+
}))), index.h("p", { class: "mrd-merge-view__warning" }, cellRenderer.t('merge_confirm_warning', locale), " (", bLabel, ")"), index.h("div", { class: "mrd-merge-view__actions" }, index.h("button", { type: "button", class: "mrd-merge-view__btn mrd-merge-view__btn--secondary", onClick: onBack }, cellRenderer.t('merge_back', locale)), index.h("button", { type: "button", class: "mrd-merge-view__btn mrd-merge-view__btn--danger", onClick: onConfirm }, cellRenderer.t('merge_confirm_button', locale)))));
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
const mrdMergeViewCss = () => `.sc-mrd-merge-view-h{display:block;width:100%}.mrd-merge-view.sc-mrd-merge-view{font-family:var(--mrd-font-family);font-size:var(--mrd-font-size-sm);color:var(--mrd-color-neutral-900)}.mrd-merge-view__hint.sc-mrd-merge-view{margin:0 0 var(--mrd-space-4);color:var(--mrd-color-neutral-600)}.mrd-merge-view__heading.sc-mrd-merge-view{margin:0 0 var(--mrd-space-4);font-size:var(--mrd-font-size-lg);font-weight:var(--mrd-font-weight-semibold)}.mrd-merge-view__search-wrapper.sc-mrd-merge-view{position:relative;display:flex;align-items:center}.mrd-merge-view__search.sc-mrd-merge-view{width:100%;height:var(--mrd-input-height);padding:var(--mrd-input-padding-y) var(--mrd-input-padding-x);border:var(--mrd-border-width) solid var(--mrd-border-color);border-radius:var(--mrd-border-radius);background:var(--mrd-input-bg);color:var(--mrd-input-color);font-size:var(--mrd-font-size-sm)}.mrd-merge-view__search.sc-mrd-merge-view:focus{outline:none;border-color:var(--mrd-border-color-focus);box-shadow:var(--mrd-shadow-focus)}.mrd-merge-view__spinner.sc-mrd-merge-view{position:absolute;right:var(--mrd-space-3);width:1rem;height:1rem;border:2px solid var(--mrd-color-neutral-300);border-top-color:var(--mrd-color-primary);border-radius:var(--mrd-border-radius-full);animation:mrd-merge-view-spin var(--mrd-transition-slow) linear infinite}.mrd-merge-view__results.sc-mrd-merge-view{margin-top:var(--mrd-space-2);border:var(--mrd-border-width) solid var(--mrd-border-color);border-radius:var(--mrd-border-radius);overflow:hidden}.mrd-merge-view__no-results.sc-mrd-merge-view{padding:var(--mrd-space-3) var(--mrd-space-4);color:var(--mrd-color-neutral-500)}.mrd-merge-view__result-item.sc-mrd-merge-view{display:flex;align-items:center;justify-content:space-between;gap:var(--mrd-space-3);padding:var(--mrd-space-2) var(--mrd-space-4)}.mrd-merge-view__result-item.sc-mrd-merge-view:not(:last-child){border-bottom:var(--mrd-border-width) solid var(--mrd-border-color)}.mrd-merge-view__result-label.sc-mrd-merge-view{font-weight:var(--mrd-font-weight-medium)}.mrd-merge-view__no-conflicts.sc-mrd-merge-view{color:var(--mrd-color-neutral-600)}.mrd-merge-view__table.sc-mrd-merge-view{width:100%;border-collapse:collapse;margin-bottom:var(--mrd-space-4)}.mrd-merge-view__header.sc-mrd-merge-view{text-align:left;padding:var(--mrd-space-2) var(--mrd-space-3);border-bottom:2px solid var(--mrd-border-color);font-size:var(--mrd-font-size-xs);text-transform:uppercase;letter-spacing:0.04em;color:var(--mrd-color-neutral-600)}.mrd-merge-view__header-name.sc-mrd-merge-view{display:block;text-transform:none;letter-spacing:normal;font-size:var(--mrd-font-size-sm);font-weight:var(--mrd-font-weight-medium);color:var(--mrd-color-neutral-900);margin-top:var(--mrd-space-1)}.mrd-merge-view__row.sc-mrd-merge-view:nth-child(even){background:var(--mrd-color-neutral-50)}.mrd-merge-view__cell.sc-mrd-merge-view{padding:var(--mrd-space-2) var(--mrd-space-3);border-bottom:var(--mrd-border-width) solid var(--mrd-border-color);vertical-align:top}.mrd-merge-view__cell--label.sc-mrd-merge-view{font-weight:var(--mrd-font-weight-medium);white-space:nowrap}.mrd-merge-view__option.sc-mrd-merge-view{display:flex;align-items:flex-start;gap:var(--mrd-space-2);cursor:pointer}.mrd-merge-view__option.sc-mrd-merge-view input[type=radio].sc-mrd-merge-view{margin-top:0.2em}.mrd-merge-view__history-wrap.sc-mrd-merge-view{position:relative;display:inline-flex;align-items:center;margin-left:var(--mrd-space-1)}.mrd-merge-view__history-btn.sc-mrd-merge-view{display:inline-flex;align-items:center;justify-content:center;width:1.25rem;height:1.25rem;padding:0;background:transparent;border:none;border-radius:var(--mrd-border-radius-sm);color:var(--mrd-color-neutral-400);cursor:pointer;flex-shrink:0;transition:color var(--mrd-transition-fast), background-color var(--mrd-transition-fast)}.mrd-merge-view__history-btn.sc-mrd-merge-view:hover{color:var(--mrd-color-neutral-700);background-color:var(--mrd-color-neutral-100)}.mrd-merge-view__history-btn.sc-mrd-merge-view:focus{outline:none;box-shadow:var(--mrd-shadow-focus)}.mrd-merge-view__history-icon.sc-mrd-merge-view{width:0.875rem;height:0.875rem}.mrd-merge-view__history-popover.sc-mrd-merge-view{position:absolute;top:calc(100% + 0.25rem);left:0;min-width:18rem;max-width:18rem;background-color:var(--mrd-color-white);border:var(--mrd-border-width) solid var(--mrd-border-color);border-radius:var(--mrd-border-radius-md);box-shadow:var(--mrd-shadow-md);padding:var(--mrd-space-1) 0;z-index:var(--mrd-z-dropdown)}.mrd-merge-view__history-entry.sc-mrd-merge-view{font-size:var(--mrd-font-size-sm);color:var(--mrd-color-neutral-700);padding:var(--mrd-space-2) var(--mrd-space-3);line-height:var(--mrd-line-height-normal)}.mrd-merge-view__history-entry.sc-mrd-merge-view:not(:last-child){border-bottom:var(--mrd-border-width) solid var(--mrd-color-neutral-100)}.mrd-merge-view__auto-note.sc-mrd-merge-view{margin-bottom:var(--mrd-space-4);padding:var(--mrd-space-3) var(--mrd-space-4);background:var(--mrd-color-neutral-50);border:var(--mrd-border-width) solid var(--mrd-border-color);border-radius:var(--mrd-border-radius);color:var(--mrd-color-neutral-700)}.mrd-merge-view__auto-note.sc-mrd-merge-view p.sc-mrd-merge-view{margin:0 0 var(--mrd-space-1);font-weight:var(--mrd-font-weight-medium)}.mrd-merge-view__auto-note.sc-mrd-merge-view ul.sc-mrd-merge-view{margin:0;padding-left:var(--mrd-space-5)}.mrd-merge-view__summary-list.sc-mrd-merge-view{margin:0 0 var(--mrd-space-4);padding-left:var(--mrd-space-5)}.mrd-merge-view__warning.sc-mrd-merge-view{padding:var(--mrd-space-3) var(--mrd-space-4);background:var(--mrd-color-danger-light);border:var(--mrd-border-width) solid var(--mrd-color-danger);border-radius:var(--mrd-border-radius);color:var(--mrd-color-danger-dark);margin-bottom:var(--mrd-space-4)}.mrd-merge-view__actions.sc-mrd-merge-view{display:flex;gap:var(--mrd-space-3)}.mrd-merge-view__btn.sc-mrd-merge-view{height:var(--mrd-input-height-sm);padding:0 var(--mrd-space-4);border-radius:var(--mrd-border-radius);border:var(--mrd-border-width) solid transparent;font-size:var(--mrd-font-size-sm);font-weight:var(--mrd-font-weight-medium);cursor:pointer;transition:background-color var(--mrd-transition-fast), border-color var(--mrd-transition-fast)}.mrd-merge-view__btn--primary.sc-mrd-merge-view{background:var(--mrd-color-primary);color:var(--mrd-color-white)}.mrd-merge-view__btn--primary.sc-mrd-merge-view:hover{background:var(--mrd-color-primary-hover)}.mrd-merge-view__btn--secondary.sc-mrd-merge-view{background:var(--mrd-color-white);border-color:var(--mrd-border-color);color:var(--mrd-color-neutral-700)}.mrd-merge-view__btn--secondary.sc-mrd-merge-view:hover{background:var(--mrd-color-neutral-50)}.mrd-merge-view__btn--danger.sc-mrd-merge-view{background:var(--mrd-color-danger);color:var(--mrd-color-white)}.mrd-merge-view__btn--danger.sc-mrd-merge-view:hover{background:var(--mrd-color-danger-dark)}@keyframes mrd-merge-view-spin{to{transform:rotate(360deg)}}`;
|
|
147
|
+
|
|
148
|
+
const MrdMergeView = class {
|
|
149
|
+
constructor(hostRef) {
|
|
150
|
+
index.registerInstance(this, hostRef);
|
|
151
|
+
this.mrdSearch = index.createEvent(this, "mrdSearch");
|
|
152
|
+
this.mrdMerge = index.createEvent(this, "mrdMerge");
|
|
153
|
+
this.locale = navigator.language;
|
|
154
|
+
/** Dot-notation class, passed through on mrdSearch (host may use it to pick the right search endpoint). */
|
|
155
|
+
this.basicType = '';
|
|
156
|
+
/** URL segment for the search endpoint; falls back to basicType when absent. */
|
|
157
|
+
this.mostSignificantClass = '';
|
|
158
|
+
/** Flat FIELD/RELATION items describing the basic type's schema (host flattens SECTION/GROUP, same as mrd-table's `columns`). */
|
|
159
|
+
this.fields = [];
|
|
160
|
+
/** Href of A (winner) — the object the merge screen was opened from. */
|
|
161
|
+
this.currentHref = '';
|
|
162
|
+
/** A's full raw record: flat fields + `_links`, the same row shape CellRenderer.render expects. */
|
|
163
|
+
this.currentRecord = {};
|
|
164
|
+
/** Display name for A, shown in the compare step's column header. */
|
|
165
|
+
this.currentLabel = '';
|
|
166
|
+
this.step = 'search';
|
|
167
|
+
this.searchQuery = '';
|
|
168
|
+
this.searchResults = [];
|
|
169
|
+
this.isLoading = false;
|
|
170
|
+
this.candidate = null;
|
|
171
|
+
this.resolutions = {};
|
|
172
|
+
/** Which history badge popover is open, keyed e.g. `legalName:A` / `legalName:confirm`. */
|
|
173
|
+
this.openHistoryKey = null;
|
|
174
|
+
this.historyClickOutside = null;
|
|
175
|
+
this.searchDebounce = null;
|
|
176
|
+
this.handleSearchInput = (query) => {
|
|
177
|
+
this.searchQuery = query;
|
|
178
|
+
if (this.searchDebounce)
|
|
179
|
+
clearTimeout(this.searchDebounce);
|
|
180
|
+
if (query.trim().length >= 2) {
|
|
181
|
+
this.isLoading = true;
|
|
182
|
+
this.searchDebounce = setTimeout(() => {
|
|
183
|
+
this.mrdSearch.emit({
|
|
184
|
+
query,
|
|
185
|
+
basicType: this.basicType,
|
|
186
|
+
mostSignificantClass: this.mostSignificantClass || undefined,
|
|
187
|
+
});
|
|
188
|
+
}, 300);
|
|
189
|
+
}
|
|
190
|
+
else {
|
|
191
|
+
this.searchResults = [];
|
|
192
|
+
this.isLoading = false;
|
|
193
|
+
}
|
|
194
|
+
};
|
|
195
|
+
this.handleSelect = (record) => {
|
|
196
|
+
this.candidate = record;
|
|
197
|
+
const { conflicts } = diffFields(this.fields, this.currentRecord, record);
|
|
198
|
+
const resolutions = {};
|
|
199
|
+
conflicts.forEach(({ item }) => { var _a; resolutions[(_a = item.name) !== null && _a !== void 0 ? _a : ''] = 'A'; });
|
|
200
|
+
this.resolutions = resolutions;
|
|
201
|
+
this.openHistoryKey = null;
|
|
202
|
+
this.step = 'compare';
|
|
203
|
+
};
|
|
204
|
+
this.handleChangeCandidate = () => {
|
|
205
|
+
this.candidate = null;
|
|
206
|
+
this.resolutions = {};
|
|
207
|
+
this.openHistoryKey = null;
|
|
208
|
+
this.step = 'search';
|
|
209
|
+
};
|
|
210
|
+
this.handleResolve = (name, side) => {
|
|
211
|
+
this.resolutions = Object.assign(Object.assign({}, this.resolutions), { [name]: side });
|
|
212
|
+
};
|
|
213
|
+
this.handleToggleHistory = (key) => {
|
|
214
|
+
this.openHistoryKey = key;
|
|
215
|
+
};
|
|
216
|
+
this.handleContinue = () => {
|
|
217
|
+
this.step = 'confirm';
|
|
218
|
+
};
|
|
219
|
+
this.handleBack = () => {
|
|
220
|
+
this.step = 'compare';
|
|
221
|
+
};
|
|
222
|
+
this.handleConfirm = () => {
|
|
223
|
+
var _a, _b, _c, _d;
|
|
224
|
+
if (!this.candidate)
|
|
225
|
+
return;
|
|
226
|
+
const diff = diffFields(this.fields, this.currentRecord, this.candidate);
|
|
227
|
+
const patch = buildPatch(diff, this.resolutions);
|
|
228
|
+
const loserHref = (_d = (_c = (_b = (_a = this.candidate) === 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 : '';
|
|
229
|
+
this.mrdMerge.emit({ winnerHref: this.currentHref, loserHref, patch });
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
connectedCallback() {
|
|
233
|
+
this.historyClickOutside = (e) => {
|
|
234
|
+
if (!this.el.contains(e.target))
|
|
235
|
+
this.openHistoryKey = null;
|
|
236
|
+
};
|
|
237
|
+
document.addEventListener('mousedown', this.historyClickOutside);
|
|
238
|
+
}
|
|
239
|
+
disconnectedCallback() {
|
|
240
|
+
if (this.historyClickOutside) {
|
|
241
|
+
document.removeEventListener('mousedown', this.historyClickOutside);
|
|
242
|
+
this.historyClickOutside = null;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* Stores search results, filtering out A itself regardless of what the
|
|
247
|
+
* host's search returns — A must never appear as a mergeable candidate.
|
|
248
|
+
*/
|
|
249
|
+
async setSearchResults(records) {
|
|
250
|
+
this.searchResults = (records !== null && records !== void 0 ? records : []).filter(r => { var _a, _b, _c; return ((_c = (_b = (_a = r === null || r === void 0 ? void 0 : r._links) === null || _a === void 0 ? void 0 : _a.self) === null || _b === void 0 ? void 0 : _b.href) !== null && _c !== void 0 ? _c : null) !== this.currentHref; });
|
|
251
|
+
this.isLoading = false;
|
|
252
|
+
}
|
|
253
|
+
async setLoading(loading) {
|
|
254
|
+
this.isLoading = loading;
|
|
255
|
+
}
|
|
256
|
+
render() {
|
|
257
|
+
var _a, _b;
|
|
258
|
+
let stepContent;
|
|
259
|
+
if (this.step === 'search') {
|
|
260
|
+
stepContent = (index.h(MergeSearchStep, { key: '4b79504f62f85274c9cd9681b6819c2318f4246a', locale: this.locale, searchQuery: this.searchQuery, searchResults: this.searchResults, isLoading: this.isLoading, onSearchInput: this.handleSearchInput, onSelect: this.handleSelect }));
|
|
261
|
+
}
|
|
262
|
+
else {
|
|
263
|
+
const diff = diffFields(this.fields, this.currentRecord, this.candidate);
|
|
264
|
+
const bLabel = candidateLabel(this.candidate);
|
|
265
|
+
stepContent = this.step === 'compare' ? (index.h(MergeCompareStep, { locale: this.locale, currentLabel: this.currentLabel, candidateLabel: bLabel, conflicts: diff.conflicts, autoFilled: diff.autoFilled, recordA: this.currentRecord, recordB: (_a = this.candidate) !== null && _a !== void 0 ? _a : {}, resolutions: this.resolutions, openHistoryKey: this.openHistoryKey, onResolve: this.handleResolve, onToggleHistory: this.handleToggleHistory, onChangeCandidate: this.handleChangeCandidate, onContinue: this.handleContinue })) : (index.h(MergeConfirmStep, { locale: this.locale, candidateLabel: bLabel, conflicts: diff.conflicts, autoFilled: diff.autoFilled, resolutions: this.resolutions, recordB: (_b = this.candidate) !== null && _b !== void 0 ? _b : {}, openHistoryKey: this.openHistoryKey, onToggleHistory: this.handleToggleHistory, onBack: this.handleBack, onConfirm: this.handleConfirm }));
|
|
266
|
+
}
|
|
267
|
+
return (index.h(index.Host, { key: 'f3fc046fa728646e2ff686d2eae30024da3121e1' }, index.h("div", { key: '55bef786973d22dce49be021cfb78588b119139c', class: "mrd-merge-view" }, stepContent)));
|
|
268
|
+
}
|
|
269
|
+
get el() { return index.getElement(this); }
|
|
270
|
+
};
|
|
271
|
+
MrdMergeView.style = mrdMergeViewCss();
|
|
272
|
+
|
|
273
|
+
exports.mrd_merge_view = MrdMergeView;
|
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
"components/form/mrd-field/mrd-field.js",
|
|
23
23
|
"components/form/mrd-form/mrd-form.js",
|
|
24
24
|
"components/layout/mrd-layout-section/mrd-layout-section.js",
|
|
25
|
+
"components/merge/mrd-merge-view/mrd-merge-view.js",
|
|
25
26
|
"components/table/mrd-table/mrd-table.js"
|
|
26
27
|
],
|
|
27
28
|
"mixins": [],
|
|
@@ -10,26 +10,38 @@
|
|
|
10
10
|
flex-direction: column;
|
|
11
11
|
gap: var(--mrd-space-6);
|
|
12
12
|
}
|
|
13
|
-
.mrd-document-
|
|
14
|
-
display:
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
13
|
+
.mrd-document-view__container {
|
|
14
|
+
display: flex;
|
|
15
|
+
flex-direction: column;
|
|
16
|
+
align-items: flex-start;
|
|
17
|
+
gap: var(--mrd-space-1);
|
|
18
|
+
margin-bottom: var(--mrd-space-6);
|
|
19
|
+
}
|
|
20
|
+
.mrd-document-view__container-link {
|
|
21
|
+
background: none;
|
|
22
|
+
border: none;
|
|
23
|
+
padding: 0;
|
|
22
24
|
font-family: var(--mrd-font-family);
|
|
23
25
|
font-size: var(--mrd-font-size-sm);
|
|
24
|
-
|
|
26
|
+
font-weight: var(--mrd-font-weight-semibold);
|
|
27
|
+
color: var(--mrd-color-primary);
|
|
25
28
|
cursor: pointer;
|
|
29
|
+
text-align: left;
|
|
26
30
|
}
|
|
27
|
-
.mrd-document-
|
|
28
|
-
|
|
31
|
+
.mrd-document-view__container-link:hover {
|
|
32
|
+
text-decoration: underline;
|
|
29
33
|
}
|
|
30
|
-
.mrd-document-
|
|
31
|
-
|
|
32
|
-
|
|
34
|
+
.mrd-document-view__folder {
|
|
35
|
+
display: inline-flex;
|
|
36
|
+
align-items: center;
|
|
37
|
+
gap: var(--mrd-space-1);
|
|
38
|
+
font-size: var(--mrd-font-size-xs);
|
|
39
|
+
color: var(--mrd-color-neutral-400);
|
|
40
|
+
word-break: break-word;
|
|
41
|
+
}
|
|
42
|
+
.mrd-document-view__folder-icon {
|
|
43
|
+
width: 0.75rem;
|
|
44
|
+
height: 0.75rem;
|
|
33
45
|
flex-shrink: 0;
|
|
34
46
|
}
|
|
35
47
|
.mrd-document-view__title {
|
|
@@ -48,17 +60,8 @@
|
|
|
48
60
|
.mrd-document-view__file-meta {
|
|
49
61
|
display: flex;
|
|
50
62
|
align-items: center;
|
|
51
|
-
gap: var(--mrd-space-4);
|
|
52
63
|
margin-bottom: var(--mrd-space-2);
|
|
53
64
|
}
|
|
54
|
-
.mrd-document-view__file-name {
|
|
55
|
-
flex: 1;
|
|
56
|
-
min-width: 0;
|
|
57
|
-
font-weight: var(--mrd-font-weight-semibold);
|
|
58
|
-
font-size: var(--mrd-font-size-sm);
|
|
59
|
-
color: var(--mrd-color-neutral-800);
|
|
60
|
-
word-break: break-word;
|
|
61
|
-
}
|
|
62
65
|
.mrd-document-view__download {
|
|
63
66
|
display: inline-flex;
|
|
64
67
|
align-items: center;
|
|
@@ -4,9 +4,10 @@ import { formatDate } from "../../../utils/format";
|
|
|
4
4
|
import { attachmentsFromRecord } from "../../../utils/document-attachments";
|
|
5
5
|
/**
|
|
6
6
|
* Archetype-driven single-document view. Given a `commons.document` binding and a
|
|
7
|
-
* data record, renders the document recognisably — title,
|
|
8
|
-
*
|
|
9
|
-
*
|
|
7
|
+
* data record, renders the document recognisably — title, date, a navigation
|
|
8
|
+
* link to the container (the dossier) presented like any other data-object link
|
|
9
|
+
* (primary-coloured text link), an optional muted folder path, a download button
|
|
10
|
+
* and summary — instead of a generic field form.
|
|
10
11
|
*
|
|
11
12
|
* The component keys on the slot→field map (`archetype.slots`) to locate values:
|
|
12
13
|
* value slots read `data[field]`; the `container` relation slot reads
|
|
@@ -59,16 +60,22 @@ export class MrdDocumentView {
|
|
|
59
60
|
const links = ((_d = (_c = this.data) === null || _c === void 0 ? void 0 : _c._links) !== null && _d !== void 0 ? _d : {});
|
|
60
61
|
return links[field];
|
|
61
62
|
}
|
|
63
|
+
/** Container link (styled like relation links in layout sections: primary text
|
|
64
|
+
* link) plus a muted, non-interactive folder path when present. */
|
|
62
65
|
renderContainer() {
|
|
63
66
|
const link = this.slotLink('container');
|
|
64
|
-
|
|
67
|
+
const hasLink = !!((link === null || link === void 0 ? void 0 : link.href) && link.name);
|
|
68
|
+
const folderRaw = this.slotValue('folder');
|
|
69
|
+
const folder = folderRaw != null && folderRaw !== '' ? String(folderRaw) : null;
|
|
70
|
+
if (!hasLink && !folder)
|
|
65
71
|
return null;
|
|
66
|
-
return (h("button", { class: "mrd-document-
|
|
72
|
+
return (h("div", { class: "mrd-document-view__container" }, hasLink && (h("button", { type: "button", class: "mrd-document-view__container-link", onClick: () => this.mrdNavigate.emit({ href: link.href, label: link.name }) }, link.name)), folder && (h("span", { class: "mrd-document-view__folder" }, h("svg", { class: "mrd-document-view__folder-icon", 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" })), folder))));
|
|
67
73
|
}
|
|
68
|
-
/**
|
|
69
|
-
*
|
|
74
|
+
/** Download button — the only file-related UI this component owns; the filename
|
|
75
|
+
* is deliberately not shown (rarely relevant, the title identifies the document)
|
|
76
|
+
* and inline preview rendering is entirely the host's responsibility. */
|
|
70
77
|
renderFileMeta(fileName, href) {
|
|
71
|
-
return (h("div", { class: "mrd-document-view__file-meta" },
|
|
78
|
+
return (h("div", { class: "mrd-document-view__file-meta" }, href && (h("button", { type: "button", class: "mrd-document-view__download", onClick: () => this.mrdDownload.emit({ href, fileName }) }, 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 3v12m0 0l-4-4m4 4l4-4M4 21h16" })), t('download', this.locale)))));
|
|
72
79
|
}
|
|
73
80
|
render() {
|
|
74
81
|
var _a, _b;
|
|
@@ -81,7 +88,7 @@ export class MrdDocumentView {
|
|
|
81
88
|
const fileRaw = this.slotValue('file');
|
|
82
89
|
const fileHref = (_a = fileRaw === null || fileRaw === void 0 ? void 0 : fileRaw.href) !== null && _a !== void 0 ? _a : '';
|
|
83
90
|
const fileName = (_b = fileRaw === null || fileRaw === void 0 ? void 0 : fileRaw.fileName) !== null && _b !== void 0 ? _b : (fileRaw ? String(fileRaw) : '');
|
|
84
|
-
return (h(Host, { key: '
|
|
91
|
+
return (h(Host, { key: 'd4e8d4a6c5c66677df3d4e80943fc4538d9a2be7' }, h("div", { key: 'c67bb1742590dc3ab3cec959f20dfa9030268411', class: "mrd-document-view" }, h("div", { key: '5da6e15d1f67990a9ee06bc07b717e5fa17a2ee7', class: "mrd-document-view__body" }, h("div", { key: '0fc38933add3fff48096c48d2fb6cd06fb5d1eac', class: "mrd-document-view__meta" }, title && h("h1", { key: 'e9ac8d99f9afae6b64dd7e6bd461c54b92a6ea39', class: "mrd-document-view__title" }, title), date && h("p", { key: '8591d77b300ed3c021cfa64b8e9564dee98199cd', class: "mrd-document-view__date" }, date), this.renderContainer(), fileRaw && this.renderFileMeta(fileName, fileHref), summary && (h("div", { key: '8a6d630afcf187fa86c9bab77d06d93695fd5c9f', class: "mrd-document-view__summary" }, h("div", { key: 'bd98e2cf740d08837accd8d260973d4e51cd24f7', class: "mrd-document-view__summary-body", innerHTML: summary }))))))));
|
|
85
92
|
}
|
|
86
93
|
static get is() { return "mrd-document-view"; }
|
|
87
94
|
static get encapsulation() { return "scoped"; }
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Host, h } from "@stencil/core";
|
|
2
|
+
import DOMPurify from "dompurify";
|
|
2
3
|
import { FieldLabel, FieldError, controlClass, requiredError } from "../field-helpers";
|
|
3
4
|
export class MrdTextareaField {
|
|
4
5
|
constructor() {
|
|
@@ -40,14 +41,12 @@ export class MrdTextareaField {
|
|
|
40
41
|
quill.clipboard.dangerouslyPasteHTML(this.value);
|
|
41
42
|
}
|
|
42
43
|
quill.on('text-change', () => {
|
|
43
|
-
|
|
44
|
-
const html = (_b = (_a = this.editorEl.querySelector('.ql-editor')) === null || _a === void 0 ? void 0 : _a.innerHTML) !== null && _b !== void 0 ? _b : '';
|
|
44
|
+
const html = this.getSanitizedHtml();
|
|
45
45
|
this.mrdChange.emit({ name: this.name, value: html });
|
|
46
46
|
});
|
|
47
47
|
quill.on('selection-change', (range) => {
|
|
48
|
-
var _a, _b;
|
|
49
48
|
if (!range) {
|
|
50
|
-
const html =
|
|
49
|
+
const html = this.getSanitizedHtml();
|
|
51
50
|
this.error = requiredError(html, this.required, this.locale);
|
|
52
51
|
this.mrdBlur.emit({ name: this.name, value: html });
|
|
53
52
|
}
|
|
@@ -59,8 +58,13 @@ export class MrdTextareaField {
|
|
|
59
58
|
console.error('Failed to load Quill editor:', err);
|
|
60
59
|
}
|
|
61
60
|
}
|
|
61
|
+
getSanitizedHtml() {
|
|
62
|
+
var _a, _b;
|
|
63
|
+
const html = (_b = (_a = this.editorEl.querySelector('.ql-editor')) === null || _a === void 0 ? void 0 : _a.innerHTML) !== null && _b !== void 0 ? _b : '';
|
|
64
|
+
return DOMPurify.sanitize(html);
|
|
65
|
+
}
|
|
62
66
|
render() {
|
|
63
|
-
return (h(Host, { key: '
|
|
67
|
+
return (h(Host, { key: '708c1f8f14d2de4b5a274155d190fc19a304d7f0' }, h("div", { key: 'cf5c7a571e74cad08e0e320e91db4e22a3b2e080', class: "mrd-textarea-field" }, h(FieldLabel, { key: '4ced05f3914440f89dc5605901fc311e8ad54360', base: "mrd-textarea-field", label: this.label, required: this.required }), h("div", { key: '81147829bc575d05f3521bf3704edfdfe0d9b2cf', class: controlClass('mrd-textarea-field', !!this.error, 'container') }, h("div", { key: 'a9f42d86cab460d5e0cf6de87e0c0a0a1ca7d77a', class: "mrd-textarea-field__editor" })), h(FieldError, { key: '9f4b5271d0901e474ef6c0f1170b3ef718a844a9', base: "mrd-textarea-field", error: this.error }))));
|
|
64
68
|
}
|
|
65
69
|
static get is() { return "mrd-textarea-field"; }
|
|
66
70
|
static get encapsulation() { return "scoped"; }
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { ClientLayoutItemType } from "../../../types/client-layout";
|
|
2
|
+
/**
|
|
3
|
+
* Whether a layout item can appear in the merge diff at all. m-n relations
|
|
4
|
+
* (`multiple: true`) are unioned automatically server-side (DataService.merge)
|
|
5
|
+
* and must never be surfaced as a user choice — only FIELD and single-value
|
|
6
|
+
* (n-1) RELATION items are diffable.
|
|
7
|
+
*/
|
|
8
|
+
export function isDiffable(item) {
|
|
9
|
+
if (item.type === ClientLayoutItemType.FIELD)
|
|
10
|
+
return true;
|
|
11
|
+
if (item.type === ClientLayoutItemType.RELATION)
|
|
12
|
+
return !item.multiple;
|
|
13
|
+
return false;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Raw value for a field/relation off a record in the API's row shape (flat
|
|
17
|
+
* fields + `_links`). History-enabled fields come back as `{ current,
|
|
18
|
+
* history: [...] }` — this returns that whole object as-is, since a winning
|
|
19
|
+
* B value must carry its entire history into the patch (FINDING-0114: "the
|
|
20
|
+
* entire history list follows whichever value wins ... never interleaved").
|
|
21
|
+
*/
|
|
22
|
+
export function getRawValue(item, record) {
|
|
23
|
+
var _a, _b, _c, _d, _e;
|
|
24
|
+
const name = (_a = item.name) !== null && _a !== void 0 ? _a : '';
|
|
25
|
+
if (item.type === ClientLayoutItemType.RELATION) {
|
|
26
|
+
return (_d = (_c = (_b = record === null || record === void 0 ? void 0 : record._links) === null || _b === void 0 ? void 0 : _b[name]) === null || _c === void 0 ? void 0 : _c.href) !== null && _d !== void 0 ? _d : null;
|
|
27
|
+
}
|
|
28
|
+
return (_e = record === null || record === void 0 ? void 0 : record[name]) !== null && _e !== void 0 ? _e : null;
|
|
29
|
+
}
|
|
30
|
+
/** Unwraps a history-enabled field's `{ current, history }` shape to its current value, for comparison only. */
|
|
31
|
+
function comparableValue(item, value) {
|
|
32
|
+
if (item.historyEnabled && value != null && typeof value === 'object' && !Array.isArray(value) && 'current' in value) {
|
|
33
|
+
return value.current;
|
|
34
|
+
}
|
|
35
|
+
return value;
|
|
36
|
+
}
|
|
37
|
+
function normalize(value) {
|
|
38
|
+
return value === undefined || value === '' ? null : value;
|
|
39
|
+
}
|
|
40
|
+
function valuesEqual(a, b) {
|
|
41
|
+
const na = normalize(a);
|
|
42
|
+
const nb = normalize(b);
|
|
43
|
+
if (na === nb)
|
|
44
|
+
return true;
|
|
45
|
+
if (na === null || nb === null)
|
|
46
|
+
return false;
|
|
47
|
+
return JSON.stringify(na) === JSON.stringify(nb);
|
|
48
|
+
}
|
|
49
|
+
function isEmpty(value) {
|
|
50
|
+
return normalize(value) === null;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Splits diffable fields into `conflicts` (both records have a — different —
|
|
54
|
+
* value, user must pick) and `autoFilled` (A has no value, B does; silently
|
|
55
|
+
* taken from B without a UI choice).
|
|
56
|
+
*/
|
|
57
|
+
export function diffFields(fields, recordA, recordB) {
|
|
58
|
+
const conflicts = [];
|
|
59
|
+
const autoFilled = [];
|
|
60
|
+
for (const item of fields.filter(isDiffable)) {
|
|
61
|
+
const aValue = getRawValue(item, recordA);
|
|
62
|
+
const bValue = getRawValue(item, recordB);
|
|
63
|
+
const aComparable = comparableValue(item, aValue);
|
|
64
|
+
const bComparable = comparableValue(item, bValue);
|
|
65
|
+
if (valuesEqual(aComparable, bComparable))
|
|
66
|
+
continue;
|
|
67
|
+
if (isEmpty(aComparable) && !isEmpty(bComparable)) {
|
|
68
|
+
autoFilled.push({ item, aValue, bValue });
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
conflicts.push({ item, aValue, bValue });
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return { conflicts, autoFilled };
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Builds the patch payload per the merge endpoint contract: every `autoFilled`
|
|
78
|
+
* entry is always included (A had nothing to lose); a `conflicts` entry is
|
|
79
|
+
* only included when the user explicitly resolved it to B — anything left
|
|
80
|
+
* unresolved keeps A's value by omission.
|
|
81
|
+
*/
|
|
82
|
+
export function buildPatch(diff, resolutions) {
|
|
83
|
+
var _a, _b, _c;
|
|
84
|
+
const patch = {};
|
|
85
|
+
for (const { item, bValue } of diff.autoFilled) {
|
|
86
|
+
patch[(_a = item.name) !== null && _a !== void 0 ? _a : ''] = bValue;
|
|
87
|
+
}
|
|
88
|
+
for (const { item, bValue } of diff.conflicts) {
|
|
89
|
+
if (resolutions[(_b = item.name) !== null && _b !== void 0 ? _b : ''] === 'B') {
|
|
90
|
+
patch[(_c = item.name) !== null && _c !== void 0 ? _c : ''] = bValue;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return patch;
|
|
94
|
+
}
|
|
95
|
+
/** Display label for a candidate record in the search results list. */
|
|
96
|
+
export function candidateLabel(record) {
|
|
97
|
+
var _a, _b, _c, _d, _e;
|
|
98
|
+
return (_e = (_b = (_a = record === null || record === void 0 ? void 0 : record.name) !== null && _a !== void 0 ? _a : record === null || record === void 0 ? void 0 : record.label) !== null && _b !== void 0 ? _b : (_d = (_c = record === null || record === void 0 ? void 0 : record._links) === null || _c === void 0 ? void 0 : _c.self) === null || _d === void 0 ? void 0 : _d.href) !== null && _e !== void 0 ? _e : '';
|
|
99
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { h } from "@stencil/core";
|
|
2
|
+
import { CellRenderer } from "../../../utils/cell-renderer";
|
|
3
|
+
import { t } from "../../../utils/i18n";
|
|
4
|
+
import { formatDate } from "../../../utils/format";
|
|
5
|
+
import { candidateLabel } from "./merge-diff";
|
|
6
|
+
/** History-enabled fields carry `{ current, history: [...] }` — this is the legal record itself (e.g. past company names), not an audit log. */
|
|
7
|
+
function getHistory(item, record) {
|
|
8
|
+
var _a, _b;
|
|
9
|
+
if (!item.historyEnabled || !item.name)
|
|
10
|
+
return [];
|
|
11
|
+
return (_b = (_a = record === null || record === void 0 ? void 0 : record[item.name]) === null || _a === void 0 ? void 0 : _a.history) !== null && _b !== void 0 ? _b : [];
|
|
12
|
+
}
|
|
13
|
+
/** Small clock badge + popover listing past values for a history-enabled field — same UX as mrd-layout-section's field view. */
|
|
14
|
+
const HistoryBadge = ({ locale, history, isOpen, onToggle }) => {
|
|
15
|
+
if (history.length === 0)
|
|
16
|
+
return null;
|
|
17
|
+
const sorted = [...history].sort((a, b) => b.until.localeCompare(a.until));
|
|
18
|
+
return (h("span", { class: "mrd-merge-view__history-wrap" }, h("button", { type: "button", class: "mrd-merge-view__history-btn", title: t('history_badge_tooltip', locale), "aria-label": t('history_badge_tooltip', locale), onClick: (e) => { e.stopPropagation(); onToggle(); } }, h("svg", { class: "mrd-merge-view__history-icon", viewBox: "0 0 20 20", fill: "currentColor", "aria-hidden": "true" }, h("path", { "fill-rule": "evenodd", d: "M10 18a8 8 0 100-16 8 8 0 000 16zm.75-13a.75.75 0 00-1.5 0v5c0 .207.085.394.22.53l2.5 2.5a.75.75 0 101.06-1.06L10.75 9.69V5z", "clip-rule": "evenodd" }))), isOpen && (h("div", { class: "mrd-merge-view__history-popover", role: "listbox" }, sorted.map((entry, i) => (h("div", { key: String(i), class: "mrd-merge-view__history-entry" }, entry.value, " (", t('history_until', locale), " ", formatDate(entry.until, locale), ")")))))));
|
|
19
|
+
};
|
|
20
|
+
export const MergeSearchStep = ({ locale, searchQuery, searchResults, isLoading, onSearchInput, onSelect }) => (h("div", { class: "mrd-merge-view__search-step" }, h("p", { class: "mrd-merge-view__hint" }, t('merge_search_hint', locale)), h("div", { class: "mrd-merge-view__search-wrapper" }, h("input", { class: "mrd-merge-view__search", type: "text", value: searchQuery, placeholder: t('search_placeholder', locale), onInput: (e) => onSearchInput(e.target.value) }), isLoading && h("span", { class: "mrd-merge-view__spinner", "aria-label": t('loading', locale) })), searchQuery.trim().length >= 2 && !isLoading && (h("div", { class: "mrd-merge-view__results" }, searchResults.length === 0 ? (h("div", { class: "mrd-merge-view__no-results" }, t('no_results', locale))) : (searchResults.map(record => {
|
|
21
|
+
var _a, _b, _c;
|
|
22
|
+
return (h("div", { key: (_c = (_b = (_a = record === null || record === void 0 ? void 0 : record._links) === null || _a === void 0 ? void 0 : _a.self) === null || _b === void 0 ? void 0 : _b.href) !== null && _c !== void 0 ? _c : candidateLabel(record), class: "mrd-merge-view__result-item" }, h("span", { class: "mrd-merge-view__result-label" }, candidateLabel(record)), h("button", { type: "button", class: "mrd-merge-view__btn mrd-merge-view__btn--secondary", onClick: () => onSelect(record) }, t('merge_select', locale))));
|
|
23
|
+
}))))));
|
|
24
|
+
export const MergeCompareStep = ({ locale, currentLabel, candidateLabel: bLabel, conflicts, autoFilled, recordA, recordB, resolutions, openHistoryKey, onResolve, onToggleHistory, onChangeCandidate, onContinue }) => (h("div", { class: "mrd-merge-view__compare-step" }, h("h3", { class: "mrd-merge-view__heading" }, t('merge_compare_heading', locale)), conflicts.length === 0 ? (h("p", { class: "mrd-merge-view__no-conflicts" }, t('merge_no_conflicts', locale))) : (h("table", { class: "mrd-merge-view__table" }, h("thead", null, h("tr", null, h("th", { class: "mrd-merge-view__header" }), h("th", { class: "mrd-merge-view__header" }, t('merge_column_a', locale), h("span", { class: "mrd-merge-view__header-name" }, currentLabel)), h("th", { class: "mrd-merge-view__header" }, t('merge_column_b', locale), h("span", { class: "mrd-merge-view__header-name" }, bLabel)))), h("tbody", null, conflicts.map(({ item }) => {
|
|
25
|
+
var _a, _b, _c;
|
|
26
|
+
const name = (_a = item.name) !== null && _a !== void 0 ? _a : '';
|
|
27
|
+
const resolution = (_b = resolutions[name]) !== null && _b !== void 0 ? _b : 'A';
|
|
28
|
+
const historyA = getHistory(item, recordA);
|
|
29
|
+
const historyB = getHistory(item, recordB);
|
|
30
|
+
const keyA = `${name}:A`;
|
|
31
|
+
const keyB = `${name}:B`;
|
|
32
|
+
return (h("tr", { key: name, class: "mrd-merge-view__row" }, h("td", { class: "mrd-merge-view__cell mrd-merge-view__cell--label" }, (_c = item.label) !== null && _c !== void 0 ? _c : name), h("td", { class: "mrd-merge-view__cell" }, h("label", { class: "mrd-merge-view__option" }, h("input", { type: "radio", name: `merge-${name}`, checked: resolution === 'A', onChange: () => onResolve(name, 'A') }), h("span", null, CellRenderer.render(item, recordA, locale) || '—')), h(HistoryBadge, { locale: locale, history: historyA, isOpen: openHistoryKey === keyA, onToggle: () => onToggleHistory(openHistoryKey === keyA ? null : keyA) })), h("td", { class: "mrd-merge-view__cell" }, h("label", { class: "mrd-merge-view__option" }, h("input", { type: "radio", name: `merge-${name}`, checked: resolution === 'B', onChange: () => onResolve(name, 'B') }), h("span", null, CellRenderer.render(item, recordB, locale) || '—')), h(HistoryBadge, { locale: locale, history: historyB, isOpen: openHistoryKey === keyB, onToggle: () => onToggleHistory(openHistoryKey === keyB ? null : keyB) }))));
|
|
33
|
+
})))), autoFilled.length > 0 && (h("div", { class: "mrd-merge-view__auto-note" }, h("p", null, t('merge_auto_heading', locale)), h("ul", null, autoFilled.map(({ item }) => { var _a; return h("li", { key: item.name }, (_a = item.label) !== null && _a !== void 0 ? _a : item.name); })))), h("div", { class: "mrd-merge-view__actions" }, h("button", { type: "button", class: "mrd-merge-view__btn mrd-merge-view__btn--secondary", onClick: onChangeCandidate }, t('merge_back', locale)), h("button", { type: "button", class: "mrd-merge-view__btn mrd-merge-view__btn--primary", onClick: onContinue }, t('merge_continue', locale)))));
|
|
34
|
+
export const MergeConfirmStep = ({ locale, candidateLabel: bLabel, conflicts, autoFilled, resolutions, recordB, openHistoryKey, onToggleHistory, onBack, onConfirm }) => {
|
|
35
|
+
const changed = [
|
|
36
|
+
...autoFilled,
|
|
37
|
+
...conflicts.filter(({ item }) => { var _a; return resolutions[(_a = item.name) !== null && _a !== void 0 ? _a : ''] === 'B'; }),
|
|
38
|
+
];
|
|
39
|
+
return (h("div", { class: "mrd-merge-view__confirm-step" }, h("h3", { class: "mrd-merge-view__heading" }, t('merge_confirm_heading', locale)), changed.length === 0 ? (h("p", { class: "mrd-merge-view__no-conflicts" }, t('merge_no_conflicts', locale))) : (h("ul", { class: "mrd-merge-view__summary-list" }, changed.map(({ item }) => {
|
|
40
|
+
var _a;
|
|
41
|
+
const key = `${item.name}:confirm`;
|
|
42
|
+
return (h("li", { key: item.name }, (_a = item.label) !== null && _a !== void 0 ? _a : item.name, ": ", CellRenderer.render(item, recordB, locale) || '—', h(HistoryBadge, { locale: locale, history: getHistory(item, recordB), isOpen: openHistoryKey === key, onToggle: () => onToggleHistory(openHistoryKey === key ? null : key) })));
|
|
43
|
+
}))), h("p", { class: "mrd-merge-view__warning" }, t('merge_confirm_warning', locale), " (", bLabel, ")"), h("div", { class: "mrd-merge-view__actions" }, h("button", { type: "button", class: "mrd-merge-view__btn mrd-merge-view__btn--secondary", onClick: onBack }, t('merge_back', locale)), h("button", { type: "button", class: "mrd-merge-view__btn mrd-merge-view__btn--danger", onClick: onConfirm }, t('merge_confirm_button', locale)))));
|
|
44
|
+
};
|