@mmlogic/components 0.5.3 → 0.5.5
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-7KX9Qbml.js → cell-renderer-Mx3pwONr.js} +261 -0
- package/dist/cjs/loader.cjs.js +1 -1
- package/dist/cjs/mosterdcomponents.cjs.js +1 -1
- package/dist/cjs/mrd-boolean-field_25.cjs.entry.js +391 -77
- package/dist/cjs/mrd-merge-view.cjs.entry.js +3 -3
- package/dist/collection/components/documents/mrd-document-container/mrd-document-container.css +3 -0
- package/dist/collection/components/documents/mrd-document-container/mrd-document-container.js +23 -3
- package/dist/collection/components/documents/mrd-document-list/mrd-document-list.js +76 -2
- package/dist/collection/components/fields/mrd-currency-field/mrd-currency-field.js +56 -5
- package/dist/collection/components/fields/mrd-date-field/mrd-date-field.css +55 -4
- package/dist/collection/components/fields/mrd-date-field/mrd-date-field.js +118 -8
- package/dist/collection/components/fields/mrd-datetime-field/mrd-datetime-field.css +55 -4
- package/dist/collection/components/fields/mrd-datetime-field/mrd-datetime-field.js +117 -15
- package/dist/collection/components/fields/mrd-email-field/mrd-email-field.js +1 -1
- package/dist/collection/components/fields/mrd-file-field/mrd-file-field.js +1 -1
- package/dist/collection/components/fields/mrd-image-field/mrd-image-field.js +1 -1
- package/dist/collection/components/fields/mrd-longtext-field/mrd-longtext-field.js +1 -1
- package/dist/collection/components/fields/mrd-number-field/mrd-number-field.js +33 -8
- package/dist/collection/components/fields/mrd-relation-field/mrd-relation-field.js +13 -3
- package/dist/collection/components/fields/mrd-secret-field/mrd-secret-field.js +2 -2
- package/dist/collection/components/fields/mrd-text-field/mrd-text-field.js +1 -1
- package/dist/collection/components/fields/mrd-textarea-field/mrd-textarea-field.js +1 -1
- package/dist/collection/components/fields/mrd-time-field/mrd-time-field.js +1 -1
- package/dist/collection/components/form/mrd-field/mrd-field.js +11 -11
- package/dist/collection/components/form/mrd-form/mrd-form.js +8 -4
- package/dist/collection/components/invoice/mrd-invoice-view/mrd-invoice-view.js +1 -1
- package/dist/collection/components/layout/mrd-layout-section/field-value.js +4 -2
- package/dist/collection/components/layout/mrd-layout-section/layout-helpers.js +6 -0
- package/dist/collection/components/layout/mrd-layout-section/mrd-layout-section.css +4 -0
- package/dist/collection/components/layout/mrd-layout-section/mrd-layout-section.js +8 -8
- package/dist/collection/components/merge/mrd-merge-view/mrd-merge-view.js +2 -2
- package/dist/collection/dev/app.js +58 -13
- package/dist/collection/dev/example-data.js +6 -1
- package/dist/collection/utils/format.js +160 -0
- package/dist/collection/utils/i18n.js +96 -0
- package/dist/components/cell-renderer.js +1 -1
- package/dist/components/format.js +1 -1
- package/dist/components/i18n.js +1 -1
- package/dist/components/mrd-currency-field2.js +1 -1
- package/dist/components/mrd-date-field2.js +1 -1
- package/dist/components/mrd-datetime-field2.js +1 -1
- package/dist/components/mrd-document-container2.js +1 -1
- package/dist/components/mrd-document-list2.js +1 -1
- package/dist/components/mrd-document-view2.js +1 -1
- package/dist/components/mrd-email-field2.js +1 -1
- package/dist/components/mrd-email-view2.js +1 -1
- package/dist/components/mrd-field2.js +1 -1
- package/dist/components/mrd-file-field2.js +1 -1
- package/dist/components/mrd-form.js +1 -1
- package/dist/components/mrd-image-field2.js +1 -1
- package/dist/components/mrd-invoice-view2.js +1 -1
- package/dist/components/mrd-layout-section.js +1 -1
- package/dist/components/mrd-longtext-field2.js +1 -1
- package/dist/components/mrd-merge-view.js +1 -1
- package/dist/components/mrd-number-field2.js +1 -1
- package/dist/components/mrd-relation-field2.js +1 -1
- package/dist/components/mrd-secret-field2.js +1 -1
- package/dist/components/mrd-table2.js +1 -1
- package/dist/components/mrd-text-field2.js +1 -1
- package/dist/components/mrd-textarea-field2.js +1 -1
- package/dist/components/mrd-time-field2.js +1 -1
- package/dist/esm/{cell-renderer-DIsNX9D7.js → cell-renderer-UQOYlDv7.js} +257 -1
- package/dist/esm/loader.js +1 -1
- package/dist/esm/mosterdcomponents.js +1 -1
- package/dist/esm/mrd-boolean-field_25.entry.js +391 -77
- package/dist/esm/mrd-merge-view.entry.js +3 -3
- package/dist/mosterdcomponents/mosterdcomponents.esm.js +1 -1
- package/dist/mosterdcomponents/p-8cc374e0.entry.js +3 -0
- package/dist/mosterdcomponents/p-CF--ScWr.js +1 -0
- package/dist/mosterdcomponents/{p-4e061416.entry.js → p-d31b71d9.entry.js} +1 -1
- package/dist/types/components/documents/mrd-document-container/mrd-document-container.d.ts +7 -0
- package/dist/types/components/documents/mrd-document-list/mrd-document-list.d.ts +17 -0
- package/dist/types/components/fields/mrd-currency-field/mrd-currency-field.d.ts +13 -0
- package/dist/types/components/fields/mrd-date-field/mrd-date-field.d.ts +22 -1
- package/dist/types/components/fields/mrd-datetime-field/mrd-datetime-field.d.ts +21 -2
- package/dist/types/components/fields/mrd-number-field/mrd-number-field.d.ts +6 -0
- package/dist/types/components/fields/mrd-relation-field/mrd-relation-field.d.ts +7 -0
- package/dist/types/components/form/mrd-form/mrd-form.d.ts +3 -2
- package/dist/types/components/layout/mrd-layout-section/layout-helpers.d.ts +3 -0
- package/dist/types/components.d.ts +35 -0
- package/dist/types/types/client-layout.d.ts +7 -0
- package/dist/types/utils/format.d.ts +34 -0
- package/package.json +1 -1
- package/dist/mosterdcomponents/p-910ebfee.entry.js +0 -3
- package/dist/mosterdcomponents/p-Ce2QDq0t.js +0 -1
|
@@ -3,6 +3,7 @@ import { ClientLayoutItemFieldDataType } from "../../../types/client-layout";
|
|
|
3
3
|
import { CellRenderer } from "../../../utils/cell-renderer";
|
|
4
4
|
import { t } from "../../../utils/i18n";
|
|
5
5
|
import { formatDate } from "../../../utils/format";
|
|
6
|
+
import { showsEmpty } from "./layout-helpers";
|
|
6
7
|
/** FILE and IMAGE cells share the same download button. */
|
|
7
8
|
function downloadLink(ctx, href, fileName, label) {
|
|
8
9
|
return (h("button", { class: "mrd-layout-section__download-link", onClick: () => href && ctx.on.download({ href, fileName }) }, h("svg", { class: "mrd-layout-section__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" })), label));
|
|
@@ -106,10 +107,11 @@ export function renderField(ctx, item) {
|
|
|
106
107
|
if (item.header) {
|
|
107
108
|
return (h("h1", { class: "mrd-layout-section__field-header", key: item.name }, typeof renderedValue === 'string' ? renderedValue : rawValue != null ? String(rawValue) : item.label));
|
|
108
109
|
}
|
|
109
|
-
|
|
110
|
+
const isEmpty = renderedValue == null;
|
|
111
|
+
if (isEmpty && !showsEmpty(item))
|
|
110
112
|
return null;
|
|
111
113
|
const isBlock = item.dataType === ClientLayoutItemFieldDataType.TEXTBLOCK
|
|
112
114
|
|| item.dataType === ClientLayoutItemFieldDataType.LONGTEXT
|
|
113
115
|
|| item.dataType === ClientLayoutItemFieldDataType.JSON;
|
|
114
|
-
return (h("div", { class: `mrd-layout-section__field${isBlock ? ' mrd-layout-section__field--block' : ''}`, key: item.name }, h("span", { class: "mrd-layout-section__field-label" }, item.label), h("span", { class: "mrd-layout-section__field-value" }, renderedValue, renderHistoryBadge(ctx, item, raw))));
|
|
116
|
+
return (h("div", { class: `mrd-layout-section__field${isBlock ? ' mrd-layout-section__field--block' : ''}${isEmpty ? ' mrd-layout-section__field--empty' : ''}`, key: item.name }, h("span", { class: "mrd-layout-section__field-label" }, item.label), h("span", { class: "mrd-layout-section__field-value" }, isEmpty ? t('value_empty', ctx.locale) : renderedValue, renderHistoryBadge(ctx, item, raw))));
|
|
115
117
|
}
|
|
@@ -6,6 +6,12 @@ export function viewKeyFor(item) {
|
|
|
6
6
|
return (_b = (_a = item.relatedClass) !== null && _a !== void 0 ? _a : item.name) !== null && _b !== void 0 ? _b : '';
|
|
7
7
|
return (_d = (_c = item.dataClass) !== null && _c !== void 0 ? _c : item.name) !== null && _d !== void 0 ? _d : '';
|
|
8
8
|
}
|
|
9
|
+
/** Whether a FIELD/RELATION item stays visible on an OBJECT_DASHBOARD when it
|
|
10
|
+
* has no value. Reads the flat API flag, falling back to the nested wrappers. */
|
|
11
|
+
export function showsEmpty(item) {
|
|
12
|
+
var _a, _b, _c, _d, _e;
|
|
13
|
+
return (_e = (_c = (_a = item.showEmpty) !== null && _a !== void 0 ? _a : (_b = item.field) === null || _b === void 0 ? void 0 : _b.showEmpty) !== null && _c !== void 0 ? _c : (_d = item.relation) === null || _d === void 0 ? void 0 : _d.showEmpty) !== null && _e !== void 0 ? _e : false;
|
|
14
|
+
}
|
|
9
15
|
/** Depth-first flatten of the SECTION/GROUP tree. */
|
|
10
16
|
export function flattenItems(items) {
|
|
11
17
|
const result = [];
|
|
@@ -33,6 +33,10 @@
|
|
|
33
33
|
align-items: center;
|
|
34
34
|
gap: var(--mrd-space-1);
|
|
35
35
|
}
|
|
36
|
+
.mrd-layout-section__field--empty .mrd-layout-section__field-value {
|
|
37
|
+
color: var(--mrd-color-neutral-400);
|
|
38
|
+
font-weight: var(--mrd-font-weight-normal);
|
|
39
|
+
}
|
|
36
40
|
.mrd-layout-section__history-wrap {
|
|
37
41
|
position: relative;
|
|
38
42
|
display: inline-flex;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Host, h } from "@stencil/core";
|
|
2
2
|
import { ClientLayoutItemType, ClientLayoutItemFieldDataType, ClientLayoutNavigateBehaviour, resolveArchetype, ARCHETYPE_DOCUMENT, ARCHETYPE_INVOICE, ARCHETYPE_EMAIL, ARCHETYPE_INVOICE_LINE, } from "../../../types/client-layout";
|
|
3
|
-
import {
|
|
3
|
+
import { t } from "../../../utils/i18n";
|
|
4
|
+
import { viewKeyFor, flattenItems, buildContainerHref, showsEmpty } from "./layout-helpers";
|
|
4
5
|
import { renderField } from "./field-value";
|
|
5
6
|
export class MrdLayoutSection {
|
|
6
7
|
constructor() {
|
|
@@ -173,19 +174,18 @@ export class MrdLayoutSection {
|
|
|
173
174
|
return null;
|
|
174
175
|
const links = ((_b = (_a = this.data) === null || _a === void 0 ? void 0 : _a._links) !== null && _b !== void 0 ? _b : {});
|
|
175
176
|
const link = links[item.name];
|
|
176
|
-
if (!link)
|
|
177
|
-
return null;
|
|
178
177
|
const makeBtn = (href, name) => (h("button", { key: href, class: "mrd-layout-section__relation-link", onClick: () => this.mrdNavigate.emit({ href, label: name }) }, name));
|
|
179
178
|
let valueContent;
|
|
180
|
-
if ((_c = link.values) === null || _c === void 0 ? void 0 : _c.length) {
|
|
179
|
+
if ((_c = link === null || link === void 0 ? void 0 : link.values) === null || _c === void 0 ? void 0 : _c.length) {
|
|
181
180
|
valueContent = link.values.map(v => makeBtn(v.href, v.name));
|
|
182
181
|
}
|
|
183
|
-
else if (link.name) {
|
|
182
|
+
else if (link === null || link === void 0 ? void 0 : link.name) {
|
|
184
183
|
valueContent = makeBtn(link.href, link.name);
|
|
185
184
|
}
|
|
186
|
-
|
|
185
|
+
const isEmpty = !valueContent;
|
|
186
|
+
if (isEmpty && !showsEmpty(item))
|
|
187
187
|
return null;
|
|
188
|
-
return (h("div", { class:
|
|
188
|
+
return (h("div", { class: `mrd-layout-section__field${isEmpty ? ' mrd-layout-section__field--empty' : ''}`, key: item.name }, h("span", { class: "mrd-layout-section__field-label" }, item.label), h("span", { class: "mrd-layout-section__field-value" }, isEmpty ? t('value_empty', this.locale) : valueContent)));
|
|
189
189
|
}
|
|
190
190
|
renderSearch(item) {
|
|
191
191
|
var _a, _b, _c;
|
|
@@ -359,7 +359,7 @@ export class MrdLayoutSection {
|
|
|
359
359
|
const docArchetype = resolveArchetype(this.archetypes, ARCHETYPE_DOCUMENT);
|
|
360
360
|
const invoiceArchetype = !docArchetype ? resolveArchetype(this.archetypes, ARCHETYPE_INVOICE) : undefined;
|
|
361
361
|
const emailArchetype = !docArchetype && !invoiceArchetype ? resolveArchetype(this.archetypes, ARCHETYPE_EMAIL) : undefined;
|
|
362
|
-
return (h(Host, { key: '
|
|
362
|
+
return (h(Host, { key: '8c4e0a0e71d777295010702584b4aa8f7817cafb' }, h("div", { key: 'fe021021eba48acad3d52ee4849ebe355e83c348', class: "mrd-layout-section" }, docArchetype && this.renderDocumentObject(docArchetype), invoiceArchetype && this.renderInvoiceObject(invoiceArchetype), emailArchetype && this.renderEmailObject(emailArchetype), !docArchetype && !invoiceArchetype && !emailArchetype && this.items.map(item => this.renderItem(item))), this.renderImageModal()));
|
|
363
363
|
}
|
|
364
364
|
static get is() { return "mrd-layout-section"; }
|
|
365
365
|
static get encapsulation() { return "scoped"; }
|
|
@@ -121,14 +121,14 @@ export class MrdMergeView {
|
|
|
121
121
|
var _a, _b;
|
|
122
122
|
let stepContent;
|
|
123
123
|
if (this.step === 'search') {
|
|
124
|
-
stepContent = (h(MergeSearchStep, { key: '
|
|
124
|
+
stepContent = (h(MergeSearchStep, { key: 'b965c21176faf7844e58cb156958fa1305d07cdc', locale: this.locale, searchQuery: this.searchQuery, searchResults: this.searchResults, isLoading: this.isLoading, onSearchInput: this.handleSearchInput, onSelect: this.handleSelect }));
|
|
125
125
|
}
|
|
126
126
|
else {
|
|
127
127
|
const diff = diffFields(this.fields, this.currentRecord, this.candidate);
|
|
128
128
|
const bLabel = candidateLabel(this.candidate);
|
|
129
129
|
stepContent = this.step === 'compare' ? (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 })) : (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 }));
|
|
130
130
|
}
|
|
131
|
-
return (h(Host, { key: '
|
|
131
|
+
return (h(Host, { key: 'e9cffda6c5cff51f802b044f3d8e417df83b7846' }, h("div", { key: '26690ef2db7489cbf7994191ad19491a0fdd7002', class: "mrd-merge-view" }, stepContent)));
|
|
132
132
|
}
|
|
133
133
|
static get is() { return "mrd-merge-view"; }
|
|
134
134
|
static get encapsulation() { return "scoped"; }
|
|
@@ -134,7 +134,7 @@ async function initDetailViewTab() {
|
|
|
134
134
|
table.item = window.EXAMPLE_COMPANY_VIEW_ITEM;
|
|
135
135
|
table.totalElements = window.EXAMPLE_COMPANIES.length;
|
|
136
136
|
table.pageSize = 20;
|
|
137
|
-
table.locale =
|
|
137
|
+
table.locale = _locale;
|
|
138
138
|
|
|
139
139
|
// Register before init(): init() emits mrdLoadPage for page 0 itself.
|
|
140
140
|
table.addEventListener('mrdLoadPage', async (e) => {
|
|
@@ -174,7 +174,7 @@ function showCompanyDetail(row) {
|
|
|
174
174
|
const section = document.createElement('mrd-layout-section');
|
|
175
175
|
section.items = layout.items;
|
|
176
176
|
section.data = company;
|
|
177
|
-
section.locale =
|
|
177
|
+
section.locale = _locale;
|
|
178
178
|
|
|
179
179
|
section.addEventListener('mrdNavigate', (e) => {
|
|
180
180
|
logEvent('mrdNavigate', e.detail);
|
|
@@ -1136,16 +1136,57 @@ customElements.whenDefined('mrd-form').then(() => {
|
|
|
1136
1136
|
form.addEventListener('mrdFetchAll', (e) => { console.log('[mrdFetchAll]', e.detail); logEvent('mrdFetchAll', e.detail); });
|
|
1137
1137
|
});
|
|
1138
1138
|
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1139
|
+
/* =====================================================================
|
|
1140
|
+
LOCALE CONTROL
|
|
1141
|
+
===================================================================== */
|
|
1142
|
+
|
|
1143
|
+
/** Every component that takes a `locale` prop — the control drives all of them. */
|
|
1144
|
+
const LOCALE_HOSTS = [
|
|
1145
|
+
'mrd-form', 'mrd-layout-section', 'mrd-table', 'mrd-merge-view',
|
|
1146
|
+
'mrd-document-container', 'mrd-document-list', 'mrd-document-view',
|
|
1147
|
+
'mrd-invoice-view', 'mrd-email-view',
|
|
1148
|
+
].join(', ');
|
|
1149
|
+
|
|
1150
|
+
/** Single source of truth: push the selected locale into everything on the page. */
|
|
1151
|
+
function applyLocale(locale) {
|
|
1152
|
+
_locale = locale;
|
|
1153
|
+
document.querySelectorAll(LOCALE_HOSTS).forEach((el) => { el.locale = locale; });
|
|
1154
|
+
renderLocaleHint();
|
|
1155
|
+
}
|
|
1156
|
+
|
|
1157
|
+
/**
|
|
1158
|
+
* Shows what the selected locale does next to what the browser would do on its
|
|
1159
|
+
* own. Those two differ exactly where the `locale` prop earns its keep — the
|
|
1160
|
+
* date fields format by prop, a native date input would follow the browser.
|
|
1161
|
+
*/
|
|
1162
|
+
function renderLocaleHint() {
|
|
1163
|
+
const hint = document.getElementById('locale-hint');
|
|
1164
|
+
if (!hint) return;
|
|
1165
|
+
const sample = new Date(2026, 6, 6, 14, 30); // 6 July — day > 12 keeps d/m order readable
|
|
1166
|
+
const format = (loc) => new Intl.DateTimeFormat(loc, {
|
|
1167
|
+
year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit',
|
|
1168
|
+
}).format(sample);
|
|
1169
|
+
|
|
1170
|
+
hint.innerHTML =
|
|
1171
|
+
`<span>prop <code>${escHtml(_locale)}</code> → <b>${escHtml(format(_locale))}</b></span>` +
|
|
1172
|
+
`<span>browser <code>${escHtml(navigator.language)}</code> → <b>${escHtml(format(navigator.language))}</b></span>`;
|
|
1173
|
+
}
|
|
1174
|
+
|
|
1175
|
+
function initLocaleControl() {
|
|
1176
|
+
const select = document.getElementById('locale-select');
|
|
1177
|
+
// The browser locale is worth testing too, even when it is not in the list.
|
|
1178
|
+
if (![...select.options].some((o) => o.value === _locale)) {
|
|
1179
|
+
const option = document.createElement('option');
|
|
1180
|
+
option.value = _locale;
|
|
1181
|
+
option.textContent = `🖥️ Browser (${_locale})`;
|
|
1182
|
+
select.insertBefore(option, select.firstChild);
|
|
1183
|
+
}
|
|
1184
|
+
select.value = _locale;
|
|
1185
|
+
select.addEventListener('change', (e) => applyLocale(e.target.value));
|
|
1186
|
+
applyLocale(_locale);
|
|
1187
|
+
}
|
|
1188
|
+
|
|
1189
|
+
initLocaleControl();
|
|
1149
1190
|
|
|
1150
1191
|
document.getElementById('btn-inject-results').addEventListener('click', () => {
|
|
1151
1192
|
const relationField = document.querySelector('mrd-relation-field');
|
|
@@ -1170,7 +1211,7 @@ document.addEventListener('DOMContentLoaded', async () => {
|
|
|
1170
1211
|
const ftSection = document.getElementById('field-types-section');
|
|
1171
1212
|
ftSection.items = window.EXAMPLE_FIELD_TYPES_ITEMS;
|
|
1172
1213
|
ftSection.data = window.EXAMPLE_FIELD_TYPES_DATA;
|
|
1173
|
-
ftSection.locale =
|
|
1214
|
+
ftSection.locale = _locale;
|
|
1174
1215
|
|
|
1175
1216
|
// Simulate host resolving a signed URL for IMAGE fields (thumbnail on load).
|
|
1176
1217
|
ftSection.addEventListener('mrdLoadImage', (e) => {
|
|
@@ -1197,6 +1238,10 @@ document.addEventListener('DOMContentLoaded', async () => {
|
|
|
1197
1238
|
ftSection.setSearchResults(mock, dataClass);
|
|
1198
1239
|
});
|
|
1199
1240
|
|
|
1241
|
+
// Re-apply once the whole page is parsed, so components mounted in markup
|
|
1242
|
+
// (and any upgraded after the control ran) all agree on the locale.
|
|
1243
|
+
applyLocale(_locale);
|
|
1244
|
+
|
|
1200
1245
|
authRestoreSession();
|
|
1201
1246
|
await authHandleCallback();
|
|
1202
1247
|
|
|
@@ -223,7 +223,9 @@ window.EXAMPLE_COMPANY_METADATA = {
|
|
|
223
223
|
{ type: 'FIELD', label: 'legal form extended', name: 'legalFormExtended', dataType: 'TEXT', required: false },
|
|
224
224
|
{ type: 'FIELD', label: 'rsin', name: 'rsin', dataType: 'TEXT', required: false },
|
|
225
225
|
{ type: 'FIELD', label: 'start date', name: 'startDate', dataType: 'DATE', required: false },
|
|
226
|
-
|
|
226
|
+
// showEmpty demo (TASK-0231): endDate is null on EXAMPLE_COMPANY, so it
|
|
227
|
+
// stays visible with a placeholder instead of being hidden.
|
|
228
|
+
{ type: 'FIELD', label: 'end date', name: 'endDate', dataType: 'DATE', required: false, showEmpty: true },
|
|
227
229
|
{ type: 'FIELD', label: 'primary activity description', name: 'primaryActivityDescription', dataType: 'TEXT', required: false },
|
|
228
230
|
{ type: 'FIELD', label: 'primary activity code', name: 'primaryActivityCode', dataType: 'TEXT', required: false },
|
|
229
231
|
{ type: 'FIELD', label: 'total employees', name: 'totalEmployees', dataType: 'INTEGER', required: false },
|
|
@@ -243,6 +245,9 @@ window.EXAMPLE_COMPANY_METADATA = {
|
|
|
243
245
|
{ type: 'FIELD', label: 'mailing postal code', name: 'mailingPostalCode', dataType: 'TEXT', required: false },
|
|
244
246
|
{ type: 'FIELD', label: 'mailing city', name: 'mailingCity', dataType: 'TEXT', required: false },
|
|
245
247
|
{ type: 'RELATION', label: 'country', name: 'country', relatedClass: 'commons.country', mostSignificantClass: 'countries', required: false },
|
|
248
|
+
// showEmpty demo (TASK-0231): no `parentCompany` link on EXAMPLE_COMPANY,
|
|
249
|
+
// so the relation still renders with a placeholder value.
|
|
250
|
+
{ type: 'RELATION', label: 'parent company', name: 'parentCompany', relatedClass: 'commons.company', mostSignificantClass: 'companies', required: false, showEmpty: true },
|
|
246
251
|
],
|
|
247
252
|
},
|
|
248
253
|
{
|
|
@@ -53,6 +53,166 @@ export function formatTime(value, locale) {
|
|
|
53
53
|
date.setHours(hours, minutes, 0, 0);
|
|
54
54
|
return new Intl.DateTimeFormat(locale, { hour: '2-digit', minute: '2-digit' }).format(date);
|
|
55
55
|
}
|
|
56
|
+
/**
|
|
57
|
+
* Locale-aware date input support.
|
|
58
|
+
*
|
|
59
|
+
* A native <input type="date"> always displays in the browser/OS locale, so the
|
|
60
|
+
* date fields render a plain text input instead and use these helpers to format
|
|
61
|
+
* and parse the value in the locale the host passed down — the same approach
|
|
62
|
+
* mrd-number-field takes with formatNumber / parseLocalizedNumber.
|
|
63
|
+
*/
|
|
64
|
+
/** 31 December 2026, 13:45 — day > 12 so day/month order is unambiguous. */
|
|
65
|
+
const PATTERN_SAMPLE = new Date(2026, 11, 31, 13, 45, 0);
|
|
66
|
+
function dateTimeParts(locale, withTime) {
|
|
67
|
+
const options = { year: 'numeric', month: '2-digit', day: '2-digit' };
|
|
68
|
+
if (withTime) {
|
|
69
|
+
options.hour = '2-digit';
|
|
70
|
+
options.minute = '2-digit';
|
|
71
|
+
}
|
|
72
|
+
return new Intl.DateTimeFormat(locale, options).formatToParts(PATTERN_SAMPLE);
|
|
73
|
+
}
|
|
74
|
+
/** Order in which day/month/year appear for a locale, e.g. ['day','month','year'] for nl-NL. */
|
|
75
|
+
function datePartOrder(locale) {
|
|
76
|
+
const order = dateTimeParts(locale, false)
|
|
77
|
+
.filter(p => p.type === 'day' || p.type === 'month' || p.type === 'year')
|
|
78
|
+
.map(p => p.type);
|
|
79
|
+
return order.length === 3 ? order : ['day', 'month', 'year'];
|
|
80
|
+
}
|
|
81
|
+
/** The locale's am/pm markers, so 12-hour input can be parsed back. */
|
|
82
|
+
function dayPeriodMarkers(locale) {
|
|
83
|
+
const marker = (hour) => {
|
|
84
|
+
var _a, _b;
|
|
85
|
+
return (_b = (_a = new Intl.DateTimeFormat(locale, { hour: 'numeric', hour12: true })
|
|
86
|
+
.formatToParts(new Date(2026, 0, 1, hour))
|
|
87
|
+
.find(p => p.type === 'dayPeriod')) === null || _a === void 0 ? void 0 : _a.value) !== null && _b !== void 0 ? _b : '';
|
|
88
|
+
};
|
|
89
|
+
return { am: marker(9), pm: marker(21) };
|
|
90
|
+
}
|
|
91
|
+
/** Arabic-Indic and extended Arabic-Indic digits → ASCII, so typed input parses everywhere. */
|
|
92
|
+
function toLatinDigits(str) {
|
|
93
|
+
return str.replace(/[٠-٩۰-۹]/g, d => {
|
|
94
|
+
const code = d.charCodeAt(0);
|
|
95
|
+
return String(code >= 0x06f0 ? code - 0x06f0 : code - 0x0660);
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
function pad(n, size = 2) {
|
|
99
|
+
return String(n).padStart(size, '0');
|
|
100
|
+
}
|
|
101
|
+
/** Two-digit years: < 70 → 20xx, otherwise 19xx. */
|
|
102
|
+
function expandYear(value, digits) {
|
|
103
|
+
if (digits > 2)
|
|
104
|
+
return value;
|
|
105
|
+
return value < 70 ? 2000 + value : 1900 + value;
|
|
106
|
+
}
|
|
107
|
+
function isValidYmd(year, month, day) {
|
|
108
|
+
if (year < 1 || month < 1 || month > 12 || day < 1)
|
|
109
|
+
return false;
|
|
110
|
+
return day <= new Date(year, month, 0).getDate();
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Placeholder showing the locale's date order, e.g. 'dd-mm-jjjj' (nl-NL) or
|
|
114
|
+
* 'mm/dd/yyyy' (en-US). Pass `hour`/`minute` tokens to include the time part.
|
|
115
|
+
*/
|
|
116
|
+
export function localeDatePattern(locale, tokens) {
|
|
117
|
+
const withTime = !!(tokens.hour && tokens.minute);
|
|
118
|
+
return dateTimeParts(locale, withTime)
|
|
119
|
+
.map(part => {
|
|
120
|
+
var _a, _b;
|
|
121
|
+
switch (part.type) {
|
|
122
|
+
case 'day': return tokens.day;
|
|
123
|
+
case 'month': return tokens.month;
|
|
124
|
+
case 'year': return tokens.year;
|
|
125
|
+
case 'hour': return (_a = tokens.hour) !== null && _a !== void 0 ? _a : '';
|
|
126
|
+
case 'minute': return (_b = tokens.minute) !== null && _b !== void 0 ? _b : '';
|
|
127
|
+
case 'dayPeriod': return 'AM';
|
|
128
|
+
case 'literal': return part.value;
|
|
129
|
+
default: return '';
|
|
130
|
+
}
|
|
131
|
+
})
|
|
132
|
+
.join('')
|
|
133
|
+
.trim();
|
|
134
|
+
}
|
|
135
|
+
/** Pulls day/month/year out of the number groups a user typed, per locale order. */
|
|
136
|
+
function ymdFromGroups(groups, locale) {
|
|
137
|
+
const order = datePartOrder(locale);
|
|
138
|
+
const values = {};
|
|
139
|
+
order.forEach((type, i) => (values[type] = groups[i]));
|
|
140
|
+
const year = expandYear(Number(values.year), values.year.length);
|
|
141
|
+
const month = Number(values.month);
|
|
142
|
+
const day = Number(values.day);
|
|
143
|
+
return isValidYmd(year, month, day) ? { year, month, day } : null;
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Parses a date the user typed in their own locale format → 'YYYY-MM-DD'.
|
|
147
|
+
* Accepts any separator, one- or two-digit day/month, two-digit years, and
|
|
148
|
+
* plain ISO input (pasted values). Returns null when it is not a valid date.
|
|
149
|
+
*/
|
|
150
|
+
export function parseLocalizedDate(input, locale) {
|
|
151
|
+
const raw = toLatinDigits(String(input !== null && input !== void 0 ? input : '')).trim();
|
|
152
|
+
if (!raw)
|
|
153
|
+
return null;
|
|
154
|
+
const groups = raw.match(/\d+/g);
|
|
155
|
+
if (!groups || groups.length < 3)
|
|
156
|
+
return null;
|
|
157
|
+
const iso = /^(\d{4})-(\d{1,2})-(\d{1,2})$/.exec(raw);
|
|
158
|
+
const parsed = iso
|
|
159
|
+
? (isValidYmd(+iso[1], +iso[2], +iso[3]) ? { year: +iso[1], month: +iso[2], day: +iso[3] } : null)
|
|
160
|
+
: ymdFromGroups(groups.slice(0, 3), locale);
|
|
161
|
+
if (!parsed)
|
|
162
|
+
return null;
|
|
163
|
+
return `${pad(parsed.year, 4)}-${pad(parsed.month)}-${pad(parsed.day)}`;
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Parses a date+time the user typed in their own locale format →
|
|
167
|
+
* 'YYYY-MM-DDTHH:mm' in local time (the shape mrd-datetime-field converts to
|
|
168
|
+
* UTC before emitting). A missing time defaults to 00:00; am/pm is recognised
|
|
169
|
+
* both in English and in the locale's own markers.
|
|
170
|
+
*/
|
|
171
|
+
export function parseLocalizedDateTime(input, locale) {
|
|
172
|
+
const raw = toLatinDigits(String(input !== null && input !== void 0 ? input : '')).trim();
|
|
173
|
+
if (!raw)
|
|
174
|
+
return null;
|
|
175
|
+
const groups = raw.match(/\d+/g);
|
|
176
|
+
if (!groups || groups.length < 3)
|
|
177
|
+
return null;
|
|
178
|
+
const iso = /^(\d{4})-(\d{1,2})-(\d{1,2})(?:[T ]|$)/.exec(raw);
|
|
179
|
+
const date = iso
|
|
180
|
+
? (isValidYmd(+iso[1], +iso[2], +iso[3]) ? { year: +iso[1], month: +iso[2], day: +iso[3] } : null)
|
|
181
|
+
: ymdFromGroups(groups.slice(0, 3), locale);
|
|
182
|
+
if (!date)
|
|
183
|
+
return null;
|
|
184
|
+
let hour = groups.length > 3 ? Number(groups[3]) : 0;
|
|
185
|
+
const minute = groups.length > 4 ? Number(groups[4]) : 0;
|
|
186
|
+
const lower = raw.toLowerCase();
|
|
187
|
+
const { am, pm } = dayPeriodMarkers(locale);
|
|
188
|
+
const isPm = /p\.?\s?m\.?/.test(lower) || (!!pm && lower.includes(pm.toLowerCase()));
|
|
189
|
+
const isAm = /a\.?\s?m\.?/.test(lower) || (!!am && lower.includes(am.toLowerCase()));
|
|
190
|
+
if (isPm && hour < 12)
|
|
191
|
+
hour += 12;
|
|
192
|
+
if (isAm && hour === 12)
|
|
193
|
+
hour = 0;
|
|
194
|
+
if (hour > 23 || minute > 59)
|
|
195
|
+
return null;
|
|
196
|
+
return `${pad(date.year, 4)}-${pad(date.month)}-${pad(date.day)}T${pad(hour)}:${pad(minute)}`;
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* 'YYYY-MM-DD' → localized display string. Parsed as a *local* date, unlike
|
|
200
|
+
* formatDate(), which would read a date-only string as UTC midnight and shift
|
|
201
|
+
* the day in negative-offset timezones.
|
|
202
|
+
*/
|
|
203
|
+
export function formatIsoDate(iso, locale) {
|
|
204
|
+
const m = /^(\d{4})-(\d{2})-(\d{2})/.exec(String(iso !== null && iso !== void 0 ? iso : ''));
|
|
205
|
+
if (!m)
|
|
206
|
+
return '';
|
|
207
|
+
return formatDate(new Date(+m[1], +m[2] - 1, +m[3]), locale);
|
|
208
|
+
}
|
|
209
|
+
/** 'YYYY-MM-DDTHH:mm' in local time → localized display string. */
|
|
210
|
+
export function formatLocalDateTime(local, locale) {
|
|
211
|
+
const m = /^(\d{4})-(\d{2})-(\d{2})[T ](\d{2}):(\d{2})/.exec(String(local !== null && local !== void 0 ? local : ''));
|
|
212
|
+
if (!m)
|
|
213
|
+
return '';
|
|
214
|
+
return formatDateTime(new Date(+m[1], +m[2] - 1, +m[3], +m[4], +m[5]), locale);
|
|
215
|
+
}
|
|
56
216
|
export function parseLocalizedNumber(str, locale) {
|
|
57
217
|
var _a, _b, _c, _d;
|
|
58
218
|
if (!str || !str.trim())
|